Sfoglia il codice sorgente

fix(cockpit): 补齐管网展示字典单源与跨端点契约回归

Kazerin 1 settimana fa
parent
commit
29d1ae2995

+ 158 - 0
pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/cockpit/CockpitPipeNetworkCrossEndpointContractTest.java

@@ -0,0 +1,158 @@
+package com.zksy.web.controller.cockpit;
+
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.zksy.WaterSupply.WaterPlantInfo.service.IWaterPlantInfoService;
+import com.zksy.WaterSupply.WaterPumpStation.domain.WaterPumpStation;
+import com.zksy.WaterSupply.WaterPumpStation.service.IWaterPumpStationService;
+import com.zksy.WaterSupply.WaterSourceInfo.service.IWaterSourceInfoService;
+import com.zksy.base.domain.PipeNetworkBase;
+import com.zksy.base.service.EquipmentBaseService;
+import com.zksy.base.service.EquipmentTypeService;
+import com.zksy.base.service.ManholeBaseService;
+import com.zksy.base.service.PipeNetworkBaseService;
+import com.zksy.cockpit.device.BusinessCockpitDeviceBoundaryAdapter;
+import com.zksy.cockpit.device.CockpitDeviceBoundaryService;
+import com.zksy.cockpit.infrastructure.BusinessCockpitInfrastructureBaseArchivesAdapter;
+import com.zksy.cockpit.infrastructure.BusinessCockpitInfrastructureManagementInformationAdapter;
+import com.zksy.cockpit.infrastructure.BusinessCockpitInfrastructureOperatingInformationAdapter;
+import com.zksy.cockpit.infrastructure.CockpitInfrastructureBaseArchivesService;
+import com.zksy.cockpit.infrastructure.CockpitInfrastructureManagementInformationService;
+import com.zksy.cockpit.infrastructure.CockpitInfrastructureOperatingInformationService;
+import com.zksy.cockpit.overview.BusinessCockpitOverviewInfrastructureAdapter;
+import com.zksy.cockpit.overview.CockpitOverviewInfrastructureService;
+import com.zksy.drainage.service.EngineeringFacilityService;
+import org.apache.ibatis.builder.MapperBuilderAssistant;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/** 同一份管网数据贯通五套真实适配器与服务,锁定行业计数及未知项排除口径。 */
+class CockpitPipeNetworkCrossEndpointContractTest {
+    private static final Clock CLOCK = Clock.fixed(Instant.parse("2026-10-12T00:00:00Z"), ZoneOffset.UTC);
+    private static final ObjectMapper JSON = new ObjectMapper();
+
+    @BeforeAll
+    static void initializeLambdaCache() {
+        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
+                WaterPumpStation.class);
+    }
+
+    @Test
+    void numericAndHistoricalTypesHaveEqualIndustryCountsAcrossFiveEndpoints() {
+        assertCrossEndpointCounts(Arrays.asList("0", "1", "2", "gas", "drainage", "water"), 0);
+    }
+
+    @Test
+    void unknownTypesIncludingRemovedCodeThreeAreCountedRatherThanLost() {
+        assertCrossEndpointCounts(Arrays.asList("0", "1", "2", "gas", "drainage", "water", "3", "99", null), 3);
+    }
+
+    private void assertCrossEndpointCounts(List<String> types, int unknown) {
+        PipeNetworkBaseService pipes = mock(PipeNetworkBaseService.class);
+        List<PipeNetworkBase> source = new ArrayList<>();
+        for (int i = 0; i < types.size(); i++) {
+            PipeNetworkBase pipe = new PipeNetworkBase();
+            pipe.setNetworkCode("PIPE-" + i);
+            pipe.setNetworkName("管网" + i);
+            pipe.setNetworkType(types.get(i));
+            pipe.setLength(BigDecimal.valueOf(1000));
+            source.add(pipe);
+        }
+        when(pipes.list()).thenReturn(source);
+        IWaterPlantInfoService plants = mock(IWaterPlantInfoService.class);
+        IWaterPumpStationService stations = mock(IWaterPumpStationService.class);
+        EngineeringFacilityService engineering = mock(EngineeringFacilityService.class);
+        EquipmentTypeService equipmentTypes = mock(EquipmentTypeService.class);
+        EquipmentBaseService equipment = mock(EquipmentBaseService.class);
+
+        // 只替换表读取;类型解析、未知计数、行业归属与响应构造全部走真实代码。
+        JsonNode archives = JSON.valueToTree(new CockpitInfrastructureBaseArchivesService(
+                new BusinessCockpitInfrastructureBaseArchivesAdapter(plants, stations, pipes, engineering, CLOCK),
+                CLOCK).load("archives", null, null, null, null));
+        JsonNode management = JSON.valueToTree(new CockpitInfrastructureManagementInformationService(
+                new BusinessCockpitInfrastructureManagementInformationAdapter(equipment, equipmentTypes,
+                        mock(ManholeBaseService.class), pipes, mock(IWaterSourceInfoService.class)),
+                CLOCK).load("management", null, null));
+        JsonNode operating = JSON.valueToTree(new CockpitInfrastructureOperatingInformationService(
+                new BusinessCockpitInfrastructureOperatingInformationAdapter(plants, stations, pipes, engineering),
+                CLOCK, 60).load("operating", null, null, null, null));
+        JsonNode overview = JSON.valueToTree(new CockpitOverviewInfrastructureService(
+                new BusinessCockpitOverviewInfrastructureAdapter(equipmentTypes, equipment, pipes, plants, stations),
+                CLOCK).load("overview"));
+        JsonNode boundary = JSON.valueToTree(new CockpitDeviceBoundaryService(
+                new BusinessCockpitDeviceBoundaryAdapter(equipmentTypes, equipment, pipes), CLOCK).load("boundary"));
+
+        Map<String, Integer> expected = new HashMap<>();
+        expected.put("GAS", 2);
+        expected.put("WATER_SUPPLY", 2);
+        expected.put("DRAINAGE", 2);
+        expected.put("MANHOLE", 0);
+        Map<String, Integer> archiveCounts = zeroCounts();
+        for (JsonNode row : archives.path("industryDistribution")) {
+            archiveCounts.put(row.path("code").asText(), row.path("count").asInt());
+        }
+        assertEquals(expected, archiveCounts, "基础档案不能漏计 0/1 管网");
+        Map<String, Integer> managementCounts = zeroCounts();
+        for (JsonNode row : management.path("districtIndustry")) {
+            managementCounts.merge(row.path("industry").asText(), row.path("facilityCount").asInt(), Integer::sum);
+        }
+        assertEquals(expected, managementCounts, "管理信息与基础档案的管网行业计数必须一致");
+        Map<String, Integer> operatingCounts = zeroCounts();
+        for (JsonNode row : operating.path("industries")) {
+            int count = 0;
+            for (JsonNode metric : row.path("metrics")) {
+                if (metric.path("metricCode").asText().endsWith("_pipe_network_length")) {
+                    count++;
+                }
+            }
+            operatingCounts.put(row.path("industry").path("code").asText(), count);
+        }
+        assertEquals(expected, operatingCounts, "运行信息的管网指标条数必须与其它端点一致");
+        Map<String, Integer> overviewCounts = zeroCounts();
+        for (JsonNode row : overview.path("industries")) {
+            overviewCounts.put(row.path("code").asText(), row.path("facilityCount").asInt());
+        }
+        assertEquals(expected, overviewCounts, "总体概览不得把管网行业置空");
+        Map<String, Integer> boundaryCounts = zeroCounts();
+        for (JsonNode row : boundary.path("facilities")) {
+            if (row.path("formalScope").asBoolean()) {
+                boundaryCounts.merge(row.path("industry").asText(), 1, Integer::sum);
+            }
+        }
+        assertEquals(expected, boundaryCounts, "设备边界设施不能借用设备类型命名空间");
+
+        assertEquals(6, archives.path("totals").path("facilityCount").asInt());
+        assertEquals(6, management.path("totals").path("facilityCount").asInt());
+        assertEquals(6, overview.path("totals").path("facilityCount").asInt());
+        assertEquals(types.size(), boundary.path("statistics").path("facilityCount").asInt());
+        assertEquals(unknown, archives.path("excluded").path("unknownTypeCount").asInt());
+        assertEquals(unknown, management.path("excluded").path("unknownIndustryCount").asInt());
+        assertEquals(unknown, operating.path("excluded").path("unknownIndustryCount").asInt());
+        assertEquals(unknown, overview.path("excluded").path("unknownTypeCount").asInt());
+        assertEquals(unknown, boundary.path("statistics").path("unknownTypeCount").asInt());
+    }
+
+    private static Map<String, Integer> zeroCounts() {
+        Map<String, Integer> result = new HashMap<>();
+        for (String industry : Arrays.asList("GAS", "WATER_SUPPLY", "DRAINAGE", "MANHOLE")) {
+            result.put(industry, 0);
+        }
+        return result;
+    }
+}

+ 9 - 3
pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/infrastructure/BusinessCockpitInfrastructureBaseArchivesAdapter.java

@@ -47,9 +47,15 @@ public class BusinessCockpitInfrastructureBaseArchivesAdapter
     private static final List<CockpitInfrastructureBaseArchivesSnapshot.SourceType> TYPES = Arrays.asList(
             type("WATER_PLANT", "水厂", "WATER_SUPPLY"),
             type("WATER_PUMP_STATION", "供水泵站", "WATER_SUPPLY"),
-            type("WATER_PIPE", "供水管线", "WATER_SUPPLY"),
-            type("GAS_PIPE", "燃气管线", "GAS"),
-            type("DRAINAGE_PIPE", "排水管线", "DRAINAGE"),
+            type(CockpitPipeNetworkType.WATER_PIPE,
+                    CockpitPipeNetworkType.nameOf(CockpitPipeNetworkType.WATER_PIPE),
+                    CockpitPipeNetworkType.industryOfTypeCode(CockpitPipeNetworkType.WATER_PIPE)),
+            type(CockpitPipeNetworkType.GAS_PIPE,
+                    CockpitPipeNetworkType.nameOf(CockpitPipeNetworkType.GAS_PIPE),
+                    CockpitPipeNetworkType.industryOfTypeCode(CockpitPipeNetworkType.GAS_PIPE)),
+            type(CockpitPipeNetworkType.DRAINAGE_PIPE,
+                    CockpitPipeNetworkType.nameOf(CockpitPipeNetworkType.DRAINAGE_PIPE),
+                    CockpitPipeNetworkType.industryOfTypeCode(CockpitPipeNetworkType.DRAINAGE_PIPE)),
             type("DRAINAGE_GATE", "排水闸门", "DRAINAGE"),
             type("DRAINAGE_STORAGE", "排水调蓄设施", "DRAINAGE"),
             type("DRAINAGE_PUMP_STATION", "排水泵站", "DRAINAGE"));