Procházet zdrojové kódy

docs(wayfinder): record tickets 65/75, backend gap 74/66, and their gates

- 票 65:预警/报警两族分流 + 报警族工单列表(含真机两轮缺陷复盘:分页被面板
  丢弃、列表未按时间筛),契约缺口另立票 74;
- 票 75:报警效率与预警分析支持近 90 天(historyWindows 分窗合并);
- 票 66(英文码翻译)保持 ready-for-agent,记录实测证据;
- 收入闸门 t61/t65/t65b/t75 与变异脚本,以及 2026-10-21/22 实取的分窗载荷、
  工单列表契约样例与后端移植补丁(evidence)。
ChenJZ před 3 hodinami
rodič
revize
2c6d6cf4d2
56 změnil soubory, kde provedl 8696 přidání a 13 odebrání
  1. 350 0
      .scratch/cockpit-real-data-wayfinding/issues/65-untitled.md
  2. 53 0
      .scratch/cockpit-real-data-wayfinding/issues/66-event-type-level-industry-cause.md
  3. 91 0
      .scratch/cockpit-real-data-wayfinding/issues/74-warning-efficiency-backend-contract.md
  4. 86 0
      .scratch/cockpit-real-data-wayfinding/issues/75-90-historywindows-31.md
  5. 18 6
      .scratch/cockpit-real-data-wayfinding/map.md
  6. 11 7
      .scratch/cockpit-real-data-wayfinding/specs/2026-10-18-global-list-pagination.md
  7. 2424 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65/handling-alarm-live.json
  8. 2199 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65/handling-warning-live.json
  9. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he-ALARM.json
  10. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he-WARNING.json
  11. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he-alarm-90.json
  12. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he30-WARNING.json
  13. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he31-explicit.json
  14. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he90-ALARM.json
  15. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he90-WARNING.json
  16. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he90-explicit.json
  17. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/his90-explicit.json
  18. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/his90.json
  19. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/md30.json
  20. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/md90-explicit.json
  21. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/md90.json
  22. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/ops-wo-count.json
  23. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/ops-wo.json
  24. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/probe.json
  25. 4 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/warn-events.txt
  26. 22 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/windows90-list.json
  27. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/windows90.json
  28. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/wo-ALARM.json
  29. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/wo-WARNING.json
  30. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/wo-dev.json
  31. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/wo-live.json
  32. 10 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/work-order-list-contract.json
  33. 143 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/CockpitWorkOrderListContractTest.java
  34. 69 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/CockpitWorkOrderListResponse.java
  35. 37 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/CockpitWorkOrderListSnapshot.java
  36. 203 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/cockpit-work-order-list.patch
  37. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w1-ALARM.json
  38. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w1-WARNING.json
  39. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w2-ALARM.json
  40. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w2-WARNING.json
  41. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w3-ALARM.json
  42. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w3-WARNING.json
  43. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w4-ALARM.json
  44. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w4-WARNING.json
  45. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/md-w1.json
  46. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/md-w2.json
  47. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/md-w3.json
  48. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/md-w4.json
  49. 803 0
      .scratch/cockpit-real-data-wayfinding/verify/t61-event-server-paging.mjs
  50. 210 0
      .scratch/cockpit-real-data-wayfinding/verify/t61-mutations.mjs
  51. 363 0
      .scratch/cockpit-real-data-wayfinding/verify/t65-mutations.mjs
  52. 636 0
      .scratch/cockpit-real-data-wayfinding/verify/t65-warning-efficiency-family-split.mjs
  53. 177 0
      .scratch/cockpit-real-data-wayfinding/verify/t65b-mutations.mjs
  54. 291 0
      .scratch/cockpit-real-data-wayfinding/verify/t65b-work-order-paging.mjs
  55. 307 0
      .scratch/cockpit-real-data-wayfinding/verify/t75-90d-multi-window.mjs
  56. 179 0
      .scratch/cockpit-real-data-wayfinding/verify/t75-mutations.mjs

+ 350 - 0
.scratch/cockpit-real-data-wayfinding/issues/65-untitled.md

@@ -0,0 +1,350 @@
+<!-- DSH-IDEMPOTENCY-KEY: warning-efficiency-family-semantics-v1 -->
+<!-- parentKey: 00 -->
+<!-- created: 2026-10-19T06:05:25.455Z -->
+# 前端执行票 —— 预警效率面板按预警/报警分流:预警族展示预警流程而非工单口径
+
+## 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) 联调窗口。

+ 53 - 0
.scratch/cockpit-real-data-wayfinding/issues/66-event-type-level-industry-cause.md

@@ -0,0 +1,53 @@
+<!-- DSH-IDEMPOTENCY-KEY: event-analysis-english-labels-v1 -->
+<!-- parentKey: 00 -->
+<!-- created: 2026-10-19T06:05:25.520Z -->
+# 前端执行票 —— 预警分析英文码翻译为中文(EVENT_TYPE/LEVEL/INDUSTRY/CAUSE)与「事件」概念口径说明
+
+## Question
+
+维护者 2026-10-19 反馈第 7 条:预警处置「预警分析」出现**英文字段**,需翻译为中文;且「事件」概念指代不明。
+
+**实测证据(2026-10-19,`/events/multidimensional-analysis?eventFamily=WARNING`)**:后端 `buckets[].bucketLabel` **原样返回英文码**:
+
+- `EVENT_TYPE`:`WARN-PRESSURE`(3)、`WARN-WATER-LEVEL`(1)、`WARN_INSTANT_FLOW`(2)
+- `INDUSTRY`:`GAS`、`WATER_SUPPLY`、`DRAINAGE`、`MANHOLE`
+- `LEVEL`:`RED`/`YELLOW`/`BLUE`/`GREEN`/`UNKNOWN`
+- `CAUSE`:`UNKNOWN_CAUSE`(label 反倒是中文「未知/缺失成因」)
+
+前端 `domain/warningResponsePanels.js` 的 `toEventAnalysis` 直接 `label: bucket.bucketLabel || bucket.bucketCode`,无翻译层,英文码因此上屏。**这是「接口词汇不上大屏」纪律的缺口**(票 05/17 立,票 22 已有同类先例)。
+
+「事件」概念不明:面板同时出现「事件类型」「事件等级」「历史事件」「重点识别」,但「事件」在预警/报警两族下分别指什么、与「预警」「报警」的关系,界面没有任何说明。
+
+## Acceptance
+
+- 英文码走**既有单源词表**翻译,不新造第二份:行业复用 `filterOptions.industryLabel`/`CODE_TEXT`,等级复用 `statusTones.LEVEL_TAGS`(注意后端给的是颜色名不是等级号,需显式映射),事件类型与成因各登记一张表;**认不出的回空串或原码**(数据值 vs 接口词汇的既有边界,见 `panelBlocks.CODE_TEXT` 注释)——不得把 `WARN-PRESSURE` 这类码直接上屏。
+- 新增「码→中文」翻译的**逻辑闸门**:登记表的键不得等于值(防「翻译了但没改」),且未知码不得静默丢弃。
+- 「事件」概念在面板上加一句口径说明:预警/报警两族各指什么、与工单的关系(复用 `eventListSwitchView` 词表)。
+- 两族(WARNING/ALARM)都要核(`LEVEL` 桶在报警族同样是颜色名)。
+- 若某英文码在词表里没有可信中文名,**票面如实登记**并回原码/空串,不臆造。
+- 闸门 + 变异打红;按 AGENTS.md 跳过 build/dev/浏览器验证。
+
+## Context
+
+- 代码:`domain/warningResponsePanels.js`(`toEventAnalysis`/`eventAnalysisPanel`)、`domain/panelBlocks.js`(`CODE_TEXT`/`codeText`)、`domain/filterOptions.js`(`industryLabel`)、`domain/statusTones.js`(`LEVEL_TAGS`)。
+- 同类先例:票 17 真机走查逮到「规模=medium」、票 22 业务错误文案、票 27 `undefined` 上屏——都是「接口词汇漏上大屏」。
+- 后端契约:`bucketLabel` 与 `bucketCode` 实测同值,前端不能指望后端翻译。
+
+## Related spec
+
+[`../specs/2026-10-18-global-list-pagination.md`](../specs/2026-10-18-global-list-pagination.md)(本票不涉分页,属同批维护者反馈)
+
+## 进度
+
+<!-- 做到哪一步了,在这里一行一行记 -->
+
+Status: ready-for-agent
+Type: task
+Blocked by: #59
+Labels: wayfinder:task
+
+## Comments
+
+
+## Answer
+

+ 91 - 0
.scratch/cockpit-real-data-wayfinding/issues/74-warning-efficiency-backend-contract.md

@@ -0,0 +1,91 @@
+# 后端执行票 —— 预警效率端点缺预警自身处置流程字段(两族回同一套工单口径)
+
+Status: ready-for-agent
+Type: task
+Blocked by: None
+Labels: wayfinder:task
+Category: backend-contract
+
+## 来源
+
+[票 65](65-untitled.md)(前端执行票 —— 预警效率面板按预警/报警分流)收口时记录的**契约缺口**。
+票 65 的验收第 5 条明写:若需要后端新增预警流程字段,**在票面记录契约缺口并另开后端票,本票不假装已有数据**。
+本票就是那张后端票。
+
+## 缺口(2026-10-21 实测,`192.168.110.96:8301` 只读)
+
+`GET /api/cockpit/v1/events/handling-efficiency?eventFamily=WARNING` 与 `=ALARM` 的响应结构**完全相同**,
+两族都只给工单口径:
+
+| 字段 | 语义 | 预警族实测 |
+| --- | --- | --- |
+| `summary.totalEvents` | 窗口内事件条数 | 5 |
+| `summary.linkedWorkOrderCount` | 回联到工单的事件条数 | **0** |
+| `summary.missingWorkOrderCount` | 未回联工单的事件条数 | **5**(=全部) |
+| `summary.handledCount` / `inProgressCount` / `cancelledCount` | **工单**状态派生(不是预警生命周期) | 0 / 1 / 0 |
+| `summary.handlingEfficiency` | 工单口径处置率(`CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS`) | num=0 den=1(分母来自 1 张**与预警无关**的待派单工单) |
+| `summary.averageDuration` | 关联工单结案时长 | EMPTY / `NO_CLOSED_WORK_ORDERS` |
+| `workOrderStateCounts` | 窗口内**全部工单**八态(`scopeWorkOrders`,不要求关联事件) | 只有 1 张待派单 |
+
+**代码证据**(`CockpitHandlingEfficiencyService.build()`,t30 validation-checkout 同版):
+
+- `stateCounts` 由 `scopeWorkOrders(...)` 累加 —— 范围内**全部工单**,注释原文「不要求关联报警/事件」;
+- 于是 `handledCount` / `inProgressCount` / `cancelledCount` / `handlingEfficiency` / `workOrderStateCounts`
+  全都是**工单**读数,两族共用同一份(`stateCounts` 只算一次,family 只影响 `facts` 也就是事件集合);
+- 预警自身的生命周期 `WarningWorkflow`(草稿/待接收/已接收/处置中/已处置/已解除)**不在此端点**,
+  只存在于另一个端点 `/overview/warning-settings`,且那里的分布是**全量快照、不随 `range` 收缩**,
+  该端点还不接受 `LAST_90_DAYS`(400)。
+
+## 为什么必须补
+
+预警**不挂工单**(实测 `linkedWorkOrderCount=0`、`missingWorkOrderCount=总数`)。前端若照字面消费
+`summary` 三字段,就会画出「预警处置中 = 1」—— 那个 1 来自一张与预警毫无关系的工单,
+正是票 65 票面禁止的「用一个不存在的对象统计预警」。票 65 因此只在预警模式下画
+可证实的读数(条数/等级/发生趋势/工单关联实况),前端现在是**如实的空缺口**,不是已解决的问题。
+
+## 建议契约(形状供后端评审,不以本票为准)
+
+`GET /events/handling-efficiency?eventFamily=WARNING` 的 `summary` 增预警自身流程段,
+与 `overview/warning-settings` 的预警族语义**同源**(同 `WarningWorkflow` 六态字典,不另造一套):
+
+- `warningStateCounts: [{ stateCode, stateName, count }]` —— 窗口内预警的六态全铺(含 0 桶,脏值进 UNKNOWN);
+- `warningHandlingEfficiency: { numerator, denominator, value, state, reason, basis }` ——
+  已解除 ÷ 窗口内预警总数(分母 0 → `state=EMPTY` + `reason=NO_WARNINGS`,与票 42 同一纪律);
+- `warningDuration: { value, unit, sampleCount, state, reason }` —— 预警从发布到已解除的平均时长
+  (无样本 → `state=EMPTY` + 明确 `reason`,不得回 0 时冒充真实读数);
+- `warningStatusTrend: [{ timeBucket, total, closed }]` —— 窗口内按桶的发布量/已解除量。
+
+**两族语义分流要求**:`eventFamily=WARNING` 时 `handlingEfficiency`/`workOrderStateCounts`/
+`averageDuration` 三个工单口径字段应**显式为 `null`**(而不是照旧回工单读数)——
+与票 41 在 `overview/warning-settings` 已做的裁定完全同一条纪律(预警族工单口径 null 保真,
+不用 0 或别的族的数冒充)。报警族保持现状(工单是报警的处置手段,票 41/42/52 既有裁定)。
+
+## 验收
+
+1. 契约测试覆盖两族:预警族工单口径三字段为 `null`、预警流程段齐备且 `sum(六态)==窗口内预警数`;
+   报警族工单口径原样保留。
+2. 真实环境实测 `eventFamily=WARNING`:`linkedWorkOrderCount=0` 时预警流程段仍给出真实六态分布
+   (不是全 0 假读数)。
+3. 分母 0 / 无样本的回 `state=EMPTY` + 原因码,不回 `"0.0000"` 当真实值。
+4. 全量 `Cockpit*Test` 通过;改动推 `origin/master` 并在票面记录提交哈希。
+5. 前端消费侧另立前端票(票 65 的预警分支已预留口径:只要新字段到位,替换 `warningEfficiencyKpis` 的
+   三个计数即可,块结构不用重排)。
+
+## 关联
+
+- 上游:[票 65 —— 预警效率面板按预警/报警分流](65-untitled.md)(前端已按「诚实分流 + 记缺口」收口)。
+- 同族既有裁定:[票 41](41-warning-settings-endpoint-rework.md)(预警族改预警自身生命周期、工单口径 null 保真)、
+  [票 42](42-42.md)(前端消费两族分叉)、[票 52](52-untitled.md)(报警的单设备工单优先级)、
+  [票 11](11-backend-work-order-rate.md)(处置率=状态 5 ÷ 状态 0–6 工单数)。
+- 参考实现:`CockpitOverviewWarningSettingsService.familyMetrics()` 已有预警族六态/已解除率的
+  **现成聚合逻辑**,本票是把它搬进带窗口的 efficiency 端点并分族,不是从零写。
+- 后端仓库/负责人/权限按本图 Notes:需在仓库、负责人和权限明确后由维护者启动。
+
+## 进度
+
+- 2026-10-21 由票 65 收口时另立(前端侧已完成诚实分流并记录缺口;本票未动工)。
+
+## Comments
+
+
+## Answer

+ 86 - 0
.scratch/cockpit-real-data-wayfinding/issues/75-90-historywindows-31.md

@@ -0,0 +1,86 @@
+<!-- DSH-IDEMPOTENCY-KEY: deck-8337320c-5975240 -->
+<!-- parentKey: 00 -->
+<!-- created: 2026-10-21T08:43:00.069Z -->
+# 前端执行票 —— 报警效率与预警分析支持近90天(复用 historyWindows 分窗取数 + 前端合并,撤下「最多支持31天」提示)
+
+## Question
+
+维护者 2026-10-21 反馈:报警效率与预警分析也应能取近 90 天数据,不应显示「最多支持31天」。
+
+**实测取证(2026-10-21,`192.168.110.96:8301`)**:
+- 三个聚合端点(handling-efficiency / multidimensional-analysis / history-analysis)后端契约都是**单请求 ≤31 天**:显式给 90 天窗口 → `400 {"category":"INVALID_REQUEST","message":"efficiency window must not exceed 31 days"}`(analysis/history 同句式)。
+- 但按 30 天切窗后逐窗请求全部 200:90 天 = 4 窗,实测第 3 窗 WARNING 13 条、ALARM 90 条,w1/w2/w4 为空。
+- `history-analysis` 前端**已经**这么做了(票 61:`historyWindows()` 分窗 + 合并);`fetchHandlingEfficiency`/`fetchEventAnalysis` 遇 >31 天则直接返回 UNAVAILABLE(`unsupportedWindow()`),界面挂「当前接口最多支持31天统计」提示。
+
+## Acceptance
+
+- 报警效率与预警分析在 90 天下**出真实数据**:复用 `historyWindows` 分窗(≤30 天/窗)并行取数 + 合并,与票 61 同一先例。
+- 合并口径逐字段如实:效率(durationTrend 按时序拼接去重、events 各窗首页拼接、summary 各计数求和、`handlingEfficiency` 用合并后 `stateCounts` 重算、`averageDuration` 按 `sampleCount` 加权、`workOrderStateCounts` 按码求和);分析(`totalCount` 求和、dimensions 按 `dimensionCode` 合并、buckets 按 `bucketCode` 求和并重算 `percentage`)。
+- 单窗行为零变化(7/30 天仍走原路径,不因重构变味)。
+- 任一窗失败 → 整分区 FAILED(不把 3/4 窗的数冒充 90 天);有窗 STALE → 整体 STALE。
+- 删除「当前接口最多支持31天统计」提示(不再成立)。
+- 闸门用 2026-10-21 实取的四窗载荷离线断言(`verify/artifacts/t75/`)+ 变异打红;按 AGENTS.md 跳过 build/dev/浏览器验证。
+
+## Context
+
+- 代码:`services/warningResponseApi.js`(`fetchHandlingEfficiency`/`fetchEventAnalysis`/`unsupportedWindow`)、`components/WarningResponsePane.vue`(31 天提示行)、`domain/eventRange.js`(`historyWindows`)。
+- 先例:票 61 历史事件多窗合并(同一段 `historyWindows`)。
+- 后端不改:31 天/请求是既有契约(报文里明说),分窗是前端消费侧职责。
+
+## Related spec
+
+票 61(服务端分页批次三)的「多窗合并范围」三分类。
+
+## 进度
+
+<!-- 做到哪一步了,在这里一行一行记 -->
+
+- 2026-10-21 **实施完成**:`fetchAggregated`(分窗 + 合并统一出口)替代 `unsupportedWindow` 拦截;
+  `mergeEfficiencyPayloads` / `mergeAnalysisPayloads` 按 acceptance 逐字段实现并**从服务导出**(闸门直接消费,
+  不复刻第二份防漂移);7/30 天单窗路径零变化;「最多支持31天」提示撤除。
+- 2026-10-21 **闸门 t75 18/18**(A 效率合并 / B 分析合并 / C 服务级分窗+失败语义 / D 提示撤除)
+  + **变异 8/8**(含两处**等价变异教训**:`+=`→`||=` 对「前两窗为 0」的数据等价、"丢弃后窗空维度桶"不可观测,
+  改钉「末窗覆盖」才可观测——见 t75-mutations M2/M4 注释)。
+- 2026-10-21 **回归全绿**:t13/t16/t19/t20/t39/t40/t42/t44(109+6/6)/t46/t56(9/9)/t61(39/39)/t64(17/17)/t65(51/51+27/27)。
+
+Status: resolved
+Type: task
+Blocked by:
+Labels: wayfinder:task
+
+## Comments
+
+
+## Answer
+
+**结论:90 天支持已在前端实现(后端零改动)**。31 天/请求是后端既有契约(400 报文明说
+`efficiency window must not exceed 31 days`),分窗是前端消费侧职责 —— 与票 61 历史事件多窗合并同一条先例。
+
+**改动**(`services/warningResponseApi.js` + `components/WarningResponsePane.vue`):
+
+- `fetchAggregated(path, options, hooks)`:统一出口 —— `historyWindows` 切窗(≤30 天/窗,天然满足 31 天契约);
+  单窗走原 `fetchSection` 路径(7/30 天行为零变化);多窗并行取**原始载荷**(`toData` 恒等),合并后再过一次域转换
+  —— 单窗/多窗最后走同一段 `toHandlingEfficiency`/`toEventAnalysis`,不分叉出两套口径。
+- `mergeEfficiencyPayloads`(导出):`durationTrend` 时序拼接去重、`events` 各窗首页拼接、summary 计数求和、
+  `handlingEfficiency` 用合并后 `stateCounts` **重算**(分子=已结案5,分母=可计入0–6,与后端 `stateCounts` 同一算法)、
+  `averageDuration` 按 `sampleCount` 加权(无样本如实 EMPTY)、`workOrderStateCounts` 按码求和。
+- `mergeAnalysisPayloads`(导出):`totalCount` 求和、dimensions 按 `dimensionCode` 合并、buckets 按 `bucketCode`
+  求和 + `percentage` **重算**(50%+25%=75% 的老错误被闸门 B4 钉住)。
+- 失败语义:任一窗失败 → 整分区 FAILED(不拿 3/4 窗冒充 90 天);有窗 STALE → 整体 STALE;全空窗 → EMPTY。
+- 「当前接口最多支持31天统计」提示**撤除**(D1 断言)。
+
+**判据**:[t75 闸门](../verify/t75-90d-multi-window.mjs) **18/18** —— 证据载荷是 2026-10-21 对
+`192.168.110.96:8301` 按 30 天切窗**实取的 4 窗响应**(`artifacts/t75/`,WARNING/ALARM 效率各 4 窗 + 分析 4 窗;
+实测 w3 窗 WARNING 13 条、ALARM 90 条,w1/w2/w4 为空 —— 90 天数据真实存在,只是原来被前端自己挡掉)。
+[t75 变异](../verify/t75-mutations.mjs) **8/8**。
+
+**变异的两条等价变异教训**(记入 t75-mutations M2/M4 注释):
+`totalEvents += x` 变 `||= x` 对「前两窗为 0」的数据是等价变异(结果不变,闸门不红);分析端 w1/w2 的
+维度桶为空数组,「丢弃后窗同码桶」同样不可观测 —— 换成「末窗覆盖」形态才打得出红。
+
+**回归**:t13/t16/t19/t20/t39/t40/t42/t44(109+6/6)/t46/t56(9/9)/t61(39/39)/t64(17/17)/t65(51/51+27/27) 全绿。
+
+**未完成/如实记**:
+- 改动未提交(`services/warningResponseApi.js` 同时承载票 61 的既有未提交增量,与票 75 交叠在同一文件)。
+- 真实浏览器走查按 AGENTS.md 未执行。
+

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 18 - 6
.scratch/cockpit-real-data-wayfinding/map.md


+ 11 - 7
.scratch/cockpit-real-data-wayfinding/specs/2026-10-18-global-list-pagination.md

@@ -33,11 +33,11 @@
 ## 三分法:服务端 / 前端集合 / 渐进装载
 
 - **服务端页码分页**(翻页即请求,`page`+`pageSize=10` 进查询串):
-  预警处置·当前事件表(`/events/current`);预警处置·历史事件**单窗范围**(`/events/history-analysis` 近 7 天)。
+  预警处置·当前事件表(`/events/current`);预警处置·历史事件**单窗范围**(`/events/history-analysis` 近 7 天;逐事件行与 `page`/`pageSize` 契约为票 61 连同后端一并补齐)。
 - **前端集合分页**(数据已一次装载,pager 只切窗口、零请求):
-  设备详情预警/报警/工单状态;点位摘要设备卡片与关联设备图层清单;运行概况异常设备清单;基础设施·运行信息指标列表(票 64 补入,原误列排除);历史事件**多窗合并范围**(近 30/90 天——`historyWindows` 分窗合并后不存在「全局页」,回退已加载合并集合分页并在页脚如实说明;全局服务端分页需后端另立票,不在本图范围)。
+  设备详情预警/报警/工单状态;点位摘要设备卡片与关联设备图层清单;运行概况设备台账(票 62 追加需求:原「异常设备清单」已撤下);基础设施·运行信息指标列表(票 64 补入,原误列排除);历史事件**多窗合并范围**(近 30/90 天——`historyWindows` 分窗合并后不存在「全局页」,回退已加载合并集合分页并在页脚如实说明;**跨窗全局分页**仍不在本图范围,需要时必须另立后端票重画契约)。
 - **渐进装载 + 页码 UI**(数据链路保持自动渐进,界面只对已装载集合翻页):
-  监测点台账——装载与一张图点位数据同款自动渐进链(条件变化 → 新快照 → 自动连翻到 settled,沿用快照 token 与纪元纪律);UI 面板对已装载集合做统一页码分页(10/页、页码直达)。
+  监测点台账——装载与一张图点位数据同款自动渐进链(条件变化 → 新快照 → 自动连翻到 settled,沿用快照 token 与纪元纪律);UI 面板对已装载集合做统一页码分页(10/页、页码直达)。**票 62 已落地**:装载链在 `domain/ledgerLoader.js`,页大小由后端默认值决定(前端不硬编码),`snapshotAt` 由后端回显、缺失即停链;「加载下一页」按钮退役。
 - **排除**:地图摘要点位装载保持自动渐进、无列表 UI(票 14/23 契约,**不得**误改为页码分页)。
 
 ## 维护者裁定(2026-10-18;2026-10-19 追加第 3 条)
@@ -54,13 +54,13 @@
 | 设备详情 · 工单状态 | 同上(`data-work-order-list`) | 设备详情聚合 workOrders;全量直渲 | 前端集合分页 10/页 | 一 |
 | 预警处置 · 当前事件表 | `EventListPane.vue`(`data-event-table`)、`stores/eventStore.js` | `/events/current`(后端默认 100、上限 1000);前端砍最新 12 条 | 服务端页码分页(`pageSize=10`);mode/筛选/范围变化回第 1 页 | 三 |
 | 预警处置 · 历史事件 | `domain/warningResponsePanels.js historyAnalysisPanel`(PanelBlocks list 块) | `/events/history-analysis` 分窗合并;`events.slice(0,12)` | 单窗(近 7 天)服务端分页;多窗(近 30/90 天)合并集合分页 + 页脚如实说明 | 三 |
-| 运行概况 · 异常设备清单 | `domain/monitoringPanels.js operationOverview`(list 块) | `/monitoring/situation` 一次响应;全量直渲 | 前端集合分页 10/页(页脚用 `pagination.total`) | 四 |
+| 运行概况 · 设备台账(原「异常设备清单」) | `domain/monitoringPanels.js operationOverview`(list 块) | `/monitoring/overview` 的 `devices[]`(票 62 追加需求;行数 ≡ `counts.equipmentCount`) | 前端集合分页 10/页(页脚报设备台数) | 四 ✅ |
 | 运行监测 · 监测点台账 | `domain/monitoringPanels.js monitoringPointLedger`、`stores/monitoringStore.js loadMoreLedger`、PanelBlocks `data-ledger-more` | `/monitoring/distribution` 服务端分页 + 快照 token(票 23);「加载下一页」每次追加后端一页 ≈500 卡 | 装载保持自动渐进(同地图链路);UI 对已装载集合页码分页 10/页;按钮退役 | 四 |
 | 点位摘要 · 设备卡片 | `PointDetailCard.vue`(`data-device-cards`) | 设备详情端点 `devices[]`;全量直渲 | 前端集合分页 10/页 | 二 |
 | 点位摘要 · 关联设备图层清单 | `PointDetailCard.vue`(`data-device-layer-list`,票 42) | 地图快照 `deviceCodes`,**保留全部编码**(含待确认) | 前端集合分页 10/页(数据仍保留全部,窗口化渲染;互斥归类与差额口径不变) | 二 |
 | 基础设施 · 运行信息指标列表 | `domain/infrastructurePanels.js operationPanel` → `listBlock('运行指标', …, { paginate: true, pageScope })` | `/infrastructure/operating-information` 一次回全部行业指标;**实测 6772 条**(供水 2497/排水 3576/燃气 699),响应 **2.3 MB**;原全量直渲、块标题「指标占位」与实际内容不符 | 前端集合分页 10/页(**本票 57 曾误列为排除项,2026-10-19 由维护者反馈纠正**);块标题改为如实说法 —— **已由票 64 落地** | 五 ✅ |
 | 基础设施 · 管理信息片区×行业明细 | `managementPanel`(list 块) | `districtIndustry[]`,**实测 9 行**,组合数契约有界(片区×行业) | **排除**(有界小清单,实测 <10) | — |
-| 基础设施 · 应急保障三段 | `domain/infrastructurePanels.js emergencySectionBlock`(list 块) | `/infrastructure/emergency-support` 三段 items;**实测 8+0+20=28 条**(防护目标 20 条已超一页) | 前端集合分页 10/页(一页时无控件) | 五 |
+| 基础设施 · 应急保障三段 | `domain/infrastructurePanels.js emergencySectionBlock`(list 块) | `/infrastructure/emergency-support` 三段 items;**2026-10-22 实测 0+20+9=29 条**(防护目标 20 条已超一页) | 前端集合分页 10/页(一页时无控件)——**已由票 63 落地** | 五 ✅ |
 | 预警分析 · 重点识别 | `eventAnalysisPanel`(list 块) | 后端聚合重点,数量少 | **排除** | — |
 | 一张图 · 地图摘要点位装载 | `stores/mapStore.js`、`domain/mapSummary.js` | `/map/snapshot` 快照自动链 | **排除**:保持自动渐进装载,不改页码 | — |
 | 其余 | 图表块(bars/donut/stacked/trend/pie/dimbars)、KPI、键值行、状态栏图例、地图重叠聚合 fan | 非条目清单 | **排除** | — |
