|
|
@@ -0,0 +1,510 @@
|
|
|
+package com.hdkj.lt.bf.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
+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.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.event.MvtransOverVoltageEvent;
|
|
|
+import com.hdkj.lt.bf.event.ReconTriggerEvent;
|
|
|
+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.SeSnapshotService;
|
|
|
+import com.hdkj.fhzg.optimization.response.FeederGroupTreeNode;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.context.ApplicationEventPublisher;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.time.Duration;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class SeSnapshotServiceImpl implements SeSnapshotService {
|
|
|
+
|
|
|
+ private final FhzgSeSnapshotDetailMapper snapshotDetailMapper;
|
|
|
+ private final FhzgSeMonitorCurrentMapper monitorCurrentMapper;
|
|
|
+ private final FhzgSeMonitorVoltageMapper monitorVoltageMapper;
|
|
|
+ private final FhzgSeCurrentEventMapper currentEventMapper;
|
|
|
+ private final FhzgSeVoltageEventMapper voltageEventMapper;
|
|
|
+ private final FeederGroupApplication feederGroupApplication;
|
|
|
+ private final ApplicationEventPublisher eventPublisher;
|
|
|
+
|
|
|
+ private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void processSnapshots(String snapshotJsonArray) {
|
|
|
+ JSONArray arr = JSON.parseArray(snapshotJsonArray);
|
|
|
+ if (arr == null || arr.isEmpty()) return;
|
|
|
+ for (int i = 0; i < arr.size(); i++) {
|
|
|
+ processSnapshot(arr.getJSONObject(i).toJSONString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void processSnapshot(String snapshotJson) {
|
|
|
+ JSONObject root = JSON.parseObject(snapshotJson);
|
|
|
+ if (root == null) return;
|
|
|
+
|
|
|
+ JSONArray pointTimes = root.getJSONArray("pointTime");
|
|
|
+ if (pointTimes == null || pointTimes.isEmpty()) return;
|
|
|
+ LocalDateTime snapTime = LocalDateTime.parse(pointTimes.getString(0), DT_FMT);
|
|
|
+
|
|
|
+ Map<String, String[]> feederMap = buildFeederMapping();
|
|
|
+ List<FhzgSeSnapshotDetail> batch = new ArrayList<>();
|
|
|
+
|
|
|
+ // === 1. 馈线 ===
|
|
|
+ JSONArray feeders = root.getJSONArray("periodFeederSeResult");
|
|
|
+ if (feeders != null) {
|
|
|
+ for (int i = 0; i < feeders.size(); i++) {
|
|
|
+ JSONObject f = feeders.getJSONObject(i);
|
|
|
+ FhzgSeSnapshotDetail d = parseFeeder(f, snapTime, feederMap);
|
|
|
+ if (d != null) {
|
|
|
+ batch.add(d);
|
|
|
+ processFeederCurrent(f, d, snapTime, feederMap);
|
|
|
+ processFeederVoltage(f, d, snapTime, feederMap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // === 2. 配变 ===
|
|
|
+ JSONArray mvtrans = root.getJSONArray("periodMVTransSeResult");
|
|
|
+ if (mvtrans != null) {
|
|
|
+ for (int i = 0; i < mvtrans.size(); i++) {
|
|
|
+ JSONObject m = mvtrans.getJSONObject(i);
|
|
|
+ FhzgSeSnapshotDetail d = parseMvtrans(m, snapTime, feederMap);
|
|
|
+ if (d != null) {
|
|
|
+ batch.add(d);
|
|
|
+ processMvtransMonitor(m, d, snapTime, feederMap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ // 防重:过滤掉该断面时刻已存在的 device_id
|
|
|
+ Set<String> existingIds = new HashSet<>();
|
|
|
+ snapshotDetailMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<FhzgSeSnapshotDetail>()
|
|
|
+ .select(FhzgSeSnapshotDetail::getDeviceId)
|
|
|
+ .eq(FhzgSeSnapshotDetail::getSnapshotTime, snapTime)
|
|
|
+ .in(FhzgSeSnapshotDetail::getDeviceId,
|
|
|
+ batch.stream().map(FhzgSeSnapshotDetail::getDeviceId).collect(java.util.stream.Collectors.toList()))
|
|
|
+ ).forEach(e -> existingIds.add(e.getDeviceId()));
|
|
|
+
|
|
|
+ int inserted = 0;
|
|
|
+ for (FhzgSeSnapshotDetail d : batch) {
|
|
|
+ if (existingIds.contains(d.getDeviceId())) continue;
|
|
|
+ snapshotDetailMapper.insert(d);
|
|
|
+ inserted++;
|
|
|
+ }
|
|
|
+ log.info("状估断面处理完成 snapTime={}, 写入{}条(去重跳过{}条)",
|
|
|
+ snapTime, inserted, batch.size() - inserted);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================================================
|
|
|
+ // 馈线
|
|
|
+ // ============================================================
|
|
|
+
|
|
|
+ private FhzgSeSnapshotDetail parseFeeder(JSONObject f, LocalDateTime snapTime, Map<String, String[]> feederMap) {
|
|
|
+ String feederId = f.getString("psrId");
|
|
|
+ String[] loc = feederMap.getOrDefault(feederId, new String[]{"", "", ""});
|
|
|
+ BigDecimal loadRate = firstValue(f.getJSONArray("loadRates"));
|
|
|
+ BigDecimal voltage = firstValue(f.getJSONArray("seoUs"));
|
|
|
+ BigDecimal voltageRatio = voltage != null
|
|
|
+ ? voltage.divide(SeMonitorThreshold.FEEDER_BASE_KV, 4, RoundingMode.HALF_UP) : null;
|
|
|
+ return FhzgSeSnapshotDetail.builder()
|
|
|
+ .snapshotTime(snapTime).deviceId(feederId).deviceType("feeder")
|
|
|
+ .feederId(feederId).feederName(loc.length > 2 ? loc[2] : null)
|
|
|
+ .subsId(loc[0]).countyId(loc[1])
|
|
|
+ .activePower(firstValue(f.getJSONArray("seoPs")))
|
|
|
+ .reactivePower(firstValue(f.getJSONArray("seoQs")))
|
|
|
+ .currentValue(firstValue(f.getJSONArray("seoIs")))
|
|
|
+ .voltageValue(voltage).loadRate(loadRate).voltageRatio(voltageRatio)
|
|
|
+ .currentStatus(calcCurrentStatus(loadRate))
|
|
|
+ .voltageStatus(calcFeederVoltageStatus(voltageRatio))
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void processFeederCurrent(JSONObject f, FhzgSeSnapshotDetail detail,
|
|
|
+ LocalDateTime snapTime, Map<String, String[]> feederMap) {
|
|
|
+ if (detail.getCurrentStatus() == null) return;
|
|
|
+ String feederId = detail.getFeederId();
|
|
|
+ String[] loc = feederMap.get(feederId);
|
|
|
+ String feederName = loc != null && loc.length > 2 ? loc[2] : null;
|
|
|
+ String countyId = loc != null ? loc[1] : "";
|
|
|
+ String subsId = loc != null ? loc[0] : "";
|
|
|
+ BigDecimal loadRate = detail.getLoadRate();
|
|
|
+ int status = detail.getCurrentStatus();
|
|
|
+ boolean isOverLimit = status != 0;
|
|
|
+
|
|
|
+ FhzgSeMonitorCurrent state = monitorCurrentMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<FhzgSeMonitorCurrent>()
|
|
|
+ .eq(FhzgSeMonitorCurrent::getFeederId, feederId).last("LIMIT 1"));
|
|
|
+
|
|
|
+ if (state == null) {
|
|
|
+ state = FhzgSeMonitorCurrent.builder()
|
|
|
+ .feederId(feederId).feederName(feederName)
|
|
|
+ .lastLoadRate(loadRate).lastStatus(status).lastSnapTime(snapTime)
|
|
|
+ .alarmTriggered(0).build();
|
|
|
+ if (isOverLimit) state.setFirstOverTime(snapTime);
|
|
|
+ monitorCurrentMapper.insert(state);
|
|
|
+ } else {
|
|
|
+ LocalDateTime lastSnap = state.getLastSnapTime();
|
|
|
+ if (lastSnap != null && Duration.between(lastSnap, snapTime).toMinutes() > SeMonitorThreshold.SNAP_INTERVAL_MINUTES) {
|
|
|
+ state.setFirstOverTime(null);
|
|
|
+ state.setAlarmTriggered(0);
|
|
|
+ state.setAlarmEventId(null);
|
|
|
+ }
|
|
|
+ BigDecimal maxLoadRate = state.getLastLoadRate();
|
|
|
+ if (loadRate != null && (maxLoadRate == null || loadRate.compareTo(maxLoadRate) > 0))
|
|
|
+ maxLoadRate = loadRate;
|
|
|
+
|
|
|
+ if (isOverLimit) {
|
|
|
+ if (state.getFirstOverTime() == null) state.setFirstOverTime(snapTime);
|
|
|
+ if (state.getFirstOverTime() != null
|
|
|
+ && Duration.between(state.getFirstOverTime(), snapTime).toMinutes() >= SeMonitorThreshold.TRIGGER_DURATION_MINUTES
|
|
|
+ && state.getAlarmTriggered() == 0) {
|
|
|
+ // 触发时往回看1小时窗口:有任意断面≥100%算过载,否则重载
|
|
|
+ boolean hasOverload = hasOverloadSnapshotInWindow(feederId, state.getFirstOverTime(), snapTime);
|
|
|
+ String alarmType = hasOverload ? "current_overload" : "current_heavy";
|
|
|
+ String countyName = lookupCountyName(countyId, feederMap);
|
|
|
+ FhzgSeCurrentEvent event = FhzgSeCurrentEvent.builder()
|
|
|
+ .feederId(feederId).feederName(feederName).alarmType(alarmType)
|
|
|
+ .countyId(countyId).countyName(countyName).subsId(subsId)
|
|
|
+ .firstOverTime(state.getFirstOverTime()).triggerTime(snapTime)
|
|
|
+ .triggerLoadRate(loadRate).maxLoadRate(maxLoadRate).lastLoadRate(loadRate).status(1)
|
|
|
+ .build();
|
|
|
+ currentEventMapper.insert(event);
|
|
|
+ state.setAlarmTriggered(1);
|
|
|
+ state.setAlarmEventId(event.getId());
|
|
|
+ // 异步触发重构算法(需要 feeder_id + point_time)
|
|
|
+ eventPublisher.publishEvent(new ReconTriggerEvent(
|
|
|
+ event.getId(), feederId, feederName, countyId, subsId,
|
|
|
+ alarmType, snapTime, state.getFirstOverTime()));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (state.getAlarmTriggered() == 1 && state.getAlarmEventId() != null) {
|
|
|
+ FhzgSeCurrentEvent update = new FhzgSeCurrentEvent();
|
|
|
+ update.setId(state.getAlarmEventId());
|
|
|
+ update.setStatus(2); update.setRecoverTime(snapTime);
|
|
|
+ update.setLastLoadRate(loadRate); update.setMaxLoadRate(maxLoadRate);
|
|
|
+ currentEventMapper.updateById(update);
|
|
|
+ }
|
|
|
+ state.setFirstOverTime(null); state.setAlarmTriggered(0); state.setAlarmEventId(null);
|
|
|
+ }
|
|
|
+ state.setLastLoadRate(loadRate); state.setLastStatus(status); state.setLastSnapTime(snapTime);
|
|
|
+ monitorCurrentMapper.updateById(state);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void processFeederVoltage(JSONObject f, FhzgSeSnapshotDetail detail,
|
|
|
+ LocalDateTime snapTime, Map<String, String[]> feederMap) {
|
|
|
+ if (detail.getVoltageStatus() == null) return;
|
|
|
+ String[] loc = feederMap.get(detail.getFeederId());
|
|
|
+ checkVoltageMonitor(detail.getFeederId(), "feeder", detail.getVoltageRatio(), detail.getVoltageStatus(),
|
|
|
+ snapTime, detail.getFeederId(), loc != null && loc.length > 2 ? loc[2] : null,
|
|
|
+ loc != null ? loc[0] : "", loc != null ? loc[1] : "", null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================================================
|
|
|
+ // 配变
|
|
|
+ // ============================================================
|
|
|
+
|
|
|
+ private FhzgSeSnapshotDetail parseMvtrans(JSONObject m, LocalDateTime snapTime, Map<String, String[]> feederMap) {
|
|
|
+ String mvtransId = m.getString("psrId");
|
|
|
+ String feederId = m.getString("feederId");
|
|
|
+ String[] loc = feederMap.getOrDefault(feederId, new String[]{"", "", ""});
|
|
|
+ BigDecimal lvVoltageA = firstValue(m.getJSONArray("seiLvUas"));
|
|
|
+ BigDecimal voltageRatio = lvVoltageA != null
|
|
|
+ ? lvVoltageA.divide(SeMonitorThreshold.MVTRANS_BASE_KV, 4, RoundingMode.HALF_UP) : null;
|
|
|
+ BigDecimal capacity = null;
|
|
|
+ String capStr = m.getString("capacitys");
|
|
|
+ if (capStr != null) { try { capacity = new BigDecimal(capStr); } catch (NumberFormatException ignored) {} }
|
|
|
+ return FhzgSeSnapshotDetail.builder()
|
|
|
+ .snapshotTime(snapTime).deviceId(mvtransId).deviceType("mvtrans")
|
|
|
+ .feederId(feederId).subsId(loc[0]).countyId(loc[1])
|
|
|
+ .lvVoltageA(lvVoltageA).capacity(capacity).voltageRatio(voltageRatio)
|
|
|
+ .voltageStatus(calcMvtransVoltageStatus(voltageRatio))
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void processMvtransMonitor(JSONObject m, FhzgSeSnapshotDetail detail,
|
|
|
+ LocalDateTime snapTime, Map<String, String[]> feederMap) {
|
|
|
+ if (detail.getVoltageStatus() == null) return;
|
|
|
+ String mvtransId = detail.getDeviceId();
|
|
|
+ String[] loc = feederMap.get(detail.getFeederId());
|
|
|
+ int status = detail.getVoltageStatus();
|
|
|
+ if (status == 0) recoverUserAlarms(mvtransId, snapTime);
|
|
|
+
|
|
|
+ checkVoltageMonitor(mvtransId, "mvtrans", detail.getVoltageRatio(), status,
|
|
|
+ snapTime, detail.getFeederId(), loc != null && loc.length > 2 ? loc[2] : null,
|
|
|
+ loc != null ? loc[0] : "", loc != null ? loc[1] : "", mvtransId, null);
|
|
|
+
|
|
|
+ if (status != 0) {
|
|
|
+ eventPublisher.publishEvent(new MvtransOverVoltageEvent(
|
|
|
+ this, mvtransId, detail.getFeederId(),
|
|
|
+ loc != null ? loc[1] : "", loc != null ? loc[0] : "",
|
|
|
+ snapTime, detail.getVoltageRatio()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================================================
|
|
|
+ // 电压状态机
|
|
|
+ // ============================================================
|
|
|
+
|
|
|
+ private void checkVoltageMonitor(String deviceId, String deviceType,
|
|
|
+ BigDecimal voltageRatio, int voltageStatus,
|
|
|
+ LocalDateTime snapTime, String feederId, String feederName,
|
|
|
+ String subsId, String countyId, String mvtransId, String consumerId) {
|
|
|
+ FhzgSeMonitorVoltage state = monitorVoltageMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<FhzgSeMonitorVoltage>()
|
|
|
+ .eq(FhzgSeMonitorVoltage::getDeviceId, deviceId)
|
|
|
+ .eq(FhzgSeMonitorVoltage::getDeviceType, deviceType).last("LIMIT 1"));
|
|
|
+ boolean isOverLimit = voltageStatus != 0;
|
|
|
+
|
|
|
+ if (state == null) {
|
|
|
+ state = FhzgSeMonitorVoltage.builder()
|
|
|
+ .deviceId(deviceId).deviceType(deviceType)
|
|
|
+ .feederId(feederId).feederName(feederName)
|
|
|
+ .subsId(subsId).countyId(countyId)
|
|
|
+ .mvtransId(mvtransId).consumerId(consumerId)
|
|
|
+ .lastVoltageRatio(voltageRatio).lastVoltageStatus(voltageStatus)
|
|
|
+ .lastSnapTime(snapTime).alarmTriggered(0).build();
|
|
|
+ if (isOverLimit) state.setFirstOverTime(snapTime);
|
|
|
+ monitorVoltageMapper.insert(state);
|
|
|
+ } else {
|
|
|
+ LocalDateTime lastSnap = state.getLastSnapTime();
|
|
|
+ if (lastSnap != null && Duration.between(lastSnap, snapTime).toMinutes() > SeMonitorThreshold.SNAP_INTERVAL_MINUTES) {
|
|
|
+ state.setFirstOverTime(null); state.setAlarmTriggered(0); state.setAlarmEventId(null);
|
|
|
+ }
|
|
|
+ if (isOverLimit) {
|
|
|
+ if (state.getFirstOverTime() == null) state.setFirstOverTime(snapTime);
|
|
|
+ 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()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } 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);
|
|
|
+ }
|
|
|
+ state.setFirstOverTime(null); state.setAlarmTriggered(0); state.setAlarmEventId(null);
|
|
|
+ }
|
|
|
+ state.setLastVoltageRatio(voltageRatio); state.setLastVoltageStatus(voltageStatus);
|
|
|
+ state.setLastSnapTime(snapTime);
|
|
|
+ monitorVoltageMapper.updateById(state);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================================================
|
|
|
+ // 配变→用户恢复
|
|
|
+ // ============================================================
|
|
|
+
|
|
|
+ private void recoverUserAlarms(String mvtransId, LocalDateTime snapTime) {
|
|
|
+ FhzgSeVoltageEvent updateEvent = new FhzgSeVoltageEvent();
|
|
|
+ updateEvent.setStatus(2); updateEvent.setRecoverTime(snapTime);
|
|
|
+ voltageEventMapper.update(updateEvent,
|
|
|
+ new LambdaQueryWrapper<FhzgSeVoltageEvent>()
|
|
|
+ .eq(FhzgSeVoltageEvent::getMvtransId, mvtransId)
|
|
|
+ .eq(FhzgSeVoltageEvent::getAlarmLevel, "consumer")
|
|
|
+ .eq(FhzgSeVoltageEvent::getStatus, 1));
|
|
|
+ FhzgSeMonitorVoltage reset = new FhzgSeMonitorVoltage();
|
|
|
+ reset.setFirstOverTime(null); reset.setAlarmTriggered(0);
|
|
|
+ reset.setAlarmEventId(null); reset.setLastVoltageStatus(0);
|
|
|
+ monitorVoltageMapper.update(reset,
|
|
|
+ new LambdaQueryWrapper<FhzgSeMonitorVoltage>()
|
|
|
+ .eq(FhzgSeMonitorVoltage::getMvtransId, mvtransId)
|
|
|
+ .eq(FhzgSeMonitorVoltage::getDeviceType, "consumer"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================================================
|
|
|
+ // 查询辅助
|
|
|
+ // ============================================================
|
|
|
+
|
|
|
+ private BigDecimal[] queryVoltageRangeFromSnap(String feederId, LocalDateTime start, LocalDateTime end) {
|
|
|
+ List<FhzgSeSnapshotDetail> list = snapshotDetailMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<FhzgSeSnapshotDetail>()
|
|
|
+ .eq(FhzgSeSnapshotDetail::getFeederId, feederId)
|
|
|
+ .eq(FhzgSeSnapshotDetail::getDeviceType, "feeder")
|
|
|
+ .isNotNull(FhzgSeSnapshotDetail::getVoltageRatio)
|
|
|
+ .ge(FhzgSeSnapshotDetail::getSnapshotTime, start)
|
|
|
+ .le(FhzgSeSnapshotDetail::getSnapshotTime, end));
|
|
|
+ BigDecimal maxV = null, minV = null;
|
|
|
+ for (FhzgSeSnapshotDetail s : list) {
|
|
|
+ BigDecimal v = s.getVoltageRatio();
|
|
|
+ if (v == null) continue;
|
|
|
+ if (maxV == null || v.compareTo(maxV) > 0) maxV = v;
|
|
|
+ if (minV == null || v.compareTo(minV) < 0) minV = v;
|
|
|
+ }
|
|
|
+ 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%)
|
|
|
+ */
|
|
|
+ private boolean hasOverloadSnapshotInWindow(String feederId, LocalDateTime start, LocalDateTime end) {
|
|
|
+ Long count = snapshotDetailMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<FhzgSeSnapshotDetail>()
|
|
|
+ .eq(FhzgSeSnapshotDetail::getFeederId, feederId)
|
|
|
+ .eq(FhzgSeSnapshotDetail::getDeviceType, "feeder")
|
|
|
+ .ge(FhzgSeSnapshotDetail::getLoadRate, SeMonitorThreshold.LOAD_RATE_OVERLOAD)
|
|
|
+ .ge(FhzgSeSnapshotDetail::getSnapshotTime, start)
|
|
|
+ .le(FhzgSeSnapshotDetail::getSnapshotTime, end));
|
|
|
+ return count != null && count > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================================================
|
|
|
+ // 状态判断 / 阈值
|
|
|
+ // ============================================================
|
|
|
+
|
|
|
+ private int calcCurrentStatus(BigDecimal loadRate) {
|
|
|
+ if (loadRate == null) return 0;
|
|
|
+ if (loadRate.compareTo(SeMonitorThreshold.LOAD_RATE_OVERLOAD) >= 0) return 2;
|
|
|
+ if (loadRate.compareTo(SeMonitorThreshold.LOAD_RATE_HEAVY) >= 0) return 1;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int calcFeederVoltageStatus(BigDecimal ratio) {
|
|
|
+ if (ratio == null) return 0;
|
|
|
+ if (ratio.compareTo(SeMonitorThreshold.FEEDER_V_OVER) > 0) return 1;
|
|
|
+ if (ratio.compareTo(SeMonitorThreshold.FEEDER_V_UNDER) < 0) return 2;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int calcMvtransVoltageStatus(BigDecimal ratio) {
|
|
|
+ if (ratio == null) return 0;
|
|
|
+ if (ratio.compareTo(SeMonitorThreshold.MVTRANS_V_OVER) > 0) return 1;
|
|
|
+ if (ratio.compareTo(SeMonitorThreshold.MVTRANS_V_UNDER) < 0) return 2;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String resolveVoltageAlarmType(String deviceType, int status) {
|
|
|
+ if ("consumer".equals(deviceType)) {
|
|
|
+ switch (status) {
|
|
|
+ case 1: return "voltage_over_general";
|
|
|
+ case 2: return "voltage_over_severe";
|
|
|
+ case 3: return "voltage_under_general";
|
|
|
+ case 4: return "voltage_under_severe";
|
|
|
+ default: return "voltage_over";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================================================
|
|
|
+ // 辅助
|
|
|
+ // ============================================================
|
|
|
+
|
|
|
+ private BigDecimal firstValue(JSONArray arr) {
|
|
|
+ if (arr == null || arr.isEmpty()) return null;
|
|
|
+ String v = arr.getString(0);
|
|
|
+ if (v == null || v.isEmpty()) return null;
|
|
|
+ try { return new BigDecimal(v); } catch (NumberFormatException e) { return null; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String lookupCountyName(String countyId, Map<String, String[]> feederMap) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, String[]> buildFeederMapping() {
|
|
|
+ Map<String, String[]> map = new HashMap<>();
|
|
|
+ try {
|
|
|
+ List<FeederGroupTreeNode> tree = feederGroupApplication.getFeederGroupTree();
|
|
|
+ if (tree != null) buildFeederMappingRecursive(tree, map, "", "");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("构建馈线映射失败", e);
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void buildFeederMappingRecursive(List<FeederGroupTreeNode> nodes, Map<String, String[]> map,
|
|
|
+ String parentSubsId, String parentCountyId) {
|
|
|
+ if (nodes == null) return;
|
|
|
+ for (FeederGroupTreeNode node : nodes) {
|
|
|
+ String subsId = parentSubsId, countyId = parentCountyId;
|
|
|
+ if (node.getType() == 2) countyId = node.getId();
|
|
|
+ else if (node.getType() == 3) subsId = node.getId();
|
|
|
+ else if (node.getType() == 4) map.put(node.getId(), new String[]{subsId, countyId, node.getName()});
|
|
|
+ if (node.getChildren() != null && !node.getChildren().isEmpty())
|
|
|
+ buildFeederMappingRecursive(node.getChildren(), map, subsId, countyId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|