Bladeren bron

fix(cockpit): district/industry filter coverage — audit, honest notes, pipe layer by code-prefix district (#67)

票 67:逐端点实测片区/行业的生效面,能接的接上、接不上的上屏说明。

- 新增 domain/filterCaliber.js:筛选生效面的单一措辞来源(11 块未接线面板 + 5 处行业缺口 + 管线那句),
  由 panelViews.panelView() 出口统一挂到 gap(不逐面板各写一句)。
- services/warningResponseApi.js:事件族只有 /events/multidimensional-analysis 真收 district(实测),
  只给它接;current/history/efficiency 一律不下发(给不认的端点塞参数是伪装的筛选)。
- stores/eventStore.js:scope 进条件键(换片区三个页码回第 1 页,定时刷新不复位)。
- CockpitShell.vue:watch(scope) 补上原本整条旁路的 eventStore 重载;启动与周期刷新带 scope;
  状态详情新增管线片区说明(data-pipe-district-note)。
- domain/pipeNetwork.js + stores/pipeNetworkStore.js(维护者 2026-10-23 更正):管线片区并非不可得 ——
  设施编码是「片区+行业+时间编码」,按编码前缀本地推导并裁剪(PIPE_DISTRICT_PREFIX/pipeDistrictOf),
  推不出片区的对象选具体片区时排除并计数(districtUnresolvedCount)。
- domain/eventList.js + components/EventListPane.vue:当前预警面板的片区缺口说明(data-panel-gap)。
- domain/deviceMetricSources.js:顺带修 deviceMetricKind(null) 崩溃(档案分区 EMPTY 时打断设备详情聚合)。
- 闸门 .scratch/.../verify/t67-filter-caliber.mjs 77/77;变异 t67-mutations.mjs 14/14(含伪造 district、
  管线谎称已筛、前缀映射错位、推不出片区被静默保留四条反向变异)。证据落盘 verify/artifacts/t67/。
- 后端契约缺口另立票 77(事件族三端点补 district、几何下发片区、work-orders 补 industry)。
ChenJZ 8 uur geleden
bovenliggende
commit
264f22f1b2
100 gewijzigde bestanden met toevoegingen van 1075 en 0 verwijderingen
  1. 294 0
      .scratch/cockpit-real-data-wayfinding/issues/67-district.md
  2. 95 0
      .scratch/cockpit-real-data-wayfinding/issues/77-untitled.md
  3. 30 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/README.md
  4. 11 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/compile-selftest.mjs
  5. 47 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/diff-district.py
  6. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-analysis-bad.json
  7. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-analysis-drn.json
  8. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-analysis-gas.json
  9. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-analysis-none.json
  10. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-current-bad.json
  11. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-current-drn.json
  12. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-current-gas.json
  13. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-current-none.json
  14. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-efficiency-bad.json
  15. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-efficiency-drn.json
  16. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-efficiency-gas.json
  17. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-efficiency-none.json
  18. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-history-bad.json
  19. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-history-drn.json
  20. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-history-gas.json
  21. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-history-none.json
  22. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-base-archives-bad.json
  23. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-base-archives-drn.json
  24. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-base-archives-gas.json
  25. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-base-archives-none.json
  26. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-emergency-bad.json
  27. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-emergency-drn.json
  28. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-emergency-gas.json
  29. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-emergency-none.json
  30. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-management-bad.json
  31. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-management-drn.json
  32. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-management-gas.json
  33. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-management-none.json
  34. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-operating-bad.json
  35. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-operating-drn.json
  36. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-operating-gas.json
  37. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-operating-none.json
  38. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-map-snapshot-bad.json
  39. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-map-snapshot-drn.json
  40. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-map-snapshot-gas.json
  41. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-map-snapshot-none.json
  42. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-distribution-bad.json
  43. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-distribution-drn.json
  44. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-distribution-gas.json
  45. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-distribution-none.json
  46. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-overview-bad.json
  47. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-overview-drn.json
  48. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-overview-gas.json
  49. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-overview-none.json
  50. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-situation-bad.json
  51. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-situation-drn.json
  52. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-situation-gas.json
  53. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-situation-none.json
  54. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-monitoring-bad.json
  55. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-monitoring-drn.json
  56. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-monitoring-gas.json
  57. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-monitoring-none.json
  58. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-warning-settings-bad.json
  59. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-warning-settings-drn.json
  60. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-warning-settings-gas.json
  61. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-warning-settings-none.json
  62. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-work-orders-bad.json
  63. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-work-orders-drn.json
  64. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-work-orders-gas.json
  65. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-work-orders-none.json
  66. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geom-none.json
  67. 11 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-analysis.py
  68. 30 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-code-rule.py
  69. 13 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-echo.py
  70. 32 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-geometry-district.py
  71. 24 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-geometry.py
  72. 12 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/link-check.mjs
  73. 16 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/overview-endpoints-not-deployed.md
  74. 48 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/pipe-district-selftest.mjs
  75. 50 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/probe-district.sh
  76. 107 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/probe-geometry-and-industry.sh
  77. 117 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/replay-startup.mjs
  78. 66 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/selftest-lead.mjs
  79. 46 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/store-selftest.mjs
  80. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-d0.json
  81. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-d1.json
  82. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-d99.json
  83. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-dun.json
  84. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-none.json
  85. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-d0.json
  86. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-d1.json
  87. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-d99.json
  88. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-dun.json
  89. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-none.json
  90. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-d0.json
  91. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-d1.json
  92. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-d99.json
  93. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-dun.json
  94. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-none.json
  95. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-d0.json
  96. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-d1.json
  97. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-d99.json
  98. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-dun.json
  99. 0 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-none.json
  100. 1 0
      .scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-infra-base-archives-d0.json

+ 294 - 0
.scratch/cockpit-real-data-wayfinding/issues/67-district.md

@@ -0,0 +1,294 @@
+<!-- DSH-IDEMPOTENCY-KEY: scope-industry-filter-coverage-v1 -->
+<!-- parentKey: 00 -->
+<!-- created: 2026-10-19T06:05:37.799Z -->
+# 前端执行票 —— 「当前范围」与「行业」筛选生效面审计与补齐(事件族仅预警分析收 district;管线片区由编码前缀推导)
+
+## Question
+
+维护者 2026-10-19 反馈第 8 条:上方菜单栏「当前范围」(全部/城南/城北/太常/未分区)**没有影响到场景及物体的筛选及部分 UI 中的信息筛选**;行业也需一并检查。
+
+**核查结论(2026-10-19 读代码 + 只读接口):部分真实缺陷,需按接缝逐条补齐。** 已接线与未接线的现状:
+
+**行业(较完整,存在 1 处缺口)**
+- 已接线:`map-snapshot`(`services/mapApi.js`)、基础设施(`infrastructureApi`)、概览(`overviewApi`)、监测(`monitoringApi`)、事件(`eventApi`/`warningResponseApi`)、管线图层(`pipeNetworkStore.setIndustry`)。
+- 缺口:**管线几何只吃行业、不吃片区**(`domain/pipeNetwork.js` 第 288 行 `districtApplied: false`,且注释说明「片区只筛设施点位与统计」)。这是仓内**已知并已如实标注**的边界,不是静默漏筛。
+
+**片区(缺口较多)**
+- 已接线:`map-snapshot`、基础设施、概览、监测分布。
+- **缺口 1(严重)**:事件族**完全没有 district 入参**——`services/eventApi.js` 的 `fetchCurrentEvents` 无 `district`;`services/warningResponseApi.js` 的 `rangeParams` 也无 `district`。后端 `filters.district` 字段存在(实测响应里有),但前端从不传。
+- **缺口 2**:`CockpitShell.vue` 的 `watch(scope)`(第 1804-1810 行)只重载 `infrastructureStore`/`overviewStore`/`monitoringStore` + `refreshMapForFilters`,**不重载 `eventStore`**——切片区后预警处置四块面板完全不刷新。
+- **缺口 3**:管线几何与构件图层不随片区变化(同上 `districtApplied:false`)。
+- **缺口 4**:`overviewStore.loadPanels({ scope })` 实际只覆盖部分面板(代码注释「概览只有运行监测那块收行业入参(另两块不收)」),需核实片区是否同样只覆盖一块。
+
+## Acceptance
+
+- **先做完整审计再改**:逐面板列出「当前范围/行业」是否进入请求参数、是否进入本地图层筛选,产出对照表写进票面(含后端是否支持该参数)。后端不支持的参数**不得前端伪造筛选**,也不得静默忽略——要在界面如实说明。
+- 补齐事件族的片区入参(后端 `filters.district` 已存在);`watch(scope)` 补 `eventStore` 重载并回第 1 页。
+- 管线/构件图层的片区能力:若后端几何接口支持 district → 接线;不支持 → 沿用 `districtApplied:false` 的**如实说明**并记录后端契约缺口(另开后端票,不在本票伪造)。
+- 审计结论若发现某面板「片区/行业」本就不适用(如纯聚合口径),在票面登记理由,**不强行加筛选**。
+- 补充闸门:筛选参数进查询串可断言;切范围/行业后受影响的 store 都有重载;未接线的面板有可见说明。
+- 闸门 + 变异打红;按 AGENTS.md 跳过 build/dev/浏览器验证。
+
+## Context
+
+- 代码:`CockpitShell.vue`(`watch(scope)`/`watch(industry)`/`refreshMapForFilters`)、`services/eventApi.js`、`services/warningResponseApi.js`、`stores/eventStore.js`、`domain/pipeNetwork.js`(`districtApplied`)、`stores/pipeNetworkStore.js`、各 `*Api.js`。
+- 既有裁定:票 18/20(窨井作为第四行业接线)、票 24(管线几何图层)、票 37(deviceType 全局筛选)。
+- 后端只读接口:`http://192.168.110.96:8301/api/cockpit/v1/**`,`filters.district` 字段已在响应中,可作联调依据。
+
+## Related spec
+
+[`../specs/2026-10-18-global-list-pagination.md`](../specs/2026-10-18-global-list-pagination.md)(本票不涉分页,属同批维护者反馈)
+
+## 进度
+
+<!-- 做到哪一步了,在这里一行一行记 -->
+
+- [x] 认领(Assignees: agent;阻塞票 #59 已关闭,本票处于 frontier)+ 读全(正文 / 评论(空)/ 标签 wayfinder:task / 父 00 / 阻塞边 #59)
+- [x] 先查后做:**逐端点实测**代替读代码猜测(见下方对照表;探针脚本与载荷落盘 `verify/artifacts/t67/`)
+- [x] 审计对照表成稿(本票正文下方),并据它纠正票面两条前提(见 Answer「票面前提的两处纠正」)
+- [x] 实施:预警分析接片区;`watch(scope)` 补 `eventStore` 重载并回第 1 页;未接线面板补可见口径说明;管线图层的片区落点上屏
+- [x] 新增闸门 [t67-filter-caliber.mjs](../verify/t67-filter-caliber.mjs) **77/77** + 变异 [t67-mutations.mjs](../verify/t67-mutations.mjs) **14/14** 打红并按字节还原
+- [x] 实施期间并行在制的票 05–08 落地了概览四端点重做(旧三端点退役)⇒ 复查并更新:概览四块的口径说明改按**规格 §1「四端点不收入参」**书写(新端点在本机后端实测 404、无实测定性),审计表与结论同步改写,闸门 A7/A8 改钉新导出
+- [x] 回归:t61 39 / t65b 14 / t66 22 / t68 17 / t62 51 / t63 23 / t64 61 / t75 18 / t39 24 / t40 17 / t43 23 / t46 54 / t48 33 / t50 15 / t51 20 / t54 24 / t55 5 / t59 37 / t60 42 / t27 8 / t16 / t45 35 / t13 50 / t14-event-detail 44 / t09 38 全绿;更正后补验管线相关:t24-pipe-layer 18 / t24-pipe-layer-flow 17 / t48-pipe-color-legend 33 / t22-pipeline-rendering 12 全绿
+- [x] 后端契约缺口另立 [票 77](77-untitled.md)(本票不伪造筛选)
+- [x] **2026-10-23 维护者更正:管线有片区可分**(编码 = 片区+行业+时间编码)⇒ 重开重做 §十:`pipeNetwork.js` 按编码前缀推导片区、图层真按片区裁剪、说明改口;E 组 18 项重写、变异 14 条(新增前缀错位/推不出对象被静默保留两条)
+- [x] 真实载荷复核(`geom-none.json`):6762 条前缀推导与 `name` 片区词 **100% 一致零反例**;燃气∩太常 586 = 编码 (TC,RQ) 计数 586(三路交叉);现网无 CB/城北
+- [x] **2026-10-23 真机复验失败排查**:根因=dev server 转译缓存给浏览器发了**旧版** `pipeNetworkStore`(无 `setScope`)→ `watch(scope)` 第 5 步抛错 → 后面的 `refreshMapForFilters` 永不执行(面板刷了、一张图不刷);处置=touch 模块强制重转译,复验点击后 `map-snapshot?...&district=0` 且场景计数真的变(§十一)
+- [x] 顺带修 `domain/deviceMetricSources.js` 的 `deviceMetricKind(null)` 崩溃(档案分区 EMPTY 时打断设备详情聚合,真机控制台反复出现)
+- [x] 收口(本票 resolved)
+
+Status: resolved
+Assignees: agent
+Type: task
+Blocked by: #59(已关闭)
+Labels: wayfinder:task
+
+## Comments
+
+### local — 2026-10-22 开工与调查说明
+
+**已查阅范围(一句话结论 / 路径)**
+- 本票正文 / 评论(空)/ 标签 wayfinder:task / 父图 00 / 阻塞边 #59(已关闭)⇒ **无 needs-triage**,按执行票推进。
+- 代码:CockpitShell.vue(`watch(scope)`/`watch(industry)`/`refreshMapForFilters`/`panelViewOf`)、domain/{pipeNetwork, panelViews, panelCatalog, filterOptions, eventList, warningResponsePanels, monitoringPanels, infrastructurePanels, overviewPanels}.js、services/{mapApi, infrastructureApi, monitoringApi, overviewApi, eventApi, warningResponseApi, pipeLayerService}.js、stores/{eventStore, infrastructureStore, monitoringStore, overviewStore, pipeNetworkStore, mapStore}.js。
+- 既有裁定:票 12(district 契约)、票 24(几何图层)、票 37(deviceType 全局筛选)、票 56/64(面板口径说明的折叠摘要接缝与 `data-*` 断言面)、票 61/62(页码条件键纪律)、票 63/65(下属面板与工单端点)。
+- 仓内既有笔记够不够用:**不够**——票面记的「后端 `filters.district` 字段存在(实测响应里有)」只对**一个**端点成立,其余端点收不收这个参数必须**逐端点实测**才能定;仓内没有这份逐端点台账。因此按 `/research` 的替代做法做了**只读探针实测**(不查外网,查的是本仓自己的后端),脚本与原始载荷落盘 `verify/artifacts/t67/`,结论回填本票。
+
+**探针方法与判据(避免"看起来筛了"的假绿)**
+- 判据一:非法值 `district=99` / `industry=NOT_A_REAL_INDUSTRY` 是否 **400**(说明后端真的读并校验该参数)。
+- 判据二:`district=0` 与 `district=1` 的响应**逐字段比对**是否真的不同(排除 `requestId`/`updatedAt`/`serverUpdatedAt`/`range.start|end`/`freshness.ageSeconds` 这类随挂钟变的字段)。
+- 只凭"响应里有 filters 字段"会判错:`/events/current` 的 `filters` 只有 `{mode, level, industry, status}`,根本没有片区;而 `/events/multidimensional-analysis` 的 `filters` 里有 `district` / `districtName` 且真的过滤。
+
+收口见 ## Answer。
+
+## Answer
+
+**已实施并关闭(2026-10-22;2026-10-23 按维护者更正重做管线片区,见 §十)。按 AGENTS.md 跳过 build/dev/preview 与浏览器验证。**
+
+### 一、审计对照表(本票的第一交付物)
+
+判据见上(400 + 逐字段比对)。"前端现状"= 本票开工前的代码实际行为。
+
+| 面板 / 图层 | panelId | 数据端点 | **片区** 后端 | **片区** 前端 | **行业** 后端 | **行业** 前端 |
+|---|---|---|---|---|---|---|
+| 一张图·监测点 | — | `/map-snapshot` | ✅ 收且过滤 | ✅ 已接(`refreshMapForFilters`) | ✅ | ✅ |
+| 管线 / 构件几何 | — | `/infrastructure/geometry` | ⚠️ 端点不收,但**编码前缀里有片区**(片区+行业+时间编码) | ✅ **本地按编码前缀裁剪**(§十 更正) | ❌ 不收(**properties 有 industry**) | ✅ 本地裁剪 |
+| 基础设施·基础档案 | `archive` | `/infrastructure/base-archives` | ✅ | ✅ | ✅ | ✅ |
+| 基础设施·运行信息 | `operation` | `/infrastructure/operating-information` | ❌ 忽略 | ❌ | ✅ | ✅ |
+| 基础设施·管理信息 | `management` | `/infrastructure/management-information` | ✅ | ✅ | ✅ | ✅ |
+| 基础设施·应急保障 | `emergency` | `/infrastructure/emergency-support` | ❌ 忽略 | ❌ | ✅ | ✅ |
+| 运行监测·监测概览 | `monitoringOverview` | `/monitoring/overview` | ❌ 忽略 | ❌ | ✅ | ✅ |
+| 运行监测·监测分布 | `monitoringDistribution` | `/monitoring/distribution` | ✅ | ✅ | ✅ | ✅ |
+| 运行监测·运行概况 | `operationOverview` | `/monitoring/situation` | ❌ 忽略 | ❌ | ✅ | ✅ |
+| 运行监测·点位台账 | `monitoringPointLedger` | 同上(共用一次取数) | ✅ | ✅ | ✅ | ✅ |
+| 预警处置·当前预警 | `currentEvents` | `/events/current` | ❌ **忽略** | ❌(且旧版整条旁路) | ✅ | ✅ |
+| 预警处置·历史分析 | `historyAnalysis` | `/events/history-analysis` | ❌ **忽略** | ❌ | ✅ | ✅ |
+| 预警处置·预警效率 | `responseEfficiency` | `/events/handling-efficiency` | ❌ **忽略** | ❌ | ✅ | ✅ |
+| 预警处置·当前工单列表 | 块 | `/work-orders` | ❌ 忽略 | ❌ | ❌ **忽略** | ❌ |
+| 预警处置·预警分析 | `eventAnalysis` | `/events/multidimensional-analysis` | ✅ **收且过滤** | ❌ **本票补上** | ✅ | ✅ |
+| 概览·感知设备 | `perceptionDevices` | `/overview/perception` | ❌ 不收入参(规格) | ❌ | ❌ 不收入参(规格) | ❌ |
+| 概览·基础设施 | `pipeNetwork` | `/overview/pipe-network` | ❌ 不收入参(规格) | ❌ | ❌ 不收入参(规格) | ❌ |
+| 概览·预警研判 | `warningStudy` | `/overview/warning-study` | ❌ 不收入参(规格) | ❌ | ❌ 不收入参(规格) | ❌ |
+| 概览·报警处置 | `alarmDisposal` | `/overview/alarm-disposal` | ❌ 不收入参(规格) | ❌ | ❌ 不收入参(规格) | ❌ |
+
+> **概览四块这一栏的依据强度与别处不同,如实标出来**:票 05–08(并行在制)在本票实施期间把概览数据源从旧三端点(`/overview/infrastructure`、`/overview/monitoring`、`/overview/warning-settings`)换成了四个新端点,而这四个新端点**在本机后端尚未部署(实测一律 404)**,没法用"非法值 400 / 0-1 响应不同"那套判据。所以它们的"不收入参"依据是**前端实现 + 规格 §1**,不是实测。旧三端点时期是实测过的(`/overview/infrastructure` 任何入参都不变、`/overview/monitoring` 收行业、`/overview/warning-settings` 只收窗口)。端点上线后按 `verify/artifacts/t67/` 复跑升级证据,登记在票 77。
+
+**行业面结论:前端已经接满"后端收"的全部端点**(原始审计里"行业较完整、存在 1 处缺口"成立,且那 1 处 = 管线片区,不是行业)。行业缺口只有**后端不收**的四处,本票不伪造、能上屏的都已说明:`/work-orders`(实测不认行业 → 工单列表页脚说明)、概览四块(四个端点不收入参 → 四块面板各一句口径说明);另两处(旧 `/overview/infrastructure`、`/overview/warning-settings`)随概览重做已退役,其继任者归票 77 复核。
+
+**片区面结论:不是前端漏接,而是后端覆盖参差**——真收片区的只有 5 个端点(`map-snapshot`、`base-archives`、`management-information`、`monitoring-distribution`、`multidimensional-analysis`),其余 7 个事件/监测端点**完全不认**该参数,概览四块则是端点根本不收入参。
+
+**唯一的例外是管线几何**:端点也不收片区,但片区信息**在设施编码里**(片区+行业+时间编码),所以前端能按编码前缀本地裁剪 —— 这条本票起初判错(只看 properties 有没有片区字段),由维护者 2026-10-23 更正,见 §十。
+
+### 二、票面前提的两处纠正(实测推翻,写进票面以免后人沿错路)
+
+1. **"事件族完全没有 district 入参,后端 `filters.district` 字段存在"** —— 只对**预警分析**那一个端点成立。`/events/current`、`/events/history-analysis`、`/events/handling-efficiency` 实测**不读**该参数(非法值照 200、0/1 响应逐字节相同,响应 `filters` 里也没有片区字段)。所以"补齐事件族片区入参"不可能靠前端补齐,只能:**把真收的那一块接上**(预警分析),其余**如实说明 + 开后端票**(票 77)。
+2. **"管线/构件图层沿用 `districtApplied:false` 的如实说明"** —— 那句话此前**只写在注释里**:`districtApplied` 全仓只有 3 处引用(领域函数、store 的 EMPTY 常量、注释),**没有任何界面消费点**。也就是说用户从来没看到过这条"如实说明"。本票把它做成真的可见说明(见下)。
+
+### 三、改动(5 个源文件 + 2 个组件)
+
+| 文件 | 改动 |
+|---|---|
+| [domain/filterCaliber.js](../../../src/modules/cockpit/domain/filterCaliber.js) **新增** | 筛选生效面的**单一措辞来源**:`SCOPE_FILTER_LABEL`/`INDUSTRY_FILTER_LABEL`、`DISTRICT_GAPS`(11 块)、`INDUSTRY_GAPS`(5 处:工单列表 + 概览四块)、`districtGap()`/`industryGap()`、`PIPE_DISTRICT_NOTE`、`withFilterCaliber()`。文件头把逐端点实测结论写死,便于下次复用 |
+| [domain/panelViews.js](../../../src/modules/cockpit/domain/panelViews.js) | `panelView()` 出口统一 `withFilterCaliber(...)` —— 口径说明挂在**注册表这一处**,面板新增/改名不会漏(也省掉 11 处重复代码) |
+| [services/warningResponseApi.js](../../../src/modules/cockpit/services/warningResponseApi.js) | `fetchAggregated` 增 `hooks.acceptsDistrict`;**只有** `fetchEventAnalysis` 打开。多窗(90 天)路径的每个窗都带 |
+| [stores/eventStore.js](../../../src/modules/cockpit/stores/eventStore.js) | `CONDITION_KEYS` 与 `conditionSnapshot()` 补 `scope` ⇒ 换片区时三个页码一起回第 1 页,定时刷新不复位 |
+| [CockpitShell.vue](../../../src/modules/cockpit/CockpitShell.vue) | ① `watch(scope)` 补 `eventStore.loadPanels({ mode, scope })`;② `refreshCurrentModePanels()` 的预警处置分支补 `scope`;③ 状态详情里新增 `data-pipe-district-note`(渲染 `PIPE_DISTRICT_NOTE`) |
+| [domain/eventList.js](../../../src/modules/cockpit/domain/eventList.js) | `currentEventsView()` 增 `gap`(当前预警不在 panelViews 注册表里,走自己的视图) |
+| [components/EventListPane.vue](../../../src/modules/cockpit/components/EventListPane.vue) | 渲染 `data-panel-gap`(与 PanelBlocks 同一个断言钩子) |
+| [domain/warningResponsePanels.js](../../../src/modules/cockpit/domain/warningResponsePanels.js) | 工单列表块的 `pageNote` 追加行业缺口句(`industryGap('workOrderList')`) |
+| [domain/pipeNetwork.js](../../../src/modules/cockpit/domain/pipeNetwork.js) **§十 更正** | 新增 `PIPE_DISTRICT_PREFIX`(CN=0 城南 / CB=1 城北 / TC=2 太常)与 `pipeDistrictOf()`;`toPipeNetwork()` 每条对象带 `district`;`filterPipeNetwork()` 吃 `filters.scope`,返回 `districtApplied`(全域 false)与 `districtUnresolvedCount`(编码推不出片区、被排除的可上图对象数) |
+| [stores/pipeNetworkStore.js](../../../src/modules/cockpit/stores/pipeNetworkStore.js) **§十 更正** | 新增 `scope` 状态与 `setScope()`;`filtered` 同时吃行业与片区;`load()` 收 `scope` |
+
+**没改**:`mapStore`/`monitoringStore`/`infrastructureStore`/`overviewStore`(片区接线原本就通);任何后端代码;`pipeLayerService.js`(几何仍是一次全量取回,片区在本地裁剪 —— 与行业同一条纪律)。
+
+### 四、用户实际看到的两句话(折叠摘要里)
+
+**每块"不随当前范围变"的面板**(11 块,措辞按端点分化,例如当前预警):
+
+> 「当前范围」不作用于本面板:后端该端点未提供片区入参(当前事件端点实测下发该参数也不生效),本表始终列出全部片区
+
+**一张图的状态详情里**(管线/构件,§十 更正后的措辞):
+
+> 「当前范围」按设施编码规则(片区 + 行业 + 时间编码)作用到管线与构件图层:几何数据本身没有片区字段,片区是从编码前缀推出来的;编码推不出片区的对象(阀门、窨井等少量构件)在按片区查看时不显示,选「未分区」才看得到它们
+
+**概览四块**(同样在折叠摘要里,凭据是规格而非实测,见上表注):
+
+> 「当前范围」不作用于本面板:概览四块的数据端点按规格不收入参(不收片区),本面板读数始终为全域
+>
+> 「行业」不作用于本面板:概览四块的数据端点按规格不收入参,本面板读数始终为全部行业
+
+**报警效率的工单列表**(页脚口径行):
+
+> 「行业」不作用于本工单列表:后端工单端点未提供行业入参,列表始终列出全部行业的工单(上方分布仍按行业统计)
+
+### 五、验收自查(对抗式,逐条对 Acceptance)
+
+1. ✅ **先做完整审计再改,产出对照表(含后端是否支持)** —— 见上方第一张表;支撑证据是**逐端点实测**(不是读代码猜),脚本 `probe-district.sh` / `probe-geometry-and-industry.sh` / `diff-district.py` 与全部原始载荷在 `verify/artifacts/t67/`,可原样复跑。
+2. ✅ **补齐事件族片区入参(后端已存在的那处)** —— 预警分析接上 `district`(A1/A2/A3 逐片区取值 + 全域不传 + 90 天多窗每窗都带);**三个不认该参数的端点一个都不传**(A4/F1 反向钉住)。这是票面前提被实测纠正后的诚实版本:能接的接上,接不了的不伪造。
+3. ✅ **`watch(scope)` 补 `eventStore` 重载并回第 1 页** —— C1(源码接线)+ B2(行为:三个页码都回 1,且请求带新片区);B3 钉住"同条件重取不复位"(条文 6 的反面,别把定时刷新也复位)。
+4. ✅ **管线/构件的片区能力** —— **本票起初判错、已按维护者更正重做(§十)**:几何 properties 确实没有片区字段(R3 用落盘载荷复算 6766 feature 证实),但设施编码是「片区+行业+时间编码」,片区**能推出来** ⇒ 改为本地按编码前缀裁剪(E 组逐条钉住:推导与名称 100% 一致、四个片区取值、推不出片区的对象如实计数、行业×片区正交)。说明同步改口(不再是「不作用于」)。
+5. ✅ **本就不适用的面板登记理由、不强行加筛选** —— 概览四块登记片区 + 行业两条缺口说明,**依据强度单独标注**(四个新端点在本机后端未部署、实测 404 ⇒ 依据是规格 §1 与前端实现,不是实测;见上表注与 `verify/artifacts/t67/overview-endpoints-not-deployed.md`)。纯聚合口径的面板(监测分布的状态饼图是全局未筛口径)保持原样,未强加筛选。
+6. ✅ **补充闸门三条** —— ① 参数进查询串可断言:A 组 9 项(含"该带的带上"与"不该带的不许带"两个方向);② 切范围/行业后受影响的 store 都有重载:B 组行为 + C 组外壳接线;③ 未接线的面板有可见说明:D 组 38 项(逐块 + 全注册表正向普查 + 反向普查 + SSR 真的进 DOM)。
+7. ✅ **闸门 + 变异打红** —— t67 **77/77**;变异 **14/14**(M1 分析丢 district、M2 当前事件伪造 district、M3 scope 不进条件键、M4 watch 丢 eventStore、M5/M11 删缺口、M6 缺口里塞接口字段名、**M7 片区恒不生效、M8 管线说明写回「不作用于」、M13 编码前缀映射错位、M14 推不出片区的对象被静默保留**、M9 刷新漏传 scope、M10 删工单行业缺口、M12 注册表绕过口径出口),每条点名打红指定断言并按字节还原。
+
+### 六、闸门设计里两条值得记住的判据
+
+- **"响应里有 filters 字段"不是证据,非法值 400 才是**:`/events/current` 的 `filters` 里压根没有片区字段,而票面当初就是被"实测响应里有 district"误导成"后端都支持"。闸门 R 组因此固化了双判据(400 + 逐字段比对),并**排除随挂钟变的字段**(`requestId`/`updatedAt`/`serverUpdatedAt`/`range.start|end`,还有 `/infrastructure/operating-information` 指标的 `freshness.ageSeconds`)—— 不排除的话,这些端点会被误判成"按片区过滤了"。
+- **口径说明要正向普查,不能只查你改过的面板**:D0c 以 `implementedPanelIds()` 为准,要求**每个**已实现面板要么有片区缺口、要么属于已接线集合 —— 将来新增面板漏写说明会立刻红。这与票 56 把"说明字段是否有消费点"做成机制是同一条路子。
+- **"字段里没有"不等于"数据里没有"**(§十 记的第二次同类翻车):我先后两次同一个毛病 —— 一次把"响应里有 `filters` 字段"当成"后端支持",一次把"properties 里没有片区字段"当成"片区不可得"。两次都是**只看一个信号就下结论**。E1b 现在用「编码前缀 × `name` 里的片区词」两路独立信号交叉复核(6762/6762、零反例),E7 再加一路(燃气∩太常 586 与编码 (TC,RQ) 计数相等)—— 判据至少两路,才配叫证据。
+
+### 七、后端契约缺口(不在本票伪造)
+
+另立 [票 77:后端执行票 —— 片区/行业筛选的端点覆盖缺口](77-untitled.md):事件族三端点补 `district`、`/work-orders` 补 `industry`、概览四新端点上线时定参数面。部署后按 `verify/artifacts/t67/` 的同一套探针复跑即可验收。
+
+**几何读模型的片区归属已降级为"锦上添花"**(§十 更正):前端已能按设施编码前缀推出片区并据此裁剪图层,
+所以"几何必须补片区字段"不再是阻塞项;若后端将来直接下发 `district`/`location`,前端改 `pipeDistrictOf` 一处即可。
+
+### 八、既有红如实留档(与本票 diff 零交集)
+
+| 闸门 | 现状 | 根因(有据) |
+|---|---|---|
+| t56-panel-summary-disclosure | 整脚本导入失败 | 它 import `MONITORING_FAULT_GAP`/`monitoringSummaryPanel`,而 `domain/overviewPanels.js` 在**并行在制的概览重构(票 02)**里已移除这些导出:`git show HEAD:` 该文件有,工作树没有;**本票未修改该文件** |
+| t38-infrastructure-panel-adjustments | 整脚本导入失败 | 同上,缺 `infrastructureSummaryPanel` |
+| t44-distribution-ranking | 整脚本导入失败 | 同上,缺 `alertSettingsPanel` |
+| t15-time-district | 断言期异常(`operationOverviewPanel` 的 list 块 rows 为空) | 运行概况已按票 62 的追加需求改成读 `monitoringOverview.devices`(`extra`),该脚本仍按旧形状直接调面板函数;**本票未修改 `monitoringPanels.js`,且该断言不经过 `panelViews`** |
+| t14-final-walkthrough | puppeteer `spawn EPERM` | 浏览器脚本;本机沙箱边界 + AGENTS.md 禁止浏览器验证 |
+
+按仓内成例**不半修**这些闸门。
+
+### 九、下一步
+
+- 真实环境走查(片区/行业在四模式下的观感、说明文案的折叠摘要是否易读)归[票 18](18-frontend-real-integration-gate.md) 联调窗口。
+- 后端票 77 落地后,前端只需把 `filterCaliber.js` 里对应条目的措辞删掉、把参数接上(`acceptsDistrict` 打开),闸门 D 组的正向普查会自动要求更新。
+- **改动未提交**:工作树混有大量并行在制品(概览重构、城市体块、场景图标等 20+ 文件),本票 9 个文件需按票 33/36/37 纪律单独裁剪入库。
+
+### 十、更正(2026-10-23):管线**有**片区可分,本票 §二/§四 的"不作用于管线"作废
+
+**维护者原话**:管线编码 `network_code` 的组成规则就是「**片区 + 行业 + 时间编码**」
+(如 `TCRQ202609080001` = 太常·燃气管),另外 `location` 字段作为位置描述也描述了管线信息
+(如那段太常燃气管的 `location` 是「太常|燃气管线」)。
+
+**我错在哪**:§二 第 2 条只查了「几何 properties 里有没有片区字段」,没有就下结论"不可本地筛"——
+把**字段里没有**当成了**数据里没有**。这跟本票 §六 批评的那条错误(把"响应里有个 filters 字段"
+当成"后端支持")是同一类毛病:**只看一个信号就下判断**。片区信息一直在编码前两位里。
+
+**用真实载荷复核(`verify/artifacts/t67/geom-none.json`,6766 feature)**
+
+| 检验 | 结果 |
+|---|---|
+| code 两字母前缀分布 | TC **4116**、CN **2646**、GD 1、YL 3 |
+| 前缀 × `name` 里的片区词(**两个独立信号**) | TC → 4116/4116 含「太常」;CN → 2646/2646 含「城南」;**零反例** |
+| 前缀 + 行业字母 | TC/CN 各带 RQ(燃气)/WS(供水)/YS(雨水)/GC(管沟),与「片区+行业+时间」规则吻合 |
+| 三路交叉独立验证 | 燃气∩太常 = `586`,与编码里 (TC,RQ) 计数 **586** 完全相等 |
+| `location` 字段 | 几何 properties **里没有这个键**(只有 source/sourceId/code/name/industry/typeCode/networkType/networkLevel/pointType/networkId/diameter/remark/sourceCoordinates/coordinateValidation*)⇒ 片区只能靠编码前缀,不靠 location |
+| 推不出片区的对象 | 4 个(`GD-001` 阀门、`YL-YJ-001/002/003` 窨井,用的是另一套编码);其中 3 个本来就在「无地图位置」拦下清单(blocked)里,只有 `YL-YJ-003` 是可上图对象 |
+
+**实现(`domain/pipeNetwork.js` / `stores/pipeNetworkStore.js` / `CockpitShell.vue`)**
+- `PIPE_DISTRICT_PREFIX = { CN: 0, CB: 1, TC: 2 }`(与 `filterOptions.DISTRICT_CODES` 的后端片区编码同值)+
+  `pipeDistrictOf(code)`:只认「两位片区字母 + 两位行业字母」的正式形状,认不出回 `null`(不猜)。
+- `toPipeNetwork()` 每条 pipe/component/blocked 都带 `district`;
+  `filterPipeNetwork(network, { industry, scope })` 现在**真的按片区裁剪**,并返回
+  `districtApplied`(全域 false/具体片区 true)与 `districtUnresolvedCount`(被排除的可上图对象数)。
+- 「未分区」的语义=**编码推不出片区的那批对象**(与后端 `district=null` 同一读法);
+  选具体片区时它们被排除并计数 —— 不硬塞进某个片区,也不静默留在结果里。
+- 几何仍是**一次全量取回**、片区在本地裁剪(与行业同一条纪律:切换即时、不重新发请求)。
+
+**两条如实标注**
+1. **片区仍是前端推导,不是后端筛选**:几何端点实测仍**不收** `district`(`district=0/1` 响应逐字节相同、非法值 99 也不 400),
+   所以界面上的措辞改成「按设施编码规则作用到管线图层……片区是从编码前缀推出来的」,
+   而不是含糊地说"片区已生效"。
+2. **`CB = 城北` 已由维护者 2026-10-23 确认**(此前我按拼音推定、标注"可推翻",现已落定):
+   现网 6766 条几何里**一条 `CB` 都没有** —— 选「城北」时管线为空,是编码里没有、不是没有管线。
+
+**票 77 相应调整**:几何端点补片区字段从"前端等着用"降级为"锦上添花"——前端已能工作;
+若后端将来直接下发 `district`/`location`,前端只改 `pipeDistrictOf` 一处(闸门 E1 会用落盘载荷复核前缀与名称的一致性)。
+
+
+### 十一、真机复验失败 → 根因与修复(2026-10-23,维护者实测「按钮不起作用」)
+
+**现象**:维护者在 `http://127.0.0.1:8000/#/cockpit` 点「当前范围」按钮,界面毫无变化。
+
+**取证**(Chrome DevTools 协议只读取证,未改页面):
+1. 点「城南」后 **4 个面板确实带上了片区**(`monitoring/distribution?district=0`、
+   `events/multidimensional-analysis?...&district=0`、`infrastructure/base-archives?district=0`、
+   `management-information?district=0`)—— 说明点击→store→watch 这一段是通的;
+2. 但**一张图一条 `map-snapshot` 请求都没发**;
+3. 控制台里有:
+
+```
+TypeError: pipeNetworkStore.setScope is not a function
+  at CockpitShell.vue:1442  (watcher callback)
+[Vue warn]: Unhandled error during execution of watcher callback
+```
+
+**根因(两级)**
+- **直接原因**:我这次给 `pipeNetworkStore` 新加的 `setScope` 在**浏览器拿到的模块里不存在**。
+  实测证明是 dev server 的**转译缓存**给旧版:同一 URL 直接取回,`return {...}` 里没有 `setScope`;
+  加 `?t=999999` 强制重取就正常。dev server 的 mtime 失效没跟上(Windows 上常见),
+  **硬刷新也救不回来**(浏览器刷新不会让 Vite 重新转译),只能 `touch` 文件或重启 dev server。
+- **放大原因**:`watch(scope)` 里 6 个联动是一串**同一个回调**,第 5 个 `pipeNetworkStore.setScope(next)`
+  一抛错,**排在它后面的 `refreshMapForFilters` 就永远不执行** —— 于是「面板刷了、一张图不刷」,
+  而界面上没有任何提示(只有控制台 warn)。这正是「筛了但没生效」的最隐蔽形态。
+
+**修复与复验**
+1. `touch` 我改过的模块强制 Vite 重新转译(已用 `curl` 逐文件核对服务端返回的是新版)。
+2. 复验(同一页面、只读取证):
+   - 点「城南」→ `map-snapshot?page=1&pageSize=2&district=0`(第 1 页起全部带 `district=0`);
+   - 一张图状态带真的变了:**太常** 报警0/预警1/工单0/正常0/离线0 ↔ **全域** 报警1/预警1/工单5/正常1/离线2;
+   - 基础设施「基础档案」在太常下读数为 `设施总数 0 处`(设施档案没有片区来源,全部落未分区 —— 票 68 记过的既知事实,不是缺陷)。
+
+**顺带修掉一个真实缺陷**(`domain/deviceMetricSources.js`,committed 代码、非本票引入):
+`deviceMetricKind(archive = {})` 的默认值只在**未传**时生效,而 `fetchEquipmentDetailAggregate` 在档案分区为 EMPTY 时
+显式传 `null` → `archive.type` 抛 `TypeError`,**整条设备详情聚合被打断**(真机控制台反复出现该异常)。
+改为函数内 `const source = archive || {}` 收口;复验:刷新后控制台 error 归零。
+
+**留给运维/开发的一条纪律(写进票面,避免重复踩)**
+> 改了 `stores/*.js`、`services/*.js` 这类**纯 JS 模块**后,dev server 可能仍把旧转译结果发给浏览器
+> (`.vue` 有 HMR 边界会换,纯 JS 不会)。表现是「新增的 store 方法 is not a function」,
+> 而且**浏览器硬刷新也无效**。处置:`touch` 该文件(或重启 dev server),再刷新页面。
+
+**观察(不在本票处理)**:`MAP_SUMMARY_PAGE_SIZE = 2`(2026-09-18 为演示渐进加载而设,注释写明)。
+真实数据下每切一次筛选会连翻几十到上千次 `map-snapshot`(本次实测一次筛选 40+ 请求)。
+演示无碍,**真实投用前应调回后端默认 500**,否则既是性能问题也像"卡住不动"。
+
+
+

+ 95 - 0
.scratch/cockpit-real-data-wayfinding/issues/77-untitled.md

@@ -0,0 +1,95 @@
+<!-- DSH-IDEMPOTENCY-KEY: backend-district-industry-param-gaps-v1 -->
+<!-- parentKey: 00 -->
+<!-- created: 2026-10-22T09:32:26.113Z -->
+# 后端执行票 —— 片区/行业筛选的端点覆盖缺口(事件族三端点、几何读模型、概览与工单端点)
+
+## Question
+
+维护者 2026-10-19 反馈第 8 条:上方菜单栏「当前范围」(全部/城南/城北/太常/未分区)没有影响到场景及物体的筛选,行业也需一并检查。
+
+前端执行票 [67](67-district.md) 已把**前端能接的都接上**(一张图、基础档案、管理信息、监测分布、预警分析),并把**后端不收该参数的端点**在界面上如实说明。本票承接剩下的**后端契约缺口**——前端不伪造筛选(票 67 的验收纪律:不支持的参数不得前端伪造,也不得静默忽略)。
+
+**实测证据(2026-10-22,只读探针 `http://192.168.110.96:8301/api/cockpit/v1/**`,载荷与脚本落盘 `verify/artifacts/t67/`)**
+判据两条:① 非法值 `district=99` 是否 400;② `district=0/1` 的响应是否真的不同。
+
+### 一、片区(district):事件族三个端点完全不读该参数
+
+| 端点 | 非法值 99 | 0/1 响应 | 结论 |
+|---|---|---|---|
+| `/events/multidimensional-analysis` | **400**(`district must be 0, 1, 2 or UNASSIGNED`) | 桶计数不同 | **已支持**(前端已接线) |
+| `/events/current` | 200 | 逐字节相同 | **不读参数** |
+| `/events/history-analysis` | 200 | 逐字节相同 | **不读参数** |
+| `/events/handling-efficiency` | 200 | 逐字节相同 | **不读参数** |
+
+三个端点的响应 `filters` 里也**没有**片区字段(只有 `mode/level/industry/status` 与 `eventFamily/industry/level/workOrderState`)。
+于是「预警处置」模式里:预警分析随片区收窄,当前预警 / 历史事件 / 预警效率始终是全域 —— 同一模式四个数各说各话,前端只能靠界面说明交代(票 67 已做),根子在契约。
+
+**要求**:三个端点按 `multidimensional-analysis` 的既有形态补齐 `district`(0/1/2/UNASSIGNED)+ 非法值 400 + `filters` 回显 `district`/`districtName`。
+
+### 二、片区:几何读模型没有片区字段(**降级为锦上添花**,前端已能自行推导)
+
+`/infrastructure/geometry` 实测 **6766 个 feature**,properties 只有:
+`source` / `sourceId` / `code` / `name` / `industry` / `typeCode` / `networkType` / `networkLevel` / `pointType` / `networkId` / `diameter` / `remark` / `sourceCoordinates` / `coordinateValidation(s)`。
+**没有任何片区字段**;端点本身也不收 `district`(`district=0/1` 响应逐字节相同、非法值 99 不 400)。
+
+> **2026-10-23 更正(维护者指出 + 前端已落地)**:片区信息一直在**设施编码**里 ——
+> 编码规则是「片区 + 行业 + 时间编码」(`TCRQ202609080001` = 太常·燃气管)。
+> 前端票 67 已按编码前缀推出片区并据此裁剪管线图层(实测前缀与 `name` 里的片区词 6762/6762 一致、零反例)。
+> **所以本段不再是阻塞项**:管线现在能按片区筛。
+
+**仍然建议(非阻塞)**:几何 feature 直接下发 `district`(与其它端点同一套 0/1/2/null 语义)或 `location`/正式编码字段,
+理由有三:① 前端不必依赖拼音前缀约定(`CB`=城北目前是**推定**,现网无该前缀数据可验证);
+② 现网 4 个对象用的是另一套编码(`GD-001`、`YL-YJ-00x`),片区只能回 null、按片区查看时被排除;
+③ 编码规则一变,前端推导会静默错位(票 67 的 E1b/E13 只能在闸门里靠落盘载荷发现)。
+
+### 三、行业(industry):三个端点不读该参数
+
+| 端点 | 非法行业 | 结论 |
+|---|---|---|
+| `/overview/infrastructure` | 200,响应不变 | **无任何入参**(控制器没有 @RequestParam) |
+| `/overview/warning-settings` | 200,响应不变 | 不读 `industry` |
+| `/work-orders` | 200,响应不变 | 不读 `industry`(票 65 新增端点) |
+| `/infrastructure/geometry` | 200,响应不变 | 不读 `industry`,但几何全量取回后前端本地筛,**不算缺口** |
+
+**要求**:给 `/work-orders` 补 `industry`(非法值 400)。
+
+> **概览三端点已被替代(2026-10-22 当日晚些时候)**:前端并行落地的票 05–08 把概览数据源换成四个新端点
+> `/overview/perception`、`/overview/pipe-network`、`/overview/warning-study`、`/overview/alarm-disposal`。
+> 这四个端点在本机后端**尚未部署(实测一律 404)**,参数面还无法实测;前端按规格 §1「四个端点都不收入参」实现,
+> 并在界面为概览四块的片区/行业各挂了一句口径说明。**要求**:这四端点上线时明确参数面 —— 要么按规格继续不收
+> (前端说明维持),要么补 `district`/`industry` 并与其它端点同一套 400 校验;上线后按
+> `verify/artifacts/t67/` 的探针复跑,把前端说明的依据从"规格"升级为"实测"。
+
+### 四、已支持且前端已接线的(本票不改,仅作对照)
+
+`/map-snapshot`、`/infrastructure/base-archives`、`/infrastructure/management-information`、`/monitoring/distribution` 的 `district` 与 `industry` 均实测生效。
+
+## Acceptance
+
+- 事件族三端点补齐 `district`:非法值 400、`filters` 回显、`district=0/1` 的计数真的不同(契约测试成对正反证)。
+- (非阻塞)几何读模型下发片区归属,或正式确认「片区看编码前两位」这条规则(含城北前缀字母),并在 `coverage` 里说明管段/构件各自的片区来源。
+- `/work-orders` 补 `industry`;非法值 400。
+- 概览四个新端点部署时明确参数面(不收则维持前端说明;收则与既有端点同套语义 + 400),并补齐实测证据。
+- 契约测试覆盖:合法值生效、非法值 400、不传=全域(不得把「不传」静默当成某个片区)。
+- 前端侧复验归 [票 18](18-frontend-real-integration-gate.md)(部署后按 `verify/artifacts/t67/` 的同一套探针复跑)。
+
+## Context
+
+- 前端票:[67: 「当前范围」与「行业」筛选生效面审计与补齐](67-district.md)(本票的全部实测数据、对照表与界面说明由它产出)。
+- 复跑探针:`.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/probe-district.sh`、`probe-geometry-and-industry.sh`、`diff-district.py`。
+- 既有裁定:票 12(district 片区契约)、票 24(几何图层)、票 49(设施编码)、票 65(work-orders 端点)。
+
+## 进度
+
+- 2026-10-22 由前端票 67 收口时另立:前端能接的都接了,剩下的是后端契约本身不认这些参数。
+
+Status: ready-for-agent
+Type: task
+Blocked by:
+Labels: wayfinder:task
+
+## Comments
+
+
+## Answer
+

+ 30 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/README.md

@@ -0,0 +1,30 @@
+# 票 67 证据索引(artifacts/t67/)
+
+只读探针实测产物 + 闸门自检脚本。**判据**(票 67):① 非法值(`district=99`/`industry=NOT_A_REAL_INDUSTRY`)
+是否 400;② `district=0` 与 `district=1` 响应逐字段比对是否真的不同(排除 `requestId`/`updatedAt`/
+`serverUpdatedAt`/`range.start|end`/`freshness.ageSeconds` 这类随挂钟变的字段);
+③ 对管线片区这类"字段里没有但编码里有"的信息,改用**两路独立信号交叉**(编码前缀 × name 里的片区词)。
+
+## 脚本
+| 文件 | 作用 |
+|---|---|
+| `probe-district.sh` | 逐端点跑 none/d0/d1/dun/d99 五个 district 取值,存 `v2-<端点>-<取值>.json` |
+| `probe-geometry-and-industry.sh` | 几何端点对 district 的反应 + 各端点 industry=GAS/DRAINAGE/非法,存 `geo-<端点>-<取值>.json` |
+| `diff-district.py` | 结构化比对 none 与各取值的差异(判据 ① ② 的机器版) |
+| `inspect-echo.py` / `inspect-geometry.py` / `inspect-analysis.py` | 响应里的 `filters`/`mapFilter` 回显、几何 feature 属性、分析维度 |
+| **`inspect-geometry-district.py`** | **§十 更正的核心证据**:code 前缀分布、前缀 × name 片区词交叉表、非标编码对象 |
+| **`inspect-code-rule.py`** | 验证「片区+行业+时间编码」规则(前缀×行业字母分布) |
+| `pipe-district-selftest.mjs` | Lead 自检:真实载荷推导一致率、四个片区取值的裁剪结果、行业×片区正交 |
+| `selftest-lead.mjs` / `store-selftest.mjs` / `compile-selftest.mjs` | Lead 的独立自检(参数面 / eventStore 页码行为 / 外壳 SFC 编译),与 t67 闸门互补 |
+| `link-check.mjs` | 检查票面相对链接是否都落地 |
+
+## 载荷
+- `v2-<端点>-<none|d0|d1|dun|d99>.json`:district 探针原始响应(15 个端点 × 5 取值)。
+- `geo-<端点>-<none|gas|drn|bad>.json`:industry 探针原始响应。
+- `geom-none.json`:几何读模型全量响应(5.97 MB;其余 district 取值的副本与之**逐字节相同**,已删以免仓库膨胀,复跑脚本可再生成)。闸门 R3 拿它复算"properties 无片区字段",
+  **闸门 E1b 拿它复算"编码前缀与 name 片区词 100% 一致"** —— 两件事都要留原始载荷,别只留结论。
+- `overview-endpoints-not-deployed.md`:概览四个新端点(票 05–08)实测 404 的记录 —— 说明概览四块的口径
+  说明依据是**规格 §1**而非实测,别把两种强度的证据混为一谈。
+
+**清理说明**:早期 v1 探针脚本对无查询串的路径拼成了 `path?x`→`path&x`(404),那批产物已删除;
+现存的都是 v2(修好拼接后)与 industry/geometry 两族。

+ 11 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/compile-selftest.mjs

@@ -0,0 +1,11 @@
+import { readFileSync } from "node:fs"
+import { parse, compileScript, compileTemplate } from "@vue/compiler-sfc"
+const path = "src/modules/cockpit/CockpitShell.vue"
+const source = readFileSync(path, "utf8")
+const { descriptor } = parse(source, { filename: path })
+const script = compileScript(descriptor, { id: "t67", genDefaultAs: "__sfc__" })
+const template = compileTemplate({ source: descriptor.template.content, filename: path, id: "t67", ssr: true, compilerOptions: { bindingMetadata: script.bindings } })
+console.log("template errors:", JSON.stringify(template.errors))
+console.log("has data-pipe-district-note in compiled:", template.code.includes("data-pipe-district-note"))
+console.log("binds PIPE_DISTRICT_NOTE:", template.code.includes("PIPE_DISTRICT_NOTE"))
+console.log("has data-panel-note count:", (source.match(/data-pipe-district-note/g) || []).length)

+ 47 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/diff-district.py

@@ -0,0 +1,47 @@
+
+import json, os, sys
+
+OUT = '.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67'
+names = ["map-snapshot","infra-base-archives","infra-operating","infra-management","infra-emergency",
+         "mon-overview","mon-distribution","mon-situation","ev-current","ev-history","ev-efficiency",
+         "ev-analysis","ov-monitoring","ov-warning-settings","work-orders"]
+variants = ["none","d0","d1","dun","d99"]
+
+def load(n, v):
+    p = os.path.join(OUT, f"v2-{n}-{v}.json")
+    try:
+        with open(p, encoding='utf-8') as f:
+            return json.load(f)
+    except Exception as e:
+        return {"__load_error__": str(e)}
+
+def walk(a, b, path="", out=None, limit=400):
+    if out is None: out = []
+    if len(out) >= limit: return out
+    if type(a) != type(b):
+        out.append((path, repr(a)[:60], repr(b)[:60])); return out
+    if isinstance(a, dict):
+        for k in sorted(set(a) | set(b)):
+            if k not in a: out.append((path+"/"+k, "<absent>", repr(b[k])[:60])); continue
+            if k not in b: out.append((path+"/"+k, repr(a[k])[:60], "<absent>")); continue
+            walk(a[k], b[k], path+"/"+k, out, limit)
+    elif isinstance(a, list):
+        if len(a) != len(b):
+            out.append((path+"/len", str(len(a)), str(len(b))))
+        for i in range(min(len(a), len(b))):
+            walk(a[i], b[i], path+f"[{i}]", out, limit)
+    else:
+        if a != b: out.append((path, repr(a)[:60], repr(b)[:60]))
+    return out
+
+for n in names:
+    print("="*100)
+    print(n)
+    for v in variants[1:]:
+        a = load(n,"none"); b = load(n,v)
+        if "__load_error__" in b:
+            print(f"  none vs {v}: LOAD ERROR {b['__load_error__']}"); continue
+        d = walk(a,b)
+        print(f"  none vs {v}: {len(d)} diff(s)")
+        for p,x,y in d[:12]:
+            print(f"     {p}: {x}  ->  {y}")

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-analysis-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"e9e5ea5e2cf641ca84338f2c7b928b61","serverTime":"2026-10-22T16:52:51+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-analysis-drn.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-analysis-gas.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-analysis-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-current-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"7fc743aa99ef41d7bee2c0fc8b549881","serverTime":"2026-10-22T16:52:50.9509665+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-current-drn.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-current-gas.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"aa79f60f4d7b4d5aaeb513152fa71b49","apiVersion":"v1","readOnly":true,"displayName":"当前预警","timezone":"Asia/Shanghai","mode":"WARNING","updatedAt":"2026-10-22T15:14:45.2612781+08:00","filters":{"mode":"WARNING","level":null,"industry":"GAS","status":null},"pagination":{"page":1,"pageSize":10,"total":0,"totalPages":0,"hasMore":false},"mapFilter":{"mode":"WARNING","level":null,"industry":"GAS","status":null,"deviceCodes":[]},"sections":{"events":{"state":"EMPTY","reason":"NO_EVENTS","message":null,"data":null},"workOrders":{"state":"SUCCESS","reason":"SUCCESS","message":null,"data":null}},"freshness":"EMPTY"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-current-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-efficiency-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"f9e2bff124f643b6a1e4c7ad0dfb41de","serverTime":"2026-10-22T16:52:51+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-efficiency-drn.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-efficiency-gas.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-efficiency-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-history-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"80f857ce6b024de5ae9b6df17e887702","serverTime":"2026-10-22T16:52:51+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-history-drn.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-history-gas.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-history-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-base-archives-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE, MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE, MANHOLE","requestId":"74f6c4a171ec4ca1a731a6dec42b79e7","serverTime":"2026-10-22T16:52:46.6452662+08:00"}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-base-archives-drn.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"25a4d87468094dcba92af16294643adb","apiVersion":"v1","readOnly":true,"displayName":"基础档案","timezone":"Asia/Shanghai","range":{"type":"CURRENT_BASELINE"},"updatedAt":"2026-10-21T17:28:26+08:00","freshness":{"status":"CURRENT","updatedAt":"2026-10-21T17:28:26+08:00","serverUpdatedAt":"2026-10-22T16:52:46.5457572+08:00"},"totals":{"facilityCount":3575,"lengthKm":52.26098,"scale":null},"industryDistribution":[{"code":"GAS","count":0},{"code":"WATER_SUPPLY","count":0},{"code":"DRAINAGE","count":3575},{"code":"MANHOLE","count":0}],"facilityTypeDistribution":[{"code":"DRAINAGE_PIPE","name":"排水管线","count":3574,"lengthKm":52.26098,"scale":null,"range":null},{"code":"DRAINAGE_PUMP_STATION","name":"排水泵站","count":1,"lengthKm":null,"scale":null,"range":null}],"ageDistribution":[{"code":"UNDER_5_YEARS","count":3575},{"code":"5_TO_10_YEARS","count":0},{"code":"10_TO_20_YEARS","count":0},{"code":"20_YEARS_OR_OVER","count":0},{"code":"UNKNOWN","count":0}],"ageReference":{"asOfDate":"2026-10-22","commissionedCount":0,"builtCount":3575,"unknownCount":0},"districtDistribution":[{"district":null,"districtName":"未分区","count":3575}],"selectedFilters":{"industry":"DRAINAGE","facilityType":null,"ageBucket":null,"district":null,"districtName":null,"districtFilter":"NONE"},"mapFilter":{"industry":"drainage","facilityType":null,"ageBucket":null,"district":null,"districtName":null,"districtFilter":"NONE"},"excluded":{"unknownTypeCount":0,"typeKindMismatchCount":0,"smartDeviceCount":0,"futureDateCount":0}}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-base-archives-gas.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"62382fbea5ba415798e79e9d8a882b45","apiVersion":"v1","readOnly":true,"displayName":"基础档案","timezone":"Asia/Shanghai","range":{"type":"CURRENT_BASELINE"},"updatedAt":"2026-10-21T17:28:26+08:00","freshness":{"status":"CURRENT","updatedAt":"2026-10-21T17:28:26+08:00","serverUpdatedAt":"2026-10-22T16:52:46.0943752+08:00"},"totals":{"facilityCount":699,"lengthKm":7.18038,"scale":null},"industryDistribution":[{"code":"GAS","count":699},{"code":"WATER_SUPPLY","count":0},{"code":"DRAINAGE","count":0},{"code":"MANHOLE","count":0}],"facilityTypeDistribution":[{"code":"GAS_PIPE","name":"燃气管线","count":699,"lengthKm":7.18038,"scale":null,"range":null}],"ageDistribution":[{"code":"UNDER_5_YEARS","count":699},{"code":"5_TO_10_YEARS","count":0},{"code":"10_TO_20_YEARS","count":0},{"code":"20_YEARS_OR_OVER","count":0},{"code":"UNKNOWN","count":0}],"ageReference":{"asOfDate":"2026-10-22","commissionedCount":0,"builtCount":699,"unknownCount":0},"districtDistribution":[{"district":null,"districtName":"未分区","count":699}],"selectedFilters":{"industry":"GAS","facilityType":null,"ageBucket":null,"district":null,"districtName":null,"districtFilter":"NONE"},"mapFilter":{"industry":"gas","facilityType":null,"ageBucket":null,"district":null,"districtName":null,"districtFilter":"NONE"},"excluded":{"unknownTypeCount":0,"typeKindMismatchCount":0,"smartDeviceCount":0,"futureDateCount":0}}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-base-archives-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-emergency-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"18b8dee977dc4e079cfb008c858a7684","serverTime":"2026-10-22T16:52:49.8733791+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-emergency-drn.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-emergency-gas.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-emergency-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-management-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"22d06c8aab1541c7b440947ea22cf05e","serverTime":"2026-10-22T16:52:49.5480982+08:00"}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-management-drn.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"c478b50e00754f9997095ace4b4ecb88","apiVersion":"v1","readOnly":true,"displayName":"管理信息","timezone":"Asia/Shanghai","updatedAt":"2026-10-21T17:28:26+08:00","freshness":{"status":"CURRENT","reason":"READY","updatedAt":"2026-10-21T17:28:26+08:00","serverUpdatedAt":"2026-10-22T16:52:49.4780981+08:00"},"selectedFilters":{"industry":"DRAINAGE","district":null,"districtName":null,"districtFilter":"NONE"},"totals":{"facilityCount":3575,"equipmentCount":5},"districtIndustry":[{"district":0,"districtName":"城南","industry":"DRAINAGE","facilityCount":0,"equipmentCount":3},{"district":1,"districtName":"城北","industry":"DRAINAGE","facilityCount":0,"equipmentCount":2},{"district":null,"districtName":"未分区","industry":"DRAINAGE","facilityCount":3575,"equipmentCount":0}],"mapFilter":{"industry":"DRAINAGE","district":null,"districtName":null,"districtFilter":"NONE"},"excluded":{"unknownIndustryCount":0,"nonFormalIndustryCount":0,"duplicateRelationCount":0,"unknownObjectKindCount":0}}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-management-gas.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"f56d8abe6b9e4e549b74ed9c541f2706","apiVersion":"v1","readOnly":true,"displayName":"管理信息","timezone":"Asia/Shanghai","updatedAt":"2026-10-21T17:28:26+08:00","freshness":{"status":"CURRENT","reason":"READY","updatedAt":"2026-10-21T17:28:26+08:00","serverUpdatedAt":"2026-10-22T16:52:49.0311024+08:00"},"selectedFilters":{"industry":"GAS","district":null,"districtName":null,"districtFilter":"NONE"},"totals":{"facilityCount":699,"equipmentCount":0},"districtIndustry":[{"district":null,"districtName":"未分区","industry":"GAS","facilityCount":699,"equipmentCount":0}],"mapFilter":{"industry":"GAS","district":null,"districtName":null,"districtFilter":"NONE"},"excluded":{"unknownIndustryCount":0,"nonFormalIndustryCount":0,"duplicateRelationCount":0,"unknownObjectKindCount":0}}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-management-none.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"050b543b48e04a2fbfca138f4d531d33","apiVersion":"v1","readOnly":true,"displayName":"管理信息","timezone":"Asia/Shanghai","updatedAt":"2026-10-21T17:28:26+08:00","freshness":{"status":"CURRENT","reason":"READY","updatedAt":"2026-10-21T17:28:26+08:00","serverUpdatedAt":"2026-10-22T16:52:48.576327+08:00"},"selectedFilters":{"industry":null,"district":null,"districtName":null,"districtFilter":"NONE"},"totals":{"facilityCount":6776,"equipmentCount":9},"districtIndustry":[{"district":0,"districtName":"城南","industry":"DRAINAGE","facilityCount":0,"equipmentCount":3},{"district":0,"districtName":"城南","industry":"MANHOLE","facilityCount":0,"equipmentCount":1},{"district":0,"districtName":"城南","industry":"WATER_SUPPLY","facilityCount":0,"equipmentCount":2},{"district":1,"districtName":"城北","industry":"DRAINAGE","facilityCount":0,"equipmentCount":2},{"district":1,"districtName":"城北","industry":"WATER_SUPPLY","facilityCount":0,"equipmentCount":1},{"district":null,"districtName":"未分区","industry":"DRAINAGE","facilityCount":3575,"equipmentCount":0},{"district":null,"districtName":"未分区","industry":"GAS","facilityCount":699,"equipmentCount":0},{"district":null,"districtName":"未分区","industry":"MANHOLE","facilityCount":3,"equipmentCount":0},{"district":null,"districtName":"未分区","industry":"WATER_SUPPLY","facilityCount":2499,"equipmentCount":0}],"mapFilter":{"industry":null,"district":null,"districtName":null,"districtFilter":"NONE"},"excluded":{"unknownIndustryCount":0,"nonFormalIndustryCount":0,"duplicateRelationCount":0,"unknownObjectKindCount":0}}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-operating-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"ac82fedc9f474a97b9fc211b1a40c0a2","serverTime":"2026-10-22T16:52:48.126751+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-operating-drn.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-operating-gas.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-infra-operating-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-map-snapshot-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"437081ca95e4444da98d2cc4528fbdeb","serverTime":"2026-10-22T16:52:45.1667612+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-map-snapshot-drn.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-map-snapshot-gas.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"4e5abd76bc4b42958cb2e4c561d8146f","apiVersion":"v1","readOnly":true,"page":1,"pageSize":500,"snapshotAt":"2026-10-22T16:52:44.9327614+08:00","total":0,"batchUpdatedAt":"2026-10-22T16:52:44.9327614+08:00","complete":true,"monitoringPoints":[],"unassociatedEquipment":[],"coordinateValidation":{"crs":"WGS84","totalObjectCount":0,"renderableCount":0,"invalidCount":0,"unverifiedCount":0},"mapFilter":{"industry":"GAS","district":null,"districtName":null,"districtFilter":"NONE","deviceType":null,"pointStatus":null,"pointCodes":[],"deviceCodes":[]},"pointStatusDistribution":[{"status":"ALARM","count":1},{"status":"WARNING","count":0},{"status":"WORK_ORDER","count":0},{"status":"NORMAL","count":0},{"status":"OFFLINE","count":0},{"status":"EMPTY","count":6},{"status":"UNKNOWN","count":0},{"status":"UNASSOCIATED","count":9}]}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-map-snapshot-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-distribution-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"0c7672b7f9074677ae79e195fc6ff1f5","serverTime":"2026-10-22T16:52:50.4199378+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-distribution-drn.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-distribution-gas.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"b03b361732ce471588e99151e3c192ad","apiVersion":"v1","readOnly":true,"displayName":"监测分布","timezone":"Asia/Shanghai","range":{"type":"CURRENT_BASELINE"},"snapshotAt":"2026-10-22T15:14:45.2800669+08:00","updatedAt":"2026-10-22T16:52:50.2968213+08:00","createdAt":null,"freshness":{"status":"EMPTY","reason":"NO_RESOLVED_DEVICES","updatedAt":"2026-10-22T16:52:50.2968213+08:00","serverUpdatedAt":"2026-10-22T16:52:50.2968213+08:00"},"districtDistribution":[],"industryDistribution":[],"deviceTypeDistribution":[],"pointStatusDistribution":[{"status":"ALARM","count":1},{"status":"WARNING","count":0},{"status":"WORK_ORDER","count":0},{"status":"NORMAL","count":0},{"status":"OFFLINE","count":0},{"status":"EMPTY","count":6},{"status":"UNKNOWN","count":0},{"status":"UNASSOCIATED","count":9}],"mapFilter":{"industry":"GAS","deviceType":null,"pointStatus":null,"district":null,"districtName":null,"districtFilter":"NONE","pointCodes":[],"deviceCodes":[]},"pagination":{"page":1,"pageSize":10,"total":0,"totalPages":0,"hasMore":false},"monitoringPoints":[],"unassociatedEquipment":[],"excluded":{"unknownTypeCount":1,"typeKindMismatchCount":0,"nonFormalIndustryCount":0,"invalidCoordinateCount":0}}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-distribution-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-overview-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"836dee3d787b4a14a944c3a92d6af4d9","serverTime":"2026-10-22T16:52:50.1573811+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-overview-drn.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-overview-gas.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"d2e38cbfcb9f4b1f91be9341b3f00123","apiVersion":"v1","readOnly":true,"displayName":"监测概览","timezone":"Asia/Shanghai","range":{"type":"CURRENT_BASELINE"},"updatedAt":"2026-10-22T15:14:48.0332809+08:00","createdAt":null,"freshness":{"status":"EMPTY","reason":"NO_RESOLVED_DEVICES","updatedAt":"2026-10-22T15:14:48.0332809+08:00","serverUpdatedAt":"2026-10-22T16:52:50.0333803+08:00"},"counts":{"monitoringPointCount":0,"equipmentCount":0,"facilityCount":0,"infrastructureFacilityCount":0},"status":{"onlineCount":0,"offlineCount":0,"staleCount":0,"unknownOrMissingCount":0,"denominator":0},"onlineRate":{"value":"0.0000","numerator":0,"denominator":0,"state":"EMPTY","reason":"NO_RESOLVED_DEVICES"},"typeDistribution":[],"devices":[],"mapFilter":{"industry":"GAS","deviceType":null,"pointCodes":[],"deviceCodes":[]},"excluded":{"unknownTypeCount":1,"typeKindMismatchCount":0,"nonFormalIndustryCount":0}}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-overview-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-situation-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"57089be59690492696e928d77e46b214","serverTime":"2026-10-22T16:52:50+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-situation-drn.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-situation-gas.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-mon-situation-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-monitoring-bad.json

