17-infrastructure-panels.md 18 KB

17: 基础设施模式面板

What to build: 在基础设施一级模式下同时展示基础档案、运行信息、管理信息、应急保障四个二级面板,并与中央地图保持联动。

Blocked by: 06, 08

Status: resolved Labels: ready-for-agent

  • 基础档案面板展示设施规模、行业分类、区域分布和档案摘要。
  • 运行信息面板展示供应、输送、承载能力和指标占位。
  • 管理信息面板展示管理单位、权属单位和责任区域。
  • 应急保障面板展示危险源、防护目标和应急资源。
  • 四个面板支持独立展开/收起。
  • 点击面板中的可定位对象可联动地图。
  • 有面板数据适配和联动单测。

进度:100%

已完成并关闭(2026-09-18)。 逻辑闸门 57/57、真机走查 28/28; 回归:15 个逻辑闸门全绿 + 4 个既有真机走查全绿(t01 20/20、t09 28/28、t10 28/28、t11 29/29)。

交付物

  • domain/infrastructurePanels.js(新):四块面板的契约适配 + 视图计划(纯函数)。 四个 to*() 把后端四份面板级聚合读模型翻成领域对象;四个 *Panel() 把领域对象翻成 组件可直接画的块(kpi / bars / rows / list)。
  • domain/mapLocate.js(新):面板可定位对象 → 地图定位的共享接缝。三种"点不动"分得开 (不在已加载范围 / 后端判定不可上图 / 地图未就绪)。票 18 的台账与票 19 的事件定位复用同一条。
  • services/infrastructureApi.js(新):四个端点的唯一出口,每块各自成败。
  • stores/infrastructureStore.js(新):四块分区状态 + 按行业取数 + 定位联动。
  • components/InfrastructurePanel.vue(新):四块面板的共享渲染层,按 block.kind 分派。
  • domain/filterOptions.js:新增 INDUSTRY_CODES / industryQuery() / industryLabel() (前端行业键 → 后端正式行业码)。
  • domain/sectionState.js:BACKEND_TO_SECTION 认下基础设施三切片子分区用的 READY / UNAVAILABLE (翻译同义词,不是放宽判定;认不出的取值仍一律落 FAILED)。
  • mocks/contractMocks.js:四块面板的契约化样例 + infrastructure-partial 场景。

五条值得记下的判断

1. 「责任区域」在后端契约里根本不存在,面板如实标缺口而不拿行业冒充。 逐类读过 CockpitInfrastructureManagementInformationResponse/Snapshot/Service: SourceRelation 只有 objectCode/objectKind/industry/organizationCode/organizationName/ownershipType, 响应是 organizations + ownershipDistribution + mapFilter + excluded —— 全链路没有 region/area。 对比 base-archives 有 regionDistribution、emergency-support 有 area,所以不是漏读。 处理:MANAGEMENT_REGION_GAP 常量 + 界面一条缺口说明(指向票 25 的 E 组),不拿 industry 冒充区域。 这条修正了票 25 表格里「17 基础设施面板:D/E/F 契约已交付,可对接」的说法 —— E 交付了, 但票 17 验收点名的第三项(责任区域)不在该契约内。

2. 「可定位对象联动地图」是一条共享接缝,不是每个面板各写一套。 后端这些聚合切片只给点位编码、不给经纬度(operating-information 与 emergency-support 的 pointCodes),坐标在 mapStore 已加载的地图摘要里。所以联动本质是「回联 + 判定」: 按编码找对象 → 过票 07 的 isRenderable()(只认后端 renderable,前端绝不重算经纬度) → 票 03 的 setCameraFocus() 移动相机 → 票 10 的 openPoint() 打开同一张点位详情卡。 三种失败必须分得开(否则值班人员不知道是自己点错了还是数据有问题): missing(不在已加载分页里)/ blocked(后端判定不可上图,原因用票 07 同一张词表)/ unavailable(地图未就绪)。 archive 与 management 没有可定位对象(前者是聚合分布口径、后端不返回逐设施坐标; 后者只到机构维度),如实说明而不是造假按钮。

3. 行业筛选只接行业一维,范围/区域不接。 CONTEXT.md 定义行业筛选是作用于整个驾驶舱的全局条件,所以本票把它落到四个端点的 industry 入参上。 但井盖在后端正式行业枚举里没有契约位(票 25 第八轮已记:库里另有 manhole_base,正式枚举只有三个), 所以 manhole 按「不传参」处理并在面板上如实说明,不拿一个后端不认的字符串去换 400。

