42-associated-device-layer.md 18 KB

42: 关联监测设备临时图层

进度(2026-10-13 诊断,needs-triage 已摘除)

分类:bug。终态:ready-for-agent(诊断完成、规格已 resolved、根因与代码路径已钉牢,可直接实施)。历史实施真实(37/37 逻辑+19/19 变异+真机走查),重开缺陷是刷新/快照失效生命周期,非原实施虚假。同编码坐标/状态重绘缺口已随整体验收修复(ead3944,t48-linkage-invariants 通过),勿重复。

现象

选中监测点(设备层已上图)后:

  1. 点「手动刷新快照」:请求窗口内旧设备实体与面板仍显示旧快照数据,直到首页返回才撤换——与规格 14:86「换新快照时先撤下旧关联设备图层」相悖。
  2. 分页请求被后端判 SNAPSHOT_EXPIRED(快照失效):装载器进 expired 相位,但 batchUpdatedAt 不变 → 外壳撤层监听永不触发 → 旧设备层永不撤下,持续误读为当前范围数据。
  3. 刷新完成后:所选点位若在后续页,objects 监听+计划指纹会让设备层重建(重找部分生效);但详情缓存不重载(跨端点不同版风险提示常驻)、无「完整加载仍找不到→关闭详情并说明原因」核对、无「暂隐+顶栏待核对」状态——规格 14:86 要求的重载链未实现。

影响范围

刷新按钮路径(applyFilters→refresh 同链)、快照失效路径、以及刷新后的详情一致性。不涉及:同编码重绘(已修)、翻页闪烁(正常)、关闭/换点(正常)、初始加载(正常)。

最小复现

  1. 打开带有效坐标关联设备的点位(?scenario=device-layer),确认设备层已上图。
  2. 点「手动刷新快照」→ 观察请求窗口内 data-map-devices 仍非 0(根因 ①)。
  3. mock 第 2 页返回 SNAPSHOT_EXPIRED → 相位 expired 后 data-map-devices 仍非 0(根因 ②)。
  4. 刷新完成后把所选点位移到第 2 页 → 设备层重建但详情仍是旧缓存且永不核对;把点位从后端完全移除 → 详情永不关闭(根因 ③)。

根因(均已代码核验,附置信度)

# 根因 位置 置信度
① 刷新窗口:loader.refresh() 内部立即清空,但 store 的 summary.value 只在 await 首页返回后更新;外壳监听 batchUpdatedAt 在那之前不触发 mapStore.js:153(await loader.refresh())+ CockpitShell.vue 快照监听 高(已确认)
② 快照失效:expireMapSummary 不改 batchUpdatedAt → 撤层监听去重判据不变 → 永不触发 mapSummary.js:expireMapSummary + CockpitShell.vue:syncDeviceLayerForSnapshot 去重 高(已确认)
③ 重载链缺失:详情仅在用户重点且缓存无明确答复时重载;无快照变更驱动的"逐页重找→找到重载详情→完整找不到才关闭"核对;invalidateForSnapshot 清空后立即按调用时刻摘要重算(生产路径调用时摘要已新,此处修正先前审查的定性:不是独立缺陷,真正缺口是 ①② 与本条的链) detailStore.js:openSelection + deviceLayerStore.js:invalidateForSnapshot + 规格对照 高(缺失已确认;原"旧摘要重绘"定性修正为中低)

修复方向(供实施票执行,不在诊断阶段改码)

  • 刷新发起时即撤下设备层并暂隐详情(顶栏待核对),不等首页返回(根因 ①)。
  • 快照失效相位直接触发撤层(与 batchUpdatedAt 判据解耦,或监听 phase 变化)(根因 ②)。
  • 按规格 14:86 补核对链:逐页查找所选点位(覆盖已取页与后续页,不得以合并态护栏判不存在)→ 找到有效标记恢复锚定并重载设备详情;确认无地图位置则固定详情;完整找不到才关闭并说明;查找失败保持待核对可重试(根因 ③)。

实施记录(2026-10-13,维护者裁定「按规格」后实施)

