package com.usky.vpp.util; import com.usky.vpp.service.VppTsdbQueryService; 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.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * IntervalDataReport 明日预测曲线(15 分钟整点,按量测点类型均值/累加)。 */ public final class VppUnIntervalDataHelper { public static final int SLOT_MINUTES = VppUnMomentDataHelper.WINDOW_MINUTES; private static final DateTimeFormatter TIME_KEY_FMT = DateTimeFormatter.ofPattern("HH:mm"); private VppUnIntervalDataHelper() { } public static LocalDate resolveForecastDate() { return LocalDate.now().plusDays(1); } public static List buildDayQuarterHourSlots(LocalDate forecastDate) { List slots = new ArrayList<>(); if (forecastDate == null) { return slots; } LocalDateTime cursor = forecastDate.atStartOfDay(); LocalDateTime dayEnd = forecastDate.plusDays(1).atStartOfDay(); while (cursor.isBefore(dayEnd)) { slots.add(cursor); cursor = cursor.plusMinutes(SLOT_MINUTES); } return slots; } /** * 基于典型历史日预测 forecastDate 全天 96 个 15 分钟点。 */ public static List> buildForecastCurveValues(VppTsdbQueryService tsdbQueryService, List deviceUuids, List referenceDates, LocalDate forecastDate, VppUnRegisterMetricDefinitions.RegisterMetric registerMetric) { List slots = buildDayQuarterHourSlots(forecastDate); if (slots.isEmpty()) { return Collections.emptyList(); } if (tsdbQueryService == null || deviceUuids == null || deviceUuids.isEmpty() || referenceDates == null || referenceDates.isEmpty() || registerMetric == null) { return buildZeroCurve(slots); } List tsdbMetrics = VppUnMomentDataHelper.resolveTsdbMetrics(registerMetric); if (tsdbMetrics.isEmpty()) { return buildZeroCurve(slots); } List distinctUuids = new ArrayList<>(new LinkedHashSet<>(deviceUuids)); if (VppUnMomentDataHelper.resolveAggregationMode(registerMetric) == VppUnMomentDataHelper.AggregationMode.AVERAGE) { return buildAverageForecastCurve(tsdbQueryService, distinctUuids, referenceDates, slots, tsdbMetrics); } return buildAccumulativeForecastCurve(tsdbQueryService, distinctUuids, referenceDates, slots, tsdbMetrics); } private static List> buildAverageForecastCurve( VppTsdbQueryService tsdbQueryService, List deviceUuids, List referenceDates, List slots, List tsdbMetrics) { Map avgByTime = tsdbQueryService.queryBaselineAvgByTimePoint( deviceUuids, referenceDates, LocalTime.MIN, LocalTime.of(23, 59, 59), tsdbMetrics, SLOT_MINUTES, null); List> values = new ArrayList<>(slots.size()); for (LocalDateTime slot : slots) { String timeKey = slot.format(TIME_KEY_FMT); BigDecimal value = avgByTime != null ? avgByTime.get(timeKey) : null; values.add(VppUnMessageBuilder.buildIntervalCurveValue(scale(value), slot, "good")); } return values; } private static List> buildAccumulativeForecastCurve( VppTsdbQueryService tsdbQueryService, List deviceUuids, List referenceDates, List slots, List tsdbMetrics) { Map> samplesByTimeKey = new TreeMap<>(); for (LocalDate referenceDate : referenceDates) { LocalDateTime dayStart = referenceDate.atStartOfDay(); LocalDateTime dayEnd = referenceDate.plusDays(1).atStartOfDay(); Map>> history = tsdbQueryService.queryDeviceMetricHistory(deviceUuids, dayStart, dayEnd, tsdbMetrics); if (history == null || history.isEmpty()) { continue; } for (LocalDateTime slot : slots) { LocalDateTime slotStart = LocalDateTime.of(referenceDate, slot.toLocalTime()); LocalDateTime slotEnd = slotStart.plusMinutes(SLOT_MINUTES); LocalDateTime endExclusive = slotEnd.plusSeconds(1); BigDecimal slotTotal = BigDecimal.ZERO; for (String deviceUuid : deviceUuids) { Map> metricMap = history.get(deviceUuid); if (metricMap == null || metricMap.isEmpty()) { continue; } TreeMap series = pickSeries(metricMap, tsdbMetrics); BigDecimal usage = VppEnergyUsageHelper.calcUsage(series, slotStart, endExclusive); if (usage != null) { slotTotal = slotTotal.add(usage); } } samplesByTimeKey.computeIfAbsent(slot.format(TIME_KEY_FMT), key -> new ArrayList<>()) .add(slotTotal); } } List> values = new ArrayList<>(slots.size()); for (LocalDateTime slot : slots) { List samples = samplesByTimeKey.get(slot.format(TIME_KEY_FMT)); values.add(VppUnMessageBuilder.buildIntervalCurveValue( scale(average(samples)), slot, "good")); } return values; } private static List> buildZeroCurve(List slots) { List> values = new ArrayList<>(slots.size()); for (LocalDateTime slot : slots) { values.add(VppUnMessageBuilder.buildIntervalCurveValue(BigDecimal.ZERO, slot, "good")); } return values; } private static TreeMap pickSeries(Map> metricMap, List preferredMetrics) { if (metricMap == null || metricMap.isEmpty()) { return null; } if (preferredMetrics != null) { for (String metric : preferredMetrics) { TreeMap series = metricMap.get(metric); if (series != null && !series.isEmpty()) { return series; } } } for (TreeMap series : metricMap.values()) { if (series != null && !series.isEmpty()) { return series; } } return null; } private static BigDecimal average(List values) { if (values == null || values.isEmpty()) { return BigDecimal.ZERO; } BigDecimal sum = BigDecimal.ZERO; int count = 0; for (BigDecimal value : values) { if (value != null) { sum = sum.add(value); count++; } } if (count == 0) { return BigDecimal.ZERO; } return sum.divide(BigDecimal.valueOf(count), 6, RoundingMode.HALF_UP); } private static BigDecimal scale(BigDecimal value) { if (value == null) { return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); } return value.setScale(2, RoundingMode.HALF_UP); } }