Эх сурвалжийг харах

fix(cockpit): 单设备状态序改为「未转工单的报警 > 工单 > 预警」(票 71)

维护者 2026-10-22 裁定 Q1=A/Q2=A/Q3=A:非报警设备的未结束工单压在未解决预警之前;票 52 的「全部未处理报警被未结束工单承接 → WORK_ORDER」保留。枚举 priority 与点位聚合 min(priority) 不变(Q2);monitoring/situation.metricStatus 按事实判、不跟随(Q3,仅在方法注释留档)。新增 CockpitMonitoringPointStatusOrderTest 9/9 钉住判定序与两条边界。
lead 1 долоо хоног өмнө
parent
commit
a416f4ab44

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

@@ -0,0 +1,121 @@
+package com.zksy.web.controller.cockpit;
+
+import com.zksy.cockpit.point.CockpitMonitoringPointDeviceSnapshot;
+import com.zksy.cockpit.point.CockpitMonitoringPointStatus;
+import com.zksy.cockpit.point.CockpitMonitoringPointStatusResolver;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * 票 71 契约:单设备业务处置序 =「未转工单的报警 > 工单处置 > 预警」(维护者 2026-10-22 裁定 Q1=A)。
+ *
+ * <p>覆盖本次新增的二路(无报警时「有未结束工单」压过「有未解决预警」)、票 52 的窄三路
+ * (全部未处理报警被未结束工单承接 → WORK_ORDER),并钉住两条同日裁定:
+ * Q2(枚举 priority 与点位聚合语义不变)、Q3({@code monitoring/situation} 不跟随,另行在适配器注释留档)。
+ *
+ * <p>注:这些是判定逻辑的纯测试;真实后端复验(改序后 map-snapshot 读数)需后端部署后再做。
+ */
+class CockpitMonitoringPointStatusOrderTest {
+
+    @Test
+    void openWorkOrderOutranksOpenWarningWhenNoAlarm() {
+        assertEquals(CockpitMonitoringPointStatus.WORK_ORDER,
+                CockpitMonitoringPointStatusResolver.deviceStatus(device(true, false, true, true)),
+                "无报警:未结束工单压过未解决预警(本次新增)");
+    }
+
+    @Test
+    void openWarningStillWinsWhenNoOpenWorkOrder() {
+        assertEquals(CockpitMonitoringPointStatus.WARNING,
+                CockpitMonitoringPointStatusResolver.deviceStatus(device(true, false, true, false)),
+                "只剩预警时仍判预警,不拿已结案/已取消工单倒推");
+    }
+
+    @Test
+    void openWorkOrderWithoutWarningStaysWorkOrder() {
+        assertEquals(CockpitMonitoringPointStatus.WORK_ORDER,
+                CockpitMonitoringPointStatusResolver.deviceStatus(device(true, false, false, true)));
+    }
+
+    @Test
+    void alarmsCoveredByOpenWorkOrdersOutrankWarning() {
+        assertEquals(CockpitMonitoringPointStatus.WORK_ORDER,
+                CockpitMonitoringPointStatusResolver.deviceStatus(alarmDevice(true, true, true)),
+                "票 52 窄三路保留:报警全被未结束工单承接 + 预警仍在 → 工单处置");
+    }
+
+    @Test
+    void uncoveredAlarmOutranksWorkOrderAndWarning() {
+        assertEquals(CockpitMonitoringPointStatus.ALARM,
+                CockpitMonitoringPointStatusResolver.deviceStatus(alarmDevice(true, true, false)),
+                "存在未转工单的报警时报警最高(未结束工单/预警都不能压过它)");
+        assertEquals(CockpitMonitoringPointStatus.ALARM,
+                CockpitMonitoringPointStatusResolver.deviceStatus(alarmDevice(false, true, false)),
+                "报警标志为真但无工单承接(含「查不到未处理报警行」)→ 报警,不能渲染成处置中");
+    }
+
+    @Test
+    void normalAndOfflineKeepCurrentOrder() {
+        assertEquals(CockpitMonitoringPointStatus.NORMAL,
+                CockpitMonitoringPointStatusResolver.deviceStatus(device(true, false, false, false)));
+        assertEquals(CockpitMonitoringPointStatus.OFFLINE,
+                CockpitMonitoringPointStatusResolver.deviceStatus(device(false, false, false, false)));
+    }
+
+    /** Q2:枚举优先级一个字都没动(前端两张词表 / 图例排序 / t09 闸门因此零改动)。 */
+    @Test
+    void enumPriorityIsUnchanged() {
+        assertEquals(1, CockpitMonitoringPointStatus.ALARM.getPriority());
+        assertEquals(2, CockpitMonitoringPointStatus.WARNING.getPriority());
+        assertEquals(3, CockpitMonitoringPointStatus.WORK_ORDER.getPriority());
+        assertEquals(4, CockpitMonitoringPointStatus.NORMAL.getPriority());
+        assertEquals(5, CockpitMonitoringPointStatus.OFFLINE.getPriority());
+        assertEquals(6, CockpitMonitoringPointStatus.EMPTY.getPriority());
+    }
+
+    /** Q2:点位聚合仍取 min(priority),不被单设备业务序倒灌。 */
+    @Test
+    void pointAggregationStillUsesEnumPriority() {
+        CockpitMonitoringPointDeviceSnapshot warningOnly = device("D-W", true, false, true, false);
+        CockpitMonitoringPointDeviceSnapshot warningWithOpenWorkOrder = device("D-WO", true, false, true, true);
+
+        assertEquals(CockpitMonitoringPointStatus.WARNING,
+                CockpitMonitoringPointStatusResolver.resolve(
+                        Arrays.asList(warningOnly, warningWithOpenWorkOrder)),
+                "两台设备时聚合取 WARNING(2) < WORK_ORDER(3)");
+    }
+
+    /** 单设备点位的自动跟随:改序的直接后果,固定下来防回退。 */
+    @Test
+    void singleDevicePointFollowsDeviceOrder() {
+        assertEquals(CockpitMonitoringPointStatus.WORK_ORDER,
+                CockpitMonitoringPointStatusResolver.resolve(Collections.singletonList(
+                        device("D-WO", true, false, true, true))));
+    }
+
+    private static CockpitMonitoringPointDeviceSnapshot device(
+            boolean online, boolean alarm, boolean warning, boolean workOrderInProgress) {
+        return device("DEVICE-1", online, alarm, warning, workOrderInProgress);
+    }
+
+    private static CockpitMonitoringPointDeviceSnapshot device(String code, boolean online, boolean alarm,
+            boolean warning, boolean workOrderInProgress) {
+        return new CockpitMonitoringPointDeviceSnapshot(
+                code, code, "压力计",
+                online, alarm, warning, workOrderInProgress,
+                Collections.emptyList(), null);
+    }
+
+    /** 报警设备:报警标志为真,另带预警/工单与「全部未处理报警均被未结束工单承接」事实。 */
+    private static CockpitMonitoringPointDeviceSnapshot alarmDevice(
+            boolean warning, boolean workOrderInProgress, boolean alarmsCoveredByOpenWorkOrders) {
+        return new CockpitMonitoringPointDeviceSnapshot(
+                "DEVICE-1", "DEVICE-1", "压力计", null, null, null,
+                true, true, warning, workOrderInProgress,
+                Collections.emptyList(), null, true, alarmsCoveredByOpenWorkOrders);
+    }
+}