维护者对 ❓ Q1 回复按规格:完整实现规格 14:86 核对链。交付:

  • domain/mapSummary.js:装载器维护本快照全页对象键索引(换快照即重置;每页合并后全量记入,护栏截断不影响),新增 hasObject(kind, code) —— 存在性判定覆盖所有已取页,合并态护栏不作缺席证据(规格明写)。
  • stores/mapStore.js:refreshing 信号(刷新链发起即亮、收口即灭)+ hasSnapshotObject() 出口。
  • stores/detailStore.js:reloadPointDetail(pointCode) —— 强制重取点位摘要(不查「已答过」缓存),刻意不动 selection(保留选择身份与详情层级)。
  • stores/deviceLayerStore.js:suspendForSnapshot() —— 撤下旧设备图层但不按当前(旧)摘要立即重算;图层挂起至新快照首页或核对链恢复。
  • CockpitShell.vue:三个监听(刷新发起 → 挂起+清收场提示+待核对;expired 相位 → 挂起;settled → 核对收口)+ reconcileSnapshotSelection()(找到→强制重载详情;完整加载找不到→关闭详情并顶栏说明;失败/失效→保持待核对可重试)+ 顶栏 data-snapshot-pending / data-snapshot-absent 提示(关闭入口由既有 data-anchor-hidden chip 保留)。

证据:verify/t42-refresh-lifecycle.mjs 29/29(真实 store + 注入 fetch:refreshing 发起/收口、expired 相位、全页索引含跨页/重置、强制重载计数与层级保留、挂起不重算;外壳静态守卫钉住三个监听/核对函数/chip)。相邻 19 组闸门复跑,本票范围全绿。

三处红为并发会话在途改动、与本票无关(据实记录):① pointAnchor.js 他票面板宽度修改(t41 D6 裁切断言随之过期,归该票同步);② cesiumEngine syncAssociatedDevices 被地图文字工作加入 markerEntityIndex,t45 连续性脚本的 vm 沙箱未同步;③ t05 D1-overviewStore 裸字段(他票,先前已记录)。

保持 ready-for-agent:顶栏 chip 的画面观感与「待核对→恢复」全链真实走查待补(同票 41 先例:证据齐再关闭)。

Type: implementation Status: ready-for-agent Blocked by: 41 Labels: ready-for-agent

What to build

按规格 14 接入选中监测点后的关联监测设备临时图层。地图摘要继续只包含监测点与未关联设备;关联设备不写回摘要序列、分页、默认图例或摘要计数。地图点击和台账选择都从同一监测点选择语义启动设备加载,并与点位定位并行;设备数据到达即可渲染,不等待相机动画结束。

后端 #69 已发布:有 snapshotAt 时详情与地图共享同一快照;设备编码集合以地图快照 deviceCodes 为准,坐标只认设备自身 longitude/latitude/coordinateValidation.renderable。无坐标、非法、未校验、归属不明、详情失败或当前快照不一致的设备继续出现在清单中,并显示待确认/不可用原因,禁止复制监测点坐标或人工偏移。现网 UNVERIFIED_COORDINATE 的正式边界是清单-only。

Acceptance

  • 选中监测点后关联设备图层自动加载;地图与台账入口行为一致。
  • 使用受控的坐标准入有效样例验证独立图层渲染与拾取;现网后端 UNVERIFIED_COORDINATE/renderable=false 时只显示清单及原因,不用临时样例冒称正式设备已能上图。
  • 有效设备只进入独立临时图层,设备身份同时携带 deviceCode 和所属 monitoringPointCode,不冒充 pointCode。
  • 关闭、换点、刷新、快照失效时旧图层立即撤下;迟到响应不能覆盖当前选择。
  • 同一 snapshotAt 翻页不重建或闪烁设备图层;快照查找逐页进行,第一页没有不能判定点位不存在。
  • 设备总数来自当前快照去重 deviceCodes;详情独有/快照独有设备分别标记不一致或待确认,不静默丢弃。
  • 总览/运行监测显示全部坐标准入有效设备;基础设施隐藏;预警处置只显示设备自身 ALARM/WARNING,未知状态待确认。
  • 清单保留全部关联设备,明确区分已上图、无地图位置、模式隐藏、待确认/失败;摘要图例和关联设备图例/计数分开。
  • 纯逻辑覆盖集合分类、快照生命周期、计数互斥;外壳走查覆盖地图/台账入口、关闭、换点、分页和降级。

Out of scope

  • 设备 hover/选中双向反应、资产选择、状态特效、重叠展开、完整模式计划的独立实现。

进度:100%(2026-09-29 收口)

已完成并关闭。 逻辑闸门 37/37、变异测试 19/19 全部打红、真机走查已执行(见下), 全量零依赖闸门回归绿。

交付物

  • domain/deviceLayer.js(新):集合分类(六类互斥:drawn/no-location/hidden/pending/ inconsistent/unavailable)、快照生命周期(总数只认当前快照去重 deviceCodes; 快照独有→待确认、详情独有→不一致且禁止上图)、互斥计数与独立图例、跨端点风险提示。
  • stores/deviceLayerStore.js(新):合成点。它读 mapStore 的当前快照 deviceCodes(总数依据) 与 detailStore 的 devices[](状态与设备级坐标),按 domain 判定后交地图服务。 两侧都不为自己长出对方(票 10 的 F4、票 02 的层次纪律)。
  • services/cesiumEngine.js:syncAssociatedDevices / readAssociatedDevices / clearAssociatedDevices / pickDeviceMarkerAt / hasDeviceMarker + 独立数据源 cockpit-associated-device-markers(与摘要 cockpit-point-markers 分开)。
  • services/mapService.js:syncAssociatedDevices / clearAssociatedDevices / getAssociatedDeviceState + 宿主读数 data-map-devices(-plan)(与摘要 data-map-markers* 分开)。
  • domain/pointDetail.js:设备卡片带出设备级坐标与校验(后端 #69 已交付)。
  • pointSelection.js:统一选择入口在打开详情前先出一次设备计划(不等详情、不等镜头)。
  • CockpitShell.vue:四类订阅(选择 / 详情到达 / 快照变化 / 一级模式)+ 卸载拆监听与复位。
  • components/PointDetailCard.vue:关联设备清单(保留全部、每行标注互斥归类)+ 独立图例。
  • mock:默认场景照抄现网边界(设备统一 UNVERIFIED_COORDINATE/renderable=false); 新增 device-layer 受控有效坐标样例(两台设备带自身有效坐标,且地图 deviceCodes 与详情 devices[] 一致)。
  • 证据:verify/t42-device-layer.mjs(37/37)+ 变异 19/19 全部打红。

三条关键取舍(都来自已 resolved 的规格 14 / 决策票 05,不在本票发明)

  1. 总数只认当前快照去重 deviceCodes,不拿详情统计或卡片数冒充 —— 详情不携地图快照的 snapshotAt、还可能复用缓存,两边可能不同版。所以「快照独有」与「详情独有」必须分开: 前者待确认、后者标不一致且禁止上图,两边都不静默丢弃。
  2. 坐标准入只认设备自身 coordinateValidation.renderable(票 07 的唯一判据): 前端不重算经纬度范围、绝不用监测点坐标代替设备坐标、不人工偏移(CONTEXT.md 明令禁止)。 现网无可信校核源 → 正式边界是清单-only,本票照抄这个边界。
  3. 数据可用性先于模式可见性(实现时抓到并修掉的真问题):若先判模式,一台「坐标被后端否决」 的设备在预警处置里会显示成「当前模式隐藏」,值班人员会以为"切到总览就能看到它"—— 而它任何模式都上不了图。说成模式隐藏是把数据问题推给了模式,差额就解释错了。

真机走查(已执行):抓到并修掉 1 处真缺陷

本机 @puppeteer/browsers 是空目录(票 37 记录的既有环境缺陷),故按票 36/37/41 成例 改用 DSH 自带浏览器真实驱动(yarn dev @8000,真实 public/cockpit/config.json)。

修掉的缺陷:UI 把「清单行数」当成了「关联总数」。 真机读数暴露:总数显示 2 而当前快照 只有 1 台设备 —— 多出来的是「详情独有」那一行(规格明确它不计入总数)。 修:store 单独持有 total(=当前快照去重 deviceCodes 条数),与 rows.length 分开; 界面两个数都显示且各有明确含义。已补断言 B1b-total-is-not-row-count 钉住。

实测证据(真实浏览器,与验收逐条对应):

  • 受控有效坐标样例:两台设备真的画在独立图层上(onMap=2),实体 id 形如 device|MP-001|EQ-001,属性同时带 deviceCode + monitoringPointCode、不含 pointCode (不冒充监测点);摘要标记仍为 2(data-map-markers=2)、设备另计(data-map-devices=2)。
  • 现网降级边界(清单-only):默认场景下设备统一 UNVERIFIED_COORDINATE/renderable=false → 清单保留并标 no-location、引擎 0 台、总数 1、清单 2 行(inconsistent 那行不计入总数、 不上图)、跨端点风险提示出现、摘要标记仍 2。
  • 关闭即撤下:点关闭 → 设备层 onMap=0、计划清空、宿主读数归零,而摘要标记仍 2(互不牵连)。
  • 换点:MP-001 → MP-002 后只剩 B 的设备(归属 pointCode 正确),A 的不残留。
  • 独立拾取:pickDeviceMarkerAt() 只认本图层带 deviceCode 的实体并回身份; 设备优先于监测点(票 15 的优先级)。

未覆盖项(如实记录):走查时浏览器标签 document.hidden===true,rAF 被冻结、Cesium 不渲染 (票 37/41 记过同一环境事实),因此依赖渲染的 scene.pick 在本次环境下连监测点标记也拾不到 (已用"连 pointHit 也为 null"的对照确认是环境而非本票缺陷)——「真实鼠标点设备点」这条 双向联动入口的走查归票 43(它正是双向对应的实现票,且 pickDeviceMarkerAt() 的接缝已就位)。 建议维护者在前台标签页复跑一次以补该条观感。

与下游票的边界(不在本票实现)

  • 票 44:设备形状/尺寸/资产接缝与独立设备状态词表。本票的设备标记用中性占位符号 (shape 由调用方给,默认 circle)与 14px 尺寸(小于监测点的 18px),不实现状态词表。
  • 票 46:完整四模式图层计划(domain/modeLayerPlan.js)与图例合成。本票只实现 单点位的模式可见性(isVisibleInMode),不做全图矩阵。
  • 票 47:设备状态特效。
  • 票 43:面板与三维设备点的双向对应(hover/选中/引线/定位)。

回归

零依赖逻辑闸门全绿(t01/t03/t04/t05 254/t06/t07 39/t08 44/t09/t10 57/t11/t12/t13/ t14/t15/t16/t17/t18 71/t19/t21/t21-view/t29/t30/t36/t37/t38/t39/t41 46/t42 37)。 两处红与本票无关:① t28-fill-container.mjs 需 puppeteer,而本机 @puppeteer/browsers 是空目录 (既有环境缺陷);② 某次运行中 t05 的 C8-real-attaches-credential 一度红 —— 经核实是并发会话 正在改 services/cockpitApi.js / mapApi.js(本票改动清单里没有这两个文件),随后复跑已恢复绿。

按 AGENTS.md:本会话未跑 build(真机走查是维护者本轮明确要求才执行的)。

下一步

  1. 维护者在前台标签页复跑一次,补「真实鼠标点设备点」的双向联动观感(或直接由票 43 覆盖)。
  2. 43(面板与三维设备点双向对应)、44(设备位置符号与状态词表)、46(模式图层计划) 均已解除阻塞,可并行。

Comments

local — 2026-10-10T08:10:42.403Z

整体验收审查:刷新等待首屏期间旧设备层仍在;快照失效未改变batchUpdatedAt时不撤层;缺重找点位、强制重取详情及完整找不到后关闭链。另同编码坐标/状态未重绘已在整体验收当前工作区修复(t48-linkage-invariants),尚待提交与真实样本回验。当前票因刷新生命周期未满足重新打开,先诊断再实施。

local — 2026-10-13T06:51:11.942Z

This was generated by AI during triage.

Triage Notes — 诊断结论(2026-10-13)

分类:bug|终态:ready-for-agent(needs-triage 已摘除)。

已确认(代码核验 + 规格 14:86 原文对照):

  • 历史实施真实:37/37 逻辑 + 19/19 变异 + 真机走查;重开缺陷是刷新/快照失效生命周期,非原实施虚假。
  • 同编码坐标/状态重绘缺口已随整体验收修复(ead3944,t48-linkage-invariants 通过),勿重复。
  • 根因 ①(高置信):刷新请求窗口内 store 仍发布旧摘要,外壳 batchUpdatedAt 监听不触发 → 旧设备层存活,违背规格"先撤下"。
  • 根因 ②(高置信):快照失效走 expireMapSummary,batchUpdatedAt 不变 → 撤层监听永不触发 → 旧层永不撤下。
  • 根因 ③(高置信,定性修正):规格 14:86 要求的"逐页重找→找到重载详情→完整找不到才关闭"核对链未实现;objects 监听让设备层在后续页重建(部分生效),但详情不重载、无收场核对。原审查"撤下后从旧摘要重绘"定性修正——生产路径调用时摘要已新,非独立缺陷。

最小复现: 打开 ?scenario=device-layer 设备层 → 刷新(窗口期旧层存活)→ mock 第 2 页 SNAPSHOT_EXPIRED(失效后旧层仍上图)→ 刷新后点位移页/移除(详情不重载/永不关闭)。

实施方向与详细根因表: 见票面「进度(2026-10-13 诊断)」节;证据 verify/artifacts/t48/acceptance-status.md。

❓ Q1(待拍板,不阻塞标签流转): 修复范围按规格 14:86 完整实现核对链,还是先只修 ①② 时序缺口、核对链另立子票?➡️ 推荐:按规格完整实现(规格已 resolved,核对链是本票验收第 4 条的字面要求;拆出会让整体验收 48 的第 2 条仍差一块)。回复后按选定范围实施,标签维持 ready-for-agent。