ソースを参照

优化执行监控-负荷分析&对比分析

fuyuchuan 5 日 前
コミット
a74b2ee335

+ 101 - 19
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppResponseMonitorServiceImpl.java

@@ -26,6 +26,7 @@ import com.usky.vpp.service.vo.ResponseLoadPointVO;
 import com.usky.vpp.service.vo.ResponseLoadStatRowVO;
 import com.usky.vpp.service.vo.ResponseMonitorTreeNodeVO;
 import com.usky.vpp.util.VppAuditHelper;
+import com.usky.vpp.util.VppBaselineHelper;
 import com.usky.vpp.util.VppCapabilityEvalHelper;
 import com.usky.vpp.util.VppResponseMonitorHelper;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -33,8 +34,10 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
@@ -42,6 +45,7 @@ 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.List;
 import java.util.Map;
@@ -54,6 +58,7 @@ import java.util.stream.Collectors;
 public class VppResponseMonitorServiceImpl implements VppResponseMonitorService {
 
     private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final int EVENT_STATUS_ENDED = 3;
 
     @Autowired
     private VppSiteMapper siteMapper;
@@ -206,26 +211,86 @@ public class VppResponseMonitorServiceImpl implements VppResponseMonitorService
                                         BigDecimal declaredCapacityKw,
                                         DrContext drContext) {
         List<String> deviceUuids = resolveDeviceUuids(resources, devices);
-        if (!deviceUuids.isEmpty()) {
-            LocalDateTime dayStart = statDate.atStartOfDay();
-            LocalDateTime dayEnd = statDate.atTime(23, 59, 59);
-            LocalDate baselineDate = statDate.minusDays(VppResponseMonitorHelper.BASELINE_DAYS_AGO);
-            List<String> powerMetrics = new ArrayList<>(VppTsdbConstants.ACTIVE_POWER_METRICS);
-            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> actualData =
-                    tsdbQueryService.queryDeviceMetricHistory(deviceUuids, dayStart, dayEnd, powerMetrics);
-            if (VppCapabilityEvalHelper.hasTsdbPowerData(actualData, powerMetrics)) {
-                Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> baselineData =
-                        tsdbQueryService.queryDeviceMetricHistory(deviceUuids,
-                                baselineDate.atStartOfDay(), baselineDate.atTime(23, 59, 59), powerMetrics);
-                List<ResponseLoadPointVO> points = VppResponseMonitorHelper.buildCurveFromTsdb(
-                        actualData, baselineData, statDate, powerMetrics, declaredCapacityKw,
-                        drContext.executionByTime);
-                return new CurveBuildResult(points, "tsdb");
-            }
+        if (deviceUuids.isEmpty()) {
+            return new CurveBuildResult(
+                    VppResponseMonitorHelper.buildMockCurve(resources, devices, siteId, statDate, declaredCapacityKw),
+                    "mock");
         }
-        return new CurveBuildResult(
-                VppResponseMonitorHelper.buildMockCurve(resources, devices, siteId, statDate, declaredCapacityKw),
-                "mock");
+
+        // 确定响应时段(有事件用事件时段,无事件用全天)
+        LocalDateTime responseStart;
+        LocalDateTime responseEnd;
+        if (drContext.event != null && drContext.event.getStartTime() != null) {
+            responseStart = drContext.event.getStartTime();
+            responseEnd = drContext.event.getEndTime() != null
+                    ? drContext.event.getEndTime() : statDate.atTime(23, 59, 59);
+        } else {
+            responseStart = statDate.atStartOfDay();
+            responseEnd = statDate.atTime(23, 59, 59);
+        }
+
+        // 选取典型历史日(与基线管理一致:法定节假日 + 调休,工作日5天 / 非工作日3天)
+        Set<LocalDate> excludeDates = loadResponseHistoryDates();
+        boolean workday = VppBaselineHelper.isWorkdayResponse(statDate);
+        int requiredCount = workday
+                ? VppBaselineHelper.WEEKDAY_REFERENCE_COUNT
+                : VppBaselineHelper.NON_WEEKDAY_REFERENCE_COUNT;
+        List<LocalDate> referenceDates = VppBaselineHelper.selectReferenceDates(
+                statDate, workday, excludeDates, requiredCount);
+
+        int interval = VppResponseMonitorHelper.BUCKET_MINUTES;
+
+        // 查询原始基线(全天,多个典型历史日同时段均值)
+        Map<String, BigDecimal> originalBaseline = tsdbQueryService.queryBaselineAvgByTimePoint(
+                deviceUuids, referenceDates,
+                LocalTime.MIN, LocalTime.of(23, 59, 59),
+                VppBaselineHelper.POWER_METRICS, interval, null);
+        if (originalBaseline.isEmpty()) {
+            return new CurveBuildResult(
+                    VppResponseMonitorHelper.buildMockCurve(resources, devices, siteId, statDate, declaredCapacityKw),
+                    "mock");
+        }
+
+        // 今日实际负荷(全天)
+        Map<String, BigDecimal> todayActual = VppBaselineHelper.buildActualLoadByTimePoint(
+                tsdbQueryService.queryDeviceMetricHistory(
+                        deviceUuids, statDate.atStartOfDay(), statDate.atTime(23, 59, 59),
+                        VppBaselineHelper.POWER_METRICS),
+                interval);
+
+        // 修正系数 K:响应开始前2小时今日实测 / 同时段原始基线,限制 [0.7, 1.2]
+        LocalDateTime correctionWindowStart = responseStart.minusHours(VppBaselineHelper.CORRECTION_HOURS);
+        Map<String, BigDecimal> correctionBaseline = tsdbQueryService.queryBaselineAvgByTimePoint(
+                deviceUuids, referenceDates,
+                correctionWindowStart.toLocalTime(), responseStart.toLocalTime(),
+                VppBaselineHelper.POWER_METRICS, interval, null);
+        BigDecimal correctionFactorK = VppBaselineHelper.calculateCorrectionFactor(
+                responseStart, todayActual,
+                correctionBaseline.isEmpty() ? originalBaseline : correctionBaseline);
+
+        // 预测基线 = 原始基线 × 修正系数 K
+        Map<String, BigDecimal> predictedBaseline = new TreeMap<>();
+        for (Map.Entry<String, BigDecimal> entry : originalBaseline.entrySet()) {
+            predictedBaseline.put(entry.getKey(),
+                    entry.getValue().multiply(correctionFactorK).setScale(2, RoundingMode.HALF_UP));
+        }
+
+        // TSDB 申报容量(仅当有事件时计算)
+        BigDecimal tsdbDeclaredKw = BigDecimal.ZERO;
+        if (drContext.event != null) {
+            Map<String, BigDecimal> responseWindowBaseline = tsdbQueryService.queryBaselineAvgByTimePoint(
+                    deviceUuids, referenceDates,
+                    responseStart.toLocalTime(), responseEnd.toLocalTime(),
+                    VppBaselineHelper.POWER_METRICS, interval, null);
+            tsdbDeclaredKw = VppBaselineHelper.calculateDeclaredCapacityKw(
+                    responseWindowBaseline, responseStart, responseEnd, correctionFactorK);
+        }
+        BigDecimal finalDeclared = tsdbDeclaredKw.compareTo(BigDecimal.ZERO) > 0
+                ? tsdbDeclaredKw : declaredCapacityKw;
+
+        List<ResponseLoadPointVO> points = VppResponseMonitorHelper.buildCurveWithBaseline(
+                predictedBaseline, todayActual, statDate, finalDeclared, drContext.executionByTime, interval);
+        return new CurveBuildResult(points, "tsdb");
     }
 
     private DrContext resolveDrContext(Long siteId, LocalDate statDate, Long eventId) {
@@ -299,6 +364,23 @@ public class VppResponseMonitorServiceImpl implements VppResponseMonitorService
         return time.withMinute(minute).withSecond(0).withNano(0);
     }
 
+    /**
+     * 查询历史已结束的 DR 事件日期,用于基线测算时排除。
+     */
+    private Set<LocalDate> loadResponseHistoryDates() {
+        List<VppDrEvent> events = eventMapper.selectList(
+                new LambdaQueryWrapper<VppDrEvent>()
+                        .eq(VppDrEvent::getEventStatus, EVENT_STATUS_ENDED)
+                        .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED));
+        Set<LocalDate> dates = new HashSet<>();
+        for (VppDrEvent event : events) {
+            if (event.getStartTime() != null) {
+                dates.add(event.getStartTime().toLocalDate());
+            }
+        }
+        return dates;
+    }
+
     private ResponseMonitorTreeNodeVO toSiteNode(VppSite site, Long parentId) {
         ResponseMonitorTreeNodeVO node = new ResponseMonitorTreeNodeVO();
         node.setNodeType("SITE");

+ 39 - 3
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppResponseMonitorHelper.java

@@ -102,6 +102,44 @@ public final class VppResponseMonitorHelper {
         return points;
     }
 
+    /**
+     * 基于预计算的预测基线和今日实际,构建曲线点(替代直接操作 TSDB 原始时序)。
+     */
+    public static List<ResponseLoadPointVO> buildCurveWithBaseline(
+            Map<String, BigDecimal> predictedBaseline,
+            Map<String, BigDecimal> todayActual,
+            LocalDate date,
+            BigDecimal declaredCapacityKw,
+            Map<LocalDateTime, BigDecimal> executionActualByTime,
+            int intervalMinutes) {
+        List<ResponseLoadPointVO> points = new ArrayList<>();
+        for (int minute = 0; minute < 1440; minute += intervalMinutes) {
+            LocalTime time = LocalTime.of(minute / 60, minute % 60);
+            String timeKey = time.format(TIME_FMT);
+            LocalDateTime bucketStart = date.atTime(time);
+
+            BigDecimal baseline = predictedBaseline.getOrDefault(timeKey, BigDecimal.ZERO);
+            BigDecimal actual = todayActual.getOrDefault(timeKey, BigDecimal.ZERO);
+
+            BigDecimal responseLoad = executionActualByTime != null
+                    ? executionActualByTime.getOrDefault(bucketStart, null)
+                    : null;
+            if (responseLoad == null) {
+                responseLoad = baseline.subtract(actual).max(BigDecimal.ZERO);
+            }
+
+            ResponseLoadPointVO point = new ResponseLoadPointVO();
+            point.setTime(timeKey);
+            point.setDatetime(bucketStart.format(DATETIME_FMT));
+            point.setBaselineLoadKw(scale(baseline));
+            point.setActualLoadKw(scale(actual));
+            point.setDeclaredCapacityKw(declaredCapacityKw != null ? scale(declaredCapacityKw) : BigDecimal.ZERO);
+            point.setActualResponseLoadKw(scale(responseLoad));
+            points.add(point);
+        }
+        return points;
+    }
+
     public static List<ResponseLoadStatRowVO> buildStatistics(List<ResponseLoadPointVO> points, LocalDate date) {
         if (points == null || points.isEmpty()) {
             return Collections.emptyList();
@@ -170,9 +208,7 @@ public final class VppResponseMonitorHelper {
             if (kw == null) {
                 continue;
             }
-            if (!actual) {
-                kw = kw.multiply(BigDecimal.valueOf(1.02 + pseudo(point.getTime()) * 0.05));
-            }
+            // baselineLoadKw 已是修正后的预测基线(原始基线 × K),无需伪随机调整
             allKw.add(kw);
             String hourKey = point.getTime().substring(0, 2) + ":00";
             hourlyBuckets.computeIfAbsent(hourKey, k -> new ArrayList<>()).add(kw);