|
|
@@ -0,0 +1,196 @@
|
|
|
+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<LocalDateTime> buildDayQuarterHourSlots(LocalDate forecastDate) {
|
|
|
+ List<LocalDateTime> 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<Map<String, Object>> buildForecastCurveValues(VppTsdbQueryService tsdbQueryService,
|
|
|
+ List<String> deviceUuids,
|
|
|
+ List<LocalDate> referenceDates,
|
|
|
+ LocalDate forecastDate,
|
|
|
+ VppUnRegisterMetricDefinitions.RegisterMetric registerMetric) {
|
|
|
+ List<LocalDateTime> 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<String> tsdbMetrics = VppUnMomentDataHelper.resolveTsdbMetrics(registerMetric);
|
|
|
+ if (tsdbMetrics.isEmpty()) {
|
|
|
+ return buildZeroCurve(slots);
|
|
|
+ }
|
|
|
+ List<String> 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<Map<String, Object>> buildAverageForecastCurve(
|
|
|
+ VppTsdbQueryService tsdbQueryService,
|
|
|
+ List<String> deviceUuids,
|
|
|
+ List<LocalDate> referenceDates,
|
|
|
+ List<LocalDateTime> slots,
|
|
|
+ List<String> tsdbMetrics) {
|
|
|
+ Map<String, BigDecimal> avgByTime = tsdbQueryService.queryBaselineAvgByTimePoint(
|
|
|
+ deviceUuids,
|
|
|
+ referenceDates,
|
|
|
+ LocalTime.MIN,
|
|
|
+ LocalTime.of(23, 59, 59),
|
|
|
+ tsdbMetrics,
|
|
|
+ SLOT_MINUTES,
|
|
|
+ null);
|
|
|
+ List<Map<String, Object>> 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<Map<String, Object>> buildAccumulativeForecastCurve(
|
|
|
+ VppTsdbQueryService tsdbQueryService,
|
|
|
+ List<String> deviceUuids,
|
|
|
+ List<LocalDate> referenceDates,
|
|
|
+ List<LocalDateTime> slots,
|
|
|
+ List<String> tsdbMetrics) {
|
|
|
+ Map<String, List<BigDecimal>> samplesByTimeKey = new TreeMap<>();
|
|
|
+ for (LocalDate referenceDate : referenceDates) {
|
|
|
+ LocalDateTime dayStart = referenceDate.atStartOfDay();
|
|
|
+ LocalDateTime dayEnd = referenceDate.plusDays(1).atStartOfDay();
|
|
|
+ Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> 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<String, TreeMap<LocalDateTime, BigDecimal>> metricMap = history.get(deviceUuid);
|
|
|
+ if (metricMap == null || metricMap.isEmpty()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ TreeMap<LocalDateTime, BigDecimal> 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<Map<String, Object>> values = new ArrayList<>(slots.size());
|
|
|
+ for (LocalDateTime slot : slots) {
|
|
|
+ List<BigDecimal> samples = samplesByTimeKey.get(slot.format(TIME_KEY_FMT));
|
|
|
+ values.add(VppUnMessageBuilder.buildIntervalCurveValue(
|
|
|
+ scale(average(samples)), slot, "good"));
|
|
|
+ }
|
|
|
+ return values;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<Map<String, Object>> buildZeroCurve(List<LocalDateTime> slots) {
|
|
|
+ List<Map<String, Object>> values = new ArrayList<>(slots.size());
|
|
|
+ for (LocalDateTime slot : slots) {
|
|
|
+ values.add(VppUnMessageBuilder.buildIntervalCurveValue(BigDecimal.ZERO, slot, "good"));
|
|
|
+ }
|
|
|
+ return values;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static TreeMap<LocalDateTime, BigDecimal> pickSeries(Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap,
|
|
|
+ List<String> preferredMetrics) {
|
|
|
+ if (metricMap == null || metricMap.isEmpty()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (preferredMetrics != null) {
|
|
|
+ for (String metric : preferredMetrics) {
|
|
|
+ TreeMap<LocalDateTime, BigDecimal> series = metricMap.get(metric);
|
|
|
+ if (series != null && !series.isEmpty()) {
|
|
|
+ return series;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for (TreeMap<LocalDateTime, BigDecimal> series : metricMap.values()) {
|
|
|
+ if (series != null && !series.isEmpty()) {
|
|
|
+ return series;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static BigDecimal average(List<BigDecimal> 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);
|
|
|
+ }
|
|
|
+}
|