票 18 更正(2026-09-28):这条判断的依据已被后端推翻。 MANHOLE 现已是第四个正式行业,且全部 11 个 cockpit 服务都收(含 map-snapshot)—— git show HEAD 可见 CockpitMapSnapshotService 第 39 行为 Arrays.asList("GAS","WATER_SUPPLY","DRAINAGE","MANHOLE"),认不出的行业仍 400 但文案已改为 「must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE」。 因此 manhole 现在正常下发(domain/filterOptions.js 的 INDUSTRY_CODES 已加 manhole: 'MANHOLE', 该文件由本票与票 18 共用)。本票闸门 t17-infrastructure-panels.mjs 的 E1/E2 断言已同步更新 —— 它原来断言的 industryQuery('manhole') === '' 守的正是这条已被推翻的假设。 本票其余结论不受影响(它交付的四块面板与坐标准入/定位接缝都未变,逻辑闸门仍 57/57)。 范围/区域不接:archive 是 region(CN/CB/…)、emergency 是 area(CN/TC/…),与 filterOptions 的 south/north/taichang 对不上,那需要先裁定一套映射 —— 属票 18 的域,本票不预支。

4. 后端 Distribution 是三类维度共用结构,一律读 code 会全部落空。 CockpitInfrastructureEmergencySupportService 的三个构造函数把值分别填在 industry / area / category 上,code 一律是 null。所以适配层必须按维度给取键字段。 这条只有对着后端源码逐行读才会发现,光看演示载荷(那几行确实是 { industry: '…', count: n }) 容易当成 code。已写进 B7b 断言,并做了变异测试确认它抓得到(改回 code 即红)。

5. BigDecimal 在线上是字符串,Number() 直吃会把真值变成假 0。 演示载荷里是 "0.32"、"1.2500",所以适配层用 numberish 宽进严出:数字与数字串都认, 真 0 是有效读数,认不出的回 null → 界面画占位符。变异测试确认:把 numberish 换成 Number() 会打红 4 条(含「能力基准缺失不显示虚假 0 负荷」)。

验证

逻辑闸门 54/54(verify/t17-infrastructure-panels.mjs,七段):

node .scratch/cockpit-shell-panel-layout/verify/t17-infrastructure-panels.mjs
  • A 面板映射(2):四块与 ADR 0003 目录一致;不越界接其它模式的面板。
  • B 契约适配(13):BigDecimal 字符串、真 0 vs 缺值、三类能力、管理单位+权属、应急三段、 共用 Distribution 按维度取键、哨兵值(UNKNOWN/EMPTY)翻人话而不泄漏。
  • C 视图层(13):验收点名的画面元素逐项在场;缺值占位符;色键必带文字(票 06); 未取数说"正在取数"而不是"没有数据"(整段缺失也算"还没有",不算"加载失败")。
  • D 分区独立(6):四块各自成败、一块坏不遮其它三块 + 对抗式反证(全成功场景全绿, 故"一块坏"有信息量)、空/过期分得开、子分区 UNAVAILABLE ≠ EMPTY。
  • E 行业筛选(3):前端键→后端码;all/manhole 不传参;端到端请求串里真的带上了码。
  • F 地图联动(7):三种"点不动"分得开;坐标准入只认后端 renderable(越界坐标只要后端说 可上图就放行、合法坐标只要后端说不可上图就拦下);store 只在成功那一次动相机+开详情。
  • G 静态守卫(10):组件不拼路径、不认后端裸字段、零后端代号;domain 不发请求/不 import service; store 零裸字段;api 零凭据;零写操作;四块走同一接缝;行业筛选真的重取而不只是记在 store 里。

对抗式自查(变异测试,五条都确认守卫有牙): ① 把 isRenderable() 换成前端自算经纬度范围(票 07 明令禁止的第二套判据)→ 打红 4 条; ② Distribution 改回一律读 code → 打红 B7b;③ numberish 换成 Number() → 打红 4 条; ④ 去掉"未取数 → 正在取数"分支(首帧就会谎报"没有数据")→ 打红 C7; ⑤ 摘掉行业跟随(筛选只记在 store、不重取)→ 打红 G10。 五次变异后均已还原并复跑至全绿。

回归 14 个既有闸门 + 本票闸门全绿(exit=0): t01 / t04 / t05 206 / t06 / t07 39 / t08 / t09 38 / t10 57 / t11 61 / t12 / t21 / t21-view / t29 / t30(既有),t17 54(本票)。

  • t05 的静态守卫面已扩到本票新增的四个文件(组件 + store + 两个 domain)—— 不补进去,新代码就是检查不到的荒地(票 11 的成例)。t05 因此由 194 → 206 项。
  • t06 的 C1 断言放宽了一处(原为 <PanelPlaceholder :panel-id="panelId" /> 字面匹配): 外壳现在按 panelId 分派,占位组件前面出现了 v-else。放宽的是「前面不许有指令」, 保留的是它真正要守的两条契约(占位文本未变 + 占位组件仍带着 panelId 绑定被真实使用)。 改完做了变异测试:把占位组件从外壳里摘掉,C1 与 C5 仍然双红。

真机走查(28/28,2026-09-18)

node .scratch/cockpit-shell-panel-layout/verify/t17-infrastructure-panels-walkthrough.mjs

六组,面板内容一律从 DOM 读(证明界面真画出来了,而不是 store 里有值):

  • A 正常场景(13 项):切到基础设施模式后四块面板全 success 且各有真内容 (档案 设施总数=5处 / 里程=12.4km / 规模=中型、运行信息 供应=0.32 MPa / 输送=1200.5 m3/h / 承载=800 m3 = 验收点名的三类能力、管理信息 3 家管理单位 + 责任区域缺口说明、应急保障三段 危险源=1 / 防护目标=1 / 应急资源=2); 本模式 0 块占位(四块全是真面板),切到总体概览仍是 4 块占位(票 18/19/20 的域); 两栏可见文本零后端代号(查 26 个状态码与枚举值);无页面错误。
  • B 定位联动(5 项,验收第 6 条):面板给出 6 个可定位对象;点 MP-002 后 相机中心真的落在 110.14200,28.45300(目标 110.142,28.453)、打开了同一张点位详情卡 (level=point pointCode=MP-002)、成功时不给多余文字、地图标记数不变(2 → 2)(面板不往地图上加对象)。
  • C 不可上图(1 项):点 MP-003(后端判 UNVERIFIED)→ 给出 坐标未经校验、相机没动、 详情卡仍停在 MP-002(没被换成被拒的点位)。
  • D 展开收起(1 项):档案面板 414 → 42px(收起真的缩回标题栏高度,不是只藏内容)。
  • E 失败隔离(5 项):四端点全坏时四块都说「加载失败(其它面板不受影响)」且无页面报错; 只坏管理信息一个端点时,恰好 1 坏 3 好,档案 KPI 3 个与应急条目 4 条照常画出(验收第 3 条的形态)。
  • F 分段失败与空(3 项):infrastructure-partial 下防护目标段「该段数据未取到」且 0 条, 危险源 1 条、应急资源 2 条照常画出(一段坏了不遮盖其它段);空场景四块都是明确占位说明且不伪造 0。

走查逮到的真问题(这层证据的价值所在,三条都不是纯逻辑能发现的):

  1. 英文枚举值直接上屏(真泄漏,产品缺陷):规模=medium —— 后端 scale 是数据源透传的自由字符串 (契约手册与 CockpitContractDictionaryTest 都只把它当值,后端测试断言的就是 "medium"), 我的 CODE_TEXT 漏了这三个词,于是英文枚举印到了大屏上。已修:补 large/medium/small → 大型/中型/小型, 并加两条断言(逐值 + 归纳版「登记过的码翻译结果不得等于原码」)。 我的 A9 守卫当时只查状态码、不查枚举值,所以纯逻辑全绿而真机露馅 —— 守卫面已按此加宽到 26 个词。
  2. 组件里一段死代码:data-item-state 挂在 v-else-if 上,而与本契约里 stateText 与 rows 互斥, 那个分支永远不执行,段状态实际走的是空列表分支(文案能显示但挂在错误的节点上)。 已改成 stateText 优先且不挂 v-else-if,并补 D6b 把两者的互斥性钉住。
  3. 我自己走查脚本的两条错断言:A8 以为基础设施模式有 12 块占位(其实该模式 4 块全是真面板, 应为 0;已改为「本模式 0 占位 + 切到总体概览 4 占位」两步);C1 断言「没有详情卡」, 而 B 组刚打开过卡片,必然假(已改为「卡片没被换成被拒的那个点位」)。

回归(含既有真机走查)

逻辑闸门 15 个全绿(exit=0): t01 / t04 / t05 206 / t06 / t07 39 / t08 / t09 38 / t10 57 / t11 61 / t12 / t21 / t21-view / t29 / t30(既有),t17 57(本票)。

