|
@@ -18,18 +18,25 @@ import org.apache.commons.lang3.StringUtils;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
-import java.util.ArrayList;
|
|
|
|
|
-import java.util.HashMap;
|
|
|
|
|
-import java.util.List;
|
|
|
|
|
-import java.util.Map;
|
|
|
|
|
|
|
+import java.math.RoundingMode;
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
|
|
+import java.time.YearMonth;
|
|
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.time.format.DateTimeParseException;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 静态网架评估仪表盘服务实现
|
|
* 静态网架评估仪表盘服务实现
|
|
|
* <p>
|
|
* <p>
|
|
|
- * 从 t_static_indicator_result + t_static_indicator_metric + t_static_problem_detail 三张表
|
|
|
|
|
- * 聚合数据,构建前端仪表盘 VO。
|
|
|
|
|
- * <p>
|
|
|
|
|
- * 预留字段(运行态指标/外部台账)直接返回 null,前端渲染为"-"。
|
|
|
|
|
|
|
+ * 支持三种节点维度查询:
|
|
|
|
|
+ * <ul>
|
|
|
|
|
+ * <li>type=2 区县 — 取 county 级指标</li>
|
|
|
|
|
+ * <li>type=3 变电站 — 聚合该变电站下所有馈线的 feeder 级指标</li>
|
|
|
|
|
+ * <li>type=4 馈线 — 取单条馈线的 feeder 级指标(聚合后 bool→0/1、rate→0%/100%)</li>
|
|
|
|
|
+ * </ul>
|
|
|
|
|
+ * 静态指标按月份过滤,前端传 daytime(如 2026-7-16),取该月最新一批数据。
|
|
|
|
|
+ * 停电时户数通过馈线组树递归收集馈线名列表后查询。
|
|
|
*
|
|
*
|
|
|
* @author lsl
|
|
* @author lsl
|
|
|
* @since 2026-07-19
|
|
* @since 2026-07-19
|
|
@@ -46,73 +53,239 @@ public class StaticGridDashboardServiceImpl
|
|
|
private final FeederGroupApplication feederGroupApplication;
|
|
private final FeederGroupApplication feederGroupApplication;
|
|
|
private final SjztPwwyyqxtPrmPdrOutageMvDsService pwwyyqxtPrmPdrOutageMvDsService;
|
|
private final SjztPwwyyqxtPrmPdrOutageMvDsService pwwyyqxtPrmPdrOutageMvDsService;
|
|
|
|
|
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 指标聚合映射表:county 级 metric_code → feeder 级 metric_code
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /** 布尔计数类:count(value="true") */
|
|
|
|
|
+ private static final Map<String, String> BOOL_COUNT_MAP = new LinkedHashMap<>();
|
|
|
|
|
+ static {
|
|
|
|
|
+ BOOL_COUNT_MAP.put("same_bus_interconnection_issue_count", "has_same_bus_interconnection_issue");
|
|
|
|
|
+ BOOL_COUNT_MAP.put("excessive_tie_point_feeder_count", "has_excessive_tie_points");
|
|
|
|
|
+ BOOL_COUNT_MAP.put("single_radial_feeder_count", "is_single_radial_feeder");
|
|
|
|
|
+ BOOL_COUNT_MAP.put("large_branch_issue_feeder_count", "has_large_branch_issue");
|
|
|
|
|
+ BOOL_COUNT_MAP.put("main_trunk_lantern_issue_feeder_count", "has_main_trunk_lantern_issue");
|
|
|
|
|
+ BOOL_COUNT_MAP.put("unreasonable_segment_count_feeder_count", "has_unreasonable_segment_count");
|
|
|
|
|
+ BOOL_COUNT_MAP.put("supply_radius_exceed_feeder_count", "is_supply_radius_exceeded");
|
|
|
|
|
+ BOOL_COUNT_MAP.put("extra_long_feeder_count", "is_extra_long_feeder");
|
|
|
|
|
+ BOOL_COUNT_MAP.put("main_trunk_bottleneck_feeder_count", "has_main_trunk_bottleneck");
|
|
|
|
|
+ BOOL_COUNT_MAP.put("connected_capacity_exceed_feeder_count", "is_connected_capacity_exceeded");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 布尔比率类:count(true)/total×100 */
|
|
|
|
|
+ private static final Map<String, String> BOOL_RATE_MAP = new LinkedHashMap<>();
|
|
|
|
|
+ static {
|
|
|
|
|
+ BOOL_RATE_MAP.put("wiring_standardization_rate", "is_standard_wiring");
|
|
|
|
|
+ BOOL_RATE_MAP.put("main_trunk_section_standardization_rate", "main_trunk_section_compliant");
|
|
|
|
|
+ BOOL_RATE_MAP.put("n_minus_1_pass_rate", "n_minus_1_passed");
|
|
|
|
|
+ BOOL_RATE_MAP.put("line_interconnection_rate", "has_tie");
|
|
|
|
|
+ BOOL_RATE_MAP.put("inter_substation_interconnection_rate", "has_inter_substation_tie");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 整数求和类:sum(value) */
|
|
|
|
|
+ private static final Map<String, String> INT_SUM_MAP = new LinkedHashMap<>();
|
|
|
|
|
+ static {
|
|
|
|
|
+ INT_SUM_MAP.put("feeder_head_tie_point_issue_count", "feeder_head_tie_point_issue_count");
|
|
|
|
|
+ INT_SUM_MAP.put("same_pole_tie_point_issue_count", "same_pole_tie_point_issue_count");
|
|
|
|
|
+ INT_SUM_MAP.put("load_group_capacity_anomaly_count", "load_group_capacity_anomaly_count");
|
|
|
|
|
+ INT_SUM_MAP.put("main_trunk_total_count", "main_trunk_count");
|
|
|
|
|
+ INT_SUM_MAP.put("section_compliant_main_trunk_count", "section_compliant_main_trunk_count");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 主入口
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
- public StaticGridDashboardVO queryLatestDashboard() {
|
|
|
|
|
- LambdaQueryWrapper<StaticIndicatorResult> wrapper = new LambdaQueryWrapper<StaticIndicatorResult>()
|
|
|
|
|
- .orderByDesc(StaticIndicatorResult::getCreateTime)
|
|
|
|
|
- .last("LIMIT 1");
|
|
|
|
|
- StaticIndicatorResult result = getOne(wrapper);
|
|
|
|
|
|
|
+ public StaticGridDashboardVO queryLatestDashboard(String daytime, String id, String type) {
|
|
|
|
|
+ if (StringUtils.isBlank(id) || StringUtils.isBlank(type)) {
|
|
|
|
|
+ log.warn("参数缺失: id={}, type={}", id, type);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int nodeType;
|
|
|
|
|
+ try {
|
|
|
|
|
+ nodeType = Integer.parseInt(type);
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ log.warn("type 参数非法: {}", type);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ YearMonth ym = parseYearMonth(daytime);
|
|
|
|
|
+ if (nodeType == 2) {
|
|
|
|
|
+ return queryByCounty(id, ym);
|
|
|
|
|
+ } else if (nodeType == 3) {
|
|
|
|
|
+ return queryBySubstation(id, ym);
|
|
|
|
|
+ } else if (nodeType == 4) {
|
|
|
|
|
+ return queryByFeeder(id, ym);
|
|
|
|
|
+ }
|
|
|
|
|
+ log.warn("不支持的节点类型: {}", nodeType);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 三种维度的查询路径
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * type=2 区县:主表按 maintOrgId + 月份查最新,取 county 级指标
|
|
|
|
|
+ */
|
|
|
|
|
+ private StaticGridDashboardVO queryByCounty(String maintOrgId, YearMonth ym) {
|
|
|
|
|
+ StaticIndicatorResult result = getLatestByMaintOrgId(maintOrgId, ym);
|
|
|
if (result == null) {
|
|
if (result == null) {
|
|
|
- log.warn("无静态网架评估数据");
|
|
|
|
|
|
|
+ log.warn("区县无静态网架数据 maintOrgId={}, month={}", maintOrgId, ym);
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
- return buildDashboardVO(result);
|
|
|
|
|
|
|
+ // county 级指标
|
|
|
|
|
+ List<StaticIndicatorMetric> countyMetrics = getMetrics(result.getId(), "county", null, null);
|
|
|
|
|
+ Map<String, String> metricMap = metricsToMap(countyMetrics);
|
|
|
|
|
+ // 停电时户数
|
|
|
|
|
+ BigDecimal outage = handleOutageDurationHoursHouseholds(maintOrgId, 2);
|
|
|
|
|
+ return buildDashboardVO(result, metricMap, outage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- @Override
|
|
|
|
|
- public StaticGridDashboardVO queryDashboardByBatchId(String batchId) {
|
|
|
|
|
- if (StringUtils.isBlank(batchId)) {
|
|
|
|
|
- return queryLatestDashboard();
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * type=3 变电站:先查该月所有主表批次,再按 sourceSubstationId 过滤 feeder 级指标,聚合
|
|
|
|
|
+ */
|
|
|
|
|
+ private StaticGridDashboardVO queryBySubstation(String substationId, YearMonth ym) {
|
|
|
|
|
+ List<Long> resultIds = getResultIdsByMonth(ym);
|
|
|
|
|
+ if (resultIds.isEmpty()) {
|
|
|
|
|
+ log.warn("月份无静态网架数据 month={}", ym);
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
- LambdaQueryWrapper<StaticIndicatorResult> wrapper = new LambdaQueryWrapper<StaticIndicatorResult>()
|
|
|
|
|
- .eq(StaticIndicatorResult::getBatchId, batchId)
|
|
|
|
|
- .last("LIMIT 1");
|
|
|
|
|
- StaticIndicatorResult result = getOne(wrapper);
|
|
|
|
|
- if (result == null) {
|
|
|
|
|
- log.warn("无静态网架评估数据 batchId={}", batchId);
|
|
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorMetric> wrapper = new LambdaQueryWrapper<StaticIndicatorMetric>()
|
|
|
|
|
+ .in(StaticIndicatorMetric::getResultId, resultIds)
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getLevel, "feeder")
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getSourceSubstationId, substationId);
|
|
|
|
|
+ List<StaticIndicatorMetric> feederMetrics = metricMapper.selectList(wrapper);
|
|
|
|
|
+ if (feederMetrics.isEmpty()) {
|
|
|
|
|
+ log.warn("变电站无 feeder 级指标 substationId={}", substationId);
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
- return buildDashboardVO(result);
|
|
|
|
|
|
|
+ Long resultId = feederMetrics.get(0).getResultId();
|
|
|
|
|
+ StaticIndicatorResult result = getById(resultId);
|
|
|
|
|
+ Map<String, String> metricMap = aggregateFeederMetrics(feederMetrics);
|
|
|
|
|
+ BigDecimal outage = handleOutageDurationHoursHouseholds(substationId, 3);
|
|
|
|
|
+ return buildDashboardVO(result, metricMap, outage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 构建仪表盘 VO
|
|
|
|
|
|
|
+ * type=4 馈线:按 feederId 查 feeder 级指标,聚合(单条馈线 → bool 转 0/1,rate 转 0%/100%)
|
|
|
*/
|
|
*/
|
|
|
- private StaticGridDashboardVO buildDashboardVO(StaticIndicatorResult result) {
|
|
|
|
|
- Long resultId = result.getId();
|
|
|
|
|
|
|
+ private StaticGridDashboardVO queryByFeeder(String feederId, YearMonth ym) {
|
|
|
|
|
+ List<Long> resultIds = getResultIdsByMonth(ym);
|
|
|
|
|
+ if (resultIds.isEmpty()) {
|
|
|
|
|
+ log.warn("月份无静态网架数据 month={}", ym);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorMetric> wrapper = new LambdaQueryWrapper<StaticIndicatorMetric>()
|
|
|
|
|
+ .in(StaticIndicatorMetric::getResultId, resultIds)
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getLevel, "feeder")
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getFeederId, feederId);
|
|
|
|
|
+ List<StaticIndicatorMetric> feederMetrics = metricMapper.selectList(wrapper);
|
|
|
|
|
+ if (feederMetrics.isEmpty()) {
|
|
|
|
|
+ log.warn("馈线无 feeder 级指标 feederId={}", feederId);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ Long resultId = feederMetrics.get(0).getResultId();
|
|
|
|
|
+ StaticIndicatorResult result = getById(resultId);
|
|
|
|
|
+ Map<String, String> metricMap = aggregateFeederMetrics(feederMetrics);
|
|
|
|
|
+ BigDecimal outage = handleOutageDurationHoursHouseholds(feederId, 4);
|
|
|
|
|
+ return buildDashboardVO(result, metricMap, outage);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 查 county 级指标,按 metricCode 索引
|
|
|
|
|
- LambdaQueryWrapper<StaticIndicatorMetric> metricWrapper = new LambdaQueryWrapper<StaticIndicatorMetric>()
|
|
|
|
|
- .eq(StaticIndicatorMetric::getResultId, resultId)
|
|
|
|
|
- .eq(StaticIndicatorMetric::getLevel, "county");
|
|
|
|
|
- List<StaticIndicatorMetric> countyMetrics = metricMapper.selectList(metricWrapper);
|
|
|
|
|
- Map<String, String> countyMetricMap = new HashMap<>();
|
|
|
|
|
- for (StaticIndicatorMetric m : countyMetrics) {
|
|
|
|
|
- countyMetricMap.put(m.getMetricCode(), m.getValue());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 查问题详情
|
|
|
|
|
- /*LambdaQueryWrapper<StaticProblemDetail> problemWrapper = new LambdaQueryWrapper<StaticProblemDetail>()
|
|
|
|
|
- .eq(StaticProblemDetail::getResultId, resultId)
|
|
|
|
|
- .orderByAsc(StaticProblemDetail::getProblemId);
|
|
|
|
|
- List<StaticProblemDetail> problems = problemDetailMapper.selectList(problemWrapper);
|
|
|
|
|
- List<StaticGridDashboardVO.ProblemDetailItem> problemItems = new ArrayList<StaticGridDashboardVO.ProblemDetailItem>();
|
|
|
|
|
- for (StaticProblemDetail p : problems) {
|
|
|
|
|
- problemItems.add(StaticGridDashboardVO.ProblemDetailItem.builder()
|
|
|
|
|
- .problemId(p.getProblemId())
|
|
|
|
|
- .feederId(p.getFeederId())
|
|
|
|
|
- .categoryName(p.getCategoryName())
|
|
|
|
|
- .metricName(p.getMetricName())
|
|
|
|
|
- .severity(p.getSeverity())
|
|
|
|
|
- .objectType(p.getObjectType())
|
|
|
|
|
- .actualValue(p.getActualValue())
|
|
|
|
|
- .description(p.getDescription())
|
|
|
|
|
- .suggestion(p.getSuggestion())
|
|
|
|
|
- .build());
|
|
|
|
|
- }*/
|
|
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 聚合逻辑:feeder 级指标 → county 级格式指标 Map
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将 feeder 级指标列表按 metric_code 聚合,
|
|
|
|
|
+ * 输出与 county 级指标同 key 的 Map(供 buildDashboardVO 使用)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, String> aggregateFeederMetrics(List<StaticIndicatorMetric> feederMetrics) {
|
|
|
|
|
+ // 按 metric_code 分组
|
|
|
|
|
+ Map<String, List<StaticIndicatorMetric>> byCode = feederMetrics.stream()
|
|
|
|
|
+ .collect(Collectors.groupingBy(StaticIndicatorMetric::getMetricCode));
|
|
|
|
|
+
|
|
|
|
|
+ // 馈线总数(去重 feederId)
|
|
|
|
|
+ long totalFeeders = feederMetrics.stream()
|
|
|
|
|
+ .map(StaticIndicatorMetric::getFeederId)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .count();
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, String> result = new HashMap<>();
|
|
|
|
|
+ result.put("feeder_total_count", String.valueOf(totalFeeders));
|
|
|
|
|
+
|
|
|
|
|
+ // 布尔计数类:count(true)
|
|
|
|
|
+ for (Map.Entry<String, String> entry : BOOL_COUNT_MAP.entrySet()) {
|
|
|
|
|
+ long count = countTrue(byCode.get(entry.getValue()));
|
|
|
|
|
+ result.put(entry.getKey(), String.valueOf(count));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 布尔比率类:count(true)/total×100
|
|
|
|
|
+ for (Map.Entry<String, String> entry : BOOL_RATE_MAP.entrySet()) {
|
|
|
|
|
+ long trueCount = countTrue(byCode.get(entry.getValue()));
|
|
|
|
|
+ if (totalFeeders > 0) {
|
|
|
|
|
+ BigDecimal rate = new BigDecimal(trueCount)
|
|
|
|
|
+ .divide(new BigDecimal(totalFeeders), 6, RoundingMode.HALF_UP)
|
|
|
|
|
+ .multiply(new BigDecimal("100"))
|
|
|
|
|
+ .setScale(2, RoundingMode.HALF_UP);
|
|
|
|
|
+ result.put(entry.getKey(), rate.toPlainString());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 整数求和类
|
|
|
|
|
+ for (Map.Entry<String, String> entry : INT_SUM_MAP.entrySet()) {
|
|
|
|
|
+ int sum = sumInt(byCode.get(entry.getValue()));
|
|
|
|
|
+ result.put(entry.getKey(), String.valueOf(sum));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 特殊:tied_feeder_total_count = count(has_tie=true)
|
|
|
|
|
+ result.put("tied_feeder_total_count", String.valueOf(countTrue(byCode.get("has_tie"))));
|
|
|
|
|
+
|
|
|
|
|
+ // 特殊:standard_wiring_feeder_count = count(is_standard_wiring=true)
|
|
|
|
|
+ result.put("standard_wiring_feeder_count",
|
|
|
|
|
+ String.valueOf(countTrue(byCode.get("is_standard_wiring"))));
|
|
|
|
|
+
|
|
|
|
|
+ // 特殊:connected_capacity_anomaly_count = 超标准条数 + 负荷组异常数
|
|
|
|
|
+ int capacityAnomaly = Integer.parseInt(result.getOrDefault("connected_capacity_exceed_feeder_count", "0"))
|
|
|
|
|
+ + Integer.parseInt(result.getOrDefault("load_group_capacity_anomaly_count", "0"));
|
|
|
|
|
+ result.put("connected_capacity_anomaly_count", String.valueOf(capacityAnomaly));
|
|
|
|
|
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private long countTrue(List<StaticIndicatorMetric> metrics) {
|
|
|
|
|
+ if (metrics == null) return 0;
|
|
|
|
|
+ return metrics.stream()
|
|
|
|
|
+ .filter(m -> "true".equalsIgnoreCase(m.getValue()))
|
|
|
|
|
+ .count();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int sumInt(List<StaticIndicatorMetric> metrics) {
|
|
|
|
|
+ if (metrics == null) return 0;
|
|
|
|
|
+ return metrics.stream()
|
|
|
|
|
+ .filter(m -> StringUtils.isNotBlank(m.getValue()))
|
|
|
|
|
+ .mapToInt(m -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return new BigDecimal(m.getValue()).intValue();
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .sum();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 构建 VO(type=2 和 type=3/4 共用)
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ private StaticGridDashboardVO buildDashboardVO(StaticIndicatorResult result,
|
|
|
|
|
+ Map<String, String> metricMap,
|
|
|
|
|
+ BigDecimal outageDuration) {
|
|
|
return StaticGridDashboardVO.builder()
|
|
return StaticGridDashboardVO.builder()
|
|
|
- .resultId(resultId)
|
|
|
|
|
|
|
+ .resultId(result.getId())
|
|
|
.batchId(result.getBatchId())
|
|
.batchId(result.getBatchId())
|
|
|
.countyId(result.getCountyId())
|
|
.countyId(result.getCountyId())
|
|
|
.countySid(result.getCountySid())
|
|
.countySid(result.getCountySid())
|
|
@@ -121,62 +294,142 @@ public class StaticGridDashboardServiceImpl
|
|
|
.dataQualityStatus(result.getDataQualityStatus())
|
|
.dataQualityStatus(result.getDataQualityStatus())
|
|
|
.dataQualityCompletenessRate(result.getDataQualityCompletenessRate())
|
|
.dataQualityCompletenessRate(result.getDataQualityCompletenessRate())
|
|
|
// 一、标准化网架
|
|
// 一、标准化网架
|
|
|
- .wiringStandardizationRate(toBigDecimal(countyMetricMap.get("wiring_standardization_rate")))
|
|
|
|
|
- .mainTrunkSectionStandardizationRate(toBigDecimal(countyMetricMap.get("main_trunk_section_standardization_rate")))
|
|
|
|
|
|
|
+ .wiringStandardizationRate(toBigDecimal(metricMap.get("wiring_standardization_rate")))
|
|
|
|
|
+ .mainTrunkSectionStandardizationRate(toBigDecimal(metricMap.get("main_trunk_section_standardization_rate")))
|
|
|
// 二、供电可靠性
|
|
// 二、供电可靠性
|
|
|
- .outageDurationHoursHouseholds(handleOutageDurationHoursHouseholds("BD69707EA19E4FED85C595AFB5D06720","BA6481B8F34143FDA7E1C35A76846911")) // 暂时写死:洪湖7.1—现在的停电时户数
|
|
|
|
|
- .lineNMinus1PassRate(toBigDecimal(countyMetricMap.get("n_minus_1_pass_rate")))
|
|
|
|
|
- .interSubstationNoConnectionCount(toInteger(countyMetricMap.get("inter_substation_interconnection_rate")))
|
|
|
|
|
- .sameBusInterconnectionIssueCount(toInteger(countyMetricMap.get("same_bus_interconnection_issue_count")))
|
|
|
|
|
- .excessiveTiePointFeederCount(toInteger(countyMetricMap.get("excessive_tie_point_feeder_count")))
|
|
|
|
|
- .singleRadialFeederCount(toInteger(countyMetricMap.get("single_radial_feeder_count")))
|
|
|
|
|
- .largeBranchFeederCount(toInteger(countyMetricMap.get("large_branch_issue_feeder_count")))
|
|
|
|
|
- .unreasonableSegmentCount(toInteger(countyMetricMap.get("unreasonable_segment_count_feeder_count")))
|
|
|
|
|
- .mainTrunkSegmentDenseCount(toInteger(countyMetricMap.get("main_trunk_lantern_issue_feeder_count")))
|
|
|
|
|
|
|
+ .outageDurationHoursHouseholds(outageDuration)
|
|
|
|
|
+ .lineNMinus1PassRate(toBigDecimal(metricMap.get("n_minus_1_pass_rate")))
|
|
|
|
|
+ .interSubstationNoConnectionCount(calcInterSubstationNoConnectionCount(
|
|
|
|
|
+ metricMap.get("feeder_total_count"),
|
|
|
|
|
+ metricMap.get("inter_substation_interconnection_rate")))
|
|
|
|
|
+ .sameBusInterconnectionIssueCount(toInteger(metricMap.get("same_bus_interconnection_issue_count")))
|
|
|
|
|
+ .excessiveTiePointFeederCount(toInteger(metricMap.get("excessive_tie_point_feeder_count")))
|
|
|
|
|
+ .singleRadialFeederCount(toInteger(metricMap.get("single_radial_feeder_count")))
|
|
|
|
|
+ .largeBranchFeederCount(toInteger(metricMap.get("large_branch_issue_feeder_count")))
|
|
|
|
|
+ .unreasonableSegmentCount(toInteger(metricMap.get("unreasonable_segment_count_feeder_count")))
|
|
|
|
|
+ .mainTrunkSegmentDenseCount(toInteger(metricMap.get("main_trunk_lantern_issue_feeder_count")))
|
|
|
// 三、电压质量
|
|
// 三、电压质量
|
|
|
.iecUserVoltageIndex(null) // 预留
|
|
.iecUserVoltageIndex(null) // 预留
|
|
|
- .supplyRadiusExceedFeederCount(toInteger(countyMetricMap.get("supply_radius_exceed_feeder_count")))
|
|
|
|
|
- .extraLongFeederCount(toInteger(countyMetricMap.get("extra_long_feeder_count")))
|
|
|
|
|
|
|
+ .supplyRadiusExceedFeederCount(toInteger(metricMap.get("supply_radius_exceed_feeder_count")))
|
|
|
|
|
+ .extraLongFeederCount(toInteger(metricMap.get("extra_long_feeder_count")))
|
|
|
// 四、供电能力
|
|
// 四、供电能力
|
|
|
- .regionalMaxLoadRate(null) // 预留:运行态指标
|
|
|
|
|
- .regionalAvgLoadRate(null) // 预留:运行态指标
|
|
|
|
|
- .lineOverloadCount(null) // 预留:运行态指标
|
|
|
|
|
- .mainTrunkSectionMismatchCount(toInteger(countyMetricMap.get("main_trunk_bottleneck_feeder_count")))
|
|
|
|
|
- .connectedCapacityExceedFeederCount(toInteger(countyMetricMap.get("connected_capacity_exceed_feeder_count")))
|
|
|
|
|
- .loadGroupCapacityAnomalyCount(toInteger(countyMetricMap.get("load_group_capacity_anomaly_count")))
|
|
|
|
|
- .comprehensiveLossRate(null) // 预留:运行态指标
|
|
|
|
|
- .powerFactorPassRate(null) // 预留:运行态指标
|
|
|
|
|
- // 问题详情
|
|
|
|
|
- //.problemDetails(problemItems)
|
|
|
|
|
|
|
+ .regionalMaxLoadRate(null)
|
|
|
|
|
+ .regionalAvgLoadRate(null)
|
|
|
|
|
+ .lineOverloadCount(null)
|
|
|
|
|
+ .mainTrunkSectionMismatchCount(toInteger(metricMap.get("main_trunk_bottleneck_feeder_count")))
|
|
|
|
|
+ .connectedCapacityExceedFeederCount(toInteger(metricMap.get("connected_capacity_exceed_feeder_count")))
|
|
|
|
|
+ .loadGroupCapacityAnomalyCount(toInteger(metricMap.get("load_group_capacity_anomaly_count")))
|
|
|
|
|
+ .comprehensiveLossRate(null)
|
|
|
|
|
+ .powerFactorPassRate(null)
|
|
|
.build();
|
|
.build();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //处理停电时户数
|
|
|
|
|
- public BigDecimal handleOutageDurationHoursHouseholds(String cityId, String maintOrgId){
|
|
|
|
|
- List<FeederGroupTreeNode> feederGroupTree = feederGroupApplication.getFeederGroupTree();
|
|
|
|
|
- FeederGroupTreeNode type1 = findNodeRecursively(feederGroupTree,1,cityId);
|
|
|
|
|
- if (type1 == null) {
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 停电时户数 — 改造为 (id, type)
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据节点类型和ID,从馈线组树收集馈线名列表,查询停电时户数
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param id 节点设备ID
|
|
|
|
|
+ * @param type 节点类型 2=区县 3=变电站 4=馈线
|
|
|
|
|
+ * @return 停电时户数
|
|
|
|
|
+ */
|
|
|
|
|
+ public BigDecimal handleOutageDurationHoursHouseholds(String id, int type) {
|
|
|
|
|
+ List<FeederGroupTreeNode> tree = feederGroupApplication.getFeederGroupTree();
|
|
|
|
|
+ List<String> feederNames = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ if (type == 4) {
|
|
|
|
|
+ // 馈线:直接找该节点取 name
|
|
|
|
|
+ FeederGroupTreeNode node = findNodeRecursively(tree, 4, id);
|
|
|
|
|
+ if (node != null) {
|
|
|
|
|
+ feederNames.add(node.getName());
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 区县/变电站:找该节点 → 递归收集子树所有 type=4 的馈线名
|
|
|
|
|
+ FeederGroupTreeNode node = findNodeRecursively(tree, type, id);
|
|
|
|
|
+ if (node != null && node.getChildren() != null) {
|
|
|
|
|
+ collectNodesByType(node.getChildren(), feederNames);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- FeederGroupTreeNode type2 = findNodeRecursively(feederGroupTree,2,maintOrgId);
|
|
|
|
|
- if (type2 == null) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (feederNames.isEmpty()) {
|
|
|
|
|
+ log.warn("未找到馈线名列表 id={}, type={}", id, type);
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
- List<String> type4Result = new ArrayList<>();
|
|
|
|
|
- collectNodesByType(type2.getChildren(),type4Result);
|
|
|
|
|
- return pwwyyqxtPrmPdrOutageMvDsService.selectStaticOutageDurationHoursHouseholds(type4Result);
|
|
|
|
|
|
|
+ return pwwyyqxtPrmPdrOutageMvDsService.selectStaticOutageDurationHoursHouseholds(feederNames);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 主表查询辅助
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 区县维度:按 maintOrgId + 月份查最新一条主表记录
|
|
|
|
|
+ */
|
|
|
|
|
+ private StaticIndicatorResult getLatestByMaintOrgId(String maintOrgId, YearMonth ym) {
|
|
|
|
|
+ LocalDate monthStart = ym.atDay(1);
|
|
|
|
|
+ LocalDate monthEnd = ym.atEndOfMonth();
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorResult> wrapper = new LambdaQueryWrapper<StaticIndicatorResult>()
|
|
|
|
|
+ .eq(StaticIndicatorResult::getMaintOrgId, maintOrgId)
|
|
|
|
|
+ .ge(StaticIndicatorResult::getCreateTime, monthStart.atStartOfDay())
|
|
|
|
|
+ .le(StaticIndicatorResult::getCreateTime, monthEnd.atTime(23, 59, 59))
|
|
|
|
|
+ .orderByDesc(StaticIndicatorResult::getCreateTime)
|
|
|
|
|
+ .last("LIMIT 1");
|
|
|
|
|
+ return getOne(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取该月所有主表批次ID(type=3/4 用——先拿到月份范围内的 result_id 再查 metric)
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<Long> getResultIdsByMonth(YearMonth ym) {
|
|
|
|
|
+ LocalDate monthStart = ym.atDay(1);
|
|
|
|
|
+ LocalDate monthEnd = ym.atEndOfMonth();
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorResult> wrapper = new LambdaQueryWrapper<StaticIndicatorResult>()
|
|
|
|
|
+ .ge(StaticIndicatorResult::getCreateTime, monthStart.atStartOfDay())
|
|
|
|
|
+ .le(StaticIndicatorResult::getCreateTime, monthEnd.atTime(23, 59, 59));
|
|
|
|
|
+ List<StaticIndicatorResult> results = list(wrapper);
|
|
|
|
|
+ return results.stream()
|
|
|
|
|
+ .map(StaticIndicatorResult::getId)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- //递归树
|
|
|
|
|
- private FeederGroupTreeNode findNodeRecursively(List<FeederGroupTreeNode> nodes,int nodeType, String targetId){
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查 county 级或 feeder 级指标
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<StaticIndicatorMetric> getMetrics(Long resultId, String level,
|
|
|
|
|
+ String feederId, String substationId) {
|
|
|
|
|
+ LambdaQueryWrapper<StaticIndicatorMetric> wrapper = new LambdaQueryWrapper<StaticIndicatorMetric>()
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getResultId, resultId)
|
|
|
|
|
+ .eq(StaticIndicatorMetric::getLevel, level);
|
|
|
|
|
+ if (feederId != null) {
|
|
|
|
|
+ wrapper.eq(StaticIndicatorMetric::getFeederId, feederId);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (substationId != null) {
|
|
|
|
|
+ wrapper.eq(StaticIndicatorMetric::getSourceSubstationId, substationId);
|
|
|
|
|
+ }
|
|
|
|
|
+ return metricMapper.selectList(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, String> metricsToMap(List<StaticIndicatorMetric> metrics) {
|
|
|
|
|
+ Map<String, String> map = new HashMap<>();
|
|
|
|
|
+ for (StaticIndicatorMetric m : metrics) {
|
|
|
|
|
+ map.put(m.getMetricCode(), m.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+ return map;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 树递归辅助
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ private FeederGroupTreeNode findNodeRecursively(List<FeederGroupTreeNode> nodes, int nodeType, String targetId) {
|
|
|
if (nodes == null || nodes.isEmpty()) return null;
|
|
if (nodes == null || nodes.isEmpty()) return null;
|
|
|
for (FeederGroupTreeNode node : nodes) {
|
|
for (FeederGroupTreeNode node : nodes) {
|
|
|
- if (node.getType() == nodeType && node.getId().equals(targetId)){
|
|
|
|
|
|
|
+ if (node.getType() == nodeType && targetId.equals(node.getId())) {
|
|
|
return node;
|
|
return node;
|
|
|
}
|
|
}
|
|
|
- if (node.getChildren() != null && !node.getChildren().isEmpty()){
|
|
|
|
|
|
|
+ if (node.getChildren() != null && !node.getChildren().isEmpty()) {
|
|
|
FeederGroupTreeNode found = findNodeRecursively(node.getChildren(), nodeType, targetId);
|
|
FeederGroupTreeNode found = findNodeRecursively(node.getChildren(), nodeType, targetId);
|
|
|
if (found != null) return found;
|
|
if (found != null) return found;
|
|
|
}
|
|
}
|
|
@@ -184,22 +437,56 @@ public class StaticGridDashboardServiceImpl
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //收集树线路节点
|
|
|
|
|
private void collectNodesByType(List<FeederGroupTreeNode> nodes, List<String> collector) {
|
|
private void collectNodesByType(List<FeederGroupTreeNode> nodes, List<String> collector) {
|
|
|
if (nodes == null || nodes.isEmpty()) return;
|
|
if (nodes == null || nodes.isEmpty()) return;
|
|
|
for (FeederGroupTreeNode node : nodes) {
|
|
for (FeederGroupTreeNode node : nodes) {
|
|
|
if (node.getType() == 4) {
|
|
if (node.getType() == 4) {
|
|
|
collector.add(node.getName());
|
|
collector.add(node.getName());
|
|
|
- } else if (node.getChildren()!= null && !node.getChildren().isEmpty()) {
|
|
|
|
|
- collectNodesByType(node.getChildren(),collector);
|
|
|
|
|
|
|
+ } else if (node.getChildren() != null && !node.getChildren().isEmpty()) {
|
|
|
|
|
+ collectNodesByType(node.getChildren(), collector);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private BigDecimal toBigDecimal(String value) {
|
|
|
|
|
- if (StringUtils.isBlank(value)) {
|
|
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 工具方法
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析 daytime(如 "2026-7-16")为 YearMonth
|
|
|
|
|
+ */
|
|
|
|
|
+ private YearMonth parseYearMonth(String daytime) {
|
|
|
|
|
+ if (StringUtils.isBlank(daytime)) {
|
|
|
|
|
+ return YearMonth.now();
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ LocalDate date = LocalDate.parse(daytime, DateTimeFormatter.ofPattern("yyyy-M-d"));
|
|
|
|
|
+ return YearMonth.from(date);
|
|
|
|
|
+ } catch (DateTimeParseException e) {
|
|
|
|
|
+ log.warn("daytime 格式非法: {}, 使用当前月份", daytime);
|
|
|
|
|
+ return YearMonth.now();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 站间无联络线路数 = 馈线总数 × (1 - 站间联络率/100)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Integer calcInterSubstationNoConnectionCount(String feederTotalCountStr, String interSubstationRateStr) {
|
|
|
|
|
+ Integer total = toInteger(feederTotalCountStr);
|
|
|
|
|
+ if (total == null || total == 0) return null;
|
|
|
|
|
+ if (StringUtils.isBlank(interSubstationRateStr)) return total;
|
|
|
|
|
+ try {
|
|
|
|
|
+ BigDecimal rate = new BigDecimal(interSubstationRateStr);
|
|
|
|
|
+ BigDecimal noConnRatio = BigDecimal.ONE.subtract(
|
|
|
|
|
+ rate.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP));
|
|
|
|
|
+ return new BigDecimal(total).multiply(noConnRatio).setScale(0, RoundingMode.HALF_UP).intValue();
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private BigDecimal toBigDecimal(String value) {
|
|
|
|
|
+ if (StringUtils.isBlank(value)) return null;
|
|
|
try {
|
|
try {
|
|
|
return new BigDecimal(value);
|
|
return new BigDecimal(value);
|
|
|
} catch (NumberFormatException e) {
|
|
} catch (NumberFormatException e) {
|
|
@@ -208,9 +495,7 @@ public class StaticGridDashboardServiceImpl
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private Integer toInteger(String value) {
|
|
private Integer toInteger(String value) {
|
|
|
- if (StringUtils.isBlank(value)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (StringUtils.isBlank(value)) return null;
|
|
|
try {
|
|
try {
|
|
|
return Integer.valueOf(value);
|
|
return Integer.valueOf(value);
|
|
|
} catch (NumberFormatException e) {
|
|
} catch (NumberFormatException e) {
|
|
@@ -221,4 +506,4 @@ public class StaticGridDashboardServiceImpl
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
+}
|