|
@@ -0,0 +1,1106 @@
|
|
|
|
|
+package com.zksy.drainage.service.impl;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
|
|
+import com.zksy.base.alarm.domain.AlarmData;
|
|
|
|
|
+import com.zksy.base.alarm.mapper.AlarmDataMapper;
|
|
|
|
|
+import com.zksy.base.domain.EquipmentBase;
|
|
|
|
|
+import com.zksy.base.domain.EquipmentType;
|
|
|
|
|
+import com.zksy.base.domain.PipeNetworkBase;
|
|
|
|
|
+import com.zksy.base.mapper.EquipmentBaseMapper;
|
|
|
|
|
+import com.zksy.base.mapper.EquipmentTypeMapper;
|
|
|
|
|
+import com.zksy.base.mapper.PipeNetworkBaseMapper;
|
|
|
|
|
+import com.zksy.drainage.domain.DrainageWarning;
|
|
|
|
|
+import com.zksy.drainage.domain.EngineeringFacility;
|
|
|
|
|
+import com.zksy.drainage.domain.WaterloggingPoint;
|
|
|
|
|
+import com.zksy.drainage.dto.DrainageRiskItemVO;
|
|
|
|
|
+import com.zksy.drainage.dto.DrainageRiskPageInDTO;
|
|
|
|
|
+import com.zksy.drainage.mapper.DrainageWarningMapper;
|
|
|
|
|
+import com.zksy.drainage.mapper.EngineeringFacilityMapper;
|
|
|
|
|
+import com.zksy.drainage.mapper.WaterloggingPointMapper;
|
|
|
|
|
+import com.zksy.drainage.service.DrainageRiskService;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+import java.time.ZoneId;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.ArrayDeque;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.Deque;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.LinkedHashMap;
|
|
|
|
|
+import java.util.LinkedHashSet;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Locale;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.Set;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 排水管网综合风险评估实现
|
|
|
|
|
+ *
|
|
|
|
|
+ * 评估对象为 pipe_network_base 中 network_type=1 的排水管网,输入数据全部来自本系统已有业务表:
|
|
|
|
|
+ * 设备台账(equipment_base)、报警(alarm_data)、预警(early_warning)、
|
|
|
|
|
+ * 易积水点(drainage_waterlogging_point)、工程设施(drainage_engineering_facility)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 评分模型为 4 个维度合计 100 分:管网本体 35、运行报警 35、环境叠加 20、数据质量 10。
|
|
|
|
|
+ * 结果整体走内存快照,避免逐行查库,也保证列表、统计、地图三处口径一致。
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class DrainageRiskServiceImpl implements DrainageRiskService {
|
|
|
|
|
+
|
|
|
|
|
+ /** 快照有效期,超时后重新计算 */
|
|
|
|
|
+ private static final long SNAPSHOT_TTL_MILLIS = 5 * 60 * 1000L;
|
|
|
|
|
+
|
|
|
|
|
+ /** 报警/预警统计窗口(天) */
|
|
|
|
|
+ private static final int RUNNING_WINDOW_DAYS = 30;
|
|
|
|
|
+
|
|
|
|
|
+ /** 设备视为管网沿线的最大距离(米) */
|
|
|
|
|
+ private static final double EQUIPMENT_RADIUS_METERS = 300d;
|
|
|
|
|
+
|
|
|
|
|
+ /** 易积水点视为管网周边的最大距离(米) */
|
|
|
|
|
+ private static final double WATERLOGGING_RADIUS_METERS = 500d;
|
|
|
|
|
+
|
|
|
|
|
+ /** 泵站、闸门等设施视为管网周边的最大距离(米) */
|
|
|
|
|
+ private static final double FACILITY_RADIUS_METERS = 300d;
|
|
|
|
|
+
|
|
|
|
|
+ private static final String LEVEL_HIGH = "high";
|
|
|
|
|
+ private static final String LEVEL_MEDIUM = "medium";
|
|
|
|
|
+ private static final String LEVEL_LOW = "low";
|
|
|
|
|
+
|
|
|
|
|
+ /** 排水设备根类型ID,见 EquipmentTypeEnum.ROOT_DRAINAGE_DEVICE */
|
|
|
|
|
+ private static final int DRAINAGE_ROOT_TYPE_ID = 2;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private PipeNetworkBaseMapper pipeNetworkBaseMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private EquipmentBaseMapper equipmentBaseMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private EquipmentTypeMapper equipmentTypeMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private AlarmDataMapper alarmDataMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private DrainageWarningMapper drainageWarningMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private WaterloggingPointMapper waterloggingPointMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private EngineeringFacilityMapper engineeringFacilityMapper;
|
|
|
|
|
+
|
|
|
|
|
+ private volatile Snapshot snapshot;
|
|
|
|
|
+
|
|
|
|
|
+ /* ==================== 对外接口 ==================== */
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Map<String, Object> queryPage(DrainageRiskPageInDTO dto) {
|
|
|
|
|
+ long pageNum = dto == null || dto.getPageNum() == null || dto.getPageNum() < 1 ? 1L : dto.getPageNum();
|
|
|
|
|
+ long pageSize = dto == null || dto.getPageSize() == null || dto.getPageSize() < 1 ? 10L : dto.getPageSize();
|
|
|
|
|
+
|
|
|
|
|
+ List<DrainageRiskItemVO> filtered = filter(snapshot().rows, dto);
|
|
|
|
|
+ int from = (int) Math.min((pageNum - 1) * pageSize, filtered.size());
|
|
|
|
|
+ int to = (int) Math.min(from + pageSize, filtered.size());
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
|
|
+ result.put("total", filtered.size());
|
|
|
|
|
+ result.put("pageNum", pageNum);
|
|
|
|
|
+ result.put("pageSize", pageSize);
|
|
|
|
|
+ result.put("records", new ArrayList<>(filtered.subList(from, to)));
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Map<String, Object> statistics(DrainageRiskPageInDTO dto) {
|
|
|
|
|
+ List<DrainageRiskItemVO> rows = filter(snapshot().rows, dto);
|
|
|
|
|
+
|
|
|
|
|
+ int withAlarm = 0;
|
|
|
|
|
+ int totalAlarms = 0;
|
|
|
|
|
+ int highRisk = 0;
|
|
|
|
|
+ int mediumRisk = 0;
|
|
|
|
|
+ int lowRisk = 0;
|
|
|
|
|
+ int equipmentTotal = 0;
|
|
|
|
|
+ int waterloggingTotal = 0;
|
|
|
|
|
+ for (DrainageRiskItemVO row : rows) {
|
|
|
|
|
+ int alarmCount = row.getAlarmCount() == null ? 0 : row.getAlarmCount();
|
|
|
|
|
+ if (alarmCount > 0) {
|
|
|
|
|
+ withAlarm++;
|
|
|
|
|
+ }
|
|
|
|
|
+ totalAlarms += alarmCount;
|
|
|
|
|
+ equipmentTotal += row.getEquipmentCount() == null ? 0 : row.getEquipmentCount();
|
|
|
|
|
+ waterloggingTotal += row.getWaterloggingCount() == null ? 0 : row.getWaterloggingCount();
|
|
|
|
|
+ if (LEVEL_HIGH.equals(row.getRiskLevel())) {
|
|
|
|
|
+ highRisk++;
|
|
|
|
|
+ } else if (LEVEL_MEDIUM.equals(row.getRiskLevel())) {
|
|
|
|
|
+ mediumRisk++;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ lowRisk++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
|
|
+ result.put("total", rows.size());
|
|
|
|
|
+ result.put("withAlarm", withAlarm);
|
|
|
|
|
+ result.put("noAlarm", rows.size() - withAlarm);
|
|
|
|
|
+ result.put("totalAlarms", totalAlarms);
|
|
|
|
|
+ result.put("highRisk", highRisk);
|
|
|
|
|
+ result.put("mediumRisk", mediumRisk);
|
|
|
|
|
+ result.put("lowRisk", lowRisk);
|
|
|
|
|
+ result.put("equipmentTotal", equipmentTotal);
|
|
|
|
|
+ result.put("waterloggingTotal", waterloggingTotal);
|
|
|
|
|
+ result.put("levelDistribution", levelDistribution(highRisk, mediumRisk, lowRisk));
|
|
|
|
|
+ result.put("areaDistribution", distribution(rows, DrainageRiskItemVO::getManagementUnit));
|
|
|
|
|
+ result.put("categoryDistribution", distribution(rows, DrainageRiskItemVO::getNetworkLevel));
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Map<String, Object> detail(String networkId) {
|
|
|
|
|
+ Snapshot current = snapshot();
|
|
|
|
|
+ PipeNetworkBase pipe = current.pipes.get(networkId);
|
|
|
|
|
+ if (pipe == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ Assessment assessment = current.assessments.get(networkId);
|
|
|
|
|
+ List<EquipmentBase> matched = matchEquipments(pipe, current.equipments, EQUIPMENT_RADIUS_METERS);
|
|
|
|
|
+ List<AlarmData> recentAlarms = loadRecentAlarms(matched, 20);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> basic = new LinkedHashMap<>();
|
|
|
|
|
+ basic.put("networkId", pipe.getNetworkId());
|
|
|
|
|
+ basic.put("networkCode", pipe.getNetworkCode());
|
|
|
|
|
+ basic.put("networkName", pipe.getNetworkName());
|
|
|
|
|
+ basic.put("networkLevel", pipe.getNetworkLevel());
|
|
|
|
|
+ basic.put("managementUnit", pipe.getManagementUnit());
|
|
|
|
|
+ basic.put("location", pipe.getLocation());
|
|
|
|
|
+ basic.put("status", pipe.getStatus());
|
|
|
|
|
+ basic.put("createTime", pipe.getCreateTime());
|
|
|
|
|
+ basic.put("updateTime", pipe.getUpdateTime());
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> pipeInfo = new LinkedHashMap<>();
|
|
|
|
|
+ pipeInfo.put("material", pipe.getMaterial());
|
|
|
|
|
+ pipeInfo.put("diameter", pipe.getDiameter());
|
|
|
|
|
+ pipeInfo.put("length", pipe.getLength());
|
|
|
|
|
+ pipeInfo.put("buildDate", pipe.getBuildDate());
|
|
|
|
|
+ pipeInfo.put("pipeAge", pipeAge(pipe.getBuildDate()));
|
|
|
|
|
+ pipeInfo.put("networkLevel", pipe.getNetworkLevel());
|
|
|
|
|
+ pipeInfo.put("startLongitude", pipe.getStartLongitude());
|
|
|
|
|
+ pipeInfo.put("startLatitude", pipe.getStartLatitude());
|
|
|
|
|
+ pipeInfo.put("endLongitude", pipe.getEndLongitude());
|
|
|
|
|
+ pipeInfo.put("endLatitude", pipe.getEndLatitude());
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> assessBlock = new LinkedHashMap<>();
|
|
|
|
|
+ if (assessment != null) {
|
|
|
|
|
+ assessBlock.put("riskScore", assessment.total);
|
|
|
|
|
+ assessBlock.put("riskLevel", assessment.level);
|
|
|
|
|
+ assessBlock.put("riskLevelName", levelName(assessment.level));
|
|
|
|
|
+ assessBlock.put("riskColor", levelColor(assessment.level));
|
|
|
|
|
+ assessBlock.put("dimensions", assessment.dimensions);
|
|
|
|
|
+ assessBlock.put("assessmentModel", "排水管网综合风险模型 v1(本体/报警/环境/数据质量)");
|
|
|
|
|
+ assessBlock.put("assessmentTime", current.loadTime);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<Map<String, Object>> equipmentItems = new ArrayList<>();
|
|
|
|
|
+ for (EquipmentBase equipment : matched) {
|
|
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
|
|
+ item.put("equipmentId", equipment.getEquipmentId());
|
|
|
|
|
+ item.put("equipmentCode", equipment.getEquipmentCode());
|
|
|
|
|
+ item.put("equipmentName", equipment.getEquipmentName());
|
|
|
|
|
+ item.put("equipmentLocation", equipment.getEquipmentLocation());
|
|
|
|
|
+ item.put("longitude", equipment.getLongitude());
|
|
|
|
|
+ item.put("latitude", equipment.getLatitude());
|
|
|
|
|
+ equipmentItems.add(item);
|
|
|
|
|
+ }
|
|
|
|
|
+ assessBlock.put("equipmentCount", matched.size());
|
|
|
|
|
+ assessBlock.put("alarmCount", assessment == null ? 0 : assessment.alarmCount);
|
|
|
|
|
+ assessBlock.put("openAlarmCount", assessment == null ? 0 : assessment.openAlarmCount);
|
|
|
|
|
+ assessBlock.put("waterloggingCount", assessment == null ? 0 : assessment.waterloggingCount);
|
|
|
|
|
+ assessBlock.put("equipments", equipmentItems);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> analysis = new LinkedHashMap<>();
|
|
|
|
|
+ analysis.put("riskFactors", assessment == null ? Collections.emptyList() : assessment.factors);
|
|
|
|
|
+ analysis.put("mitigationMeasures", assessment == null ? Collections.emptyList() : assessment.measures);
|
|
|
|
|
+ analysis.put("protectiveTargets", assessment == null ? null : assessment.protectiveTargets);
|
|
|
|
|
+ analysis.put("hazardSources", assessment == null ? null : assessment.hazardSources);
|
|
|
|
|
+ analysis.put("recentAlarms", recentAlarms);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
|
|
+ result.put("basic", basic);
|
|
|
|
|
+ result.put("pipeInfo", pipeInfo);
|
|
|
|
|
+ result.put("assessment", assessBlock);
|
|
|
|
|
+ result.put("analysis", analysis);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Map<String, Object> filterOptions() {
|
|
|
|
|
+ Snapshot current = snapshot();
|
|
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
|
|
+ result.put("networkLevels", distinct(current.rows, DrainageRiskItemVO::getNetworkLevel));
|
|
|
|
|
+ result.put("managementUnits", distinct(current.rows, DrainageRiskItemVO::getManagementUnit));
|
|
|
|
|
+ List<Map<String, Object>> riskLevels = new ArrayList<>();
|
|
|
|
|
+ riskLevels.add(option(LEVEL_HIGH, "高风险"));
|
|
|
|
|
+ riskLevels.add(option(LEVEL_MEDIUM, "中风险"));
|
|
|
|
|
+ riskLevels.add(option(LEVEL_LOW, "低风险"));
|
|
|
|
|
+ result.put("riskLevels", riskLevels);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<DrainageRiskItemVO> mapData(DrainageRiskPageInDTO dto) {
|
|
|
|
|
+ return filter(snapshot().rows, dto).stream()
|
|
|
|
|
+ .filter(row -> hasCoordinate(row.getStartLongitude(), row.getStartLatitude())
|
|
|
|
|
+ || hasCoordinate(row.getEndLongitude(), row.getEndLatitude()))
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void refresh() {
|
|
|
|
|
+ snapshot = buildSnapshot();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* ==================== 快照 ==================== */
|
|
|
|
|
+
|
|
|
|
|
+ private Snapshot snapshot() {
|
|
|
|
|
+ Snapshot current = snapshot;
|
|
|
|
|
+ if (current != null && System.currentTimeMillis() - current.loadTime < SNAPSHOT_TTL_MILLIS) {
|
|
|
|
|
+ return current;
|
|
|
|
|
+ }
|
|
|
|
|
+ synchronized (this) {
|
|
|
|
|
+ if (snapshot != null && System.currentTimeMillis() - snapshot.loadTime < SNAPSHOT_TTL_MILLIS) {
|
|
|
|
|
+ return snapshot;
|
|
|
|
|
+ }
|
|
|
|
|
+ snapshot = buildSnapshot();
|
|
|
|
|
+ return snapshot;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Snapshot buildSnapshot() {
|
|
|
|
|
+ long started = System.currentTimeMillis();
|
|
|
|
|
+ List<PipeNetworkBase> pipes = loadDrainagePipes();
|
|
|
|
|
+ if (pipes.isEmpty()) {
|
|
|
|
|
+ log.info("排水管网综合风险评估:未找到排水管网数据");
|
|
|
|
|
+ return new Snapshot(Collections.emptyList(), Collections.emptyMap(), Collections.emptyMap(),
|
|
|
|
|
+ Collections.emptyList(), started);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<EquipmentBase> equipments = loadDrainageEquipments();
|
|
|
|
|
+ // 报警按设备编码、预警按设备ID聚合,先圈定排水设备的标识集合,避免燃气/供水数据混入
|
|
|
|
|
+ Set<String> drainageEquipmentCodes = equipments.stream()
|
|
|
|
|
+ .map(EquipmentBase::getEquipmentCode)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+ Set<String> drainageEquipmentIds = equipments.stream()
|
|
|
|
|
+ .map(EquipmentBase::getEquipmentId)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+ Map<String, long[]> alarmStat = loadAlarmStat(drainageEquipmentCodes);
|
|
|
|
|
+ Map<String, Long> warningStat = loadWarningStat(drainageEquipmentIds);
|
|
|
|
|
+ List<WaterloggingPoint> waterloggingPoints = loadWaterloggingPoints();
|
|
|
|
|
+ List<EngineeringFacility> facilities = loadFacilities();
|
|
|
|
|
+
|
|
|
|
|
+ log.info("排水管网综合风险评估开始:管网 {} 条,设备 {} 台,报警设备 {} 个,易积水点 {} 个,设施 {} 个",
|
|
|
|
|
+ pipes.size(), equipments.size(), alarmStat.size(), waterloggingPoints.size(), facilities.size());
|
|
|
|
|
+
|
|
|
|
|
+ List<DrainageRiskItemVO> rows = new ArrayList<>(pipes.size());
|
|
|
|
|
+ Map<String, PipeNetworkBase> pipeMap = new LinkedHashMap<>();
|
|
|
|
|
+ Map<String, Assessment> assessmentMap = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ for (PipeNetworkBase pipe : pipes) {
|
|
|
|
|
+ List<EquipmentBase> matched = matchEquipments(pipe, equipments, EQUIPMENT_RADIUS_METERS);
|
|
|
|
|
+
|
|
|
|
|
+ int alarmCount = 0;
|
|
|
|
|
+ int openAlarmCount = 0;
|
|
|
|
|
+ int highAlarmCount = 0;
|
|
|
|
|
+ long openWarningCount = 0;
|
|
|
|
|
+ for (EquipmentBase equipment : matched) {
|
|
|
|
|
+ long[] stat = alarmStat.get(equipment.getEquipmentCode());
|
|
|
|
|
+ if (stat != null) {
|
|
|
|
|
+ alarmCount += (int) stat[0];
|
|
|
|
|
+ openAlarmCount += (int) stat[1];
|
|
|
|
|
+ highAlarmCount += (int) stat[2];
|
|
|
|
|
+ }
|
|
|
|
|
+ Long warning = warningStat.get(equipment.getEquipmentId());
|
|
|
|
|
+ if (warning != null) {
|
|
|
|
|
+ openWarningCount += warning;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int waterloggingCount = countPointsWithin(pipe, waterloggingPoints, WATERLOGGING_RADIUS_METERS);
|
|
|
|
|
+ int facilityCount = countFacilitiesWithin(pipe, facilities, FACILITY_RADIUS_METERS);
|
|
|
|
|
+
|
|
|
|
|
+ Assessment assessment = assess(pipe, matched.size(), alarmCount, openAlarmCount,
|
|
|
|
|
+ highAlarmCount, (int) openWarningCount, waterloggingCount, facilityCount);
|
|
|
|
|
+
|
|
|
|
|
+ DrainageRiskItemVO row = toRow(pipe, matched.size(), alarmCount, openAlarmCount,
|
|
|
|
|
+ waterloggingCount, assessment);
|
|
|
|
|
+ rows.add(row);
|
|
|
|
|
+ pipeMap.put(pipe.getNetworkId(), pipe);
|
|
|
|
|
+ assessmentMap.put(pipe.getNetworkId(), assessment);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 高风险优先,其次按评分倒序,便于首页就能看到最需要关注的管网
|
|
|
|
|
+ rows.sort((a, b) -> {
|
|
|
|
|
+ int byScore = Integer.compare(b.getRiskScore() == null ? 0 : b.getRiskScore(),
|
|
|
|
|
+ a.getRiskScore() == null ? 0 : a.getRiskScore());
|
|
|
|
|
+ if (byScore != 0) {
|
|
|
|
|
+ return byScore;
|
|
|
|
|
+ }
|
|
|
|
|
+ return String.valueOf(a.getNetworkCode()).compareTo(String.valueOf(b.getNetworkCode()));
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ log.info("排水管网综合风险评估完成:耗时 {} ms", System.currentTimeMillis() - started);
|
|
|
|
|
+ return new Snapshot(rows, pipeMap, assessmentMap, equipments, started);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* ==================== 数据加载 ==================== */
|
|
|
|
|
+
|
|
|
|
|
+ private List<PipeNetworkBase> loadDrainagePipes() {
|
|
|
|
|
+ LambdaQueryWrapper<PipeNetworkBase> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.eq(PipeNetworkBase::getNetworkType, PipeNetworkBase.NETWORK_TYPE_DRAINAGE);
|
|
|
|
|
+ return pipeNetworkBaseMapper.selectList(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 加载排水设备(含排水根类型下所有子类型),只保留带坐标的
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<EquipmentBase> loadDrainageEquipments() {
|
|
|
|
|
+ Set<Integer> typeIds = drainageTypeIds();
|
|
|
|
|
+ if (typeIds.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ LambdaQueryWrapper<EquipmentBase> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.in(EquipmentBase::getEquipmentTypeId, typeIds);
|
|
|
|
|
+ wrapper.isNotNull(EquipmentBase::getLongitude).isNotNull(EquipmentBase::getLatitude);
|
|
|
|
|
+ return equipmentBaseMapper.selectList(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析排水设备根类型下的全部类型ID
|
|
|
|
|
+ */
|
|
|
|
|
+ private Set<Integer> drainageTypeIds() {
|
|
|
|
|
+ Set<Integer> result = new LinkedHashSet<>();
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<EquipmentType> types = equipmentTypeMapper.selectList(null);
|
|
|
|
|
+ Map<Integer, List<Integer>> children = new HashMap<>();
|
|
|
|
|
+ for (EquipmentType type : types) {
|
|
|
|
|
+ if (type.getParentId() != null) {
|
|
|
|
|
+ children.computeIfAbsent(type.getParentId(), key -> new ArrayList<>()).add(type.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Deque<Integer> queue = new ArrayDeque<>();
|
|
|
|
|
+ queue.add(DRAINAGE_ROOT_TYPE_ID);
|
|
|
|
|
+ while (!queue.isEmpty()) {
|
|
|
|
|
+ Integer id = queue.poll();
|
|
|
|
|
+ if (!result.add(id)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Integer> sub = children.get(id);
|
|
|
|
|
+ if (sub != null) {
|
|
|
|
|
+ queue.addAll(sub);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("解析排水设备类型失败,退化为仅根类型", e);
|
|
|
|
|
+ result.add(DRAINAGE_ROOT_TYPE_ID);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 近30天报警按设备编码聚合:[次数, 未处置数, 高等级数]
|
|
|
|
|
+ *
|
|
|
|
|
+ * 只统计传入的排水设备编码,燃气与供水设备不进统计。
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, long[]> loadAlarmStat(Set<String> drainageEquipmentCodes) {
|
|
|
|
|
+ Map<String, long[]> result = new HashMap<>();
|
|
|
|
|
+ if (drainageEquipmentCodes.isEmpty()) {
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ QueryWrapper<AlarmData> wrapper = new QueryWrapper<>();
|
|
|
|
|
+ wrapper.select("device_code as device_code",
|
|
|
|
|
+ "count(*) as total_cnt",
|
|
|
|
|
+ "sum(case when alarm_status = 0 then 1 else 0 end) as open_cnt",
|
|
|
|
|
+ "sum(case when alarm_level <= 2 then 1 else 0 end) as high_cnt")
|
|
|
|
|
+ .ge("create_time", LocalDateTime.now().minusDays(RUNNING_WINDOW_DAYS))
|
|
|
|
|
+ .in("device_code", drainageEquipmentCodes)
|
|
|
|
|
+ .groupBy("device_code");
|
|
|
|
|
+ List<Map<String, Object>> rows = alarmDataMapper.selectMaps(wrapper);
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ String deviceCode = str(row, "device_code");
|
|
|
|
|
+ if (deviceCode == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ result.put(deviceCode, new long[]{num(row, "total_cnt"), num(row, "open_cnt"), num(row, "high_cnt")});
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("统计排水管网报警数据失败", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 近30天未闭环预警按设备ID聚合
|
|
|
|
|
+ *
|
|
|
|
|
+ * 只统计传入的排水设备ID,early_warning 中其他业务线的预警不进统计。
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, Long> loadWarningStat(Set<String> drainageEquipmentIds) {
|
|
|
|
|
+ Map<String, Long> result = new HashMap<>();
|
|
|
|
|
+ if (drainageEquipmentIds.isEmpty()) {
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ QueryWrapper<DrainageWarning> wrapper = new QueryWrapper<>();
|
|
|
|
|
+ wrapper.select("equipment_id as equipment_id", "count(*) as total_cnt")
|
|
|
|
|
+ .ge("create_time", LocalDateTime.now().minusDays(RUNNING_WINDOW_DAYS))
|
|
|
|
|
+ .in("equipment_id", drainageEquipmentIds)
|
|
|
|
|
+ .and(w -> w.isNull("status").or().ne("status", "CLOSED"))
|
|
|
|
|
+ .groupBy("equipment_id");
|
|
|
|
|
+ List<Map<String, Object>> rows = drainageWarningMapper.selectMaps(wrapper);
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ String equipmentId = str(row, "equipment_id");
|
|
|
|
|
+ if (equipmentId != null) {
|
|
|
|
|
+ result.put(equipmentId, num(row, "total_cnt"));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("统计排水管网预警数据失败", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<WaterloggingPoint> loadWaterloggingPoints() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ LambdaQueryWrapper<WaterloggingPoint> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.isNotNull(WaterloggingPoint::getLongitude).isNotNull(WaterloggingPoint::getLatitude);
|
|
|
|
|
+ return waterloggingPointMapper.selectList(wrapper);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("加载易积水点失败", e);
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<EngineeringFacility> loadFacilities() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ LambdaQueryWrapper<EngineeringFacility> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.isNotNull(EngineeringFacility::getLongitude).isNotNull(EngineeringFacility::getLatitude);
|
|
|
|
|
+ return engineeringFacilityMapper.selectList(wrapper);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("加载工程设施失败", e);
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 取管网周边指定距离内的设备
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<EquipmentBase> matchEquipments(PipeNetworkBase pipe, List<EquipmentBase> equipments, double radius) {
|
|
|
|
|
+ if (equipments.isEmpty() || !hasAnyCoordinate(pipe)) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<EquipmentBase> matched = new ArrayList<>();
|
|
|
|
|
+ for (EquipmentBase equipment : equipments) {
|
|
|
|
|
+ double lng = equipment.getLongitude().doubleValue();
|
|
|
|
|
+ double lat = equipment.getLatitude().doubleValue();
|
|
|
|
|
+ if (distanceToPipe(pipe, lng, lat) <= radius) {
|
|
|
|
|
+ matched.add(equipment);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return matched;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int countPointsWithin(PipeNetworkBase pipe, List<WaterloggingPoint> points, double radius) {
|
|
|
|
|
+ if (points.isEmpty() || !hasAnyCoordinate(pipe)) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (WaterloggingPoint point : points) {
|
|
|
|
|
+ if (distanceToPipe(pipe, point.getLongitude().doubleValue(), point.getLatitude().doubleValue()) <= radius) {
|
|
|
|
|
+ count++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return count;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int countFacilitiesWithin(PipeNetworkBase pipe, List<EngineeringFacility> facilities, double radius) {
|
|
|
|
|
+ if (facilities.isEmpty() || !hasAnyCoordinate(pipe)) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (EngineeringFacility facility : facilities) {
|
|
|
|
|
+ if (distanceToPipe(pipe, facility.getLongitude().doubleValue(), facility.getLatitude().doubleValue()) <= radius) {
|
|
|
|
|
+ count++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return count;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<AlarmData> loadRecentAlarms(List<EquipmentBase> equipments, int limit) {
|
|
|
|
|
+ if (equipments.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<String> codes = equipments.stream()
|
|
|
|
|
+ .map(EquipmentBase::getEquipmentCode)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ if (codes.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ LambdaQueryWrapper<AlarmData> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.in(AlarmData::getDeviceCode, codes);
|
|
|
|
|
+ wrapper.orderByDesc(AlarmData::getCreateTime);
|
|
|
|
|
+ wrapper.last("limit " + Math.max(1, limit));
|
|
|
|
|
+ return alarmDataMapper.selectList(wrapper);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("加载管网关联报警失败", e);
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* ==================== 评分模型 ==================== */
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 四维评分:管网本体 35 + 运行报警 35 + 环境叠加 20 + 数据质量 10
|
|
|
|
|
+ */
|
|
|
|
|
+ private Assessment assess(PipeNetworkBase pipe, int equipmentCount, int alarmCount, int openAlarmCount,
|
|
|
|
|
+ int highAlarmCount, int openWarningCount, int waterloggingCount, int facilityCount) {
|
|
|
|
|
+ int age = pipeAge(pipe.getBuildDate());
|
|
|
|
|
+
|
|
|
|
|
+ int ageScore = age <= 5 ? 3 : age <= 10 ? 8 : age <= 15 ? 12 : age <= 20 ? 14 : 15;
|
|
|
|
|
+ int materialScore = materialScore(pipe.getMaterial());
|
|
|
|
|
+ int diameterScore = diameterScore(pipe.getDiameter());
|
|
|
|
|
+ int levelScore = levelScore(pipe.getNetworkLevel());
|
|
|
|
|
+ int pipeScore = ageScore + materialScore + diameterScore + levelScore;
|
|
|
|
|
+
|
|
|
|
|
+ int alarmScore = alarmCount <= 0 ? 0 : alarmCount <= 2 ? 8 : alarmCount <= 5 ? 12 : 15;
|
|
|
|
|
+ int openScore = openAlarmCount <= 0 ? 0 : openAlarmCount == 1 ? 5 : openAlarmCount == 2 ? 8 : 10;
|
|
|
|
|
+ int highScore = highAlarmCount <= 0 ? 0 : highAlarmCount == 1 ? 5 : 10;
|
|
|
|
|
+ int runningScore = alarmScore + openScore + highScore;
|
|
|
|
|
+
|
|
|
|
|
+ int waterScore = waterloggingCount <= 0 ? 0 : waterloggingCount == 1 ? 5 : waterloggingCount == 2 ? 7 : 10;
|
|
|
|
|
+ int warningScore = openWarningCount <= 0 ? 0 : openWarningCount == 1 ? 4 : 6;
|
|
|
|
|
+ int facilityScore = facilityCount > 0 ? 4 : 0;
|
|
|
|
|
+ int environmentScore = waterScore + warningScore + facilityScore;
|
|
|
|
|
+
|
|
|
|
|
+ int missingCoordinate = hasAnyCoordinate(pipe) ? 0 : 4;
|
|
|
|
|
+ int missingMaterial = isBlank(pipe.getMaterial()) ? 3 : 0;
|
|
|
|
|
+ int missingDimension = isBlank(pipe.getLength() == null ? null : pipe.getLength().toPlainString())
|
|
|
|
|
+ || pipe.getDiameter() == null ? 3 : 0;
|
|
|
|
|
+ int qualityScore = missingCoordinate + missingMaterial + missingDimension;
|
|
|
|
|
+
|
|
|
|
|
+ int total = Math.min(100, pipeScore + runningScore + environmentScore + qualityScore);
|
|
|
|
|
+ String level = total >= 70 ? LEVEL_HIGH : total >= 40 ? LEVEL_MEDIUM : LEVEL_LOW;
|
|
|
|
|
+
|
|
|
|
|
+ Assessment assessment = new Assessment();
|
|
|
|
|
+ assessment.total = total;
|
|
|
|
|
+ assessment.level = level;
|
|
|
|
|
+ assessment.alarmCount = alarmCount;
|
|
|
|
|
+ assessment.openAlarmCount = openAlarmCount;
|
|
|
|
|
+ assessment.waterloggingCount = waterloggingCount;
|
|
|
|
|
+ assessment.dimensions = dimensions(pipeScore, ageScore, materialScore, diameterScore, levelScore,
|
|
|
|
|
+ runningScore, alarmScore, openScore, highScore,
|
|
|
|
|
+ environmentScore, waterScore, warningScore, facilityScore,
|
|
|
|
|
+ qualityScore, missingCoordinate, missingMaterial, missingDimension,
|
|
|
|
|
+ age, pipe.getMaterial(), pipe.getDiameter(), waterloggingCount, facilityCount);
|
|
|
|
|
+ assessment.factors = factors(pipe, age, alarmCount, openAlarmCount, highAlarmCount,
|
|
|
|
|
+ waterloggingCount, facilityCount);
|
|
|
|
|
+ assessment.measures = measures(level, age, alarmCount, openAlarmCount, waterloggingCount);
|
|
|
|
|
+ assessment.protectiveTargets = protectiveTargets(waterloggingCount, facilityCount);
|
|
|
|
|
+ assessment.hazardSources = hazardSources(equipmentCount, alarmCount, openAlarmCount);
|
|
|
|
|
+ return assessment;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<Map<String, Object>> dimensions(int pipeScore, int ageScore, int materialScore, int diameterScore,
|
|
|
|
|
+ int levelScore, int runningScore, int alarmScore, int openScore,
|
|
|
|
|
+ int highScore, int environmentScore, int waterScore, int warningScore,
|
|
|
|
|
+ int facilityScore, int qualityScore, int missingCoordinate,
|
|
|
|
|
+ int missingMaterial, int missingDimension, int age, String material,
|
|
|
|
|
+ BigDecimal diameter, int waterloggingCount, int facilityCount) {
|
|
|
|
|
+ List<Map<String, Object>> dimensions = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> pipeDim = dimension("pipe", "管网本体", pipeScore, 35);
|
|
|
|
|
+ addItem(pipeDim, "管龄", ageScore, age <= 0 ? "未登记建设日期" : "已运行 " + age + " 年");
|
|
|
|
|
+ addItem(pipeDim, "材质", materialScore, isBlank(material) ? "未登记材质" : material);
|
|
|
|
|
+ addItem(pipeDim, "管径", diameterScore, diameter == null ? "未登记管径" : diameter.stripTrailingZeros().toPlainString() + " mm");
|
|
|
|
|
+ addItem(pipeDim, "管线类别", levelScore, "按类别影响范围计分");
|
|
|
|
|
+ dimensions.add(pipeDim);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> runningDim = dimension("running", "运行报警", runningScore, 35);
|
|
|
|
|
+ addItem(runningDim, "近30天报警", alarmScore, alarmScore + " 分");
|
|
|
|
|
+ addItem(runningDim, "未处置报警", openScore, openScore + " 分");
|
|
|
|
|
+ addItem(runningDim, "高等级报警", highScore, highScore + " 分");
|
|
|
|
|
+ dimensions.add(runningDim);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> envDim = dimension("environment", "环境叠加", environmentScore, 20);
|
|
|
|
|
+ addItem(envDim, "周边易积水点", waterScore, waterloggingCount + " 处");
|
|
|
|
|
+ addItem(envDim, "周边未闭环预警", warningScore, "按关联设备预警计分");
|
|
|
|
|
+ addItem(envDim, "周边工程设施", facilityScore, facilityCount + " 处");
|
|
|
|
|
+ dimensions.add(envDim);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> qualityDim = dimension("quality", "数据质量", qualityScore, 10);
|
|
|
|
|
+ addItem(qualityDim, "坐标完整性", missingCoordinate, missingCoordinate > 0 ? "缺起终点坐标" : "正常");
|
|
|
|
|
+ addItem(qualityDim, "材质完整性", missingMaterial, missingMaterial > 0 ? "缺材质" : "正常");
|
|
|
|
|
+ addItem(qualityDim, "规格完整性", missingDimension, missingDimension > 0 ? "缺管径或长度" : "正常");
|
|
|
|
|
+ dimensions.add(qualityDim);
|
|
|
|
|
+
|
|
|
|
|
+ return dimensions;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> dimension(String key, String label, int score, int max) {
|
|
|
|
|
+ Map<String, Object> dim = new LinkedHashMap<>();
|
|
|
|
|
+ dim.put("key", key);
|
|
|
|
|
+ dim.put("label", label);
|
|
|
|
|
+ dim.put("score", score);
|
|
|
|
|
+ dim.put("max", max);
|
|
|
|
|
+ dim.put("items", new ArrayList<Map<String, Object>>());
|
|
|
|
|
+ return dim;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ private void addItem(Map<String, Object> dimension, String label, int score, String detail) {
|
|
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
|
|
+ item.put("label", label);
|
|
|
|
|
+ item.put("score", score);
|
|
|
|
|
+ item.put("detail", detail);
|
|
|
|
|
+ ((List<Map<String, Object>>) dimension.get("items")).add(item);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 风险构成要素,直接给出可读原因
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<String> factors(PipeNetworkBase pipe, int age, int alarmCount, int openAlarmCount,
|
|
|
|
|
+ int highAlarmCount, int waterloggingCount, int facilityCount) {
|
|
|
|
|
+ List<String> factors = new ArrayList<>();
|
|
|
|
|
+ if (age > 20) {
|
|
|
|
|
+ factors.add("管网已运行 " + age + " 年,超过设计使用年限,结构性缺陷概率高");
|
|
|
|
|
+ } else if (age > 10) {
|
|
|
|
|
+ factors.add("管网已运行 " + age + " 年,进入老化加速期");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isBlank(pipe.getMaterial())) {
|
|
|
|
|
+ factors.add("未登记管材,无法判断材质风险");
|
|
|
|
|
+ } else if (pipe.getMaterial().contains("混凝土") || pipe.getMaterial().contains("砼") || pipe.getMaterial().contains("水泥")) {
|
|
|
|
|
+ factors.add("管材为「" + pipe.getMaterial() + "」,存在腐蚀与接口渗漏风险");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (pipe.getDiameter() != null && pipe.getDiameter().doubleValue() < 300) {
|
|
|
|
|
+ factors.add("管径偏小(" + pipe.getDiameter().stripTrailingZeros().toPlainString() + " mm),易淤积堵塞");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (alarmCount > 0) {
|
|
|
|
|
+ factors.add("近 " + RUNNING_WINDOW_DAYS + " 天关联设备产生 " + alarmCount + " 次报警");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (openAlarmCount > 0) {
|
|
|
|
|
+ factors.add("存在 " + openAlarmCount + " 条未处置报警");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (highAlarmCount > 0) {
|
|
|
|
|
+ factors.add("存在 " + highAlarmCount + " 条高等级报警");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (waterloggingCount > 0) {
|
|
|
|
|
+ factors.add("周边 " + (int) WATERLOGGING_RADIUS_METERS + " 米内存在 " + waterloggingCount + " 处易积水点");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (facilityCount > 0) {
|
|
|
|
|
+ factors.add("周边 " + (int) FACILITY_RADIUS_METERS + " 米内存在 " + facilityCount + " 处泵站/闸门等设施");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!hasAnyCoordinate(pipe)) {
|
|
|
|
|
+ factors.add("缺少起终点坐标,未能完成空间关联,实际风险可能被低估");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (factors.isEmpty()) {
|
|
|
|
|
+ factors.add("管网本体状态良好,近 " + RUNNING_WINDOW_DAYS + " 天无关联报警");
|
|
|
|
|
+ }
|
|
|
|
|
+ return factors;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按等级给出可执行的处置建议
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<String> measures(String level, int age, int alarmCount, int openAlarmCount, int waterloggingCount) {
|
|
|
|
|
+ List<String> measures = new ArrayList<>();
|
|
|
|
|
+ if (LEVEL_HIGH.equals(level)) {
|
|
|
|
|
+ measures.add("列入重点管控清单,巡检频次提高至每周不少于 1 次");
|
|
|
|
|
+ measures.add("安排管道内窥检测(CCTV/声呐),核实结构性缺陷");
|
|
|
|
|
+ } else if (LEVEL_MEDIUM.equals(level)) {
|
|
|
|
|
+ measures.add("纳入常规巡检计划,巡检频次不低于每两周 1 次");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ measures.add("维持现有巡检频次,按季度复核风险等级");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (openAlarmCount > 0) {
|
|
|
|
|
+ measures.add("优先处置 " + openAlarmCount + " 条未闭环报警,完成现场核实");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (alarmCount > 0) {
|
|
|
|
|
+ measures.add("核查报警设备阈值配置,排除误报并同步维护设备");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (waterloggingCount > 0) {
|
|
|
|
|
+ measures.add("汛期前疏通该管段并联动周边易积水点排水设施");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (age > 20) {
|
|
|
|
|
+ measures.add("纳入老旧管网改造计划,评估翻建或非开挖修复方案");
|
|
|
|
|
+ }
|
|
|
|
|
+ return measures;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String protectiveTargets(int waterloggingCount, int facilityCount) {
|
|
|
|
|
+ if (waterloggingCount == 0 && facilityCount == 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<String> parts = new ArrayList<>();
|
|
|
|
|
+ if (waterloggingCount > 0) {
|
|
|
|
|
+ parts.add("周边易积水点 " + waterloggingCount + " 处");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (facilityCount > 0) {
|
|
|
|
|
+ parts.add("周边泵站/闸门等设施 " + facilityCount + " 处");
|
|
|
|
|
+ }
|
|
|
|
|
+ return String.join(";", parts);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String hazardSources(int equipmentCount, int alarmCount, int openAlarmCount) {
|
|
|
|
|
+ if (equipmentCount == 0) {
|
|
|
|
|
+ return "未关联监测设备";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "关联监测设备 " + equipmentCount + " 台,近 " + RUNNING_WINDOW_DAYS + " 天报警 "
|
|
|
|
|
+ + alarmCount + " 次,其中未处置 " + openAlarmCount + " 条";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int materialScore(String material) {
|
|
|
|
|
+ if (isBlank(material)) {
|
|
|
|
|
+ return 5;
|
|
|
|
|
+ }
|
|
|
|
|
+ String value = material.trim();
|
|
|
|
|
+ if (value.contains("混凝土") || value.contains("砼") || value.contains("水泥")) {
|
|
|
|
|
+ return 8;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.contains("球墨")) {
|
|
|
|
|
+ return 4;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.contains("铸铁") || value.contains("铁")) {
|
|
|
|
|
+ return 6;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.contains("钢")) {
|
|
|
|
|
+ return 5;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.contains("PVC") || value.contains("PE") || value.contains("塑料") || value.contains("HDPE")) {
|
|
|
|
|
+ return 2;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 4;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int diameterScore(BigDecimal diameter) {
|
|
|
|
|
+ if (diameter == null) {
|
|
|
|
|
+ return 7;
|
|
|
|
|
+ }
|
|
|
|
|
+ double value = diameter.doubleValue();
|
|
|
|
|
+ if (value < 300) {
|
|
|
|
|
+ return 7;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value < 600) {
|
|
|
|
|
+ return 4;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value < 1000) {
|
|
|
|
|
+ return 2;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int levelScore(String networkLevel) {
|
|
|
|
|
+ if (isBlank(networkLevel)) {
|
|
|
|
|
+ return 2;
|
|
|
|
|
+ }
|
|
|
|
|
+ String value = networkLevel.trim();
|
|
|
|
|
+ if (value.contains("主干") || value.contains("高") || "high".equalsIgnoreCase(value)) {
|
|
|
|
|
+ return 5;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.contains("次干") || value.contains("中") || "medium".equalsIgnoreCase(value)) {
|
|
|
|
|
+ return 3;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.contains("支") || value.contains("低") || "low".equalsIgnoreCase(value)) {
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 2;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* ==================== 空间计算 ==================== */
|
|
|
|
|
+
|
|
|
|
|
+ private boolean hasAnyCoordinate(PipeNetworkBase pipe) {
|
|
|
|
|
+ return hasCoordinate(pipe.getStartLongitude(), pipe.getStartLatitude())
|
|
|
|
|
+ || hasCoordinate(pipe.getEndLongitude(), pipe.getEndLatitude());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean hasCoordinate(BigDecimal lng, BigDecimal lat) {
|
|
|
|
|
+ return lng != null && lat != null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 点到管段的最短距离(米)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 城市尺度下把经纬度按当地纬度折算成米做平面近似,精度足以支撑 300/500 米的关联判定。
|
|
|
|
|
+ */
|
|
|
|
|
+ private double distanceToPipe(PipeNetworkBase pipe, double lng, double lat) {
|
|
|
|
|
+ boolean hasStart = hasCoordinate(pipe.getStartLongitude(), pipe.getStartLatitude());
|
|
|
|
|
+ boolean hasEnd = hasCoordinate(pipe.getEndLongitude(), pipe.getEndLatitude());
|
|
|
|
|
+ if (hasStart && hasEnd) {
|
|
|
|
|
+ return distancePointToSegment(lng, lat,
|
|
|
|
|
+ pipe.getStartLongitude().doubleValue(), pipe.getStartLatitude().doubleValue(),
|
|
|
|
|
+ pipe.getEndLongitude().doubleValue(), pipe.getEndLatitude().doubleValue());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (hasStart) {
|
|
|
|
|
+ return distancePointToSegment(lng, lat,
|
|
|
|
|
+ pipe.getStartLongitude().doubleValue(), pipe.getStartLatitude().doubleValue(),
|
|
|
|
|
+ pipe.getStartLongitude().doubleValue(), pipe.getStartLatitude().doubleValue());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (hasEnd) {
|
|
|
|
|
+ return distancePointToSegment(lng, lat,
|
|
|
|
|
+ pipe.getEndLongitude().doubleValue(), pipe.getEndLatitude().doubleValue(),
|
|
|
|
|
+ pipe.getEndLongitude().doubleValue(), pipe.getEndLatitude().doubleValue());
|
|
|
|
|
+ }
|
|
|
|
|
+ return Double.MAX_VALUE;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private double distancePointToSegment(double px, double py, double ax, double ay, double bx, double by) {
|
|
|
|
|
+ double meterPerLat = 110540d;
|
|
|
|
|
+ double meterPerLng = 111320d * Math.cos(Math.toRadians((ay + by) / 2));
|
|
|
|
|
+ // 以目标点为原点建立局部平面坐标
|
|
|
|
|
+ double x1 = (ax - px) * meterPerLng;
|
|
|
|
|
+ double y1 = (ay - py) * meterPerLat;
|
|
|
|
|
+ double x2 = (bx - px) * meterPerLng;
|
|
|
|
|
+ double y2 = (by - py) * meterPerLat;
|
|
|
|
|
+ double dx = x2 - x1;
|
|
|
|
|
+ double dy = y2 - y1;
|
|
|
|
|
+ double lengthSquared = dx * dx + dy * dy;
|
|
|
|
|
+ double t = lengthSquared == 0 ? 0 : -(x1 * dx + y1 * dy) / lengthSquared;
|
|
|
|
|
+ t = Math.max(0, Math.min(1, t));
|
|
|
|
|
+ return Math.hypot(x1 + t * dx, y1 + t * dy);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* ==================== 组装与工具 ==================== */
|
|
|
|
|
+
|
|
|
|
|
+ private DrainageRiskItemVO toRow(PipeNetworkBase pipe, int equipmentCount, int alarmCount,
|
|
|
|
|
+ int openAlarmCount, int waterloggingCount, Assessment assessment) {
|
|
|
|
|
+ DrainageRiskItemVO row = new DrainageRiskItemVO();
|
|
|
|
|
+ row.setNetworkId(pipe.getNetworkId());
|
|
|
|
|
+ row.setNetworkCode(pipe.getNetworkCode());
|
|
|
|
|
+ row.setNetworkName(pipe.getNetworkName());
|
|
|
|
|
+ row.setNetworkLevel(pipe.getNetworkLevel());
|
|
|
|
|
+ row.setLocation(pipe.getLocation());
|
|
|
|
|
+ row.setManagementUnit(pipe.getManagementUnit());
|
|
|
|
|
+ row.setMaterial(pipe.getMaterial());
|
|
|
|
|
+ row.setDiameter(pipe.getDiameter());
|
|
|
|
|
+ row.setLength(pipe.getLength());
|
|
|
|
|
+ row.setPipeAge(pipeAge(pipe.getBuildDate()));
|
|
|
|
|
+ row.setStartLongitude(pipe.getStartLongitude());
|
|
|
|
|
+ row.setStartLatitude(pipe.getStartLatitude());
|
|
|
|
|
+ row.setEndLongitude(pipe.getEndLongitude());
|
|
|
|
|
+ row.setEndLatitude(pipe.getEndLatitude());
|
|
|
|
|
+ row.setEquipmentCount(equipmentCount);
|
|
|
|
|
+ row.setAlarmCount(alarmCount);
|
|
|
|
|
+ row.setOpenAlarmCount(openAlarmCount);
|
|
|
|
|
+ row.setWaterloggingCount(waterloggingCount);
|
|
|
|
|
+ row.setRiskScore(assessment.total);
|
|
|
|
|
+ row.setRiskLevel(assessment.level);
|
|
|
|
|
+ row.setRiskLevelName(levelName(assessment.level));
|
|
|
|
|
+ row.setRiskColor(levelColor(assessment.level));
|
|
|
|
|
+ row.setAssessTime(LocalDateTime.now());
|
|
|
|
|
+ return row;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<DrainageRiskItemVO> filter(List<DrainageRiskItemVO> rows, DrainageRiskPageInDTO dto) {
|
|
|
|
|
+ if (dto == null) {
|
|
|
|
|
+ return new ArrayList<>(rows);
|
|
|
|
|
+ }
|
|
|
|
|
+ String networkCode = trim(dto.getNetworkCode());
|
|
|
|
|
+ String networkName = trim(dto.getNetworkName());
|
|
|
|
|
+ String networkLevel = trim(dto.getNetworkLevel());
|
|
|
|
|
+ String managementUnit = trim(dto.getManagementUnit());
|
|
|
|
|
+ String location = trim(dto.getLocation());
|
|
|
|
|
+ String riskLevel = trim(dto.getRiskLevel());
|
|
|
|
|
+
|
|
|
|
|
+ List<DrainageRiskItemVO> result = new ArrayList<>();
|
|
|
|
|
+ for (DrainageRiskItemVO row : rows) {
|
|
|
|
|
+ if (networkCode != null && !containsIgnoreCase(row.getNetworkCode(), networkCode)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (networkName != null && !containsIgnoreCase(row.getNetworkName(), networkName)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (networkLevel != null && !Objects.equals(row.getNetworkLevel(), networkLevel)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (managementUnit != null && !Objects.equals(row.getManagementUnit(), managementUnit)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (location != null && !containsIgnoreCase(row.getLocation(), location)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (riskLevel != null && !Objects.equals(row.getRiskLevel(), riskLevel)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ result.add(row);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<Map<String, Object>> levelDistribution(int high, int medium, int low) {
|
|
|
|
|
+ List<Map<String, Object>> list = new ArrayList<>();
|
|
|
|
|
+ list.add(distributionItem("高风险", high, levelColor(LEVEL_HIGH)));
|
|
|
|
|
+ list.add(distributionItem("中风险", medium, levelColor(LEVEL_MEDIUM)));
|
|
|
|
|
+ list.add(distributionItem("低风险", low, levelColor(LEVEL_LOW)));
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> distributionItem(String name, int value, String color) {
|
|
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
|
|
+ item.put("name", name);
|
|
|
|
|
+ item.put("value", value);
|
|
|
|
|
+ item.put("color", color);
|
|
|
|
|
+ return item;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<Map<String, Object>> distribution(List<DrainageRiskItemVO> rows,
|
|
|
|
|
+ java.util.function.Function<DrainageRiskItemVO, String> getter) {
|
|
|
|
|
+ Map<String, Integer> counter = new LinkedHashMap<>();
|
|
|
|
|
+ for (DrainageRiskItemVO row : rows) {
|
|
|
|
|
+ String key = getter.apply(row);
|
|
|
|
|
+ if (isBlank(key)) {
|
|
|
|
|
+ key = "未填写";
|
|
|
|
|
+ }
|
|
|
|
|
+ counter.merge(key, 1, Integer::sum);
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Map<String, Object>> list = new ArrayList<>();
|
|
|
|
|
+ counter.forEach((name, value) -> {
|
|
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
|
|
+ item.put("name", name);
|
|
|
|
|
+ item.put("value", value);
|
|
|
|
|
+ list.add(item);
|
|
|
|
|
+ });
|
|
|
|
|
+ list.sort((a, b) -> Integer.compare(((Number) b.get("value")).intValue(),
|
|
|
|
|
+ ((Number) a.get("value")).intValue()));
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<String> distinct(List<DrainageRiskItemVO> rows,
|
|
|
|
|
+ java.util.function.Function<DrainageRiskItemVO, String> getter) {
|
|
|
|
|
+ return rows.stream()
|
|
|
|
|
+ .map(getter)
|
|
|
|
|
+ .filter(value -> !isBlank(value))
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .sorted()
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> option(String value, String label) {
|
|
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
|
|
+ item.put("value", value);
|
|
|
|
|
+ item.put("label", label);
|
|
|
|
|
+ return item;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String levelName(String level) {
|
|
|
|
|
+ if (LEVEL_HIGH.equals(level)) {
|
|
|
|
|
+ return "高风险";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (LEVEL_MEDIUM.equals(level)) {
|
|
|
|
|
+ return "中风险";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "低风险";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String levelColor(String level) {
|
|
|
|
|
+ if (LEVEL_HIGH.equals(level)) {
|
|
|
|
|
+ return "#F56C6C";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (LEVEL_MEDIUM.equals(level)) {
|
|
|
|
|
+ return "#E6A23C";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "#67C23A";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int pipeAge(java.util.Date buildDate) {
|
|
|
|
|
+ if (buildDate == null) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ int buildYear = buildDate.toInstant().atZone(ZoneId.systemDefault()).getYear();
|
|
|
|
|
+ return Math.max(0, LocalDate.now().getYear() - buildYear);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String trim(String value) {
|
|
|
|
|
+ if (value == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ String trimmed = value.trim();
|
|
|
|
|
+ return trimmed.isEmpty() ? null : trimmed;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean containsIgnoreCase(String source, String keyword) {
|
|
|
|
|
+ if (source == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return source.toLowerCase(Locale.ROOT).contains(keyword.toLowerCase(Locale.ROOT));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean isBlank(String value) {
|
|
|
|
|
+ return value == null || value.trim().isEmpty();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 分组查询结果取值:兼容不同数据库对列别名大小写的处理
|
|
|
|
|
+ */
|
|
|
|
|
+ private String str(Map<String, Object> row, String key) {
|
|
|
|
|
+ for (Map.Entry<String, Object> entry : row.entrySet()) {
|
|
|
|
|
+ if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(key) && entry.getValue() != null) {
|
|
|
|
|
+ return entry.getValue().toString();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private long num(Map<String, Object> row, String key) {
|
|
|
|
|
+ String value = str(row, key);
|
|
|
|
|
+ if (value == null || value.isEmpty()) {
|
|
|
|
|
+ return 0L;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ return (long) Double.parseDouble(value);
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ return 0L;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* ==================== 内部结构 ==================== */
|
|
|
|
|
+
|
|
|
|
|
+ private static class Assessment {
|
|
|
|
|
+ private int total;
|
|
|
|
|
+ private String level;
|
|
|
|
|
+ private int alarmCount;
|
|
|
|
|
+ private int openAlarmCount;
|
|
|
|
|
+ private int waterloggingCount;
|
|
|
|
|
+ private List<Map<String, Object>> dimensions;
|
|
|
|
|
+ private List<String> factors;
|
|
|
|
|
+ private List<String> measures;
|
|
|
|
|
+ private String protectiveTargets;
|
|
|
|
|
+ private String hazardSources;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static class Snapshot {
|
|
|
|
|
+ private final List<DrainageRiskItemVO> rows;
|
|
|
|
|
+ private final Map<String, PipeNetworkBase> pipes;
|
|
|
|
|
+ private final Map<String, Assessment> assessments;
|
|
|
|
|
+ private final List<EquipmentBase> equipments;
|
|
|
|
|
+ private final long loadTime;
|
|
|
|
|
+
|
|
|
|
|
+ private Snapshot(List<DrainageRiskItemVO> rows, Map<String, PipeNetworkBase> pipes,
|
|
|
|
|
+ Map<String, Assessment> assessments, List<EquipmentBase> equipments,
|
|
|
|
|
+ long loadTime) {
|
|
|
|
|
+ this.rows = rows;
|
|
|
|
|
+ this.pipes = pipes;
|
|
|
|
|
+ this.assessments = assessments;
|
|
|
|
|
+ this.equipments = equipments;
|
|
|
|
|
+ this.loadTime = loadTime;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|