@@ -0,0 +1 @@
+{"msg":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","code":400,"data":{"category":"INVALID_REQUEST","message":"industry must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE","requestId":"ae64d3122335453db497623d469e6ae0","serverTime":"2026-10-22T16:52:53.2886039+08:00"}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-monitoring-drn.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"c262e58094cb440ba6c4d6409a97816b","apiVersion":"v1","readOnly":true,"timezone":"Asia/Shanghai","updatedAt":"2026-10-12T15:15:47+08:00","freshness":{"status":"CURRENT","reason":"RESOLVED_MONITORING_STATES","updatedAt":"2026-10-12T15:15:47+08:00","serverUpdatedAt":"2026-10-22T16:52:53.2219484+08:00"},"metricFreshness":{"status":"CURRENT","updatedAt":"2026-10-16T09:55:36+08:00","serverUpdatedAt":"2026-10-22T16:52:53.2219484+08:00"},"selectedIndustry":{"code":"DRAINAGE","key":"drainage","label":"排水"},"formalScope":{"industries":["GAS","WATER_SUPPLY","DRAINAGE","MANHOLE"],"keys":["gas","waterSupply","drainage","manhole"]},"counts":{"deviceCount":5,"monitoringPointCount":1,"monitoringFacilityCount":0,"infrastructureFacilityCount":6762,"status":{"online":3,"offline":2,"stale":0,"unknownOrMissing":0,"denominator":5},"onlineRate":{"value":0.6000,"onlineCount":3,"denominator":5,"state":"AVAILABLE","reason":null}},"typeDistribution":[{"industry":{"code":"DRAINAGE","key":"drainage","label":"排水"},"deviceType":{"code":"15","name":"仁科环境监测器"},"deviceCount":1},{"industry":{"code":"DRAINAGE","key":"drainage","label":"排水"},"deviceType":{"code":"20","name":"视频监控(排口+内涝点)"},"deviceCount":2},{"industry":{"code":"DRAINAGE","key":"drainage","label":"排水"},"deviceType":{"code":"16","name":"平升液位流量终端"},"deviceCount":2}],"excluded":{"unknownTypeCount":1,"typeKindMismatchCount":0}}}

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-monitoring-gas.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"0eb50617ae4d46198c42b75868e453b3","apiVersion":"v1","readOnly":true,"timezone":"Asia/Shanghai","updatedAt":"2026-10-12T15:15:47+08:00","freshness":{"status":"EMPTY","reason":"NO_RESOLVED_DEVICES","updatedAt":"2026-10-12T15:15:47+08:00","serverUpdatedAt":"2026-10-22T16:52:52.7366792+08:00"},"metricFreshness":{"status":"CURRENT","updatedAt":"2026-10-16T09:55:36+08:00","serverUpdatedAt":"2026-10-22T16:52:52.7366792+08:00"},"selectedIndustry":{"code":"GAS","key":"gas","label":"燃气"},"formalScope":{"industries":["GAS","WATER_SUPPLY","DRAINAGE","MANHOLE"],"keys":["gas","waterSupply","drainage","manhole"]},"counts":{"deviceCount":0,"monitoringPointCount":0,"monitoringFacilityCount":0,"infrastructureFacilityCount":6762,"status":{"online":0,"offline":0,"stale":0,"unknownOrMissing":0,"denominator":0},"onlineRate":{"value":0.0000,"onlineCount":0,"denominator":0,"state":"EMPTY","reason":"NO_RESOLVED_DEVICES"}},"typeDistribution":[],"excluded":{"unknownTypeCount":1,"typeKindMismatchCount":0}}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-monitoring-none.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-warning-settings-bad.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-warning-settings-drn.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-warning-settings-gas.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ov-warning-settings-none.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-work-orders-bad.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-work-orders-drn.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-work-orders-gas.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-work-orders-none.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geom-none.json


