|
@@ -0,0 +1,559 @@
|
|
|
|
|
+package com.hdkj.lt.bf.service.staticgrid.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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
|
|
+import com.hdkj.lt.bf.constants.OptimizationConstants;
|
|
|
|
|
+import com.hdkj.lt.bf.constants.StaticGridMetricTypeEnum;
|
|
|
|
|
+import com.hdkj.lt.bf.constants.StaticGridMetricTypeEnum.DetailSource;
|
|
|
|
|
+import com.hdkj.lt.bf.constants.StaticGridMetricTypeEnum.FieldSpec;
|
|
|
|
|
+import com.hdkj.lt.bf.entity.dto.StaticGridDetailQueryDTO;
|
|
|
|
|
+import com.hdkj.lt.bf.entity.staticgrid.StaticIndicatorMetric;
|
|
|
|
|
+import com.hdkj.lt.bf.entity.staticgrid.StaticIndicatorResult;
|
|
|
|
|
+import com.hdkj.lt.bf.entity.staticgrid.StaticProblemDetail;
|
|
|
|
|
+import com.hdkj.lt.bf.entity.vo.StaticGridDetailItemVO;
|
|
|
|
|
+import com.hdkj.lt.bf.entity.vo.StaticGridDetailResultVO;
|
|
|
|
|
+import com.hdkj.lt.bf.mapper.staticgrid.StaticIndicatorMetricMapper;
|
|
|
|
|
+import com.hdkj.lt.bf.mapper.staticgrid.StaticIndicatorResultMapper;
|
|
|
|
|
+import com.hdkj.lt.bf.mapper.staticgrid.StaticProblemDetailMapper;
|
|
|
|
|
+import com.hdkj.lt.bf.service.staticgrid.StaticGridDetailService;
|
|
|
|
|
+import com.hdkj.lt.utils.cache.RedisRepository;
|
|
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.math.RoundingMode;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+import java.time.YearMonth;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 静态网架指标详情服务实现
|
|
|
|
|
+ * 馈线列表来自 t_static_indicator_metric(level=feeder,按主指标 metric_code + 筛选条件)。
|
|
|
|
|
+ * details 取数遵循「表优先」:reason←t_static_problem_detail.description;value/unit←指标表;
|
|
|
|
|
+ * 表里没有的结构化字段←t_static_indicator_result.raw_response 主指标 details[0]。
|
|
|
|
|
+ * deviceIds←t_static_problem_detail.object_id(object_type=device),缺失则回退 raw_response details[0].device_ids。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author liuaini
|
|
|
|
|
+ * @since 2026-08-06
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+@RequiredArgsConstructor
|
|
|
|
|
+public class StaticGridDetailServiceImpl
|
|
|
|
|
+ extends ServiceImpl<StaticIndicatorResultMapper, StaticIndicatorResult>
|
|
|
|
|
+ implements StaticGridDetailService {
|
|
|
|
|
+
|
|
|
|
|
+ private final StaticIndicatorMetricMapper metricMapper;
|
|
|
|
|
+ private final StaticProblemDetailMapper problemDetailMapper;
|
|
|
|
|
+ private final RedisRepository redisRepository;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public StaticGridDetailResultVO queryDetail(StaticGridDetailQueryDTO query) {
|
|
|
|
|
+ if (query == null || query.getType() == null
|
|
|
|
|
+ || StringUtils.isBlank(query.getId())
|
|
|
|
|
+ || StringUtils.isBlank(query.getMetricType())) {
|
|
|
|
|
+ log.warn("静态网架指标详情参数缺失: {}", query);
|
|
|
|
|
+ return emptyResult();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int type = query.getType();
|
|
|
|
|
+ if (type != 2 && type != 3) {
|
|
|
|
|
+ log.warn("不支持的统计维度 type={}", type);
|
|
|
|
|
+ return emptyResult();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ StaticGridMetricTypeEnum metricEnum = StaticGridMetricTypeEnum.fromMetricType(query.getMetricType());
|
|
|
|
|
+ if (metricEnum == null) {
|
|
|
|
|
+ log.warn("不支持的 metricType={}", query.getMetricType());
|
|
|
|
|
+ return emptyResult();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ YearMonth ym = YearMonth.now();
|
|
|
|
|
+// String cacheKey = OptimizationConstants.CACHE_KEY_GRID_DETAIL_PREFIX
|
|
|
|
|
+// + type + ":" + query.getId() + ":" + query.getMetricType() + ":" + ym;
|
|
|
|
|
+// Object cached = redisRepository.get(cacheKey);
|
|
|
|
|
+// if (cached instanceof StaticGridDetailResultVO) {
|
|
|
|
|
+// return (StaticGridDetailResultVO) cached;
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+ // redisRepository.setExpire(cacheKey, data,
|
|
|
|
|
+// OptimizationConstants.CACHE_TTL_GRID_DETAIL_MINUTES, TimeUnit.MINUTES);
|
|
|
|
|
+ return doQuery(type, query.getId(), metricEnum, ym);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private StaticGridDetailResultVO doQuery(int type, String id,StaticGridMetricTypeEnum e, YearMonth ym) {
|
|
|
|
|
+ // 1. 定位当月最新批次结果
|
|
|
|
|
+ Long chosenResultId;
|
|
|
|
|
+ String rawResponse;
|
|
|
|
|
+ if (type == 2) {
|
|
|
|
|
+ StaticIndicatorResult result = getLatestByCountyId(id, ym);
|
|
|
|
|
+ if (result == null) {
|
|
|
|
|
+ log.warn("区县当月无静态网架数据 countyId={}, month={}", id, ym);
|
|
|
|
|
+ return emptyResult();
|
|
|
|
|
+ }
|
|
|
|
|
+ chosenResultId = result.getId();
|
|
|
|
|
+ rawResponse = result.getRawResponse();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ List<StaticIndicatorResult> monthResults = listByMonth(ym);
|
|
|
|
|
+ if (monthResults.isEmpty()) {
|
|
|
|
|
+ log.warn("当月无静态网架数据 month={}", ym);
|
|
|
|
|
+ return emptyResult();
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<Long, StaticIndicatorResult> resultById = monthResults.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(StaticIndicatorResult::getId, r -> r, (a, b) -> a));
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorMetric> subWrapper = new LambdaQueryWrapper<StaticIndicatorMetric>()
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getLevel, "feeder")
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getSourceSubstationId, id)
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getMetricCode, e.getPrimaryMetricCode())
|
|
|
|
|
+ .in(StaticIndicatorMetric::getResultId, resultById.keySet());
|
|
|
|
|
+ List<StaticIndicatorMetric> subMetrics = metricMapper.selectList(subWrapper);
|
|
|
|
|
+ if (subMetrics.isEmpty()) {
|
|
|
|
|
+ log.warn("变电站当月无 feeder 级指标 substationId={}", id);
|
|
|
|
|
+ return emptyResult();
|
|
|
|
|
+ }
|
|
|
|
|
+ chosenResultId = subMetrics.stream()
|
|
|
|
|
+ .map(StaticIndicatorMetric::getResultId)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .max(Comparator.comparing(rid -> resultById.get(rid).getCreateTime()))
|
|
|
|
|
+ .orElse(null);
|
|
|
|
|
+ StaticIndicatorResult result = resultById.get(chosenResultId);
|
|
|
|
|
+ rawResponse = result.getRawResponse();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 取问题馈线列表(主指标 + 筛选条件)
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorMetric> feederWrapper = new LambdaQueryWrapper<StaticIndicatorMetric>()
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getLevel, "feeder")
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getResultId, chosenResultId)
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getMetricCode, e.getPrimaryMetricCode())
|
|
|
|
|
+ .orderByAsc(StaticIndicatorMetric::getFeederName);
|
|
|
|
|
+ if (type == 3) {
|
|
|
|
|
+ feederWrapper.eq(StaticIndicatorMetric::getSourceSubstationId, id);
|
|
|
|
|
+ }
|
|
|
|
|
+ applyValueCondition(feederWrapper, e);
|
|
|
|
|
+ List<StaticIndicatorMetric> feederMetrics = metricMapper.selectList(feederWrapper);
|
|
|
|
|
+ if (feederMetrics.isEmpty()) {
|
|
|
|
|
+ return emptyResult();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<String> feederIds = feederMetrics.stream()
|
|
|
|
|
+ .map(StaticIndicatorMetric::getFeederId)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 批量取 value/unit 指标行
|
|
|
|
|
+ Map<String, Map<String, StaticIndicatorMetric>> valueMetricsByFeeder = loadValueMetrics(e, chosenResultId, feederIds);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 批量取问题详情(主指标,object_type=device)
|
|
|
|
|
+ Map<String, StaticProblemDetail> problemDetailByFeeder = loadProblemDetails(e, chosenResultId, feederIds);
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 解析 raw_response,建立 feederId -> 主指标 details 数组
|
|
|
|
|
+ Map<String, JSONArray> rawDetailsByFeeder = parseRawDetails(rawResponse, e.getPrimaryMetricCode(), feederIds);
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 组装
|
|
|
|
|
+ List<StaticGridDetailItemVO> items = new ArrayList<>(feederMetrics.size());
|
|
|
|
|
+ for (StaticIndicatorMetric m : feederMetrics) {
|
|
|
|
|
+ items.add(buildItem(m, e, valueMetricsByFeeder, problemDetailByFeeder, rawDetailsByFeeder));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return buildResult(e, items);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 主表 / 指标查询辅助
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * type=2 区县:按 county_id + 月份取最新一条主表记录
|
|
|
|
|
+ */
|
|
|
|
|
+ private StaticIndicatorResult getLatestByCountyId(String countyId, YearMonth ym) {
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorResult> wrapper = new LambdaQueryWrapper<StaticIndicatorResult>()
|
|
|
|
|
+ .eq(StaticIndicatorResult::getCountyId, countyId)
|
|
|
|
|
+ .ge(StaticIndicatorResult::getCreateTime, monthStart(ym))
|
|
|
|
|
+ .le(StaticIndicatorResult::getCreateTime, monthEnd(ym))
|
|
|
|
|
+ .orderByDesc(StaticIndicatorResult::getCreateTime)
|
|
|
|
|
+ .last("LIMIT 1");
|
|
|
|
|
+ return getOne(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 当月所有主表批次(type=3 用:先拿到月份范围内的结果再按变电站过滤)
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<StaticIndicatorResult> listByMonth(YearMonth ym) {
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorResult> wrapper = new LambdaQueryWrapper<StaticIndicatorResult>()
|
|
|
|
|
+ .ge(StaticIndicatorResult::getCreateTime, monthStart(ym))
|
|
|
|
|
+ .le(StaticIndicatorResult::getCreateTime, monthEnd(ym))
|
|
|
|
|
+ .orderByDesc(StaticIndicatorResult::getCreateTime);
|
|
|
|
|
+ return list(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void applyValueCondition(LambdaQueryWrapper<StaticIndicatorMetric> wrapper, StaticGridMetricTypeEnum e) {
|
|
|
|
|
+ switch (e.getFilterType()) {
|
|
|
|
|
+ case VALUE_TRUE:
|
|
|
|
|
+ wrapper.eq(StaticIndicatorMetric::getValue, "true");
|
|
|
|
|
+ break;
|
|
|
|
|
+ case VALUE_FALSE:
|
|
|
|
|
+ wrapper.eq(StaticIndicatorMetric::getValue, "false");
|
|
|
|
|
+ break;
|
|
|
|
|
+ case IS_PROBLEM_TRUE:
|
|
|
|
|
+ wrapper.eq(StaticIndicatorMetric::getIsProblem, 1);
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 批量查询 details 所需的 value/unit 指标行,按 feederId -> metricCode 索引
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, Map<String, StaticIndicatorMetric>> loadValueMetrics(StaticGridMetricTypeEnum e,
|
|
|
|
|
+ Long resultId, List<String> feederIds) {
|
|
|
|
|
+ Set<String> valueCodes = new LinkedHashSet<>();
|
|
|
|
|
+ for (FieldSpec fs : e.getDetailFields()) {
|
|
|
|
|
+ if ((fs.getSource() == DetailSource.METRIC_VALUE || fs.getSource() == DetailSource.METRIC_UNIT)
|
|
|
|
|
+ && fs.getRef() != null) {
|
|
|
|
|
+ valueCodes.add(fs.getRef());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Map<String, StaticIndicatorMetric>> result = new HashMap<>();
|
|
|
|
|
+ if (valueCodes.isEmpty()) {
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorMetric> wrapper = new LambdaQueryWrapper<StaticIndicatorMetric>()
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getLevel, "feeder")
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getResultId, resultId)
|
|
|
|
|
+ .in(StaticIndicatorMetric::getFeederId, feederIds)
|
|
|
|
|
+ .in(StaticIndicatorMetric::getMetricCode, valueCodes);
|
|
|
|
|
+ List<StaticIndicatorMetric> metrics = metricMapper.selectList(wrapper);
|
|
|
|
|
+ for (StaticIndicatorMetric m : metrics) {
|
|
|
|
|
+ result.computeIfAbsent(m.getFeederId(), k -> new HashMap<>()).put(m.getMetricCode(), m);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 批量查询问题详情(主指标 + object_type=device),按 feederId 索引
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, StaticProblemDetail> loadProblemDetails(StaticGridMetricTypeEnum e,
|
|
|
|
|
+ Long resultId, List<String> feederIds) {
|
|
|
|
|
+ LambdaQueryWrapper<StaticProblemDetail> wrapper = new LambdaQueryWrapper<StaticProblemDetail>()
|
|
|
|
|
+ .eq(StaticProblemDetail::getResultId, resultId)
|
|
|
|
|
+ .in(StaticProblemDetail::getFeederId, feederIds)
|
|
|
|
|
+ .eq(StaticProblemDetail::getMetricCode, e.getPrimaryMetricCode())
|
|
|
|
|
+ .eq(StaticProblemDetail::getObjectType, "device");
|
|
|
|
|
+ List<StaticProblemDetail> details = problemDetailMapper.selectList(wrapper);
|
|
|
|
|
+ Map<String, StaticProblemDetail> result = new HashMap<>();
|
|
|
|
|
+ for (StaticProblemDetail d : details) {
|
|
|
|
|
+ // 同一 (result_id, feeder_id, metric_code) 至多一行,putIfAbsent 保险
|
|
|
|
|
+ result.putIfAbsent(d.getFeederId(), d);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // raw_response 解析
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析 raw_response,建立 feederId -> 主指标 details(JSONArray) 索引(仅保留需要的 feeder)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, JSONArray> parseRawDetails(String rawResponse, String primaryMetricCode, List<String> feederIds) {
|
|
|
|
|
+ Map<String, JSONArray> result = new HashMap<>();
|
|
|
|
|
+ if (StringUtils.isBlank(rawResponse)) {
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ Set<String> feederIdSet = new LinkedHashSet<>(feederIds);
|
|
|
|
|
+ try {
|
|
|
|
|
+ JSONObject root = JSON.parseObject(rawResponse);
|
|
|
|
|
+ JSONObject calc = root.getJSONObject("calculation_result");
|
|
|
|
|
+ if (calc == null) {
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONArray feederResults = calc.getJSONArray("feeder_results");
|
|
|
|
|
+ if (feederResults == null) {
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (int i = 0; i < feederResults.size(); i++) {
|
|
|
|
|
+ JSONObject fr = feederResults.getJSONObject(i);
|
|
|
|
|
+ String fid = fr.getString("feeder_id");
|
|
|
|
|
+ if (!feederIdSet.contains(fid)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONArray categories = fr.getJSONArray("categories");
|
|
|
|
|
+ if (categories == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (int j = 0; j < categories.size(); j++) {
|
|
|
|
|
+ JSONArray metrics = categories.getJSONObject(j).getJSONArray("metrics");
|
|
|
|
|
+ if (metrics == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (int k = 0; k < metrics.size(); k++) {
|
|
|
|
|
+ JSONObject m = metrics.getJSONObject(k);
|
|
|
|
|
+ if (primaryMetricCode.equals(m.getString("metric_code"))) {
|
|
|
|
|
+ result.put(fid, m.getJSONArray("details"));
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (result.containsKey(fid)) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ log.warn("解析 raw_response 失败 primaryMetricCode={}", primaryMetricCode, ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 组装
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ private StaticGridDetailItemVO buildItem(StaticIndicatorMetric metric, StaticGridMetricTypeEnum e,
|
|
|
|
|
+ Map<String, Map<String, StaticIndicatorMetric>> valueMetricsByFeeder,
|
|
|
|
|
+ Map<String, StaticProblemDetail> problemDetailByFeeder,
|
|
|
|
|
+ Map<String, JSONArray> rawDetailsByFeeder) {
|
|
|
|
|
+ String fid = metric.getFeederId();
|
|
|
|
|
+ Map<String, StaticIndicatorMetric> valueMetrics = valueMetricsByFeeder.getOrDefault(fid, Collections.emptyMap());
|
|
|
|
|
+ StaticProblemDetail pd = problemDetailByFeeder.get(fid);
|
|
|
|
|
+ JSONArray details = rawDetailsByFeeder.get(fid);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> detailsMap = new LinkedHashMap<>();
|
|
|
|
|
+ if (e.isShowDetails()) {
|
|
|
|
|
+ for (FieldSpec fs : e.getDetailFields()) {
|
|
|
|
|
+ detailsMap.put(fs.getOutputKey(), resolveField(fs, valueMetrics, pd, details));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return StaticGridDetailItemVO.builder()
|
|
|
|
|
+ .feederId(fid)
|
|
|
|
|
+ .feederName(metric.getFeederName())
|
|
|
|
|
+ .details(detailsMap)
|
|
|
|
|
+ .deviceIds(resolveDeviceIds(pd, details))
|
|
|
|
|
+ .build();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Object resolveField(FieldSpec fs, Map<String, StaticIndicatorMetric> valueMetrics,
|
|
|
|
|
+ StaticProblemDetail pd, JSONArray details) {
|
|
|
|
|
+ switch (fs.getSource()) {
|
|
|
|
|
+ case METRIC_VALUE:
|
|
|
|
|
+ StaticIndicatorMetric mv = valueMetrics.get(fs.getRef());
|
|
|
|
|
+ return mv != null ? toNumber(mv.getValue()) : null;
|
|
|
|
|
+ case METRIC_UNIT:
|
|
|
|
|
+ StaticIndicatorMetric mu = valueMetrics.get(fs.getRef());
|
|
|
|
|
+ return mu != null ? mu.getUnit() : null;
|
|
|
|
|
+ case REASON:
|
|
|
|
|
+ return pd != null ? pd.getDescription() : null;
|
|
|
|
|
+ case RAW_LEAF:
|
|
|
|
|
+ return roundRawLeaf(getDetailLeaf(details, fs.getRef()), fs.getScale());
|
|
|
|
|
+ case RAW_BRANCH_FIRST:
|
|
|
|
|
+ return getBranchFirst(details, fs.getRef());
|
|
|
|
|
+ case RAW_LOAD_GROUPS:
|
|
|
|
|
+ return getLoadGroups(details);
|
|
|
|
|
+ default:
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * deviceIds:优先取问题详情表 object_id(object_type=device),缺失则回退 raw_response details[0].device_ids
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<String> resolveDeviceIds(StaticProblemDetail pd, JSONArray details) {
|
|
|
|
|
+ if (pd != null && StringUtils.isNotBlank(pd.getObjectId())) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<String> ids = JSON.parseArray(pd.getObjectId(), String.class);
|
|
|
|
|
+ if (ids != null && !ids.isEmpty()) {
|
|
|
|
|
+ return ids;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ log.warn("解析 deviceIds 失败 objectId={}", pd.getObjectId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (details != null && !details.isEmpty()) {
|
|
|
|
|
+ JSONObject d0 = details.getJSONObject(0);
|
|
|
|
|
+ if (d0 != null) {
|
|
|
|
|
+ Object ids = d0.get("device_ids");
|
|
|
|
|
+ if (ids instanceof JSONArray) {
|
|
|
|
|
+ return ((JSONArray) ids).toJavaList(String.class);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Object getDetailLeaf(JSONArray details, String key) {
|
|
|
|
|
+ if (details == null || details.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject d0 = details.getJSONObject(0);
|
|
|
|
|
+ return d0 == null ? null : d0.get(key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Object getBranchFirst(JSONArray details, String key) {
|
|
|
|
|
+ if (details == null || details.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject d0 = details.getJSONObject(0);
|
|
|
|
|
+ if (d0 == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONArray branches = d0.getJSONArray("branches");
|
|
|
|
|
+ if (branches == null || branches.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject b0 = branches.getJSONObject(0);
|
|
|
|
|
+ return b0 == null ? null : b0.get(key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<Map<String, Object>> getLoadGroups(JSONArray details) {
|
|
|
|
|
+ if (details == null || details.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject d0 = details.getJSONObject(0);
|
|
|
|
|
+ if (d0 == null) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONArray groups = d0.getJSONArray("load_groups");
|
|
|
|
|
+ if (groups == null || groups.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Map<String, Object>> list = new ArrayList<>(groups.size());
|
|
|
|
|
+ for (int i = 0; i < groups.size(); i++) {
|
|
|
|
|
+ JSONObject g = groups.getJSONObject(i);
|
|
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
|
|
+ item.put("branchId", g.get("branch_id"));
|
|
|
|
|
+ item.put("capacityMva", g.get("capacity_mva"));
|
|
|
|
|
+ item.put("transformerCount", g.get("transformer_count"));
|
|
|
|
|
+ list.add(item);
|
|
|
|
|
+ }
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 指标表 value 字符串 -> 数值:整数返回 Integer,小数返回 Double
|
|
|
|
|
+ */
|
|
|
|
|
+ private Object toNumber(String valueStr) {
|
|
|
|
|
+ if (StringUtils.isBlank(valueStr)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ BigDecimal bd = new BigDecimal(valueStr);
|
|
|
|
|
+ if (bd.scale() <= 0) {
|
|
|
|
|
+ return bd.intValue();
|
|
|
|
|
+ }
|
|
|
|
|
+ return bd.doubleValue();
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ return valueStr;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * raw_response 叶子数值:scale>=0 时按 HALF_UP 保留 n 位小数(如 N-1 的估算负载保留三位小数)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Object roundRawLeaf(Object val, int scale) {
|
|
|
|
|
+ if (val == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (scale >= 0) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return new BigDecimal(val.toString()).setScale(scale, RoundingMode.HALF_UP).doubleValue();
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ return val;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return val;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 结果构建
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 固定 13 个指标字段,仅入参 metricType 对应字段填充数据,其余为空数组
|
|
|
|
|
+ */
|
|
|
|
|
+ private StaticGridDetailResultVO buildResult(StaticGridMetricTypeEnum e,
|
|
|
|
|
+ List<StaticGridDetailItemVO> items) {
|
|
|
|
|
+ StaticGridDetailResultVO vo = emptyResult();
|
|
|
|
|
+ switch (e) {
|
|
|
|
|
+ case N1_FAIL:
|
|
|
|
|
+ vo.setN1Fail(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case NO_CONNECTION:
|
|
|
|
|
+ vo.setNoConnection(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SAME_BUS_INTERCONNECTION:
|
|
|
|
|
+ vo.setSameBusInterconnection(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case EXCESSIVE_TIE_POINT:
|
|
|
|
|
+ vo.setExcessiveTiePoint(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SINGLE_RADIAL:
|
|
|
|
|
+ vo.setSingleRadial(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case LARGE_BRANCH:
|
|
|
|
|
+ vo.setLargeBranch(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case UNREASONABLE_SEGMENT:
|
|
|
|
|
+ vo.setUnreasonableSegment(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case DENSE_SEGMENT:
|
|
|
|
|
+ vo.setDenseSegment(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case SUPPLY_RADIUS_EXCEED:
|
|
|
|
|
+ vo.setSupplyRadiusExceed(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case EXTRA_LONG_FEEDER:
|
|
|
|
|
+ vo.setExtraLongFeeder(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case TRUNK_MISMATCH:
|
|
|
|
|
+ vo.setTrunkMismatch(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case CAPACITY_EXCEED:
|
|
|
|
|
+ vo.setCapacityExceed(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case LOAD_GROUP_ANOMALY:
|
|
|
|
|
+ vo.setLoadGroupAnomaly(items);
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private StaticGridDetailResultVO emptyResult() {
|
|
|
|
|
+ return StaticGridDetailResultVO.builder()
|
|
|
|
|
+ .n1Fail(Collections.emptyList())
|
|
|
|
|
+ .noConnection(Collections.emptyList())
|
|
|
|
|
+ .sameBusInterconnection(Collections.emptyList())
|
|
|
|
|
+ .excessiveTiePoint(Collections.emptyList())
|
|
|
|
|
+ .singleRadial(Collections.emptyList())
|
|
|
|
|
+ .largeBranch(Collections.emptyList())
|
|
|
|
|
+ .unreasonableSegment(Collections.emptyList())
|
|
|
|
|
+ .denseSegment(Collections.emptyList())
|
|
|
|
|
+ .supplyRadiusExceed(Collections.emptyList())
|
|
|
|
|
+ .extraLongFeeder(Collections.emptyList())
|
|
|
|
|
+ .trunkMismatch(Collections.emptyList())
|
|
|
|
|
+ .capacityExceed(Collections.emptyList())
|
|
|
|
|
+ .loadGroupAnomaly(Collections.emptyList())
|
|
|
|
|
+ .build();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 工具
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ private LocalDateTime monthStart(YearMonth ym) {
|
|
|
|
|
+ return ym.atDay(1).atStartOfDay();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private LocalDateTime monthEnd(YearMonth ym) {
|
|
|
|
|
+ return ym.atEndOfMonth().atTime(23, 59, 59);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|