|
|
@@ -5,23 +5,27 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.usky.common.core.bean.CommonPage;
|
|
|
import com.usky.common.core.exception.BusinessException;
|
|
|
import com.usky.common.security.utils.SecurityUtils;
|
|
|
+import com.usky.vpp.domain.VppBiddingConfig;
|
|
|
import com.usky.vpp.domain.VppCustomer;
|
|
|
import com.usky.vpp.domain.VppDrEvent;
|
|
|
import com.usky.vpp.domain.VppDrInvitation;
|
|
|
import com.usky.vpp.domain.VppDrSubsidyPrediction;
|
|
|
import com.usky.vpp.domain.VppResponseDeviationPrice;
|
|
|
import com.usky.vpp.domain.VppSite;
|
|
|
-import com.usky.vpp.mapper.*;
|
|
|
import com.usky.vpp.mapper.VppCustomerMapper;
|
|
|
-import com.usky.vpp.mapper.VppResponseDeviationPriceMapper;
|
|
|
+import com.usky.vpp.mapper.VppDrEventMapper;
|
|
|
+import com.usky.vpp.mapper.VppDrInvitationMapper;
|
|
|
+import com.usky.vpp.mapper.VppDrSubsidyPredictionMapper;
|
|
|
+import com.usky.vpp.mapper.VppSiteMapper;
|
|
|
import com.usky.vpp.service.VppBaselineService;
|
|
|
import com.usky.vpp.service.VppDrSubsidyPredictionService;
|
|
|
+import com.usky.vpp.service.VppSubsidyPredictionCalculator;
|
|
|
import com.usky.vpp.service.vo.DrSubsidyPredictionCalculateRequest;
|
|
|
import com.usky.vpp.service.vo.DrSubsidyPredictionRequest;
|
|
|
import com.usky.vpp.service.vo.DrSubsidyPredictionVO;
|
|
|
import com.usky.vpp.service.vo.SiteDeclaredCapacityVO;
|
|
|
+import com.usky.vpp.service.vo.SubsidyPredictionCalcResult;
|
|
|
import com.usky.vpp.util.VppAuditHelper;
|
|
|
-import com.usky.vpp.util.VppResponseCoefficientHelper;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
@@ -32,7 +36,6 @@ import org.springframework.util.StringUtils;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
-import java.time.Duration;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.Collections;
|
|
|
@@ -49,7 +52,6 @@ import java.util.stream.Collectors;
|
|
|
public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPredictionService {
|
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(VppDrSubsidyPredictionServiceImpl.class);
|
|
|
- private static final BigDecimal MINUTES_PER_HOUR = BigDecimal.valueOf(60);
|
|
|
private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
|
@Autowired
|
|
|
@@ -63,9 +65,9 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
@Autowired
|
|
|
private VppCustomerMapper customerMapper;
|
|
|
@Autowired
|
|
|
- private VppResponseDeviationPriceMapper priceAdjustmentMapper;
|
|
|
- @Autowired
|
|
|
private VppBaselineService vppBaselineService;
|
|
|
+ @Autowired
|
|
|
+ private VppSubsidyPredictionCalculator subsidyPredictionCalculator;
|
|
|
|
|
|
@Override
|
|
|
public CommonPage<DrSubsidyPredictionVO> page(String siteName, String customerName,
|
|
|
@@ -128,14 +130,11 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
fillCustomerName(entity);
|
|
|
entity.setEventStartTime(event.getStartTime());
|
|
|
entity.setEventEndTime(event.getEndTime());
|
|
|
- entity.setSubsidyPrice(event.getSubsidyPrice());
|
|
|
|
|
|
BigDecimal declaredKw = invitation.getDeclaredCapacityKw() != null
|
|
|
? invitation.getDeclaredCapacityKw()
|
|
|
: invitation.getActualClearedCapacityKw();
|
|
|
- BigDecimal actualClearedKw = invitation.getActualClearedCapacityKw() != null
|
|
|
- ? invitation.getActualClearedCapacityKw()
|
|
|
- : declaredKw;
|
|
|
+ BigDecimal actualClearedKw = invitation.getActualClearedCapacityKw();
|
|
|
BigDecimal platformKw = resolvePlatformActualResponseKw(
|
|
|
invitation.getSiteId(), event.getStartTime(), event.getEndTime(),
|
|
|
invitation.getEstimatedResponseCapacityKw());
|
|
|
@@ -143,12 +142,11 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
entity.setActualResponseGridKw(request.getActualResponseGridKw());
|
|
|
entity.setActualResponsePlatformKw(platformKw);
|
|
|
entity.setDeclaredClearedKw(declaredKw);
|
|
|
- entity.setEstimatedCapacityKw(platformKw);
|
|
|
+ entity.setEstimatedCapacityKw(invitation.getEstimatedResponseCapacityKw());
|
|
|
entity.setClearedCapacityKw(actualClearedKw);
|
|
|
entity.setTenantId(event.getTenantId() != null ? event.getTenantId() : invitation.getTenantId());
|
|
|
|
|
|
- recalculate(entity, loadActivePriceAdjustments(
|
|
|
- entity.getTenantId() != null ? entity.getTenantId() : SecurityUtils.getTenantId()));
|
|
|
+ applyCalcResult(entity, subsidyPredictionCalculator.calculate(invitation, event));
|
|
|
|
|
|
Map<Long, String> invitationNoMap = Collections.singletonMap(invitation.getId(), invitation.getInvitationNo());
|
|
|
return toVo(entity, invitationNoMap);
|
|
|
@@ -246,7 +244,7 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
}
|
|
|
|
|
|
VppDrEvent event = eventMapper.selectById(drEventId);
|
|
|
- if (event == null) {
|
|
|
+ if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
|
|
|
log.warn("补贴预测生成跳过:事件不存在 drEventId={}", drEventId);
|
|
|
return;
|
|
|
}
|
|
|
@@ -275,11 +273,13 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
.collect(Collectors.toSet());
|
|
|
Map<Long, String> customerNameMap = loadCustomerNameMap(customerIds);
|
|
|
|
|
|
- List<VppResponseDeviationPrice> adjustments = loadActivePriceAdjustments(event.getTenantId());
|
|
|
+ List<VppResponseDeviationPrice> adjustments = subsidyPredictionCalculator.loadAdjustments(event.getTenantId());
|
|
|
+ List<VppBiddingConfig> biddingConfigs =
|
|
|
+ subsidyPredictionCalculator.loadBiddingConfigs(event.getTenantId());
|
|
|
|
|
|
for (VppDrInvitation invitation : invitations) {
|
|
|
VppDrSubsidyPrediction prediction = buildPrediction(
|
|
|
- event, invitation, siteNameMap, customerNameMap, adjustments);
|
|
|
+ event, invitation, siteNameMap, customerNameMap, adjustments, biddingConfigs);
|
|
|
|
|
|
VppDrSubsidyPrediction existing = subsidyPredictionMapper.selectOne(
|
|
|
new LambdaQueryWrapper<VppDrSubsidyPrediction>()
|
|
|
@@ -303,7 +303,8 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
private VppDrSubsidyPrediction buildPrediction(VppDrEvent event, VppDrInvitation invitation,
|
|
|
Map<Long, String> siteNameMap,
|
|
|
Map<Long, String> customerNameMap,
|
|
|
- List<VppResponseDeviationPrice> adjustments) {
|
|
|
+ List<VppResponseDeviationPrice> adjustments,
|
|
|
+ List<VppBiddingConfig> biddingConfigs) {
|
|
|
VppDrSubsidyPrediction p = new VppDrSubsidyPrediction();
|
|
|
p.setInvitationId(invitation.getId());
|
|
|
p.setDrEventId(event.getId());
|
|
|
@@ -313,7 +314,6 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
p.setCustomerName(invitation.getCustomerId() == null ? null : customerNameMap.get(invitation.getCustomerId()));
|
|
|
p.setEventStartTime(event.getStartTime());
|
|
|
p.setEventEndTime(event.getEndTime());
|
|
|
- p.setSubsidyPrice(event.getSubsidyPrice());
|
|
|
|
|
|
BigDecimal platformKw = resolvePlatformActualResponseKw(
|
|
|
invitation.getSiteId(), event.getStartTime(), event.getEndTime(),
|
|
|
@@ -322,17 +322,16 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
BigDecimal declaredKw = invitation.getDeclaredCapacityKw() != null
|
|
|
? invitation.getDeclaredCapacityKw()
|
|
|
: invitation.getActualClearedCapacityKw();
|
|
|
- BigDecimal actualClearedKw = invitation.getActualClearedCapacityKw() != null
|
|
|
- ? invitation.getActualClearedCapacityKw()
|
|
|
- : declaredKw;
|
|
|
+ BigDecimal actualClearedKw = invitation.getActualClearedCapacityKw();
|
|
|
|
|
|
p.setActualResponsePlatformKw(platformKw);
|
|
|
p.setActualResponseGridKw(gridKw);
|
|
|
p.setDeclaredClearedKw(declaredKw);
|
|
|
- p.setEstimatedCapacityKw(platformKw);
|
|
|
+ p.setEstimatedCapacityKw(invitation.getEstimatedResponseCapacityKw());
|
|
|
p.setClearedCapacityKw(actualClearedKw);
|
|
|
p.setTenantId(event.getTenantId());
|
|
|
- recalculate(p, adjustments);
|
|
|
+ applyCalcResult(p, subsidyPredictionCalculator.calculate(
|
|
|
+ invitation, event, adjustments, biddingConfigs));
|
|
|
return p;
|
|
|
}
|
|
|
|
|
|
@@ -473,29 +472,13 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
.collect(Collectors.toMap(VppCustomer::getId, VppCustomer::getCustomerName, (a, b) -> a));
|
|
|
}
|
|
|
|
|
|
- private List<VppResponseDeviationPrice> loadActivePriceAdjustments(Integer tenantId) {
|
|
|
- return priceAdjustmentMapper.selectList(
|
|
|
- new LambdaQueryWrapper<VppResponseDeviationPrice>()
|
|
|
- .eq(VppResponseDeviationPrice::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
- .eq(tenantId != null, VppResponseDeviationPrice::getTenantId, tenantId)
|
|
|
- .orderByAsc(VppResponseDeviationPrice::getId));
|
|
|
- }
|
|
|
-
|
|
|
- private BigDecimal resolvePriceAdjustmentCoefficient(BigDecimal completionRate,
|
|
|
- List<VppResponseDeviationPrice> adjustments) {
|
|
|
- if (adjustments != null) {
|
|
|
- for (VppResponseDeviationPrice adjustment : adjustments) {
|
|
|
- if (VppResponseCoefficientHelper.matches(
|
|
|
- adjustment.getCoeffLower(),
|
|
|
- adjustment.getCoeffUpper(),
|
|
|
- adjustment.getLowerInclusive(),
|
|
|
- adjustment.getUpperInclusive(),
|
|
|
- completionRate)) {
|
|
|
- return adjustment.getPriceAdjustmentCoefficient();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- throw new BusinessException("未匹配到响应量偏差价格调整配置!");
|
|
|
+ private void applyCalcResult(VppDrSubsidyPrediction entity, SubsidyPredictionCalcResult calc) {
|
|
|
+ entity.setResponseDurationMin(calc.getResponseDurationMin());
|
|
|
+ entity.setPriceAdjustmentCoefficient(calc.getPriceAdjustmentCoefficient());
|
|
|
+ entity.setCompletionRate(calc.getCompletionRate());
|
|
|
+ entity.setDeviationRate(calc.getDeviationRate());
|
|
|
+ entity.setSubsidyPrice(calc.getSubsidyPrice());
|
|
|
+ entity.setEstimatedSubsidyAmount(calc.getEstimatedSubsidyAmount());
|
|
|
}
|
|
|
|
|
|
private Map<Long, String> loadInvitationNoMap(List<VppDrSubsidyPrediction> predictions) {
|
|
|
@@ -511,58 +494,6 @@ public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPrediction
|
|
|
(left, right) -> left));
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 自动计算:响应时长、偏差率、完成率、补贴金额
|
|
|
- * <ul>
|
|
|
- * <li>完成率(%)= 实际响应容量(电网) / 实际出清 × 100(实际出清为 null 或 ≤0 时不计算)</li>
|
|
|
- * <li>偏差率(%)= (1 − 完成率) × 100</li>
|
|
|
- * <li>价格调整系数:按完成率比例匹配响应量偏差价格调整配置</li>
|
|
|
- * <li>补贴金额 = 电网实际响应量 × 响应时长(h) × 价格调整系数 × 补贴标准</li>
|
|
|
- * </ul>
|
|
|
- */
|
|
|
- private void recalculate(VppDrSubsidyPrediction entity,
|
|
|
- List<VppResponseDeviationPrice> adjustments) {
|
|
|
- int durationMin = 0;
|
|
|
- if (entity.getEventStartTime() != null && entity.getEventEndTime() != null
|
|
|
- && entity.getEventEndTime().isAfter(entity.getEventStartTime())) {
|
|
|
- durationMin = (int) Duration.between(entity.getEventStartTime(), entity.getEventEndTime()).toMinutes();
|
|
|
- }
|
|
|
- entity.setResponseDurationMin(durationMin);
|
|
|
-
|
|
|
- BigDecimal gridKw = entity.getActualResponseGridKw();
|
|
|
- BigDecimal clearedKw = entity.getClearedCapacityKw() != null
|
|
|
- ? entity.getClearedCapacityKw()
|
|
|
- : entity.getDeclaredClearedKw();
|
|
|
-
|
|
|
- if (clearedKw == null || clearedKw.compareTo(BigDecimal.ZERO) <= 0 || gridKw == null) {
|
|
|
- entity.setDeviationRate(null);
|
|
|
- entity.setCompletionRate(null);
|
|
|
- entity.setPriceAdjustmentCoefficient(null);
|
|
|
- entity.setEstimatedSubsidyAmount(null);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- BigDecimal completionRatio = gridKw.divide(clearedKw, 6, RoundingMode.HALF_UP);
|
|
|
- BigDecimal deviationRatio = BigDecimal.ONE.subtract(completionRatio);
|
|
|
- BigDecimal priceAdjustmentCoefficient = resolvePriceAdjustmentCoefficient(completionRatio, adjustments);
|
|
|
-
|
|
|
- entity.setCompletionRate(completionRatio.multiply(BigDecimal.valueOf(100)).setScale(4, RoundingMode.HALF_UP));
|
|
|
- entity.setDeviationRate(deviationRatio.multiply(BigDecimal.valueOf(100)).setScale(4, RoundingMode.HALF_UP));
|
|
|
- entity.setPriceAdjustmentCoefficient(priceAdjustmentCoefficient);
|
|
|
-
|
|
|
- // 补贴按电网核定实际响应量结算
|
|
|
- BigDecimal subsidyAmount = null;
|
|
|
- if (entity.getSubsidyPrice() != null && durationMin > 0) {
|
|
|
- BigDecimal hours = BigDecimal.valueOf(durationMin).divide(MINUTES_PER_HOUR, 6, RoundingMode.HALF_UP);
|
|
|
- subsidyAmount = gridKw
|
|
|
- .multiply(hours)
|
|
|
- .multiply(priceAdjustmentCoefficient)
|
|
|
- .multiply(entity.getSubsidyPrice())
|
|
|
- .setScale(4, RoundingMode.HALF_UP);
|
|
|
- }
|
|
|
- entity.setEstimatedSubsidyAmount(subsidyAmount);
|
|
|
- }
|
|
|
-
|
|
|
/** 分成比例统一为 0~1;若传入值 > 1 则按百分数换算 */
|
|
|
private BigDecimal normalizeShareRatio(BigDecimal raw) {
|
|
|
if (raw == null) {
|