From d2ab6f7153e604bac7bc4ad58f27f368b65d8a1e Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期二, 16 六月 2026 13:54:58 +0800
Subject: [PATCH] feat: 添加能耗数据综合分析功能,支持按天和周维度的趋势分析
---
src/main/java/com/ruoyi/http/util/StatisticEleAnalyticsUtil.java | 288 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 288 insertions(+), 0 deletions(-)
diff --git a/src/main/java/com/ruoyi/http/util/StatisticEleAnalyticsUtil.java b/src/main/java/com/ruoyi/http/util/StatisticEleAnalyticsUtil.java
new file mode 100644
index 0000000..4a469ea
--- /dev/null
+++ b/src/main/java/com/ruoyi/http/util/StatisticEleAnalyticsUtil.java
@@ -0,0 +1,288 @@
+package com.ruoyi.http.util;
+
+import com.ruoyi.http.vo.StatisticEleComparisonVo;
+import com.ruoyi.http.vo.StatisticEleRecordVo;
+import com.ruoyi.http.vo.StatisticEleSplitItemVo;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.WeekFields;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * 鑳借�楃患鍚堝垎鏋愯绠�
+ */
+public final class StatisticEleAnalyticsUtil {
+
+ private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyyMMdd");
+ private static final int SUMMARY_SCALE = 2;
+ /** 鐧界彮 08:00-19:59 */
+ private static final int DAY_SHIFT_START = 8;
+ private static final int DAY_SHIFT_END = 19;
+
+ private StatisticEleAnalyticsUtil() {
+ }
+
+ public static final Function<String, String> HOUR_TO_WEEK = tk -> {
+ if (tk == null || tk.length() < 8) {
+ return null;
+ }
+ LocalDate date = LocalDate.parse(tk.substring(0, 8), DAY_FMT);
+ WeekFields wf = WeekFields.of(Locale.CHINA);
+ int week = date.get(wf.weekOfWeekBasedYear());
+ int year = date.get(wf.weekBasedYear());
+ return String.format("%04dW%02d", year, week);
+ };
+
+ public static Function<String, String> trendBucketFn(String granularity) {
+ return switch (granularity) {
+ case "hour" -> StatisticEleAggregateUtil.HOUR_TO_HOUR;
+ case "week" -> HOUR_TO_WEEK;
+ case "month" -> StatisticEleAggregateUtil.HOUR_TO_MONTH;
+ case "year" -> StatisticEleAggregateUtil.HOUR_TO_YEAR;
+ default -> StatisticEleAggregateUtil.HOUR_TO_DAY;
+ };
+ }
+
+ /**
+ * 鍛ㄦ湡绱鏉ヨ嚜姹囨�绘《锛屾瀬鍊�/鍧囧�煎缁堟潵鑷皬鏃舵《銆�
+ */
+ public static StatisticEleAggregateUtil.StatisticEleSummaryMetrics calcCombinedMetrics(
+ List<StatisticEleRecordVo> hourRecords,
+ List<StatisticEleRecordVo> periodChartRecords) {
+ List<StatisticEleRecordVo> hourlyBuckets = StatisticEleAggregateUtil.aggregateHourToBuckets(
+ hourRecords, StatisticEleAggregateUtil.HOUR_TO_HOUR);
+ StatisticEleAggregateUtil.StatisticEleSummaryMetrics hourMetrics =
+ StatisticEleAggregateUtil.calcMetrics(hourlyBuckets);
+ StatisticEleAggregateUtil.StatisticEleSummaryMetrics periodMetrics =
+ StatisticEleAggregateUtil.calcMetrics(periodChartRecords);
+
+ StatisticEleAggregateUtil.StatisticEleSummaryMetrics result =
+ new StatisticEleAggregateUtil.StatisticEleSummaryMetrics();
+ result.setRecordCount(periodMetrics.getRecordCount());
+ result.setTotalConsumption(hourMetrics.getTotalConsumption());
+ result.setAvgConsumption(hourMetrics.getAvgConsumption());
+ result.setMaxConsumption(hourMetrics.getMaxConsumption());
+ result.setMinConsumption(hourMetrics.getMinConsumption());
+ return result;
+ }
+
+ public static double calcLoadRate(Double avgHourly, Double maxHourly) {
+ if (avgHourly == null || maxHourly == null || maxHourly <= 0) {
+ return 0.0;
+ }
+ return roundSummary(BigDecimal.valueOf(avgHourly)
+ .divide(BigDecimal.valueOf(maxHourly), SUMMARY_SCALE + 2, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100)));
+ }
+
+ public static List<StatisticEleSplitItemVo> calcPeriodSplits(List<StatisticEleRecordVo> hourRecords) {
+ double sharp = sumField(hourRecords, StatisticEleRecordVo::getSharpConsumption);
+ double peak = sumField(hourRecords, StatisticEleRecordVo::getPeakConsumption);
+ double flat = sumField(hourRecords, StatisticEleRecordVo::getFlatConsumption);
+ double valley = sumField(hourRecords, StatisticEleRecordVo::getValleyConsumption);
+ double total = sharp + peak + flat + valley;
+ if (total <= 0) {
+ total = sumField(hourRecords, StatisticEleRecordVo::getTotalConsumption);
+ }
+ List<StatisticEleSplitItemVo> items = new ArrayList<>();
+ addSplitItem(items, "灏�", sharp, total);
+ addSplitItem(items, "宄�", peak, total);
+ addSplitItem(items, "骞�", flat, total);
+ addSplitItem(items, "璋�", valley, total);
+ return items.stream().filter(i -> i.getConsumption() != null && i.getConsumption() > 0).toList();
+ }
+
+ public static List<StatisticEleSplitItemVo> calcShiftSplits(List<StatisticEleRecordVo> hourRecords) {
+ Map<String, Double> map = new LinkedHashMap<>();
+ map.put("鐧界彮", 0.0);
+ map.put("澶滅彮", 0.0);
+ for (StatisticEleRecordVo record : hourRecords) {
+ int hour = parseHour(record.getTimeKey());
+ if (hour < 0) {
+ continue;
+ }
+ double val = safe(record.getTotalConsumption());
+ if (hour >= DAY_SHIFT_START && hour <= DAY_SHIFT_END) {
+ map.merge("鐧界彮", val, Double::sum);
+ } else {
+ map.merge("澶滅彮", val, Double::sum);
+ }
+ }
+ return toSplitItems(map);
+ }
+
+ public static List<StatisticEleSplitItemVo> calcDayTypeSplits(List<StatisticEleRecordVo> hourRecords) {
+ Map<String, Double> map = new LinkedHashMap<>();
+ map.put("宸ヤ綔鏃�", 0.0);
+ map.put("浼戞伅鏃�", 0.0);
+ for (StatisticEleRecordVo record : hourRecords) {
+ LocalDate date = parseDate(record.getTimeKey());
+ if (date == null) {
+ continue;
+ }
+ double val = safe(record.getTotalConsumption());
+ DayOfWeek dow = date.getDayOfWeek();
+ if (dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY) {
+ map.merge("浼戞伅鏃�", val, Double::sum);
+ } else {
+ map.merge("宸ヤ綔鏃�", val, Double::sum);
+ }
+ }
+ return toSplitItems(map);
+ }
+
+ public static StatisticEleComparisonVo buildComparison(
+ String type, String label, Double currentTotal, Double compareTotal) {
+ StatisticEleComparisonVo vo = new StatisticEleComparisonVo();
+ vo.setType(type);
+ vo.setLabel(label);
+ vo.setCurrentTotal(round(currentTotal));
+ vo.setCompareTotal(round(compareTotal));
+ double delta = round(safe(currentTotal) - safe(compareTotal));
+ vo.setDelta(delta);
+ if (compareTotal != null && compareTotal > 0) {
+ vo.setChangeRate(roundSummary(BigDecimal.valueOf(delta)
+ .divide(BigDecimal.valueOf(compareTotal), SUMMARY_SCALE + 2, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100))));
+ } else {
+ vo.setChangeRate(currentTotal != null && currentTotal > 0 ? 100.0 : 0.0);
+ }
+ return vo;
+ }
+
+ public record DateBounds(LocalDate start, LocalDate end) {}
+
+ public static DateBounds resolveBounds(String dimension, String startTime, String endTime) {
+ LocalDate start = resolveStartDate(dimension, startTime);
+ LocalDate end = resolveEndDate(dimension, endTime);
+ if (start.isAfter(end)) {
+ LocalDate tmp = start;
+ start = end;
+ end = tmp;
+ }
+ return new DateBounds(start, end);
+ }
+
+ public static DateBounds shiftChain(DateBounds bounds) {
+ long days = ChronoUnit.DAYS.between(bounds.start(), bounds.end()) + 1;
+ return new DateBounds(bounds.start().minusDays(days), bounds.start().minusDays(1));
+ }
+
+ public static DateBounds shiftYoy(DateBounds bounds) {
+ return new DateBounds(bounds.start().minusYears(1), bounds.end().minusYears(1));
+ }
+
+ public static String defaultTrendGranularity(String dimension, boolean singleDay) {
+ if (singleDay) {
+ return "hour";
+ }
+ return switch (dimension) {
+ case "week", "day" -> "day";
+ case "month", "quarter" -> "week";
+ case "year" -> "month";
+ default -> "day";
+ };
+ }
+
+ private static LocalDate resolveStartDate(String dimension, String startTime) {
+ return switch (dimension) {
+ case "year" -> LocalDate.of(Integer.parseInt(startTime), 1, 1);
+ case "month" -> YearMonth.parse(startTime, DateTimeFormatter.ofPattern("yyyyMM")).atDay(1);
+ default -> LocalDate.parse(normalizeDayKey(startTime), DAY_FMT);
+ };
+ }
+
+ private static LocalDate resolveEndDate(String dimension, String endTime) {
+ return switch (dimension) {
+ case "year" -> LocalDate.of(Integer.parseInt(endTime), 12, 31);
+ case "month" -> YearMonth.parse(endTime, DateTimeFormatter.ofPattern("yyyyMM")).atEndOfMonth();
+ default -> LocalDate.parse(normalizeDayKey(endTime), DAY_FMT);
+ };
+ }
+
+ private static String normalizeDayKey(String timeKey) {
+ if (timeKey == null) {
+ return LocalDate.now().format(DAY_FMT);
+ }
+ if (timeKey.length() == 4) {
+ return timeKey + "0101";
+ }
+ if (timeKey.length() == 6) {
+ return timeKey + "01";
+ }
+ return timeKey.length() >= 8 ? timeKey.substring(0, 8) : timeKey;
+ }
+
+ private static List<StatisticEleSplitItemVo> toSplitItems(Map<String, Double> map) {
+ double total = map.values().stream().mapToDouble(v -> v).sum();
+ List<StatisticEleSplitItemVo> items = new ArrayList<>();
+ map.forEach((name, val) -> addSplitItem(items, name, val, total));
+ return items;
+ }
+
+ private static void addSplitItem(List<StatisticEleSplitItemVo> items, String name, double val, double total) {
+ if (val <= 0) {
+ return;
+ }
+ StatisticEleSplitItemVo item = new StatisticEleSplitItemVo();
+ item.setName(name);
+ item.setConsumption(round(val));
+ item.setRatio(total > 0 ? roundSummary(BigDecimal.valueOf(val)
+ .divide(BigDecimal.valueOf(total), SUMMARY_SCALE + 2, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100))) : 0.0);
+ items.add(item);
+ }
+
+ private static double sumField(List<StatisticEleRecordVo> records,
+ Function<StatisticEleRecordVo, Double> getter) {
+ return records.stream().map(getter).mapToDouble(StatisticEleAnalyticsUtil::safe).sum();
+ }
+
+ private static int parseHour(String timeKey) {
+ if (timeKey == null || timeKey.length() < 10) {
+ return -1;
+ }
+ try {
+ return Integer.parseInt(timeKey.substring(8, 10));
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+ private static LocalDate parseDate(String timeKey) {
+ if (timeKey == null || timeKey.length() < 8) {
+ return null;
+ }
+ try {
+ return LocalDate.parse(timeKey.substring(0, 8), DAY_FMT);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static double safe(Double v) {
+ return v == null ? 0.0 : v;
+ }
+
+ private static double round(Double v) {
+ if (v == null) {
+ return 0.0;
+ }
+ return roundSummary(BigDecimal.valueOf(v));
+ }
+
+ private static double roundSummary(BigDecimal value) {
+ return value.setScale(SUMMARY_SCALE, RoundingMode.HALF_UP).doubleValue();
+ }
+}
--
Gitblit v1.9.3