+ 11 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-analysis.py

@@ -0,0 +1,11 @@
+
+import json
+p='.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geo-ev-analysis-none.json'
+with open(p,encoding='utf-8') as f: d=json.load(f)
+data=d['data']
+for dim in data['dimensions']:
+    print(dim['dimensionCode'], '|', dim.get('dimensionLabel'), '| total', dim.get('totalCount'))
+    for b in dim.get('buckets',[]):
+        print('    ', b.get('bucketCode'), '|', b.get('bucketLabel'), '|', b.get('count'), '|', b.get('percentage'))
+print()
+print('filters:', json.dumps(data.get('filters'), ensure_ascii=False))

+ 30 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-code-rule.py

@@ -0,0 +1,30 @@
+
+import json, collections, re
+p='.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geom-none.json'
+d=json.load(open(p,encoding='utf-8'))
+feats=d['data']['features']
+def pref(code):
+    m=re.match(r'^([A-Za-z]{2})', str(code or ''))
+    return m.group(1) if m else '(none)'
+# 前缀 × name 里出现的片区词
+tab=collections.defaultdict(collections.Counter)
+for ft in feats:
+    pr=ft.get('properties') or {}
+    name=str(pr.get('name') or '')
+    words=tuple(w for w in ['城南','城北','太常'] if w in name)
+    tab[pref(pr.get('code'))][words]+=1
+for k in sorted(tab): print('prefix', k, dict(tab[k]))
+print()
+# 非 TC/CN 的样本
+for ft in feats:
+    pr=ft.get('properties') or {}
+    if pref(pr.get('code')) in ('GD','YL'):
+        print('ODD', json.dumps({kk:pr.get(kk) for kk in ('source','code','name','industry','typeCode','pointType','remark')}, ensure_ascii=False))
+print()
+# 第二段:行业字母(第 3-4 位)分布,验证"片区+行业+时间"
+ind=collections.Counter()
+for ft in feats:
+    pr=ft.get('properties') or {}
+    code=str(pr.get('code') or '')
+    if len(code)>=4: ind[(pref(code), code[2:4])]+=1
+print('prefix+biz code:', dict(ind))

