|
|
@@ -8,6 +8,7 @@ import com.zksy.cockpit.monitoring.CockpitMonitoringDistributionService;
|
|
|
import com.zksy.cockpit.monitoring.CockpitMonitoringDistributionSnapshot;
|
|
|
import com.zksy.cockpit.monitoring.CockpitMonitoringDistributionResponse;
|
|
|
import com.zksy.cockpit.point.CockpitMonitoringPointDeviceSnapshot;
|
|
|
+import com.zksy.cockpit.point.CockpitPointStatusQuery;
|
|
|
import com.zksy.cockpit.point.CockpitMonitoringPointStatus;
|
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
@@ -107,12 +108,18 @@ class CockpitMonitoringDistributionControllerTest {
|
|
|
.andExpect(jsonPath("$.data.pointStatusDistribution[1].count").value(1))
|
|
|
.andExpect(jsonPath("$.data.pointStatusDistribution[2].status").value("WORK_ORDER"))
|
|
|
.andExpect(jsonPath("$.data.pointStatusDistribution[2].count").value(1))
|
|
|
+ // 票 81:八桶与三份分布同一 scope,被 classify() 排除的 P-MISMATCH / P-UNKNOWN
|
|
|
+ // (原本各计 1 个 NORMAL)不再进状态桶 —— 它们已由 excluded 三个计数器如实报数。
|
|
|
+ // 有效 5 点位是 WORK_ORDER/EMPTY/ALARM/OFFLINE/WARNING,故 NORMAL 回到 0。
|
|
|
.andExpect(jsonPath("$.data.pointStatusDistribution[3].status").value("NORMAL"))
|
|
|
- .andExpect(jsonPath("$.data.pointStatusDistribution[3].count").value(2))
|
|
|
+ .andExpect(jsonPath("$.data.pointStatusDistribution[3].count").value(0))
|
|
|
.andExpect(jsonPath("$.data.pointStatusDistribution[4].status").value("OFFLINE"))
|
|
|
.andExpect(jsonPath("$.data.pointStatusDistribution[4].count").value(1))
|
|
|
.andExpect(jsonPath("$.data.pointStatusDistribution[5].status").value("EMPTY"))
|
|
|
.andExpect(jsonPath("$.data.pointStatusDistribution[5].count").value(1))
|
|
|
+ // 设备侧同口径:EQ-FREE-004(智能井盖、无行业)被排除,UNASSOCIATED 只数剩下 4 台。
|
|
|
+ .andExpect(jsonPath("$.data.pointStatusDistribution[7].status").value("UNASSOCIATED"))
|
|
|
+ .andExpect(jsonPath("$.data.pointStatusDistribution[7].count").value(4))
|
|
|
.andExpect(jsonPath("$.data.districtDistribution", hasSize(3)))
|
|
|
.andExpect(jsonPath("$.data.districtDistribution[0].district").value(0))
|
|
|
.andExpect(jsonPath("$.data.districtDistribution[0].districtName").value("城南"))
|
|
|
@@ -175,9 +182,13 @@ class CockpitMonitoringDistributionControllerTest {
|
|
|
.andExpect(jsonPath("$.data.mapFilter.industry").value("GAS"))
|
|
|
.andExpect(jsonPath("$.data.industryDistribution", hasSize(1)))
|
|
|
.andExpect(jsonPath("$.data.industryDistribution[0].industry").value("GAS"))
|
|
|
+ // 票 81 前这条断言钉的是「全量」:GAS 范围内根本没有 NORMAL 点位,
|
|
|
+ // 却因为 global 在过滤前累计而恒读 2(旧值来自被排除的两个无类型/错种类点)。
|
|
|
.andExpect(jsonPath("$.data.pointStatusDistribution[0].count").value(1))
|
|
|
- .andExpect(jsonPath("$.data.pointStatusDistribution[3].count").value(2))
|
|
|
- .andExpect(jsonPath("$.data.pointStatusDistribution[4].count").value(1));
|
|
|
+ .andExpect(jsonPath("$.data.pointStatusDistribution[3].count").value(0))
|
|
|
+ .andExpect(jsonPath("$.data.pointStatusDistribution[4].count").value(1))
|
|
|
+ // GAS 范围内未关联设备只剩 EQ-FREE-001 / EQ-FREE-005 两台。
|
|
|
+ .andExpect(jsonPath("$.data.pointStatusDistribution[7].count").value(2));
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
@@ -255,7 +266,9 @@ class CockpitMonitoringDistributionControllerTest {
|
|
|
.andExpect(status().isOk())
|
|
|
.andExpect(jsonPath("$.data.pagination.total").value(0))
|
|
|
.andExpect(jsonPath("$.data.monitoringPoints", hasSize(0)))
|
|
|
- .andExpect(jsonPath("$.data.pointStatusDistribution[0].count").value(1))
|
|
|
+ // 票 81:分布随设备类型收窄,此 scope 空 ⇒ 八桶必须全 0;
|
|
|
+ // 旧断言读 1 是 global 在过滤前累计留下的全量残值。
|
|
|
+ .andExpect(jsonPath("$.data.pointStatusDistribution[0].count").value(0))
|
|
|
.andExpect(jsonPath("$.data.mapFilter.deviceType").value("NOT_A_TYPE"))
|
|
|
.andExpect(jsonPath("$.data.excluded.invalidCoordinateCount").value(0))
|
|
|
.andExpect(jsonPath("$.data.freshness.status").value("EMPTY"))
|
|
|
@@ -379,7 +392,9 @@ class CockpitMonitoringDistributionControllerTest {
|
|
|
CockpitMonitoringDistributionResponse initial = service.load(
|
|
|
"delta-baseline", null, null, null, null, 1, 100, SNAPSHOT_AT);
|
|
|
assertEquals(1, statusCount(initial, CockpitMonitoringPointStatus.OFFLINE));
|
|
|
- assertEquals(2, statusCount(initial, CockpitMonitoringPointStatus.NORMAL));
|
|
|
+ // 票 81:基线 NORMAL 从 2 变 0 —— 那 2 个是被排除的 P-MISMATCH / P-UNKNOWN;
|
|
|
+ // 本用例真正要钉的是「增量 +1 能被复算」,故期望值随之整体下移一位。
|
|
|
+ assertEquals(0, statusCount(initial, CockpitMonitoringPointStatus.NORMAL));
|
|
|
|
|
|
CockpitMonitoringDistributionResponse applied = service.applyDeviceStatusDelta(
|
|
|
"delta-1", null, null, null, null, 1, 100, SNAPSHOT_AT,
|
|
|
@@ -388,7 +403,7 @@ class CockpitMonitoringDistributionControllerTest {
|
|
|
"2026-09-02T10:05:00+08:00"));
|
|
|
|
|
|
assertEquals(0, statusCount(applied, CockpitMonitoringPointStatus.OFFLINE));
|
|
|
- assertEquals(3, statusCount(applied, CockpitMonitoringPointStatus.NORMAL));
|
|
|
+ assertEquals(1, statusCount(applied, CockpitMonitoringPointStatus.NORMAL));
|
|
|
|
|
|
CockpitMonitoringDistributionResponse ignoredOld = service.applyDeviceStatusDelta(
|
|
|
"delta-2", null, null, null, null, 1, 100, SNAPSHOT_AT,
|
|
|
@@ -397,7 +412,7 @@ class CockpitMonitoringDistributionControllerTest {
|
|
|
"2026-09-02T10:04:00+08:00"));
|
|
|
|
|
|
assertEquals(0, statusCount(ignoredOld, CockpitMonitoringPointStatus.OFFLINE));
|
|
|
- assertEquals(3, statusCount(ignoredOld, CockpitMonitoringPointStatus.NORMAL));
|
|
|
+ assertEquals(1, statusCount(ignoredOld, CockpitMonitoringPointStatus.NORMAL));
|
|
|
|
|
|
CockpitMonitoringDistributionResponse ignoredUnknown = service.applyDeviceStatusDelta(
|
|
|
"delta-3", null, null, null, null, 1, 100, SNAPSHOT_AT,
|
|
|
@@ -406,7 +421,42 @@ class CockpitMonitoringDistributionControllerTest {
|
|
|
"2026-09-02T10:06:00+08:00"));
|
|
|
|
|
|
assertEquals(0, statusCount(ignoredUnknown, CockpitMonitoringPointStatus.OFFLINE));
|
|
|
- assertEquals(3, statusCount(ignoredUnknown, CockpitMonitoringPointStatus.NORMAL));
|
|
|
+ assertEquals(1, statusCount(ignoredUnknown, CockpitMonitoringPointStatus.NORMAL));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 票 81:八桶必须与片区/行业/设备类型分布描述**同一个 scope**,且不因状态筛选塌成一根。
|
|
|
+ *
|
|
|
+ * <p>下面两条等式把「累加位置」钉死 —— 谁把 {@code global.addPoint}/
|
|
|
+ * {@code addUnassociatedDevice} 挪回过滤之前(或挪到状态过滤之后),它们都会立刻变红:
|
|
|
+ * ①六态 + UNKNOWN 只数点位,故必须等于片区分布 {@code pointCount} 的跨片区求和;
|
|
|
+ * ②再加 UNASSOCIATED 就是整张台账,故必须等于分页 {@code total}。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void eightBucketsShareTheSameScopeAsDistributionsAndNeverCollapseUnderStatusFilter() {
|
|
|
+ for (String[] scope : Arrays.asList(
|
|
|
+ new String[]{null, null, null},
|
|
|
+ new String[]{"GAS", null, null},
|
|
|
+ new String[]{null, "GAS_SENSOR", null},
|
|
|
+ new String[]{null, null, "1"})) {
|
|
|
+ CockpitMonitoringDistributionResponse response =
|
|
|
+ service.load("scope", scope[0], scope[1], null, scope[2], 1, 100, null);
|
|
|
+ String scopeText = "[industry=" + scope[0] + " deviceType=" + scope[1]
|
|
|
+ + " district=" + scope[2] + "]";
|
|
|
+ assertEquals(districtPointSum(response), statusBucketSum(response, true),
|
|
|
+ "八桶的点位部分与片区分布 scope 不一致:" + scopeText);
|
|
|
+ assertEquals(response.getPagination().getTotal(), statusBucketSum(response, false),
|
|
|
+ "八桶合计与台账总数不一致:" + scopeText);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 状态是「选中项」不是「收窄项」:选了某个状态,八桶仍读同一 scope,不得塌成一根。
|
|
|
+ int unscoped = statusBucketSum(
|
|
|
+ service.load("all", null, null, null, null, 1, 100, null), false);
|
|
|
+ for (String status : Arrays.asList("ALARM", "NORMAL", "UNASSOCIATED")) {
|
|
|
+ int scoped = statusBucketSum(
|
|
|
+ service.load("status", null, null, status, null, 1, 100, null), false);
|
|
|
+ assertEquals(unscoped, scoped, "状态筛选把八桶收窄了:" + status);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -529,6 +579,25 @@ class CockpitMonitoringDistributionControllerTest {
|
|
|
.getCount();
|
|
|
}
|
|
|
|
|
|
+ /** 六态 + UNKNOWN(index 0-6)只数点位;加上 UNASSOCIATED(index 7)才是整张台账。 */
|
|
|
+ private int statusBucketSum(CockpitMonitoringDistributionResponse response, boolean pointsOnly) {
|
|
|
+ List<CockpitPointStatusQuery.Count> rows = response.getPointStatusDistribution();
|
|
|
+ int sum = 0;
|
|
|
+ for (int index = 0; index < (pointsOnly ? 7 : 8); index++) {
|
|
|
+ sum += rows.get(index).getCount();
|
|
|
+ }
|
|
|
+ return sum;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int districtPointSum(CockpitMonitoringDistributionResponse response) {
|
|
|
+ int sum = 0;
|
|
|
+ for (CockpitMonitoringDistributionResponse.DistrictDistribution row
|
|
|
+ : response.getDistrictDistribution()) {
|
|
|
+ sum += row.getPointCount();
|
|
|
+ }
|
|
|
+ return sum;
|
|
|
+ }
|
|
|
+
|
|
|
private boolean hasRequestMappingAnnotation(Method method) {
|
|
|
return method.isAnnotationPresent(org.springframework.web.bind.annotation.RequestMapping.class)
|
|
|
|| method.isAnnotationPresent(GetMapping.class)
|