|
@@ -12,8 +12,11 @@ import java.time.Instant;
|
|
|
import java.time.ZoneOffset;
|
|
import java.time.ZoneOffset;
|
|
|
import java.util.Arrays;
|
|
import java.util.Arrays;
|
|
|
import java.util.Collections;
|
|
import java.util.Collections;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
import static org.mockito.Mockito.mock;
|
|
import static org.mockito.Mockito.mock;
|
|
|
import static org.mockito.Mockito.when;
|
|
import static org.mockito.Mockito.when;
|
|
@@ -31,69 +34,146 @@ class CockpitOverviewWarningSettingsServiceTest {
|
|
|
Clock.fixed(Instant.parse("2026-09-02T02:00:00Z"), ZoneOffset.UTC));
|
|
Clock.fixed(Instant.parse("2026-09-02T02:00:00Z"), ZoneOffset.UTC));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 维护者 2026-10-14 裁定:预警不走工单流程 → 预警族的工单口径字段**必须为 null**(不适用),
|
|
|
|
|
+ * 而不是 0 —— 0 会被读成"真的是零"。
|
|
|
|
|
+ */
|
|
|
@Test
|
|
@Test
|
|
|
- void currentEightStateModelSeparatesHandledPendingAndUnlinkedEvents() {
|
|
|
|
|
- when(adapter.load()).thenReturn(eightStateFixture());
|
|
|
|
|
|
|
+ void warningFamilyDropsWorkOrderVocabularyEntirely() {
|
|
|
|
|
+ when(adapter.load()).thenReturn(warningFixture());
|
|
|
|
|
|
|
|
CockpitOverviewWarningSettingsResponse response =
|
|
CockpitOverviewWarningSettingsResponse response =
|
|
|
- service.load("request-eight", "WARNING", null, null, null);
|
|
|
|
|
-
|
|
|
|
|
- // 事件计数仍按 family 各自统计。
|
|
|
|
|
- assertEquals(Integer.valueOf(10), response.getWarning().getTotal());
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getHandled());
|
|
|
|
|
- assertEquals(Integer.valueOf(6), response.getWarning().getPending());
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getCancelledCount());
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getUnlinkedEventCount());
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getInvalidWorkOrderStateCount());
|
|
|
|
|
- // 工单状态分布与处置率改为工单口径(决策票 06):逐态 1 张,共 8 张。
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getWorkOrderStates().get(0));
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getWorkOrderStates().get(1));
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getWorkOrderStates().get(2));
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getWorkOrderStates().get(3));
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getWorkOrderStates().get(4));
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getWorkOrderStates().get(5));
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getWorkOrderStates().get(6));
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getWorkOrderStates().get(7));
|
|
|
|
|
- assertEquals(8, response.getWarning().getWorkOrderStates().size());
|
|
|
|
|
- assertTrue(response.getWarning().getWorkOrderStates().keySet().stream()
|
|
|
|
|
- .allMatch(code -> code >= 0 && code <= 7));
|
|
|
|
|
- // 分母=状态 0–6(7 张,状态 7 排除),分子=状态 5(1 张)→ 1/7。
|
|
|
|
|
- assertEquals(Integer.valueOf(1), response.getWarning().getHandlingEfficiency().getNumerator());
|
|
|
|
|
- assertEquals(Integer.valueOf(7), response.getWarning().getHandlingEfficiency().getDenominator());
|
|
|
|
|
- assertEquals("0.1429", response.getWarning().getHandlingEfficiency().getValue().toPlainString());
|
|
|
|
|
|
|
+ service.load("request-warning", "WARNING", null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse.FamilyMetrics warning = response.getWarning();
|
|
|
|
|
+ assertNull(warning.getHandled());
|
|
|
|
|
+ assertNull(warning.getPending());
|
|
|
|
|
+ assertNull(warning.getCancelledCount());
|
|
|
|
|
+ assertNull(warning.getUnlinkedEventCount());
|
|
|
|
|
+ assertNull(warning.getInvalidWorkOrderStateCount());
|
|
|
|
|
+ assertNull(warning.getWorkOrderStates());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** 预警族状态分布=预警自身生命周期(六态),逐桶计数自洽(合计=总数)。 */
|
|
|
@Test
|
|
@Test
|
|
|
- void bothFamiliesShareTheSameWorkOrderScopedRate() {
|
|
|
|
|
- when(adapter.load()).thenReturn(eightStateFixture());
|
|
|
|
|
|
|
+ void warningStatusDistributionCoversItsOwnLifecycleAndSumsToTotal() {
|
|
|
|
|
+ when(adapter.load()).thenReturn(warningFixture());
|
|
|
|
|
|
|
|
CockpitOverviewWarningSettingsResponse response =
|
|
CockpitOverviewWarningSettingsResponse response =
|
|
|
- service.load("request-shared", "WARNING", null, null, null);
|
|
|
|
|
|
|
+ service.load("request-warning", "WARNING", null, null, null);
|
|
|
|
|
|
|
|
- // 分母不要求事件回联 → 两 family 共用同一份全范围工单读数。
|
|
|
|
|
- assertEquals(response.getWarning().getHandlingEfficiency().getDenominator(),
|
|
|
|
|
- response.getAlarm().getHandlingEfficiency().getDenominator());
|
|
|
|
|
- assertEquals(response.getWarning().getHandlingEfficiency().getNumerator(),
|
|
|
|
|
- response.getAlarm().getHandlingEfficiency().getNumerator());
|
|
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse.FamilyMetrics warning = response.getWarning();
|
|
|
|
|
+ assertEquals(6, warning.getTotal());
|
|
|
|
|
+ assertEquals(1, count(warning.getStatusDistribution(), "DRAFT"));
|
|
|
|
|
+ assertEquals(1, count(warning.getStatusDistribution(), "PENDING"));
|
|
|
|
|
+ assertEquals(1, count(warning.getStatusDistribution(), "RECEIVED"));
|
|
|
|
|
+ assertEquals(1, count(warning.getStatusDistribution(), "PROCESSING"));
|
|
|
|
|
+ assertEquals(1, count(warning.getStatusDistribution(), "HANDLED"));
|
|
|
|
|
+ assertEquals(1, count(warning.getStatusDistribution(), "CLOSED"));
|
|
|
|
|
+ assertEquals(warning.getTotal(), sum(warning.getStatusDistribution()));
|
|
|
|
|
+ // 六态全铺(缺的态是真实 0,不是"没有数据")。
|
|
|
|
|
+ assertEquals(6, warning.getStatusDistribution().size());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** 预警自身生命周期「已解除率」=已解除 ÷ 总数(1/6),与工单口径无关。 */
|
|
|
@Test
|
|
@Test
|
|
|
- void cancelledWorkOrdersAreExcludedFromTheRateDenominator() {
|
|
|
|
|
- when(adapter.load()).thenReturn(cancelledHeavyFixture());
|
|
|
|
|
|
|
+ void warningEfficiencyIsItsOwnClosedOverTotalNotAWorkOrderRate() {
|
|
|
|
|
+ when(adapter.load()).thenReturn(warningFixture());
|
|
|
|
|
|
|
|
CockpitOverviewWarningSettingsResponse response =
|
|
CockpitOverviewWarningSettingsResponse response =
|
|
|
- service.load("request-cancelled", "WARNING", null, null, null);
|
|
|
|
|
|
|
+ service.load("request-warning", "WARNING", null, null, null);
|
|
|
|
|
|
|
|
- // 只有状态 7(2 张)→ 分母 0,不显示 0%,而是不适用。
|
|
|
|
|
- assertEquals(Integer.valueOf(0), response.getWarning().getHandlingEfficiency().getDenominator());
|
|
|
|
|
- assertEquals(Integer.valueOf(0), response.getWarning().getHandlingEfficiency().getNumerator());
|
|
|
|
|
- assertEquals("EMPTY", response.getWarning().getHandlingEfficiency().getState());
|
|
|
|
|
- assertEquals("NO_ELIGIBLE_WORK_ORDERS",
|
|
|
|
|
- response.getWarning().getHandlingEfficiency().getReason());
|
|
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse.Efficiency efficiency =
|
|
|
|
|
+ response.getWarning().getHandlingEfficiency();
|
|
|
|
|
+ assertEquals(1, efficiency.getNumerator());
|
|
|
|
|
+ assertEquals(6, efficiency.getDenominator());
|
|
|
|
|
+ assertEquals("0.1667", efficiency.getValue().toPlainString());
|
|
|
|
|
+ assertEquals("AVAILABLE", efficiency.getState());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 等级 / 行业 / 片区分布都逐桶自洽,且未分区是显式桶。 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void warningLevelIndustryAndDistrictDistributionsAreSelfConsistent() {
|
|
|
|
|
+ when(adapter.load()).thenReturn(warningFixture());
|
|
|
|
|
+
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse response =
|
|
|
|
|
+ service.load("request-warning", "WARNING", null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse.FamilyMetrics warning = response.getWarning();
|
|
|
|
|
+ // 1–4 全铺 + 一条等级缺失进未知桶。
|
|
|
|
|
+ assertEquals(5, warning.getLevelDistribution().size());
|
|
|
|
|
+ assertEquals(2, count(warning.getLevelDistribution(), "2"));
|
|
|
|
|
+ assertEquals(3, count(warning.getLevelDistribution(), "3"));
|
|
|
|
|
+ assertEquals(1, count(warning.getLevelDistribution(), "UNKNOWN"));
|
|
|
|
|
+ assertEquals(warning.getTotal(), sum(warning.getLevelDistribution()));
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(2, count(warning.getIndustryDistribution(), "GAS"));
|
|
|
|
|
+ assertEquals(3, count(warning.getIndustryDistribution(), "WATER_SUPPLY"));
|
|
|
|
|
+ assertEquals(1, count(warning.getIndustryDistribution(), "UNKNOWN"));
|
|
|
|
|
+ assertEquals(warning.getTotal(), sum(warning.getIndustryDistribution()));
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(2, count(warning.getDistrictDistribution(), "0"));
|
|
|
|
|
+ assertEquals(3, count(warning.getDistrictDistribution(), "1"));
|
|
|
|
|
+ // 未分区恒有显式桶(设备缺失是合法结果,不是缺数据)。
|
|
|
|
|
+ assertEquals(1, count(warning.getDistrictDistribution(), "UNASSIGNED"));
|
|
|
|
|
+ assertEquals(warning.getTotal(), sum(warning.getDistrictDistribution()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 报警族保留工单口径(维护者裁定:报警靠工单闭环),并并存报警自身生命周期。 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void alarmFamilyKeepsWorkOrderVocabularyAndAddsItsOwnLifecycle() {
|
|
|
|
|
+ when(adapter.load()).thenReturn(alarmFixture());
|
|
|
|
|
+
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse response =
|
|
|
|
|
+ service.load("request-alarm", "ALARM", null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse.FamilyMetrics alarm = response.getAlarm();
|
|
|
|
|
+ // 工单口径仍在:分母=状态 0–6 工单数(7 张,排除状态 7),分子=状态 5(1 张)。
|
|
|
|
|
+ assertEquals(1, alarm.getHandlingEfficiency().getNumerator());
|
|
|
|
|
+ assertEquals(7, alarm.getHandlingEfficiency().getDenominator());
|
|
|
|
|
+ assertEquals("0.1429", alarm.getHandlingEfficiency().getValue().toPlainString());
|
|
|
|
|
+ assertEquals(8, alarm.getWorkOrderStates().size());
|
|
|
|
|
+ assertEquals(1, alarm.getWorkOrderStates().get(5));
|
|
|
|
|
+ // 报警自身生命周期:alarm_status 0/1。
|
|
|
|
|
+ assertEquals(1, count(alarm.getStatusDistribution(), "HANDLED"));
|
|
|
|
|
+ assertEquals(2, count(alarm.getStatusDistribution(), "UNHANDLED"));
|
|
|
|
|
+ assertEquals(alarm.getTotal(), sum(alarm.getStatusDistribution()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 报警族工单口径分列:未回联 / 脏码 / 已取消 各自如实计数,不互相冒充。 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void alarmWorkOrderBucketsSeparateUnlinkedInvalidAndCancelled() {
|
|
|
|
|
+ when(adapter.load()).thenReturn(alarmFixture());
|
|
|
|
|
+
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse response =
|
|
|
|
|
+ service.load("request-alarm", "ALARM", null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse.FamilyMetrics alarm = response.getAlarm();
|
|
|
|
|
+ assertEquals(1, alarm.getUnlinkedEventCount());
|
|
|
|
|
+ assertEquals(1, alarm.getInvalidWorkOrderStateCount());
|
|
|
|
|
+ assertEquals(1, alarm.getCancelledCount());
|
|
|
|
|
+ assertEquals(1, alarm.getPending());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** 工单表整体不可用时,预警族仍如实出数(这是本票要消除的故障面)。 */
|
|
|
@Test
|
|
@Test
|
|
|
- void emptyFamilyUsesExplicitEmptyStateAndZeroDenominatorEfficiency() {
|
|
|
|
|
|
|
+ void warningFamilyStaysAvailableWhenNoWorkOrderFactsExistAtAll() {
|
|
|
|
|
+ when(adapter.load()).thenReturn(warningFixtureWithoutWorkOrders());
|
|
|
|
|
+
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse response =
|
|
|
|
|
+ service.load("request-warning", "WARNING", null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(2, response.getWarning().getTotal());
|
|
|
|
|
+ assertEquals("READY", response.getWarning().getState());
|
|
|
|
|
+ assertEquals(1, count(response.getWarning().getStatusDistribution(), "CLOSED"));
|
|
|
|
|
+ // 报警族没有工单事实 → 处置率显式「不适用」,不是 0%。
|
|
|
|
|
+ assertEquals("EMPTY", response.getAlarm().getHandlingEfficiency().getState());
|
|
|
|
|
+ assertEquals("NO_ELIGIBLE_WORK_ORDERS", response.getAlarm().getHandlingEfficiency().getReason());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 空态:两族都显式 EMPTY,预警族处置率不适用。 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void emptyFamilyUsesExplicitEmptyState() {
|
|
|
when(adapter.load()).thenReturn(emptyFixture());
|
|
when(adapter.load()).thenReturn(emptyFixture());
|
|
|
|
|
|
|
|
CockpitOverviewWarningSettingsResponse response =
|
|
CockpitOverviewWarningSettingsResponse response =
|
|
@@ -101,14 +181,13 @@ class CockpitOverviewWarningSettingsServiceTest {
|
|
|
|
|
|
|
|
assertEquals("EMPTY", response.getAlarm().getState());
|
|
assertEquals("EMPTY", response.getAlarm().getState());
|
|
|
assertEquals("NO_EVENTS", response.getAlarm().getStateReason());
|
|
assertEquals("NO_EVENTS", response.getAlarm().getStateReason());
|
|
|
- assertEquals(Integer.valueOf(0), response.getAlarm().getTotal());
|
|
|
|
|
- assertEquals(Integer.valueOf(0), response.getAlarm().getHandled());
|
|
|
|
|
- assertEquals(Integer.valueOf(0), response.getAlarm().getPending());
|
|
|
|
|
- assertEquals("0.0000", response.getAlarm().getHandlingEfficiency().getValue().toPlainString());
|
|
|
|
|
- assertEquals(Integer.valueOf(0), response.getAlarm().getHandlingEfficiency().getNumerator());
|
|
|
|
|
- assertEquals(Integer.valueOf(0), response.getAlarm().getHandlingEfficiency().getDenominator());
|
|
|
|
|
- assertEquals("EMPTY", response.getAlarm().getHandlingEfficiency().getState());
|
|
|
|
|
- assertEquals("NO_ELIGIBLE_WORK_ORDERS", response.getAlarm().getHandlingEfficiency().getReason());
|
|
|
|
|
|
|
+ assertEquals(0, response.getAlarm().getTotal());
|
|
|
|
|
+ assertEquals("EMPTY", response.getWarning().getState());
|
|
|
|
|
+ assertEquals("EMPTY", response.getWarning().getHandlingEfficiency().getState());
|
|
|
|
|
+ assertEquals("NO_WARNINGS", response.getWarning().getHandlingEfficiency().getReason());
|
|
|
|
|
+ // 空态下分布仍全铺已知桶,只是计数为 0。
|
|
|
|
|
+ assertEquals(6, response.getWarning().getStatusDistribution().size());
|
|
|
|
|
+ assertEquals(0, sum(response.getWarning().getStatusDistribution()));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
@Test
|
|
@@ -120,52 +199,101 @@ class CockpitOverviewWarningSettingsServiceTest {
|
|
|
|
|
|
|
|
assertEquals("EMPTY", response.getFreshness().getStatus());
|
|
assertEquals("EMPTY", response.getFreshness().getStatus());
|
|
|
assertEquals("NO_SNAPSHOT", response.getFreshness().getReason());
|
|
assertEquals("NO_SNAPSHOT", response.getFreshness().getReason());
|
|
|
- assertTrue(response.getUpdatedAt() == null);
|
|
|
|
|
|
|
+ assertNull(response.getUpdatedAt());
|
|
|
assertEquals("EMPTY", response.getWarning().getState());
|
|
assertEquals("EMPTY", response.getWarning().getState());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static CockpitOverviewWarningSettingsSnapshot eightStateFixture() {
|
|
|
|
|
|
|
+ /** 趋势窗口跟随 `range`:窗口外桶被裁掉,窗口内保留。 */
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void trendWindowFollowsTheDeclaredRange() {
|
|
|
|
|
+ when(adapter.load()).thenReturn(warningFixture());
|
|
|
|
|
+
|
|
|
|
|
+ CockpitOverviewWarningSettingsResponse response =
|
|
|
|
|
+ service.load("request-range", "WARNING", "LAST_7_DAYS", null, null);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals("LAST_7_DAYS", response.getRange().getType());
|
|
|
|
|
+ List<String> buckets = response.getWarning().getTrend().stream()
|
|
|
|
|
+ .map(CockpitOverviewWarningSettingsResponse.TrendPoint::getBucket)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ // 固定时钟 2026-09-02T10:00+08:00 起算近 7 日 → 只保留 2026-09-01。
|
|
|
|
|
+ assertTrue(buckets.contains("2026-09-01"));
|
|
|
|
|
+ assertTrue(!buckets.contains("2026-08-01"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static int count(List<CockpitOverviewWarningSettingsResponse.Bucket> buckets, String code) {
|
|
|
|
|
+ return buckets.stream()
|
|
|
|
|
+ .filter(bucket -> code.equals(bucket.getCode()))
|
|
|
|
|
+ .mapToInt(CockpitOverviewWarningSettingsResponse.Bucket::getCount)
|
|
|
|
|
+ .findFirst().orElse(-1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static int sum(List<CockpitOverviewWarningSettingsResponse.Bucket> buckets) {
|
|
|
|
|
+ return buckets.stream().mapToInt(CockpitOverviewWarningSettingsResponse.Bucket::getCount).sum();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 预警族六态各一条(CLOSED 两条)+ 等级/行业/片区维度齐全。 */
|
|
|
|
|
+ private static CockpitOverviewWarningSettingsSnapshot warningFixture() {
|
|
|
return new CockpitOverviewWarningSettingsSnapshot(
|
|
return new CockpitOverviewWarningSettingsSnapshot(
|
|
|
"2026-09-02T09:59:50+08:00",
|
|
"2026-09-02T09:59:50+08:00",
|
|
|
Arrays.asList(
|
|
Arrays.asList(
|
|
|
- event("WARNING", "WRN-000", "WO-000", 0),
|
|
|
|
|
- event("WARNING", "WRN-001", "WO-001", 1),
|
|
|
|
|
- event("WARNING", "WRN-002", "WO-002", 2),
|
|
|
|
|
- event("WARNING", "WRN-003", "WO-003", 3),
|
|
|
|
|
- event("WARNING", "WRN-004", "WO-004", 4),
|
|
|
|
|
- event("WARNING", "WRN-005", "WO-005", 5),
|
|
|
|
|
- event("WARNING", "WRN-006", "WO-006", 6),
|
|
|
|
|
- event("WARNING", "WRN-007", "WO-007", 7),
|
|
|
|
|
- new CockpitOverviewWarningSettingsSnapshot.SourceEvent(
|
|
|
|
|
- "WARNING", "WRN-008", "DEV-008", null, null),
|
|
|
|
|
- event("WARNING", "WRN-009", "WO-009", 9),
|
|
|
|
|
- event("ALARM", "ALM-001", "WO-101", 3),
|
|
|
|
|
- event("ALARM", "ALM-002", "WO-102", 5)),
|
|
|
|
|
- Collections.emptyList(),
|
|
|
|
|
- // 全范围工单:状态 0–7 各一张 → 分母 7(排除状态 7)、分子 1(状态 5)。
|
|
|
|
|
|
|
+ warning("W-DRAFT", "DRAFT", 2, "GAS", 0),
|
|
|
|
|
+ warning("W-PENDING", "PENDING", 3, "WATER_SUPPLY", 1),
|
|
|
|
|
+ warning("W-RECEIVED", "RECEIVED", 3, "WATER_SUPPLY", 1),
|
|
|
|
|
+ warning("W-PROCESSING", "PROCESSING", null, null, null),
|
|
|
|
|
+ warning("W-HANDLED", "HANDLED", 3, "WATER_SUPPLY", 1),
|
|
|
|
|
+ warning("W-CLOSED", "CLOSED", 2, "GAS", 0)),
|
|
|
Arrays.asList(
|
|
Arrays.asList(
|
|
|
- order(0), order(1), order(2), order(3),
|
|
|
|
|
- order(4), order(5), order(6), order(7)));
|
|
|
|
|
|
|
+ new CockpitOverviewWarningSettingsSnapshot.SourceTrendPoint(
|
|
|
|
|
+ "WARNING", "2026-09-01", 2, 1),
|
|
|
|
|
+ new CockpitOverviewWarningSettingsSnapshot.SourceTrendPoint(
|
|
|
|
|
+ "WARNING", "2026-08-01", 1, 0)),
|
|
|
|
|
+ Collections.emptyList());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** 只有已取消工单 → 分母 0,用于验证「不适用」而非 0%。 */
|
|
|
|
|
- private static CockpitOverviewWarningSettingsSnapshot cancelledHeavyFixture() {
|
|
|
|
|
|
|
+ /** 预警族有数据、工单事实为空:验证预警不再依赖工单。 */
|
|
|
|
|
+ private static CockpitOverviewWarningSettingsSnapshot warningFixtureWithoutWorkOrders() {
|
|
|
return new CockpitOverviewWarningSettingsSnapshot(
|
|
return new CockpitOverviewWarningSettingsSnapshot(
|
|
|
"2026-09-02T09:59:50+08:00",
|
|
"2026-09-02T09:59:50+08:00",
|
|
|
- Collections.singletonList(event("WARNING", "WRN-007", "WO-007", 7)),
|
|
|
|
|
|
|
+ Arrays.asList(
|
|
|
|
|
+ warning("W-1", "CLOSED", 2, "GAS", 0),
|
|
|
|
|
+ warning("W-2", "PROCESSING", 3, "GAS", 0)),
|
|
|
Collections.emptyList(),
|
|
Collections.emptyList(),
|
|
|
- Arrays.asList(order(7), order(7)));
|
|
|
|
|
|
|
+ Collections.emptyList());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static CockpitOverviewWarningSettingsSnapshot.SourceWorkOrder order(Integer state) {
|
|
|
|
|
- return new CockpitOverviewWarningSettingsSnapshot.SourceWorkOrder(
|
|
|
|
|
- state, "2026-09-02T09:00:00+08:00");
|
|
|
|
|
|
|
+ /** 报警族:工单口径(状态 0–7 各一张)+ 自身生命周期(1 已处理 / 2 未处理 / 1 未回联 / 1 脏码)。 */
|
|
|
|
|
+ private static CockpitOverviewWarningSettingsSnapshot alarmFixture() {
|
|
|
|
|
+ return new CockpitOverviewWarningSettingsSnapshot(
|
|
|
|
|
+ "2026-09-02T09:59:50+08:00",
|
|
|
|
|
+ Arrays.asList(
|
|
|
|
|
+ alarm("A-1", "HANDLED", 5, 3, "GAS", 0),
|
|
|
|
|
+ alarm("A-2", "UNHANDLED", 3, 3, "WATER_SUPPLY", 1),
|
|
|
|
|
+ alarm("A-3", "UNHANDLED", 7, 2, "GAS", 0),
|
|
|
|
|
+ alarm("A-4", null, null, 2, "GAS", 0),
|
|
|
|
|
+ alarm("A-5", null, 9, 2, "GAS", 0)),
|
|
|
|
|
+ Collections.emptyList(),
|
|
|
|
|
+ Arrays.asList(
|
|
|
|
|
+ order(0), order(1), order(2), order(3),
|
|
|
|
|
+ order(4), order(5), order(6), order(7)));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static CockpitOverviewWarningSettingsSnapshot.SourceEvent event(
|
|
|
|
|
- String family, String eventCode, String workOrderCode, Integer state) {
|
|
|
|
|
|
|
+ private static CockpitOverviewWarningSettingsSnapshot.SourceEvent warning(
|
|
|
|
|
+ String code, String status, Integer level, String industry, Integer district) {
|
|
|
return new CockpitOverviewWarningSettingsSnapshot.SourceEvent(
|
|
return new CockpitOverviewWarningSettingsSnapshot.SourceEvent(
|
|
|
- family, eventCode, "DEV-" + eventCode, workOrderCode, state);
|
|
|
|
|
|
|
+ "WARNING", code, "DEV-" + code, null, null, status, level, district, industry);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static CockpitOverviewWarningSettingsSnapshot.SourceEvent alarm(
|
|
|
|
|
+ String code, String status, Integer workOrderState, Integer level,
|
|
|
|
|
+ String industry, Integer district) {
|
|
|
|
|
+ return new CockpitOverviewWarningSettingsSnapshot.SourceEvent(
|
|
|
|
|
+ "ALARM", code, "DEV-" + code, "WO-" + code, workOrderState,
|
|
|
|
|
+ status, level, district, industry);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static CockpitOverviewWarningSettingsSnapshot.SourceWorkOrder order(Integer state) {
|
|
|
|
|
+ return new CockpitOverviewWarningSettingsSnapshot.SourceWorkOrder(
|
|
|
|
|
+ state, "2026-09-02T09:00:00+08:00");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static CockpitOverviewWarningSettingsSnapshot emptyFixture() {
|
|
private static CockpitOverviewWarningSettingsSnapshot emptyFixture() {
|