+ 5 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/monitoring/BusinessCockpitMonitoringSituationAdapter.java

@@ -177,6 +177,11 @@ public class BusinessCockpitMonitoringSituationAdapter implements CockpitMonitor
     /**
      * 指标态:缺状态记录 → UNKNOWN(不冒充 OFFLINE);离线 → OFFLINE;报警 → ALARM;预警中 → WARNING;其余 → NORMAL。
      * 现网无样本新鲜度依据,故不产生 EXPIRED。
+     *
+     * <p><b>刻意不跟随单设备业务序</b>(票 71 / 维护者 2026-10-22 裁定 Q3=A):本方法按设备事实判,
+     * 预警中即 WARNING、不因「有未结束工单」变成 WORK_ORDER,且没有 {@code WORK_ORDER} 桶。
+     * 所以同一台「预警未解决 + 工单未结束」的设备会出现「一张图/设备卡 = 工单处置、
+     * 运行监测异常清单 = 预警」——那是裁定结果,不是缺陷;「异常点」可见范围也因此不变。
      */
     private String metricStatus(String deviceCode, CockpitMonitoringPointDeviceSnapshot fact) {
         EquipmentStatus status = null;

+ 17 - 9
pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/point/CockpitMonitoringPointStatusResolver.java

@@ -62,14 +62,21 @@ public final class CockpitMonitoringPointStatusResolver {
     }
 
     /**
-     * 单设备业务状态(维护者 2026-10-17 裁定)。
+     * 单设备业务状态(维护者 2026-10-22 裁定:未转工单的报警 &gt; 工单处置 &gt; 预警)。
      *
-     * <p>报警**且**该设备全部未处理报警都已由未结束工单承接时 → {@code WORK_ORDER}
-     * (处置在进行,优先于报警/预警);否则维持现行序
-     * {@code alarm → warning → workOrderInProgress → online ? NORMAL : OFFLINE}。
+     * <p>判定序(**业务处置序**,不是枚举优先级序):
+     * <ol>
+     *   <li>报警标志为真:该设备全部未处理报警都已由未结束工单承接 → {@code WORK_ORDER}
+     *       (票 52 / 2026-10-17 裁定,保留);否则 {@code ALARM} —— 「查不到承接」不等于「已承接」;</li>
+     *   <li>否则存在未结束工单 → {@code WORK_ORDER}(票 71 / 2026-10-22 新增:压在预警之前);</li>
+     *   <li>否则存在未解决预警 → {@code WARNING};</li>
+     *   <li>否则 {@code online ? NORMAL : OFFLINE}。</li>
+     * </ol>
      *
-     * <p>该裁定只作用于**单设备**判定;点位聚合 {@code resolve()} 仍取
-     * {@code min(priority)},故枚举优先级与前端词表不变。
+     * <p>边界(2026-10-22 同日裁定):枚举 {@code priority} 与点位聚合 {@code min(priority)}
+     * <b>不变</b>(Q2);{@code monitoring/situation} 的 {@code metricStatus()} 按设备事实判、
+     * <b>不跟随</b>本序(Q3)—— 同一设备可能「地图 = 工单处置、运行监测异常清单 = 预警」,
+     * 那是裁定结果,不是缺陷。
      */
     public static CockpitMonitoringPointStatus deviceStatus(CockpitMonitoringPointDeviceSnapshot device) {
         if (device.isAlarm()) {
@@ -77,12 +84,13 @@ public final class CockpitMonitoringPointStatusResolver {
                     ? CockpitMonitoringPointStatus.WORK_ORDER
                     : CockpitMonitoringPointStatus.ALARM;
         }
-        if (device.isWarning()) {
-            return CockpitMonitoringPointStatus.WARNING;
-        }
+        // 「未转工单的报警」已在上面拦截;剩下的「有未结束工单」压过「有未解决预警」。
         if (device.isWorkOrderInProgress()) {
             return CockpitMonitoringPointStatus.WORK_ORDER;
         }
+        if (device.isWarning()) {
+            return CockpitMonitoringPointStatus.WARNING;
+        }
         return device.isOnline() ? CockpitMonitoringPointStatus.NORMAL : CockpitMonitoringPointStatus.OFFLINE;
     }