| | |
| | | |
| | | import com.ruoyi.http.vo.StatisticEleRecordVo; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | |
| | | public final class StatisticEleAggregateUtil { |
| | | |
| | | private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyyMMdd"); |
| | | private static final int CONSUMPTION_SCALE = StatisticEleReadingUtil.CONSUMPTION_SCALE; |
| | | private static final int SUMMARY_SCALE = 2; |
| | | |
| | | private StatisticEleAggregateUtil() { |
| | | } |
| | |
| | | String monthKey = HOUR_TO_MONTH.apply(tk); |
| | | return monthKey != null ? toQuarterKey(monthKey) : null; |
| | | }; |
| | | public static final Function<String, String> HOUR_TO_WEEK = StatisticEleAnalyticsUtil.HOUR_TO_WEEK; |
| | | |
| | | /** |
| | | * 按时间桶汇总(多电表合并,用于图表) |
| | |
| | | mergeInto(map, key, bucket, record); |
| | | StatisticEleRecordVo agg = map.get(key); |
| | | agg.setMeterId(record.getMeterId()); |
| | | agg.setAddress(record.getAddress()); |
| | | agg.setCollectorNo(record.getCollectorNo()); |
| | | mergeMeterInfo(agg, record); |
| | | } |
| | | return sorted(map); |
| | | } |
| | | |
| | | private static void mergeMeterInfo(StatisticEleRecordVo agg, StatisticEleRecordVo record) { |
| | | if (hasText(record.getMeterName())) { |
| | | agg.setMeterName(record.getMeterName()); |
| | | } |
| | | if (hasText(record.getAddress())) { |
| | | agg.setAddress(record.getAddress()); |
| | | } |
| | | mergeTimeRange(agg, record); |
| | | } |
| | | |
| | | private static void mergeTimeRange(StatisticEleRecordVo agg, StatisticEleRecordVo record) { |
| | | if (hasText(record.getStartTime())) { |
| | | if (!hasText(agg.getStartTime()) || record.getStartTime().compareTo(agg.getStartTime()) < 0) { |
| | | agg.setStartTime(record.getStartTime()); |
| | | } |
| | | } |
| | | if (hasText(record.getEndTime())) { |
| | | if (!hasText(agg.getEndTime()) || record.getEndTime().compareTo(agg.getEndTime()) > 0) { |
| | | agg.setEndTime(record.getEndTime()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** 汇总后若起止时间为空,按 timeKey 推导 */ |
| | | private static void fillTimeRangeIfEmpty(StatisticEleRecordVo vo) { |
| | | if (hasText(vo.getStartTime()) && hasText(vo.getEndTime())) { |
| | | return; |
| | | } |
| | | String timeKey = vo.getTimeKey(); |
| | | if (!hasText(timeKey)) { |
| | | return; |
| | | } |
| | | if (timeKey.contains("Q")) { |
| | | String[] parts = timeKey.split("Q"); |
| | | if (parts.length != 2) { |
| | | return; |
| | | } |
| | | int year = Integer.parseInt(parts[0]); |
| | | int quarter = Integer.parseInt(parts[1]); |
| | | int startMonth = (quarter - 1) * 3 + 1; |
| | | int endMonth = startMonth + 2; |
| | | YearMonth endYm = YearMonth.of(year, endMonth); |
| | | vo.setStartTime(String.format("%04d-%02d-01 00:00:00", year, startMonth)); |
| | | vo.setEndTime(String.format("%04d-%02d-%02d 23:59:59", year, endMonth, endYm.lengthOfMonth())); |
| | | return; |
| | | } |
| | | if (timeKey.length() == 4) { |
| | | vo.setStartTime(timeKey + "-01-01 00:00:00"); |
| | | vo.setEndTime(timeKey + "-12-31 23:59:59"); |
| | | return; |
| | | } |
| | | if (timeKey.length() == 6) { |
| | | YearMonth ym = YearMonth.parse(timeKey, DateTimeFormatter.ofPattern("yyyyMM")); |
| | | vo.setStartTime(String.format("%04d-%02d-01 00:00:00", ym.getYear(), ym.getMonthValue())); |
| | | vo.setEndTime(String.format("%04d-%02d-%02d 23:59:59", |
| | | ym.getYear(), ym.getMonthValue(), ym.lengthOfMonth())); |
| | | return; |
| | | } |
| | | if (timeKey.length() >= 8) { |
| | | String day = timeKey.substring(0, 8); |
| | | vo.setStartTime(toDateTime(day, "00:00:00")); |
| | | vo.setEndTime(toDateTime(day, "23:59:59")); |
| | | } |
| | | } |
| | | |
| | | private static String toDateTime(String yyyyMMdd, String time) { |
| | | return yyyyMMdd.substring(0, 4) + "-" |
| | | + yyyyMMdd.substring(4, 6) + "-" |
| | | + yyyyMMdd.substring(6, 8) + " " + time; |
| | | } |
| | | |
| | | private static boolean hasText(String value) { |
| | | return value != null && !value.isBlank(); |
| | | } |
| | | |
| | | /** |
| | |
| | | return switch (dimension) { |
| | | case "hour" -> new HourRange(startTime, endTime); |
| | | case "day" -> new HourRange(startTime + "00", endTime + "23"); |
| | | case "week" -> new HourRange(startTime + "00", endTime + "23"); |
| | | case "month" -> new HourRange(startTime + "0100", endTime + lastDayOfMonth(endTime) + "23"); |
| | | case "year" -> new HourRange(startTime + "010100", endTime + "123123"); |
| | | case "quarter" -> new HourRange( |
| | |
| | | |
| | | /** 明细记录总用电量(与数据采集页求和方式一致) */ |
| | | public static double sumRecordsTotal(List<StatisticEleRecordVo> records) { |
| | | return records.stream() |
| | | BigDecimal total = records.stream() |
| | | .map(StatisticEleRecordVo::getTotalConsumption) |
| | | .filter(v -> v != null) |
| | | .mapToDouble(Double::doubleValue) |
| | | .sum(); |
| | | .map(BigDecimal::valueOf) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | return roundSummary(total); |
| | | } |
| | | |
| | | public static Function<String, String> bucketFn(String dimension) { |
| | | return switch (dimension) { |
| | | case "hour" -> HOUR_TO_HOUR; |
| | | case "day" -> HOUR_TO_DAY; |
| | | case "week" -> HOUR_TO_WEEK; |
| | | case "month" -> HOUR_TO_MONTH; |
| | | case "quarter" -> HOUR_TO_QUARTER; |
| | | case "year" -> HOUR_TO_YEAR; |
| | |
| | | metrics.setMinConsumption(0.0); |
| | | return metrics; |
| | | } |
| | | List<Double> values = buckets.stream() |
| | | List<BigDecimal> values = buckets.stream() |
| | | .map(StatisticEleRecordVo::getTotalConsumption) |
| | | .filter(v -> v != null) |
| | | .map(BigDecimal::valueOf) |
| | | .collect(Collectors.toList()); |
| | | double total = values.stream().mapToDouble(Double::doubleValue).sum(); |
| | | metrics.setTotalConsumption(round(total)); |
| | | metrics.setAvgConsumption(round(total / values.size())); |
| | | metrics.setMaxConsumption(round(values.stream().mapToDouble(Double::doubleValue).max().orElse(0))); |
| | | metrics.setMinConsumption(round(values.stream().mapToDouble(Double::doubleValue).min().orElse(0))); |
| | | BigDecimal total = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | metrics.setTotalConsumption(roundSummary(total)); |
| | | metrics.setAvgConsumption(roundSummary(total.divide( |
| | | BigDecimal.valueOf(values.size()), CONSUMPTION_SCALE, RoundingMode.HALF_UP))); |
| | | metrics.setMaxConsumption(roundSummary(values.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO))); |
| | | metrics.setMinConsumption(roundSummary(values.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO))); |
| | | return metrics; |
| | | } |
| | | |
| | |
| | | |
| | | private static List<StatisticEleRecordVo> sorted(Map<String, StatisticEleRecordVo> map) { |
| | | return map.values().stream() |
| | | .peek(vo -> { |
| | | normalizeConsumptions(vo); |
| | | fillTimeRangeIfEmpty(vo); |
| | | }) |
| | | .sorted(Comparator.comparing(StatisticEleRecordVo::getTimeKey)) |
| | | .collect(Collectors.toList()); |
| | | } |
| | |
| | | } |
| | | |
| | | private static Double add(Double a, Double b) { |
| | | return (a == null ? 0.0 : a) + (b == null ? 0.0 : b); |
| | | BigDecimal sum = BigDecimal.valueOf(a == null ? 0.0 : a) |
| | | .add(BigDecimal.valueOf(b == null ? 0.0 : b)); |
| | | return roundConsumption(sum); |
| | | } |
| | | |
| | | private static double round(double value) { |
| | | return Math.round(value * 100.0) / 100.0; |
| | | /** 统一电量字段精度(明细展示) */ |
| | | public static void normalizeConsumptions(StatisticEleRecordVo vo) { |
| | | if (vo == null) { |
| | | return; |
| | | } |
| | | vo.setTotalConsumption(roundConsumption(vo.getTotalConsumption())); |
| | | vo.setSharpConsumption(roundConsumption(vo.getSharpConsumption())); |
| | | vo.setPeakConsumption(roundConsumption(vo.getPeakConsumption())); |
| | | vo.setFlatConsumption(roundConsumption(vo.getFlatConsumption())); |
| | | vo.setValleyConsumption(roundConsumption(vo.getValleyConsumption())); |
| | | } |
| | | |
| | | public static void normalizeConsumptions(List<StatisticEleRecordVo> records) { |
| | | if (records == null) { |
| | | return; |
| | | } |
| | | records.forEach(vo -> normalizeConsumptions(vo)); |
| | | } |
| | | |
| | | private static Double roundConsumption(Double value) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | return roundConsumption(BigDecimal.valueOf(value)); |
| | | } |
| | | |
| | | private static Double roundConsumption(BigDecimal value) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | return value.setScale(CONSUMPTION_SCALE, RoundingMode.HALF_UP).doubleValue(); |
| | | } |
| | | |
| | | private static double roundSummary(BigDecimal value) { |
| | | return value.setScale(SUMMARY_SCALE, RoundingMode.HALF_UP).doubleValue(); |
| | | } |
| | | |
| | | public record HourRange(String startTime, String endTime) {} |