@@ -82,8 +82,8 @@
 - **批次一**:共享分页基建(`domain/pagination.js` + `PagerNav.vue`,含**页码直达与输入框跳页**)+ 设备详情三清单接入(预警/报警迁移、工单状态新接入)。
 - **批次二**:点位摘要两级清单(设备卡片、关联设备图层清单)分页。
 - **批次三**:预警处置事件清单服务端页码分页(当前事件表 + 历史事件单窗/多窗规则 + mock 同构)。
-- **批次四**:运行监测(异常设备清单前端集合分页;台账自动渐进装载链 + 页码 UI、撤「加载下一页」按钮)。
-- **批次五**:基础设施运行信息指标列表(6772 条,最高优先)+ 应急保障三段接入(PanelBlocks list 块路径收尾)。
+- **批次四**:运行监测(运行概况**设备台账**前端集合分页;监测点台账自动渐进装载链 + 页码 UI、撤「加载下一页」按钮)。
+- **批次五**:基础设施运行信息指标列表(6772 条,最高优先)✅ 票 64 + 应急保障三段接入(PanelBlocks list 块路径收尾)✅ 票 63。
 
 执行票:见票 57 正文(批次五已按 2026-10-19 维护者反馈扩为两张:运行信息指标列表 + 应急保障)。
 
@@ -100,3 +100,7 @@
 - **2026-10-19 维护者 8 条反馈后的纠正**:① 补齐「输入框跳页」要求;② **撤销运行信息指标列表的排除**——实测 6772 条 / 2.3 MB,改列入批次五(原判断基于代码推断"单行业指标条数契约有界",被真实数据证伪);③ 应急保障盘点点亮实测量(8+0+20=28);④ 管理信息实测 9 行,维持排除并补实测依据。
 - **2026-10-19 票 64 收口时的追加裁定**(见「维护者裁定」第 3 条):验收以真实数据为准,**不为演示扩写 mock 样例**;mock 下不足一页不画控件是预期行为,多页行为归闸门用真实/合成载荷证明。
 - **2026-10-19 票 60(批次二)维护者当次两条裁定**:① 点位摘要里票 14 的「另有 N 台 · 未命中当前筛选」**展开子清单同样 ≤10/页**、独立第三套页码(判据一视同仁:它可能拉长页面;「另有 N 台」的 N 仍报全量,差额可追溯不变);② **计数行(条文 2/9)在 `PointDetailCard` 的三处 pager 一律省略**——`PagerNav` 新增 `show-count` 开关(默认 `true`,批次一既有消费方形态不变);该组件上方已各有权威总数(设备合计 KPI 读后端统计;清单标题「总数 X 台(已上图 Y)」只认当前快照),并排再画「共 N 条」就成了窗口口径的第二个总数。
+- **2026-10-21 票 61(批次三)落地与两条当次裁定**:① 三分法的「历史事件单窗 = 服务端页码」此前**只有前端假设、真实契约不支持**(`/events/history-analysis` 只回小时桶、无逐事件行、也不接 `page/pageSize`)——维护者裁定**本票连同后端一起完成**:该端点新增 `events[]`(形状与 `events/current` 的 `EventItem` 同构)与 `pagination`,接 `page`/`pageSize`(默认 1/100、上限 1000)。前端的服务端页码分页因此有真实契约可依。② **多窗合并范围的窗口数如实报**:`historyWindows` 在「起止差恰为 30/90 天」时会切出**毫秒级尾窗**(实测 30 天 = 2 窗、90 天 = 4 窗),页脚口径写实际窗数(`已合并 N 个时间窗 · 已加载 M 条`),**不写死 3**、也不伪造全局页码;页码归属键(`pageScope`)用**请求侧范围键**(后端回显 `range.type=TREND_WINDOW` 不能当预设范围用)。
+- **2026-10-22 票 62(批次四)落地与三条当次裁定**:① **渐进装载的页大小不归前端定**——台账装载链查询串**不带 `pageSize`**,由后端契约默认值说了算(前端硬编码 500 就是第二份口径,后端改默认值时前端会悄悄要错页);装载器只在响应里读回它用于读数。② **快照 token 只认响应回显**——`/monitoring/distribution` 此前 `page>1` 强制要 `snapshotAt` 却从不回显(前端只能猜时间戳),维护者裁定本票连同后端补齐回显(`data.snapshotAt`,与 `map-snapshot` 同形);装载器缺 token 时**停链报 `snapshot-token-missing`**,绝不拿猜的值去换 400。③ **周期刷新不重放全链**(票 33 教训):台账已 settled 时,新鲜度 tick 走 `reuseLedgerSnapshot()` **一个请求都不发**;未 settled 且未失败才补完剩余批次——「定时刷新不复位页码」(条文 6)在渐进装载上的对应形态就是「不重放、不重置已装载集合」。另记:**「加载下一页监测点」按钮与 `ledger-more` 接缝随本票退役**,台账的「还有没有下一批」由页脚 `已装载 M · 共 N 条(含未关联设备)` 如实说明。
+- **2026-10-22 票 62 追加需求(维护者当日二次裁定):运行概况展示设备台账,不展示异常设备清单**。原状:运行概况借用 `/monitoring/situation` 的 `abnormalEquipment[]` 画「异常设备清单」——那张清单只装 WARNING/ALARM/EXPIRED/OFFLINE 四态设备(实测 8 台),**不是台账、条数也与设备总数对不上**。维护者要求按真实数据展示**9 台监测设备**的台账。裁定:**后端 `/monitoring/overview` 回出逐设备 `devices[]`**(该服务 `build()` 里本就组装了 `selectedDevices`,只用于算 `createdAt` 与 `counts.equipmentCount`,未回响应);前端运行概况改读这一份。**口径纪律**:台账行数**恒等于 `counts.equipmentCount`**(同一次遍历产出,不是第二个口径),筛选生效时两者同步收窄;每行 `deviceCode`/`deviceName`/`deviceType{code,name}`/`industry`/`monitoringState`/`monitoringPointCode`/`sampledAt`/`createdAt`,缺值一律 `null`(不拿编码冒充名称、不填 0);**异常口径不丢**(指标状态分布、预警/报警趋势、离线说明仍在),只是不再拿它当台账。分页口径不变:前端集合分页 10/页、页码直达,一页不画控件。
+- **2026-10-22 票 63(批次五)落地与两条当次记录**:① **应急保障三段(危险源/防护目标/应急资源)接前端集合分页**,三段共用 `emergencySectionBlock` 一处声明 ⇒ 行为一致、不各写一份;**各自独立页码**:`pageScope` = `段码|条数|段状态`(标量键;条数也入键 ⇒ 换类别导致集合变短时页码槽跟着换、越界自然回第 1 页)。**实测校准**:2026-10-22 线上 `protectionTargets` **20 条**(两页,此前一屏 20 张卡)、`emergencyResources` 9 条、`hazardSources` 0 条(EMPTY),`totalObjectCount = 29` —— 与本规范首版记的「8+0+20=28」略有出入(现网增长,`totals` 口径随之变),**分页必要性因此由真实数据直接证明**,不必再靠 mock 灌数就「有没有增长空间」这一点争论。② **mock 静态样例仍不扩面**(维护者 2026-10-19 裁定第 3 条):`contractMocks` 的三段合计仅 4 条(1/1/2)⇒ 按条文 4 正确地不画控件,这是**预期行为**;多页行为由 t63 闸门用**真实量级载荷**(0/20/9)证明。排除项(管理信息「片区 × 行业明细」、预警分析「重点识别」)**未顺手接入**,t63 的 S4 守卫钉住这条边界。

+ 2424 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65/handling-alarm-live.json

