# 后端执行票 —— 单设备业务优先级裁定落地(全部报警均有未结束工单时工单处置最高) Category: backend-execution ## 来源 [前端执行票 45](45-untitled.md) 实施前裁定(维护者 2026-10-16): - 监测点聚合:维持报警优先级最高(后端现行语义不动)。 - **单设备**:只要该设备的**所有报警都有对应的工单**(即报警均已被工单承接、处置在进行),单设备自身状态应判**工单处置最高**——高于报警/预警。 - 判定权归后端;前端只渲染(CONTEXT.md / ADR 0002 不变)。 ## 现状证据(前端票 45 侦察) - 权威解析器 `CockpitMonitoringPointStatusResolver.deviceStatus()` 现行顺序: `alarm → warning → workOrderInProgress → online?NORMAL:OFFLINE`,即报警恒压工单。 实测设备 `21119740`(alarm=true, warning=true, workOrderInProgress=true)→ 后端判 ALARM; 按裁定应为工单处置(其报警已有工单承接时)。 - `CockpitMonitoringPointStatus` 枚举优先级 ALARM(1)/WARNING(2)/WORK_ORDER(3)/NORMAL(4)/OFFLINE(5)/EMPTY(6)。 - map-snapshot 不携带工单布尔——前端无法也不得自行判定。 ## 范围 1. 明确「所有报警均有对应工单」的判定口径(报警-工单回联读模型已有 `alarm_data.work_order_id` 关联,票 12 已核实关联键)。 2. 调整单设备状态解析:设备存在报警、且每条报警均有未结束/已关联工单时 → `WORK_ORDER`(工单处置);否则维持现行序。 注意只影响**单设备**状态(设备卡片 deviceStatus、快照未关联设备 deviceStatus、统计计数),**监测点聚合 `resolve()` 语义由维护者另行确认是否跟随**(聚合用 min(priority),若单设备可判 WORK_ORDER(3) 而报警设备判 WORK_ORDER,聚合优先级序是否需把 WORK_ORDER 提到 ALARM 之前,须裁定并同步枚举 priority 与前端词表)。 3. 契约测试覆盖:报警无工单 → ALARM;全部报警有工单 → WORK_ORDER;混合 → 按裁定;无报警维持现状。 4. 落地后通知前端票 45 消费侧复核(前端 `DEVICE_STATUS_VISUALS.priority` 与 `POINT_STATUS_VISUALS.priority` 需同步新序——前端票 45 已按双轴实现,priority 同步是唯一耦合点)。 ## 非目标 - 不改前端代码(前端词表同步由票 45 收口时复核)。 - 不动监测点聚合 `resolve()` 除非第 2 条裁明确认需要。 ## 进度 **已实施、验证、推送完成(2026-10-17)→ 关闭。** 提交 `3772220e` + 编译解阻 `5d4d03c6`,已推 `origin/master`(远端核实 3772220e)。 `Cockpit*Test,BusinessCockpit*Test` **512/512**;变异闸 **7/7** 打红并字节还原;前端 t09 **38/38**(零耦合,裁定 4 成立)。 裁定全部落地:只取未处理报警(`alarm_status=0`);承接=`work_order_id` 非空且工单 `order_status ∈ {0..4,6}`; 空集/读失败 → 维持 ALARM;`resolve()` 与枚举 priority 不动;已推送。 两处如实留档:① 票面「21119740 应为工单处置」被真实数据证伪(该设备 2 条未处理报警均无工单承接, 按裁定应为 ALARM);② 新规则在当前数据上不改变任何读数(正确行为,非未生效)。 `zksy-system` 全量 2 失败/1 错误为既有(stash 对照逐条相同),与本票零交集。 **下一步**:无(本票交付物已闭环)。前端消费侧无需跟随改动。 Status: resolved Type: task Blocked by: Labels: wayfinder:task, backend-execution ## Comments ### local — 2026-10-16T18:13:25.050Z 阶段闸门诊断完成(2026-10-17):当前工作树是前端 City-Lifeline-Cockpit(origin=192.168.110.30/ChenJZ/City-Lifeline-Cockpit),没有后端 Java/Maven/Gradle 源码或 CockpitMonitoringPointStatusResolver;后端仓库/负责人/权限未提供。只读真实接口复核:map-snapshot 仍返回设备 21119740 的 ALARM,设备工单接口中存在多条未结束工单但也有未关联当前报警的工单;因此票面“全部报警均有未结束/已关联工单”的设备级判据不能从现有设备汇总直接推断,需要后端按每条当前报警关联键(alarm_data.work_order_id)且工单状态 0–4/6 视为未结束,状态 5/7 视为结束,空/缺关联即不满足。未改前端或模拟后端,避免越权猜测。 ### local — 2026-10-16T18:32:36.319Z 【诊断记录 2026-10-17 —— 后端仓库已定位,票面前提与真实数据不符,判定口径待裁定】 ### local — 2026-10-16T18:46:42.632Z 【维护者裁定 2026-10-17(5 项,全部采纳推荐项)—— 据此实施】 1. **报警范围**:只取未处理报警(`alarm_data.alarm_status = 0`)。已处理的历史流水不参与。 2. **承接判据**:`alarm_data.work_order_id` 非空 **且** 该工单未结束(`work_order.order_status ∈ {0,1,2,3,4,6}`,0-based;5 已结案 / 7 已取消为终态)。 3. **空集与缺数据**:设备 `alarm` 标志为真但取不到任何未处理报警行(或根本没有 `alarm_data` 行)→ **维持现行序 → ALARM**(「查不到」不等于「已承接」)。 4. **聚合与优先级**:**不跟随**。`resolve()` 与枚举 `priority` 不改,前端 `POINT_STATUS_VISUALS.priority` 零改动、无耦合;仅单设备 `deviceStatus()` 按新规则判定。 5. **交付边界**:提交并推送 `origin/master`。 **本条同时记录一处契约边界(非目标,如实留档)**:`monitoring/situation` 的设备 `metricStatus`(`BusinessCockpitMonitoringSituationAdapter.metricStatus()`)词表为 `NORMAL/WARNING/ALARM/EXPIRED/OFFLINE/UNKNOWN`,**没有 `WORK_ORDER` 桶**,因此本次不改它——改它需要动 situation 响应契约,超出本票范围。 ### local — 2026-10-16T19:24:28.181Z 【实施完成 2026-10-17 —— 已提交并推送 origin/master】 **提交**:`3772220e`(本票) + `5d4d03c6`(编译解阻)→ 已推 `origin/master`(`git ls-remote` 核实远端 `master = 3772220e`,本地/远端 0 ahead/0 behind)。推送前远端已有 2 个新提交,已 `git fetch` + `rebase` 后复跑测试再推(未强推)。 ## 交付 判定单源仍是 `CockpitMonitoringPointStatusResolver.deviceStatus()`: 报警**且** `isAlarmsCoveredByOpenWorkOrders()` → `WORK_ORDER`,否则维持现行序。 事实由 `BusinessCockpitDeviceFactResolver` 新增计算(新增 `AlarmDataService` 依赖): 取 `alarm_data.alarm_status=0` 的行,**每一条**都要有 `work_order_id` 且对应工单 `order_status ∈ {0..4,6}`;无未处理行 / 任一行不满足 / 读失败 → `false`(保守降级为 ALARM)。 **三处透传接缝**(丢任一处结论都会被抹掉,变异 M6/M7 专门钉住): `CockpitMonitoringPointDeviceSnapshot`、分布快照 `SourceSmartDevice` + 分布适配器、 `CockpitMonitoringDistributionService.effectiveDevice()` 重建、`CockpitMonitoringPointDetailService` 归一化。 ## 验证 - `Cockpit*Test,BusinessCockpit*Test` **512/512**(基线 510 + 本票新增 2)。 - 变异闸 [t52-mutations.mjs](../verify/t52-mutations.mjs) **7/7 打红并按字节还原**;覆盖:判定分支删除、判据放宽为「至少一条」、已结案/已取消算作承接、空集视为已承接、去掉 `alarm_status` 过滤、分布适配器不透传、`effectiveDevice` 重建丢事实。 - 真实载荷取证 [real-data-consequence.md](../verify/artifacts/t52/real-data-consequence.md)。 - 前端 t09 闸门 **38/38**(priority 与词表零改动,确认裁定 4「不跟随」成立)。 ## 顺带修复(既有阻塞,非本票引入) `zksy-system` 在 HEAD 上**根本编译不过**:`SseEmitterManager`(`5bc0c6e4` 引入)用 `SseEmitter`, 而 `spring-webmvc` 只由 `springfox-spring-webmvc` 以 **provided** 传递(provided 不参与编译期), 模块编译期缺类。已按 same-scope 显式声明 `provided` 补齐编译期(`5d4d03c6`);运行期无影响 (boot 包 `BOOT-INF/lib` 本就含该 jar)。**未改任何运行语义。** ## 既有失败(如实登记,与本票零交集) `zksy-system` 全量另有 **2 失败 / 1 错误**:`AlarmLifecycleServiceTest.insertWritesLogBeforeCommittingWithoutTrigger`、 `WorkOrderServiceImplTest.cancelDoesNotWriteSuccessLogWhenStatusChangesConcurrently` / `cancelAllowsOnlyOrdersAwaitingDispatchOrAcceptance`。 已把本票源码整体 `git stash` 后复跑,**同样 2 失败/1 错误、逐条相同** → 确认为 HEAD 级既有失败。 ## 两点如实留档 1. **票面举例被证伪**:票面写「实测 21119740 按裁定应为工单处置」。实测该设备 2 条未处理报警 **都没有工单承接**,按裁定**就应当是 ALARM**。票面把设备级 `workOrderInProgress`(该设备存在任意 未结束工单)误当成了「未处理报警已被逐条承接」——两者不是同一事实。 2. **新规则在当前真实数据上不改变任何设备读数**(今天不存在「未处理且已承接」的报警行)。 这是正确行为而非未生效;待业务侧为未处理报警建单后,该设备才会由 `ALARM` 变 `WORK_ORDER`。 ## 消费侧影响 前端**零改动**(裁定 4):`POINT_STATUS_VISUALS`/`DEVICE_STATUS_VISUALS` 的 `priority` (ALARM 1 / WARNING 2 / WORK_ORDER 3)与后端枚举一致,本次未动枚举。 `monitoring/situation` 的 `metricStatus` 词表无 `WORK_ORDER` 桶,仍按现状(已在裁定评论留档为非目标)。 ## 1. 交付环境(已具备) - 后端仓库:`E:\pipe-ner-server`,远端 `http://192.168.110.30:3000/shaoy/pipe-ner-server.git`,`master = origin/master = 91444f51`(`feat(alarm): 扩展报警指标并重构遥测报警评估逻辑`),工作树干净。 - 只读数据库可达:KingbaseES `192.168.110.30:54321 / pipi_ner`(schema `app_user`),已用 psql 直连核对(非只看接口自证)。 - 模块:后端代码在 `pipe-network-service/zksy-system`(域)与 `zksy-admin`(控制器+测试)。 ## 2. 权威链(现状) `CockpitMonitoringPointStatusResolver`(`zksy-system/src/main/java/com/zksy/cockpit/point/`): - `deviceStatus()`:`alarm → warning → workOrderInProgress → online ? NORMAL : OFFLINE`(报警恒压工单)。 - 枚举 `CockpitMonitoringPointStatus`:ALARM(1)/WARNING(2)/WORK_ORDER(3)/NORMAL(4)/OFFLINE(5)/EMPTY(6)。 - 调用点(改 `deviceStatus` 即全覆盖):`resolve()` 点位聚合(min priority)、`cards()` 设备卡 deviceStatus、`statistics()` 设备统计计数、`orderedDevices()` 排序、`getPublishedDeviceStatus()`(快照/未关联设备 deviceStatus)。 事实来源 `BusinessCockpitDeviceFactResolver.resolve()`: - `alarm` = `equipment_status.alarm_status == 1`(设备级报警标志)。 - `warning` = `early_warning.status ∈ {PENDING, PROCESSING, HANDLING, RELEASED}`。 - `workOrderInProgress` = 该设备**任意**一张 `work_order.order_status ∈ {0,1,2,3,4,6}`(0-based;5 已结案、7 已取消为终态)——**不与报警逐条回联**。 ## 3. 真实数据(推翻票面前提) `alarm_data` 是**报警历史流水**(同一设备的重复报警会一行一条): | 事实 | 实测 | | --- | --- | | `alarm_data` 全表行数 / 涉及设备 | 90 行 / 仅 `21119740` 一台 | | 未处理(`alarm_status=0`) | 2 行(`BJ202610161600430761`、`BJ202610101851389458`) | | 带 `work_order_id` 的行 | 2 行,**且这 2 行 `alarm_status` 都是 1(已处理)** | | 「未处理 且 已关联工单」的行 | **0 行** | | 已关联工单的两条报警 | `89b53f56…`→WO291(order_status=0 待派单,未结束);`e68a942d…`→WO287(order_status=5 已结案) | | 设备 `21119740` | `equipment_status.alarm_status=1`、`online_status=1`;7 张工单,3 张未结束(291=0、286=1、285=3) | **因此票面「实测设备 21119740(alarm=true, warning=true, workOrderInProgress=true)→ 按裁定应为工单处置(其报警已有工单承接时)」无法成立**:该设备当前的 2 条未处理报警**都没有工单承接**;有工单的那 2 条报警都已经是「已处理」历史记录。 ## 4. 为什么必须先裁定 范围第 1 条要求「明确『所有报警均有对应工单』的判定口径」,范围第 2 条又要求「监测点聚合语义由维护者另行确认」。上述数据事实使这两条**各自都有多种自洽读法、且结果可观测不同**(同一台设备在四种口径下两种判 ALARM、两种判 WORK_ORDER),按纪律不猜。 另:`CockpitMonitoringPointStatus` 的 priority 同时驱动点位聚合(min)与前端图例排序(前端 `POINT_STATUS_VISUALS.priority` 与 t09 A3 闸门按此锁定),是否调整属维护者裁定范围。 ## 5. 尚未动工 本轮只做诊断与事实核对,**未改任何后端代码**,未提交、未推送。裁定后按单源(`deviceStatus()`)实施并补契约测试。 ## Answer **已实施、验证并推送关闭(2026-10-17)。** ### 裁定口径(维护者 2026-10-17,5 项) 单设备存在报警(`equipment_status.alarm_status=1`)、且该设备**全部未处理报警** (`alarm_data.alarm_status=0`)都满足 `work_order_id` 非空 **且** 对应工单 `work_order.order_status ∈ {0,1,2,3,4,6}`(未结束;5 已结案、7 已取消为终态) → 判 `WORK_ORDER`;否则维持现行序。无未处理报警行 / 任一行不满足 / 读失败 → 保守判 `ALARM`。 `resolve()` 聚合与枚举 `priority` **不动**(前端零耦合)。 ### 交付 - 判定单源:`CockpitMonitoringPointStatusResolver.deviceStatus()`。 - 事实计算:`BusinessCockpitDeviceFactResolver.alarmsCoveredByOpenWorkOrders()`(新增 `AlarmDataService` 依赖)。 - **三处事实透传接缝**(丢任一处结论会被抹掉):`CockpitMonitoringPointDeviceSnapshot`、 分布快照 `SourceSmartDevice` + `BusinessCockpitMonitoringDistributionAdapter`、 `CockpitMonitoringDistributionService.effectiveDevice()` 重建、`CockpitMonitoringPointDetailService` 归一化。 - 提交 `3772220e`(本票)+ `5d4d03c6`(既有编译阻塞修复)→ 已推 `origin/master`(`git ls-remote` 核实远端 = `3772220e`)。 ### 验证 - `Cockpit*Test,BusinessCockpit*Test` **512/512**(基线 510 + 新增 2)。 - 变异闸 [t52-mutations.mjs](../verify/t52-mutations.mjs) **7/7 打红并按字节还原**; 覆盖:判定分支删除、判据放宽为「至少一条」、已结案/已取消算作承接、空集视为已承接、 去掉 `alarm_status` 过滤、分布适配器不透传、`effectiveDevice` 重建丢事实。 - 真实载荷取证 [real-data-consequence.md](../verify/artifacts/t52/real-data-consequence.md)。 - 前端 t09 闸门 **38/38**(确认裁定 4「不跟随」成立)。 ### 两点如实留档 1. **票面举例被证伪**:票面写「实测 21119740 按裁定应为工单处置」。实测该设备 2 条未处理报警 **都没有工单承接**,按裁定**就应当是 ALARM**。票面把设备级 `workOrderInProgress` (该设备存在任意未结束工单)误当成了「未处理报警已被逐条承接」——两者不是同一事实。 2. **新规则在当前真实数据上不改变任何设备读数**(今天不存在「未处理且已承接」的报警行)。 这是正确行为而非未生效;待业务侧为未处理报警建单后,该设备才会由 `ALARM` 变 `WORK_ORDER`。 ### 既有失败(与本票零交集) `zksy-system` 全量另有 **2 失败 / 1 错误**:`AlarmLifecycleServiceTest.insertWritesLogBeforeCommittingWithoutTrigger`、 `WorkOrderServiceImplTest.cancelDoesNotWriteSuccessLogWhenStatusChangesConcurrently` / `cancelAllowsOnlyOrdersAwaitingDispatchOrAcceptance`。已把本票源码整体 stash 后复跑, **同样 2 失败/1 错误、逐条相同** → 确认为 HEAD 级既有失败。 ### 下一步 无。本票交付物已闭环,前端消费侧无需跟随改动。