+ 13 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-echo.py

@@ -0,0 +1,13 @@
+
+import json, os
+OUT='.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67'
+def load(n,v='none'):
+    with open(os.path.join(OUT,f"v2-{n}-{v}.json"),encoding='utf-8') as f: return json.load(f)
+for n in ['ev-current','ev-history','ev-efficiency','ev-analysis','mon-situation','infra-operating','infra-emergency','mon-overview']:
+    d=load(n)
+    data=d.get('data',d)
+    print('='*80); print(n, '| top keys:', list(d.keys()))
+    if isinstance(data,dict):
+        print('  data keys:', list(data.keys()))
+        for k in ('filters','mapFilter','selectedFilters','caliber','note'):
+            if k in data: print(f'  {k}:', json.dumps(data[k],ensure_ascii=False)[:300])

+ 32 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-geometry-district.py

@@ -0,0 +1,32 @@
+
+import json, collections, re
+p='.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geom-none.json'
+with open(p,encoding='utf-8') as f: d=json.load(f)
+feats=d['data']['features']
+print('features:', len(feats))
+# 1) 是否存在 location / 位置描述类字段
+keys=collections.Counter()
+for ft in feats: keys.update((ft.get('properties') or {}).keys())
+print('property keys:', dict(keys))
+# 2) code 前缀分布
+pref=collections.Counter()
+for ft in feats:
+    code=str((ft.get('properties') or {}).get('code') or '')
+    m=re.match(r'^([A-Za-z]{2})', code)
+    pref[m.group(1) if m else '(none)']+=1
+print('code 2-letter prefix:', dict(pref))
+# 3) 按 source 分组看前缀
+bysrc=collections.defaultdict(collections.Counter)
+for ft in feats:
+    pr=ft.get('properties') or {}
+    code=str(pr.get('code') or '')
+    m=re.match(r'^([A-Za-z]{2})', code)
+    bysrc[pr.get('source')][m.group(1) if m else '(none)']+=1
+for k,v in bysrc.items(): print('source', k, dict(v))
+# 4) 样例
+for ft in feats[:3]:
+    print('SAMPLE', json.dumps(ft.get('properties'), ensure_ascii=False)[:400])
+# 5) 看 name/remark 里是否含 城南/城北/太常
+for word in ['城南','城北','太常']:
+    hits=[ (ft.get('properties') or {}).get('code') for ft in feats if word in json.dumps(ft.get('properties') or {}, ensure_ascii=False)]
+    print(word, 'appears in', len(hits), 'features; sample codes:', hits[:3])

+ 24 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/inspect-geometry.py

@@ -0,0 +1,24 @@
+
+import json, collections
+p='.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/geom-none.json'
+with open(p,encoding='utf-8') as f: d=json.load(f)
+print('top keys:', list(d.keys()))
+data=d.get('data',d)
+print('data keys:', list(data.keys()))
+feats=data.get('features') or []
+print('features:', len(feats))
+keycount=collections.Counter()
+for ft in feats:
+    props=ft.get('properties') or {}
+    keycount.update(props.keys())
+for k,v in keycount.most_common(60):
+    print(f'  {k}: appears in {v} features')
+# look for district-ish keys
+print()
+print('district-ish values sample:')
+for ft in feats[:3]:
+    props=ft.get('properties') or {}
+    print({k:props[k] for k in props if 'dist' in k.lower() or 'area' in k.lower() or 'region' in k.lower()})
+print()
+print('coverage:', json.dumps(data.get('coverage'), ensure_ascii=False)[:600])
+print('sample props:', json.dumps((feats[0].get('properties') or {}), ensure_ascii=False)[:900])

+ 12 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/link-check.mjs

@@ -0,0 +1,12 @@
+import { readFileSync, existsSync } from "node:fs"
+import { dirname, resolve } from "node:path"
+const f = ".scratch/cockpit-real-data-wayfinding/issues/67-district.md"
+const text = readFileSync(f, "utf8")
+const links = [...text.matchAll(/\]\(([^)#][^)]*)\)/g)].map((m) => m[1]).filter((l) => !/^https?:/.test(l))
+const bad = []
+for (const l of links) {
+    const target = resolve(dirname(f), l.split("#")[0])
+    if (!existsSync(target)) bad.push(l)
+}
+console.log("links:", links.length, "broken:", bad.length)
+for (const b of bad) console.log("  BROKEN", b)

+ 16 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/overview-endpoints-not-deployed.md

@@ -0,0 +1,16 @@
+# 概览四端点参数面实测(2026-10-22T10:0x UTC)
+
+并行在制的票 05–08 在票 67 实施期间把概览数据源从旧三端点换成四个新端点
+(`/overview/perception`、`/overview/pipe-network`、`/overview/warning-study`、`/overview/alarm-disposal`)。
+对这四个端点复跑本票的探针:
+
+```
+overview/perception      [] 404 156 | ?district=0 404 | ?district=99 404 | ?industry=GAS 404 | ?industry=NOT_REAL 404
+overview/pipe-network    [] 404 158 | ?district=0 404 | ?district=99 404 | ?industry=GAS 404 | ?industry=NOT_REAL 404
+overview/warning-study   [] 404 159 | ?district=0 404 | ?district=99 404 | ?industry=GAS 404 | ?industry=NOT_REAL 404
+overview/alarm-disposal  [] 404 160 | ?district=0 404 | ?district=99 404 | ?industry=GAS 404 | ?industry=NOT_REAL 404
+```
+
+**结论:四个新端点在当前后端尚未部署(一律 404),无法用"非法值 400 / 0-1 响应不同"这套实测判据**。
+因此概览四块的口径说明依据是**前端实现与规格 §1(四个端点都不收入参)**,措辞里按这个依据写,
+不冒充"实测后端忽略"。票 77 已登记:端点上线后按同一套探针复跑,并把依据从"规格"升级为"实测"。

+ 48 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/pipe-district-selftest.mjs

@@ -0,0 +1,48 @@
+import assert from "node:assert/strict"
+import { readFileSync } from "node:fs"
+const SRC = "../../../../../src/modules/cockpit/"
+const { toPipeNetwork, filterPipeNetwork, pipeDistrictOf, PIPE_DISTRICT_PREFIX } = await import(SRC + "domain/pipeNetwork.js")
+
+// ---- 1) 真实载荷:前缀与名称(独立信号)逐条对照
+const raw = JSON.parse(readFileSync(new URL("./geom-none.json", import.meta.url), "utf8"))
+const network = toPipeNetwork(raw.data)
+const all = [...network.pipes, ...network.components]
+console.log("pipes:", network.pipes.length, "components:", network.components.length, "blocked:", network.blocked.length)
+
+let checked = 0
+const mismatch = []
+for (const item of all) {
+    const word = { 0: "城南", 1: "城北", 2: "太常" }[item.district]
+    if (!word) continue
+    checked += 1
+    if (!String(item.name).includes(word)) mismatch.push(item.code + "/" + item.name + "/" + word)
+}
+console.log("derived district checked:", checked, "mismatch:", mismatch.length, mismatch.slice(0, 3))
+assert.equal(mismatch.length, 0, "编码前缀推出的片区与名称里的片区词不一致")
+
+const unresolved = all.filter((r) => r.district === null).map((r) => r.code)
+console.log("unresolved codes:", unresolved)
+
+// ---- 2) 过滤语义
+const south = filterPipeNetwork(network, { scope: "south" })
+const north = filterPipeNetwork(network, { scope: "north" })
+const taichang = filterPipeNetwork(network, { scope: "taichang" })
+const unassigned = filterPipeNetwork(network, { scope: "unassigned" })
+const everywhere = filterPipeNetwork(network, { scope: "all" })
+console.log("south pipes:", south.pipes.length, "taichang pipes:", taichang.pipes.length, "north pipes:", north.pipes.length)
+console.log("districtApplied:", { south: south.districtApplied, all: everywhere.districtApplied })
+console.log("unresolvedCount south:", south.districtUnresolvedCount, "unassigned objects:",
+    unassigned.pipes.length + unassigned.components.length)
+assert.equal(south.districtApplied, true)
+assert.equal(everywhere.districtApplied, false)
+assert.equal(south.pipes.length + taichang.pipes.length, network.pipes.length - 0 + 0 + (network.pipes.filter(p => p.district === null).length))
+assert.equal(north.pipes.length, 0, "现网编码里没有城北前缀")
+assert.equal(unassigned.pipes.length + unassigned.components.length, unresolved.length)
+
+// ---- 3) 行业 × 片区 正交
+const gasTc = filterPipeNetwork(network, { industry: "gas", scope: "taichang" })
+const onlyGas = filterPipeNetwork(network, { industry: "gas", scope: "all" })
+const onlyTc = filterPipeNetwork(network, { scope: "taichang" })
+console.log("gas:", onlyGas.pipes.length + onlyGas.components.length, "taichang:", onlyTc.pipes.length + onlyTc.components.length, "gas∩taichang:", gasTc.pipes.length + gasTc.components.length)
+assert.ok(gasTc.pipes.every((p) => p.industry === "GAS" && p.district === 2))
+console.log("SELFTEST PASS")

+ 50 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/probe-district.sh

@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+# 票 67 —— 只读探针 v2(修 v1 的 ?/& 拼接缺陷):逐个驾驶舱端点验证是否真的支持 district 入参。
+set -u
+BASE='http://192.168.110.96:8301/api/cockpit/v1'
+OUT='.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67'
+mkdir -p "$OUT"
+START=$(date -u -d '-7 days' +%Y-%m-%dT%H:%M:%SZ)
+END=$(date -u +%Y-%m-%dT%H:%M:%SZ)
+E="&range=LAST_7_DAYS&startTime=$START&endTime=$END"
+echo "window $START .. $END"
+
+probe() {
+  local name="$1" path="$2"
+  for variant in none d0 d1 dun d99; do
+    local q=''
+    case "$variant" in
+      d0) q='district=0' ;;
+      d1) q='district=1' ;;
+      dun) q='district=UNASSIGNED' ;;
+      d99) q='district=99' ;;
+    esac
+    local sep='?'
+    case "$path" in *'?'*) sep='&' ;; esac
+    local url="$BASE$path"
+    [ -n "$q" ] && url="$url$sep$q"
+    local body="$OUT/v2-$name-$variant.json"
+    local code
+    code=$(curl -s --max-time 25 -o "$body" -w '%{http_code}' "$url")
+    local size
+    size=$(wc -c < "$body" | tr -d ' ')
+    printf '%-22s %-5s %-4s %s\n' "$name" "$variant" "$code" "$size"
+  done
+  echo
+}
+
+probe map-snapshot          "/map-snapshot?page=1&pageSize=500"
+probe infra-base-archives   "/infrastructure/base-archives"
+probe infra-operating       "/infrastructure/operating-information"
+probe infra-management      "/infrastructure/management-information"
+probe infra-emergency       "/infrastructure/emergency-support"
+probe mon-overview          "/monitoring/overview"
+probe mon-distribution      "/monitoring/distribution?page=1&pageSize=10"
+probe mon-situation         "/monitoring/situation?page=1&pageSize=10"
+probe ev-current            "/events/current?mode=WARNING&page=1&pageSize=10"
+probe ev-history            "/events/history-analysis?eventFamily=WARNING$E&page=1&pageSize=10"
+probe ev-efficiency         "/events/handling-efficiency?eventFamily=WARNING$E"
+probe ev-analysis           "/events/multidimensional-analysis?eventFamily=WARNING$E"
+probe ov-monitoring         "/overview/monitoring"
+probe ov-warning-settings   "/overview/warning-settings?range=LAST_7_DAYS"
+probe work-orders           "/work-orders?page=1&pageSize=5"

+ 107 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/probe-geometry-and-industry.sh

@@ -0,0 +1,107 @@
+#!/usr/bin/env bash
+# 票 67 —— 只读探针(2):geometry 端点是否收 district;industry 入参在各端点是否真的生效。
+set -u
+BASE='http://192.168.110.96:8301/api/cockpit/v1'
+OUT='.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67'
+mkdir -p "$OUT"
+START=$(date -u -d '-7 days' +%Y-%m-%dT%H:%M:%SZ)
+END=$(date -u +%Y-%m-%dT%H:%M:%SZ)
+W="&range=LAST_7_DAYS&startTime=$START&endTime=$END"
+
+p() {
+  local name="$1" path="$2" variant="$3"
+  local q=''
+  if [ $# -ge 4 ]; then q="$4"; fi
+  local sep='?'
+  case "$path" in *'?'*) sep='&' ;; esac
+  local url="$BASE$path"
+  if [ -n "$q" ]; then url="$url$sep$q"; fi
+  local body="$OUT/geo-$name-$variant.json"
+  local code
+  code=$(curl -s --max-time 90 -o "$body" -w '%{http_code}' "$url")
+  local size
+  size=$(wc -c < "$body" | tr -d ' ')
+  printf '%-24s %-5s %-4s %s\n' "$name" "$variant" "$code" "$size"
+}
+
+echo '--- geometry(管线/构件几何)对 district 的反应 ---'
+p geometry none ""
+p geometry d0   "district=0"
+p geometry d99  "district=99"
+echo
+
+echo '--- industry 是否真的生效(GAS vs DRAINAGE vs 非法) ---'
+p map-snapshot        "/map-snapshot?page=1&pageSize=500"        none ""
+p map-snapshot        "/map-snapshot?page=1&pageSize=500"        gas  "industry=GAS"
+p map-snapshot        "/map-snapshot?page=1&pageSize=500"        drn  "industry=DRAINAGE"
+p map-snapshot        "/map-snapshot?page=1&pageSize=500"        bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p infra-base-archives "/infrastructure/base-archives"            none ""
+p infra-base-archives "/infrastructure/base-archives"            gas  "industry=GAS"
+p infra-base-archives "/infrastructure/base-archives"            drn  "industry=DRAINAGE"
+p infra-base-archives "/infrastructure/base-archives"            bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p infra-operating     "/infrastructure/operating-information"     none ""
+p infra-operating     "/infrastructure/operating-information"     gas  "industry=GAS"
+p infra-operating     "/infrastructure/operating-information"     drn  "industry=DRAINAGE"
+p infra-operating     "/infrastructure/operating-information"     bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p infra-management    "/infrastructure/management-information"   none ""
+p infra-management    "/infrastructure/management-information"   gas  "industry=GAS"
+p infra-management    "/infrastructure/management-information"   drn  "industry=DRAINAGE"
+p infra-management    "/infrastructure/management-information"   bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p infra-emergency     "/infrastructure/emergency-support"         none ""
+p infra-emergency     "/infrastructure/emergency-support"         gas  "industry=GAS"
+p infra-emergency     "/infrastructure/emergency-support"         drn  "industry=DRAINAGE"
+p infra-emergency     "/infrastructure/emergency-support"         bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p mon-overview        "/monitoring/overview"                      none ""
+p mon-overview        "/monitoring/overview"                      gas  "industry=GAS"
+p mon-overview        "/monitoring/overview"                      drn  "industry=DRAINAGE"
+p mon-overview        "/monitoring/overview"                      bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p mon-distribution    "/monitoring/distribution?page=1&pageSize=10" none ""
+p mon-distribution    "/monitoring/distribution?page=1&pageSize=10" gas  "industry=GAS"
+p mon-distribution    "/monitoring/distribution?page=1&pageSize=10" drn  "industry=DRAINAGE"
+p mon-distribution    "/monitoring/distribution?page=1&pageSize=10" bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p mon-situation       "/monitoring/situation?page=1&pageSize=10"  none ""
+p mon-situation       "/monitoring/situation?page=1&pageSize=10"  gas  "industry=GAS"
+p mon-situation       "/monitoring/situation?page=1&pageSize=10"  drn  "industry=DRAINAGE"
+p mon-situation       "/monitoring/situation?page=1&pageSize=10"  bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p ev-current          "/events/current?mode=WARNING&page=1&pageSize=10" none ""
+p ev-current          "/events/current?mode=WARNING&page=1&pageSize=10" gas  "industry=GAS"
+p ev-current          "/events/current?mode=WARNING&page=1&pageSize=10" drn  "industry=DRAINAGE"
+p ev-current          "/events/current?mode=WARNING&page=1&pageSize=10" bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p ev-history          "/events/history-analysis?eventFamily=WARNING$W&page=1&pageSize=10" none ""
+p ev-history          "/events/history-analysis?eventFamily=WARNING$W&page=1&pageSize=10" gas  "industry=GAS"
+p ev-history          "/events/history-analysis?eventFamily=WARNING$W&page=1&pageSize=10" drn  "industry=DRAINAGE"
+p ev-history          "/events/history-analysis?eventFamily=WARNING$W&page=1&pageSize=10" bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p ev-efficiency       "/events/handling-efficiency?eventFamily=WARNING$W" none ""
+p ev-efficiency       "/events/handling-efficiency?eventFamily=WARNING$W" gas  "industry=GAS"
+p ev-efficiency       "/events/handling-efficiency?eventFamily=WARNING$W" drn  "industry=DRAINAGE"
+p ev-efficiency       "/events/handling-efficiency?eventFamily=WARNING$W" bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p ev-analysis         "/events/multidimensional-analysis?eventFamily=WARNING$W" none ""
+p ev-analysis         "/events/multidimensional-analysis?eventFamily=WARNING$W" gas  "industry=GAS"
+p ev-analysis         "/events/multidimensional-analysis?eventFamily=WARNING$W" drn  "industry=DRAINAGE"
+p ev-analysis         "/events/multidimensional-analysis?eventFamily=WARNING$W" bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p ov-monitoring       "/overview/monitoring"                      none ""
+p ov-monitoring       "/overview/monitoring"                      gas  "industry=GAS"
+p ov-monitoring       "/overview/monitoring"                      drn  "industry=DRAINAGE"
+p ov-monitoring       "/overview/monitoring"                      bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p ov-warning-settings "/overview/warning-settings?range=LAST_7_DAYS" none ""
+p ov-warning-settings "/overview/warning-settings?range=LAST_7_DAYS" gas  "industry=GAS"
+p ov-warning-settings "/overview/warning-settings?range=LAST_7_DAYS" drn  "industry=DRAINAGE"
+p ov-warning-settings "/overview/warning-settings?range=LAST_7_DAYS" bad  "industry=NOT_A_REAL_INDUSTRY"
+echo
+p work-orders         "/work-orders?page=1&pageSize=5"            none ""
+p work-orders         "/work-orders?page=1&pageSize=5"            gas  "industry=GAS"
+p work-orders         "/work-orders?page=1&pageSize=5"            drn  "industry=DRAINAGE"
+p work-orders         "/work-orders?page=1&pageSize=5"            bad  "industry=NOT_A_REAL_INDUSTRY"

+ 117 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/replay-startup.mjs

@@ -0,0 +1,117 @@
+import { readFileSync } from 'node:fs'
+import { registerHooks } from 'node:module'
+import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc'
+
+const SRC = '../../../../../src/modules/cockpit/'
+const ENV_STUB = 'const import_meta_env = { BASE_URL: "/", MODE: "development", DEV: true, PROD: false, SSR: false };'
+registerHooks({
+    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 {}' }
+        if (url.endsWith('.js') && url.includes('/src/')) {
+            const raw = readFileSync(new URL(url), 'utf8')
+            if (raw.includes('import.meta.env')) return { format: 'module', shortCircuit: true, source: ENV_STUB + raw.replace(/import\.meta\.env/g, 'import_meta_env') }
+        }
+        if (url.endsWith('.vue')) {
+            const source = readFileSync(new URL(url), 'utf8')
+            const { descriptor } = parse(source, { filename: url })
+            const script = compileScript(descriptor, { id: 'replay', genDefaultAs: '__sfc__' })
+            const template = compileTemplate({ source: descriptor.template.content, filename: url, id: 'replay', ssr: true, compilerOptions: { bindingMetadata: script.bindings } })
+            const body = script.content.replace(/import\.meta\.env/g, 'import_meta_env')
+            return { format: 'module', shortCircuit: true, source: ENV_STUB + '\n' + body + '\n' + template.code + '\n__sfc__.ssrRender = ssrRender; export default __sfc__;' }
+        }
+        return nextLoad(url, context)
+    },
+})
+
+// 最小 DOM/window stub(启动计划里只有 WS/字体/计时器需要它们)
+const noop = () => {}
+globalThis.window = globalThis.window || {
+    addEventListener: noop, removeEventListener: noop, setTimeout, clearTimeout, setInterval, clearInterval,
+    requestAnimationFrame: (cb) => setTimeout(() => cb(Date.now()), 16), cancelAnimationFrame: noop,
+    devicePixelRatio: 1, innerWidth: 1680, innerHeight: 950,
+    location: { href: 'http://127.0.0.1:8000/#/cockpit', search: '', hash: '#/cockpit', origin: 'http://127.0.0.1:8000' },
+    matchMedia: () => ({ matches: false, addEventListener: noop, removeEventListener: noop }),
+    getComputedStyle: () => ({ getPropertyValue: () => '' }),
+    ResizeObserver: class { observe() {} unobserve() {} disconnect() {} },
+}
+globalThis.document = globalThis.document || {
+    activeElement: null, visibilityState: 'visible', hidden: false,
+    createElement: () => ({ style: {}, setAttribute: noop, appendChild: noop, addEventListener: noop, classList: { add: noop, remove: noop } }),
+    createElementNS: () => ({ style: {}, setAttribute: noop, appendChild: noop, addEventListener: noop }),
+    createTextNode: () => ({}), createComment: () => ({}),
+    querySelector: () => null, querySelectorAll: () => [], getElementById: () => null,
+    head: { appendChild: noop, children: [] }, body: { appendChild: noop, children: [] },
+    addEventListener: noop, removeEventListener: noop,
+}
+if (!globalThis.navigator) Object.defineProperty(globalThis, 'navigator', { value: { userAgent: 'node' }, configurable: true })
+for (const k of ['ResizeObserver', 'matchMedia', 'getComputedStyle']) if (globalThis[k] === undefined) globalThis[k] = globalThis.window[k]
+
+const { createPinia, setActivePinia } = await import('pinia')
+setActivePinia(createPinia())
+
+const { loadCockpitConfig } = await import(SRC + 'services/cockpitConfig.js')
+const { planCockpitStartup } = await import(SRC + 'domain/startupPlan.js')
+const { STARTUP_ACTION } = await import(SRC + 'domain/startupPlan.js')
+const { registerMockTransport, resetMockTransportsForTest } = await import(SRC + 'services/cockpitApi.js')
+const { registerCockpitMocks } = await import(SRC + 'mocks/contractMocks.js')
+const { useMapStore } = await import(SRC + 'stores/mapStore.js')
+const { useDetailStore } = await import(SRC + 'stores/detailStore.js')
+const { usePipeNetworkStore } = await import(SRC + 'stores/pipeNetworkStore.js')
+const { useInfrastructureStore } = await import(SRC + 'stores/infrastructureStore.js')
+const { useMonitoringStore } = await import(SRC + 'stores/monitoringStore.js')
+const { useEventStore } = await import(SRC + 'stores/eventStore.js')
+const { useOverviewStore } = await import(SRC + 'stores/overviewStore.js')
+const { useRuntimeStore } = await import(SRC + 'stores/runtimeStore.js')
+const { useFilterStore } = await import(SRC + 'stores/filterStore.js')
+
+const requests = []
+resetMockTransportsForTest()
+registerCockpitMocks({
+    scenario: 'success',
+    handlers: {
+        registerMockTransport: (path, handler) => registerMockTransport(path, (req) => { requests.push(typeof req === 'string' ? req : JSON.stringify(req).slice(0, 120)); return handler(req) }),
+        resetMockTransportsForTest,
+    },
+})
+
+const result = await loadCockpitConfig()
+const plan = planCockpitStartup(result.config)
+console.log('config state =', result.state, 'dataSource =', result.config.dataSource)
+console.log('plan.actions =', plan.actions.join(', '))
+
+const filterStore = useFilterStore()
+const mapStore = useMapStore()
+const detailStore = useDetailStore()
+const pipeNetworkStore = usePipeNetworkStore()
+const infrastructureStore = useInfrastructureStore()
+const monitoringStore = useMonitoringStore()
+const eventStore = useEventStore()
+const overviewStore = useOverviewStore()
+const runtimeStore = useRuntimeStore()
+
+const scope = { value: filterStore.scope }
+let failedAt = null
+for (const action of plan.actions) {
+    try {
+        if (action === STARTUP_ACTION.REGISTER_MOCKS) continue
+        if (action === STARTUP_ACTION.LOAD_MAP_SUMMARY) { mapStore.setup({ config: result.config, industry: 'all', scope: 'all', deviceType: '', pointStatus: [] }); continue }
+        if (action === STARTUP_ACTION.LOAD_SHARED_SECTIONS) { detailStore.loadSharedSections({ config: result.config }); pipeNetworkStore.load({ industry: 'all', scope: 'all', config: result.config }); continue }
+        if (action === STARTUP_ACTION.LOAD_INFRASTRUCTURE_PANELS) { infrastructureStore.configure({ config: result.config }); infrastructureStore.loadPanels({ industry: 'all', scope: 'all' }); continue }
+        if (action === STARTUP_ACTION.LOAD_MONITORING_PANELS) { monitoringStore.configure({ config: result.config }); monitoringStore.loadPanels({ industry: 'all', deviceType: '', pointStatus: [], scope: 'all' }); continue }
+        if (action === STARTUP_ACTION.LOAD_EVENT_PANELS) { eventStore.configure({ config: result.config }); eventStore.loadPanels({ mode: 'WARNING', range: '7d', industry: 'all' }); continue }
+        if (action === STARTUP_ACTION.LOAD_OVERVIEW_PANELS) { overviewStore.configure({ config: result.config }); overviewStore.loadPanels(); continue }
+        if (action === STARTUP_ACTION.START_WEBSOCKET) { runtimeStore.startWithConfig(result.config, { scenario: 'success' }); continue }
+        if (action === STARTUP_ACTION.START_FRESHNESS) { continue }
+    } catch (error) {
+        failedAt = action + ' → ' + (error && error.stack ? error.stack.split('\n').slice(0, 4).join(' | ') : error)
+        break
+    }
+}
+console.log('启动计划同步执行:', failedAt || '全部动作未抛错')
+await new Promise((r) => setTimeout(r, 300))
+console.log('请求数 =', requests.length)
+console.log('mapStore.filters =', JSON.stringify(mapStore.filters))
+console.log('mapStore.summary.phase =', mapStore.summary.phase)
+console.log('infrastructure 分区 loaded =', Object.entries(infrastructureStore.sections).map(([k, v]) => k + ':' + v.state + '/' + v.loaded).join(' '))
+process.exit(0)

+ 66 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/selftest-lead.mjs

@@ -0,0 +1,66 @@
+import assert from "node:assert/strict"
+const SRC = "../../../../../src/modules/cockpit/"
+const { fetchEventAnalysis, fetchHandlingEfficiency, fetchEventHistory } = await import(SRC + "services/warningResponseApi.js")
+const { fetchCurrentEvents } = await import(SRC + "services/eventApi.js")
+const { panelView } = await import(SRC + "domain/panelViews.js")
+const { SECTION_STATE, section } = await import(SRC + "domain/sectionState.js")
+
+const config = { dataSource: "real", api: { baseUrl: "http://test.invalid" } }
+const urls = []
+const respond = { ok: true, status: 200, json: async () => ({ code: 200, msg: "ok", data: {} }) }
+const fetchImpl = async (url) => { urls.push(String(url)); return respond }
+
+async function collect(fn, options) {
+    urls.length = 0
+    await fn({ ...options, config, fetchImpl })
+    return [...urls]
+}
+
+// A. 预警分析:真收片区
+for (const [scope, expect] of [["south","district=0"],["north","district=1"],["taichang","district=2"],["unassigned","district=UNASSIGNED"]]) {
+    const list = await collect(fetchEventAnalysis, { mode: "WARNING", rangeType: "LAST_7_DAYS", scope })
+    const hit = list.filter((u) => u.includes(expect))
+    console.log("analysis", scope, "requests:", list.length, "with", expect + ":", hit.length)
+    assert.ok(hit.length >= 1, "analysis 缺 " + expect)
+}
+const allScope = await collect(fetchEventAnalysis, { mode: "WARNING", rangeType: "LAST_7_DAYS", scope: "all" })
+console.log("analysis all -> district present:", allScope.some((u) => u.includes("district=")))
+assert.equal(allScope.some((u) => u.includes("district=")), false, "all 不该带 district")
+
+// 多窗(90 天)每窗都要带
+const multi = await collect(fetchEventAnalysis, { mode: "WARNING", rangeType: "LAST_90_DAYS", scope: "south" })
+console.log("analysis 90d windows:", multi.length, "all district=0:", multi.every((u) => u.includes("district=0")))
+assert.ok(multi.length > 1, "90d 应是多窗")
+assert.ok(multi.every((u) => u.includes("district=0")), "多窗每窗都要带 district")
+
+// B. 不认片区的三个端点一个都不许带
+for (const [name, fn, opt] of [
+    ["history", fetchEventHistory, { mode: "WARNING", rangeType: "LAST_7_DAYS", scope: "south" }],
+    ["efficiency", fetchHandlingEfficiency, { mode: "WARNING", rangeType: "LAST_7_DAYS", scope: "south" }],
+    ["current", fetchCurrentEvents, { mode: "WARNING", scope: "south" }],
+]) {
+    const list = await collect(fn, opt)
+    const leak = list.filter((u) => u.includes("district="))
+    console.log(name, "requests:", list.length, "leaking district:", leak.length)
+    assert.equal(leak.length, 0, name + " 不该带 district: " + leak.join(","))
+}
+
+// D. 面板口径说明
+const empty = section(SECTION_STATE.EMPTY, { data: null })
+const { currentEventsView } = await import(SRC + "domain/eventList.js")
+const curView = currentEventsView(null, empty)
+console.log("currentEvents gap:", String(curView.gap).slice(0, 50) + "…")
+assert.ok(curView.gap && curView.gap.includes("当前范围"), "currentEvents 缺片区口径说明")
+const mustHave = ["historyAnalysis","responseEfficiency","monitoringOverview","operationOverview","operation","emergency","perceptionDevices","pipeNetwork","warningStudy","alarmDisposal"]
+const mustNot = ["archive","management","monitoringDistribution","eventAnalysis"]
+for (const id of mustHave) {
+    const view = panelView(id, null, empty)
+    assert.ok(view && view.gap && view.gap.includes("当前范围"), id + " 缺片区口径说明: " + (view && view.gap))
+    console.log("gap ok:", id, "->", String(view.gap).slice(0, 40) + "…")
+}
+for (const id of mustNot) {
+    const view = panelView(id, null, empty)
+    assert.ok(!view || !String(view.gap || "").includes("不作用于本面板"), id + " 不该有片区缺口: " + (view && view.gap))
+    console.log("no district gap ok:", id)
+}
+console.log("SELFTEST PASS")

+ 46 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/store-selftest.mjs

@@ -0,0 +1,46 @@
+import assert from "node:assert/strict"
+import { createPinia, setActivePinia } from "pinia"
+const SRC = "../../../../../src/modules/cockpit/"
+setActivePinia(createPinia())
+const { useEventStore } = await import(SRC + "stores/eventStore.js")
+
+const config = { dataSource: "real", api: { baseUrl: "http://test.invalid" } }
+let urls = []
+/** 回显请求里的 page,让分页视图与请求一致(否则会被"越界夹回"逻辑拉回第 1 页)。 */
+const fetchImpl = async (url) => {
+    urls.push(String(url))
+    const page = Number((String(url).match(/[?&]page=(\d+)/) || [])[1] || 1)
+    return { ok: true, status: 200, json: async () => ({ code: 200, msg: "ok", data: { pagination: { page, pageSize: 10, total: 30, totalPages: 3 } } }) }
+}
+
+const store = useEventStore()
+store.configure({ config, fetchImpl })
+
+await store.loadPanels({ mode: "WARNING", industry: "all", scope: "all" })
+assert.equal(store.currentPage, 1)
+
+await store.setCurrentPage(2)
+console.log("after setCurrentPage(2), currentPage =", store.currentPage)
+assert.equal(store.currentPage, 2)
+
+urls = []
+await store.loadPanels({ scope: "south" })
+console.log("after scope change, pages =", store.currentPage, store.historyPage, store.workOrderPage)
+assert.equal(store.currentPage, 1, "换片区必须回第 1 页")
+assert.equal(store.historyPage, 1)
+assert.equal(store.workOrderPage, 1)
+const currentReq = urls.find((u) => u.includes("/events/current"))
+const analysisReq = urls.find((u) => u.includes("/multidimensional-analysis"))
+console.log("current url:", currentReq)
+console.log("analysis url:", analysisReq)
+assert.ok(currentReq && /[?&]page=1(&|$)/.test(currentReq), "当前事件应回第 1 页")
+assert.ok(currentReq && !currentReq.includes("district="), "当前事件不该带 district")
+assert.ok(analysisReq && analysisReq.includes("district=0"), "预警分析应带 district=0")
+
+await store.setCurrentPage(2)
+assert.equal(store.currentPage, 2)
+urls = []
+await store.loadPanels({})
+console.log("after same-condition reload, currentPage =", store.currentPage)
+assert.equal(store.currentPage, 2, "同条件重取不复位页码")
+console.log("STORE TEST PASS")

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-d0.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-d1.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-d99.json

@@ -0,0 +1 @@
+{"msg":"district must be 0, 1, 2 or UNASSIGNED","code":400,"data":{"category":"INVALID_REQUEST","message":"district must be 0, 1, 2 or UNASSIGNED","requestId":"e03bc355da1041cc83b4093a00a9a41a","serverTime":"2026-10-22T16:48:59+08:00"}}

File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-dun.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-analysis-none.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-d0.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-d1.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-d99.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-dun.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-current-none.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-d0.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-d1.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-d99.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-dun.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-efficiency-none.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-d0.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-d1.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-d99.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-dun.json


File diff suppressed because it is too large
+ 0 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-ev-history-none.json


+ 1 - 0
.scratch/cockpit-real-data-wayfinding/verify/artifacts/t67/v2-infra-base-archives-d0.json

@@ -0,0 +1 @@
+{"msg":"操作成功","code":200,"data":{"requestId":"674211aa578441d685630bccd2e6b85b","apiVersion":"v1","readOnly":true,"displayName":"基础档案","timezone":"Asia/Shanghai","range":{"type":"CURRENT_BASELINE"},"updatedAt":"2026-10-21T17:28:26+08:00","freshness":{"status":"CURRENT","updatedAt":"2026-10-21T17:28:26+08:00","serverUpdatedAt":"2026-10-22T16:48:50.2527246+08:00"},"totals":{"facilityCount":0,"lengthKm":null,"scale":null},"industryDistribution":[{"code":"GAS","count":0},{"code":"WATER_SUPPLY","count":0},{"code":"DRAINAGE","count":0},{"code":"MANHOLE","count":0}],"facilityTypeDistribution":[],"ageDistribution":[{"code":"UNDER_5_YEARS","count":0},{"code":"5_TO_10_YEARS","count":0},{"code":"10_TO_20_YEARS","count":0},{"code":"20_YEARS_OR_OVER","count":0},{"code":"UNKNOWN","count":0}],"ageReference":{"asOfDate":"2026-10-22","commissionedCount":0,"builtCount":0,"unknownCount":0},"districtDistribution":[],"selectedFilters":{"industry":null,"facilityType":null,"ageBucket":null,"district":0,"districtName":"城南","districtFilter":"ASSIGNED"},"mapFilter":{"industry":null,"facilityType":null,"ageBucket":null,"district":0,"districtName":"城南","districtFilter":"ASSIGNED"},"excluded":{"unknownTypeCount":0,"typeKindMismatchCount":0,"smartDeviceCount":0,"futureDateCount":0}}}

Some files were not shown because too many files changed in this diff