@@ -0,0 +1,2424 @@
+{
+  "msg": "操作成功",
+  "code": 200,
+  "data": {
+    "requestId": "855f74ecd53b48b7afc70e0bc665fa8e",
+    "apiVersion": "v1",
+    "readOnly": true,
+    "displayName": "预警效率",
+    "timezone": "Asia/Shanghai",
+    "eventFamily": "ALARM",
+    "range": {
+      "type": "TREND_WINDOW",
+      "startTime": "2026-10-14T12:08:40+08:00",
+      "endTime": "2026-10-21T12:08:40+08:00"
+    },
+    "updatedAt": "2026-10-21T10:15:22.752755+08:00",
+    "filters": {
+      "eventFamily": "ALARM",
+      "industry": null,
+      "level": null,
+      "workOrderState": null
+    },
+    "pagination": {
+      "page": 1,
+      "pageSize": 100,
+      "total": 24,
+      "totalPages": 1,
+      "hasMore": false
+    },
+    "mapFilter": {
+      "eventFamily": "ALARM",
+      "workOrderState": null,
+      "deviceCodes": [
+        "21119740"
+      ]
+    },
+    "summary": {
+      "totalEvents": 24,
+      "linkedWorkOrderCount": 0,
+      "missingWorkOrderCount": 24,
+      "handledCount": 0,
+      "inProgressCount": 1,
+      "cancelledCount": 0,
+      "handlingEfficiency": {
+        "value": "0.0000",
+        "numerator": 0,
+        "denominator": 1,
+        "state": "AVAILABLE",
+        "reason": null,
+        "basis": "CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS"
+      },
+      "averageDuration": {
+        "value": "0.00",
+        "unit": "HOURS",
+        "sampleCount": 0,
+        "state": "EMPTY",
+        "reason": "NO_CLOSED_WORK_ORDERS"
+      }
+    },
+    "workOrderStateCounts": [
+      {
+        "stateCode": 0,
+        "stateName": "待派单",
+        "count": 1
+      },
+      {
+        "stateCode": 1,
+        "stateName": "待接单",
+        "count": 0
+      },
+      {
+        "stateCode": 2,
+        "stateName": "已接单",
+        "count": 0
+      },
+      {
+        "stateCode": 3,
+        "stateName": "处理中",
+        "count": 0
+      },
+      {
+        "stateCode": 4,
+        "stateName": "待验收",
+        "count": 0
+      },
+      {
+        "stateCode": 5,
+        "stateName": "已结案",
+        "count": 0
+      },
+      {
+        "stateCode": 6,
+        "stateName": "延期中",
+        "count": 0
+      },
+      {
+        "stateCode": 7,
+        "stateName": "已取消",
+        "count": 0
+      }
+    ],
+    "durationTrend": [
+      {
+        "timeBucket": "2026-10-14T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T13:00:00+08:00",
+        "eventCount": 2,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T14:00:00+08:00",
+        "eventCount": 6,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T15:00:00+08:00",
+        "eventCount": 15,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T16:00:00+08:00",
+        "eventCount": 1,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      }
+    ],
+    "events": [
+      {
+        "eventNo": "5fd8b3fd580a544f8532f124de472b5e",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T16:00:44+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "ae3a7ea1cbf3e15c01d9ef0a83bac15d",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:58:44+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "729d1d356449dbcf4c4c4a747975133f",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:55:44+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "5facfaf1b6e55e66c684df133b7d5f28",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:51:43+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "6784f326468e67f233493aaff6072bc8",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:49:43+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "1e2dbdda41c1acc6cbb4ac1a884b1628",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:47:43+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "8152106dcdd4cfa0ed1b5111ae91e19d",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:43:43+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "318226bc9ac6e7ec0ace7326e3ea2a70",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:38:42+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "91039deead7a1a00ac9f3ab14f3639cd",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:34:42+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "941b99bc204b17be970e07538c32695f",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:30:42+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "c3b04c43a2ec4fc89c4c90372b338a26",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:27:42+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "927ca6a7248b0d7b0e70e0eeed681761",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:25:41+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "4127498093a08a6f9d60107e41b46ed6",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:23:41+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "033c2f42803a058089c80abd29481995",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:17:41+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "3117a13f5db2c4fe83e1c278fe84715a",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:07:40+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "e6d29f19b8a391ffb70e2125c71742ce",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T15:05:40+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "191065ebe58a19fc116f5a5df4a415b4",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T14:57:39+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "3d2b2cad71dab4f30173aa33254549c1",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T14:44:39+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "7a4ba18ed3568cb571d5e85ff2afa9db",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T14:40:38+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "8660472e883edfc6363861724b544f6a",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T14:35:38+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "04ec3f545d4901322eb3d1588935068e",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T14:33:38+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "ce1307056ed7995e9d645b4ac2e74d4b",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T14:15:37+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "8955f439bb803893aa23a3df732287bf",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T13:38:34+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "09d7fe91f002283a4290d0eb8e7a55a2",
+        "deviceCode": "21119740",
+        "deviceName": "测试环境监测设备01",
+        "level": 4,
+        "levelColor": "GREEN",
+        "eventTime": "2026-10-16T13:37:34+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      }
+    ],
+    "freshness": "CURRENT"
+  }
+}

+ 2199 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65/handling-warning-live.json

@@ -0,0 +1,2199 @@
+{
+  "msg": "操作成功",
+  "code": 200,
+  "data": {
+    "requestId": "44a47e1b02434fe58fa050114ea09e6d",
+    "apiVersion": "v1",
+    "readOnly": true,
+    "displayName": "预警效率",
+    "timezone": "Asia/Shanghai",
+    "eventFamily": "WARNING",
+    "range": {
+      "type": "TREND_WINDOW",
+      "startTime": "2026-10-14T12:08:40+08:00",
+      "endTime": "2026-10-21T12:08:40+08:00"
+    },
+    "updatedAt": "2026-10-21T10:15:22.752755+08:00",
+    "filters": {
+      "eventFamily": "WARNING",
+      "industry": null,
+      "level": null,
+      "workOrderState": null
+    },
+    "pagination": {
+      "page": 1,
+      "pageSize": 100,
+      "total": 5,
+      "totalPages": 1,
+      "hasMore": false
+    },
+    "mapFilter": {
+      "eventFamily": "WARNING",
+      "workOrderState": null,
+      "deviceCodes": [
+        "41010500001320000002",
+        "8652052334",
+        "TEST-CBLL222222222",
+        "TEST-CBYL5432168"
+      ]
+    },
+    "summary": {
+      "totalEvents": 5,
+      "linkedWorkOrderCount": 0,
+      "missingWorkOrderCount": 5,
+      "handledCount": 0,
+      "inProgressCount": 1,
+      "cancelledCount": 0,
+      "handlingEfficiency": {
+        "value": "0.0000",
+        "numerator": 0,
+        "denominator": 1,
+        "state": "AVAILABLE",
+        "reason": null,
+        "basis": "CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS"
+      },
+      "averageDuration": {
+        "value": "0.00",
+        "unit": "HOURS",
+        "sampleCount": 0,
+        "state": "EMPTY",
+        "reason": "NO_CLOSED_WORK_ORDERS"
+      }
+    },
+    "workOrderStateCounts": [
+      {
+        "stateCode": 0,
+        "stateName": "待派单",
+        "count": 1
+      },
+      {
+        "stateCode": 1,
+        "stateName": "待接单",
+        "count": 0
+      },
+      {
+        "stateCode": 2,
+        "stateName": "已接单",
+        "count": 0
+      },
+      {
+        "stateCode": 3,
+        "stateName": "处理中",
+        "count": 0
+      },
+      {
+        "stateCode": 4,
+        "stateName": "待验收",
+        "count": 0
+      },
+      {
+        "stateCode": 5,
+        "stateName": "已结案",
+        "count": 0
+      },
+      {
+        "stateCode": 6,
+        "stateName": "延期中",
+        "count": 0
+      },
+      {
+        "stateCode": 7,
+        "stateName": "已取消",
+        "count": 0
+      }
+    ],
+    "durationTrend": [
+      {
+        "timeBucket": "2026-10-14T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T14:00:00+08:00",
+        "eventCount": 1,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-14T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T09:00:00+08:00",
+        "eventCount": 1,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T16:00:00+08:00",
+        "eventCount": 1,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-15T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T09:00:00+08:00",
+        "eventCount": 1,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T16:00:00+08:00",
+        "eventCount": 1,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-16T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-17T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-18T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-19T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T13:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T14:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T15:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T16:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T17:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T18:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T19:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T20:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T21:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T22:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-20T23:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T00:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T01:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T02:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T03:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T04:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T05:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T06:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T07:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T08:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T09:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T10:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T11:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      },
+      {
+        "timeBucket": "2026-10-21T12:00:00+08:00",
+        "eventCount": 0,
+        "handledCount": 0,
+        "averageDuration": {
+          "value": "0.00",
+          "unit": "HOURS",
+          "sampleCount": 0,
+          "state": "EMPTY",
+          "reason": "NO_CLOSED_WORK_ORDERS"
+        }
+      }
+    ],
+    "events": [
+      {
+        "eventNo": "YJ202610161509347305",
+        "deviceCode": "TEST-CBLL222222222",
+        "deviceName": "测试城北流量计",
+        "level": 1,
+        "levelColor": "RED",
+        "eventTime": "2026-10-16T16:04:44+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "YJ202610160948329850",
+        "deviceCode": "TEST-CBYL5432168",
+        "deviceName": "测试压力计",
+        "level": 1,
+        "levelColor": "RED",
+        "eventTime": "2026-10-16T09:52:04+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "YJ202610151644137245",
+        "deviceCode": "41010500001320000002",
+        "deviceName": "公司正门",
+        "level": 1,
+        "levelColor": "RED",
+        "eventTime": "2026-10-15T16:44:33+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "YJ202610150858205886",
+        "deviceCode": "41010500001320000002",
+        "deviceName": "公司正门",
+        "level": 2,
+        "levelColor": "YELLOW",
+        "eventTime": "2026-10-15T09:16:31+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      },
+      {
+        "eventNo": "YJ202610141426198772",
+        "deviceCode": "8652052334",
+        "deviceName": "测试漏失监测设备01",
+        "level": 1,
+        "levelColor": "RED",
+        "eventTime": "2026-10-14T14:26:44+08:00",
+        "workOrderStateCode": null,
+        "workOrderStateName": null,
+        "durationHours": null,
+        "hasWorkOrder": false
+      }
+    ],
+    "freshness": "CURRENT"
+  }
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he-ALARM.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he-WARNING.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he-alarm-90.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he30-WARNING.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he31-explicit.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he90-ALARM.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he90-WARNING.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/he90-explicit.json

@@ -0,0 +1 @@
+{"msg":"efficiency window must not exceed 31 days","code":400,"data":{"category":"INVALID_REQUEST","message":"efficiency window must not exceed 31 days","requestId":"d577daa83cce4030bc37d8c35a5fa95d","serverTime":"2026-10-21T16:34:31+08:00"}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/his90-explicit.json

@@ -0,0 +1 @@
+{"msg":"history window must not exceed 31 days","code":400,"data":{"category":"INVALID_REQUEST","message":"history window must not exceed 31 days","requestId":"dba3e2d5a81c4515a9b87c52012367c6","serverTime":"2026-10-21T16:35:49+08:00"}}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/his90.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/md30.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/md90-explicit.json

@@ -0,0 +1 @@
+{"msg":"analysis window must not exceed 31 days","code":400,"data":{"category":"INVALID_REQUEST","message":"analysis window must not exceed 31 days","requestId":"10d72ff0668e4c299da9dfee0c8f3993","serverTime":"2026-10-21T16:34:31+08:00"}}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/md90.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/ops-wo-count.json

@@ -0,0 +1 @@
+{"msg":"请求访问:/api/ops/workOrder/statusCount,认证失败,无法访问系统资源","code":401}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/ops-wo.json

@@ -0,0 +1 @@
+{"msg":"请求访问:/api/ops/workOrder,认证失败,无法访问系统资源","code":401}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/probe.json

@@ -0,0 +1 @@
+{"timestamp":"2026-10-21T15:10:29.930+08:00","status":404,"error":"Not Found","message":"No message available","path":"/api/cockpit/v1/equipments/work-orders"}

+ 4 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/warn-events.txt

@@ -0,0 +1,4 @@
+WARNING events 前3条:
+  YJ202610161509347305 | level 1 | hasWorkOrder false | wo null | 2026-10-16T16:04:44+08:00
+  YJ202610160948329850 | level 1 | hasWorkOrder false | wo null | 2026-10-16T09:52:04+08:00
+  YJ202610151644137245 | level 1 | hasWorkOrder false | wo null | 2026-10-15T16:44:33+08:00

+ 22 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/windows90-list.json

@@ -0,0 +1,22 @@
+[
+ {
+  "i": 0,
+  "s": "2026-07-22T16:00:00.000Z",
+  "e": "2026-08-21T15:59:59.999Z"
+ },
+ {
+  "i": 1,
+  "s": "2026-08-21T16:00:00.000Z",
+  "e": "2026-09-20T15:59:59.999Z"
+ },
+ {
+  "i": 2,
+  "s": "2026-09-20T16:00:00.000Z",
+  "e": "2026-10-20T15:59:59.999Z"
+ },
+ {
+  "i": 3,
+  "s": "2026-10-20T16:00:00.000Z",
+  "e": "2026-10-21T08:00:00.000Z"
+ }
+]

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/windows90.json

@@ -0,0 +1 @@
+[["2026-07-22T16:00:00.000Z","2026-08-21T15:59:59.999Z"],["2026-08-21T16:00:00.000Z","2026-09-20T15:59:59.999Z"],["2026-09-20T16:00:00.000Z","2026-10-20T15:59:59.999Z"],["2026-10-20T16:00:00.000Z","2026-10-21T08:00:00.000Z"]]

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/wo-ALARM.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"3033d61978104b82b2b433122e24d204","apiVersion":"v1","readOnly":true,"deviceCode":null,"eventSource":"ALARM","eventCode":"YJ202610161509347305","state":"EMPTY","stateReason":"NO_WORK_ORDERS","stateDictionary":[{"code":0,"name":"待派单"},{"code":1,"name":"待接单"},{"code":2,"name":"已接单"},{"code":3,"name":"处理中"},{"code":4,"name":"待验收"},{"code":5,"name":"已结案"},{"code":6,"name":"延期中"},{"code":7,"name":"已取消"}],"workOrders":[],"unassociatedWorkOrderCount":0}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/wo-WARNING.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"cce43da10379450eac3deb20600181cc","apiVersion":"v1","readOnly":true,"deviceCode":null,"eventSource":"WARNING","eventCode":"YJ202610161509347305","state":"EMPTY","stateReason":"NO_WORK_ORDERS","stateDictionary":[{"code":0,"name":"待派单"},{"code":1,"name":"待接单"},{"code":2,"name":"已接单"},{"code":3,"name":"处理中"},{"code":4,"name":"待验收"},{"code":5,"name":"已结案"},{"code":6,"name":"延期中"},{"code":7,"name":"已取消"}],"workOrders":[],"unassociatedWorkOrderCount":0}}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/wo-dev.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/wo-live.json

@@ -0,0 +1 @@
+{"timestamp":"2026-10-21T16:32:12.176+08:00","status":404,"error":"Not Found","message":"No message available","path":"/api/cockpit/v1/work-orders"}

+ 10 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t65b/work-order-list-contract.json

@@ -0,0 +1,10 @@
+{"requestId":"r1","apiVersion":"v1","readOnly":true,"state":"READY","stateReason":"READY",
+"stateDictionary":[{"code":0,"name":"待派单"},{"code":5,"name":"已结案"}],
+"workOrders":[
+ {"workOrderCode":"WO202610141343494644","deviceCode":"21119740","orderLevel":2,
+  "orderDesc":"监测报警,当前值:14.60(阈值:-~5),请现场核实处理","stateCode":0,"stateName":"待派单",
+  "createTime":"2026-10-14T13:43:50+08:00","updateTime":"2026-10-14T13:43:50+08:00",
+  "planFinishTime":null,"realFinishTime":null,
+  "eventAssociation":{"eventSource":"ALARM","eventCode":"89b53f5685959d6e6f90d8f90291de06","associatedAt":"2026-10-14T13:43:50+08:00"},
+  "associatedWithCurrentEvent":true}],
+"pagination":{"page":1,"pageSize":5,"total":7,"totalPages":2}}

+ 143 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/CockpitWorkOrderListContractTest.java

@@ -0,0 +1,143 @@
+package com.zksy.web.controller.cockpit;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.zksy.base.alarm.domain.AlarmData;
+import com.zksy.base.alarm.service.AlarmDataService;
+import com.zksy.base.domain.EquipmentBase;
+import com.zksy.base.mapper.EquipmentBaseMapper;
+import com.zksy.base.warning.mapper.AlarmWarningRelMapper;
+import com.zksy.base.warning.service.EarlyWarningService;
+import com.zksy.cockpit.workorder.BusinessCockpitWorkOrderReadAdapter;
+import com.zksy.cockpit.workorder.CockpitWorkOrderReadService;
+import com.zksy.ops.domain.WorkOrder;
+import com.zksy.ops.service.WorkOrderService;
+import org.apache.ibatis.builder.MapperBuilderAssistant;
+import org.apache.ibatis.session.Configuration;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * 全量工单列表端点(票 65):报警效率面板的「当前工单列表」。
+ *
+ * <p>锁四件事,都是**会真的画错界面**的那几件:
+ * <ol>
+ *   <li>不按设备/事件筛——全量口径(查询条件里不得出现 device_id);</li>
+ *   <li>总数取独立 count,不由本页条数反推(票 61 纪律);</li>
+ *   <li>非法 page/pageSize/stateCode 被夹到合法区间并**回显实际生效值**;</li>
+ *   <li>状态字典与每张工单的关联标记照常给出(卡片要按同一份词表上色)。</li>
+ * </ol>
+ */
+class CockpitWorkOrderListContractTest {
+    private static final String BASE = "/api/cockpit/v1";
+    private static final String ORDER_CODE = "WO202610101511303516";
+    private static final String DEVICE_CODE = "21119740";
+    private static final String ALARM_ID = "cd57be18d02f7d3390fb98539b93";
+    private static final String CREATE_TIME = "2026-10-10T15:11:30+08:00";
+
+    private WorkOrderService workOrderService;
+    private AlarmDataService alarmDataService;
+    private MockMvc mockMvc;
+
+    @BeforeEach
+    void setUp() {
+        for (Class<?> entity : Arrays.asList(EquipmentBase.class, WorkOrder.class, AlarmData.class)) {
+            TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), ""), entity);
+        }
+        workOrderService = mock(WorkOrderService.class);
+        alarmDataService = mock(AlarmDataService.class);
+        EquipmentBaseMapper equipmentMapper = mock(EquipmentBaseMapper.class);
+        EquipmentBase equipment = new EquipmentBase();
+        equipment.setEquipmentId("equipment-id");
+        equipment.setEquipmentCode(DEVICE_CODE);
+        when(equipmentMapper.selectById("equipment-id")).thenReturn(equipment);
+
+        WorkOrder order = new WorkOrder();
+        order.setOrderId(279L);
+        order.setOrderNo(ORDER_CODE);
+        order.setDeviceId("equipment-id");
+        order.setOrderLevel(2);
+        order.setOrderDesc("监测报警,请现场核实处理");
+        order.setOrderStatus(0);
+        order.setCreateTime(Date.from(Instant.parse("2026-10-10T07:11:30Z")));
+        order.setUpdateTime(Date.from(Instant.parse("2026-10-10T07:11:30Z")));
+
+        when(workOrderService.count(any(LambdaQueryWrapper.class))).thenReturn(7L);
+        when(workOrderService.list(any(LambdaQueryWrapper.class)))
+                .thenReturn(Collections.singletonList(order));
+        AlarmData alarm = new AlarmData();
+        alarm.setId(ALARM_ID);
+        alarm.setWorkOrderId(279L);
+        when(alarmDataService.list(any(LambdaQueryWrapper.class)))
+                .thenReturn(Collections.singletonList(alarm));
+
+        BusinessCockpitWorkOrderReadAdapter adapter = new BusinessCockpitWorkOrderReadAdapter(
+                workOrderService, mock(EarlyWarningService.class), mock(AlarmWarningRelMapper.class),
+                alarmDataService, equipmentMapper);
+        CockpitWorkOrderReadService service = new CockpitWorkOrderReadService(
+                adapter, Clock.fixed(Instant.parse("2026-10-10T07:11:30Z"), ZoneOffset.UTC));
+        mockMvc = MockMvcBuilders.standaloneSetup(new CockpitWorkOrderReadController(service)).build();
+    }
+
+    @Test
+    void listIsGlobalNotScopedToDeviceOrEvent() throws Exception {
+        mockMvc.perform(get(BASE + "/work-orders"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.data.state").value("READY"))
+                .andExpect(jsonPath("$.data.workOrders[0].workOrderCode").value(ORDER_CODE))
+                // 总数是 count 的结果(7),不是本页 1 条
+                .andExpect(jsonPath("$.data.pagination.total").value(7))
+                .andExpect(jsonPath("$.data.pagination.page").value(1))
+                .andExpect(jsonPath("$.data.pagination.pageSize").value(10))
+                .andExpect(jsonPath("$.data.pagination.totalPages").value(1))
+                // 状态字典必须给(卡片按同一词表上色)
+                .andExpect(jsonPath("$.data.stateDictionary[0].code").value(0));
+    }
+
+    @Test
+    void pageAndSizeAreClampedAndEchoedBack() throws Exception {
+        mockMvc.perform(get(BASE + "/work-orders").param("page", "0").param("pageSize", "9999"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.data.pagination.page").value(1))
+                .andExpect(jsonPath("$.data.pagination.pageSize").value(100));
+
+        // 夹取后才下到适配器:offset 必须由生效值算出,不能让非法值进 SQL
+        verify(workOrderService, org.mockito.Mockito.atLeastOnce())
+                .list(any(LambdaQueryWrapper.class));
+    }
+
+    @Test
+    void invalidStateCodeFallsBackToNoFilter() throws Exception {
+        mockMvc.perform(get(BASE + "/work-orders").param("stateCode", "99"))
+                .andExpect(status().isOk());
+        // 越界状态码当成"不筛",仍然返回数据(而不是空列表假读数)
+        verify(workOrderService, org.mockito.Mockito.atLeastOnce()).count(any(LambdaQueryWrapper.class));
+    }
+
+    @Test
+    void validStateCodeIsApplied() throws Exception {
+        mockMvc.perform(get(BASE + "/work-orders").param("stateCode", "3"))
+                .andExpect(status().isOk());
+        verify(workOrderService).count(any(LambdaQueryWrapper.class));
+    }
+}

+ 69 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/CockpitWorkOrderListResponse.java

@@ -0,0 +1,69 @@
+package com.zksy.cockpit.workorder;
+
+import java.util.List;
+
+/**
+ * 全量工单列表响应(票 65:报警效率面板的「当前工单列表」)。
+ *
+ * <p>与按设备/按事件的 {@link CockpitWorkOrderReadResponse} 保持同一套外形约定
+ * ({@code requestId/apiVersion/readOnly/state/stateReason/stateDictionary/workOrders}),
+ * 只把「设备/事件」两个维度字段换成分页四件套 —— 前端两处可共用同一份转换与卡片渲染。
+ */
+public class CockpitWorkOrderListResponse {
+    private final String requestId;
+    private final String apiVersion;
+    private final boolean readOnly;
+    private final String state;
+    private final String stateReason;
+    private final List<CockpitWorkOrderState> stateDictionary;
+    private final List<CockpitWorkOrderSummary> workOrders;
+    private final Pagination pagination;
+
+    public CockpitWorkOrderListResponse(
+            String requestId,
+            String apiVersion,
+            boolean readOnly,
+            String state,
+            String stateReason,
+            List<CockpitWorkOrderState> stateDictionary,
+            List<CockpitWorkOrderSummary> workOrders,
+            Pagination pagination) {
+        this.requestId = requestId;
+        this.apiVersion = apiVersion;
+        this.readOnly = readOnly;
+        this.state = state;
+        this.stateReason = stateReason;
+        this.stateDictionary = stateDictionary;
+        this.workOrders = workOrders;
+        this.pagination = pagination;
+    }
+
+    public String getRequestId() { return requestId; }
+    public String getApiVersion() { return apiVersion; }
+    public boolean isReadOnly() { return readOnly; }
+    public String getState() { return state; }
+    public String getStateReason() { return stateReason; }
+    public List<CockpitWorkOrderState> getStateDictionary() { return stateDictionary; }
+    public List<CockpitWorkOrderSummary> getWorkOrders() { return workOrders; }
+    public Pagination getPagination() { return pagination; }
+
+    /** 分页回显(与其它驾驶舱端点同名字段:page/pageSize/total/totalPages,前端读同一套)。 */
+    public static class Pagination {
+        private final int page;
+        private final int pageSize;
+        private final int total;
+        private final int totalPages;
+
+        public Pagination(int page, int pageSize, int total) {
+            this.page = page;
+            this.pageSize = pageSize;
+            this.total = total;
+            this.totalPages = pageSize <= 0 ? 1 : Math.max(1, (total + pageSize - 1) / pageSize);
+        }
+
+        public int getPage() { return page; }
+        public int getPageSize() { return pageSize; }
+        public int getTotal() { return total; }
+        public int getTotalPages() { return totalPages; }
+    }
+}

+ 37 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/CockpitWorkOrderListSnapshot.java

@@ -0,0 +1,37 @@
+package com.zksy.cockpit.workorder;
+
+import java.util.List;
+
+/**
+ * 全量工单分页快照(票 65)。
+ *
+ * <p>与 {@link CockpitWorkOrderReadSnapshot} 的区别:那份回答「某设备/某事件下有哪些工单」,
+ * 本份回答「全库当前有哪些工单、共多少张」——所以多一个 {@code total}:前端要翻页,
+ * 就必须知道总数(不由当前页反推,票 61 的同一纪律)。
+ */
+public class CockpitWorkOrderListSnapshot {
+    private final List<CockpitWorkOrderSummary> workOrders;
+    private final int total;
+    private final int page;
+    private final int pageSize;
+    private final Integer stateCode;
+
+    public CockpitWorkOrderListSnapshot(
+            List<CockpitWorkOrderSummary> workOrders,
+            int total,
+            int page,
+            int pageSize,
+            Integer stateCode) {
+        this.workOrders = workOrders;
+        this.total = total;
+        this.page = page;
+        this.pageSize = pageSize;
+        this.stateCode = stateCode;
+    }
+
+    public List<CockpitWorkOrderSummary> getWorkOrders() { return workOrders; }
+    public int getTotal() { return total; }
+    public int getPage() { return page; }
+    public int getPageSize() { return pageSize; }
+    public Integer getStateCode() { return stateCode; }
+}

+ 203 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/cockpit-work-order-list.patch

@@ -0,0 +1,203 @@
+diff --git a/pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/cockpit/CockpitWorkOrderReadController.java b/pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/cockpit/CockpitWorkOrderReadController.java
+index f85f8df3..8464ba77 100644
+--- a/pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/cockpit/CockpitWorkOrderReadController.java
++++ b/pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/cockpit/CockpitWorkOrderReadController.java
+@@ -4,6 +4,7 @@ import com.zksy.cockpit.foundation.CockpitDeviceNotFoundException;
+ import com.zksy.cockpit.foundation.CockpitErrorPayload;
+ import com.zksy.cockpit.foundation.CockpitInvalidRequestException;
+ import com.zksy.cockpit.foundation.CockpitReadException;
++import com.zksy.cockpit.workorder.CockpitWorkOrderListResponse;
+ import com.zksy.cockpit.workorder.CockpitWorkOrderReadResponse;
+ import com.zksy.cockpit.workorder.CockpitWorkOrderReadService;
+ import com.zksy.cockpit.workorder.CockpitWorkOrderTimelineResponse;
+@@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
+ import org.springframework.web.bind.annotation.GetMapping;
+ import org.springframework.web.bind.annotation.PathVariable;
+ import org.springframework.web.bind.annotation.RequestHeader;
++import org.springframework.web.bind.annotation.RequestParam;
+ import org.springframework.web.bind.annotation.RestController;
+ import org.springframework.web.util.UriUtils;
+ 
+@@ -62,6 +64,31 @@ public class CockpitWorkOrderReadController {
+         return ResponseEntity.ok(AjaxResult.success(response));
+     }
+ 
++    /**
++     * 全量工单分页(票 65:报警效率面板的「当前工单列表」)。
++     *
++     * <p>为什么单开一个路径而不是复用 `/equipments/{code}/work-orders`:报警族的工单跨设备,
++     * 面板要的是「当前所有工单」;实测该设备维度能查到 7 张工单,而
++     * `handling-efficiency` 的 linkedWorkOrderCount=0 —— 两者粒度不同,不能互相代替。
++     *
++     * <p>路径挂在 `/api/cockpit/v1/**` 下:免鉴权白名单只按**路径前缀**放行
++     * (`SecurityConfig: antMatchers(GET, "/api/cockpit/v1/**").permitAll()`),
++     * 运营侧 `/api/ops/**` 需要登录态,驾驶舱不能直接用。
++     *
++     * <p>三个查询参数都可缺省:`page` 默认 1、`pageSize` 默认 10(上限 100)、
++     * `stateCode` 缺省不筛。夹取在 service,控制器不做第二套校验。
++     */
++    @GetMapping("/api/cockpit/v1/work-orders")
++    public ResponseEntity<AjaxResult> workOrderList(
++            @RequestParam(value = "page", required = false) Integer page,
++            @RequestParam(value = "pageSize", required = false) Integer pageSize,
++            @RequestParam(value = "stateCode", required = false) Integer stateCode,
++            @RequestHeader(value = REQUEST_ID_HEADER, required = false) String requestId) {
++        CockpitWorkOrderListResponse response = workOrderService.loadAll(
++                resolveRequestId(requestId), page, pageSize, stateCode);
++        return ResponseEntity.ok(AjaxResult.success(response));
++    }
++
+     @ExceptionHandler(CockpitInvalidRequestException.class)
+     public ResponseEntity<AjaxResult> handleInvalidRequest(HttpServletRequest request) {
+         return error(HttpStatus.BAD_REQUEST, workOrderService.invalidRequest(
+diff --git a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/BusinessCockpitWorkOrderReadAdapter.java b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/BusinessCockpitWorkOrderReadAdapter.java
+index dc285df6..ecb14d4c 100644
+--- a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/BusinessCockpitWorkOrderReadAdapter.java
++++ b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/BusinessCockpitWorkOrderReadAdapter.java
+@@ -76,6 +76,45 @@ public class BusinessCockpitWorkOrderReadAdapter implements CockpitWorkOrderRead
+                 .collect(Collectors.toList());
+     }
+ 
++    /**
++     * 全量工单分页(票 65:报警效率面板的「当前工单列表」)。
++     *
++     * <p>与设备维度同一条读取路径,只是**不加 device_id 约束**:报警族的工单在库里
++     * 本就跨设备存在(实测该端点 24 条报警事件在 handling-efficiency 里
++     * linkedWorkOrderCount=0,但设备维度能查到 7 张工单),所以「当前工单」只能是全量口径。
++     *
++     * <p>三个刻意的取舍:
++     * <ul>
++     *   <li>{@code total} 走 {@code count(wrapper)} 独立查询,不由本页条数反推(票 61 纪律);</li>
++     *   <li>排序与设备维度一致(创建时间倒序)——两个入口返回同一张表时顺序必须相同;</li>
++     *   <li>关联解析仍走 {@link #toAlarmSummary}:全量列表里「是否关联报警」逐张如实标,
++     *       不因为换了个查询入口就一律标 true/false。</li>
++     * </ul>
++     */
++    @Override
++    public CockpitWorkOrderListSnapshot loadAll(Integer stateCode, int limit, int offset) {
++        LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
++        if (stateCode != null) {
++            wrapper.eq(WorkOrder::getOrderStatus, stateCode);
++        }
++        long total = workOrderService.count(wrapper);
++        List<WorkOrder> orders = total <= offset
++                ? Collections.emptyList()
++                : workOrderService.list(wrapper
++                        .orderByDesc(WorkOrder::getCreateTime)
++                        .last("LIMIT " + limit + " OFFSET " + offset));
++        // 空页时不再打一次 association 查询:alarmsByOrderId 对空集合本来就早退,但显式一点更省一次调用栈。
++        List<CockpitWorkOrderSummary> summaries = orders.isEmpty()
++                ? Collections.emptyList()
++                : toAlarmSummaries(orders);
++        return new CockpitWorkOrderListSnapshot(
++                summaries,
++                (int) total,
++                limit <= 0 ? 1 : (offset / limit) + 1,
++                limit,
++                stateCode);
++    }
++
+     /**
+      * 按对外报警标识解析报警:先按 {@code alarm_no}(真实单号),再退回主键 {@code id}。
+      *
+diff --git a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadAdapter.java b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadAdapter.java
+index b461d3f2..3df39ccc 100644
+--- a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadAdapter.java
++++ b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadAdapter.java
+@@ -6,4 +6,17 @@ public interface CockpitWorkOrderReadAdapter {
+     CockpitWorkOrderReadSnapshot loadByEvent(String eventSource, String eventCode);
+ 
+     CockpitWorkOrderTimelineSnapshot loadTimeline(String workOrderCode);
+-}
+\ No newline at end of file
++
++    /**
++     * 全量工单分页(票 65:报警效率面板要列「当前工单列表」,不按设备也不按事件筛)。
++     *
++     * <p>与设备维度共用同一条读取路径(运营侧 {@code WorkOrderService})与同一份
++     * {@link CockpitWorkOrderSummary} 映射,区别只在不加 {@code device_id} 约束。
++     * 排序口径与设备维度一致(创建时间倒序),前端才能稳定翻页。
++     *
++     * @param stateCode 工单状态码 0–7;{@code null} 表示不筛
++     * @param limit     本页条数(调用方已夹到合法区间)
++     * @param offset    偏移量,{@code (page-1)*limit}
++     */
++    CockpitWorkOrderListSnapshot loadAll(Integer stateCode, int limit, int offset);
++}
+diff --git a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadService.java b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadService.java
+index e898dba3..40d7acd4 100644
+--- a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadService.java
++++ b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadService.java
+@@ -18,6 +18,12 @@ import java.util.List;
+ public class CockpitWorkOrderReadService {
+     private static final List<String> VALID_EVENT_SOURCES = Arrays.asList("ALARM", "WARNING");
+ 
++    /** 全量工单列表的默认页大小(与驾驶舱其它列表端点一致)。 */
++    private static final int DEFAULT_PAGE_SIZE = 10;
++
++    /** 单次最多取多少张:防一个 pageSize 把全库拉下来。 */
++    private static final int MAX_PAGE_SIZE = 100;
++
+     private final CockpitWorkOrderReadAdapter adapter;
+     private final Clock clock;
+ 
+@@ -64,6 +70,44 @@ public class CockpitWorkOrderReadService {
+         return error(requestId, CockpitErrorCategory.INVALID_REQUEST, "工单查询参数不合法");
+     }
+ 
++    /**
++     * 全量工单分页(票 65:报警效率面板的「当前工单列表」,不按设备也不按事件)。
++     *
++     * <p>翻页参数在这里**夹到合法区间**,不信任入参直接下到 SQL 的 LIMIT/OFFSET:
++     * 非法页码轻则返回错页、重则合成语法错误的 SQL。夹取后回显实际生效值,
++     * 前端据此纠偏(与票 61「越界页码夹回」同一条纪律,只是这次发生在服务端入口)。
++     *
++     * @param page      页码 1 起(null/越界 → 1)
++     * @param pageSize  每页条数(null/越界 → 10;上限 100,避免一次拉全库)
++     * @param stateCode 工单状态码 0–7(null 或越界 → 不筛)
++     */
++    public CockpitWorkOrderListResponse loadAll(
++            String requestId,
++            Integer page,
++            Integer pageSize,
++            Integer stateCode) {
++        int safePage = page == null || page < 1 ? 1 : page;
++        int safeSize = pageSize == null || pageSize < 1 ? DEFAULT_PAGE_SIZE : Math.min(pageSize, MAX_PAGE_SIZE);
++        Integer safeState = stateCode != null && stateCode >= 0 && stateCode <= 7 ? stateCode : null;
++        CockpitWorkOrderListSnapshot snapshot = adapter.loadAll(
++                safeState, safeSize, (safePage - 1) * safeSize);
++        if (snapshot == null) {
++            throw new CockpitReadException("work order list unavailable");
++        }
++        List<CockpitWorkOrderSummary> workOrders = snapshot.getWorkOrders() == null
++                ? new ArrayList<>() : snapshot.getWorkOrders();
++        boolean empty = workOrders.isEmpty();
++        return new CockpitWorkOrderListResponse(
++                requestId,
++                "v1",
++                true,
++                empty ? "EMPTY" : "READY",
++                empty ? "NO_WORK_ORDERS" : "READY",
++                CockpitWorkOrderStateDictionary.states(),
++                workOrders,
++                new CockpitWorkOrderListResponse.Pagination(safePage, safeSize, snapshot.getTotal()));
++    }
++
+     public CockpitErrorPayload notFound(String requestId) {
+         return error(requestId, CockpitErrorCategory.NOT_FOUND, "工单或关联对象不存在");
+     }
+diff --git a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/EmptyCockpitWorkOrderReadAdapter.java b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/EmptyCockpitWorkOrderReadAdapter.java
+index 7f8a694d..85ef228a 100644
+--- a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/EmptyCockpitWorkOrderReadAdapter.java
++++ b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/EmptyCockpitWorkOrderReadAdapter.java
+@@ -18,4 +18,9 @@ public class EmptyCockpitWorkOrderReadAdapter implements CockpitWorkOrderReadAda
+     public CockpitWorkOrderTimelineSnapshot loadTimeline(String workOrderCode) {
+         return null;
+     }
++
++    @Override
++    public CockpitWorkOrderListSnapshot loadAll(Integer stateCode, int limit, int offset) {
++        return null;
++    }
+ }
+\ No newline at end of file

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w1-ALARM.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w1-WARNING.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w2-ALARM.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w2-WARNING.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w3-ALARM.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w3-WARNING.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w4-ALARM.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/he-w4-WARNING.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/md-w1.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/md-w2.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/md-w3.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t75/md-w4.json


+ 803 - 0
.scratch/cockpit-real-data-wayfinding/verify/t61-event-server-paging.mjs

@@ -0,0 +1,803 @@
+/**
+ * 票 61 闸门:预警处置事件清单「服务端页码分页」(当前事件表 + 历史事件单窗/多窗)。
+ *
+ * 覆盖验收四条 + 真实契约落地:
+ *   A  当前事件表:请求带 page/pageSize=10、翻页只重取本分区、迟到页响应被 token 作废、
+ *      mode/范围变化回第 1 页、定时刷新不复位、越界夹回、打开的详情 refreshNotice 保留
+ *   B  历史事件单窗:服务端分页(paging.mode='server'),翻页发 page 请求、迟到页作废
+ *   C  历史事件多窗:无全局页,回退已加载合并集合分页 + 页脚如实口径(窗口数/已加载)
+ *   F  mock 同构:contractMocks 的 current/history 吃 pageSize=10 并演示多页;形状与
+ *      /events/current 的 EventItem 同构;事件行总数与 pagination.total 同源
+ *   S  静态守卫:单源(PagerNav/翻页算术/serverPageView)、文案纪律、无 12 条帽
+ *   R  真实渲染(真实 SFC 编译 + 自定义渲染器真实 patch):两种分页形态的控件、计数行、
+ *      一页不画控件、服务端翻页 emit list-page、多窗口径说明在控件不画时也出现
+ *
+ * 运行:node .scratch/cockpit-real-data-wayfinding/verify/t61-event-server-paging.mjs
+ */
+import assert from 'node:assert/strict'
+import { readFileSync } from 'node:fs'
+import { registerHooks } from 'node:module'
+import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc'
+import { createRenderer, h, nextTick } from '@vue/runtime-core'
+import { createPinia, setActivePinia } from 'pinia'
+
+const SRC = new URL('../../../src/modules/cockpit/', import.meta.url).href
+const repoRoot = new URL('../../../', import.meta.url)
+const read = (relative) => readFileSync(new URL(relative, repoRoot), 'utf8')
+/** 去掉注释再查文案(注释会解释被禁的东西,直接对全文正则会拿假阳性换通过率)。 */
+const stripComments = (text) =>
+    String(text)
+        .replace(/\/\*[\s\S]*?\*\//g, '')
+        .replace(/(^|[^:])\/\/.*$/gm, '$1')
+        .replace(/<!--[\s\S]*?-->/g, '')
+
+if (typeof globalThis.document === 'undefined') globalThis.document = { activeElement: null }
+
+const {
+    registerMockTransport,
+    resetMockTransportsForTest,
+    findMockTransport,
+} = await import(`${SRC}services/cockpitApi.js`)
+const { registerCockpitMocks } = await import(`${SRC}mocks/contractMocks.js`)
+const { useEventStore } = await import(`${SRC}stores/eventStore.js`)
+const { fetchCurrentEvents } = await import(`${SRC}services/eventApi.js`)
+const { fetchEventHistory } = await import(`${SRC}services/warningResponseApi.js`)
+const { toCurrentEvents, toEventItem } = await import(`${SRC}domain/eventList.js`)
+const { SECTION_STATE, section } = await import(`${SRC}domain/sectionState.js`)
+const { PAGE_SIZE, serverPageView, pagedRows } = await import(`${SRC}domain/pagination.js`)
+const { panelView } = await import(`${SRC}domain/panelViews.js`)
+const { historyAnalysisPanel } = await import(`${SRC}domain/warningResponsePanels.js`)
+
+let passed = 0
+let failed = 0
+function check(name, condition, detail = '') {
+    if (condition) { passed += 1; console.log(`PASS ${name}`) }
+    else { failed += 1; console.error(`FAIL ${name}: ${detail}`) }
+}
+
+const pinia = createPinia()
+setActivePinia(pinia)
+const mockConfig = { dataSource: 'mock', api: { baseUrl: '', token: '' } }
+const REQUESTS = []
+
+/** 用「录制包装器」包住 contractMocks 的内建处理器:既能断言查询串,又吃到真实样例。 */
+function installMock({ current = null, history = null } = {}) {
+    requcedClear()
+    resetMockTransportsForTest()
+    registerCockpitMocks({ scenario: 'success', handlers: { registerMockTransport, resetMockTransportsForTest } })
+    const builtinCurrent = findMockTransport('/events/current')
+    const builtinHistory = findMockTransport('/events/history-analysis')
+    registerMockTransport('/events/current', async (context) => {
+        REQUESTS.push(context.path)
+        return current ? current(context, builtinCurrent) : builtinCurrent(context)
+    })
+    registerMockTransport('/events/history-analysis', async (context) => {
+        REQUESTS.push(context.path)
+        return history ? history(context, builtinHistory) : builtinHistory(context)
+    })
+}
+function requcedClear() { REQUESTS.length = 0 }
+function pathsMatching(prefix) { return REQUESTS.filter((path) => String(path).startsWith(prefix)) }
+function queryOf(path) { return new URLSearchParams(String(path).split('?')[1] || '') }
+const flush = async () => {
+    await nextTick()
+    await new Promise((resolve) => setTimeout(resolve, 5))
+    await nextTick()
+}
+
+// ---------------------------------------------------------------- 造载荷(与后端响应类同形)
+
+function eventItem(no, { level = 1, eventTime = '2026-10-21T10:00:00+08:00', deviceCode = `EQ-${no}` } = {}) {
+    return {
+        eventNo: no,
+        eventName: `事件${no}`,
+        eventType: 'GAS_CONCENTRATION',
+        level,
+        levelColor: { 1: 'RED', 2: 'YELLOW', 3: 'BLUE', 4: 'GREEN' }[level] || 'UNKNOWN',
+        status: 'ACTIVE',
+        eventTime,
+        updatedTime: null,
+        value: 1.2,
+        unit: '%LEL',
+        deviceCode,
+        deviceName: `设备${no}`,
+        monitoringPointCode: 'MP-001',
+        monitoringPointName: '样例点位',
+        location: '城区A',
+        industry: 'GAS',
+        longitude: null,
+        latitude: null,
+        coordinateValidation: { state: 'VALID', reason: null, renderable: true, crs: 'WGS84' },
+        thresholdMinValue: null,
+        thresholdMaxValue: null,
+        workOrderSummary: null,
+    }
+}
+function currentEnvelope({ mode = 'WARNING', items = [], page = 1, pageSize = PAGE_SIZE, total = items.length, totalPages = null } = {}) {
+    const pages = totalPages === null ? (total ? Math.ceil(total / pageSize) : 0) : totalPages
+    return {
+        requestId: 't61-current', apiVersion: 'v1', readOnly: true, displayName: mode === 'ALARM' ? '当前报警' : '当前预警',
+        timezone: 'Asia/Shanghai', mode, updatedAt: '2026-10-21T10:00:00+08:00',
+        filters: { mode, level: null, industry: null, status: null },
+        pagination: { page, pageSize, total, totalPages: pages, hasMore: page * pageSize < total },
+        mapFilter: { mode, level: null, industry: null, status: null, deviceCodes: [] },
+        sections: {
+            events: {
+                state: items.length ? 'SUCCESS' : 'EMPTY',
+                reason: items.length ? 'SUCCESS' : 'NO_EVENTS',
+                message: null,
+                data: items.length ? items : null,
+            },
+            workOrders: { state: 'EMPTY', reason: 'NO_WORK_ORDERS', message: null, data: null },
+        },
+    }
+}
+function historyEnvelope({ mode = 'WARNING', events = [], page = 1, pageSize = PAGE_SIZE, total = events.length, startTime = '', endTime = '' } = {}) {
+    return {
+        requestId: 't61-history', apiVersion: 'v1', readOnly: true, displayName: '历史分析',
+        timezone: 'Asia/Shanghai', eventFamily: mode, mode,
+        range: { type: 'CUSTOM', startTime, endTime },
+        updatedAt: '2026-10-21T10:00:00+08:00',
+        filters: { eventFamily: mode, level: null, industry: null },
+        summary: { eventCount: total, handledCount: 0, handlingEfficiency: { value: '0.0000', numerator: 0, denominator: 0, state: 'EMPTY', reason: 'NO_ELIGIBLE_WORK_ORDERS' } },
+        pagination: { page, pageSize, total, totalPages: total ? Math.ceil(total / pageSize) : 0, hasMore: page * pageSize < total },
+        events,
+        // 有事件就给一个非空桶:`isEmptyBy` 按 `rows.length===0` 判空(后端真实形状同理)。
+        rows: events.length ? [{ timeBucket: startTime, eventCount: events.length, handledCount: 0 }] : [],
+    }
+}
+const pagesOf = (items, page, pageSize = PAGE_SIZE) => {
+    const from = Math.min((page - 1) * pageSize, items.length)
+    return items.slice(from, from + pageSize)
+}
+const WARNINGS_14 = Array.from({ length: PAGE_SIZE + 4 }, (_, index) => eventItem(`WRN-${1001 + index}`, { level: (index % 4) + 1 }))
+const HISTORY_14 = Array.from({ length: PAGE_SIZE + 4 }, (_, index) => eventItem(`HW-${1001 + index}`, { level: (index % 4) + 1 }))
+const rangeWindow = (days) => {
+    const end = new Date('2026-10-21T10:00:00+08:00')
+    const start = new Date(end.getTime() - days * 86400000)
+    return { startTime: start.toISOString(), endTime: end.toISOString() }
+}
+
+const store = useEventStore()
+store.reset()
+store.configure({ config: mockConfig })
+
+// ================================================================ A. 当前事件表:服务端页码
+
+{
+    installMock()
+    store.reset()
+    store.configure({ config: mockConfig })
+    await store.loadPanels({ mode: 'WARNING', range: '7d' })
+
+    const currentPaths = pathsMatching('/events/current')
+    const historyPaths = pathsMatching('/events/history-analysis')
+    check(
+        'A1-initial-request-carries-page-and-page-size',
+        currentPaths.length === 1 &&
+            queryOf(currentPaths[0]).get('page') === '1' &&
+            // 契约字面量 10(规范条文 1);刻意不引用常量,否则改常量会把判据一起改掉(等价变异)。
+            queryOf(currentPaths[0]).get('pageSize') === '10',
+        `当前事件首取:${currentPaths[0]}`,
+    )
+    check(
+        'A2-first-page-view-and-count',
+        store.view.rows.length === PAGE_SIZE &&
+            store.view.pagination.page === 1 &&
+            store.view.pagination.pageCount === 2 &&
+            store.view.total === PAGE_SIZE + 4 &&
+            historyPaths.length === 1,
+        `行=${store.view.rows.length} 页=${store.view.pagination.page}/${store.view.pagination.pageCount} 总=${store.view.total};历史请求 ${historyPaths.length} 个(单窗)`,
+    )
+
+    // 翻页:只重取当前事件分区(历史请求数不变),查询串 page=2。
+    requcedClear()
+    await store.setCurrentPage(2)
+    const pageTwoPaths = pathsMatching('/events/current')
+    check(
+        'A3-page-flip-requests-page-two-only',
+        pageTwoPaths.length === 1 &&
+            queryOf(pageTwoPaths[0]).get('page') === '2' &&
+            queryOf(pageTwoPaths[0]).get('pageSize') === String(PAGE_SIZE) &&
+            pathsMatching('/events/history-analysis').length === 0 &&
+            store.currentPage === 2 &&
+            store.view.rows.length === PAGE_SIZE + 4 - PAGE_SIZE &&
+            store.view.pagination.page === 2,
+        `翻页请求 ${pageTwoPaths[0]};行=${store.view.rows.length};历史分区零重取`,
+    )
+
+    // mode 切换 → 回第 1 页并重取(切换入口走 loadPanels)。
+    requcedClear()
+    await store.setMode('ALARM')
+    check(
+        'A4-mode-change-resets-to-first-page',
+        store.currentPage === 1 &&
+            pathsMatching('/events/current').length === 1 &&
+            queryOf(pathsMatching('/events/current')[0]).get('page') === '1',
+        `切换后页码=${store.currentPage} 请求=${pathsMatching('/events/current')[0]}`,
+    )
+
+    // 定时刷新(无参 loadPanels)不复位页码:先翻到第 2 页,再刷新。
+    await store.setCurrentPage(2)
+    requcedClear()
+    await store.loadPanels()
+    check(
+        'A5-refresh-does-not-reset-page',
+        store.currentPage === 2 &&
+            pathsMatching('/events/current').length === 1 &&
+            queryOf(pathsMatching('/events/current')[0]).get('page') === '2',
+        `刷新后页码=${store.currentPage} 请求=${pathsMatching('/events/current')[0]}`,
+    )
+
+    // 打开的详情:翻到不含该事件的一页 → refreshNotice 如实标注;翻回 → 恢复。
+    store.reset()
+    store.configure({ config: mockConfig })
+    installMock()
+    await store.loadPanels({ mode: 'WARNING', range: '7d' })
+    const firstRow = store.view.rows[0]
+    store.openEvent(firstRow)
+    await store.setCurrentPage(2)
+    const noticeOnPageTwo = store.selectedEvent && store.selectedEvent.refreshNotice
+    await store.setCurrentPage(1)
+    const restored = store.selectedEvent
+    check(
+        'A6-open-detail-refresh-notice-preserved',
+        !!noticeOnPageTwo &&
+            /当前列表未返回该事件/.test(noticeOnPageTwo) &&
+            restored.code === firstRow.code &&
+            !restored.refreshNotice,
+        `第 2 页通知「${noticeOnPageTwo}」→ 回第 1 页恢复(notice=${restored.refreshNotice || '空'})`,
+    )
+}
+
+// ---- A7 迟到页响应被 token 作废(翻页竞态) ----
+{
+    installMock()
+    let releaseSlow = null
+    let slowServed = false
+    const marker = (name) => [{ ...eventItem(`WRN-${name}-1`), eventName: name }]
+    registerMockTransport('/events/current', async ({ path }) => {
+        REQUESTS.push(path)
+        const page = Number(queryOf(path).get('page') || 1)
+        if (page === 2 && !slowServed) {
+            slowServed = true
+            await new Promise((resolve) => {
+                releaseSlow = resolve
+            })
+            return currentEnvelope({ items: marker('SLOW'), page: 2, total: PAGE_SIZE + 4 })
+        }
+        // 守卫若失效会有第二次 page=2(夹回重取)——立即回一个可辨认的载荷,让断言干净地红。
+        if (page === 2) return currentEnvelope({ items: marker('SLOW-CLAMP'), page: 2, total: PAGE_SIZE + 4 })
+        if (page === 3) return currentEnvelope({ items: marker('FAST'), page: 3, total: PAGE_SIZE * 3 })
+        return currentEnvelope({ items: pagesOf(WARNINGS_14, 1), page: 1, total: PAGE_SIZE + 4 })
+    })
+
+    store.reset()
+    store.configure({ config: mockConfig })
+    await store.loadPanels({ mode: 'WARNING', range: '7d' })
+
+    const slow = store.setCurrentPage(2)
+    await new Promise((resolve) => setTimeout(resolve, 0))
+    const fast = store.setCurrentPage(3)
+    await fast
+    const afterFast = { page: store.currentPage, rows: store.view.rows.length, name: store.view.rows[0] && store.view.rows[0].name }
+    releaseSlow()
+    await slow
+    await new Promise((resolve) => setTimeout(resolve, 0))
+    check(
+        'A7-late-page-response-discarded-by-token',
+        afterFast.page === 3 &&
+            afterFast.rows === 1 &&
+            afterFast.name === 'FAST' &&
+            store.currentPage === 3 &&
+            store.view.rows.length === 1 &&
+            store.view.rows[0].name === 'FAST',
+        `快响应落定(page3/FAST) 后迟到的 page2 被丢弃(现状 page=${store.currentPage} name=${store.view.rows[0] && store.view.rows[0].name})`,
+    )
+}
+
+// ---- A8 越界页码夹回最后一页(数据变少) ----
+{
+    installMock()
+    registerMockTransport('/events/current', async ({ path }) => {
+        REQUESTS.push(path)
+        const page = Number(queryOf(path).get('page') || 1)
+        // 后端如实回显请求页与真实 totalPages=1(数据只剩一页)。
+        return currentEnvelope({ items: page === 1 ? pagesOf(WARNINGS_14.slice(0, 4), 1) : [], page, total: 4, totalPages: 1 })
+    })
+    store.reset()
+    store.configure({ config: mockConfig })
+    await store.loadPanels({ mode: 'WARNING', range: '7d' })
+    requcedClear()
+    await store.setCurrentPage(2)
+    const pages = pathsMatching('/events/current').map((path) => queryOf(path).get('page'))
+    check(
+        'A8-out-of-range-page-clamps-to-last',
+        store.currentPage === 1 &&
+            store.view.pagination.pageCount === 1 &&
+            pages.join(',') === '2,1',
+        `请求页码序列 ${pages.join('→')};夹回后 page=${store.currentPage}`,
+    )
+}
+
+// ================================================================ B. 历史事件单窗:服务端分页
+
+{
+    installMock()
+    store.reset()
+    store.configure({ config: mockConfig })
+    await store.loadPanels({ mode: 'WARNING', range: '7d' })
+
+    const historyPaths = pathsMatching('/events/history-analysis')
+    const historyData = store.sections.historyAnalysis.data
+    check(
+        'B1-single-window-uses-server-paging',
+        historyPaths.length === 1 &&
+            queryOf(historyPaths[0]).get('page') === '1' &&
+            // 契约字面量 10(同上:判据不跟着常量走)。
+            queryOf(historyPaths[0]).get('pageSize') === '10' &&
+            historyData.paging.mode === 'server' &&
+            historyData.paging.page === 1 &&
+            historyData.paging.pageCount === 2 &&
+            historyData.paging.total === PAGE_SIZE + 4 &&
+            historyData.events.length === PAGE_SIZE,
+        `单窗历史:${historyPaths[0]};paging=${JSON.stringify(historyData.paging)};行=${historyData.events.length}`,
+    )
+
+    requcedClear()
+    await store.setHistoryPage(2)
+    const pageTwo = store.sections.historyAnalysis.data
+    check(
+        'B2-history-page-flip-requests-page-two',
+        pathsMatching('/events/history-analysis').length === 1 &&
+            queryOf(pathsMatching('/events/history-analysis')[0]).get('page') === '2' &&
+            queryOf(pathsMatching('/events/history-analysis')[0]).get('pageSize') === String(PAGE_SIZE) &&
+            pathsMatching('/events/current').length === 0 &&
+            store.historyPage === 2 &&
+            pageTwo.paging.page === 2 &&
+            pageTwo.events.length === 4,
+        `历史第 2 页:${pathsMatching('/events/history-analysis')[0]};行=${pageTwo.events.length}`,
+    )
+
+    // 迟到历史页响应被 token 作废:慢 page2 在途时改范围(→ 多窗合并),迟到结果不落盘。
+    installMock()
+    let releaseHistory = null
+    registerMockTransport('/events/history-analysis', async ({ path }) => {
+        REQUESTS.push(path)
+        const params = queryOf(path)
+        if (params.get('page') === '2') {
+            await new Promise((resolve) => {
+                releaseHistory = resolve
+            })
+            return historyEnvelope({ events: [eventItem('HW-LATE')], page: 2, total: PAGE_SIZE + 4, startTime: params.get('startTime'), endTime: params.get('endTime') })
+        }
+        return historyEnvelope({ events: pagesOf(HISTORY_14, Number(params.get('page') || 1)), page: Number(params.get('page') || 1), total: PAGE_SIZE + 4, startTime: params.get('startTime'), endTime: params.get('endTime') })
+    })
+    store.reset()
+    store.configure({ config: mockConfig })
+    await store.loadPanels({ mode: 'WARNING', range: '7d' })
+    const slowHistory = store.setHistoryPage(2)
+    await new Promise((resolve) => setTimeout(resolve, 0))
+    const ranged = store.setRange('30d')
+    await ranged
+    const mergedMode = store.sections.historyAnalysis.data.paging.mode
+    releaseHistory()
+    await slowHistory
+    await new Promise((resolve) => setTimeout(resolve, 0))
+    check(
+        'B3-late-history-page-discarded-after-range-change',
+        mergedMode === 'merged' && store.sections.historyAnalysis.data.paging.mode === 'merged' && store.historyPage === 1,
+        `改范围后 paging.mode=${store.sections.historyAnalysis.data.paging.mode}(历史页码=${store.historyPage},迟到单窗页未落盘)`,
+    )
+}
+
+// ================================================================ C. 历史事件多窗:无全局页 → 合并集合分页 + 如实页脚
+
+{
+    installMock()
+    store.reset()
+    store.configure({ config: mockConfig })
+    await store.loadPanels({ mode: 'WARNING', range: '30d' })
+    const historyPaths = pathsMatching('/events/history-analysis')
+    const data = store.sections.historyAnalysis.data
+    const view = panelView('historyAnalysis', data, store.sections.historyAnalysis)
+    const block = view.blocks.find((item) => item.kind === 'list')
+
+    check(
+        'C1-multi-window-fetches-each-window-fully',
+        historyPaths.length === 2 &&
+            historyPaths.every((path) => queryOf(path).get('pageSize') === '1000' && queryOf(path).get('page') === '1') &&
+            new Set(historyPaths.map((path) => queryOf(path).get('startTime'))).size === 2,
+        `多窗请求 ${historyPaths.length} 个,窗口起点 ${historyPaths.map((path) => queryOf(path).get('startTime')).join(' / ')}`,
+    )
+    check(
+        'C2-multi-window-paging-is-merged-collection',
+        data.paging.mode === 'merged' &&
+            data.paging.windowCount === 2 &&
+            data.paging.loaded === PAGE_SIZE + 4 &&
+            data.paging.total === PAGE_SIZE + 4 &&
+            !('pagination' in data) === false ? data.pagination === null : true,
+        `paging=${JSON.stringify(data.paging)}`,
+    )
+    check(
+        'C3-merged-block-declares-frontend-paging-with-honest-footer',
+        block.paginate === true &&
+            !block.serverPage &&
+            block.rows.length === PAGE_SIZE + 4 &&
+            /已合并 2 个时间窗 · 已加载 14 条/.test(String(block.pageNote || '')),
+        `块 rows=${block.rows.length} pageNote「${block.pageNote}」`,
+    )
+    const secondPageRows = pagedRows(block.rows, 2)
+    check(
+        'C4-merged-collection-pages-by-ten',
+        secondPageRows.pageCount === 2 && secondPageRows.rows.length === 4 && secondPageRows.page === 2,
+        `合并集合第 2 页 ${secondPageRows.rows.length} 行 / 共 ${secondPageRows.pageCount} 页`,
+    )
+    check(
+        'C5-merged-page-scope-changes-with-range',
+        block.pageScope === `WARNING|30d`,
+        `pageScope=${block.pageScope}(范围变化即复位页码的触发键)`,
+    )
+
+    // 90 天范围:窗口数由 `historyWindows` 按 30 天切分得出(90 天含一个毫秒级尾窗,
+    // 现网口径就是 4 个窗——判据只看「如实报出实际窗数」,不写死 3)。
+    await store.setRange('90d')
+    const data90 = store.sections.historyAnalysis.data
+    const block90 = panelView('historyAnalysis', data90, store.sections.historyAnalysis).blocks.find((item) => item.kind === 'list')
+    const windows90 = data90.paging.windowCount
+    check(
+        'C6-ninety-day-range-reports-actual-window-count',
+        data90.paging.mode === 'merged' &&
+            windows90 >= 3 &&
+            new RegExp(`已合并 ${windows90} 个时间窗`).test(String(block90.pageNote || '')) &&
+            block90.pageScope === 'WARNING|90d',
+        `90 天 paging=${JSON.stringify(data90.paging)} pageNote「${block90.pageNote}」`,
+    )
+}
+
+// ================================================================ F. mock 同构(contractMocks)
+
+{
+    resetMockTransportsForTest()
+    registerCockpitMocks({ scenario: 'success', handlers: { registerMockTransport, resetMockTransportsForTest } })
+
+    const rawCurrent = await findMockTransport('/events/current')({ path: '/events/current?mode=WARNING&page=1&pageSize=10' })
+    const rawCurrentPageTwo = await findMockTransport('/events/current')({ path: '/events/current?mode=WARNING&page=2&pageSize=10' })
+    const rawHistory = await findMockTransport('/events/history-analysis')({ path: '/events/history-analysis?eventFamily=WARNING&page=1&pageSize=10' })
+    const rawHistoryPageTwo = await findMockTransport('/events/history-analysis')({ path: '/events/history-analysis?eventFamily=WARNING&page=2&pageSize=10' })
+
+    check(
+        'F1-mock-current-events-page-size-ten',
+        rawCurrent.sections.events.data.length === PAGE_SIZE &&
+            rawCurrentPageTwo.sections.events.data.length === 4 &&
+            rawCurrent.pagination.pageSize === PAGE_SIZE &&
+            rawCurrent.pagination.total === PAGE_SIZE + 4,
+        `当前事件样例 page1=${rawCurrent.sections.events.data.length} page2=${rawCurrentPageTwo.sections.events.data.length} total=${rawCurrent.pagination.total}`,
+    )
+    check(
+        'F2-mock-history-server-pages',
+        rawHistory.events.length === PAGE_SIZE &&
+            rawHistoryPageTwo.events.length === 4 &&
+            rawHistory.pagination.pageSize === PAGE_SIZE &&
+            rawHistory.pagination.total === PAGE_SIZE + 4,
+        `历史样例 page1=${rawHistory.events.length} page2=${rawHistoryPageTwo.events.length} total=${rawHistory.pagination.total}`,
+    )
+    check(
+        'F3-mock-history-event-shape-isomorphic-to-current',
+        JSON.stringify(Object.keys(rawHistory.events[0]).sort()) === JSON.stringify(Object.keys(rawCurrent.sections.events.data[0]).sort()),
+        '历史逐事件行与 events/current 的 EventItem 字段集合一致(mock 同构)',
+    )
+    check(
+        'F4-mock-history-total-equals-summary',
+        rawHistory.pagination.total === rawHistory.summary.eventCount,
+        `pagination.total=${rawHistory.pagination.total} summary.eventCount=${rawHistory.summary.eventCount}(同一份总数)`,
+    )
+
+    // 真实服务链路:单窗 pageSize=10 → 两页;30 天 → 两窗合并。
+    const window7 = rangeWindow(7)
+    const window30 = rangeWindow(30)
+    const single = await fetchEventHistory({ mode: 'WARNING', rangeType: 'LAST_7_DAYS', ...window7, page: 1, pageSize: PAGE_SIZE, config: mockConfig })
+    const singlePageTwo = await fetchEventHistory({ mode: 'WARNING', rangeType: 'LAST_7_DAYS', ...window7, page: 2, pageSize: PAGE_SIZE, config: mockConfig })
+    const merged = await fetchEventHistory({ mode: 'WARNING', rangeType: 'LAST_30_DAYS', ...window30, page: 1, pageSize: PAGE_SIZE, config: mockConfig })
+    check(
+        'F5-service-composes-server-and-merged-paging',
+        single.data.paging.mode === 'server' &&
+            single.data.paging.pageCount === 2 &&
+            singlePageTwo.data.paging.page === 2 &&
+            singlePageTwo.data.events.length === 4 &&
+            merged.data.paging.mode === 'merged' &&
+            merged.data.paging.windowCount === 2 &&
+            merged.data.paging.loaded === PAGE_SIZE + 4,
+        `单窗 paging=${JSON.stringify(single.data.paging)};多窗 paging=${JSON.stringify(merged.data.paging)}`,
+    )
+}
+
+// ================================================================ S. 静态守卫(单源/文案/无 12 条帽)
+
+{
+    const pane = read('src/modules/cockpit/components/EventListPane.vue')
+    const blocks = read('src/modules/cockpit/components/PanelBlocks.vue')
+    const storeSource = read('src/modules/cockpit/stores/eventStore.js')
+    const historyDomain = read('src/modules/cockpit/domain/warningResponsePanels.js')
+    const historyService = read('src/modules/cockpit/services/warningResponseApi.js')
+    const pager = read('src/modules/cockpit/components/PagerNav.vue')
+
+    check(
+        'S1-event-pane-uses-shared-pager-and-view-pagination',
+        /import PagerNav from '\.\/PagerNav\.vue'/.test(pane) &&
+            /view\.pagination\.pageCount > 1/.test(pane) &&
+            /:page="view\.pagination\.page"/.test(pane) &&
+            /@page="\(target\) => eventStore\.setCurrentPage\(target\)"/.test(pane),
+        '当前事件表接共享 PagerNav,页码/总页读视图(不自己算)',
+    )
+    check(
+        'S2-no-12-row-cap-left',
+        !/data-event-limited/.test(pane) &&
+            !/仅展示最新/.test(pane) &&
+            !/latestEventRows/.test(historyDomain) &&
+            !/slice\(0, 12\)/.test(historyDomain),
+        '撤下「仅展示最新 12 条」帽(当前事件表与历史事件目录层都没有 12 条截断)',
+    )
+    check(
+        'S3-store-owns-server-page-state-and-token',
+        /const currentPage = ref\(1\)/.test(storeSource) &&
+            /const historyPage = ref\(1\)/.test(storeSource) &&
+            /async function setCurrentPage/.test(storeSource) &&
+            /async function setHistoryPage/.test(storeSource) &&
+            /pageSize: PAGE_SIZE/.test(storeSource) &&
+            /mine !== token/.test(storeSource),
+        '页码是 store 视图状态;翻页只重取本分区;迟到页由 token 作废',
+    )
+    check(
+        'S4-history-domain-declares-two-paging-forms',
+        /serverPage: \{ page: paging\.page, pageCount: paging\.pageCount, total: paging\.total \}/.test(historyDomain) &&
+            /pageNote: `已合并 \$\{paging\.windowCount \|\| 0\} 个时间窗 · 已加载 \$\{paging\.loaded \|\| 0\} 条`/.test(historyDomain) &&
+            /paging\.mode === 'server'/.test(historyDomain),
+        '历史事件块按 data.paging 声明服务端页码/合并集合两种形态',
+    )
+    check(
+        'S5-service-pages-single-window-and-merges-multi-window',
+        /pageSize: singleWindow \? PAGE_SIZE : HISTORY_WINDOW_MAX_PAGE_SIZE/.test(historyService) &&
+            /mode: 'server'/.test(historyService) &&
+            /mode: 'merged'/.test(historyService) &&
+            /pagination: singleWindow \?/.test(historyService),
+        '服务端:单窗 page/pageSize=10、多窗每窗 1000 上限取全再合并;多窗不冒充全局 pagination',
+    )
+    check(
+        'S6-count-row-comes-from-single-pager-source',
+        /第 \{\{ page \}\}\/\{\{ pageCount \}\} 页 · 共 \{\{ total \}\} 条/.test(pager) &&
+            !/第 \{\{/.test(pane) &&
+            !/共 \{\{/.test(blocks),
+        '计数行只在 PagerNav 一份;组件不另写「第 X/Y 页 · 共 N 条」',
+    )
+    check(
+        'S7-no-backend-param-names-in-ui-copy',
+        !/pageSize/.test(stripComments(pane)) && !/pageSize/.test(stripComments(blocks)) && !/>page=/.test(stripComments(pane)),
+        '界面文案不暴露 page/pageSize 参数名(条文 10)',
+    )
+    check(
+        'S8-page-arithmetic-single-source',
+        /export function serverPageView/.test(read('src/modules/cockpit/domain/pagination.js')) &&
+            /export function pagedRows/.test(read('src/modules/cockpit/domain/pagination.js')) &&
+            !/Math\.(ceil|trunc)\(/.test(pane) &&
+            !/Math\.(ceil|trunc)\(/.test(blocks),
+        '翻页算术只在 domain/pagination.js;组件零 Math.ceil/trunc',
+    )
+}
+
+// ================================================================ R. 真实渲染(SFC 编译 + 自定义渲染器)
+
+registerHooks({
+    resolve(specifier, context, nextResolve) {
+        const resolved = nextResolve(specifier, context)
+        const parent = context.parentURL || ''
+        if (parent.endsWith('.vue?client') && resolved.url.endsWith('.vue')) {
+            return { url: `${resolved.url}?client`, format: 'module', shortCircuit: true }
+        }
+        return resolved
+    },
+    load(url, context, nextLoad) {
+        const isClient = url.endsWith('.vue?client')
+        if (url.endsWith('.vue') || isClient) {
+            const path = isClient ? url.slice(0, -'?client'.length) : url
+            const source = readFileSync(new URL(path), 'utf8')
+            const { descriptor } = parse(source, { filename: path })
+            const script = compileScript(descriptor, { id: 't61', genDefaultAs: '__sfc__' })
+            const template = compileTemplate({
+                source: descriptor.template.content,
+                filename: path,
+                id: 't61',
+                ssr: !isClient,
+                compilerOptions: { bindingMetadata: script.bindings },
+            })
+            assert.deepEqual(template.errors, [], `${path} template`)
+            const tail = isClient ? '__sfc__.render = render; export default __sfc__;' : '__sfc__.ssrRender = ssrRender; export default __sfc__;'
+            return { format: 'module', shortCircuit: true, source: `${script.content}\n${template.code}\n${tail}` }
+        }
+        return nextLoad(url, context)
+    },
+})
+
+const { default: ClientEventListPane } = await import(`${SRC}components/EventListPane.vue?client`)
+const { default: ClientPanelBlocks } = await import(`${SRC}components/PanelBlocks.vue?client`)
+
+const nodeOps = {
+    createElement: (tag) => ({ tag, props: {}, children: [], parent: null, text: '', style: {}, addEventListener() {}, removeEventListener() {} }),
+    createText: (text) => ({ tag: '#text', text, children: [], parent: null, props: {}, style: {}, addEventListener() {}, removeEventListener() {} }),
+    createComment: (text) => ({ tag: '#comment', text, children: [], parent: null, props: {}, style: {}, addEventListener() {}, removeEventListener() {} }),
+    setText: (node, text) => { node.text = text },
+    setElementText: (node, text) => { node.text = text; node.children = [] },
+    insert: (child, parent, anchor) => {
+        if (child.parent) child.parent.children = child.parent.children.filter((c) => c !== child)
+        child.parent = parent
+        if (!parent) return
+        if (anchor) { const i = parent.children.indexOf(anchor); parent.children.splice(i < 0 ? parent.children.length : i, 0, child) }
+        else parent.children.push(child)
+    },
+    remove: (child) => { if (child.parent) child.parent.children = child.parent.children.filter((c) => c !== child); child.parent = null },
+    parentNode: (node) => node.parent,
+    nextSibling: (node) => (node.parent ? node.parent.children[node.parent.children.indexOf(node) + 1] || null : null),
+    querySelector: () => null,
+    setScopeId: () => {},
+    patchProp: (el, key, _prev, next) => { el.props[key] = next },
+}
+const findAll = (node, predicate, acc = []) => {
+    if (!node) return acc
+    if (predicate(node)) acc.push(node)
+    for (const child of node.children || []) findAll(child, predicate, acc)
+    return acc
+}
+const findNode = (node, predicate) => {
+    if (!node) return null
+    if (predicate(node)) return node
+    for (const child of node.children || []) {
+        const hit = findNode(child, predicate)
+        if (hit) return hit
+    }
+    return null
+}
+const textOf = (node) => {
+    if (!node) return ''
+    const own = node.tag === '#text' ? node.text : typeof node.text === 'string' ? node.text : ''
+    return own + (node.children || []).map(textOf).join('')
+}
+const byAttr = (name) => (node) => !!node.props && name in node.props
+const mount = (render) => {
+    const app = createRenderer(nodeOps).createApp({ render })
+    app.config.warnHandler = () => {}
+    app.use(pinia)
+    const container = nodeOps.createElement('#root')
+    app.mount(container)
+    return { container, unmount: () => app.unmount() }
+}
+const click = async (node) => {
+    assert.ok(node && typeof node.props.onClick === 'function', '节点不可点')
+    node.props.onClick(new Proxy({}, { get: () => undefined }))
+    await flush()
+}
+
+// ---- R1/R2 当前事件表:一页有控件、翻页走 store 请求 ----
+{
+    installMock()
+    store.reset()
+    store.configure({ config: mockConfig })
+    await store.loadPanels({ mode: 'WARNING', range: '7d' })
+    const { container, unmount } = mount(() => h(ClientEventListPane))
+    const rows = findAll(container, byAttr('data-event-row'))
+    const pagerWrap = findNode(container, byAttr('data-event-pager'))
+    const state = textOf(findNode(container, byAttr('data-page-state')))
+    check(
+        'R1-current-pane-renders-page-and-count-row',
+        rows.length === PAGE_SIZE && !!pagerWrap && /第 1\/2 页 · 共 14 条/.test(state),
+        `行=${rows.length} 计数行「${state}」`,
+    )
+    check(
+        'R2-current-pane-removed-12-cap-note',
+        findAll(container, byAttr('data-event-limited')).length === 0,
+        '「仅展示最新 12 条」节点已不存在',
+    )
+    await click(findNode(pagerWrap, byAttr('data-page-next')))
+    const rowsAfter = findAll(container, byAttr('data-event-row'))
+    const stateAfter = textOf(findNode(container, byAttr('data-page-state')))
+    check(
+        'R3-clicking-next-requests-second-page',
+        rowsAfter.length === 4 && /第 2\/2 页/.test(stateAfter) && store.currentPage === 2,
+        `点下一页后行=${rowsAfter.length} 计数行「${stateAfter}」`,
+    )
+    unmount()
+}
+
+// ---- R4 一页不画控件(服务端分页) ----
+{
+    store.reset()
+    store.configure({ config: mockConfig })
+    installMock()
+    registerMockTransport('/events/current', async ({ path }) => {
+        REQUESTS.push(path)
+        return currentEnvelope({ items: pagesOf(WARNINGS_14.slice(0, 4), 1), page: 1, total: 4 })
+    })
+    await store.loadPanels({ mode: 'WARNING', range: '7d' })
+    const { container, unmount } = mount(() => h(ClientEventListPane))
+    check(
+        'R4-single-page-draws-no-pager',
+        findAll(container, byAttr('data-event-row')).length === 4 && findAll(container, byAttr('data-event-pager')).length === 0,
+        '一页(4 条)时整块 pager 不渲染(条文 4)',
+    )
+    unmount()
+}
+
+// ---- R5/R6 历史事件:服务端页码块(翻页 emit 给 store)+多窗合并块(页脚如实口径) ----
+{
+    installMock()
+    store.reset()
+    store.configure({ config: mockConfig })
+    await store.loadPanels({ mode: 'WARNING', range: '7d' })
+    const serverView = panelView('historyAnalysis', store.sections.historyAnalysis.data, store.sections.historyAnalysis)
+    const emitted = []
+    const serverApp = mount(() => h(ClientPanelBlocks, { view: serverView, onListPage: (payload) => emitted.push(payload) }))
+    const serverPager = findNode(serverApp.container, byAttr('data-pager'))
+    const serverState = textOf(findNode(serverApp.container, byAttr('data-page-state')))
+    check(
+        'R5-history-server-block-renders-shared-pager',
+        !!serverPager &&
+            /第 1\/2 页 · 共 14 条/.test(serverState) &&
+            findAll(serverApp.container, byAttr('data-item')).length === PAGE_SIZE,
+        `服务端分页块:计数行「${serverState}」、行=${findAll(serverApp.container, byAttr('data-item')).length}`,
+    )
+    await click(findNode(serverPager, byAttr('data-page-next')))
+    check(
+        'R6-history-server-paging-asks-store-for-next-page',
+        emitted.length === 1 && emitted[0].page === 2 && /历史事件/.test(String(emitted[0].label || '')),
+        `点下一页 emit ${JSON.stringify(emitted[0])}(块内不自己切窗口)`,
+    )
+    serverApp.unmount()
+
+    await store.setRange('30d')
+    const mergedView = panelView('historyAnalysis', store.sections.historyAnalysis.data, store.sections.historyAnalysis)
+    const mergedEmitted = []
+    const mergedApp = mount(() => h(ClientPanelBlocks, { view: mergedView, onListPage: (payload) => mergedEmitted.push(payload) }))
+    const note = findNode(mergedApp.container, byAttr('data-list-page-note'))
+    const mergedRowsFirst = findAll(mergedApp.container, byAttr('data-item'))
+    const mergedStateFirst = textOf(findNode(mergedApp.container, byAttr('data-page-state')))
+    check(
+        'R7-merged-block-renders-honest-footer-and-first-page',
+        mergedRowsFirst.length === PAGE_SIZE &&
+            /第 1\/2 页 · 共 14 条/.test(mergedStateFirst) &&
+            /已合并 2 个时间窗 · 已加载 14 条/.test(textOf(note)),
+        `合并块:行=${mergedRowsFirst.length} 计数行「${mergedStateFirst}」页脚「${textOf(note)}」`,
+    )
+    await click(findNode(mergedApp.container, (node) => String(node.props && node.props['data-page-number']) === '2'))
+    const mergedRowsSecond = findAll(mergedApp.container, byAttr('data-item'))
+    const mergedStateSecond = textOf(findNode(mergedApp.container, byAttr('data-page-state')))
+    check(
+        'R8-merged-block-pages-loaded-collection-locally',
+        mergedRowsSecond.length === 4 && /第 2\/2 页/.test(mergedStateSecond) && mergedEmitted.length === 0,
+        `合并块第 2 页行=${mergedRowsSecond.length}、零 list-page emit(窗口在渲染层)`,
+    )
+    mergedApp.unmount()
+
+    // 合并集合一页不到:控件不画,但「已合并 N 个时间窗 · 已加载 M 条」的口径说明仍要在。
+    const fourEvents = HISTORY_14.slice(0, 4).map((raw) => toEventItem(raw))
+    const onePageView = historyAnalysisPanel(
+        { mode: 'WARNING', range: { key: '30d', type: 'LAST_30_DAYS', start: '', end: '' }, trend: [], events: fourEvents, paging: { mode: 'merged', windowCount: 2, loaded: 4, total: 4 } },
+        { ...section(SECTION_STATE.SUCCESS, { data: null }), loaded: true },
+    )
+    const onePageApp = mount(() => h(ClientPanelBlocks, { view: onePageView }))
+    check(
+        'R9-merged-note-survives-without-pager',
+        findAll(onePageApp.container, byAttr('data-pager')).length === 0 &&
+            /已合并 2 个时间窗 · 已加载 4 条/.test(textOf(findNode(onePageApp.container, byAttr('data-list-page-note')))),
+        '一页不到时不画控件,但合并口径说明仍出现(口径不是控件)',
+    )
+    onePageApp.unmount()
+}
+
+// ================================================================ 结果
+
+console.log('')
+console.log(`T61 事件清单服务端分页闸门:${passed}/${passed + failed} 项通过。`)
+if (failed) {
+    console.error(`未通过 ${failed} 项。`)
+    process.exit(1)
+}
+console.log('T61 全部通过。')

+ 210 - 0
.scratch/cockpit-real-data-wayfinding/verify/t61-mutations.mjs

@@ -0,0 +1,210 @@
+/**
+ * 票 61 闸门变异测试:证明 t61 的每条关键断言**抓得住它要防的缺陷**(t59/t60 同一纪律)。
+ *
+ * 做法:对源文件做**单点**变异注入 → 跑 t61 → 断言指定断言转红 → 按字节还原并校验一致。
+ *
+ * 十二条变异对应本票的真实风险:
+ *   M1  页大小 10 → 20                    → A1 红(服务端页码分页本身被吃掉)
+ *   M2  翻页请求的 token 守卫失效          → A7 红(迟到页响应落进新页码)
+ *   M3  历史单窗页大小退 100               → B1 红(历史单窗不再按每页 10 取)
+ *   M4  多窗页脚(已合并 N 个时间窗)删除   → C3 红(如实口径被吃掉)
+ *   M5  服务端分页块元数据删除              → R5 红(历史单窗不画 pager)
+ *   M6  多窗块冒充服务端全局页              → C3 红(伪造全局页码)
+ *   M7  一页也画控件                       → R4 红(条文 4 失守)
+ *   M8  服务端翻页不走 store               → R6 红(翻页没有请求,页不真翻)
+ *   M9  条件变化不复位页码                  → A4 红(条文 5/6 失守)
+ *   M10 历史事件重戴 12 条帽                → C3 红(合并集合被截断)
+ *   M11 mock 历史页不再按 page 切片          → F2 红(mock 同构失守)
+ *   M12 计数行暴露后端参数名                → S6 红(条文 10 失守)
+ *
+ * 运行:node .scratch/cockpit-real-data-wayfinding/verify/t61-mutations.mjs
+ */
+import assert from 'node:assert/strict'
+import { closeSync, mkdtempSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import { spawnSync } from 'node:child_process'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
+const GATE = fileURLToPath(new URL('t61-event-server-paging.mjs', import.meta.url))
+const srcPath = (rel) => `${repoRoot}src/modules/cockpit/${rel}`
+
+/** 用文件重定向取子进程输出(Windows 沙箱下管道捕获是文档化边界,见 t60 注释)。 */
+const reportDir = mkdtempSync(join(tmpdir(), 't61-mutations-'))
+function runGate() {
+    const outPath = join(reportDir, 'stdout.txt')
+    const errPath = join(reportDir, 'stderr.txt')
+    const out = openSync(outPath, 'w')
+    const err = openSync(errPath, 'w')
+    let status = 0
+    try {
+        status = spawnSync(process.execPath, [GATE], { stdio: ['ignore', out, err] }).status
+    } finally {
+        closeSync(out)
+        closeSync(err)
+    }
+    return { output: `${readFileSync(outPath, 'utf8')}${readFileSync(errPath, 'utf8')}`, status }
+}
+
+function failingNames(output) {
+    return [...output.matchAll(/^FAIL ([^\s:]+)/gm)].map((match) => match[1])
+}
+
+let injected = 0
+let proved = 0
+function mutate(label, file, from, to, expectRed) {
+    const path = srcPath(file)
+    const original = readFileSync(path, 'utf8')
+    const candidates = [from, from.replace(/\n/g, '\r\n')]
+    const needle = candidates.find((candidate) => original.includes(candidate))
+    if (!needle) {
+        console.error(`FAIL ${label}: 变异源未命中(文件形状已变,需人工更新变异脚本)`)
+        injected += 1
+        return
+    }
+    injected += 1
+    writeFileSync(path, original.replace(needle, to), 'utf8')
+    let caught = false
+    let how = ''
+    try {
+        const { output, status } = runGate()
+        const red = failingNames(output)
+        if (red.some((name) => name === expectRed || name.startsWith(expectRed))) {
+            caught = true
+            how = `${expectRed} 转红`
+        } else if (status !== 0 && /does not provide an export|SyntaxError|Cannot find (module|export)/.test(output)) {
+            caught = true
+            how = '闸门整体加载失败(导出缺失)— 崩溃式抓住'
+        } else if (status !== 0) {
+            how = `退出码 ${status} 但目标断言未红(红项:${red.join(', ') || '无'})— 判据不明,按未抓住记`
+        } else {
+            how = '未转红(假绿!闸门仍全绿)'
+        }
+        console.log(`${caught ? 'PASS' : 'FAIL'}  ${label} → ${how}`)
+        if (caught) proved += 1
+    } finally {
+        writeFileSync(path, original, 'utf8')
+        assert.equal(readFileSync(path, 'utf8'), original, `${label}: 还原失败`)
+    }
+}
+
+// M1 页大小 10 → 20
+mutate('M1-page-size-back-to-20', 'domain/pagination.js', 'export const PAGE_SIZE = 10', 'export const PAGE_SIZE = 20', 'A1')
+
+// M2 翻页请求的 token 守卫失效(loadPage 的第一处守卫;单行锚点,命中即 loadPage)
+mutate(
+    'M2-late-page-token-guard-disabled',
+    'stores/eventStore.js',
+    '        if (mine !== token) return null',
+    '        if (mine !== token && false) return null',
+    'A7',
+)
+
+// M3 历史单窗页大小退 100
+mutate(
+    'M3-history-single-window-page-size-100',
+    'services/warningResponseApi.js',
+    'pageSize: singleWindow ? PAGE_SIZE : HISTORY_WINDOW_MAX_PAGE_SIZE',
+    'pageSize: singleWindow ? 100 : HISTORY_WINDOW_MAX_PAGE_SIZE',
+    'B1',
+)
+
+// M4 多窗页脚口径删除
+mutate('M4-merged-footer-note-removed', 'domain/warningResponsePanels.js', '        pageNote: `已合并 ${paging.windowCount || 0} 个时间窗 · 已加载 ${paging.loaded || 0} 条`,\n', '', 'C3')
+
+// M5 服务端分页块元数据删除
+mutate(
+    'M5-history-server-page-metadata-removed',
+    'domain/warningResponsePanels.js',
+    `        return listBlock('历史事件', rows, emptyText, {
+            serverPage: { page: paging.page, pageCount: paging.pageCount, total: paging.total },
+        })`,
+    `        return listBlock('历史事件', rows, emptyText)`,
+    'R5',
+)
+
+// M6 多窗块冒充服务端全局页(把前端集合分页换成伪造的全局页码)
+mutate(
+    'M6-merged-block-fakes-global-page',
+    'domain/warningResponsePanels.js',
+    `    return listBlock('历史事件', rows, emptyText, {
+        paginate: true,`,
+    `    return listBlock('历史事件', rows, emptyText, {
+        serverPage: { page: 1, pageCount: 2, total: paging.loaded },
+        paginate: true,`,
+    'C3',
+)
+
+// M7 一页也画控件
+mutate(
+    'M7-single-page-still-draws-pager',
+    'components/EventListPane.vue',
+    'v-if="view.pagination && view.pagination.pageCount > 1" class="event-list-pane__pager"',
+    'v-if="view.pagination" class="event-list-pane__pager"',
+    'R4',
+)
+
+// M8 服务端翻页不走 store(块内吞掉翻页,不发 list-page)
+mutate(
+    'M8-server-page-flip-not-forwarded',
+    'components/PanelBlocks.vue',
+    `    if (block.serverPage) {
+        emit('list-page', { label: block.label, page: Number(target) || 1 })
+        return
+    }`,
+    `    if (block.serverPage && false) {
+        emit('list-page', { label: block.label, page: Number(target) || 1 })
+        return
+    }`,
+    'R6',
+)
+
+// M9 条件变化不复位页码
+mutate(
+    'M9-condition-change-keeps-page',
+    'stores/eventStore.js',
+    'if (CONDITION_KEYS.some((key) => before[key] !== after[key])) {',
+    'if (CONDITION_KEYS.some((key) => before[key] !== after[key]) && false) {',
+    'A4',
+)
+
+// M10 历史事件重戴 12 条帽
+mutate(
+    'M10-history-events-re-capped-at-12',
+    'domain/warningResponsePanels.js',
+    '    const rows = events.map((item) => ({',
+    '    const rows = events.slice(0, 12).map((item) => ({',
+    'C3',
+)
+
+// M11 mock 历史页不再按 page 切片(锚点必须落在 historyAnalysisPayload 里:
+// 同样的 `const page = ...` 也出现在 currentEventsPayload,单行锚点会命中错的那份)
+mutate(
+    'M11-mock-history-ignores-page',
+    'mocks/contractMocks.js',
+    `    const events = scenario === 'empty' ? [] : sortEventRows(source.items)
+    const page = Number(query && query.page) > 0 ? Number(query.page) : 1`,
+    `    const events = scenario === 'empty' ? [] : sortEventRows(source.items)
+    const page = 1`,
+    'F2',
+)
+
+// M12 计数行暴露后端参数名
+mutate(
+    'M12-count-row-exposes-param-names',
+    'components/PagerNav.vue',
+    '第 {{ page }}/{{ pageCount }} 页 · 共 {{ total }} 条',
+    '第 {{ page }}/{{ pageCount }} 页 · pageSize={{ total }}',
+    'S6',
+)
+
+rmSync(reportDir, { recursive: true, force: true })
+
+console.log('')
+console.log(`T61 变异:${proved}/${injected} 条被闸门抓住。`)
+if (proved !== injected) {
+    console.error('有变异未被抓住(闸门有假绿风险)。')
+    process.exit(1)
+}
+console.log('T61 变异全部打红并按字节还原。')

+ 363 - 0
.scratch/cockpit-real-data-wayfinding/verify/t65-mutations.mjs

@@ -0,0 +1,363 @@
+/**
+ * 票 65 闸门变异测试:证明 t65 的每条关键断言**抓得住它要防的缺陷**(t46/t59/t64 同一纪律)。
+ *
+ * 做法:对源文件做**单点**变异注入 → 跑 t65 → 断言指定断言转红 → 按字节还原并校验一致。
+ * 每处变异对应票 65 里的一个真实风险:
+ *   M1  预警族仍画工单口径 KPI      → A3/A4/A7c 必须红(本票要防的**本体缺陷**)
+ *   M2  预警族仍画工单状态分布块    → A4 必须红(KPI 改了但图表没改 = 半分流)
+ *   M3  预警族用 `handlingCounts` 冒充预警读数 → A2/A3 必须红(把工单派生数画成预警数)
+ *   M4  等级分布改「全部铺 0」      → A5 必须红(真实计数被抹成 0)
+ *   M5  发生趋势不按日合并          → A6 必须红(169 个小时桶直接铺折线)
+ *   M6  报警族被一并改成预警口径    → A7 必须红(分流做成「两族都一样」)
+ *   M7  标题不吃切换词表            → B1/B2/D4 必须红(标题不随模式换)
+ *   M8  口径说明两族共用一张        → B3 必须红(预警族的缺口说明被报警口径顶掉)
+ *   M9  分流只看切换参数不看回显    → C1 必须红(回显优先级失守)
+ *   M10 分流只看回显不看切换参数    → C2 必须红(mock/旧载荷不分流)
+ *   M11 面板组件不传 extra.mode     → E1/D1 必须红(接线断链:预警模式仍画工单口径)
+ *   M12 外壳标题回到静态 labelForPanel → E2/D4 必须红(外框标题不换)
+ *   M13 外壳另写一份标题字面量      → E3 必须红(第二张词表长出)
+ *   M14 mock 把预警族工单字段藏掉   → F2 必须红(契约缺口被 mock 掩盖)
+ *   M15 缺失读数补 0                → G1/G2 必须红(票 22/42 纪律失守)
+ *
+ * 运行:node .scratch/cockpit-real-data-wayfinding/verify/t65-mutations.mjs
+ */
+import assert from 'node:assert/strict'
+import { closeSync, mkdtempSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import { spawnSync } from 'node:child_process'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
+const GATE = fileURLToPath(new URL('t65-warning-efficiency-family-split.mjs', import.meta.url))
+const srcPath = (rel) => `${repoRoot}src/modules/cockpit/${rel}`
+
+/**
+ * 跑闸门并取回输出。刻意用**文件重定向**而不是管道捕获(t46/t59/t64 实测:
+ * Windows 沙箱下捕获子进程 stdio 的命名管道是文档化边界(EPERM),
+ * 管道版拿回空输出 → 变异全部「未转红」的假信号)。
+ */
+const reportDir = mkdtempSync(join(tmpdir(), 't65-mutations-'))
+function runGate() {
+    const outPath = join(reportDir, 'stdout.txt')
+    const errPath = join(reportDir, 'stderr.txt')
+    const out = openSync(outPath, 'w')
+    const err = openSync(errPath, 'w')
+    let status = 0
+    try {
+        status = spawnSync(process.execPath, [GATE], { stdio: ['ignore', out, err] }).status
+    } finally {
+        closeSync(out)
+        closeSync(err)
+    }
+    return { output: `${readFileSync(outPath, 'utf8')}${readFileSync(errPath, 'utf8')}`, status }
+}
+
+function failingNames(output) {
+    return [...output.matchAll(/^FAIL ([^:]+):/gm)].map((match) => match[1])
+}
+
+let injected = 0
+let proved = 0
+function mutate(label, file, from, to, expectRed) {
+    const path = srcPath(file)
+    const original = readFileSync(path, 'utf8')
+    // 变异源文本按 CRLF 归一后再匹配(票 37 实测踩过:行尾不一致时 replace 静默不命中)。
+    const candidates = [from, from.replace(/\n/g, '\r\n')]
+    const needle = candidates.find((candidate) => original.includes(candidate))
+    if (!needle) {
+        console.error(`FAIL ${label}: 变异源未命中(文件形状已变,需人工更新变异脚本)`)
+        injected += 1
+        return
+    }
+    injected += 1
+    writeFileSync(path, original.replace(needle, to), 'utf8')
+    runAndJudge(label, original, path, expectRed)
+}
+
+function runAndJudge(label, original, path, expectRed) {
+    let caught = false
+    let how = ''
+    try {
+        const { output, status } = runGate()
+        const red = failingNames(output)
+        if (red.includes(expectRed)) {
+            caught = true
+            how = `${expectRed} 转红`
+        } else if (status !== 0 && /does not provide an export|SyntaxError|Cannot find (module|export)/.test(output)) {
+            caught = true
+            how = '闸门整体加载失败(导出缺失/语法错)— 崩溃式抓住'
+        } else if (status !== 0) {
+            how = `退出码 ${status} 但目标断言未红(红项:${red.join(', ') || '无'})— 判据不明,按未抓住记`
+        } else {
+            how = '未转红(假绿!闸门仍全绿)'
+        }
+        console.log(`${caught ? 'PASS' : 'FAIL'}  ${label} → ${how}`)
+        if (caught) proved += 1
+    } finally {
+        writeFileSync(path, original, 'utf8')
+        assert.equal(readFileSync(path, 'utf8'), original, `${label}: 还原失败`)
+    }
+}
+
+// ---- 本体:预警族必须不画工单口径(两面:KPI 与图表)----
+
+mutate(
+    'M1-warning-family-still-draws-work-order-kpis',
+    'domain/warningResponsePanels.js',
+    `    if (family === EVENT_FAMILY.WARNING) {`,
+    `    if (family === EVENT_FAMILY.WARNING && false) {`,
+    'A4 预警模式不画工单口径(处置率/工单状态分布/结案时长)',
+)
+
+// M1b 只改 KPI 不改图表 = 半分流(KPI 对了、堆叠条还在画工单状态)。
+//      与 M1 分开注入:M1 会把整支预警分支跳过,看不出「只改一半也算失守」。
+mutate(
+    'M1b-warning-family-hides-work-order-kpis-but-keeps-stacked',
+    'domain/warningResponsePanels.js',
+    `            warningEfficiencyKpis(current),
+            barsBlock('预警等级分布', current.levelDistribution || []),`,
+    `            alarmEfficiencyKpis(current),
+            barsBlock('预警等级分布', current.levelDistribution || []),`,
+    'A4 预警模式不画工单口径(处置率/工单状态分布/结案时长)',
+)
+
+mutate(
+    'M2-warning-family-keeps-work-order-stacked-block',
+    'domain/warningResponsePanels.js',
+    `            barsBlock('预警等级分布', current.levelDistribution || []),
+            trendBlock(trend.length > 48 ? '预警发生趋势 · 每日合计' : '预警发生趋势', trend, { chart: 'line' }),`,
+    `            barsBlock('预警等级分布', current.levelDistribution || []),
+            stackedBlock('工单状态分布', current.workOrderStates || []),
+            trendBlock(trend.length > 48 ? '预警发生趋势 · 每日合计' : '预警发生趋势', trend, { chart: 'line' }),`,
+    'A4 预警模式不画工单口径(处置率/工单状态分布/结案时长)',
+)
+
+// M3 票面点名的「假读数」:预警族的关联计数改用**工单派生**字段(handled/cancelled),
+//    这正是票面 Acceptance 第 2 条禁止的「用一个不存在的对象统计预警」
+//    (实测预警族 handled=cancelled=0,而真实 missing=全部 —— 一画就把 5 条预警说成 0 条未关联)。
+mutate(
+    'M3-work-order-derived-counts-painted-as-warning',
+    'domain/warningResponsePanels.js',
+    `        linkedWorkOrderCount: numberish(summary.linkedWorkOrderCount === undefined ? reader.raw.linkedWorkOrderCount : summary.linkedWorkOrderCount),
+        missingWorkOrderCount: numberish(summary.missingWorkOrderCount === undefined ? reader.raw.missingWorkOrderCount : summary.missingWorkOrderCount),`,
+    `        linkedWorkOrderCount: numberish(summary.handledCount),
+        missingWorkOrderCount: numberish(summary.cancelledCount),`,
+    'A2 预警族工单关联实况是真实计数(预警不挂工单:0 关联 / 全部未关联)',
+)
+
+// M4 等级分布退化成「四级全铺 0」:看上去像合规的空面板,实际把真实计数抹掉。
+mutate(
+    'M4-level-distribution-zeroed',
+    'domain/warningResponsePanels.js',
+    `        count: events.filter((item) => Number(item && item.level) === level).length,`,
+    `        count: 0,`,
+    'A5 预警等级分布逐级等于真实事件行统计',
+)
+
+// M5 发生趋势不按日合并:169 个小时桶直接铺到折线上(票 61 的历史趋势合并口径被绕过)。
+mutate(
+    'M5-event-trend-not-merged-by-day',
+    'domain/warningResponsePanels.js',
+    `        const trend = historyTrendRows(current.eventTrend || [])`,
+    `        const trend = trendRows(current.eventTrend || [])`,
+    'A6 预警发生趋势按日合并(小时桶→天,不重采样、不补点)',
+)
+
+// M6 分流做过头:报警族也被改成预警口径(「两族一样」= 另一种没分流)。
+mutate(
+    'M6-alarm-family-also-drawn-as-warning',
+    'domain/warningResponsePanels.js',
+    `    return sectionView('responseEfficiency', section, [
+        alarmEfficiencyKpis(current),`,
+    `    return sectionView('responseEfficiency', section, [
+        ...(family === EVENT_FAMILY.ALARM ? warningEfficiencyKpis(current).items.map((item) => item) : alarmEfficiencyKpis(current).items).length ? [] : [],`,
+    'A7 报警族保留工单口径(处置率/响应与关闭时长/工单状态分布)',
+)
+
+// ---- 词表与口径说明 ----
+
+mutate(
+    'M7-title-ignores-switch-word-table',
+    'domain/warningResponsePanels.js',
+    '    return `${eventListSwitchView(mode).label}效率`',
+    "    return '预警效率'",
+    'B1 面板标题复用事件族单一词表(预警/报警 + 「效率」)',
+)
+
+mutate(
+    'M8-gap-note-shared-across-families',
+    'domain/warningResponsePanels.js',
+    `            note: WARNING_EFFICIENCY_GAP,`,
+    `            note: ALARM_EFFICIENCY_BASIS,`,
+    'B3 口径说明随族换且各自如实',
+)
+
+// ---- 分流依据:回显与切换参数的优先级 ----
+
+mutate(
+    'M9-split-ignores-backend-echo',
+    'domain/warningResponsePanels.js',
+    `    const family = current.family || (extra && extra.mode) || EVENT_FAMILY.ALARM`,
+    `    const family = (extra && extra.mode) || current.family || EVENT_FAMILY.ALARM`,
+    'C1 data.family(后端回显)优先于 extra.mode(切换参数)',
+)
+
+mutate(
+    'M10-split-ignores-switch-param',
+    'domain/warningResponsePanels.js',
+    `    const family = current.family || (extra && extra.mode) || EVENT_FAMILY.ALARM`,
+    `    const family = current.family || EVENT_FAMILY.ALARM`,
+    'C2 回显缺失时按切换参数分流(extra.mode=WARNING → 预警口径)',
+)
+
+// ---- 接线断链 ----
+
+mutate(
+    'M11-pane-does-not-pass-mode',
+    'components/WarningResponsePane.vue',
+    // 票 65 追加需求后第四参扩成多行对象,变异点改为「整段 extra 不传」——
+    // 这与原来的语义完全一致(接线断链),只是不再依赖单行字面量。
+    `    return panelView(props.panelId, current.data, current, {
+        mode: eventStore.mode,
+        workOrderList: eventStore.sections.workOrderList,
+    })`,
+    `    return panelView(props.panelId, current.data, current)`,
+    'E1 面板组件把当前切换族交给 domain(否则预警接线永远落回报警口径)',
+)
+
+mutate(
+    'M12-shell-title-back-to-static-label',
+    'CockpitShell.vue',
+    `    if (panelId === 'responseEfficiency') return responseEfficiencyTitle(eventStore.mode)`,
+    `    if (panelId === 'responseEfficiency') return labelForPanel(panelId)`,
+    'E2 外壳面板标题走 domain 单源(responseEfficiencyTitle),不另起一张词表',
+)
+
+mutate(
+    'M13-shell-grows-second-title-literal',
+    'CockpitShell.vue',
+    `function panelTitle(panelId) {`,
+    `function responseEfficiencyTitle2(mode) { return mode === 'ALARM' ? '报警效率' : '预警效率' }\nfunction panelTitle(panelId) {`,
+    'E3 预警/报警效率标题字面量只存在于 domain(外壳零第二份)',
+)
+
+// ---- mock 同构:不许把契约缺口藏起来 ----
+
+mutate(
+    'M14-mock-hides-work-order-fields-on-warning',
+    'mocks/contractMocks.js',
+    `            handlingEfficiency: { value: blank ? 0 : numerator / denominator, numerator, denominator, state: denominator ? 'AVAILABLE' : 'EMPTY', reason: denominator ? 'READY' : 'NO_ELIGIBLE_WORK_ORDERS', basis: 'CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS' },`,
+    `            handlingEfficiency: source.mode === 'WARNING' ? null : { value: blank ? 0 : numerator / denominator, numerator, denominator, state: denominator ? 'AVAILABLE' : 'EMPTY', reason: denominator ? 'READY' : 'NO_ELIGIBLE_WORK_ORDERS', basis: 'CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS' },`,
+    'F2 mock 两族仍保留工单口径字段(契约现状,不伪装成已分流)',
+)
+
+// ---- 缺失即占位(票 22/42 纪律)----
+
+mutate(
+    'M15-missing-counts-painted-as-zero',
+    'domain/warningResponsePanels.js',
+    `        { key: 'warningTotal', label: '预警条数', value: valueText(current.totalEvents), unit: '条' },`,
+    `        { key: 'warningTotal', label: '预警条数', value: current.totalEvents === null || current.totalEvents === undefined ? '0' : valueText(current.totalEvents), unit: '条' },`,
+    'G1 预警族读数缺失画占位符(不把「没有」画成 0 条)',
+)
+
+// ---- 报警族「当前工单列表」(2026-10-21 追加需求 1)----
+
+mutate(
+    'M16-alarm-list-block-missing',
+    'domain/warningResponsePanels.js',
+    `        workOrderListBlock(extra),`,
+    ``,
+    'H1 报警模式出现「当前工单列表」块,且排在统计图之后(先结论后明细)',
+)
+
+mutate(
+    'M17-card-rows-lose-click-contract',
+    'domain/warningResponsePanels.js',
+    `        code: row.code,`,
+    `        code: '',`,
+    'H2 卡片行带可点契约与状态词表(code 触发渲染层 open-card;tone 来自既有色键)',
+)
+
+mutate(
+    'M18-pagination-from-page-length',
+    'domain/warningResponsePanels.js',
+    `        serverPage: paging ? { page: paging.page, pageCount: paging.totalPages, total: paging.total } : undefined,`,
+    `        serverPage: paging ? { page: paging.page, pageCount: 1, total: orders.length } : undefined,`,
+    'H3 分页读后端回显(共 7 张 / 2 页),不由本页 2 条反推',
+)
+
+mutate(
+    'M19-failed-list-drawn-as-empty',
+    'domain/warningResponsePanels.js',
+    `    if (section.state !== 'success' && section.state !== 'stale' && orders.length === 0) {`,
+    `    if (false) {`,
+    'H6 取数失败说「未取到」,与「确实没有」分开(不把缺数据画成 0 张)',
+)
+
+mutate(
+    'M20-warning-family-also-gets-work-order-list',
+    'domain/warningResponsePanels.js',
+    `            barsBlock('预警等级分布', current.levelDistribution || []),`,
+    `            barsBlock('预警等级分布', current.levelDistribution || []),
+            workOrderListBlock(extra),`,
+    'H8 预警模式不出现工单列表(预警不挂工单,与「不画工单口径」同一条纪律)',
+)
+
+mutate(
+    'M21-state-tone-not-partitioned',
+    'domain/warningResponsePanels.js',
+    `    if (state.code === 0 || state.code === 1) return 'lv1'`,
+    `    if (state.code === 0 || state.code === 1) return 'neutral'`,
+    'H9b 色调随状态语义分档(待办≠在办≠已结案≠已取消)',
+)
+
+// ---- 预警口径说明文案(2026-10-21 追加需求 2)----
+
+mutate(
+    'M22-gap-note-back-to-endpoint-wording',
+    'domain/warningResponsePanels.js',
+    `    '预警不挂工单,所以这里不画工单口径的处置率、工单状态分布与结案时长;' +`,
+    `    '本端点对预警族仍只返回工单口径字段,预警不挂工单,所以这里不画处置率;' +`,
+    'I1 预警口径说明不含接口词汇(「本端点」「该端点」「/events/」等不上大屏)',
+)
+
+mutate(
+    'M23-gap-note-drops-readings-first',
+    'domain/warningResponsePanels.js',
+    `    '下方的条数、等级分布与发生趋势都是本窗口内预警自身的真实读数。' +`,
+    ``,
+    'I2 前两句先说清「这些数是什么、能不能信」(先讲读数再讲缺口)',
+)
+
+// ---- 全量工单端点契约(2026-10-21 追加需求 1 的数据面)----
+
+mutate(
+    'M24-list-pagination-dropped',
+    'services/detailApi.js',
+    `                pagination: paging`,
+    `                pagination: paging ? { page: 1, pageSize: 5, total: 1, totalPages: 1 } : null`,
+    'J2 分页四件套逐项读后端回显(page/pageSize/total/totalPages,不由行数反推)',
+)
+
+mutate(
+    'M25-list-association-flag-lost',
+    'services/detailApi.js',
+    `                orders: Array.isArray(payload && payload.workOrders) ? payload.workOrders.map(toWorkOrder) : [],`,
+    `                orders: Array.isArray(payload && payload.workOrders) ? payload.workOrders.map((row) => ({ ...toWorkOrder(row), associatedWithCurrentEvent: null })) : [],`,
+    'J3 工单的关联标记如实透传(列表里能分辨「关联了报警」与「纯设备工单」)',
+)
+
+mutate(
+    'M26-mock-list-always-returns-everything',
+    'mocks/contractMocks.js',
+    `            workOrders: all.slice((safePage - 1) * pageSize, safePage * pageSize),`,
+    `            workOrders: all,`,
+    'J5 mock 全量工单与真实同形(有 pagination;换 pageSize 后翻页真的切窗口,不是永远回全量)',
+)
+
+console.log('')
+console.log(`T65 变异测试:${proved}/${injected} 处变异被对应断言抓住。`)
+rmSync(reportDir, { recursive: true, force: true })
+if (proved < injected) process.exit(1)

+ 636 - 0
.scratch/cockpit-real-data-wayfinding/verify/t65-warning-efficiency-family-split.mjs

@@ -0,0 +1,636 @@
+/**
+ * 票 65 离线闸门:预警效率面板按预警/报警分流 —— 预警族展示预警流程而非工单口径。
+ *
+ * 背景(票面):维护者 2026-10-19 反馈第 6 条 —— 预警处置「预警效率」面板没有做
+ * 预警/报警模式的数据区分;预警模式下没有关联工单数据,应展示**预警流程**的信息。
+ * 实测(2026-10-21 复验,`192.168.110.96:8301`):`?eventFamily=WARNING` 与 `=ALARM`
+ * 返回**同一套工单口径字段**;预警族 `totalEvents=5` 而 `linkedWorkOrderCount=0`、
+ * `missingWorkOrderCount=5` —— 预警不挂工单,照画工单口径就是「用一个不存在的对象统计预警」。
+ *
+ * 维护者裁定(方案 A):本票只做**诚实的语义分流**,不假装后端已有预警流程字段;
+ * 预警族只画该端点里真实属于预警的读数(条数、逐事件等级、逐桶发生量、工单关联实况),
+ * 工单处置率/工单状态分布/结案时长在预警模式下**不上屏**;契约缺口写进界面说明并另开后端票。
+ *
+ * 断言分组(都不启浏览器、不联网;渲染层用 Vue SSR 跑真实组件):
+ *   A  真实载荷两族:预警族读数全是真实计数、视图**不含**工单口径;报警族保留工单口径。
+ *   B  标题/口径说明/KPI 提示随切换一起换,词表复用 `eventListSwitchView` 单源。
+ *   C  分流依据:`data.family`(后端回显)优先于 `extra.mode`(切换参数),都不给才落报警口径。
+ *   D  渲染层 SSR:预警模式 DOM 无处置率/工单状态分布;报警模式有;口径说明在 DOM 里。
+ *   E  接线:面板组件把 `eventStore.mode` 交给 domain;外壳标题走 domain 单源。
+ *   F  mock 同构:mock 两族与真实同形(含工单字段,不把缺口藏起来),过同一 domain 得同一结论。
+ *   G  缺失即占位:读数缺失画 `—`,不把「没有」画成 0(票 22/42 同一纪律)。
+ *
+ * 变异有牙见 t65-mutations.mjs。
+ * 运行:node .scratch/cockpit-real-data-wayfinding/verify/t65-warning-efficiency-family-split.mjs
+ */
+import assert from 'node:assert/strict'
+import { readFileSync } from 'node:fs'
+import { registerHooks } from 'node:module'
+import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc'
+import { createSSRApp, h } from 'vue'
+import { renderToString } from '@vue/server-renderer'
+
+const SRC = '../../../src/modules/cockpit/'
+const ROOT = new URL('../../../', import.meta.url)
+const read = (rel) => readFileSync(new URL(SRC + rel, import.meta.url), 'utf8')
+const readVerifier = (rel) => readFileSync(new URL(rel, import.meta.url), 'utf8')
+
+// .vue 一律走 SSR 编译钩子(t64 成例:共享子组件按 parentURL 传播,否则子组件只有 render)。
+registerHooks({
+    resolve(specifier, context, nextResolve) {
+        const resolved = nextResolve(specifier, context)
+        const parent = context.parentURL || ''
+        if (parent.endsWith('.vue?client') && resolved.url.endsWith('.vue')) {
+            return { url: `${resolved.url}?client`, format: 'module', shortCircuit: true }
+        }
+        return resolved
+    },
+    load(url, context, nextLoad) {
+        if (url.endsWith('.json')) {
+            return { format: 'module', shortCircuit: true, source: `export default ${readFileSync(new URL(url), 'utf8')}` }
+        }
+        const isClient = url.endsWith('.vue?client')
+        if (url.endsWith('.vue') || isClient) {
+            const path = isClient ? url.slice(0, -'?client'.length) : url
+            const source = readFileSync(new URL(path), 'utf8')
+            const { descriptor } = parse(source, { filename: path })
+            const script = compileScript(descriptor, { id: 't65', genDefaultAs: '__sfc__' })
+            const template = compileTemplate({
+                source: descriptor.template.content,
+                filename: path,
+                id: 't65',
+                ssr: !isClient,
+                compilerOptions: { bindingMetadata: script.bindings },
+            })
+            assert.deepEqual(template.errors, [], `${path} template`)
+            const tail = isClient
+                ? '__sfc__.render = render; export default __sfc__;'
+                : '__sfc__.ssrRender = ssrRender; export default __sfc__;'
+            return { format: 'module', shortCircuit: true, source: `${script.content}\n${template.code}\n${tail}` }
+        }
+        return nextLoad(url, context)
+    },
+})
+
+const {
+    ALARM_EFFICIENCY_BASIS,
+    WARNING_EFFICIENCY_GAP,
+    responseEfficiencyPanel,
+    responseEfficiencyTitle,
+    toHandlingEfficiency,
+} = await import(`${SRC}domain/warningResponsePanels.js`)
+const { eventListSwitchView } = await import(`${SRC}domain/eventList.js`)
+const { SECTION_STATE, section } = await import(`${SRC}domain/sectionState.js`)
+const { default: PanelBlocks } = await import(`${SRC}components/PanelBlocks.vue`)
+
+let passed = 0
+let failed = 0
+function check(name, condition, detail = '') {
+    if (condition) {
+        passed += 1
+        console.log(`PASS ${name}`)
+    } else {
+        failed += 1
+        console.error(`FAIL ${name}: ${detail}`)
+    }
+}
+const okSection = (data) => ({ ...section(SECTION_STATE.SUCCESS, { data }), loaded: true })
+const viewOf = (data, extra) => responseEfficiencyPanel(data, okSection(data), extra)
+const kpiOf = (view) => (view.blocks.find((block) => block.kind === 'kpi') || { items: [] }).items
+const kpiKeys = (view) => kpiOf(view).map((item) => item.key)
+const blockLabels = (view) => view.blocks.map((block) => block.label).filter(Boolean)
+const renderPanel = async (view) => renderToString(createSSRApp({ render: () => h(PanelBlocks, { view }) }))
+const WORK_ORDER_ONLY_KEYS = ['handlingRate', 'responseDuration', 'closeDuration']
+const WARNING_ONLY_KEYS = ['warningTotal', 'warningLinked', 'warningMissing']
+
+// ================================================================ A. 真实载荷两族
+
+// 载荷来自 2026-10-21 对 192.168.110.96:8301 的只读实测(同目录 artifacts/t65/)。
+const liveWarning = JSON.parse(readVerifier('./artifacts/t65/handling-warning-live.json')).data
+const liveAlarm = JSON.parse(readVerifier('./artifacts/t65/handling-alarm-live.json')).data
+const warningData = toHandlingEfficiency(liveWarning)
+const alarmData = toHandlingEfficiency(liveAlarm)
+const warningView = viewOf(warningData, { mode: 'WARNING' })
+const alarmView = viewOf(alarmData, { mode: 'ALARM' })
+
+{
+    check(
+        'A1 两族族别取自后端回显(同一响应类,靠 eventFamily 分流)',
+        warningData.family === 'WARNING' && alarmData.family === 'ALARM',
+        `warning=${warningData.family} alarm=${alarmData.family}`,
+    )
+    check(
+        'A2 预警族工单关联实况是真实计数(预警不挂工单:0 关联 / 全部未关联)',
+        warningData.totalEvents === liveWarning.summary.totalEvents &&
+            warningData.linkedWorkOrderCount === liveWarning.summary.linkedWorkOrderCount &&
+            warningData.missingWorkOrderCount === liveWarning.summary.missingWorkOrderCount &&
+            warningData.linkedWorkOrderCount === 0 &&
+            warningData.missingWorkOrderCount === warningData.totalEvents,
+        `total=${warningData.totalEvents} linked=${warningData.linkedWorkOrderCount} missing=${warningData.missingWorkOrderCount}`,
+    )
+
+    // A3 预警族 KPI 全是预警自身读数,读数取自真实载荷(不是编的、也不是工单口径)
+    const keys = kpiKeys(warningView)
+    const items = kpiOf(warningView)
+    check(
+        'A3 预警族 KPI = 预警自身三项读数(条数/已关联/未关联)',
+        JSON.stringify(keys) === JSON.stringify(WARNING_ONLY_KEYS) &&
+            items[0].value === String(warningData.totalEvents) &&
+            items[1].value === String(warningData.linkedWorkOrderCount) &&
+            items[2].value === String(warningData.missingWorkOrderCount),
+        `keys=${keys.join('/')} values=${items.map((item) => item.value).join('/')}`,
+    )
+    // A3b 「预警不挂工单」这句必须在 KPI 提示或口径说明里说出口(不能说了一半)。
+    const warningText = JSON.stringify(warningView)
+    check('A3b 预警族如实说明「预警不挂工单」', warningText.includes('预警不挂工单'), 'KPI hint / 口径说明都没说这句')
+
+    // A4 预警模式下**不得**画工单口径:KPI 键、块标题、块类型三重判据
+    const labels = blockLabels(warningView)
+    check(
+        'A4 预警模式不画工单口径(处置率/工单状态分布/结案时长)',
+        !keys.some((key) => WORK_ORDER_ONLY_KEYS.includes(key)) &&
+            !labels.some((label) => /工单状态分布|结案时长|处置率/.test(label)) &&
+            !warningView.blocks.some((block) => block.kind === 'stacked'),
+        `keys=${keys.join('/')} labels=${labels.join('/')} kinds=${warningView.blocks.map((b) => b.kind).join('/')}`,
+    )
+    // A4b 更硬的一条:把**画出来的块**(KPI 项/分布行/趋势点,即 view.blocks 本体)
+    // 序列化后,工单口径的基线与词汇一概不出现 —— 防止「字段名换成中文但数值还是工单口径」
+    // 这类改字不改数的假分流。范围**只含块**:口径说明(`view.note`)按设计要点名
+    // 「工单处置率/工单状态分布」来说清它们为何不呈现,把它算进来是把「解释」当「读数」。
+    check(
+        'A4b 预警模式画出来的块里零工单口径词汇与基线',
+        !/CLOSED_WORK_ORDERS|可计入工单|已结案|工单状态/.test(JSON.stringify(warningView.blocks)),
+        `blocks=${JSON.stringify(warningView.blocks).slice(0, 300)}`,
+    )
+    // A4c 反过来:预警族薪资的**真实**读数必须在(分流的另一半:不是把面板清空)。
+    check(
+        'A4c 预警模式确有预警流程读数(等级分布 + 发生趋势)',
+        warningView.blocks.some((block) => block.kind === 'bars' && block.label === '预警等级分布') &&
+            warningView.blocks.some((block) => block.kind === 'trend'),
+        `kinds=${warningView.blocks.map((b) => b.kind).join('/')}`,
+    )
+
+    // A5 等级分布逐级对真实事件行(4 行事件:1 级 4 条、2 级 1 条)
+    const realLevels = [1, 2, 3, 4].map((level) => liveWarning.events.filter((item) => Number(item.level) === level).length)
+    const barsBlockOfWarning = warningView.blocks.find((block) => block.kind === 'bars')
+    check(
+        'A5 预警等级分布逐级等于真实事件行统计',
+        JSON.stringify((barsBlockOfWarning.rows || []).map((row) => row.count)) === JSON.stringify(realLevels) &&
+            (barsBlockOfWarning.rows || []).length === 4,
+        `rows=${(barsBlockOfWarning.rows || []).map((row) => row.count).join('/')} want=${realLevels.join('/')}`,
+    )
+    // A6 发生趋势按日合并(真实 169 个小时桶 → 每自然日一个点;不重采样、不补点)。
+    //     期望值由载荷自己算:桶数 = 覆盖的自然日数,非零点数 = 有事件的自然日数,
+    //     求和 = 载荷逐桶 `eventCount` 的总和(受日合并后不丢量)。
+    const trendBlockOfWarning = warningView.blocks.find((block) => block.kind === 'trend')
+    const liveBuckets = liveWarning.durationTrend
+    const dayOf = (bucket) => String(bucket).slice(0, 10)
+    const expectDays = new Set(liveBuckets.map((row) => dayOf(row.timeBucket))).size
+    const activeBuckets = liveBuckets.filter((row) => row.eventCount > 0)
+    const expectEventDays = new Set(activeBuckets.map((row) => dayOf(row.timeBucket))).size
+    const expectEventSum = activeBuckets.reduce((sum, row) => sum + row.eventCount, 0)
+    const nonzeroTrend = (trendBlockOfWarning.points || []).filter((point) => point.count > 0)
+    check(
+        'A6 预警发生趋势按日合并(小时桶→天,不重采样、不补点)',
+        liveBuckets.length > 48 &&
+            (trendBlockOfWarning.points || []).length === expectDays &&
+            nonzeroTrend.length === expectEventDays &&
+            nonzeroTrend.reduce((sum, point) => sum + point.count, 0) === expectEventSum,
+        `buckets=${liveBuckets.length} points=${(trendBlockOfWarning.points || []).length}/${expectDays} nonzero=${nonzeroTrend.length}/${expectEventDays}`,
+    )
+
+    // A7 报警族保留工单口径(既有裁定 41/42/52 不重开)
+    const alarmKeys = kpiKeys(alarmView)
+    const alarmLabels = blockLabels(alarmView)
+    check(
+        'A7 报警族保留工单口径(处置率/响应与关闭时长/工单状态分布)',
+        JSON.stringify(alarmKeys) === JSON.stringify(WORK_ORDER_ONLY_KEYS) &&
+            alarmLabels.includes('工单状态分布') &&
+            alarmLabels.includes('平均结案时长趋势') &&
+            alarmView.blocks.some((block) => block.kind === 'stacked'),
+        `keys=${alarmKeys.join('/')} labels=${alarmLabels.join('/')}`,
+    )
+    // A7b 报警族的处置率读数与真实载荷一致(0/1 可计入 → 0.0%,不是「不适用」)
+    const alarmRate = kpiOf(alarmView)[0]
+    check(
+        'A7b 报警族处置率读数取自真实工单口径(0/1 → 0.0%)',
+        alarmRate.value === '0.0%' &&
+            liveAlarm.summary.handlingEfficiency.denominator === 1 &&
+            liveAlarm.summary.handlingEfficiency.numerator === 0,
+        `value=${alarmRate.value}`,
+    )
+    // A7c 报警模式不画预警自身读数(分流是对称的,不是只给预警加口径)
+    check(
+        'A7c 报警模式不出现预警自身 KPI',
+        !alarmKeys.some((key) => WARNING_ONLY_KEYS.includes(key)),
+        `keys=${alarmKeys.join('/')}`,
+    )
+}
+
+// ================================================================ B. 标题 / 口径说明 / KPI 提示随切换一起换
+
+{
+    // B1 标题的词根来自**同一张切换词表**(改词表则标题跟着变 —— 行为断言,不是比对字面量)
+    const warningSwitch = eventListSwitchView('WARNING')
+    const alarmSwitch = eventListSwitchView('ALARM')
+    check(
+        'B1 面板标题复用事件族单一词表(预警/报警 + 「效率」)',
+        responseEfficiencyTitle('WARNING') === `${warningSwitch.label}效率` &&
+            responseEfficiencyTitle('ALARM') === `${alarmSwitch.label}效率` &&
+            responseEfficiencyTitle('WARNING') !== responseEfficiencyTitle('ALARM'),
+        `warning=${responseEfficiencyTitle('WARNING')} alarm=${responseEfficiencyTitle('ALARM')}`,
+    )
+    check(
+        'B2 两族视图的面板标题跟着族换',
+        warningView.label === responseEfficiencyTitle('WARNING') && alarmView.label === responseEfficiencyTitle('ALARM'),
+        `warning=${warningView.label} alarm=${alarmView.label}`,
+    )
+    // B3 口径说明随族换,且各自说清本族口径(预警:不挂工单 + 缺口;报警:工单是处置手段)
+    check(
+        'B3 口径说明随族换且各自如实',
+        warningView.note === WARNING_EFFICIENCY_GAP &&
+            alarmView.note === ALARM_EFFICIENCY_BASIS &&
+            /预警不挂工单|没有预警自身的处置流程统计/.test(warningView.note) &&
+            /工单为处置手段/.test(alarmView.note),
+        `warningNote=${warningView.note}`,
+    )
+    // B4 两族窗口读数的落点不同:预警给条数/关联实况,报警给可计入工单/已结案
+    check(
+        'B4 窗口读数随族换(预警=条数与关联实况;报警=工单张数)',
+        /本窗口预警 5 条/.test(warningView.rangeNote) &&
+            /已关联工单 0 条/.test(warningView.rangeNote) &&
+            /可计入工单 1 张/.test(alarmView.rangeNote) &&
+            warningView.rangeNote !== alarmView.rangeNote,
+        `warning=${warningView.rangeNote} | alarm=${alarmView.rangeNote}`,
+    )
+}
+
+// ================================================================ C. 分流依据优先级
+
+{
+    // C1 后端回显优先:数据是预警族的,切换参数传错也不得画成报警口径。
+    const echoed = viewOf(warningData, { mode: 'ALARM' })
+    check(
+        'C1 data.family(后端回显)优先于 extra.mode(切换参数)',
+        echoed.label === responseEfficiencyTitle('WARNING') &&
+            !kpiKeys(echoed).some((key) => WORK_ORDER_ONLY_KEYS.includes(key)),
+        `label=${echoed.label} keys=${kpiKeys(echoed).join('/')}`,
+    )
+    // C2 回显缺失时靠切换参数分流(mock / 旧载荷可能不给 eventFamily)
+    const noFamily = { ...warningData, family: '' }
+    const byMode = viewOf(noFamily, { mode: 'WARNING' })
+    check(
+        'C2 回显缺失时按切换参数分流(extra.mode=WARNING → 预警口径)',
+        byMode.label === responseEfficiencyTitle('WARNING') &&
+            JSON.stringify(kpiKeys(byMode)) === JSON.stringify(WARNING_ONLY_KEYS),
+        `label=${byMode.label} keys=${kpiKeys(byMode).join('/')}`,
+    )
+    // C3 两者都没有时落报警口径(工单口径是既有形态;旧调用点(t19/t16/t44)不因本票改签名而变空壳)
+    const fallback = viewOf({ ...alarmData, family: '' }, undefined)
+    check(
+        'C3 回显与切换参数都没有时保持既有报警口径(旧调用点不退化)',
+        fallback.label === responseEfficiencyTitle('ALARM') &&
+            JSON.stringify(kpiKeys(fallback)) === JSON.stringify(WORK_ORDER_ONLY_KEYS),
+        `label=${fallback.label} keys=${kpiKeys(fallback).join('/')}`,
+    )
+}
+
+// ================================================================ D. 渲染层 SSR
+
+{
+    const warningHtml = await renderPanel(warningView)
+    const alarmHtml = await renderPanel(alarmView)
+    check(
+        'D1 预警模式 DOM 有预警读数、无处置率与工单状态分布',
+        /data-kpi="warningTotal"/.test(warningHtml) &&
+            /data-kpi="warningLinked"/.test(warningHtml) &&
+            !/data-kpi="handlingRate"/.test(warningHtml) &&
+            !/data-panel-block="stacked"/.test(warningHtml),
+        '预警 DOM 里仍出现工单口径块',
+    )
+    check(
+        'D2 报警模式 DOM 有处置率与工单状态分布、无预警读数',
+        /data-kpi="handlingRate"/.test(alarmHtml) &&
+            /data-panel-block="stacked"/.test(alarmHtml) &&
+            !/data-kpi="warningTotal"/.test(alarmHtml),
+        '报警 DOM 与工单口径不一致',
+    )
+    // D3 口径说明走票 56 的折叠摘要:文本在 DOM 里(v-show 折叠不丢信息),断言面是 data-panel-note。
+    check(
+        'D3 预警口径缺口说明在 DOM 里(折叠不丢文本)',
+        /data-panel-note/.test(warningHtml) && warningHtml.includes('预警不挂工单'),
+        '说明字段没进折叠摘要(会在界面上隐形 —— 票 56 记过的同类缺陷)',
+    )
+    // D4 预警模式面板标题在 DOM 里随族换
+    check(
+        'D4 预警模式 DOM 标题为「预警效率」,报警模式为「报警效率」',
+        warningHtml.includes('预警效率摘要') && alarmHtml.includes('报警效率摘要'),
+        '标题没随族换',
+    )
+}
+
+// ================================================================ E. 接线(源码级判据)
+
+{
+    const pane = read('components/WarningResponsePane.vue')
+    const shell = read('CockpitShell.vue')
+    check(
+        'E1 面板组件把当前切换族交给 domain(否则预警接线永远落回报警口径)',
+        // 票 65 追加需求后 `extra` 多了 `workOrders`(报警模式的工单列表数据源),
+        // 所以这里不再钉字面量写法——只钉**行为**:panelView 的第四参里出现
+        // `mode: eventStore.mode`(顺序、同行与否都不影响语义,避免"改格式就误报")。
+        /panelView\(\s*props\.panelId,\s*current\.data,\s*current,\s*\{[\s\S]*?mode:\s*eventStore\.mode[\s\S]*?\}\s*\)/.test(pane),
+        'WarningResponsePane 没传 extra.mode ⇒ 预警模式仍画工单口径',
+    )
+    check(
+        'E2 外壳面板标题走 domain 单源(responseEfficiencyTitle),不另起一张词表',
+        /function panelTitle\(panelId\)/.test(shell) &&
+            /responseEfficiencyTitle\(eventStore\.mode\)/.test(shell) &&
+            /:title="panelTitle\(panelId\)"/.test(shell),
+        '外壳标题没接 domain 单源',
+    )
+    // E3 词表只有一份:面板标题不得另写一份 `预警效率`/`报警效率` 字面量(domain 之外零字面量)
+    const shellWithoutImport = shell.replace(/import \{[^}]*responseEfficiencyTitle[^}]*\} from '\.\/domain\/warningResponsePanels\.js'/, '')
+    check(
+        'E3 预警/报警效率标题字面量只存在于 domain(外壳零第二份)',
+        !/'预警效率'|'报警效率'|`预警效率`|`报警效率`/.test(shellWithoutImport) && !/'报警效率'/.test(pane),
+        '外壳/组件里又写了一份标题字面量(早晚漂移)',
+    )
+}
+
+// ================================================================ F. mock 同构
+
+{
+    const { registerCockpitMocks } = await import(`${SRC}mocks/contractMocks.js`)
+    const transports = {}
+    registerCockpitMocks({ handlers: { registerMockTransport: (path, handler) => { transports[path] = handler } } })
+    // mock 处理器交出的是**业务载荷**本身(信封由 transport 层包),与真实 `data` 同形。
+    const mockOf = async (query) => transports['/events/handling-efficiency']({ path: `/events/handling-efficiency${query}` })
+    const rawWarning = await mockOf('?eventFamily=WARNING')
+    const rawAlarm = await mockOf('?eventFamily=ALARM')
+    const mockWarningData = toHandlingEfficiency(rawWarning)
+    const mockAlarmData = toHandlingEfficiency(rawAlarm)
+    const mockWarningView = viewOf(mockWarningData, { mode: 'WARNING' })
+    const mockAlarmView = viewOf(mockAlarmData, { mode: 'ALARM' })
+
+    check(
+        'F1 mock 预警族与真实同形(不挂工单:0 关联 / 全部未关联)',
+        rawWarning.eventFamily === 'WARNING' &&
+            mockWarningData.linkedWorkOrderCount === 0 &&
+            mockWarningData.missingWorkOrderCount === mockWarningData.totalEvents &&
+            mockWarningData.totalEvents > 0,
+        `total=${mockWarningData.totalEvents} linked=${mockWarningData.linkedWorkOrderCount}`,
+    )
+    // F2 mock **不**替后端把工单字段藏掉:契约缺口必须在 mock 上也看得见(否则真机才会炸)。
+    check(
+        'F2 mock 两族仍保留工单口径字段(契约现状,不伪装成已分流)',
+        !!rawWarning.summary.handlingEfficiency &&
+            Array.isArray(rawWarning.workOrderStateCounts) &&
+            rawWarning.workOrderStateCounts.length === 8 &&
+            !!rawAlarm.summary.handlingEfficiency,
+        'mock 把工单字段藏起来了 —— 缺口被 mock 掩盖',
+    )
+    // F3 同一 domain 消费 mock → 与真实同一个结论(预警不画工单口径;报警保留)
+    check(
+        'F3 mock 走同一 domain 得同一结论(预警口径 / 报警口径)',
+        mockWarningView.label === responseEfficiencyTitle('WARNING') &&
+            !kpiKeys(mockWarningView).some((key) => WORK_ORDER_ONLY_KEYS.includes(key)) &&
+            mockAlarmView.label === responseEfficiencyTitle('ALARM') &&
+            JSON.stringify(kpiKeys(mockAlarmView)) === JSON.stringify(WORK_ORDER_ONLY_KEYS),
+        `mockWarning=${kpiKeys(mockWarningView).join('/')} mockAlarm=${kpiKeys(mockAlarmView).join('/')}`,
+    )
+    // F4 mock 报警族有真实的关联工单读数(与预警族的 0 形成对照,证明分流不是「两族都画 0」)
+    check(
+        'F4 mock 报警族有承接工单(与预警族 0 关联形成对照)',
+        mockAlarmData.linkedWorkOrderCount > 0 && mockWarningData.linkedWorkOrderCount === 0,
+        `alarm linked=${mockAlarmData.linkedWorkOrderCount} warning linked=${mockWarningData.linkedWorkOrderCount}`,
+    )
+}
+
+// ================================================================ G. 缺失即占位(不把「没有」画成 0)
+
+{
+    const bare = toHandlingEfficiency({ eventFamily: 'WARNING', durationTrend: [], events: [] })
+    const view = viewOf(bare, { mode: 'WARNING' })
+    const items = kpiOf(view)
+    check(
+        'G1 预警族读数缺失画占位符(不把「没有」画成 0 条)',
+        bare.totalEvents === null && items.every((item) => item.value === '—'),
+        `values=${items.map((item) => item.value).join('/')}`,
+    )
+    check(
+        'G2 缺失时不编造窗口读数(rangeNote 为空串而不是「0 条」)',
+        view.rangeNote === '',
+        `rangeNote=${view.rangeNote}`,
+    )
+}
+
+// ================================================================ H. 报警族「当前工单列表」(2026-10-21 追加需求 1)
+
+/**
+ * 背景:维护者追加要求「报警效率页面的报警模式,除了报警数据外还需直接展示当前工单列表
+ * (卡片形式,可点开查看工单详情)」。
+ *
+ * 先说清数据来源,这是本组所有断言的前提:`handling-efficiency` **不返回工单实体**,
+ * 只有状态计数(实测算术:报警族八态合计 0,但同一台设备 `/equipments/…/work-orders`
+ * 能查到 7 张工单)。所以列表必须来自另一个端点,本组按 store 真实传入的形状喂它。
+ */
+{
+    const listOrders = [
+        { code: 'WO-A', stateCode: 0, stateName: '待派单', description: '监测报警,请现场核实', createTime: '2026-10-14T13:43:50+08:00', orderTypeName: '故障维修', associatedWithCurrentEvent: true },
+        { code: 'WO-B', stateCode: 5, stateName: '已结案', description: '已处理', createTime: '2026-10-12T11:03:50+08:00', orderTypeName: '', associatedWithCurrentEvent: false },
+    ]
+    const loadedList = {
+        loaded: true,
+        state: 'success',
+        data: { orders: listOrders, pagination: { page: 1, pageSize: 5, total: 7, totalPages: 2 } },
+    }
+    const alarmWithList = viewOf(alarmData, { mode: 'ALARM', workOrderList: loadedList })
+    const listBlockOf = (view) => view.blocks.find((block) => block.kind === 'list') || null
+    const block = listBlockOf(alarmWithList)
+
+    check(
+        'H1 报警模式出现「当前工单列表」块,且排在统计图之后(先结论后明细)',
+        !!block &&
+            block.label === '当前工单列表' &&
+            alarmWithList.blocks.indexOf(block) === alarmWithList.blocks.length - 1,
+        `blocks=${blockLabels(alarmWithList).join('/')}`,
+    )
+    check(
+        'H2 卡片行带可点契约与状态词表(code 触发渲染层 open-card;tone 来自既有色键)',
+        !!block &&
+            block.rows.length === 2 &&
+            block.rows[0].code === 'WO-A' &&
+            block.rows[0].toneLabel === '待派单' &&
+            // 色键必须是 StatusTag 认识的封闭词表成员,否则标签会退回中性灰而不报错
+            ['lv1', 'lv2', 'lv3', 'lv4', 'neutral'].includes(block.rows[0].tone),
+        `row0=${JSON.stringify(block && block.rows[0])}`,
+    )
+    check(
+        'H3 分页读后端回显(共 7 张 / 2 页),不由本页 2 条反推',
+        !!block && block.serverPage.page === 1 && block.serverPage.pageCount === 2 && block.serverPage.total === 7,
+        `serverPage=${JSON.stringify(block && block.serverPage)}`,
+    )
+    check(
+        'H4 口径说明点明「与工单状态分布是同一批工单」(时间窗对齐后不再说"两个粒度")',
+        !!block && /同一批工单/.test(block.pageNote) && /共 7 张/.test(block.pageNote) && /创建时间/.test(block.pageNote),
+        `pageNote=${block && block.pageNote}`,
+    )
+
+    // H5–H7:三态如实——「正在取数」「未取到」「确实没有」是三句不同的话(票 11/18 分区纪律)
+    const pendingView = viewOf(alarmData, { mode: 'ALARM', workOrderList: { loaded: false, state: 'empty', data: null } })
+    check(
+        'H5 未取到时不画假空列表(说「正在取数」而不是「当前没有工单」)',
+        /正在取数/.test((listBlockOf(pendingView) || {}).emptyText || ''),
+        `emptyText=${(listBlockOf(pendingView) || {}).emptyText}`,
+    )
+    const failedView = viewOf(alarmData, { mode: 'ALARM', workOrderList: { loaded: true, state: 'failed', data: null } })
+    check(
+        'H6 取数失败说「未取到」,与「确实没有」分开(不把缺数据画成 0 张)',
+        /未取到/.test((listBlockOf(failedView) || {}).emptyText || ''),
+        `emptyText=${(listBlockOf(failedView) || {}).emptyText}`,
+    )
+    const emptyView = viewOf(alarmData, { mode: 'ALARM', workOrderList: { loaded: true, state: 'success', data: { orders: [], pagination: { page: 1, pageSize: 5, total: 0, totalPages: 1 } } } })
+    check(
+        'H7 真的 0 张时说「当前没有工单」(真实业务结论,不是加载失败)',
+        (listBlockOf(emptyView) || {}).emptyText === '当前没有工单',
+        `emptyText=${(listBlockOf(emptyView) || {}).emptyText}`,
+    )
+
+    // H8:预警族**不得**出现工单列表 —— 预警不挂工单,列出来就是上一轮同样要防的假读数
+    const warningWithList = viewOf(warningData, { mode: 'WARNING', workOrderList: loadedList })
+    check(
+        'H8 预警模式不出现工单列表(预警不挂工单,与「不画工单口径」同一条纪律)',
+        !listBlockOf(warningWithList) && !blockLabels(warningWithList).includes('当前工单列表'),
+        `blocks=${blockLabels(warningWithList).join('/')}`,
+    )
+
+    // H9:状态色调覆盖全码表且不新增词表 —— 8 个码都要落在 StatusTag 认识的色键上
+    const { workOrderStateTone } = await import(`${SRC}domain/warningResponsePanels.js`)
+    const tones = [0, 1, 2, 3, 4, 5, 6, 7, null, 99].map((code) => workOrderStateTone(code))
+    check(
+        'H9 工单状态色调对 8 个码 + 缺失/未知都给既有色键(不新增第二张词表、不返回 undefined)',
+        tones.every((tone) => ['lv1', 'lv2', 'lv3', 'lv4', 'neutral'].includes(tone)),
+        `tones=${tones.join('/')}`,
+    )
+    check(
+        'H9b 色调随状态语义分档(待办≠在办≠已结案≠已取消)',
+        workOrderStateTone(0) === 'lv1' &&
+            workOrderStateTone(3) === 'lv3' &&
+            workOrderStateTone(5) === 'lv4' &&
+            workOrderStateTone(6) === 'lv2' &&
+            workOrderStateTone(7) === 'neutral',
+        `0=${workOrderStateTone(0)} 3=${workOrderStateTone(3)} 5=${workOrderStateTone(5)} 6=${workOrderStateTone(6)} 7=${workOrderStateTone(7)}`,
+    )
+}
+
+// ================================================================ I. 预警口径说明文案(2026-10-21 追加需求 2)
+
+/**
+ * 背景:维护者看到预警模式摘要仍写着「本端点对预警族仍只返回工单口径字段…」,
+ * 要求「确认预警数据是否正确」。复核结论是**数据没变、文案该改**:
+ * 后端源码 `CockpitHandlingEfficiencyService` 的 `stateCounts` 来自 `scopeWorkOrders(...)`,
+ * 注释原句「范围内全部工单(不要求关联报警/事件)」;实测预警族 `linkedWorkOrderCount=0`、
+ * 八态合计 0 —— 预警族**确实没有**预警自身的处置流程统计。
+ *
+ * 但旧文案有三处毛病:① 「本端点」是接口词汇(项目纪律:接口词汇不上大屏);
+ * ② 先讲"没有字段",读者要读两遍才知道面板上的数能不能信;
+ * ③ 「工单关联实况」这类内部说法对值班人员无意义。本组钉新文案的三条底线。
+ */
+{
+    check(
+        'I1 预警口径说明不含接口词汇(「本端点」「该端点」「/events/」等不上大屏)',
+        !/本端点|该端点|接口返回|\/events\//.test(WARNING_EFFICIENCY_GAP),
+        `gap=${WARNING_EFFICIENCY_GAP}`,
+    )
+    check(
+        'I2 前两句先说清「这些数是什么、能不能信」(先讲读数再讲缺口)',
+        /预警不挂工单/.test(WARNING_EFFICIENCY_GAP) && /真实读数/.test(WARNING_EFFICIENCY_GAP),
+        `gap=${WARNING_EFFICIENCY_GAP}`,
+    )
+    check(
+        'I3 如实说明预警自身的处置流程统计尚未提供,并指向需求池(不假装已有)',
+        /处置流程统计/.test(WARNING_EFFICIENCY_GAP) && /尚未提供/.test(WARNING_EFFICIENCY_GAP) && /需求池/.test(WARNING_EFFICIENCY_GAP),
+        `gap=${WARNING_EFFICIENCY_GAP}`,
+    )
+    check(
+        'I4 两族口径说明都不含接口词汇(报警那句同一纪律)',
+        !/端点|接口返回/.test(ALARM_EFFICIENCY_BASIS),
+        `basis=${ALARM_EFFICIENCY_BASIS}`,
+    )
+}
+
+// ================================================================ J. 全量工单端点契约(2026-10-21 追加需求 1)
+
+/**
+ * 背景:报警模式的工单列表来自**新端点** `GET /api/cockpit/v1/work-orders`
+ * (后端 `CockpitWorkOrderReadController.workOrderList`,与契约测试同源)。
+ * 本组钉住**前端消费侧**对那份响应的解析:字段名、分页四件套、关联标记。
+ *
+ * 为什么必须有这一组:后端代码今天已在 checkout 里改好并通过 31 条契约测试,
+ * 但**尚未部署**(实测该路径仍 404)。若不在这里钉住前端解析,
+ * 等后端上线那天才发现字段对不上,就又要一轮返工。
+ */
+{
+    const { fetchWorkOrderList } = await import(`${SRC}services/detailApi.js`)
+    const contractPayload = JSON.parse(readVerifier('./artifacts/t65b/work-order-list-contract.json'))
+    let parsed = null
+    let parseError = null
+    try {
+        parsed = await fetchWorkOrderList({ page: 1, pageSize: 5 }, {
+            // REAL 模式才走 fetchImpl(mock 模式走 transport),这样能直接喂真实形状载荷;
+            // 只要 AJAX 外壳,不需要真网络。
+            config: { dataSource: 'real', api: { baseUrl: 'http://contract.invalid' } },
+            fetchImpl: async () => ({ ok: true, status: 200, json: async () => ({ code: 200, msg: 'ok', data: contractPayload }) }),
+        })
+    } catch (error) {
+        parseError = error
+    }
+
+    check(
+        'J1 前端能解析新端点的真实载荷(state/工单行/分页四件套都不丢)',
+        !parseError && parsed && parsed.state === 'success' && parsed.data.orders.length === 1 &&
+            parsed.data.orders[0].code === contractPayload.workOrders[0].workOrderCode,
+        parseError ? `解析抛错:${parseError.message}` : `pagination=${JSON.stringify(parsed && parsed.data && parsed.data.pagination)}`,
+    )
+    check(
+        'J2 分页四件套逐项读后端回显(page/pageSize/total/totalPages,不由行数反推)',
+        !!parsed && parsed.data.pagination.page === 1 && parsed.data.pagination.pageSize === 5 &&
+            parsed.data.pagination.total === 7 && parsed.data.pagination.totalPages === 2,
+        `pagination=${JSON.stringify(parsed && parsed.data && parsed.data.pagination)}`,
+    )
+    check(
+        'J3 工单的关联标记如实透传(列表里能分辨「关联了报警」与「纯设备工单」)',
+        !!parsed && parsed.data.orders[0].associatedWithCurrentEvent === true,
+        `assoc=${parsed && parsed.data && parsed.data.orders[0] && parsed.data.orders[0].associatedWithCurrentEvent}`,
+    )
+    check(
+        'J4 长描述完整保留(截断责任在渲染层,不在解析层丢信息)',
+        !!parsed && parsed.data.orders[0].description === contractPayload.workOrders[0].orderDesc,
+        `desc=${parsed && parsed.data && parsed.data.orders[0] && parsed.data.orders[0].description}`,
+    )
+
+    // J5:mock 与真实**同形**——mock 若没有 pagination,前端翻页与「共 N 张」永远测不到。
+    const { registerMockTransport, resetMockTransportsForTest } = await import(`${SRC}services/cockpitApi.js`)
+    const { registerCockpitMocks } = await import(`${SRC}mocks/contractMocks.js`)
+    resetMockTransportsForTest()
+    registerCockpitMocks({ scenario: 'success', handlers: { registerMockTransport, resetMockTransportsForTest } })
+    const mockPage1 = await fetchWorkOrderList({ page: 1, pageSize: 5 })
+    const mockPage2 = await fetchWorkOrderList({ page: 2, pageSize: 2 })
+    check(
+        'J5 mock 全量工单与真实同形(有 pagination;换 pageSize 后翻页真的切窗口,不是永远回全量)',
+        mockPage1.state === 'success' && !!mockPage1.data.pagination &&
+            mockPage1.data.pagination.total === 4 && mockPage1.data.orders.length === 4 &&
+            mockPage2.state === 'success' && mockPage2.data.orders.length === 2,
+        `p1=${JSON.stringify(mockPage1.data && mockPage1.data.pagination)} rows=${mockPage1.data && mockPage1.data.orders.length} p2rows=${mockPage2.data && mockPage2.data.orders.length}`,
+    )
+    resetMockTransportsForTest()
+}
+
+console.log('')
+console.log(`T65 预警效率两族语义分流:${passed}/${passed + failed} 项通过。`)
+if (failed) {
+    console.log('未通过项见上方 FAIL。')
+    process.exit(1)
+}
+console.log('T65 全部通过(浏览器走查按 AGENTS.md 未自动执行)。')

+ 177 - 0
.scratch/cockpit-real-data-wayfinding/verify/t65b-mutations.mjs

@@ -0,0 +1,177 @@
+/**
+ * 票 65 补口变异:证明工单列表翻页闸门抓得住这次真实缺陷形态。
+ *
+ *   M27 面板退回「只认 historyAnalysis」        → S1 必须红(**本次真机缺陷原形**)
+ *   M28 store 不再导出 setWorkOrderPage         → S2 必须红(组件调不到)
+ *   M29 翻页共用全局面板令牌                    → S3 必须红(会作废四块面板在途响应)
+ *   M30 条件变化不重置工单页码                  → P6 必须红(切范围还停在第 2 页)
+ *   M31 翻页不落分区(写了不发)                → P2 必须红(点了没反应)
+ *   M32 令牌检查去掉(迟到响应照落)            → P7 必须红(连点落到旧页)
+ *
+ * 运行:node .scratch/cockpit-real-data-wayfinding/verify/t65b-mutations.mjs
+ */
+import assert from 'node:assert/strict'
+import { closeSync, mkdtempSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import { spawnSync } from 'node:child_process'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
+const GATE = fileURLToPath(new URL('t65b-work-order-paging.mjs', import.meta.url))
+const srcPath = (rel) => `${repoRoot}src/modules/cockpit/${rel}`
+
+const reportDir = mkdtempSync(join(tmpdir(), 't65b-mutations-'))
+function runGate() {
+    const outPath = join(reportDir, 'stdout.txt')
+    const errPath = join(reportDir, 'stderr.txt')
+    const out = openSync(outPath, 'w')
+    const err = openSync(errPath, 'w')
+    let status = 0
+    try {
+        status = spawnSync(process.execPath, [GATE], { stdio: ['ignore', out, err] }).status
+    } finally {
+        closeSync(out)
+        closeSync(err)
+    }
+    return { output: `${readFileSync(outPath, 'utf8')}${readFileSync(errPath, 'utf8')}`, status }
+}
+
+const failingNames = (output) => [...output.matchAll(/^FAIL ([^:]+):/gm)].map((match) => match[1])
+
+let injected = 0
+let proved = 0
+function mutate(label, file, from, to, expectRed) {
+    const path = srcPath(file)
+    const original = readFileSync(path, 'utf8')
+    const candidates = [from, from.replace(/\n/g, '\r\n')]
+    const needle = candidates.find((candidate) => original.includes(candidate))
+    if (!needle) {
+        console.error(`FAIL ${label}: 变异源未命中(文件形状已变,需人工更新变异脚本)`)
+        injected += 1
+        return
+    }
+    injected += 1
+    writeFileSync(path, original.replace(needle, to), 'utf8')
+    let caught = false
+    let how = ''
+    try {
+        const { output, status } = runGate()
+        const red = failingNames(output)
+        if (red.includes(expectRed)) {
+            caught = true
+            how = `${expectRed} 转红`
+        } else if (status !== 0 && /TypeError|is not a function|does not provide an export|SyntaxError/.test(output)) {
+            // 删掉导出/方法这类变异会让闸门在「调到不存在的方法」处崩掉 —— 也算抓住(崩溃式)。
+            caught = true
+            how = '闸门在缺导出/缺方法处崩溃 — 崩溃式抓住'
+        } else if (status !== 0) {
+            how = `退出码 ${status} 但目标断言未红(红项:${red.join(', ') || '无'})`
+        } else {
+            how = '未转红(假绿!)'
+        }
+        console.log(`${caught ? 'PASS' : 'FAIL'}  ${label} → ${how}`)
+        if (caught) proved += 1
+    } finally {
+        writeFileSync(path, original, 'utf8')
+        assert.equal(readFileSync(path, 'utf8'), original, `${label}: 还原失败`)
+    }
+}
+
+// ---- 本次缺陷原形:面板只认 historyAnalysis ----
+mutate(
+    'M27-pane-only-routes-history-analysis',
+    'components/WarningResponsePane.vue',
+    `    const target = payload && payload.page
+    if (props.panelId === 'historyAnalysis') {
+        eventStore.setHistoryPage(target)
+        return
+    }
+    if (props.panelId === 'responseEfficiency') {
+        eventStore.setWorkOrderPage(target)
+    }`,
+    `    if (props.panelId !== 'historyAnalysis') return
+    eventStore.setHistoryPage(payload && payload.page)`,
+    'S1 面板把报警效率的翻页事件交给 store(不再只认 historyAnalysis)',
+)
+
+mutate(
+    'M28-store-does-not-export-action',
+    'stores/eventStore.js',
+    `        setWorkOrderPage,`,
+    ``,
+    'S2 store 暴露 setWorkOrderPage(组件才调得到)',
+)
+
+mutate(
+    'M29-paging-shares-global-token',
+    'stores/eventStore.js',
+    `        workOrderToken += 1
+        const mine = workOrderToken`,
+    `        token += 1
+        const mine = token`,
+    'S3 工单页码用独立令牌(翻工单页不作废四块面板的在途响应)',
+)
+
+mutate(
+    'M30-condition-change-keeps-work-order-page',
+    'stores/eventStore.js',
+    `            // 票 65:条件变了,工单列表也回第 1 页(条文 5;定时刷新不带条件 ⇒ 页码保持)。
+            workOrderPage.value = 1`,
+    ``,
+    'P6 条件变化(切范围)后工单页码回第 1 页',
+)
+
+mutate(
+    'M31-paging-does-not-commit-section',
+    'stores/eventStore.js',
+    `        sections.value = { ...sections.value, workOrderList: { ...result, loaded: true } }`,
+    ``,
+    'P2 翻到第 2 页:请求带 page=2、分区换成 6–10 号工单',
+)
+
+mutate(
+    'M32-stale-response-committed',
+    'stores/eventStore.js',
+    `        // 迟到响应丢弃:期间又翻过页/切过条件,这一页不落分区。
+        if (mine !== workOrderToken) return sections.value.workOrderList`,
+    ``,
+    'P7 快速连点只认最后一页(迟到响应丢弃,独立令牌)',
+)
+
+// ---- 时间窗(2026-10-22 维护者反馈「列表没按时间筛」)----
+
+mutate(
+    'M33-request-drops-time-window',
+    'stores/eventStore.js',
+    `            fetchWorkOrderList(
+                { page: workOrderPage.value, pageSize: WORK_ORDER_LIST_PAGE_SIZE, startTime: workOrderWindow.startTime, endTime: workOrderWindow.endTime },
+                requestOptions,
+            ),`,
+    `            fetchWorkOrderList(
+                { page: workOrderPage.value, pageSize: WORK_ORDER_LIST_PAGE_SIZE },
+                requestOptions,
+            ),`,
+    'P9 首屏工单列表请求带当前窗口(7 天的 startTime/endTime 都非空)',
+)
+
+mutate(
+    'M34-paging-recomputes-window',
+    'stores/eventStore.js',
+    `        const window = workOrderWindow || activeConditions()`,
+    `        const window = activeConditions()`,
+    'P10 翻页沿用同一时间窗(第 2 页的 startTime/endTime 与首屏完全一致)',
+)
+
+mutate(
+    'M35-range-change-keeps-old-window',
+    'stores/eventStore.js',
+    `        workOrderWindow = { startTime: active.startTime, endTime: active.endTime }`,
+    ``,
+    'P11 切到 30 天后请求换成 30 天窗口(且回第 1 页)',
+)
+
+console.log('')
+console.log(`T65B 变异测试:${proved}/${injected} 处变异被对应断言抓住。`)
+rmSync(reportDir, { recursive: true, force: true })
+if (proved < injected) process.exit(1)

+ 291 - 0
.scratch/cockpit-real-data-wayfinding/verify/t65b-work-order-paging.mjs

@@ -0,0 +1,291 @@
+/**
+ * 票 65 补口闸门:报警模式的「当前工单列表」**服务端翻页**真的生效。
+ *
+ * 为什么单独一份:维护者真机反馈「分页没生效,点不到第 2 页」。根因是
+ * `WarningResponsePane.onListPage()` 早先只认 `historyAnalysis`,工单列表画出了翻页控件、
+ * 点了却什么都没发生(事件被 `return` 掉,页码根本没进 store)。
+ *
+ * 这类缺陷**领域层闸门抓不到**:`workOrderListBlock` 的 `serverPage` 是对的、
+ * `PanelBlocks` 的 emit 也是对的,断在「组件 → store」这一跳。所以这里起 pinia + 真实
+ * store + 真实 mock transport,从「点翻页」一路断到「分区换页」。
+ *
+ * 数据用**闸门内现场注入**(12 张工单,每页 5 张 → 3 页),不扩 `contractMocks` 的静态样例
+ * (票 64 裁定:静态样例的扩面会污染其它断言,现场注入是允许的)。
+ *
+ * 用法:node .scratch/cockpit-real-data-wayfinding/verify/t65b-work-order-paging.mjs
+ */
+import assert from 'node:assert/strict'
+import { readFileSync } from 'node:fs'
+import { registerHooks } from 'node:module'
+import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc'
+
+const SRC = '../../../src/modules/cockpit/'
+const readSrc = (rel) => readFileSync(new URL(SRC + rel, import.meta.url), 'utf8')
+
+registerHooks({
+    resolve(specifier, context, nextResolve) {
+        const resolved = nextResolve(specifier, context)
+        const parent = context.parentURL || ''
+        if (parent.endsWith('.vue?client') && resolved.url.endsWith('.vue')) {
+            return { url: `${resolved.url}?client`, format: 'module', shortCircuit: true }
+        }
+        return resolved
+    },
+    load(url, context, nextLoad) {
+        if (url.endsWith('.json')) {
+            return { format: 'module', shortCircuit: true, source: `export default ${readFileSync(new URL(url), 'utf8')}` }
+        }
+        if (url.endsWith('.css')) return { format: 'module', shortCircuit: true, source: 'export default {}' }
+        const isClient = url.endsWith('.vue?client')
+        if (url.endsWith('.vue') || isClient) {
+            const path = isClient ? url.slice(0, -'?client'.length) : url
+            const source = readFileSync(new URL(path), 'utf8')
+            const { descriptor } = parse(source, { filename: path })
+            const script = compileScript(descriptor, { id: 't65b', genDefaultAs: '__sfc__' })
+            const template = compileTemplate({
+                source: descriptor.template.content,
+                filename: path,
+                id: 't65b',
+                ssr: !isClient,
+                compilerOptions: { bindingMetadata: script.bindings },
+            })
+            assert.deepEqual(template.errors, [], `${path} template`)
+            const tail = isClient
+                ? '__sfc__.render = render; export default __sfc__;'
+                : '__sfc__.ssrRender = ssrRender; export default __sfc__;'
+            return { format: 'module', shortCircuit: true, source: `${script.content}\n${template.code}\n${tail}` }
+        }
+        return nextLoad(url, context)
+    },
+})
+
+let passed = 0
+let failed = 0
+function check(name, condition, detail = '') {
+    if (condition) {
+        passed += 1
+        console.log(`PASS ${name}`)
+    } else {
+        failed += 1
+        console.error(`FAIL ${name}: ${detail}`)
+    }
+}
+
+const { createPinia, setActivePinia } = await import('pinia')
+setActivePinia(createPinia())
+
+const { registerMockTransport, resetMockTransportsForTest } = await import(`${SRC}services/cockpitApi.js`)
+const { registerCockpitMocks } = await import(`${SRC}mocks/contractMocks.js`)
+const { builtinConfig } = await import(`${SRC}services/cockpitConfig.js`)
+const { useEventStore } = await import(`${SRC}stores/eventStore.js`)
+const { responseEfficiencyPanel } = await import(`${SRC}domain/warningResponsePanels.js`)
+
+// ---- 现场注入:12 张工单,每页 5 张 → 3 页;服务端真的按 page 切窗口 ----
+const TOTAL = 12
+const ORDERS = Array.from({ length: TOTAL }, (_, index) => ({
+    workOrderCode: `WO-${String(index + 1).padStart(3, '0')}`,
+    deviceCode: '21119740',
+    orderLevel: 2,
+    orderDesc: `注入工单 ${index + 1}`,
+    stateCode: index % 8,
+    stateName: '待派单',
+    createTime: '2026-10-14T13:43:50+08:00',
+    updateTime: '2026-10-14T13:43:50+08:00',
+    planFinishTime: null,
+    realFinishTime: null,
+    eventAssociation: null,
+    associatedWithCurrentEvent: false,
+}))
+const requests = []
+function queryOf(path) {
+    const query = {}
+    const search = String(path || '').split('?')[1] || ''
+    for (const [key, value] of new URLSearchParams(search).entries()) query[key] = value
+    return query
+}
+async function workOrderListPayload(query) {
+    const page = Math.max(1, Number(query.page) || 1)
+    const pageSize = Math.max(1, Number(query.pageSize) || 5)
+    const totalPages = Math.max(1, Math.ceil(TOTAL / pageSize))
+    // 模拟后端把越界页码夹回最后一页(与真接口 `loadAll` 的行为一致)
+    const safePage = Math.min(page, totalPages)
+    requests.push({ page, pageSize, startTime: query.startTime || '', endTime: query.endTime || '' })
+    // 第 2 页刻意慢回:P7「快速连点」要能观察到迟到响应(否则两条都按请求顺序回来,变异抓不住)
+    if (safePage === 2) await new Promise((resolve) => setTimeout(resolve, 40))
+    return {
+        requestId: 'injected',
+        apiVersion: 'v1',
+        readOnly: true,
+        state: 'READY',
+        stateReason: 'READY',
+        stateDictionary: [],
+        workOrders: ORDERS.slice((safePage - 1) * pageSize, safePage * pageSize),
+        pagination: { page: safePage, pageSize, total: TOTAL, totalPages },
+    }
+}
+
+resetMockTransportsForTest()
+registerCockpitMocks({ scenario: 'success', handlers: { registerMockTransport, resetMockTransportsForTest } })
+// 覆盖 '/work-orders':本组要的是「多页」,静态样例只有 4 张(1 页)。
+registerMockTransport('/work-orders', async (context) => workOrderListPayload(queryOf(context.path)))
+
+const store = useEventStore()
+store.configure({ config: builtinConfig })
+
+// ---- P1 首屏:第 1 页、5 张、共 12 张 / 3 页 ----
+await store.loadPanels({ mode: 'ALARM', range: '7d', industry: '' })
+const first = store.sections.workOrderList
+check(
+    'P1 首屏工单列表是第 1 页(5 张 / 共 12 张 / 3 页),翻页控件该出现',
+    first.loaded === true &&
+        first.data.orders.length === 5 &&
+        first.data.pagination.page === 1 &&
+        first.data.pagination.total === 12 &&
+        first.data.pagination.totalPages === 3 &&
+        store.workOrderPage === 1,
+    `orders=${first.data && first.data.orders.length} pagination=${JSON.stringify(first.data && first.data.pagination)}`,
+)
+
+// ---- P2 点「下一页」→ 真的换到第 2 页 ----
+const othersBefore = {
+    currentEvents: store.sections.currentEvents,
+    historyAnalysis: store.sections.historyAnalysis,
+    responseEfficiency: store.sections.responseEfficiency,
+    eventAnalysis: store.sections.eventAnalysis,
+}
+requests.length = 0
+await store.setWorkOrderPage(2)
+const second = store.sections.workOrderList
+check(
+    'P2 翻到第 2 页:请求带 page=2、分区换成 6–10 号工单',
+    requests.length === 1 &&
+        requests[0].page === 2 &&
+        second.data.orders.length === 5 &&
+        second.data.orders[0].code === 'WO-006' &&
+        second.data.pagination.page === 2 &&
+        store.workOrderPage === 2,
+    `requests=${JSON.stringify(requests)} firstRow=${second.data && second.data.orders[0] && second.data.orders[0].code}`,
+)
+check(
+    'P3 翻工单页只重取工单分区(其它四块引用未变,不被牵连重取)',
+    store.sections.currentEvents === othersBefore.currentEvents &&
+        store.sections.historyAnalysis === othersBefore.historyAnalysis &&
+        store.sections.responseEfficiency === othersBefore.responseEfficiency &&
+        store.sections.eventAnalysis === othersBefore.eventAnalysis,
+    '翻工单页把其它分区也换掉了',
+)
+
+// ---- P4 重复点同一页:不重复请求 ----
+requests.length = 0
+await store.setWorkOrderPage(2)
+check('P4 重复点同一页不重复请求(幂等)', requests.length === 0, `requests=${requests.length}`)
+
+// ---- P5 越界页码:按后端回显夹回最后一页 ----
+requests.length = 0
+await store.setWorkOrderPage(9)
+const clamped = store.sections.workOrderList
+check(
+    'P5 越界页码夹回最后一页(第 3 页,2 张),页码状态与回显一致',
+    clamped.data.pagination.page === 3 &&
+        clamped.data.orders.length === 2 &&
+        store.workOrderPage === 3,
+    `page=${clamped.data && clamped.data.pagination.page} rows=${clamped.data && clamped.data.orders.length} storePage=${store.workOrderPage}`,
+)
+
+// ---- P6 条件变化:页码回第 1 页(条文 5)----
+await store.loadPanels({ mode: 'ALARM', range: '30d', industry: '' })
+check(
+    'P6 条件变化(切范围)后工单页码回第 1 页',
+    store.workOrderPage === 1 && store.sections.workOrderList.data.pagination.page === 1,
+    `storePage=${store.workOrderPage}`,
+)
+
+// ---- P7 迟到响应丢弃(独立令牌:快速连点第 2、3 页,只认最后一页)----
+requests.length = 0
+const slow = store.setWorkOrderPage(2)
+const fast = store.setWorkOrderPage(3)
+await Promise.all([slow, fast])
+check(
+    'P7 快速连点只认最后一页(迟到响应丢弃,独立令牌)',
+    store.sections.workOrderList.data.pagination.page === 3 && store.workOrderPage === 3,
+    `page=${store.sections.workOrderList.data.pagination.page} storePage=${store.workOrderPage}`,
+)
+
+// ---- P9–P11 时间窗(2026-10-22 维护者反馈:列表没按时间筛)----
+//
+// 面板按近 7/30/90 天切换时,列表必须与上方「工单状态分布」统计同一批工单。
+// 断言的是**请求真的带上了当前窗口**(不是「界面看起来对」)。
+{
+    requests.length = 0
+    await store.loadPanels({ mode: 'ALARM', range: '7d', industry: '' })
+    const seven = requests[0] || {}
+    const active7d = store.range
+    check(
+        'P9 首屏工单列表请求带当前窗口(7 天的 startTime/endTime 都非空)',
+        active7d === '7d' && Boolean(seven.startTime) && Boolean(seven.endTime) &&
+            // 7 天窗口:两端相差应约等于 7 天(允许整点取整带来的 1 小时内偏差)
+            Math.abs((Date.parse(seven.endTime) - Date.parse(seven.startTime)) - 7 * 86400000) <= 3600000,
+        'start=' + seven.startTime + ' end=' + seven.endTime,
+    )
+    const window7d = { start: seven.startTime, end: seven.endTime }
+
+    // 翻页必须沿用同一窗口(少了它第 2 页会悄悄变回全库口径)
+    requests.length = 0
+    await store.setWorkOrderPage(2)
+    const page2 = requests[0] || {}
+    check(
+        'P10 翻页沿用同一时间窗(第 2 页的 startTime/endTime 与首屏完全一致)',
+        page2.startTime === window7d.start && page2.endTime === window7d.end,
+        'p2 start=' + page2.startTime + ' end=' + page2.endTime + ' vs 首屏 ' + window7d.start + '/' + window7d.end,
+    )
+
+    // 切范围必须换窗口并回第 1 页
+    requests.length = 0
+    await store.loadPanels({ mode: 'ALARM', range: '30d', industry: '' })
+    const thirty = requests[0] || {}
+    check(
+        'P11 切到 30 天后请求换成 30 天窗口(且回第 1 页)',
+        Boolean(thirty.startTime) && thirty.startTime !== window7d.start &&
+            Math.abs((Date.parse(thirty.endTime) - Date.parse(thirty.startTime)) - 30 * 86400000) <= 3600000 &&
+            thirty.page === 1,
+        'start=' + thirty.startTime + ' end=' + thirty.endTime + ' page=' + thirty.page,
+    )
+}
+// ---- P8 预警模式不画工单列表,但也不因缺它而报错 ----
+const warningView = responseEfficiencyPanel(
+    { family: 'WARNING', totalEvents: 3, linkedWorkOrderCount: 0, missingWorkOrderCount: 3, levelDistribution: [], eventTrend: [] },
+    { state: 'success', loaded: true, data: {} },
+    { mode: 'WARNING', workOrderList: store.sections.workOrderList },
+)
+check(
+    'P8 预警模式仍不出现工单列表(翻页接线不影响两族分流)',
+    !warningView.blocks.some((block) => block.kind === 'list' && block.label === '当前工单列表'),
+    `blocks=${warningView.blocks.map((b) => b.label).join('/')}`,
+)
+
+// ---- S 组:接线(这一跳正是当初断掉的地方)----
+const paneSource = readSrc('components/WarningResponsePane.vue')
+check(
+    'S1 面板把报警效率的翻页事件交给 store(不再只认 historyAnalysis)',
+    /panelId === 'responseEfficiency'[\s\S]{0,120}setWorkOrderPage/.test(paneSource),
+    'onListPage 没有 responseEfficiency 分支 ⇒ 点了翻页没反应(本次缺陷)',
+)
+check(
+    'S2 store 暴露 setWorkOrderPage(组件才调得到)',
+    /setWorkOrderPage,/.test(readSrc('stores/eventStore.js')),
+    'store 返回表里没有 setWorkOrderPage',
+)
+check(
+    'S3 工单页码用独立令牌(翻工单页不作废四块面板的在途响应)',
+    /let workOrderToken = 0/.test(readSrc('stores/eventStore.js')) &&
+        !/async function setWorkOrderPage[\s\S]{0,400}token \+= 1/.test(readSrc('stores/eventStore.js')),
+    'setWorkOrderPage 用了全局面板令牌',
+)
+
+console.log('')
+console.log(`T65B 工单列表翻页:${passed}/${passed + failed} 项通过。`)
+if (failed) {
+    console.log('未通过项见上方 FAIL。')
+    process.exit(1)
+}
+console.log('T65B 全部通过(浏览器走查按 AGENTS.md 未自动执行)。')

+ 307 - 0
.scratch/cockpit-real-data-wayfinding/verify/t75-90d-multi-window.mjs

@@ -0,0 +1,307 @@
+/**
+ * 票 75 离线闸门:报警效率与预警分析支持近 90 天(分窗取数 + 前端合并)。
+ *
+ * 背景:后端三个聚合端点契约都是「单请求 ≤31 天」(实测 90 天显式窗口 →
+ * `400 efficiency window must not exceed 31 days`);`history-analysis` 前端已用
+ * `historyWindows` 分窗合并(票 61),但 `fetchHandlingEfficiency`/`fetchEventAnalysis`
+ * 遇 >31 天直接返回 UNAVAILABLE,界面挂「最多支持31天」提示 —— 维护者要求 90 天出真实数据。
+ *
+ * 证据载荷:2026-10-21 对 192.168.110.96:8301 按 30 天切窗实取的 4 窗响应
+ * (`artifacts/t75/`,WARNING/ALARM 效率各 4 窗 + 分析 4 窗)。全部离线断言,不联网。
+ *
+ * 断言分组:
+ *   A  效率合并:计数求和 / 状态分布按码求和 / 处置率重算 / 趋势时序拼接去重 / 事件页拼接
+ *   B  分析合并:totalCount 求和 / 维度按码合并 / bucket 求和 + percentage 重算(不是相加)
+ *   C  服务级:真实 fetchAggregated 路径(注入 fetchImpl 按窗分发)单窗零变化 + 多窗走合并
+ *   D  提示撤除:31 天提示不再出现(不再是事实,就是误导)
+ *
+ * 变异有牙见 t75-mutations.mjs。
+ * 运行:node .scratch/cockpit-real-data-wayfinding/verify/t75-90d-multi-window.mjs
+ */
+import assert from 'node:assert/strict'
+import { readFileSync } from 'node:fs'
+import { registerHooks } from 'node:module'
+import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc'
+
+const SRC = '../../../src/modules/cockpit/'
+const readVerifier = (rel) => readFileSync(new URL(rel, import.meta.url), 'utf8')
+
+registerHooks({
+    resolve(specifier, context, nextResolve) {
+        const resolved = nextResolve(specifier, context)
+        const parent = context.parentURL || ''
+        if (parent.endsWith('.vue?client') && resolved.url.endsWith('.vue')) {
+            return { url: `${resolved.url}?client`, format: 'module', shortCircuit: true }
+        }
+        return resolved
+    },
+    load(url, context, nextLoad) {
+        if (url.endsWith('.json')) {
+            return { format: 'module', shortCircuit: true, source: `export default ${readFileSync(new URL(url), 'utf8')}` }
+        }
+        if (url.endsWith('.css')) return { format: 'module', shortCircuit: true, source: 'export default {}' }
+        const isClient = url.endsWith('.vue?client')
+        if (url.endsWith('.vue') || isClient) {
+            const path = isClient ? url.slice(0, -'?client'.length) : url
+            const source = readFileSync(new URL(path), 'utf8')
+            const { descriptor } = parse(source, { filename: path })
+            const script = compileScript(descriptor, { id: 't75', genDefaultAs: '__sfc__' })
+            const template = compileTemplate({
+                source: descriptor.template.content,
+                filename: path,
+                id: 't75',
+                ssr: !isClient,
+                compilerOptions: { bindingMetadata: script.bindings },
+            })
+            assert.deepEqual(template.errors, [], `${path} template`)
+            const tail = isClient
+                ? '__sfc__.render = render; export default __sfc__;'
+                : '__sfc__.ssrRender = ssrRender; export default __sfc__;'
+            return { format: 'module', shortCircuit: true, source: `${script.content}\n${template.code}\n${tail}` }
+        }
+        return nextLoad(url, context)
+    },
+})
+
+const { fetchEventAnalysis, fetchHandlingEfficiency, mergeEfficiencyPayloads, mergeAnalysisPayloads } = await import(`${SRC}services/warningResponseApi.js`)
+const { toHandlingEfficiency, toEventAnalysis } = await import(`${SRC}domain/warningResponsePanels.js`)
+const { SECTION_STATE } = await import(`${SRC}domain/sectionState.js`)
+const { eventTimeWindow } = await import(`${SRC}domain/eventRange.js`)
+
+let passed = 0
+let failed = 0
+function check(name, condition, detail = '') {
+    if (condition) {
+        passed += 1
+        console.log(`PASS ${name}`)
+    } else {
+        failed += 1
+        console.error(`FAIL ${name}: ${detail}`)
+    }
+}
+
+const loadWindow = (name) => JSON.parse(readVerifier(`./artifacts/t75/${name}.json`)).data
+const efficiencyWindows = ['he-w1-WARNING', 'he-w2-WARNING', 'he-w3-WARNING', 'he-w4-WARNING'].map(loadWindow)
+const alarmWindows = ['he-w1-ALARM', 'he-w2-ALARM', 'he-w3-ALARM', 'he-w4-ALARM'].map(loadWindow)
+const analysisWindows = ['md-w1', 'md-w2', 'md-w3', 'md-w4'].map(loadWindow)
+const FULL_RANGE = { startTime: '2026-07-22T16:00:00.000Z', endTime: '2026-10-21T08:00:00.000Z' }
+
+// ---- A. 效率合并(服务真实合并函数,经 toHandlingEfficiency 出口)----
+{
+    const mergedDomain = toHandlingEfficiency(
+        // merge 是模块私有函数;从服务导出的唯一入口是 fetchAggregated —— 这里直接用
+        // `fetchHandlingEfficiency` 注入 fetchImpl 分窗分发(见 C 组),本组先用域出口核数字。
+        // 为了直接测合并产物,这里通过服务再导出的最小途径拿不到 merge,所以用
+        // fetchHandlingEfficiency + fetchImpl 的方式(C 组覆盖服务路径),
+        // 本组改为直接断言域转换对合并载荷新增字段的处理 —— 合并器本身在 C 组全链路钉住。
+        // 为让本组独立成立,这里手搓合并等价载荷(与 C 组服务输出互证)。
+        mergeEfficiencyPayloads(efficiencyWindows, FULL_RANGE),
+    )
+
+    check(
+        'A1 计数跨窗求和(totalEvents=0+0+13+0=13,不由单窗冒充 90 天)',
+        mergedDomain.totalEvents === 13,
+        `totalEvents=${mergedDomain.totalEvents}`,
+    )
+    check(
+        'A2 趋势按时序拼接且去重(720×3+17=2177 个小时桶,升序)',
+        mergedDomain.eventTrend.length === 2177 &&
+            mergedDomain.eventTrend.every((row, index, rows) => index === 0 || String(rows[index - 1].bucket) <= String(row.bucket)),
+        `buckets=${mergedDomain.eventTrend.length}`,
+    )
+    check(
+        'A3 处置率用合并后状态计数重算(分子=已结案4,分母=可计入9 → 44.44%)',
+        mergedDomain.numerator === 4 && mergedDomain.denominator === 9 && mergedDomain.handlingRate === 0.4444,
+        `num=${mergedDomain.numerator} den=${mergedDomain.denominator} rate=${mergedDomain.handlingRate}`,
+    )
+    check(
+        'A4 事件页各窗拼接(13 条全部到位;「已返回页 vs 全量」标记与页数一致)',
+        mergedDomain.levelDistributionComplete === (13 >= 13) &&
+            mergedDomain.levelDistribution.reduce((sum, row) => sum + row.count, 0) === 13,
+        `complete=${mergedDomain.levelDistributionComplete} sum=${mergedDomain.levelDistribution.reduce((s, r) => s + r.count, 0)}`,
+    )
+    check(
+        'A5 无结案样本时平均时长如实占位(不画 0 冒充)',
+        mergedDomain.closeDuration === null,
+        `closeDuration=${JSON.stringify(mergedDomain.closeDuration)}`,
+    )
+
+    // ALARM 交叉验证(数字不同,防「只对 WARNING 硬编码」)
+    const alarmMerged = toHandlingEfficiency(mergeEfficiencyPayloads(alarmWindows, FULL_RANGE))
+    check(
+        'A6 ALARM 族同样合并(totalEvents=90,处置率=已结案4/可计入9)',
+        alarmMerged.totalEvents === 90 && alarmMerged.numerator === 4 && alarmMerged.denominator === 9,
+        `total=${alarmMerged.totalEvents} num=${alarmMerged.numerator} den=${alarmMerged.denominator}`,
+    )
+}
+
+// ---- B. 分析合并 ----
+{
+    // 分析合并在**原始载荷**层断言(TIME 维度不进域对象;types/levels 等才是域出口)
+    const mergedRaw = mergeAnalysisPayloads(analysisWindows, FULL_RANGE)
+    const timeDim = (mergedRaw.dimensions || []).find((row) => row.dimensionCode === 'TIME')
+    check(
+        'B1 totalCount 跨窗求和(13)',
+        mergedRaw.totalCount === 13,
+        `totalCount=${mergedRaw.totalCount}`,
+    )
+    check(
+        'B2 维度按 dimensionCode 合并(7 个维度一个不少)',
+        (mergedRaw.dimensions || []).length === 7,
+        `dims=${(mergedRaw.dimensions || []).map((row) => row.dimensionCode).join(',')}`,
+    )
+    check(
+        'B3 时间维度按时序拼接(91 个日桶=30+30+30+当日,升序;窗口相接不重叠)',
+        !!timeDim && timeDim.buckets.length === 91 &&
+            timeDim.buckets.every((row, index, rows) => index === 0 || String(rows[index - 1].bucketCode) <= String(row.bucketCode)),
+        `timeBuckets=${timeDim && timeDim.buckets.length}`,
+    )
+    check(
+        'B4 percentage 重算而不是各窗相加(EVENT_TYPE 首桶 = count/totalCount×100)',
+        (mergedRaw.dimensions || []).every((row) => row.buckets.every((bucket) => {
+            const expected = row.totalCount > 0 ? ((bucket.count / row.totalCount) * 100).toFixed(2) : '0.00'
+            return bucket.percentage === expected
+        })),
+        `sample=${JSON.stringify(((mergedRaw.dimensions || [])[0] || {}).buckets && mergedRaw.dimensions[0].buckets[0])}`,
+    )
+    const mergedDomain = toEventAnalysis(mergedRaw)
+    check(
+        'B5 域出口消费合并载荷(类型合计 13)',
+        (mergedDomain.types || []).reduce((sum, row) => sum + row.count, 0) === 13,
+        `sum=${(mergedDomain.types || []).reduce((s, r) => s + r.count, 0)}`,
+    )
+}
+
+// ---- C. 服务级:真实 fetchAggregated 路径(fetchImpl 按 URL 分发录制的窗口载荷)----
+{
+    // 服务按 **now** 算窗口(eventTimeWindow → historyWindows),闸门用同一套函数算出
+    // 预期的请求键,再把录制的窗口载荷挂到这些键上 —— 键对齐才能命中。
+    const { historyWindows } = await import(`${SRC}domain/eventRange.js`)
+    const expected90 = historyWindows(eventTimeWindow('LAST_90_DAYS').startTime, eventTimeWindow('LAST_90_DAYS').endTime)
+    // 最后一窗的 endTime 是 **now 基准**(每次调用毫秒级变化),不能进键;
+    // 窗口相接不重叠 → **startTime 唯一**,按它分发即可。
+    const windowByUrl = new Map()
+    const register = (payloads) => {
+        payloads.forEach((payload, index) => {
+            const window = expected90[index]
+            if (window) windowByUrl.set(window.startTime, payload)
+        })
+    }
+    register(efficiencyWindows)
+    const responses = []
+    const fetchImpl = async (url) => {
+        const startTime = decodeURIComponent(/startTime=([^&]+)/.exec(url)[1])
+        const payload = windowByUrl.get(startTime)
+        responses.push(url)
+        if (!payload) return { ok: false, status: 500, json: async () => ({}) }
+        return { ok: true, status: 200, json: async () => ({ code: 200, msg: 'ok', data: payload }) }
+    }
+    const options = {
+        mode: 'WARNING',
+        rangeType: 'LAST_90_DAYS',
+        config: { dataSource: 'real', api: { baseUrl: 'http://contract.invalid' } },
+        fetchImpl,
+    }
+    const result = await fetchHandlingEfficiency(options)
+    check(
+        'C1 90 天触发分窗:恰好 4 次请求、每次 ≤30 天(不是一次打 90 天撞 400)',
+        responses.length === 4,
+        `requests=${responses.length}`,
+    )
+    check(
+        'C2 合并分区 SUCCESS 且域对象带 90 天真实计数(totalEvents=13)',
+        result.state === SECTION_STATE.SUCCESS && result.data && result.data.totalEvents === 13,
+        `state=${result.state} total=${result.data && result.data.totalEvents}`,
+    )
+
+    // 单窗(7 天)零变化:仍然只发 1 次请求、走 toHandlingEfficiency 直转
+    responses.length = 0
+    const single = await fetchHandlingEfficiency({
+        ...options,
+        rangeType: 'LAST_7_DAYS',
+        // 7 天窗口按 now 算,录制载荷里没有对应键 —— 单窗路径不查表,直接回第一窗载荷即可
+        fetchImpl: async (url) => {
+            responses.push(url)
+            return { ok: true, status: 200, json: async () => ({ code: 200, msg: 'ok', data: efficiencyWindows[0] }) }
+        },
+    })
+    check(
+        'C3 单窗零变化(7 天仍走原路径:1 次请求,不因重构变味)',
+        responses.length === 1 && single.state === SECTION_STATE.SUCCESS,
+        `requests=${responses.length} state=${single.state}`,
+    )
+
+    // 任一窗失败 → 整分区 FAILED(不把 3/4 窗冒充 90 天)
+    const partial = await fetchHandlingEfficiency({
+        ...options,
+        fetchImpl: async (url) => {
+            const startTime = decodeURIComponent(/startTime=([^&]+)/.exec(url)[1])
+            if (startTime === expected90[2].startTime) {
+                return { ok: false, status: 500, json: async () => ({}) }
+            }
+            const payload = windowByUrl.get(startTime)
+            return payload
+                ? { ok: true, status: 200, json: async () => ({ code: 200, msg: 'ok', data: payload }) }
+                : { ok: false, status: 500, json: async () => ({}) }
+        },
+    })
+    check(
+        'C4 一窗失败 → 整分区 FAILED(不拿 3/4 窗冒充 90 天)',
+        partial.state === SECTION_STATE.FAILED,
+        `state=${partial.state}`,
+    )
+
+    // 分析端点同样分窗(键同上:按 startTime 分发,末窗 endTime 是 now 基准不能进键)
+    const analysisByUrl = new Map()
+    analysisWindows.forEach((payload, index) => {
+        const window = expected90[index]
+        if (window) analysisByUrl.set(window.startTime, payload)
+    })
+    const analysisResult = await fetchEventAnalysis({
+        ...options,
+        fetchImpl: async (url) => {
+            const startTime = decodeURIComponent(/startTime=([^&]+)/.exec(url)[1])
+            const payload = analysisByUrl.get(startTime)
+            return payload
+                ? { ok: true, status: 200, json: async () => ({ code: 200, msg: 'ok', data: payload }) }
+                : { ok: false, status: 500, json: async () => ({}) }
+        },
+    })
+    check(
+        'C5 预警分析 90 天也分窗合并(域对象类型合计 13)',
+        analysisResult.state === SECTION_STATE.SUCCESS &&
+            (analysisResult.data.types || []).reduce((sum, row) => sum + row.count, 0) === 13,
+        `state=${analysisResult.state} sum=${(analysisResult.data.types || []).reduce((s, r) => s + r.count, 0)}`,
+    )
+}
+
+// ---- D. 提示撤除 ----
+{
+    const paneSource = readFileSync(new URL('../../../src/modules/cockpit/components/WarningResponsePane.vue', import.meta.url), 'utf8')
+    check(
+        'D1 「最多支持31天」提示已撤(不再成立;误导比没有提示更糟)',
+        !/最多支持31天/.test(paneSource),
+        'pane 里仍残留 31 天提示',
+    )
+    const apiSource = readFileSync(new URL('../../../src/modules/cockpit/services/warningResponseApi.js', import.meta.url), 'utf8')
+    check(
+        'D2 unsupportedWindow 拦截已移除(90 天不再被前端自己挡掉)',
+        !/unsupportedWindow/.test(apiSource),
+        'warningResponseApi 仍有 unsupportedWindow',
+    )
+}
+
+
+function countOf(value) {
+    const n = Number(value)
+    return Number.isFinite(n) ? n : 0
+}
+
+
+console.log('')
+console.log(`T75 90 天分窗合并:${passed}/${passed + failed} 项通过。`)
+if (failed) {
+    console.log('未通过项见上方 FAIL。')
+    process.exit(1)
+}
+console.log('T75 全部通过(浏览器走查按 AGENTS.md 未自动执行)。')

+ 179 - 0
.scratch/cockpit-real-data-wayfinding/verify/t75-mutations.mjs

@@ -0,0 +1,179 @@
+/**
+ * 票 75 变异测试:证明 90 天分窗合并的每条关键断言抓得住它要防的缺陷。
+ *
+ *   M1 合并丢趋势(只留首窗)        → A2 必须红(90 天只显示 30 天)
+ *   M2 计数不求和(取首窗)          → A1 必须红(13 变 0)
+ *   M3 处置率不重算(抄首窗百分比)   → A3 必须红(0% 冒充 44%)
+ *   M4 分析 bucket 求和丢失          → B5 必须红(类型合计变 1 窗)
+ *   M5 百分比相加不重算              → B4 必须红(50%+25%≠75% 的老错误)
+ *   M6 分窗被拆掉(>31 天仍单请求)   → C1 必须红(撞 400,界面「最多31天」复现)
+ *   M7 一窗失败仍合并其余            → C4 必须红(3/4 窗冒充 90 天)
+ *   M8 31 天提示复活                 → D1 必须红(误导性提示回屏)
+ *
+ * 运行:node .scratch/cockpit-real-data-wayfinding/verify/t75-mutations.mjs
+ */
+import assert from 'node:assert/strict'
+import { closeSync, mkdtempSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import { spawnSync } from 'node:child_process'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
+const GATE = fileURLToPath(new URL('t75-90d-multi-window.mjs', import.meta.url))
+const srcPath = (rel) => `${repoRoot}src/modules/cockpit/${rel}`
+
+const reportDir = mkdtempSync(join(tmpdir(), 't75-mutations-'))
+function runGate() {
+    const outPath = join(reportDir, 'stdout.txt')
+    const errPath = join(reportDir, 'stderr.txt')
+    const out = openSync(outPath, 'w')
+    const err = openSync(errPath, 'w')
+    let status = 0
+    try {
+        status = spawnSync(process.execPath, [GATE], { stdio: ['ignore', out, err] }).status
+    } finally {
+        closeSync(out)
+        closeSync(err)
+    }
+    return { output: `${readFileSync(outPath, 'utf8')}${readFileSync(errPath, 'utf8')}`, status }
+}
+
+function failingNames(output) {
+    return [...output.matchAll(/^FAIL ([^:]+):/gm)].map((match) => match[1])
+}
+
+let injected = 0
+let proved = 0
+function mutate(label, file, from, to, expectRed) {
+    const path = srcPath(file)
+    const original = readFileSync(path, 'utf8')
+    const candidates = [from, from.replace(/\n/g, '\r\n')]
+    const needle = candidates.find((candidate) => original.includes(candidate))
+    if (!needle) {
+        console.error(`FAIL ${label}: 变异源未命中(文件形状已变,需人工更新变异脚本)`)
+        injected += 1
+        return
+    }
+    injected += 1
+    writeFileSync(path, original.replace(needle, to), 'utf8')
+    runAndJudge(label, original, path, expectRed)
+}
+
+function runAndJudge(label, original, path, expectRed) {
+    let caught = false
+    let how = ''
+    try {
+        const { output, status } = runGate()
+        const red = failingNames(output)
+        if (red.includes(expectRed)) {
+            caught = true
+            how = `${expectRed} 转红`
+        } else if (status !== 0 && /does not provide an export|SyntaxError|Cannot find (module|export)/.test(output)) {
+            caught = true
+            how = '闸门整体加载失败(导出缺失/语法错)— 崩溃式抓住'
+        } else if (status !== 0) {
+            how = `退出码 ${status} 但目标断言未红(红项:${red.join(', ') || '无'})— 判据不明,按未抓住记`
+        } else {
+            how = '未转红(假绿!闸门仍全绿)'
+        }
+        console.log(`${caught ? 'PASS' : 'FAIL'}  ${label} → ${how}`)
+        if (caught) proved += 1
+    } finally {
+        writeFileSync(path, original, 'utf8')
+        assert.equal(readFileSync(path, 'utf8'), original, `${label}: 还原失败`)
+    }
+}
+
+// ---- 效率合并(services/warningResponseApi.js 的 mergeEfficiencyPayloads)----
+
+mutate(
+    'M1-efficiency-merge-drops-trend',
+    'services/warningResponseApi.js',
+    `        for (const row of Array.isArray(payload.durationTrend) ? payload.durationTrend : []) {
+            if (seenBuckets.has(row.timeBucket)) continue
+            seenBuckets.add(row.timeBucket)
+            durationTrend.push(row)
+        }`,
+    ``,
+    'A2 趋势按时序拼接且去重(720×3+17=2177 个小时桶,升序)',
+)
+
+mutate(
+    'M2-efficiency-counts-take-last-window',
+    'services/warningResponseApi.js',
+    `        totalEvents += countOf(summary.totalEvents)`,
+    `        totalEvents = countOf(summary.totalEvents)`,
+    // 注:`+=` 变 `||=` 的写法对本数据是**等价变异**(前两窗为 0),不采用;取「末窗覆盖」才可观测。
+    'A1 计数跨窗求和(totalEvents=0+0+13+0=13,不由单窗冒充 90 天)',
+)
+
+mutate(
+    'M3-handling-rate-copied-not-recomputed',
+    'services/warningResponseApi.js',
+    `    const numerator = stateCounts.filter((row) => row.stateCode === 5).reduce((sum, row) => sum + row.count, 0)`,
+    `    const numerator = countOf((list[0].summary && list[0].summary.handlingEfficiency || {}).numerator)`,
+    'A3 处置率用合并后状态计数重算(分子=已结案4,分母=可计入9 → 44.44%)',
+)
+
+// ---- 分析合并(mergeAnalysisPayloads)----
+
+mutate(
+    'M4-analysis-total-count-takes-last-window',
+    'services/warningResponseApi.js',
+    `        totalCount += countOf(payload.totalCount)`,
+    `        totalCount = countOf(payload.totalCount)`,
+    // 注 1:维度级 `entry.totalCount` 的「末窗覆盖」对本数据等价(w3=13、w4 无该维度),
+    // 不可观测;B1 断言的是**载荷级** totalCount(末窗 w4=0 → 0 ≠ 13,才可观测)。
+    // 注 2:w1/w2 的维度桶为空数组,"丢弃后窗同码桶"同样不可观测。
+    'B1 totalCount 跨窗求和(13)',
+)
+
+mutate(
+    'M5-analysis-percentage-added-not-recomputed',
+    'services/warningResponseApi.js',
+    `                percentage: entry.totalCount > 0 ? ((bucket.count / entry.totalCount) * 100).toFixed(2) : '0.00',`,
+    `                percentage: bucket.percentage,`,
+    'B4 percentage 重算而不是各窗相加(EVENT_TYPE 首桶 = count/totalCount×100)',
+)
+
+// ---- 分窗机制 ----
+
+mutate(
+    'M6-window-splitting-removed',
+    'services/warningResponseApi.js',
+    `    const windows = historyWindows(range.startTime, range.endTime)
+    if (windows.length <= 1) {`,
+    `    const windows = historyWindows(range.startTime, range.endTime)
+    if (true) {`,
+    'C1 90 天触发分窗:恰好 4 次请求、每次 ≤30 天(不是一次打 90 天撞 400)',
+)
+
+mutate(
+    'M7-partial-failure-still-merges',
+    'services/warningResponseApi.js',
+    `        const failed = results.find((result) => ![SECTION_STATE.SUCCESS, SECTION_STATE.STALE, SECTION_STATE.EMPTY].includes(result.state))
+        if (failed) return failed`,
+    `        // 变异:窗失败被吞掉,拿其余窗冒充全量
+        const failed = null
+        if (failed) return failed`,
+    'C4 一窗失败 → 整分区 FAILED(不拿 3/4 窗冒充 90 天)',
+)
+
+// ---- 提示 ----
+
+mutate(
+    'M8-31d-note-resurrected',
+    'components/WarningResponsePane.vue',
+    `        <p v-if="!eventStore.loading && section && section.reason && /历史接口回显/.test(section.reason)" class="warning-response-pane__range-status" role="alert">{{ section.reason }}</p>`,
+    `        <p v-if="!eventStore.loading && eventStore.range === '90d' && panelId !== 'historyAnalysis'" class="warning-response-pane__range-status" role="status">
+            当前接口最多支持31天统计,请选择近7天或近30天;近90天历史趋势可正常查询。
+        </p>
+        <p v-if="!eventStore.loading && section && section.reason && /历史接口回显/.test(section.reason)" class="warning-response-pane__range-status" role="alert">{{ section.reason }}</p>`,
+    'D1 「最多支持31天」提示已撤(不再成立;误导比没有提示更糟)',
+)
+
+console.log('')
+console.log(`T75 变异测试:${proved}/${injected} 处变异被对应断言抓住。`)
+rmSync(reportDir, { recursive: true, force: true })
+if (proved < injected) process.exit(1)

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů