# 11: 后端执行票 —— 工单状态与处置率读模型对齐 Type: task Status: resolved Label: ready-for-agent Blocked by: 01, 06 Category: backend-execution ## 来源 - 决策:[01 报警—工单闭环读模型与 Gogs 票拆分边界](01-backend-slice-ticket-boundary.md)(后端切片 1)、[06 异常统计、工单处置率与管理字段词汇](06-cross-mode-business-semantics.md) - 规格:[10 业务语义规格](10-business-semantics-spec.md) ## 状态 **已解决**(2026-10-08)。诊断 + 口径修订 + 契约测试 + 提交推送全部完成;后端编译通过、`Cockpit*Test` **299/299 通过**。提交:`3de679c`(本票口径修订,14 文件)+ `9380c1b`(既存编译修复,1 文件)→ 已推送 `origin/master`(`5b53a6d..9380c1b`)。基线卡片:[工单状态与处置率读模型](../handoff/baselines/work-order-rate.md)。 **收口拆分说明**:本票的交付物(代码 + 编译 + 契约测试 + 提交 + 基线卡片登记)已全部达成,故按其自身范围 close。原「部署后真实环境复验」项**已移交独立票 [17 部署后真实环境复验](17-postdeploy-live-verification.md)**——该项需要一次真实部署才会发生,留在本票会把「已完成的实现」与「尚未发生的部署」绑在一起,无谓阻塞下游 [执行票 12](12-backend-read-model-district.md) / [13](13-backend-realtime-sync.md)。基线阶梯的 ③ 判定由票 17 推动。 ## 范围(做什么) 1. **工单状态字典对齐**:Cockpit 对外字典与运行时 `WorkOrderStatusEnum`(0–7)逐码对齐;前端已对齐,后端对外字典与真实适配器沿用权威枚举。 2. **处置率口径修订**: - 分子=状态 `5`(已结案);分母=状态 `0–6` 工单数之和(状态 `7` 取消排除);范围=当前行业/片区/时间范围内的全部工单(不要求关联报警/事件)。 - 分母为 `0` → `state=EMPTY`(前端据此显示"不适用")。 - `basis` 改为工单分母语义(如 `CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS`);旧 `CLOSED_WORK_ORDERS_OVER_TOTAL_EVENTS` 与"状态 6 / 事件总数"口径废止。 3. **契约测试**:覆盖 0–7 字典、分母含 6、状态 7 排除、分母 0、分子 5;事件/设备工单摘要与时间线的状态码一致。 ## 非目标 - 不修改工单创建、派单、接单、处置、验收、延期、取消等操作逻辑。 - 不新增工单写接口。 ## 验收 代码提交 + 编译 + 针对性契约测试 + 联调基线登记(按 [05 真实联调基线](../../issues/05-integration-baseline-and-evidence.md));本地联调通过后提交并推送到 Gogs。 | 项 | 状态 | 证据 | | --- | --- | --- | | 编译 | **通过** | `mvn -o -pl pipe-network-service/zksy-admin -am compile` | | 针对性契约测试 | **通过** | `-Dtest=Cockpit*Test` → `Tests run: 299, Failures: 0, Errors: 0`(提交后复跑同样 299/299) | | 联调基线登记 | **已产出** | [基线卡片 02](../handoff/baselines/work-order-rate.md) | | 代码提交 | **已完成** | `3de679c` fix(cockpit): 工单处置率改为状态 0-6 工单分母口径 | | 推送到 Gogs | **已完成** | `git push origin master` → `5b53a6d..9380c1b`;远端 `master=9380c1b` 与本地一致 | | 部署后真实环境复验 | **已移交** | 抽为独立票 [17 部署后真实环境复验](17-postdeploy-live-verification.md)(本项需真实部署才发生,不再阻塞本票与下游票) | ## 诊断(2026-10-08) 诊断手段:只读源码核实(后端 `E:\pipe-ner-server`)+ 联调环境 `http://192.168.110.96:8301` 实测复现。 ### 后端基线(实施前) | 项 | 值 | | --- | --- | | 仓库 / 分支 / 提交 | `http://192.168.110.30:3000/shaoy/pipe-ner-server` / `master` / `5b53a6d` | | 远端 | `origin/master = 5b53a6d`(与本地 HEAD 一致) | | 工作区 | 本票改动之前有 1 项既存未提交:`CockpitFreshnessSemanticsControllerTest.java`(与本票无关,**未触碰**) | | 联调环境 | `http://192.168.110.96:8301` 实测可达,只读接口免鉴权 HTTP 200 | | 工单字典源码 | `CockpitWorkOrderStateDictionary` **已在 HEAD**(提交 `903d44d`,早于 `5b53a6d`) | ### 范围 1 —— 工单状态字典对齐:**实施前已满足** `CockpitWorkOrderStateDictionary`(`cockpit/workorder/`)已公开 0–7 八态,与运行时 `WorkOrderStatusEnum`(`ops/enums/` 与 `api/enums/`)逐码逐名一致(0 待派单 / 1 待接单 / 2 已接单 / 3 处理中 / 4 待验收 / 5 已结案 / 6 延期中 / 7 已取消)。实测 `workOrderStateCounts` 逐态回 8 行,与前端 `WORK_ORDER_STATES` 对齐。本票**新增**一条逐码比对运行时枚举的契约测试锁死该对齐,并复用该字典作为处置率口径的**唯一权威来源**。 ### 范围 2 —— 处置率口径修订:**实施前为旧口径,实测复现** 实施前 `CockpitHandlingEfficiencyService` 分母传 `scopeTotal = facts.size()`(**事件数**),`basis = CLOSED_WORK_ORDERS_OVER_TOTAL_EVENTS`,分母 0 回 `NO_EVENTS`。 联调环境实测(`GET /api/cockpit/v1/events/handling-efficiency`): | 请求 | totalEvents | 分子 | 分母 | basis | | --- | --- | --- | --- | --- | | `ALARM` 近 31 天 | 6 | 1 | **6**(事件数) | `CLOSED_WORK_ORDERS_OVER_TOTAL_EVENTS` | | `WARNING` 近 31 天 | 4 | 0 | **4**(事件数) | 同上 | 关键缺陷:该 `ALARM` 组 `stateCounts` 为 `0=1, 5=1`(**旧口径,按事件回联累加**),而响应分母回 **6**(`=totalEvents`)——分母是**事件数**,与工单无关,口径错误被真实数据复现。**注意**:`0=1, 5=1` 只能说明「至少涉及 2 张工单」,不能推出新分母恰为 2(旧计数按事件累加、且不含未关联工单;新分母取范围内**全部**工单)——详见下方勘误。 同族两处(`CockpitOverviewWarningSettingsService`、`CockpitHistoryAnalysisService`)同样为事件数分母,经维护者裁定一并修订。 ### 范围 3 —— 契约测试:**实施前缺失,且旧断言锁死旧口径** 原 `CockpitHandlingEfficiencyControllerTest` 12 例中断言的就是旧口径(`value="0.2500"`、`denominator=8`、旧 `basis`)。已随口径一并改写,并补足 0–7 字典、分母含 6、状态 7 排除、分母 0、分子 5 的针对性覆盖。 ## 实施(2026-10-08) 维护者裁定(本票 grilling 轮次):① 授权提权、本会话直接改后端工作树;② 由维护者承担负责人;③ **三处同族分母一并修订**;④ 时间维度用 `work_order.create_time`;⑤ 扩展 snapshot 携带范围内全部工单;⑥ overview 两 family 共用全范围工单读数;⑦ 只改汇总处置率、趋势/行级保持事件桶;⑧ 状态分布改为工单口径。片区(`district`)过滤留给执行票 12。 ### 后端改动(`E:\pipe-ner-server`,14 个源文件) **口径单源** `CockpitWorkOrderStateDictionary`:新增 `HANDLING_RATE_BASIS`、`CLOSED_RATE_CODE=5`、`CANCELLED_RATE_CODE=7`、`isRateEligible(code)`(0–6);三个服务的本地状态常量改为引用该单源。 **快照扩展**(处置率分母来源,均不要求事件回联,按 `create_time` 界定窗口): | 快照 | 新增 | | --- | --- | | `CockpitHandlingEfficiencySnapshot` | `scopeWorkOrders`(含 `stateCode` / `createTime` / `typeCode`) | | `CockpitHistoryAnalysisSnapshot` | 同上 | | `CockpitOverviewWarningSettingsSnapshot` | `scopeWorkOrders`(含 `stateCode` / `createTime`) | `BusinessCockpitEventAdapter` 新增 `devicesById` 索引(工单存 `device_id`,需经设备解析类型)+ `efficiencyScopeWorkOrders()` / `historyScopeWorkOrders()`;`BusinessCockpitOverviewWarningSettingsAdapter` 新增 `scopeWorkOrders()`。旧构造器保留为委托重载,兼容既有调用点。 **三处服务口径修订**: | 服务 / 端点 | 分母 | 备注 | | --- | --- | --- | | `CockpitHandlingEfficiencyService`(`events/handling-efficiency`) | 状态 0–6 工单数 | `basis` 改名;分母 0 → `NO_ELIGIBLE_WORK_ORDERS`;`workOrderStateCounts` 改为工单口径 | | `CockpitOverviewWarningSettingsService`(`overview/warning-settings`) | 同上 | 两 family **共用**一份全范围读数;`workOrderStates` 改为工单口径 | | `CockpitHistoryAnalysisService`(`events/history-analysis`) | 同上 | **仅汇总**处置率;小时桶与 `handledCount` 仍是事件口径(不同维度) | 效率端点的 `linkedWorkOrderCount` / `missingWorkOrderCount` 仍为事件回联口径(事件明细语义未变,仅不再参与分母)。 **文档**:`docs/cockpit/README.md` 第 2 节效率口径改写为新 `basis` 与工单分母定义,并注明历史分析小时桶 / 预警设置日趋势仍是事件桶。 ### 验证 ```powershell cd E:\pipe-ner-server mvn -o -pl pipe-network-service/zksy-admin -am compile mvn -o -pl pipe-network-service/zksy-admin -am test "-Dtest=Cockpit*Test" "-DfailIfNoTests=false" "-Dsurefire.failIfNoSpecifiedTests=false" ``` 结果:编译通过;`Tests run: 299, Failures: 0, Errors: 0, Skipped: 0`(`BUILD SUCCESS`)。新增针对性用例: - `dictionaryMatchesTheRuntimeWorkOrderStatusEnumCodeForCode`(0–7 逐码逐名 + 8 态 + `isRateEligible` 边界) - `rateDenominatorIncludesDelayingStateSixAndExcludesCancelledSeven`(5/6/7/7 → 分母 2、分子 1) - `cancelledWorkOrdersAreExcludedFromTheRateDenominator`(全为 7 → 分母 0 → `EMPTY`) - `rateIsAvailableFromWorkOrdersEvenWhenNoEventsFallInTheWindow`(窗口无事件仍有处置率 → 分母与事件解耦) - `summaryNumeratorAndDenominatorMatchTheReportedStateDistribution`(`sum(0–6) == denominator` 自洽复算) - `bothFamiliesShareTheSameWorkOrderScopedRate`(overview 两 family 共用) ### 提交与推送(2026-10-08) | 提交 | 内容 | 文件数 | | --- | --- | --- | | `3de679c` | `fix(cockpit): 工单处置率改为状态 0-6 工单分母口径` | 14(本票全部改动) | | `9380c1b` | `fix(cockpit): 修复 freshness 契约测试未闭合语句导致测试模块编译失败` | 1(**非本票**,见下) | 推送:`git push origin master` → `5b53a6d..9380c1b`;远端 `master=9380c1b` 与本地一致,工作树干净。 `9380c1b` 的说明:该文件(`CockpitFreshnessSemanticsControllerTest.java`)的改动是**本次会话之前就存在于工作树**的既存改动,按纪律本不该并入本票提交。但核实后发现 `HEAD` 中该文件末句 `assertTrue(Arrays.stream(...)` **缺少右括号与分号、根本编译不过**,即远端 `zksy-admin` 测试模块原本就是坏的;若只推本票 14 个文件,远端测试模块仍无法编译。故拆成独立提交修复,并在备注中注明与本票口径无关,便于审计与必要时单独回退。 ### 未完成项与补齐条件 1. **部署后复验**:联调实例运行的是已部署旧构建,本票改动只提交未部署 → 复核基线卡片 §4.1 命令,期望 `basis=CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS`,且 **`sum(workOrderStateCounts[0..6]) == denominator`**、分母 **≠** `totalEvents`。**未复验前不得宣称本票契约已在真实环境生效。** > **勘误(2026-10-08,本文件早先版本)**:本条原写「该 ALARM 组 `denominator=2`、`value=0.5000`」,**该预期是错的**。那个 2 是从**旧响应**的 `stateCounts`(`0=1, 5=1`)反推的,而旧 `stateCounts` 只统计**已关联报警**的工单(未关联的直接 `continue` 跳过,且按**事件**累加、同一工单多事件会重复计);新口径分母取自 `scopeWorkOrders`,含范围内**全部**工单(含未关联报警者)。因此部署后分母是「窗口内设备类型可映射且状态 0–6 的全部工单数」,通常**不等于** 2,`value` 也随之改变。正确的复验判据是**自洽性**(`sum(0–6) == denominator`、分母≠事件数),而非某个固定数值。 > > 详见下方「诊断轮次记录」2026-10-08 勘误条。 2. **前端对齐**:`contractMocks.js` 的 `denominator = total`(事件数)、`reason: 'NO_EVENTS'` 与 `handlingRateHint()` 文案仍为旧口径 → 归执行票 14–16(规格 10 已把文案改动划在前端域)。 ## 诊断轮次记录 - 2026-10-08:首轮诊断。范围 1 判定已满足;范围 2 在联调环境复现旧口径(ALARM:分母回事件数 6;当次 `stateCounts` 为 `0=1, 5=1`);范围 3 缺失且旧测试锁死旧口径;识别三项阻断(后端写权限 / 负责人 / 口径边界)。 - 2026-10-08:维护者裁定八项口径问题后实施;编译通过、299/299 测试通过;产出基线卡片 02。未提交、未部署,留票不 close。deck 读写额度本轮被闸,按 [决策票 06](06-cross-mode-business-semantics.md) 先例回落本地 Markdown tracker 落盘。 - 2026-10-08:提交推送完成(`3de679c` + `9380c1b` → `origin/master`);提交后复跑 `Cockpit*Test` 仍 299/299。因部署实例未更新,票留 `committed-pending-live-verification` 不 close。 - 2026-10-08 **勘误**:早先在本票、基线卡片 02 与票 17 中写入的复验预期「ALARM 组 `denominator=2`、`value=0.5000`」**错误**,已更正。错误成因:该 2 由**旧响应** `stateCounts`(`0=1, 5=1`)反推,而旧 `stateCounts` 只累积**已关联报警**的工单、且按事件累加(同一工单多事件重复计);新口径分母取自 `scopeWorkOrders`(范围内全部工单,含未关联报警者),两者基数不同,不可互推。复验判据已改为自洽性校验(`sum(0–6) == denominator`、分母≠`totalEvents`)。本次实测复核:旧实例 ALARM 近 31 天回 `basis=CLOSED_WORK_ORDERS_OVER_TOTAL_EVENTS`、`den=6`、`value=0.1667`,`sum(0–6)=2`(旧口径下的事件回联计数),**确证实例尚未部署新构建**。 ## 关联 - 上位:本地票 25、Gogs #63 - 下游:执行票 12(后端回联)依赖本票 - 基线:[基线卡片 02 工单状态与处置率读模型](../handoff/baselines/work-order-rate.md)