|
|
@@ -0,0 +1,249 @@
|
|
|
+package com.hdkj.lt.mq.service.indicator.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.hdkj.lt.mq.entity.indicator.AlarmEvent;
|
|
|
+import com.hdkj.lt.mq.service.indicator.AlarmEvaluateService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 告警评估服务实现
|
|
|
+ * <p>
|
|
|
+ * 解析算法返回的 rawResponse,分两步:
|
|
|
+ * <ol>
|
|
|
+ * <li>解析 over_limit_details + feeder_results,提取超限馈线和指标值</li>
|
|
|
+ * <li>调用 shouldAlarm() 自定义告警标志判断,满足条件才生成告警事件</li>
|
|
|
+ * </ol>
|
|
|
+ * <p>
|
|
|
+ * alarm_types 值域:
|
|
|
+ * <ul>
|
|
|
+ * <li>load_rate → 线路重过载</li>
|
|
|
+ * <li>voltage_over → 电压越限</li>
|
|
|
+ * <li>voltage_under → 电压越限</li>
|
|
|
+ * </ul>
|
|
|
+ *
|
|
|
+ * @author hermes
|
|
|
+ * @since 2026-07-17
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class AlarmEvaluateServiceImpl implements AlarmEvaluateService {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<AlarmEvent> evaluateAlarms(Long indicatorResultId, String batchId, Integer countyId, String rawResponseJson) {
|
|
|
+ log.info("评估告警 indicatorResultId={}, batchId={}, countyId={}, rawResponse={}",
|
|
|
+ indicatorResultId, batchId, countyId, rawResponseJson == null ? "null" : "provided");
|
|
|
+
|
|
|
+ List<AlarmEvent> alarmEvents = new ArrayList<AlarmEvent>();
|
|
|
+ if (StringUtils.isBlank(rawResponseJson)) {
|
|
|
+ log.warn("rawResponse 为空,跳过告警评估 indicatorResultId={}", indicatorResultId);
|
|
|
+ return alarmEvents;
|
|
|
+ }
|
|
|
+
|
|
|
+ JSONObject root = JSON.parseObject(rawResponseJson);
|
|
|
+ JSONObject calculationResult = root.getJSONObject("calculation_result");
|
|
|
+ if (calculationResult == null) {
|
|
|
+ log.warn("calculation_result 为空 indicatorResultId={}", indicatorResultId);
|
|
|
+ return alarmEvents;
|
|
|
+ }
|
|
|
+
|
|
|
+ JSONObject countyResult = calculationResult.getJSONObject("county_result");
|
|
|
+ if (countyResult == null) {
|
|
|
+ log.warn("county_result 为空 indicatorResultId={}", indicatorResultId);
|
|
|
+ return alarmEvents;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===== 第一步:解析超限数据 =====
|
|
|
+ // 从 over_limit_details 提取三类超限馈线
|
|
|
+ Map<String, List<String>> feederAlarmTypes = parseOverLimitDetails(countyResult);
|
|
|
+ if (feederAlarmTypes.isEmpty()) {
|
|
|
+ log.info("无超限馈线 indicatorResultId={}, batchId={}, countyId={}", indicatorResultId, batchId, countyId);
|
|
|
+ return alarmEvents;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===== 第二步:自定义告警标志判断 =====
|
|
|
+ if (!shouldAlarm(feederAlarmTypes, countyResult, root)) {
|
|
|
+ log.info("自定义告警判断未通过,不触发告警 indicatorResultId={}, batchId={}, countyId={}",
|
|
|
+ indicatorResultId, batchId, countyId);
|
|
|
+ return alarmEvents;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===== 第三步:构建告警事件 =====
|
|
|
+ // 构建 feeder_results 索引(feeder_id → 指标值)
|
|
|
+ Map<String, JSONObject> feederResultMap = buildFeederResultMap(calculationResult);
|
|
|
+
|
|
|
+ // 取县域基本信息
|
|
|
+ JSONObject countyBasicInfo = root.getJSONObject("county_basic_info");
|
|
|
+ String countySid = countyBasicInfo != null ? countyBasicInfo.getString("county_sid") : null;
|
|
|
+ String countyName = countyBasicInfo != null ? countyBasicInfo.getString("county_name") : null;
|
|
|
+
|
|
|
+ // 为每个超限馈线构建 AlarmEvent
|
|
|
+ for (Map.Entry<String, List<String>> entry : feederAlarmTypes.entrySet()) {
|
|
|
+ String feederId = entry.getKey();
|
|
|
+ List<String> types = entry.getValue();
|
|
|
+ String alarmTypes = String.join(",", types);
|
|
|
+
|
|
|
+ // 从 feeder_results 取该馈线的指标值
|
|
|
+ JSONObject feederResult = feederResultMap.get(feederId);
|
|
|
+ BigDecimal maxLoadRatePct = null;
|
|
|
+ BigDecimal maxVoltagePu = null;
|
|
|
+ BigDecimal minVoltagePu = null;
|
|
|
+ if (feederResult != null) {
|
|
|
+ JSONObject loadRate = feederResult.getJSONObject("load_rate");
|
|
|
+ if (loadRate != null) {
|
|
|
+ maxLoadRatePct = loadRate.getBigDecimal("max_load_rate_pct");
|
|
|
+ }
|
|
|
+ JSONObject voltagePu = feederResult.getJSONObject("voltage_pu");
|
|
|
+ if (voltagePu != null) {
|
|
|
+ maxVoltagePu = voltagePu.getBigDecimal("max_voltage_pu");
|
|
|
+ minVoltagePu = voltagePu.getBigDecimal("min_voltage_pu");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ AlarmEvent alarmEvent = AlarmEvent.builder()
|
|
|
+ .batchId(batchId)
|
|
|
+ .countyId(countyId)
|
|
|
+ .countySid(countySid)
|
|
|
+ .countyName(countyName)
|
|
|
+ .feederId(feederId)
|
|
|
+ .alarmTypes(alarmTypes)
|
|
|
+ .maxLoadRatePct(maxLoadRatePct)
|
|
|
+ .maxVoltagePu(maxVoltagePu)
|
|
|
+ .minVoltagePu(minVoltagePu)
|
|
|
+ .indicatorResultId(indicatorResultId)
|
|
|
+ .alarmDate(LocalDate.now())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ alarmEvents.add(alarmEvent);
|
|
|
+ log.info("告警评估-生成告警 feederId={}, alarmTypes={}, maxLoadRatePct={}, maxVoltagePu={}, minVoltagePu={}",
|
|
|
+ feederId, alarmTypes, maxLoadRatePct, maxVoltagePu, minVoltagePu);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("告警评估完成 indicatorResultId={}, batchId={}, countyId={}, alarmCount={}",
|
|
|
+ indicatorResultId, batchId, countyId, alarmEvents.size());
|
|
|
+ return alarmEvents;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================================================
|
|
|
+ // 自定义告警标志判断(预留 customization point)
|
|
|
+ // ============================================================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自定义告警标志判断
|
|
|
+ * <p>
|
|
|
+ * 自主判断指标数据是否满足告警条件。
|
|
|
+ * 满足返回 true → 继续触发下一步(生成告警事件)
|
|
|
+ * 不满足返回 false → 无事发生
|
|
|
+ * <p>
|
|
|
+ * 当前实现:算法返回 over_limit_details 有超限馈线即告警。
|
|
|
+ * 后续可在此自定义判断逻辑,例如:
|
|
|
+ * <ul>
|
|
|
+ * <li>自定义阈值(不依赖算法的 flags,自行判断 max_load_rate_pct > 阈值)</li>
|
|
|
+ * <li>白名单/黑名单(某些馈线不告警)</li>
|
|
|
+ * <li>时间段过滤(某些时段不告警)</li>
|
|
|
+ * <li>连续超限次数(N 次才告警)</li>
|
|
|
+ * </ul>
|
|
|
+ *
|
|
|
+ * @param feederAlarmTypes 超限馈线及告警类型(feederId → [load_rate / voltage_over / voltage_under])
|
|
|
+ * @param countyResult 县域汇总指标(load_rate / voltage_pu / loss / alarm)
|
|
|
+ * @param root 算法完整返回 JSON(含 county_basic_info / calculation_result)
|
|
|
+ * @return true=满足告警条件,false=不告警
|
|
|
+ */
|
|
|
+ private boolean shouldAlarm(Map<String, List<String>> feederAlarmTypes, JSONObject countyResult, JSONObject root) {
|
|
|
+ // TODO: 自定义告警标志判断逻辑
|
|
|
+ // 当前:算法判定超限即告警
|
|
|
+ return !feederAlarmTypes.isEmpty();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================================================
|
|
|
+ // 数据解析
|
|
|
+ // ============================================================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从 county_result.alarm.over_limit_details 解析三类超限馈线
|
|
|
+ *
|
|
|
+ * @return feederId → 告警类型列表(去重)
|
|
|
+ */
|
|
|
+ private Map<String, List<String>> parseOverLimitDetails(JSONObject countyResult) {
|
|
|
+ Map<String, List<String>> feederAlarmTypes = new LinkedHashMap<String, List<String>>();
|
|
|
+
|
|
|
+ JSONObject alarm = countyResult.getJSONObject("alarm");
|
|
|
+ if (alarm == null) {
|
|
|
+ return feederAlarmTypes;
|
|
|
+ }
|
|
|
+ JSONObject overLimitDetails = alarm.getJSONObject("over_limit_details");
|
|
|
+ if (overLimitDetails == null) {
|
|
|
+ return feederAlarmTypes;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> loadRateFeeders = parseFeederList(overLimitDetails, "load_rate_over_limit");
|
|
|
+ List<String> voltageOverFeeders = parseFeederList(overLimitDetails, "voltage_over_upper_limit");
|
|
|
+ List<String> voltageUnderFeeders = parseFeederList(overLimitDetails, "voltage_under_lower_limit");
|
|
|
+
|
|
|
+ addAlarmTypes(feederAlarmTypes, loadRateFeeders, "load_rate");
|
|
|
+ addAlarmTypes(feederAlarmTypes, voltageOverFeeders, "voltage_over");
|
|
|
+ addAlarmTypes(feederAlarmTypes, voltageUnderFeeders, "voltage_under");
|
|
|
+
|
|
|
+ return feederAlarmTypes;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从 over_limit_details 中解析某类超限馈线ID列表
|
|
|
+ */
|
|
|
+ private List<String> parseFeederList(JSONObject overLimitDetails, String key) {
|
|
|
+ JSONArray arr = overLimitDetails.getJSONArray(key);
|
|
|
+ if (arr == null || arr.isEmpty()) {
|
|
|
+ return new ArrayList<String>();
|
|
|
+ }
|
|
|
+ return arr.toJavaList(String.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将馈线ID和告警类型加入 map
|
|
|
+ */
|
|
|
+ private void addAlarmTypes(Map<String, List<String>> feederAlarmTypes, List<String> feederIds, String alarmType) {
|
|
|
+ for (String feederId : feederIds) {
|
|
|
+ if (StringUtils.isBlank(feederId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ List<String> types = feederAlarmTypes.get(feederId);
|
|
|
+ if (types == null) {
|
|
|
+ types = new ArrayList<String>();
|
|
|
+ feederAlarmTypes.put(feederId, types);
|
|
|
+ }
|
|
|
+ if (!types.contains(alarmType)) {
|
|
|
+ types.add(alarmType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建 feeder_results 索引:feeder_id → feederResult JSON 对象
|
|
|
+ */
|
|
|
+ private Map<String, JSONObject> buildFeederResultMap(JSONObject calculationResult) {
|
|
|
+ Map<String, JSONObject> map = new HashMap<String, JSONObject>();
|
|
|
+ JSONArray feederResults = calculationResult.getJSONArray("feeder_results");
|
|
|
+ if (feederResults == null || feederResults.isEmpty()) {
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+ for (int i = 0; i < feederResults.size(); i++) {
|
|
|
+ JSONObject feeder = feederResults.getJSONObject(i);
|
|
|
+ String feederId = feeder.getString("feeder_id");
|
|
|
+ if (StringUtils.isNotBlank(feederId)) {
|
|
|
+ map.put(feederId, feeder);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+}
|