Sfoglia il codice sorgente

运行部分修正

lisonglin 2 settimane fa
parent
commit
e378cc84dd

+ 2 - 3
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/controller/optimization/IndicatorController.java

@@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -162,13 +163,11 @@ public class IndicatorController extends BaseController {
 
     /**
      * 运行异常信息详情
-     *
-     * @param params {alarmEventId}
      */
     @PostMapping("/alarm/detail")
     public ApiResponse<SeAlarmDetailVO> alarmDetail(@RequestBody Map<String, String> params) {
         String alarmEventId = params.get("alarmEventId");
-        if (alarmEventId == null) {
+        if (alarmEventId == null || alarmEventId.isEmpty()) {
             return ApiResponse.fail("参数缺失");
         }
         return ApiResponse.success(seAlarmService.queryAlarmDetail(Long.parseLong(alarmEventId)));

+ 112 - 41
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/impl/SeAlarmServiceImpl.java

@@ -4,12 +4,14 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.hdkj.lt.bf.entity.FhzgSeCurrentEvent;
 import com.hdkj.lt.bf.entity.FhzgSeMonitorCurrent;
 import com.hdkj.lt.bf.entity.FhzgSeMonitorVoltage;
+import com.hdkj.lt.bf.entity.FhzgSeSnapshotDetail;
 import com.hdkj.lt.bf.entity.FhzgSeVoltageEvent;
 import com.hdkj.lt.bf.application.FeederGroupApplication;
 import com.hdkj.lt.bf.entity.vo.*;
 import com.hdkj.lt.bf.mapper.FhzgSeCurrentEventMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeMonitorCurrentMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeMonitorVoltageMapper;
+import com.hdkj.lt.bf.mapper.FhzgSeSnapshotDetailMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeVoltageEventMapper;
 import com.hdkj.lt.bf.service.SeAlarmService;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -42,6 +44,7 @@ public class SeAlarmServiceImpl implements SeAlarmService {
     private final FeederGroupApplication feederGroupApplication;
     private final FhzgSeMonitorCurrentMapper monitorCurrentMapper;
     private final FhzgSeMonitorVoltageMapper monitorVoltageMapper;
+    private final FhzgSeSnapshotDetailMapper snapshotDetailMapper;
 
     private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
@@ -119,7 +122,6 @@ public class SeAlarmServiceImpl implements SeAlarmService {
         LocalDateTime rangeEnd = end.plusDays(1).atStartOfDay();
 
         Page<FhzgSeVoltageEvent> mpPage = new Page<>(page, pageSize);
-
         LambdaQueryWrapper<FhzgSeVoltageEvent> wrapper = new LambdaQueryWrapper<FhzgSeVoltageEvent>()
                 .ge(FhzgSeVoltageEvent::getFirstOverTime, rangeStart)
                 .lt(FhzgSeVoltageEvent::getFirstOverTime, rangeEnd)
@@ -139,14 +141,9 @@ public class SeAlarmServiceImpl implements SeAlarmService {
 
     private SeAlarmListVO toVoltageVO(FhzgSeVoltageEvent e) {
         String displayValue = resolveVoltageDisplay(e.getAlarmType(), e.getAlarmLevel());
-        String feederName = e.getDeviceName();
-        // 台区告警时显示馈线名称
-        if ("mvtrans".equals(e.getAlarmLevel())) {
-            feederName = e.getFeederName();
-        }
         return SeAlarmListVO.builder()
                 .alarmEventId(e.getId())
-                .feederName(feederName)
+                .feederName(e.getFeederName())
                 .feederId(e.getFeederId())
                 .alarmDisplayValue(displayValue)
                 .alarmTime(e.getFirstOverTime() != null ? e.getFirstOverTime().format(DT_FMT) : null)
@@ -155,12 +152,21 @@ public class SeAlarmServiceImpl implements SeAlarmService {
                 .build();
     }
 
+    /**
+     * 展示标签:按 alarmType + alarmLevel 拼线路/台区电压越限
+     */
     private String resolveVoltageDisplay(String alarmType, String alarmLevel) {
-        boolean isMvtrans = "mvtrans".equals(alarmLevel);
-        String prefix = isMvtrans ? "台区" : "线路";
-        if (alarmType != null && alarmType.contains("over")) return prefix + "电压越上限";
-        if (alarmType != null && alarmType.contains("under")) return prefix + "电压越下限";
-        return prefix + "电压越限";
+        boolean isOver = alarmType != null && alarmType.contains("over");
+        boolean isUnder = alarmType != null && alarmType.contains("under");
+        if ("mvtrans".equals(alarmLevel)) {
+            if (isOver) return "台区电压越上限";
+            if (isUnder) return "台区电压越下限";
+            return "电压越限";
+        }
+        // 默认 feeder
+        if (isOver) return "线路电压越上限";
+        if (isUnder) return "线路电压越下限";
+        return "电压越限";
     }
 
     // ============================================================
@@ -171,56 +177,104 @@ public class SeAlarmServiceImpl implements SeAlarmService {
     public SeAlarmDetailVO queryAlarmDetail(Long alarmEventId) {
         if (alarmEventId == null) return null;
 
-        // 先查重过载事件
+        // 重过载
         FhzgSeCurrentEvent currentEvent = currentEventMapper.selectById(alarmEventId);
         if (currentEvent != null) {
             return buildOverloadDetail(currentEvent);
         }
 
-        // 再查电压越限事件
+        // 电压事件
         FhzgSeVoltageEvent voltageEvent = voltageEventMapper.selectById(alarmEventId);
         if (voltageEvent != null) {
-            return buildVoltageDetail(voltageEvent);
+            if ("feeder".equals(voltageEvent.getAlarmLevel())) {
+                return buildFeederDetail(voltageEvent);
+            }
+            if ("mvtrans".equals(voltageEvent.getAlarmLevel())) {
+                return buildMvtransDetail(voltageEvent);
+            }
         }
 
         return null;
     }
 
-    private SeAlarmDetailVO buildOverloadDetail(FhzgSeCurrentEvent event) {
-        // 告警结束时间
-        LocalDateTime endTime = event.getRecoverTime();
-        if (endTime == null && event.getStatus() == 1) {
-            FhzgSeMonitorCurrent state = monitorCurrentMapper.selectOne(
-                    new LambdaQueryWrapper<FhzgSeMonitorCurrent>()
-                            .eq(FhzgSeMonitorCurrent::getFeederId, event.getFeederId())
+    /**
+     * 线路事件详情:展示电压范围 + 窗口内配变统计
+     */
+    private SeAlarmDetailVO buildFeederDetail(FhzgSeVoltageEvent feederEvent) {
+        String feederId = feederEvent.getFeederId();
+        // 持续窗口
+        LocalDateTime endTime = feederEvent.getRecoverTime();
+        if (endTime == null && feederEvent.getStatus() == 1) {
+            FhzgSeMonitorVoltage state = monitorVoltageMapper.selectOne(
+                    new LambdaQueryWrapper<FhzgSeMonitorVoltage>()
+                            .eq(FhzgSeMonitorVoltage::getFeederId, feederId)
+                            .eq(FhzgSeMonitorVoltage::getDeviceType, "feeder")
                             .last("LIMIT 1"));
             if (state != null) endTime = state.getLastSnapTime();
         }
-        if (endTime == null) endTime = event.getTriggerTime();
+        if (endTime == null) endTime = feederEvent.getTriggerTime();
+        LocalDateTime rangeEnd = endTime != null ? endTime : LocalDateTime.now();
+
+        // 实时查该馈线在越限时段内的电压范围
+        BigDecimal maxVoltage = null, minVoltage = null;
+        List<FhzgSeSnapshotDetail> snaps = snapshotDetailMapper.selectList(
+                new LambdaQueryWrapper<FhzgSeSnapshotDetail>()
+                        .eq(FhzgSeSnapshotDetail::getFeederId, feederId)
+                        .eq(FhzgSeSnapshotDetail::getDeviceType, "feeder")
+                        .isNotNull(FhzgSeSnapshotDetail::getVoltageValue)
+                        .ge(FhzgSeSnapshotDetail::getSnapshotTime, feederEvent.getFirstOverTime())
+                        .le(FhzgSeSnapshotDetail::getSnapshotTime, rangeEnd));
+        for (FhzgSeSnapshotDetail s : snaps) {
+            BigDecimal v = s.getVoltageValue();
+            if (v == null) continue;
+            if (maxVoltage == null || v.compareTo(maxVoltage) > 0) maxVoltage = v;
+            if (minVoltage == null || v.compareTo(minVoltage) < 0) minVoltage = v;
+        }
+
+        // 窗口内配变统计(实时查,不依赖触发快照值)
+        int[] mc = countMvtransInWindow(feederId, feederEvent.getFirstOverTime(), rangeEnd);
 
+        return SeAlarmDetailVO.builder()
+                .alarmEventId(feederEvent.getId())
+                .alarmType(feederEvent.getAlarmType())
+                .alarmCategory("voltage")
+                .countyName(feederEvent.getCountyName())
+                .feederName(feederEvent.getFeederName())
+                .alarmStartTime(feederEvent.getFirstOverTime())
+                .alarmEndTime(endTime)
+                .abnormalIndicator(resolveVoltageDisplay(feederEvent.getAlarmType(), feederEvent.getAlarmLevel()))
+                .maxVoltage(maxVoltage)
+                .minVoltage(minVoltage)
+                .overUpperMvtransCount(mc[0])
+                .overLowerMvtransCount(mc[1])
+                .reconStatus(feederEvent.getReconStatus())
+                .build();
+    }
+
+    /**
+     * 单配变事件详情:展示该配变自身信息
+     */
+    private SeAlarmDetailVO buildMvtransDetail(FhzgSeVoltageEvent event) {
+        LocalDateTime endTime = event.getRecoverTime() != null ? event.getRecoverTime()
+                : (event.getTriggerTime() != null ? event.getTriggerTime() : event.getFirstOverTime());
         return SeAlarmDetailVO.builder()
                 .alarmEventId(event.getId())
                 .alarmType(event.getAlarmType())
-                .alarmCategory("overload")
+                .alarmCategory("voltage")
                 .countyName(event.getCountyName())
                 .feederName(event.getFeederName())
                 .alarmStartTime(event.getFirstOverTime())
                 .alarmEndTime(endTime)
-                .maxLoadRate(event.getMaxLoadRate())
-                .reconStatus(event.getReconStatus())
+                .abnormalIndicator(resolveVoltageDisplay(event.getAlarmType(), event.getAlarmLevel()))
                 .build();
     }
 
-    private SeAlarmDetailVO buildVoltageDetail(FhzgSeVoltageEvent event) {
-        String indicator = resolveVoltageDisplay(event.getAlarmType(), event.getAlarmLevel());
-        String feederName = event.getFeederName();
-
+    private SeAlarmDetailVO buildOverloadDetail(FhzgSeCurrentEvent event) {
         LocalDateTime endTime = event.getRecoverTime();
         if (endTime == null && event.getStatus() == 1) {
-            FhzgSeMonitorVoltage state = monitorVoltageMapper.selectOne(
-                    new LambdaQueryWrapper<FhzgSeMonitorVoltage>()
-                            .eq(FhzgSeMonitorVoltage::getDeviceId, event.getDeviceId())
-                            .eq(FhzgSeMonitorVoltage::getDeviceType, event.getDeviceType())
+            FhzgSeMonitorCurrent state = monitorCurrentMapper.selectOne(
+                    new LambdaQueryWrapper<FhzgSeMonitorCurrent>()
+                            .eq(FhzgSeMonitorCurrent::getFeederId, event.getFeederId())
                             .last("LIMIT 1"));
             if (state != null) endTime = state.getLastSnapTime();
         }
@@ -229,20 +283,37 @@ public class SeAlarmServiceImpl implements SeAlarmService {
         return SeAlarmDetailVO.builder()
                 .alarmEventId(event.getId())
                 .alarmType(event.getAlarmType())
-                .alarmCategory("voltage")
+                .alarmCategory("overload")
                 .countyName(event.getCountyName())
-                .feederName(feederName)
+                .feederName(event.getFeederName())
                 .alarmStartTime(event.getFirstOverTime())
                 .alarmEndTime(endTime)
-                .abnormalIndicator(indicator)
-                .maxVoltage(event.getVoltageRangeMax())
-                .minVoltage(event.getVoltageRangeMin())
-                .overUpperMvtransCount(event.getOverUpperMvtrans())
-                .overLowerMvtransCount(event.getOverLowerMvtrans())
+                .maxLoadRate(event.getMaxLoadRate())
                 .reconStatus(event.getReconStatus())
                 .build();
     }
 
+    /**
+     * 查 feeder 事件发生在窗口 [firstOverTime, endTime] 内的 mvtrans 事件数
+     */
+    private int[] countMvtransInWindow(String feederId, LocalDateTime firstOverTime, LocalDateTime endTime) {
+        if (firstOverTime == null) return new int[]{0, 0};
+        LocalDateTime rangeEnd = endTime != null ? endTime : LocalDateTime.now();
+        List<FhzgSeVoltageEvent> list = voltageEventMapper.selectList(
+                new LambdaQueryWrapper<FhzgSeVoltageEvent>()
+                        .eq(FhzgSeVoltageEvent::getFeederId, feederId)
+                        .eq(FhzgSeVoltageEvent::getAlarmLevel, "mvtrans")
+                        .eq(FhzgSeVoltageEvent::getStatus, 1)
+                        .ge(FhzgSeVoltageEvent::getFirstOverTime, firstOverTime)
+                        .le(FhzgSeVoltageEvent::getFirstOverTime, rangeEnd));
+        int up = 0, down = 0;
+        for (FhzgSeVoltageEvent e : list) {
+            if (e.getAlarmType() != null && e.getAlarmType().contains("over")) up++;
+            else if (e.getAlarmType() != null && e.getAlarmType().contains("under")) down++;
+        }
+        return new int[]{up, down};
+    }
+
     // ============================================================
     // 辅助
     // ============================================================

+ 130 - 74
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/impl/SeIndicatorServiceImpl.java

@@ -3,6 +3,7 @@ package com.hdkj.lt.bf.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.hdkj.lt.bf.application.FeederGroupApplication;
+import com.hdkj.lt.bf.entity.FhzgSeCurrentEvent;
 import com.hdkj.lt.bf.entity.FhzgDwmVoltageOverLimitDaily;
 import com.hdkj.lt.bf.entity.FhzgSeCapacityDaily;
 import com.hdkj.lt.bf.entity.FhzgSeVoltageEvent;
@@ -11,6 +12,7 @@ import com.hdkj.lt.bf.entity.dto.TodayOutageData;
 import com.hdkj.lt.bf.entity.vo.SeCapacityDashboardVO;
 import com.hdkj.lt.bf.entity.vo.SeOutageDashboardVO;
 import com.hdkj.lt.bf.entity.vo.SeVoltageDashboardVO;
+import com.hdkj.lt.bf.mapper.FhzgSeCurrentEventMapper;
 import com.hdkj.lt.bf.mapper.FhzgDwmVoltageOverLimitDailyMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeCapacityDailyMapper;
 import com.hdkj.lt.bf.mapper.FhzgSeVoltageEventMapper;
@@ -47,6 +49,7 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
     private final FhzgDwmVoltageOverLimitDailyMapper voltageDailyMapper;
     private final FhzgSeSnapshotDetailMapper snapshotDetailMapper;
     private final FhzgSeCapacityDailyMapper capacityDailyMapper;
+    private final FhzgSeCurrentEventMapper currentEventMapper;
     private final FeederGroupApplication feederGroupApplication;
 
     private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd");
@@ -97,17 +100,47 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
     }
 
     /**
-     * 今日电压:COUNT 聚合到 SQL,按 alarmLevel + alarmType 分组
-     * 返回4个计数:台区越上限/下限 + 馈线越上限/下限
+     * 今日电压:线路越限数从 feeder 事件 (alarm_level='feeder') 统计,
+     * 台区越限数从 mvtrans 事件 (alarm_level='mvtrans') 直接 COUNT。
+     * 按 firstOverTime 今日范围 + status=1(活跃)过滤。
      */
     private SeVoltageDashboardVO queryVoltageToday(String id, Integer type) {
         LocalDateTime todayStart = LocalDate.now().atStartOfDay();
-        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime tomorrowStart = LocalDate.now().plusDays(1).atStartOfDay();
+
+        // == 线路越限事件统计(alarm_level='feeder')==
+        LambdaQueryWrapper<FhzgSeVoltageEvent> feederWrapper = new LambdaQueryWrapper<FhzgSeVoltageEvent>()
+                .eq(FhzgSeVoltageEvent::getAlarmLevel, "feeder")
+                .ge(FhzgSeVoltageEvent::getFirstOverTime, todayStart)
+                .lt(FhzgSeVoltageEvent::getFirstOverTime, tomorrowStart)
+                .eq(FhzgSeVoltageEvent::getStatus, 1);
+        applyNodeFilter(feederWrapper, id, type);
+        List<FhzgSeVoltageEvent> feederEvents = voltageEventMapper.selectList(feederWrapper);
+
+        int overUpperFeeder = 0, overLowerFeeder = 0;
+        for (FhzgSeVoltageEvent e : feederEvents) {
+            String t = e.getAlarmType();
+            if (t == null) continue;
+            if (t.contains("over")) overUpperFeeder++;
+            if (t.contains("under")) overLowerFeeder++;
+        }
 
-        int overUpperArea = countVoltageEvents(id, type, todayStart, now, "mvtrans", "over");
-        int overLowerArea = countVoltageEvents(id, type, todayStart, now, "mvtrans", "under");
-        int overUpperFeeder = countVoltageEvents(id, type, todayStart, now, "feeder", "over");
-        int overLowerFeeder = countVoltageEvents(id, type, todayStart, now, "feeder", "under");
+        // == 配变越限事件统计(alarm_level='mvtrans')==
+        LambdaQueryWrapper<FhzgSeVoltageEvent> mvtransWrapper = new LambdaQueryWrapper<FhzgSeVoltageEvent>()
+                .eq(FhzgSeVoltageEvent::getAlarmLevel, "mvtrans")
+                .ge(FhzgSeVoltageEvent::getFirstOverTime, todayStart)
+                .lt(FhzgSeVoltageEvent::getFirstOverTime, tomorrowStart)
+                .eq(FhzgSeVoltageEvent::getStatus, 1);
+        applyNodeFilter(mvtransWrapper, id, type);
+        List<FhzgSeVoltageEvent> mvtransEvents = voltageEventMapper.selectList(mvtransWrapper);
+
+        int overUpperArea = 0, overLowerArea = 0;
+        for (FhzgSeVoltageEvent e : mvtransEvents) {
+            String t = e.getAlarmType();
+            if (t == null) continue;
+            if (t.contains("over")) overUpperArea++;
+            if (t.contains("under")) overLowerArea++;
+        }
 
         return SeVoltageDashboardVO.builder()
                 .todayOverUpperAreaCount(overUpperArea)
@@ -118,20 +151,30 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
     }
 
     /**
-     * 按节点+时段+类型统计电压越限事件数
+     * 按节点+时段统计重过载事件数
+     * 返回 int[2]: [0]=重载事件数, [1]=过载事件数
      */
-    private int countVoltageEvents(String id, Integer type,
-                                    LocalDateTime start, LocalDateTime end,
-                                    String alarmLevel, String overOrUnder) {
-        LambdaQueryWrapper<FhzgSeVoltageEvent> wrapper = new LambdaQueryWrapper<FhzgSeVoltageEvent>()
-                .ge(FhzgSeVoltageEvent::getTriggerTime, start)
-                .le(FhzgSeVoltageEvent::getTriggerTime, end)
-                .eq(FhzgSeVoltageEvent::getStatus, 1)
-                .eq(FhzgSeVoltageEvent::getAlarmLevel, alarmLevel)
-                .like(FhzgSeVoltageEvent::getAlarmType, overOrUnder);
-        applyNodeFilter(wrapper, id, type);
-        Long count = voltageEventMapper.selectCount(wrapper);
-        return count != null ? count.intValue() : 0;
+    private int[] countCurrentEvents(String id, Integer type,
+                                      LocalDateTime start, LocalDateTime end) {
+        LambdaQueryWrapper<FhzgSeCurrentEvent> wrapper = new LambdaQueryWrapper<FhzgSeCurrentEvent>()
+                .ge(FhzgSeCurrentEvent::getFirstOverTime, start)
+                .lt(FhzgSeCurrentEvent::getFirstOverTime, end)
+                .in(FhzgSeCurrentEvent::getAlarmType, "current_heavy", "current_overload");
+        if (type == 2) {
+            wrapper.eq(FhzgSeCurrentEvent::getCountyId, id);
+        } else if (type == 3) {
+            wrapper.eq(FhzgSeCurrentEvent::getSubsId, id);
+        } else if (type == 4) {
+            wrapper.eq(FhzgSeCurrentEvent::getFeederId, id);
+        }
+
+        List<FhzgSeCurrentEvent> events = currentEventMapper.selectList(wrapper);
+        int heavy = 0, overload = 0;
+        for (FhzgSeCurrentEvent e : events) {
+            if ("current_heavy".equals(e.getAlarmType())) heavy++;
+            else if ("current_overload".equals(e.getAlarmType())) overload++;
+        }
+        return new int[]{heavy, overload};
     }
 
     /**
@@ -280,40 +323,54 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
     }
 
     /**
-     * 今日供电能力:实时查 snapshot_detail(量小,一个馈线一天最多96行)
-     * 馈线级别附加96点时序(负载率+电流+电压+有功+无功)
+     * 今日供电能力:负载率从断面明细聚合,重过载次数从事件表统计
      */
     private SeCapacityDashboardVO queryCapacityToday(String id, Integer type) {
         LocalDate today = LocalDate.now();
         LocalDateTime startTime = today.atStartOfDay();
         LocalDateTime endTime = LocalDateTime.now();
 
-        LambdaQueryWrapper<FhzgSeSnapshotDetail> wrapper = new LambdaQueryWrapper<FhzgSeSnapshotDetail>()
+        // 1. 负载率:从断面明细聚合(保持馈线96点时序)
+        LambdaQueryWrapper<FhzgSeSnapshotDetail> snapshotWrapper = new LambdaQueryWrapper<FhzgSeSnapshotDetail>()
                 .eq(FhzgSeSnapshotDetail::getDeviceType, "feeder")
                 .isNotNull(FhzgSeSnapshotDetail::getLoadRate)
                 .ge(FhzgSeSnapshotDetail::getSnapshotTime, startTime)
                 .le(FhzgSeSnapshotDetail::getSnapshotTime, endTime);
+        applySnapshotFilter(snapshotWrapper, id, type);
+        List<FhzgSeSnapshotDetail> snapshots = snapshotDetailMapper.selectList(snapshotWrapper);
 
-        applySnapshotFilter(wrapper, id, type);
-
-        List<FhzgSeSnapshotDetail> snapshots = snapshotDetailMapper.selectList(wrapper);
-
+        SeCapacityDashboardVO vo;
         if (snapshots.isEmpty()) {
-            return SeCapacityDashboardVO.builder().build();
+            vo = SeCapacityDashboardVO.builder().build();
+        } else {
+            vo = aggregateSnapshots(snapshots);
+            // 馈线 → 附加96点时序
+            if (type == 4) {
+                vo.setLoadRateSeries(buildFeederTimeSeries(snapshots));
+            }
         }
 
-        SeCapacityDashboardVO vo = aggregateSnapshots(snapshots);
-
-        // 馈线 → 附加96点时序
-        if (type == 4) {
-            vo.setLoadRateSeries(buildFeederTimeSeries(snapshots));
+        // 2. 重过载事件次数:从事件表统计
+        int[] eventCounts = countCurrentEvents(id, type, startTime, endTime);
+        vo.setHeavyLoadEventCount(eventCounts[0]);
+        vo.setOverloadEventCount(eventCounts[1]);
+        int totalEvents = eventCounts[0] + eventCounts[1];
+        vo.setTotalEventCount(totalEvents);
+        if (totalEvents > 0) {
+            vo.setHeavyLoadRate(new BigDecimal(eventCounts[0] * 100)
+                    .divide(BigDecimal.valueOf(totalEvents), 2, RoundingMode.HALF_UP));
+            vo.setOverloadRate(new BigDecimal(eventCounts[1] * 100)
+                    .divide(BigDecimal.valueOf(totalEvents), 2, RoundingMode.HALF_UP));
+        } else {
+            vo.setHeavyLoadRate(BigDecimal.ZERO);
+            vo.setOverloadRate(BigDecimal.ZERO);
         }
         return vo;
     }
 
     /**
-     * 历史供电能力:查 capacity_daily 日汇总表(每馈线每天一行,量极小)
-     * month = 月初~昨天;year = 1.1~昨天
+     * 历史供电能力:负载率从日汇总表查,重过载次数从事件表统计
+     * month = 月初 ~ 昨天;year = 1.1 ~ 昨天
      */
     private SeCapacityDashboardVO queryCapacityHistory(String id, Integer type, String timeScope) {
         LocalDate today = LocalDate.now();
@@ -331,55 +388,54 @@ public class SeIndicatorServiceImpl implements SeIndicatorService {
             return SeCapacityDashboardVO.builder().build();
         }
 
+        // 1. 负载率:从日汇总表聚合
         LambdaQueryWrapper<FhzgSeCapacityDaily> wrapper = new LambdaQueryWrapper<FhzgSeCapacityDaily>()
                 .ge(FhzgSeCapacityDaily::getStatDate, startDate)
                 .le(FhzgSeCapacityDaily::getStatDate, endDate);
-
         applyCapacityDailyFilter(wrapper, id, type);
-
         List<FhzgSeCapacityDaily> dailyRecords = capacityDailyMapper.selectList(wrapper);
 
+        SeCapacityDashboardVO vo;
         if (dailyRecords.isEmpty()) {
-            return SeCapacityDashboardVO.builder().build();
-        }
-
-        // 聚合日汇总
-        BigDecimal maxLoadRate = BigDecimal.ZERO;
-        BigDecimal sumAvg = BigDecimal.ZERO;
-        int totalHeavy = 0, totalOverload = 0, totalSnapshots = 0;
-        for (FhzgSeCapacityDaily d : dailyRecords) {
-            if (d.getMaxLoadRate() != null && d.getMaxLoadRate().compareTo(maxLoadRate) > 0) {
-                maxLoadRate = d.getMaxLoadRate();
-            }
-            if (d.getAvgLoadRate() != null) {
-                sumAvg = sumAvg.add(d.getAvgLoadRate());
+            vo = SeCapacityDashboardVO.builder().build();
+        } else {
+            BigDecimal maxLoadRate = BigDecimal.ZERO;
+            BigDecimal sumAvg = BigDecimal.ZERO;
+            for (FhzgSeCapacityDaily d : dailyRecords) {
+                if (d.getMaxLoadRate() != null && d.getMaxLoadRate().compareTo(maxLoadRate) > 0) {
+                    maxLoadRate = d.getMaxLoadRate();
+                }
+                if (d.getAvgLoadRate() != null) {
+                    sumAvg = sumAvg.add(d.getAvgLoadRate());
+                }
             }
-            totalHeavy += d.getHeavyCount() != null ? d.getHeavyCount() : 0;
-            totalOverload += d.getOverloadCount() != null ? d.getOverloadCount() : 0;
-            totalSnapshots += d.getTotalSnapshots() != null ? d.getTotalSnapshots() : 0;
+            BigDecimal avgLoadRate = !dailyRecords.isEmpty()
+                    ? sumAvg.divide(BigDecimal.valueOf(dailyRecords.size()), 2, RoundingMode.HALF_UP)
+                    : BigDecimal.ZERO;
+            vo = SeCapacityDashboardVO.builder()
+                    .maxLoadRate(maxLoadRate)
+                    .avgLoadRate(avgLoadRate)
+                    .build();
         }
 
-        int totalEvents = totalHeavy + totalOverload;
-        int recordCount = dailyRecords.size();
-        BigDecimal avgLoadRate = recordCount > 0
-                ? sumAvg.divide(BigDecimal.valueOf(recordCount), 2, RoundingMode.HALF_UP)
-                : BigDecimal.ZERO;
-        BigDecimal heavyRate = totalSnapshots > 0
-                ? BigDecimal.valueOf(totalHeavy * 100).divide(BigDecimal.valueOf(totalSnapshots), 2, RoundingMode.HALF_UP)
-                : BigDecimal.ZERO;
-        BigDecimal overloadRate = totalSnapshots > 0
-                ? BigDecimal.valueOf(totalOverload * 100).divide(BigDecimal.valueOf(totalSnapshots), 2, RoundingMode.HALF_UP)
-                : BigDecimal.ZERO;
-
-        return SeCapacityDashboardVO.builder()
-                .maxLoadRate(maxLoadRate)
-                .avgLoadRate(avgLoadRate)
-                .heavyLoadEventCount(totalHeavy)
-                .overloadEventCount(totalOverload)
-                .heavyLoadRate(heavyRate)
-                .overloadRate(overloadRate)
-                .totalEventCount(totalEvents)
-                .build();
+        // 2. 重过载事件次数:从事件表统计
+        LocalDateTime rangeStart = startDate.atStartOfDay();
+        LocalDateTime rangeEnd = endDate.atTime(23, 59, 59);
+        int[] eventCounts = countCurrentEvents(id, type, rangeStart, rangeEnd);
+        vo.setHeavyLoadEventCount(eventCounts[0]);
+        vo.setOverloadEventCount(eventCounts[1]);
+        int totalEvents = eventCounts[0] + eventCounts[1];
+        vo.setTotalEventCount(totalEvents);
+        if (totalEvents > 0) {
+            vo.setHeavyLoadRate(new BigDecimal(eventCounts[0] * 100)
+                    .divide(BigDecimal.valueOf(totalEvents), 2, RoundingMode.HALF_UP));
+            vo.setOverloadRate(new BigDecimal(eventCounts[1] * 100)
+                    .divide(BigDecimal.valueOf(totalEvents), 2, RoundingMode.HALF_UP));
+        } else {
+            vo.setHeavyLoadRate(BigDecimal.ZERO);
+            vo.setOverloadRate(BigDecimal.ZERO);
+        }
+        return vo;
     }
 
     /**

+ 93 - 60
services/load-transfer-bf/src/main/java/com/hdkj/lt/bf/service/impl/SeSnapshotServiceImpl.java

@@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.hdkj.lt.bf.application.FeederGroupApplication;
 import com.hdkj.lt.bf.common.SeMonitorThreshold;
+import com.hdkj.lt.bf.common.SeSnapshotConstants;
 import com.hdkj.lt.bf.entity.FhzgSeCurrentEvent;
 import com.hdkj.lt.bf.entity.FhzgSeMonitorCurrent;
 import com.hdkj.lt.bf.entity.FhzgSeMonitorVoltage;
@@ -298,43 +299,62 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
                 if (state.getFirstOverTime() != null
                         && Duration.between(state.getFirstOverTime(), snapTime).toMinutes() >= SeMonitorThreshold.TRIGGER_DURATION_MINUTES
                         && state.getAlarmTriggered() == 0) {
-                    String alarmType = resolveVoltageAlarmType(deviceType, voltageStatus);
-                    String countyName = lookupCountyName(countyId, null);
-                    BigDecimal threshold = resolveVoltageThreshold(deviceType, voltageStatus);
-                    BigDecimal rangeMax = null, rangeMin = null;
-                    int overUpper = 0, overLower = 0;
-                    if ("feeder".equals(deviceType) && feederId != null) {
-                        BigDecimal[] vr = queryVoltageRangeFromSnap(feederId, state.getFirstOverTime(), snapTime);
-                        rangeMax = vr[0]; rangeMin = vr[1];
-                        int[] mc = queryMvtransCounts(feederId, state.getFirstOverTime(), snapTime);
-                        overUpper = mc[0]; overLower = mc[1];
-                    }
-                    FhzgSeVoltageEvent event = FhzgSeVoltageEvent.builder()
-                            .deviceId(deviceId).deviceName(feederName)
-                            .deviceType(deviceType).alarmLevel(deviceType).alarmType(alarmType)
-                            .feederId(feederId).feederName(feederName)
-                            .subsId(subsId).countyId(countyId).countyName(countyName)
-                            .mvtransId(mvtransId).consumerId(consumerId).threshold(threshold)
-                            .firstOverTime(state.getFirstOverTime()).triggerTime(snapTime)
-                            .triggerValue(voltageRatio)
-                            .voltageRangeMax(rangeMax).voltageRangeMin(rangeMin)
-                            .overUpperMvtrans(overUpper).overLowerMvtrans(overLower)
-                            .lastValue(voltageRatio).status(1).build();
-                    voltageEventMapper.insert(event);
-                    state.setAlarmTriggered(1); state.setAlarmEventId(event.getId());
-                    // 馈线越下限 → 异步触发重构算法
-                    if ("feeder".equals(deviceType) && "voltage_under".equals(alarmType)) {
-                        eventPublisher.publishEvent(new ReconTriggerEvent(
-                                event.getId(), feederId, feederName, countyId, subsId,
-                                alarmType, snapTime, state.getFirstOverTime()));
-                    }
-                }
+                            String countyName = lookupCountyName(countyId, null);
+
+                            if ("feeder".equals(deviceType)) {
+                                // === 馈线自身越限 → 创建 feeder 级独立事件 ===
+                                String alarmType = resolveVoltageAlarmType(deviceType, voltageStatus);
+                                BigDecimal threshold = resolveVoltageThreshold(deviceType, voltageStatus);
+                                BigDecimal[] vr = queryVoltageRangeFromSnap(feederId, state.getFirstOverTime(), snapTime);
+                                int[] mc = queryMvtransCounts(feederId, state.getFirstOverTime(), snapTime);
+                                FhzgSeVoltageEvent event = FhzgSeVoltageEvent.builder()
+                                        .deviceId(deviceId).deviceName(feederName)
+                                        .deviceType(deviceType).alarmLevel(deviceType).alarmType(alarmType)
+                                        .feederId(feederId).feederName(feederName)
+                                        .subsId(subsId).countyId(countyId).countyName(countyName)
+                                        .threshold(threshold)
+                                        .firstOverTime(state.getFirstOverTime()).triggerTime(snapTime)
+                                        .triggerValue(voltageRatio)
+                                        .voltageRangeMax(vr[0]).voltageRangeMin(vr[1])
+                                        .overUpperMvtrans(mc[0]).overLowerMvtrans(mc[1])
+                                        .lastValue(voltageRatio).status(1).build();
+                                voltageEventMapper.insert(event);
+                                state.setAlarmTriggered(1); state.setAlarmEventId(event.getId());
+                                // 馈线越下限 → 异步触发重构算法
+                                if ("feeder".equals(deviceType) && "voltage_under".equals(alarmType)) {
+                                    eventPublisher.publishEvent(new ReconTriggerEvent(
+                                            event.getId(), feederId, feederName, countyId, subsId,
+                                            alarmType, snapTime, state.getFirstOverTime()));
+                                }
+                            } else if ("mvtrans".equals(deviceType)) {
+                                // === 台区越限 → 创建 mvtrans 级独立事件 ===
+                                String alarmType = resolveVoltageAlarmType(deviceType, voltageStatus);
+                                BigDecimal threshold = resolveVoltageThreshold(deviceType, voltageStatus);
+                                FhzgSeVoltageEvent event = FhzgSeVoltageEvent.builder()
+                                        .deviceId(deviceId).deviceName(feederName)
+                                        .deviceType(deviceType).alarmLevel(deviceType).alarmType(alarmType)
+                                        .feederId(feederId).feederName(feederName)
+                                        .subsId(subsId).countyId(countyId).countyName(countyName)
+                                        .mvtransId(mvtransId)
+                                        .threshold(threshold)
+                                        .firstOverTime(state.getFirstOverTime()).triggerTime(snapTime)
+                                        .triggerValue(voltageRatio).lastValue(voltageRatio).status(1)
+                                        .build();
+                                voltageEventMapper.insert(event);
+                                state.setAlarmTriggered(1);
+                                state.setAlarmEventId(event.getId());
+                            }
+                            // consumer 级别暂走旧逻辑(如有可后续优化)
+                        }
             } else {
                 if (state.getAlarmTriggered() == 1 && state.getAlarmEventId() != null) {
-                    FhzgSeVoltageEvent update = new FhzgSeVoltageEvent();
-                    update.setId(state.getAlarmEventId());
-                    update.setStatus(2); update.setRecoverTime(snapTime); update.setLastValue(voltageRatio);
-                    voltageEventMapper.updateById(update);
+                    // 只有 feeder 自身恢复才标聚合事件 status=2;mvtrans 恢复不动聚合事件
+                    if ("feeder".equals(deviceType)) {
+                        FhzgSeVoltageEvent update = new FhzgSeVoltageEvent();
+                        update.setId(state.getAlarmEventId());
+                        update.setStatus(2); update.setRecoverTime(snapTime); update.setLastValue(voltageRatio);
+                        voltageEventMapper.updateById(update);
+                    }
                 }
                 state.setFirstOverTime(null); state.setAlarmTriggered(0); state.setAlarmEventId(null);
             }
@@ -387,22 +407,6 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
         return new BigDecimal[]{maxV, minV};
     }
 
-    private int[] queryMvtransCounts(String feederId, LocalDateTime start, LocalDateTime end) {
-        List<FhzgSeVoltageEvent> list = voltageEventMapper.selectList(
-                new LambdaQueryWrapper<FhzgSeVoltageEvent>()
-                        .eq(FhzgSeVoltageEvent::getFeederId, feederId)
-                        .eq(FhzgSeVoltageEvent::getAlarmLevel, "mvtrans")
-                        .eq(FhzgSeVoltageEvent::getStatus, 1)
-                        .ge(FhzgSeVoltageEvent::getFirstOverTime, start)
-                        .le(FhzgSeVoltageEvent::getFirstOverTime, end));
-        int up = 0, down = 0;
-        for (FhzgSeVoltageEvent e : list) {
-            if (e.getAlarmType() != null && e.getAlarmType().contains("over")) up++;
-            else if (e.getAlarmType() != null && e.getAlarmType().contains("under")) down++;
-        }
-        return new int[]{up, down};
-    }
-
     /**
      * 查窗口内是否有任意过载断面(≥100%)
      */
@@ -442,6 +446,18 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
         return 0;
     }
 
+    private BigDecimal resolveVoltageThreshold(String deviceType, int status) {
+        if ("feeder".equals(deviceType)) return status == 1 ? SeMonitorThreshold.FEEDER_V_OVER : SeMonitorThreshold.FEEDER_V_UNDER;
+        if ("mvtrans".equals(deviceType)) return status == 1 ? SeMonitorThreshold.MVTRANS_V_OVER : SeMonitorThreshold.MVTRANS_V_UNDER;
+        switch (status) {
+            case 1: return SeMonitorThreshold.CONSUMER_GENERAL_V_OVER;
+            case 2: return SeMonitorThreshold.CONSUMER_SEVERE_V_OVER;
+            case 3: return SeMonitorThreshold.CONSUMER_GENERAL_V_UNDER;
+            case 4: return SeMonitorThreshold.CONSUMER_SEVERE_V_UNDER;
+            default: return null;
+        }
+    }
+
     private String resolveVoltageAlarmType(String deviceType, int status) {
         if ("consumer".equals(deviceType)) {
             switch (status) {
@@ -455,16 +471,23 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
         return status == 1 ? "voltage_over" : "voltage_under";
     }
 
-    private BigDecimal resolveVoltageThreshold(String deviceType, int status) {
-        if ("feeder".equals(deviceType)) return status == 1 ? SeMonitorThreshold.FEEDER_V_OVER : SeMonitorThreshold.FEEDER_V_UNDER;
-        if ("mvtrans".equals(deviceType)) return status == 1 ? SeMonitorThreshold.MVTRANS_V_OVER : SeMonitorThreshold.MVTRANS_V_UNDER;
-        switch (status) {
-            case 1: return SeMonitorThreshold.CONSUMER_GENERAL_V_OVER;
-            case 2: return SeMonitorThreshold.CONSUMER_SEVERE_V_OVER;
-            case 3: return SeMonitorThreshold.CONSUMER_GENERAL_V_UNDER;
-            case 4: return SeMonitorThreshold.CONSUMER_SEVERE_V_UNDER;
-            default: return null;
+    /**
+     * 查窗口内已有 mvtrans 越限事件数(用于 feeder 触发时预填 overUpper/overLowerMvtrans)
+     */
+    private int[] queryMvtransCounts(String feederId, LocalDateTime start, LocalDateTime end) {
+        List<FhzgSeVoltageEvent> list = voltageEventMapper.selectList(
+                new LambdaQueryWrapper<FhzgSeVoltageEvent>()
+                        .eq(FhzgSeVoltageEvent::getFeederId, feederId)
+                        .eq(FhzgSeVoltageEvent::getAlarmLevel, "mvtrans")
+                        .eq(FhzgSeVoltageEvent::getStatus, 1)
+                        .ge(FhzgSeVoltageEvent::getFirstOverTime, start)
+                        .le(FhzgSeVoltageEvent::getFirstOverTime, end));
+        int up = 0, down = 0;
+        for (FhzgSeVoltageEvent e : list) {
+            if (e.getAlarmType() != null && e.getAlarmType().contains("over")) up++;
+            else if (e.getAlarmType() != null && e.getAlarmType().contains("under")) down++;
         }
+        return new int[]{up, down};
     }
 
     // ============================================================
@@ -478,7 +501,17 @@ public class SeSnapshotServiceImpl implements SeSnapshotService {
         try { return new BigDecimal(v); } catch (NumberFormatException e) { return null; }
     }
 
+    /**
+     * 按 countyId 从 SeSnapshotConstants.COUNTIES 查 countyName。
+     * COUNTIES 的 key=countyId(=maintOrg UUID),value=[countySid, countyName]
+     */
     private String lookupCountyName(String countyId, Map<String, String[]> feederMap) {
+        if (countyId == null) return null;
+        for (Map.Entry<String, String[]> entry : SeSnapshotConstants.COUNTIES) {
+            if (countyId.equals(entry.getKey())) {
+                return entry.getValue()[1];
+            }
+        }
         return null;
     }