# 前端执行票 —— 预警效率面板按预警/报警分流:预警族展示预警流程而非工单口径 ## Question 维护者 2026-10-19 反馈第 6 条:预警处置「预警效率」面板没有做**预警/报警模式的数据区分**;预警模式下没有关联工单数据,应展示**预警流程**的信息。 **实测证据(2026-10-19;2026-10-21 复验一致)**:`/events/handling-efficiency?eventFamily=WARNING` 与 `=ALARM` 的响应结构**完全相同**——都只有 `workOrderStateCounts`(工单八态)、工单口径 `handlingEfficiency`(`CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS`)、`averageDuration`。预警族实测 `linkedWorkOrderCount=0`、`missingWorkOrderCount=totalEvents`(10-19 那次 6/6,10-21 复验 5/5)——**预警没有工单**,但面板画的仍是工单口径,等于用一个不存在的对象统计预警。 `domain/warningResponsePanels.js` 的 `responseEfficiencyPanel` 也只消费 `workOrderStates`/`handlingRate`/时长趋势,未按 `eventFamily` 分支。模式切换(`eventStore.mode`)只换了请求参数,UI 语义没换。 ## Acceptance - 预警与报警两族**语义分流**:预警族展示预警自身流程信息;报警族保留工单口径(工单是报警的处置手段,既有裁定见票 41/42/52)。 - 预警族优先消费后端已有的预警生命周期字段(`summary.handledCount`/`inProgressCount`/`cancelledCount`/`totalEvents`,实测均存在),并如实说明「预警不挂工单」;**不得**把工单口径的数字画在预警模式下。 - 工单口径字段在预警模式下若必须展示,需明确标注为「该族无工单关联」并给出真实计数(`missingWorkOrderCount`),不画 0% 假读数。 - 模式切换后面板标题/口径说明/KPI 提示一起换(复用 `eventListSwitchView` 单一词表,不新增第二张)。 - 若需要后端新增预警流程字段(如「预警→处置动作」流转统计),**在票面记录契约缺口并另开后端票**,本票不假装已有数据。 - 闸门 + 变异打红;按 AGENTS.md 跳过 build/dev/浏览器验证。 ## Context - 代码:`domain/warningResponsePanels.js`(`toHandlingEfficiency`/`responseEfficiencyPanel`)、`services/warningResponseApi.js`、`EventListPane.vue`/`WarningResponsePane.vue`。 - 既有裁定:票 41(预警族改预警自身生命周期)、票 42(前端消费两族分叉,预警族工单口径字段 null 保真)、票 52(报警的单设备工单优先级)。 - 注意:票 42 已定「预警族工单口径 null 保真」,但那只覆盖**预警设置面板**;本票是**预警效率面板**,仍用工单口径,两者不一致。 ## Related spec [`../specs/2026-10-18-global-list-pagination.md`](../specs/2026-10-18-global-list-pagination.md)(本票不涉分页,属同批维护者反馈) ## 进度 - 2026-10-21 **认领**(无认领人、`ready-for-agent`;`Blocked by #59` 已 resolved)。 - 2026-10-21 **调查完成,发现票面一条前提与实测冲突**(见 Comments「事实纠正」): `summary.handledCount/inProgressCount/cancelledCount` **不是**预警生命周期字段,而是**工单**派生 (`CockpitHandlingEfficiencyService.build()` 的 `stateCounts` 来自窗口内**全部工单**,不要求关联事件)。 预警族实测 `inProgressCount=1` 来自 1 张与预警无关的待派单工单 —— 照票面字面消费就会重新造出 「预警处置中=1」的假读数,正是本票要禁止的东西。 - 2026-10-21 **维护者裁定=方案 A(诚实分流 + 记缺口)**:预警族只画该端点里**真实属于预警**的读数, 工单口径在预警模式下不上屏,契约缺口写进界面并另开后端票。理由:不假装后端已有预警流程字段。 - 2026-10-21 **实施完成**(改动 5 文件 + 闸门/变异两份脚本 + 新后端票),见下方 `## Answer`。 - 2026-10-21 **闸门 + 变异闭环**:t65 **32/32**、t65-mutations **16/16** 打红并按字节还原;回归见 Answer 第 6 条。 - 2026-10-21 真实浏览器走查按 AGENTS.md **未执行**(未获授权),归票 18 联调窗口。 - 2026-10-21 **维护者追加两条需求 → 重新开启**:① 报警模式要列出当前工单列表(卡片、可点开详情); ② 预警摘要仍提示「本端点…」需复核预警数据是否正确。 - 2026-10-21 **第 2 条复核完毕 = 数据没问题、文案该改**:双证据(后端源码 scopeWorkOrders 注释原句 「范围内全部工单(不要求关联报警/事件)」;实测预警族 linkedWorkOrderCount=0、八态合计 0)。 旧文案三处毛病已修:「本端点」是接口词汇、先讲缺口后讲读数、「工单关联实况」对值班人员无意义。 - 2026-10-21 **第 1 条实施完毕(前后端一起交)**:新增后端 GET /api/cockpit/v1/work-orders(全量分页, 复用运营侧 WorkOrderService,挂在免鉴权前缀下),前端加「当前工单列表」卡片块 + 时间线浮层。 - 2026-10-21 **闸门扩到 51 条 + 变异 27 处**:t65 **51/51**、t65-mutations **27/27**;新增 H/I/J 三组。 Status: resolved Assignees: codex Type: task Blocked by: #59 Labels: wayfinder:task, ready-for-agent ## Comments ### local — 2026-10-21T02:32:49.096Z 认领:状态 ready-for-agent、无认领人;Blocked by #59 已 resolved(票 59 已关闭),本票处于 frontier,可开工。先读代码与既有裁定,再确认方案。 ### codex — 2026-10-21 事实纠正(提问前已查范围) 已查阅范围:本票正文/评论/依赖、`domain/warningResponsePanels.js`、`services/warningResponseApi.js`、 `stores/eventStore.js`、`components/WarningResponsePane.vue`、`mocks/contractMocks.js` 的 handling-efficiency 载荷、 票 42 与 `verify/t42-warning-settings-contract.mjs`、后端 `CockpitHandlingEfficiencyService`(t30 validation-checkout 同版), 以及 2026-10-21 对 `192.168.110.96:8301` 的实测(载荷落盘 [verify/artifacts/t65/](../verify/artifacts/t65/))。 **Ticket 前提被证伪一条**(其余成立):Acceptance 第 2 条把 `summary.handledCount/inProgressCount/cancelledCount` 当作「后端已有的预警生命周期字段」——实测与源码都表明它们是**工单**派生: | 事实 | 证据 | | --- | --- | | `stateCounts` 来自**范围内全部工单**,不要求关联事件 | `CockpitHandlingEfficiencyService.build()` 注释原文「不要求关联报警/事件」,累加源是 `scopeWorkOrders(...)` | | 所以 handled/inProgress/cancelled 与 `handlingEfficiency`/`workOrderStateCounts` 同源 | 三者都是 `stateCounts` 的分项,`family` 只影响 `facts`(事件集合),不影响 `stateCounts` | | 预警族实测 `inProgressCount=1` 与预警无关 | `linkedWorkOrderCount=0`、`missingWorkOrderCount=5`,但 `workOrderStateCounts` 有一张「待派单」= 1 | | 号 2 字面实施会重造假读数 | 「预警处置中=1」这个 1 来自一张不挂任何预警的工单 | **提问**(已带选项,维护者选定 A): - **A 仅做诚实语义分流 + 记契约缺口(维护者选定)**:预警模式只画该端点里真实的预警口径 (条数/等级/发生趋势/工单关联实况),不画工单处置率/工单状态分布/关闭时长;缺口写进票面并另开后端票。 - B 交叉消费 `/overview/warning-settings` 的六态生命周期:分布是全量快照不随 range 收缩、且该端点不支持 90d(400), 需降级处理且在 eventStore 增一次取数。 - C 按票面字面消费 `summary` 三字段:会重新造出票面禁止的假读数。 ### codex — 2026-10-21 实施口径(供 review 对照) 1. **分流依据**:`data.family`(后端 `eventFamily` 回显)**优先**,其次 `extra.mode`(面板把 `eventStore.mode` 传下来), 都没有时落报警口径(旧调用点 t19/t16/t44 不因签名变化而变空壳)。 2. **预警族块**:KPI=预警条数 / 已关联工单 / 未关联工单(全是真实计数)→ 预警等级分布(逐事件行统计, 分页未取全时窗口读数里如实标「仅统计已返回的事件页,非全量」)→ 预警发生趋势(169 个小时桶按日合并, 复用历史分析同一条 `historyTrendRows`,不另写采样)。 3. **报警族块**:原样保留(处置率/响应与关闭时长/工单状态分布/结案时长趋势),既有裁定不重开。 4. **标题**:新 `responseEfficiencyTitle(mode)`,词根取 `eventListSwitchView(mode).label` —— 单一词表。 面板内部标题 + 外壳面板外框标题(`CockpitShell.vue` 的 `panelTitle()`)都读它,两处由构造一致。 5. **口径说明**:预警族 `WARNING_EFFICIENCY_GAP`(说清「预警不挂工单」与「没有预警自身处置流程统计」), 报警族 `ALARM_EFFICIENCY_BASIS`(工单是报警的处置手段)。走票 56 的折叠摘要(`data-panel-note`,文本在 DOM 里)。 6. **契约缺口另立后端票**:[票 74](74-warning-efficiency-backend-contract.md)(建议契约 + 验收 + 参考实现落点)。 ### local — 2026-10-21T07:10:01.545Z **重新开启(维护者 2026-10-21 追加两条修改要求)** 1. **报警效率页面的报警模式**:除了现有的工单口径统计(处置率/响应与关闭时长/工单状态分布/结案时长趋势)之外,还需**直接列出当前工单列表**——卡片形式,可点开查看工单详情。 2. **预警模式下的摘要文案存疑**:界面仍显示「本端点对预警族仍只返回工单口径字段,没有预警自身的处置流程统计」。维护者要求**确认预警数据是否正确**——即核实后端在 `eventFamily=WARNING` 下到底有没有预警自身的处置流程数据;若确实没有,摘要文案要如实且可理解;若数据其实存在(我上一轮结论有误),则预警族必须改成消费真实数据。 第 2 条是对我上一轮「方案 A」结论的复查要求,须重新取证后再动手。 ### local — 2026-10-21T07:36:00.649Z 两条追加需求已完成(前端 + 后端一起交付)。完整对账写入 Answer。 **要点一:报警模式的工单列表来自新端点,不是 handling-efficiency。** handling-efficiency 只给工单状态计数、不返回工单实体(报警族实测八态合计 0,但同一设备的 /equipments/21119740/work-orders 有 7 张真实工单)。按维护者指示参考后端既有全量实现后,新增 GET /api/cockpit/v1/work-orders:复用 WorkOrderService + CockpitWorkOrderSummary,挂在免鉴权前缀下(运营侧 /api/ops/** 实测 401)。 **要点二:预警数据复核结论是「没问题」,改的是文案。** 源码 CockpitHandlingEfficiencyService 的 stateCounts 来自 scopeWorkOrders,注释原句「范围内全部工单(不要求关联报警/事件)」;实测预警族 linkedWorkOrderCount=0、八态合计 0。旧文案三处毛病已修:本端点是接口词汇、先讲缺口再讲读数、工单关联实况对值班人员无意义。 ### local — 2026-10-22T03:28:10.484Z **真机回归缺陷 + 修复(2026-10-22,维护者反馈「工单列表分页点不到第 2 页」)** 根因(不是后端、也不是分页控件的问题):`WarningResponsePane.onListPage()` 早先只认 `historyAnalysis`—— `if (props.panelId !== 'historyAnalysis') return`。工单列表的 `serverPage` 块画出了翻页控件、 `PanelBlocks` 的 `list-page` 事件也正常发出,但**在面板这一跳被 return 掉**,页码根本没进 store。 修复(3 处): 1. `stores/eventStore.js`:新增 `workOrderPage` 页码状态 + `setWorkOrderPage(next)` 翻页动作 (只重取工单分区、不牵连另外四块;越界页码按后端回显夹回;迟到响应用**独立令牌** `workOrderToken` 丢弃 —— 不共用全局面板 `token`,否则翻一个工单页会把四块面板的在途响应整批作废)。 2. `stores/eventStore.js`:条件变化(mode/range/industry/level/status)时 `workOrderPage` 一并回第 1 页(条文 5; 定时刷新不带条件 ⇒ 页码保持);`return` 表暴露 `workOrderPage` / `setWorkOrderPage`。 3. `components/WarningResponsePane.vue`:`onListPage` 按面板路由到各自分区(历史分析→`setHistoryPage`, 报警效率→`setWorkOrderPage`),不再一刀切。 **判据:新增闸门 t65b 11/11 + 变异 6/6**(`verify/t65b-work-order-paging.mjs`)—— 这类缺陷**领域层闸门抓不到**(块的 `serverPage` 与渲染层 emit 都是对的,断在「组件 → store」这一跳), 所以该闸门起 pinia + 真实 store + 真实 mock transport,从「点翻页」一路断到「分区换页」: P1 首屏 5 张/共 12/3 页;P2 翻第 2 页请求带 page=2 且行换成 6–10;P3 只重取工单分区(其它四块引用未变); P4 重复点同一页零请求;P5 越界夹回最后一页;P6 切范围回第 1 页;P7 连点只认最后一页;P8 预警族仍不出现工单列表。 变异 M27 就是**本次缺陷原形**(面板退回只认 historyAnalysis)→ S1 红。 等价变异教训(记入 t65b-mutations):假 mock 立即返回时「删掉迟到响应检查」不可观测 —— 必须让第 2 页**故意慢回**(40ms),P7 才抓得住(M32)。 回归:t13/t19/t20/t42/t46/t61/t64(17/17)/t65(51/51+27/27)/t75(18/18+8/8) 全绿;SFC 编译通过。改动未提交。 ### local — 2026-10-22T04:49:50.046Z **再一处真机缺陷:工单列表没有按时间筛选(2026-10-22 维护者反馈)** 现象:面板按近 7 / 30 / 90 天切换时,上方「工单状态分布」按窗口统计,下方「当前工单列表」却是**全库**—— 最刺眼的形态就是「分布说 0 张,列表列出 14 张」。根因:`GET /api/cockpit/v1/work-orders` 只收 `page/pageSize/stateCode`,**没有时间参数**;前端也没传窗口。 修法(前后端一起): **后端**(已移植到真实仓库 `E:\pipe-ner-server` 并编译/测试通过): - `GET /api/cockpit/v1/work-orders` 新增可选 `startTime`/`endTime`(ISO-8601 带偏移); - `BusinessCockpitWorkOrderReadAdapter.loadAll(...)` 按 **`create_time`** 加 `ge/le` 条件 —— 刻意用 `create_time` 而不是 `update_time`:面板状态分布的分母来自 `CockpitHandlingEfficiencyService.scopeWorkOrders(...)`,那里同样是「按 create_time 落在窗口内」。 两处必须是**同一批工单**,否则自相矛盾; - 时间格式不合法 → **400**(不静默忽略:静默忽略会造出「以为筛了、其实是全量」的假绿); 起点晚于终点 → 400(空窗口是入参错误,不是「没有工单」); - 契约测试从 4 条扩到 **8 条**:时间窗真的下到 SQL(抓 `count(wrapper)` 的 `createTime >=` / `<=` 与两端实参)、缺省不加时间条件、非法格式 400、起点晚于终点 400。 **教训记一笔**:MyBatis-Plus `LambdaQueryWrapper` 的 `getSqlSegment()` 里是**属性名** `createTime`(列名 `create_time` 执行期才映射),按列名断言会假红; 而 `ORDER BY createTime DESC` 也含 `createTime`,所以「没加时间条件」只能按**比较运算符**判。 **前端**: - `services/detailApi.js`:`fetchWorkOrderList` 透传 `startTime`/`endTime`(原样,不掐格式); - `stores/eventStore.js`:`loadPanels` 取数时把 `activeConditions()` 的窗口**钉**进 `workOrderWindow`, 工单列表与另外四块同一份窗口;`setWorkOrderPage` **复用** `workOrderWindow` 而不是重算 —— P10 断言发现重算会让 `endTime` 毫秒级后移,翻页时边界工单会在页与页之间重复或消失; 条件变化时窗口随 `loadPanels` 刷新、页码回第 1 页; - `domain/warningResponsePanels.js`:口径说明从「与工单状态分布是两个粒度」改为 「本窗口内创建的工单共 N 张 · 与上方「工单状态分布」是同一批工单(均按创建时间落在所选范围)」 —— 时间窗对齐后,旧措辞已经是在解释一个不存在的问题。 **判据**:t65b 从 11 条扩到 **14/14**(新增 P9 首屏请求带当前窗口、P10 翻页沿用同一窗口、 P11 切 30 天换窗口并回第 1 页)+ 变异 **9/9**(新增 M33 请求丢窗口、M34 翻页重算窗口、 M35 切范围不刷新窗口);t65 H4 随文案更新为「同一批工单」。 回归:t13/t19/t20/t42/t46/t61/t64(17/17)/t65(51/51+27/27)/t75(18/18+8/8) 全绿。 后端:真实仓库编译通过,工单测试 **35/35**(含新增 4 条时间窗用例),驾驶舱全量 **433/433**。 **当前状态**:后端类已编译进 `target/classes`(12:41/12:45),但运行中的 JVM 仍是旧版 —— 实测非法时间参数仍返回 200 + 全量 14 条(新版应为 400),需重启后端后生效。 ## Answer ### 追加需求 1:报警模式的「当前工单列表」(2026-10-21) **先说清数据来源,这是本需求最容易做错的一步。** `handling-efficiency` **不返回工单实体**,只有状态计数: 报警族实测八态合计 **0**、24 条事件 `hasWorkOrder` 全 false。但同一台设备的 `/equipments/21119740/work-orders` 能查到 **7 张真实工单**(待派单1 / 已结案3 / 待接单1 / 处理中1 / 已取消1, 其中 2 张关联报警)。**所以列表必须取自另一个端点,不能从 handling-efficiency 里"想办法"取。** 按维护者指示「后端非驾驶舱专用接口当前存在类似的全部工单实现,请先参考」,核对后端后确定做法: | 层 | 现状与结论 | | --- | --- | | 运营侧 | `GET /api/ops/workOrder` 有完整分页查询(`WorkOrderPageDTO`),但**需鉴权**(实测 401) | | 免鉴权规则 | 只按**路径前缀**放行 `GET /api/cockpit/v1/**`(`SecurityConfig:126`);且有契约测试断言「只允许一条 cockpit permitAll 规则」——这是刻意的扩展点 | | 数据层 | `WorkOrderService` 是标准 service,驾驶舱 adapter **已在直接注入使用**;`deviceOrders()` 去掉 `eq(deviceId)` 就是全量 | | 驾驶舱对外 | 原本只有按设备 / 按事件 / 按工单时间线三个入口,**没有全量** | **后端(已在 t29 checkout 实现,编译通过)** —— 新增 `GET /api/cockpit/v1/work-orders`: - `CockpitWorkOrderReadAdapter.loadAll(stateCode, limit, offset)` + `CockpitWorkOrderListSnapshot`(带 `total`); - `CockpitWorkOrderReadService.loadAll()`:翻页参数**在服务端入口夹到合法区间**(page≥1、pageSize≤100、stateCode 0–7 否则不筛), 夹取后回显实际生效值;`total` 走独立 `count(wrapper)`,不由本页条数反推(票 61 纪律); - `CockpitWorkOrderListResponse`:外形与既有 `CockpitWorkOrderReadResponse` 同一套约定,只把设备/事件维度换成分页四件套; - 关联解析仍走 `toAlarmSummary`:全量列表里「是否关联报警」**逐张如实标**,不因换入口就一律 true/false。 - **契约测试 `CockpitWorkOrderListContractTest` 4/4**(全量口径不含 device_id、分页夹取并回显、非法 stateCode 退化为不筛、状态字典给出); 工单相关测试合计 **31/31 全绿**(含既有 27 条,零回归)。 **前端**: - `services/detailApi.js` 新增 `fetchWorkOrderList()`(与既有 `fetchWorkOrders` **刻意分开**:一个回答「某设备/某事件下有哪些工单」, 一个回答「全库有哪些、共多少张」;合并会让「没有编码」与「要全量」变成同一件事); - `stores/eventStore.js` 新增 `workOrderList` 分区(**不叫后端的裸字段名**:store 出现后端裸字段名会被 t13 的 I1 守卫判红,票 05 纪律); - `domain/warningResponsePanels.js` 新增 `workOrderListBlock()` / `workOrderCardRows()` / `workOrderStateTone()`; - `components/WarningResponsePane.vue`:卡片行复用 `PanelBlocks` 既有 list 块(`row.code` 即触发 `open-card`,不发明第二套卡片协议), 点开走**新增的时间线浮层**——复用票 12 的 `WorkOrderTimeline.vue` 与 `tracksByOrder`,不另写一份轨迹渲染。 **三条如实处理**(都有断言钉住):还没取到 → 说「正在取数」;取失败 → 说「未取到」;真的 0 张 → 说「当前没有工单」。 三句是**不同的话**,不把缺数据画成 0 张(票 11/18 分区纪律)。 **卡片刻意精简**(维护者选定):工单号(抬头,可点)+ 状态标签(色键取自 `StatusTag` 既有封闭词表)+ 来源/创建时间/关联实况(副信息行)+ 故障描述。 长描述由 `EllipsisText` 省略,完整值在浮层里看——卡片不为一段长文本撑高整块。 **分页**:报警效率面板的工单列表页大小 `5`(与事件表 `PAGE_SIZE=10` **刻意分开**:不同端点、不同翻页语义, 共用常量会让「改事件页大小」意外改掉工单列表)。 ### 追加需求 2:预警摘要文案复核(2026-10-21) **维护者要求「确认预警数据是否正确」。结论:我上一轮的判断成立,后端确实没有预警自身流程数据。** 双证据: - **源码**:`CockpitHandlingEfficiencyService` 的 `stateCounts` 来自 `scopeWorkOrders(...)`,注释原句 「范围内**全部工单**(不要求关联报警/事件)」;`family` 只影响 `facts`(事件集合),不影响 `stateCounts`。 - **实测**(2026-10-21,`192.168.110.96:8301`):预警族 `linkedWorkOrderCount=0`、八态合计 0、 `linkedWorkOrderCount + missingWorkOrderCount = totalEvents`(4+0=4;报警族 24+0=24)。 **但旧文案确实写坏了**,三处毛病已修: | 毛病 | 修法 | | --- | --- | | 「本端点」是接口词汇(违反「接口词汇不上大屏」) | 改成说「后端」「这里」 | | 先讲"没有字段",读者要读两遍才知道面板上的数能不能信 | 改成**先讲读数**(这些数是什么)**再讲缺口**(为什么没有工单口径) | | 「工单关联实况」对值班人员无意义 | 改成直说「预警不挂工单」 | 新文案:「预警不挂工单,所以这里不画工单口径的处置率、工单状态分布与结案时长;下方的条数、等级分布与发生趋势都是本窗口内预警自身的真实读数。预警的处置流程统计(如「预警→处置动作」流转)后端尚未提供,已在需求池登记,上线后此处会自动换成预警自身的口径」。 ### 闸门与变异 - [t65 闸门](../verify/t65-warning-efficiency-family-split.mjs) **51/51**(原 32 条 + 新增 H/I/J 三组 19 条) - [t65 变异](../verify/t65-mutations.mjs) **27/27** 打红并按字节还原(原 16 + 新增 11) - **J 组的意义特别说明**:新端点**尚未部署**(实测 `/api/cockpit/v1/work-orders` 仍 404)。若不在这里钉住前端解析, 等后端上线那天才发现字段对不上,就又要一轮返工。J1–J5 用真实形状载荷 + mock 同构把这个缝提前焊死。 ### 回归 全绿:t13(50/50)、t16、t19(16/16)、t20(66/66)、t39(24)、t40(16)、t42、t44(109)+变异(6/6)、t46(54/54)、 t56 变异(9/9)、t61(39/39)、t64 + 变异(17/17)。SFC 编译:`WarningResponsePane` / `CockpitShell` / `PanelBlocks` / `WorkOrderTimeline` 均 OK。 后端:`mvn compile` 通过,工单相关测试 31/31。 ### 未完成事项(如实记) - **后端改动未部署**:`GET /api/cockpit/v1/work-orders` 实测仍 404。需把 t29 checkout 的改动部署到 `192.168.110.96:8301` 后,面板的工单列表才会从「未取到」变成真实卡片。前端已按最终契约实现并有 J 组守着。 - **改动未提交**:前端工作树仍混有并行在制品;后端 checkout 7 个文件(5 改 + 2 新增 + 1 测试)也未提交。 - **真实浏览器走查按 AGENTS.md 未执行**。本轮修「菜单进不去」时确认过一次根因(dev server 内存里残留旧版 `eventList.js`,含已删除的 `latestWorkOrders` 符号,重启后正常)——那不是代码缺陷,但说明**改完前端需重启 dev server**。 --- ### 原始验收对账(2026-10-21 首轮) **判据与证据在 [t65 闸门](../verify/t65-warning-efficiency-family-split.mjs),都不启浏览器、不联网;渲染层用 Vue SSR 跑真实 `PanelBlocks.vue`**: 1. **两族语义分流** ✅ 同一个端点、同一份响应类,靠 `eventFamily` 分流:预警族只出 `预警条数 / 已关联工单 / 未关联工单 + 预警等级分布 + 预警发生趋势`;报警族保留 `处置率 / 响应与关闭时长 / 工单状态分布 / 结案时长趋势`。A1–A7c 九条逐项钉住, 其中 A7c 反过来钉「报警模式不出现预警自身 KPI」——分流是对称的,不是只给一边加口径。 2. **预警族不画工单口径** ✅ 三重判据:KPI 键表(A3/A4)、块标题与块类型(A4:不得有 stacked、 不得含「工单状态分布/结案时长/处置率」)、**画出来的块序列化后零工单口径词汇与基线**(A4b: `CLOSED_WORK_ORDERS`/`可计入工单`/`已结算`/`工单状态` 一个都不许出现)。 变异 M1(整支预警分支跳过)、M1b(KPI 换了但图表没换=半分流)、M2(预警族重新长回 stacked 块) 三种形态都被 A4 当场抓住。 3. **如实说明「预警不挂工单」+ 真实关联计数** ✅ KPI 的「已关联/未关联工单」直读 `summary.linkedWorkOrderCount`/`missingWorkOrderCount`(实测 0 / 全部),不是 0% 假读数; 两句 KPI hint 与 `WARNING_EFFICIENCY_GAP` 都明写「预警不挂工单」,A3b 钉住这句必须在。 变异 M3(改用 `handledCount`/`cancelledCount` 冒充关联计数)被 A2 当场抓住 —— 这就是票面说 要防的「把工单派生数画成预警数」。 4. **预警族读数是真的,不是空面板** ✅ 等级分布逐级等于真实事件行统计(A5:4/1/0/0 对 `events[]` 里 level 的分布)、发生趋势按日合并且量不丢(A6:169 个小时桶 → 覆盖天数个点, 非零点数 = 有事件的天数,求和 = 逐桶 `eventCount` 总和)。变异 M4(等级全铺 0)、 M5(不按日合并、169 桶直接铺折线)分别被 A5/A6 抓住。 5. **标题 / 口径说明 / KPI 提示一起换,单一词表** ✅ `responseEfficiencyTitle()` 的词根取 `eventListSwitchView(mode).label`;B1 是**行为断言**(改词表则标题跟着变,不是比对字面量), B2/B4/D4 钉面板内标题与窗口读数随族换,B3 钉两族口径说明各自如实。 外壳 `panelTitle()` 也读同一份,E2/E3 钉住「外壳零第二张标题字面量」 (变异 M12 退回静态 `labelForPanel`、M13 在外壳另写一份字面量,均当场转红)。 6. **闸门 + 变异 + 回归** ✅ t65 **32/32**、t65-mutations **16/16** 全部按字节还原。 回归全绿:t16、t42、t44(109)+ 变异 6/6、t56(33)、t61(39)、t64(61)+ 变异 17/17、 t19(16)、t13(50)、t20(66)、t46(54)、t48(33)、t39(24)、t40(16)、t01。 **两处既有红如实记**(与本票 diff 零交集,已核):`t17-infrastructure-panels` / `t18-monitoring-panels` 导入期报 `MANAGEMENT_REGION_GAP` 缺导出(HEAD 上即无,Grep 验证 0 命中); `t05-adapter` 崩溃在 `deviceMetricSources.js` 读 `archive.type`(设备档案 mock 路径,票 50/51 记过的同一条断裂)。 `t45-state-axis-and-effects` 的 4 条红(C3b/C3c/C3d/C3f)正是[票 70](70-dom-overlay-fx-css-ensurefxstyles-t45.md) 记录的既有红。 7. **契约缺口如实记录并另开后端票** ✅ [票 74 后端执行票](../issues/74-warning-efficiency-backend-contract.md): 记录了 `eventFamily=WARNING` 与 `=ALARM` 回同一套工单口径的实测与源码证据、建议契约 (`warningStateCounts`/`warningHandlingEfficiency`/`warningDuration`/`warningStatusTrend`, 两族工单口径三字段在预警族显式 `null` —— 与票 41 在 warning-settings 已做的同一条纪律)、 验收五条与参考实现落点(`CockpitOverviewWarningSettingsService.familyMetrics()`)。 **本票没有假装已有数据**:预警族的三个 KPI 全是该端点里真实的字段,没有一个来自新契约。 **改动文件**(工作树混有并行在制品,未提交): - 本票 hunks:`domain/warningResponsePanels.js`(`toHandlingEfficiency` 分族 + `responseEfficiencyPanel` 分族 + `responseEfficiencyTitle` / `WARNING_EFFICIENCY_GAP` / `ALARM_EFFICIENCY_BASIS`)、 `components/WarningResponsePane.vue`(`panelView(..., { mode: eventStore.mode })`)、 `CockpitShell.vue`(`panelTitle()` + import domain 单源)、 `domain/panelBlocks.js`(`KPI_ICONS`/`KPI_ICON_COLORS` 登记 `warningLinked`/`warningMissing` 两个键)、 `mocks/contractMocks.js`(`handlingEfficiencyPayload` 补齐真实两族形状:`eventFamily`/`summary` 关联计数/ 八态/`durationTrend[].eventCount`/逐事件行;**不把预警族的工单字段藏掉** —— 缺口必须在 mock 上也看得见)。 - `.scratch` 侧:本票面、[map](../map.md) Decisions so far、新[票 74](../issues/74-warning-efficiency-backend-contract.md)、 `verify/t65-warning-efficiency-family-split.mjs`、`verify/t65-mutations.mjs`、 `verify/artifacts/t65/handling-warning-live.json` / `handling-alarm-live.json`(2026-10-21 真实载荷,闸门读它)。 **验证层级如实说明**:判据覆盖到「真实落盘载荷 → 领域层 → 共享渲染层(Vue SSR 真实组件)→ mock 同构」, 含两族对照与缺失态;**未**在真实浏览器里走查(AGENTS.md 项目规则,且本轮未获授权), 因此视觉排布(预警族三 KPI + 分级条 + 折线在面板栏宽内的观感)未取证实物证据。 按项目规则已跳过 build/dev/preview 验证。 **入库状态**:改动**未提交** —— 工作树混有并行在制品(`MapCanvas.vue`/`mapService.js`/`monitoringPanels.js` 等 20+ 文件), 按本仓既有纪律(票 42/48/64)须由维护者裁剪协调。真实走查与推送归[票 18](18-frontend-real-integration-gate.md) 联调窗口。