既有真机走查 4 个全绿:t01 20/20、t09 28/28、t10 28/28、t11 29/29。

  • t05 的静态守卫面已扩到本票新增的四个文件(组件 + store + 两个 domain)—— 不补进去,新代码就是检查不到的荒地(票 11 的成例)。t05 因此由 194 → 206 项。
  • t06 的 C1 断言放宽了一处(原为 <PanelPlaceholder :panel-id="panelId" /> 字面匹配): 外壳现在按 panelId 分派,占位组件前面出现了 v-else。放宽的是「前面不许有指令」, 保留的是它真正要守的两条契约。改完做了变异测试:把占位组件从外壳里摘掉,C1 与 C5 仍然双红。
  • t01 的 A5 断言修正了一处根因性缺陷(走查逮到):它原本用 body.split('·')[1] 从占位文案里反推 panelId,而本票把基础设施四块换成真实面板后占位组件不再渲染, panelId 解析成空、A5 假红。断言钉在了"面板体长什么样"上 —— 那本是实现细节 (占位或真实内容都该通过)。已改为读外壳新给的稳定属性 data-panel-id (CockpitPanel 新增 panelId prop,渲染为 data-panel-id), A5 与四个既有走查随即全部恢复(t01 19/20 → 20/20)。

变异测试(对抗式自查,七条都确认守卫有牙)

① isRenderable() 换成前端自算经纬度范围(票 07 明令禁止的第二套判据)→ 打红 4 条; ② Distribution 改回一律读 code → 打红 B7b;③ numberish 换成 Number() → 打红 4 条; ④ 去掉"未取数 → 正在取数"分支(首帧就会谎报"没有数据")→ 打红 C7; ⑤ 摘掉行业跟随(筛选只记在 store、不重取)→ 打红 G10; ⑥ 撤掉 scale 直译(复现真机逮到的英文上屏)→ 打红 B4b/B4c; ⑦ 把占位组件从外壳摘掉 → t06 的 C1/C5 双红。 七次变异后均已还原并复跑至全绿。

Answer

基础设施模式四块面板落地:各接一个后端面板级聚合读模型端点、四块各自成败 (一块坏了其余三块照常画出真实内容),契约适配与视图计划收在一层纯函数里由四块共用; 「点面板里的可定位对象联动地图」做成一条共享接缝(按点位编码回联已加载的地图对象 → 只认后端 renderable 的坐标准入 → 移动相机 + 打开与地图同源的两级详情卡), 三种"点不动"分得开且不假装成功。

三条边界如实划清,不发明业务: ① 「责任区域」在后端管理信息契约里没有字段(逐类读过响应类与快照类),面板以缺口说明呈现, 不拿行业冒充区域 —— 这条修正了票 25「17 的 D/E/F 已交付可对接」的说法; ② 行业筛选只接行业一维(gas/water/drainage 1:1 映到后端码;all 不传参; 井盖在后端正式行业枚举里没有契约位,同样不传参并如实说明),范围/区域归票 18; ③ 基础档案与管理信息没有可定位对象(前者是聚合分布口径、后端不返回逐设施坐标), 如实说明而不是造假按钮。

票面七条验收全部达成(逐条见正文勾选)。收尾时走查逮到并修掉一处真泄漏 (后端透传的自由字符串 scale 未翻译,英文枚举 medium 直接印上大屏)与组件里一段死代码, 两者都只有真机走查能发现 —— 这也是本票坚持跑走查的原因。

Comments

2026-09-18 认领与阶段判定

已认领。本票无 needs-triage 标签,Blocked by: 06, 08 且两张前置票均已 resolved, 故不经过诊断阶段,直接进入实施。

2026-09-18 实施前的五条澄清(维护者裁定,均按建议)

实施前就把五处"不该猜"的地方提出来并拿到裁定:① 责任区域契约缺口的呈现方式(如实说明,不冒充); ② 可定位对象联动地图的接缝与语义;③ 是否跟随全局行业筛选(只接行业,范围归票 18); ④ 是否为 18/19/20 立共享接缝(立);⑤ 自查范围(跑逻辑脚本、写但不跑走查)。

2026-09-18 维护者同意后执行真机走查

维护者明确「同意执行真机走查」后执行:首轮 23/26(三条失败:两条是我自己走查脚本的错断言, 一条暴露出组件里的死代码),修完复跑 27/27;走查日志里读出 规模=medium, 顺藤查实是 scale 未翻译的真泄漏并修掉,扩宽泄漏守卫后末轮 28/28。 期间 t01 的 A5 因我改了面板内容而假红(它从占位文案反推 panelId),已改为读稳定的 data-panel-id, t01 随之恢复 20/20。