|
|
@@ -22,6 +22,7 @@ import com.usky.vpp.service.vo.DrMonitorCurveVO;
|
|
|
import com.usky.vpp.service.vo.DrMonitorRecordVO;
|
|
|
import com.usky.vpp.service.vo.DrMonitorSummaryVO;
|
|
|
import com.usky.vpp.service.vo.SiteBaselineVO;
|
|
|
+import com.usky.vpp.service.vo.SiteDeclaredCapacityVO;
|
|
|
import com.usky.vpp.util.VppAuditHelper;
|
|
|
import com.usky.vpp.util.VppPageHelper;
|
|
|
import org.springframework.beans.BeanUtils;
|
|
|
@@ -38,13 +39,10 @@ import java.time.LocalTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.time.format.DateTimeParseException;
|
|
|
import java.util.ArrayList;
|
|
|
-import java.util.Arrays;
|
|
|
import java.util.Collections;
|
|
|
import java.util.Comparator;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.HashSet;
|
|
|
-import java.util.LinkedHashMap;
|
|
|
-import java.util.LinkedHashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
@@ -224,143 +222,83 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public DrMonitorCurveVO getCurve(Long eventId, List<Long> siteIds, Integer baselineDays) {
|
|
|
- if (eventId == null) {
|
|
|
- throw new BusinessException("事件ID不能为空");
|
|
|
+ public DrMonitorCurveVO getCurve(Long siteId, Integer baselineDays) {
|
|
|
+ if (siteId == null) {
|
|
|
+ throw new BusinessException("站点ID不能为空");
|
|
|
}
|
|
|
- requireSiteIds(siteIds);
|
|
|
if (baselineDays != null && baselineDays != 3 && baselineDays != 5) {
|
|
|
- throw new BusinessException("baselineDays 仅支持 3 或 5");
|
|
|
+ throw new BusinessException("基线负荷仅支持3天或5天维度查询!");
|
|
|
}
|
|
|
Integer tenantId = SecurityUtils.getTenantId();
|
|
|
- VppDrEvent event = eventMapper.selectById(eventId);
|
|
|
- if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
|
|
|
- throw new BusinessException("事件不存在");
|
|
|
- }
|
|
|
- if (tenantId != null && event.getTenantId() != null && !tenantId.equals(event.getTenantId())) {
|
|
|
- throw new BusinessException("事件不存在");
|
|
|
- }
|
|
|
- if (event.getStartTime() == null) {
|
|
|
- throw new BusinessException("事件缺少开始时间");
|
|
|
- }
|
|
|
-
|
|
|
- List<VppResourcePoint> peakResources = listPeakResources(siteIds, tenantId);
|
|
|
+ List<VppResourcePoint> peakResources = listPeakResources(siteId, tenantId);
|
|
|
if (peakResources.isEmpty()) {
|
|
|
- throw new BusinessException("所选站点下无可参与调峰的资源点");
|
|
|
+ throw new BusinessException("所选站点下无可参与调峰的资源点!");
|
|
|
}
|
|
|
int resourceCount = peakResources.size();
|
|
|
- Map<Long, Long> siteResourceCount = new HashMap<>();
|
|
|
- for (VppResourcePoint resource : peakResources) {
|
|
|
- siteResourceCount.merge(resource.getSiteId(), 1L, Long::sum);
|
|
|
- }
|
|
|
- Set<Long> peakSiteIds = siteResourceCount.keySet();
|
|
|
|
|
|
- List<VppDrInvitation> invitations = listInvitations(
|
|
|
- Collections.singleton(event.getId()), peakSiteIds, tenantId);
|
|
|
- BigDecimal declaredSum = BigDecimal.ZERO;
|
|
|
- BigDecimal actualSum = BigDecimal.ZERO;
|
|
|
- for (VppDrInvitation invitation : invitations) {
|
|
|
- declaredSum = declaredSum.add(nz(invitation.getDeclaredCapacityKw()));
|
|
|
- actualSum = actualSum.add(nz(invitation.getActualResponseCapacityKw()));
|
|
|
- }
|
|
|
- BigDecimal declaredAvg = avg(declaredSum, resourceCount);
|
|
|
- BigDecimal actualAvg = avg(actualSum, resourceCount);
|
|
|
-
|
|
|
- LocalDateTime windowStart = event.getStartTime().minusHours(2);
|
|
|
- LocalDateTime windowEnd = (event.getEndTime() != null ? event.getEndTime() : event.getStartTime()).plusHours(1);
|
|
|
- String responseStartTime = event.getStartTime().format(DATE_TIME_FMT);
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
+ LocalDateTime windowStart = today.atStartOfDay();
|
|
|
+ LocalDateTime windowEnd = today.atTime(LocalTime.MAX);
|
|
|
+ String responseStartTime = windowStart.format(DATE_TIME_FMT);
|
|
|
+ String responseEndTime = windowEnd.format(DATE_TIME_FMT);
|
|
|
String actualEndTime = windowEnd.format(DATE_TIME_FMT);
|
|
|
+ int requiredDays = (baselineDays != null && baselineDays == 3) ? 3 : 5;
|
|
|
|
|
|
- Map<String, BigDecimal> baseline5Weighted = new LinkedHashMap<>();
|
|
|
- Map<String, BigDecimal> baseline3Weighted = new LinkedHashMap<>();
|
|
|
- Map<String, BigDecimal> loadWeighted = new LinkedHashMap<>();
|
|
|
- Set<String> dataSources = new LinkedHashSet<>();
|
|
|
- List<Long> usedSiteIds = new ArrayList<>();
|
|
|
-
|
|
|
- for (Map.Entry<Long, Long> entry : siteResourceCount.entrySet()) {
|
|
|
- Long siteId = entry.getKey();
|
|
|
- BigDecimal weight = BigDecimal.valueOf(entry.getValue());
|
|
|
- SiteBaselineVO baseline5 = baselineService.getSiteBaseline(siteId, responseStartTime, 5, actualEndTime);
|
|
|
- SiteBaselineVO baseline3 = baselineService.getSiteBaseline(siteId, responseStartTime, 3, actualEndTime);
|
|
|
- if ((baseline5 == null || CollectionUtils.isEmpty(baseline5.getPoints()))
|
|
|
- && (baseline3 == null || CollectionUtils.isEmpty(baseline3.getPoints()))) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- usedSiteIds.add(siteId);
|
|
|
- if (baseline5 != null && StringUtils.hasText(baseline5.getDataSource())) {
|
|
|
- dataSources.add(baseline5.getDataSource());
|
|
|
- }
|
|
|
- if (baseline5 != null && !CollectionUtils.isEmpty(baseline5.getPoints())) {
|
|
|
- for (BaselinePointVO point : baseline5.getPoints()) {
|
|
|
- if (!StringUtils.hasText(point.getTime()) || !inCurveWindow(point.getTime(), windowStart, windowEnd)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- baseline5Weighted.merge(point.getTime(),
|
|
|
- nz(point.getPredictedBaselineKw()).multiply(weight), BigDecimal::add);
|
|
|
- loadWeighted.merge(point.getTime(),
|
|
|
- nz(point.getTodayActualKw()).multiply(weight), BigDecimal::add);
|
|
|
- }
|
|
|
- }
|
|
|
- if (baseline3 != null && !CollectionUtils.isEmpty(baseline3.getPoints())) {
|
|
|
- for (BaselinePointVO point : baseline3.getPoints()) {
|
|
|
- if (!StringUtils.hasText(point.getTime()) || !inCurveWindow(point.getTime(), windowStart, windowEnd)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- baseline3Weighted.merge(point.getTime(),
|
|
|
- nz(point.getPredictedBaselineKw()).multiply(weight), BigDecimal::add);
|
|
|
- if (!loadWeighted.containsKey(point.getTime())) {
|
|
|
- loadWeighted.merge(point.getTime(),
|
|
|
- nz(point.getTodayActualKw()).multiply(weight), BigDecimal::add);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ BigDecimal actualAvg = avg(resolveSiteActualResponseKw(
|
|
|
+ siteId, responseStartTime, responseEndTime, BigDecimal.ZERO), resourceCount);
|
|
|
+
|
|
|
+ SiteBaselineVO baseline = baselineService.getSiteBaseline(
|
|
|
+ siteId, responseStartTime, requiredDays, actualEndTime);
|
|
|
|
|
|
- BigDecimal divisor = BigDecimal.valueOf(resourceCount);
|
|
|
DrMonitorCurveVO curve = new DrMonitorCurveVO();
|
|
|
- curve.setId(event.getId());
|
|
|
- curve.setEventId(event.getEventId());
|
|
|
- curve.setResponseDate(event.getStartTime().toLocalDate().format(DATE_FMT));
|
|
|
- curve.setSiteIds(usedSiteIds);
|
|
|
- curve.setDeclaredCapacityKw(declaredAvg);
|
|
|
+ curve.setResponseDate(today.format(DATE_FMT));
|
|
|
+ curve.setSiteId(siteId);
|
|
|
curve.setActualResponseCapacityKw(actualAvg);
|
|
|
- if (dataSources.isEmpty()) {
|
|
|
- curve.setDataSource("mock");
|
|
|
- } else if (dataSources.size() == 1) {
|
|
|
- curve.setDataSource(dataSources.iterator().next());
|
|
|
- } else {
|
|
|
- curve.setDataSource("mixed");
|
|
|
- }
|
|
|
-
|
|
|
- Set<String> timeSet = new LinkedHashSet<>();
|
|
|
- timeSet.addAll(baseline5Weighted.keySet());
|
|
|
- timeSet.addAll(baseline3Weighted.keySet());
|
|
|
- timeSet.addAll(loadWeighted.keySet());
|
|
|
- List<String> times = new ArrayList<>(timeSet);
|
|
|
- times.sort(Comparator.naturalOrder());
|
|
|
- for (String time : times) {
|
|
|
- DrMonitorCurveVO.Point point = new DrMonitorCurveVO.Point();
|
|
|
- point.setTime(time);
|
|
|
- BigDecimal b5 = baseline5Weighted.getOrDefault(time, BigDecimal.ZERO)
|
|
|
- .divide(divisor, SCALE, RoundingMode.HALF_UP);
|
|
|
- BigDecimal b3 = baseline3Weighted.getOrDefault(time, BigDecimal.ZERO)
|
|
|
- .divide(divisor, SCALE, RoundingMode.HALF_UP);
|
|
|
- point.setBaseline5DayKw(b5);
|
|
|
- point.setBaseline3DayKw(b3);
|
|
|
- if (baselineDays != null && baselineDays == 3) {
|
|
|
- point.setBaselineKw(b3);
|
|
|
- } else {
|
|
|
- point.setBaselineKw(b5);
|
|
|
+ if (baseline != null && StringUtils.hasText(baseline.getDataSource())) {
|
|
|
+ curve.setDataSource(baseline.getDataSource());
|
|
|
+ }
|
|
|
+ // if (dataSources.isEmpty()) {
|
|
|
+ // curve.setDataSource("mock");
|
|
|
+ // }
|
|
|
+
|
|
|
+ if (baseline == null || CollectionUtils.isEmpty(baseline.getPoints())) {
|
|
|
+ return curve;
|
|
|
+ }
|
|
|
+ for (BaselinePointVO point : baseline.getPoints()) {
|
|
|
+ if (!StringUtils.hasText(point.getTime()) || !inCurveWindow(point.getTime(), windowStart, windowEnd)) {
|
|
|
+ continue;
|
|
|
}
|
|
|
- point.setLoadKw(loadWeighted.getOrDefault(time, BigDecimal.ZERO)
|
|
|
- .divide(divisor, SCALE, RoundingMode.HALF_UP));
|
|
|
- point.setDeclaredCapacityKw(declaredAvg);
|
|
|
- point.setActualResponseCapacityKw(actualAvg);
|
|
|
- curve.getPoints().add(point);
|
|
|
+ DrMonitorCurveVO.Point curvePoint = new DrMonitorCurveVO.Point();
|
|
|
+ curvePoint.setTime(point.getTime());
|
|
|
+ curvePoint.setBaselineKw(nz(point.getPredictedBaselineKw()).setScale(SCALE, RoundingMode.HALF_UP));
|
|
|
+ curvePoint.setActualResponseCapacityKw(actualAvg);
|
|
|
+ curve.getPoints().add(curvePoint);
|
|
|
}
|
|
|
return curve;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 实际响应容量复用申报容量链路 {@link VppBaselineService#getSiteDeclaredCapacity},
|
|
|
+ * 取预估响应容量;无数据时回退邀约实际响应容量。
|
|
|
+ */
|
|
|
+ private BigDecimal resolveSiteActualResponseKw(Long siteId, String responseStartTime,
|
|
|
+ String responseEndTime, BigDecimal fallbackKw) {
|
|
|
+ if (siteId == null || !StringUtils.hasText(responseStartTime) || !StringUtils.hasText(responseEndTime)) {
|
|
|
+ return nz(fallbackKw);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ SiteDeclaredCapacityVO capacity = baselineService.getSiteDeclaredCapacity(
|
|
|
+ siteId, responseStartTime, responseEndTime);
|
|
|
+ if (capacity != null && capacity.getEstimatedResponseCapacityKw() != null
|
|
|
+ && capacity.getEstimatedResponseCapacityKw().compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ return capacity.getEstimatedResponseCapacityKw();
|
|
|
+ }
|
|
|
+ } catch (Exception ignored) {
|
|
|
+ // 回退邀约实际响应容量,保持曲线可返回
|
|
|
+ }
|
|
|
+ return nz(fallbackKw);
|
|
|
+ }
|
|
|
+
|
|
|
private boolean inCurveWindow(String timeKey, LocalDateTime windowStart, LocalDateTime windowEnd) {
|
|
|
try {
|
|
|
LocalTime t = LocalTime.parse(timeKey.length() == 5 ? timeKey : timeKey.substring(0, 5));
|
|
|
@@ -405,6 +343,14 @@ public class VppDrMonitorServiceImpl implements VppDrMonitorService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private List<VppResourcePoint> listPeakResources(Long siteId, Integer tenantId) {
|
|
|
+ return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
|
|
|
+ .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|
|
|
+ .eq(VppResourcePoint::getIsSupportPeak, SUPPORT_PEAK)
|
|
|
+ .eq(VppResourcePoint::getSiteId, siteId)
|
|
|
+ .eq(tenantId != null, VppResourcePoint::getTenantId, tenantId));
|
|
|
+ }
|
|
|
+
|
|
|
private List<VppResourcePoint> listPeakResources(List<Long> siteIds, Integer tenantId) {
|
|
|
return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
|
|
|
.eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
|