18-monitoring-panels.md 28 KB

18: 运行监测模式面板

What to build: 在运行监测一级模式下同时展示监测概览、监测分布、运行概况、监测点台账四个二级面板,并支持地图状态筛选和点位联动。

Blocked by: 06, 08

Status: resolved Labels: ready-for-agent

  • 监测概览展示监测点/设备规模、在线率、类型分布和最近采集时间。前三项已交付; 「最近采集时间」按契约现状以「数据时间」呈现并写明区别(不伪造采集时间)—— 该字段的补契约归票 25 的 I2 / 交付包 BT-8,不阻塞本票交付。
  • 监测分布展示覆盖情况、状态分布和地图筛选。
  • 运行概况展示事件趋势、离线设备和异常态势。
  • 监测点台账以滚动卡片列出当前行业筛选内的监测点;点击卡片走与地图相同的两级详情。
  • 面板筛选可作用于地图点位。industry(含井盖)与 region 已真的作用到地图点位 (票 25 冻结的 map-snapshot 路径);状态维度按后端契约现状只作用于面板与台账、 界面如实说明不假装生效 —— map-snapshot 补 pointStatus 归票 25 的 I1 / BT-7,不阻塞本票交付。
  • 面板可独立展开/收起。
  • 有统计映射和筛选联动单测。

进度:100%(已收口,2026-09-28)

四块面板已实现并全部通过:逻辑闸门 71/71、真机走查 38/38。

收口口径(维护者 2026-09-28 裁定):本票的交付面是前端页面; 后端接口联调与补契约由其他票(票 25 的 I1/I2 与交付包 BT-7/BT-8)承接, 不阻塞后续页面开发(票 20 依赖本票,见其 Blocked by: 17, 18, 19)。 故本票按交付面收口。

两处按契约现状如实呈现、并已转交票 25 的边界(不是遗留缺陷,是已知且已声明的边界):

  • 状态筛选不作用于一张图:map-snapshot 不收 pointStatus(票 25 的 I1 / BT-7)。 面板与台账的状态筛选是真的;地图那半在面板上常驻说明,没有在前端对已加载标记做过滤 (那只对翻到的页生效,会造成"地图点少了而摘要说已上图 N 个"的自相矛盾)。 后端补入参后由票 25 的联调收口。
  • 「最近采集时间」不在本面板端点上:/monitoring/overview 无该响应字段, metricFreshness 只在 /overview/monitoring(票 20 的域)上(票 25 的 I2 / BT-8)。 本面板以「数据时间」呈现并写明区别,不伪造采集时间。口径由票 25 裁定。

本票不再有前端待办;票 25 的裁定若要求前端改接线或改呈现,按新票/新裁定再开一轮。

交付物

  • domain/panelBlocks.js(新):共享板块积木(KPI / 分布 / 趋势 / 键值行 / 条目清单 / 状态筛选 五种块构造器 + 码表 + 数值工具 + 分区状态口语)。从票 17 的 infrastructurePanels.js 提出来 —— 票 17 判定「四块面板的形状是同一件事」,本票沿用同一形状,于是「怎么构造一个块」只有一份实现、 各票只写「放哪些块」。不提出来的话 monitoringPanels.js 得 import「基础设施」模块, 正是票 17 警告的耦合。
  • domain/monitoringPanels.js(新):三份契约适配 + 四块视图计划(纯函数)。
  • domain/panelViews.js(新):panelId → 视图构造器的唯一注册表(两个模式的 8 块)。
  • services/monitoringApi.js(新):三个端点的唯一出口 + 状态码白名单(statusParam)。
  • stores/monitoringStore.js(新):三块分区 + 台账渐进加载 + 徽标 N+1 + 定位接缝复用。
  • components/PanelBlocks.vue(票 17 的 InfrastructurePanel.vue 改名):吃算好的 view, 不认识 panelId、不认识任何模式 —— 所以票 19/20 接进来不用改它。
  • domain/filterOptions.js:新增 regionQuery / hasRegionMapping / REGION_MAP_GAP / POINT_STATUS_KEYS / isKnownPointStatus / DEFAULT_POINT_STATUS。
  • stores/filterStore.js:新增 pointStatus 条件(setPointStatus / togglePointStatus)。
  • services/mapApi.js + stores/mapStore.js:map-snapshot 收三个筛选入参 + applyFilters() 换新快照重载。
  • mocks/contractMocks.js:三端点契约样例(按请求查询串真的过滤/分页)+ monitoring-no-devices 场景。
  • CockpitShell.vue:四块接真面板 + 三个筛选 watcher + 状态筛选出口 + regionMap 配置读取。

六条值得记下的判断

1. 「最近采集时间」不在本面板的端点上 —— 票 24 的字段表把它记错了地方。 票 24 把 metricFreshness.updatedAt(CockpitOverviewMonitoringResponse)记在「监测概览」面板名下, 但那是 /overview/monitoring(总体概览模式的「运行监测」面板,票 20 的域)的字段; 本模式「监测概览」对应的 /monitoring/overview 只有信封级 updatedAt 与 freshness。 逐类读过后确认:metricFreshness 在整个 cockpit 包里只出现在 CockpitOverviewMonitoringResponse。 处理:面板用 updatedAt 呈现「数据时间」,并把与「采集时间」的区别写成 METRIC_FRESHNESS_GAP 由界面说明, 不伪造一个逐设备采集时间。

2. 状态筛选是真的,但它到不了一张图** —— 这是后端契约的现状,不是取舍。** pointStatus 只有 /monitoring/distribution 收;map-snapshot 的三个入参是 industry/region/deviceType(控制器逐行读过)。而票 25 已冻结「全局筛选落到地图以 map-snapshot 为准、 服务端完成过滤」,所以状态维度今天没有落图路径。 处理(Q19 裁定):面板与台账的状态筛选做真的(后端真支持),地图那半如实标缺口 (面板上常驻一条说明),缺口写进票 25 与交付包草案。没有在前端对已加载标记做过滤 —— 那只对翻到的页生效,会出现「地图上点少了而摘要说已上图 N 个不变」的自相矛盾。

3. 「范围」在后端是无枚举约束的自由文本,所以它的映射必须由配置声明。 map-snapshot 对 region 只做 isBlank(region) ? null : region.trim()(无白名单、无校验), 真实取值是数据源透传的自由串(演示载荷里同时有 城区A/B/C、CN/CB/TC、未分区)。 前端范围键是 south/north/taichang —— 两者之间没有可依的映射。 处理:新增配置 map.filters.regionMap(键为前端范围键),默认空=不传参并在界面标缺口; 猜一套(如 south → CN)只会在真实数据是 城区A 时静默筛空,那比不筛更坏。 另注:后端把空区域归一为字面量 未分区(CockpitMapSnapshotService 的 UNASSIGNED_REGION), 它是真实取值、可以筛,不是空值。

4. 点位状态的中文词有两张同名不同义的字典,混用会把「无设备」画成「无数据」。 panelBlocks.js 的 CODE_TEXT 里 EMPTY 是新鲜度哨兵(「无数据」), 而 pointStatus.js 里 EMPTY 是点位状态(「无设备」)。所以状态分布的中文与色键只取自 票 09 那张表,CODE_TEXT 不碰点位状态词(已写进两个文件的抬头,并有 C2b 断言钉住)。

5. 分母为 0 的在线率不是 0%:那是最容易被照抄的虚假零值。 后端分母为 0 时回 onlineRate.state = "EMPTY" 且 value = "0.0000" —— 那个 0 是「没有分母」的占位。 照 value 直画就是「在线率 0.0%」,而真相是「当前范围内没有可参与计算的设备」。 处理:先按 state/denominator 收口,不适用时显示占位符 + 一句说明。 专项场景 monitoring-no-devices 把它逼出来(变体测试 M3/M14 确认守卫有牙)。

6. 「已实现但还没取数」与「还没实现」必须分开,否则界面会说谎。 外壳原先用「视图算出来没有」当判据决定走组件还是走占位,于是已接数据但仍在加载的面板 会被画成「面板占位 · panelId」—— 那是在说「这块还没做」。 处理:判据改成注册表成员资格 hasPanelView(panelId);panelMeta 三态分明 (面板占位 / 正在取数 / N 组读数)。这是自查时真的犯过的错,H9 断言与变体 M11/M12 钉住它。

验证

逻辑闸门 71/71(verify/t18-monitoring-panels.mjs,八段):

node .scratch/cockpit-shell-panel-layout/verify/t18-monitoring-panels.mjs
  • A 面板映射(3):四块与 ADR 0003 目录一致;不越界接其它模式;注册表覆盖两个模式共 8 块且 未接的面板仍回 null 走占位。
  • B 契约适配(10):BigDecimal 字符串、类型分布保序、采集时间缺口是声明过的常量、 三族分布 + 全六态状态计数、生效范围回显、台账与分布共用分页、两条趋势不混合、窗口回显、 异常清单、指标状态字典六态全译(认不出的回原码)、桶标签截断且不丢桶。
  • C 视图层(11):验收点名的画面元素逐项在场;分母为 0 显示占位符而不是 0.0%; 状态词取自票 09 的表而不是新鲜度哨兵;缺口与范围说明;空/失败/未取数三种措辞分得开; 台账分页口径点明 total 含未关联设备。
  • D 分区独立(5):三块各自成败、一块坏不遮其它 + 对抗式反证;空/过期分得开。
  • E 筛选联动(8):行业映码、状态白名单(认不出的不传参)、范围走配置映射且默认空、 「未分区」是可筛的真实取值、三个筛选真的进请求串 + 默认筛选零参数(反证)、 filterStore 状态可清除且拒脏值。
  • F 地图联动(5):筛选真的改变地图内容(mock 照后端语义真的过滤,不是只看请求串)、 范围只在配了映射时才生效、换筛选从第 1 页换新快照且丢弃旧范围对象、setup() 合并而不覆盖、 状态筛选不下发到 map-snapshot。
  • G 台账(6):与分布共用一次取数(同一份载荷引用)、翻页真的追加、到底不再发请求、 徽标按需取且不重复取、徽标取不到不给 0 + 对抗式反证、未取到不画 0。
  • H 静态守卫(12):渲染层不拼路径、不认识任何 panelId(解耦的落点)、零后端裸字段/代号; domain 三模块纯函数;store 零裸字段;api 零凭据零写操作;外壳走同一接缝且不与模式耦合; 「未实现」判据是注册表成员资格;三个筛选 watcher 都在;状态筛选不重载地图(有对照组); 台账卡片函数体自己走 openPoint。

对抗式自查(变异测试,14 条全部以断言失败打红闸门): ① 状态筛选错误地下发到 map-snapshot → F5;② region 未配置也硬发前端键 → E4/E4b/E5/F2; ③ 在线率照 value 直画(分母 0 → 虚假 0.0%)→ C1c;④ 台账换页改成替换 → G2/G3; ⑤ 徽标取不到给 0 → G5;⑥ 状态词改用新鲜度哨兵词表 → C2b;⑦ setup() 整体替换注入项 → F4; ⑧ 状态筛选也去重载地图 → H11;⑨ 台账详情路径换成裸对象 → H12; ⑩ 渲染层又按 panelId 分支 → H2;⑪⑫ 把「已实现但未取数」判成「面板占位」→ H9(两种改法各一次); ⑬ 复验 H12 收窄后的牙 → H12;⑭ 在线率虚假零值 → C1c。

两次守卫自身的缺陷(都是变异测试逮到的,都已修):

  • H12 第一版对全文匹配 detailStore.openPoint:本 store 里 locateCode 也调它, 于是「把台账的详情路径换掉」这件事测不出来(变异 M9 仍然 62/62 全绿)。 已按函数体截取收窄 —— 本票第二次栽在「守卫比它要守的东西宽」上。
  • 变异脚本第一版把「崩溃」当成红:闸门没跑到断言汇总(脚本自身改坏语法)也返回非零, 那种红对自查毫无价值。已改成要求跑到断言汇总行且因断言失败而红。

回归 16 个零依赖逻辑闸门全绿(exit=0)(末轮复跑见下): t01 / t04 / t05 218 / t06 / t07 39 / t08 / t09 38 / t10 57 / t11 61 / t12 49 / t17 57 / t21 / t21-view / t29 / t30,t18 67(本票)。

  • t05 的静态守卫面已扩到本票新增的五个文件(渲染层 + store + 三个 domain)—— 不补进去,新代码就是检查不到的荒地(票 11/17 的成例)。t05 因此由 206 → 218 项, 并且当场抓到一处真违规(monitoringStore.js 注释里写了后端裸字段 deviceStatistics),已修。
  • t17 与 t05 因组件改名同步更新:守卫面文件名单、G8 的接缝断言(hasInfrastructurePanel → panelViewOf + PanelBlocks);票 17 要守的三条契约一字未改,仍是 57/57。
  • t17 的走查脚本选择器已同步(data-infrastructure-panel → data-panel-blocks 等), 未执行(票 17 的走查属票 17 的验收面,本票不动它)。

真机走查(已执行:首轮 32/36 → 修完 36/36,2026-09-24)

node .scratch/cockpit-shell-panel-layout/verify/t18-monitoring-panels-walkthrough.mjs

维护者授权后执行(起 yarn dev,Chrome 152 headless)。六组:A 正常场景(DOM 实证四块画出来、 零后端代号、趋势桶是短标签)、B 台账渐进加载 + 徽标 N+1、C 点卡片开同一张两级详情卡且不动相机、 D 筛选联动、E 一块坏不遮其它两块、F 空场景与分母为 0。

首轮 32/36,四条红。这一层的价值正在于此:三条是纯逻辑闸门不可能发现的真缺陷。

  1. 徽标功能是死的(真缺陷):loadLedgerBadge 在 store 里写好了、视图也留了徽标位, 但没有任何人调它 —— 界面上永远停在「设备统计取数中…」。 逻辑闸门全绿是因为它只验到 store 的取数与状态机(G4/G4b/G5 直接调函数), 而"这个函数在界面上被触发了吗"根本没被问过。 修:PanelBlocks.vue 用 IntersectionObserver 观察卡片、露出来时 emit badge-needed, 外壳接到 monitoringStore.loadLedgerBadge();并补 H13 断言(变异 M15/M16 确认有牙)。
  2. 监测概览面板数字不随筛选变化(真缺陷):monitoringOverviewPayload 完全忽略 industry/deviceType,永远回同一份固定载荷 —— 请求串里参数是对的, 所以 E5「筛选真的进请求串」照样绿,而画面纹丝不动(假绿的反面:请求对了、界面没变)。 修:mock 照后端语义真的按行业/类型过滤并重算点位/设备/在线率;补 F0/F0b 端到端断言 (变异 M17/M19 确认有牙)。
  3. 走查 B1 无法演示渐进加载:后端页大小默认 500,而样例只有 8 条 → 一页到底, 「加载下一页」永不出现。修:新增现场覆盖 window.__COCKPIT_MOCK_MONITORING__ = { pageSize: 3 } (与票 08 地图摘要的 __COCKPIT_MOCK_MAP__ 同一套手法);补 F0c 断言(变异 M18 确认有牙)。
  4. A5 是我自己脚本的错断言:它拿分布行的标签(城区A/B/C)去比块标题 (「覆盖情况 · 区域」)→ 恒假。已改为读块标题(blockTitles)。

修 1 时我自己又引入一个真 bug(这一轮第二次红):ref 挂在 v-for 里的 div 上, Vue 3 收回来的是数组 → listRef.value.querySelectorAll is not a function, 而且抛在 onMounted 里把整页后续挂载打断,连带 A8b/D1/D2/D3/E5/D6 一起红(66 → 26/36)。 已把 ref 改挂根元素,并把这段教训写进组件注释。

末轮 36/36 全绿。几条值得留证的实测数字:

  • 台账卡片 3 → 6 张(分页真的追加);卡片 MP-001 徽标 设备 2 · 报警 1 · 预警 0 · 离线 0(进视口后补上);
  • 行业切「燃气」:监测点 6→2 / 设备 9→3 / 在线率 88.9%→100%,一张图点位 MP-001,MP-002 → MP-002;
  • 状态筛选:ALARM 行 aria-pressed=true、台账 2 → 1 张卡,而地图点位不变(后端不收该入参);
  • 再点一次已选状态 → aria-pressed=false(退回全部);
  • 筛选后图例合计 = 地图标记数(1 = 1),票 09 的语义未回退。

三次「守卫比它要守的东西宽」(全部由变异测试逮到,都已按函数体/回调体收窄): ① H12 对全文匹配 detailStore.openPoint,被同文件 locateCode 满足 → 台账换路径测不出来; ② H13 第一版对全文匹配 emit('badge-needed'),被 IntersectionObserver 不存在时的退化分支满足 → 拿掉观察器里的按需触发仍全绿(变异 M16); ③ 变异脚本第一版把「崩溃」当成红 → 已改成要求跑到断言汇总且因断言失败而红。

回归(末轮)

16 个零依赖逻辑闸门全绿(exit=0):t01 / t04 / t05 218 / t06 / t07 39 / t08 / t09 38 / t10 57 / t11 61 / t12 49 / t17 57 / t21 / t21-view / t29 / t30,t18 71(本票)。 真机走查 t18 38/38(本票,2026-09-28 复跑)。

Answer

**运行监测模式四块面板落地:三块各接一个后端面板级聚合读模型端点、三块各自成败 (一块坏了其余照常画出真实内容),契约适配与视图计划收在一层纯函数里; 台账与监测分布共用一次取数并按端点分页渐进加载,卡片徽标走按需 N+1; 「筛选联动地图」复用票 25 冻结的落图路径(map-snapshot 的 industry/region 入参, 四个正式行业含井盖)。**

四条边界如实划清,不发明业务: ① 「最近采集时间」不在本面板端点上(票 24 把它记到了 /overview/monitoring), 以「数据时间」呈现并写明区别,不伪造采集时间; ② 状态筛选到不了一张图(map-snapshot 不收 pointStatus)—— 面板与台账做真的、 地图那半标缺口,缺口已记票 25 与交付包; ③ 范围筛选需配置声明映射(后端 region 是无枚举约束的自由文本), 默认不下发并如实标缺口,不猜一套映射去静默筛空; ④ 点位状态词与新鲜度哨兵词是两张同名不同义的字典,状态分布只取票 09 那张表。

② 与 ① 都归票 25 维护(它已接手并补正了口径,见其「现行补契约清单」的 I1/I2 与「当前基线」)。 本票只记一句结论:map-snapshot 的 industry/region/deviceType 三入参现在已提交 (HEAD 含 MANHOLE);后端联调与补契约由票 25 承接,本票不自行断言后端状态。

票面七条验收按本票的交付面(前端页面)全部达成;第 1 条与第 5 条各有一处后端契约边界 (见进度区)—— 那两处已转交票 25(I1/I2、BT-7/BT-8),按维护者 2026-09-28 裁定 不阻塞后续页面开发,故本票收口(100% / resolved)。

Comments

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

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

读现状时确认:票 17 已把两处「不该猜」的地方显式推给本票 —— 「范围/区域」的映射(其正文原句「那需要先裁定一套映射 —— 属票 18 的域,本票不预支」) 与面板内筛选入口的形态。

2026-09-18 实施前的 21 条澄清(维护者裁定)

分三轮问完,全部拿到裁定。归纳(括号内为裁定结果):

  1. 筛选落图范围(接 industry;region 走配置声明的映射,默认空=不传参并标缺口)
  2. 井盖处理(沿用票 17:不传参 + 如实说明)
  3. 筛选入口面(复用全局筛选条,面板只回显生效范围)
  4. 台账数据源(与监测分布共用一次取数,按端点 pagination 渐进加载)
  5. 验证范围(本轮跑零依赖逻辑脚本;真机走查等授权)
  6. 区域映射形状(map.filters.regionMap,默认空,键为前端 scope 键)
  7. 状态分布形态(可点筛选;且要求也作用到地图)
  8. 事件趋势窗口(面板内给 24 小时 / 7 天切换)
  9. 筛选变化时地图行为(从第 1 页换新快照、丢弃已加载对象)
  10. 台账徽标(走 N+1 逐点位取)
  11. 覆盖情况口径(用本端点自己的区域/行业/设备类型分布)
  12. 后端缺口怎么收(面板+台账先做真,地图那半标缺口不 close,缺口写进票 25 与交付包)
  13. 状态筛选控件落点(写进全局 filterStore,与行业/范围同级)
  14. 徽标触发时机(卡片进视口才拉,失败不影响其它卡片)
  15. 时间窗口归属(面板本地状态,不进 filterStore)
  16. 控件面(全局条不加行;由监测分布面板内的状态分布条驱动)
  17. 「全局但不真全局」怎么处理
  18. 地图不变时的表现(面板常驻一条「状态不影响一张图」的说明)
  19. 解 Q17 与 Q7/Q12/Q13/Q16/Q18 的冲突(以 Q12/Q16/Q18 为准:面板+台账做真的、地图标缺口)
  20. 渲染层接缝(重命名为中性名 PanelBlocks.vue,同步改票 17 的引用与守卫面)
  21. 取数时机(跟票 17:外壳挂载后 mock 就绪即取,并跟随全局行业变化)

Q7 与 Q19 的关系记一笔:Q7 原选「状态筛选也要作用到地图」,但后端 map-snapshot 不收该入参;Q12/Q19 随后把它收成「面板+台账先做真、地图那半标缺口」。 两轮之间维护者还选了 Q17=C(等后端补完再做状态筛选),与 Q7/Q12/Q13/Q16/Q18 冲突, 在 Q19 里裁定以 Q12/Q16/Q18 为准 —— 即 Q17=C 只对地图那半生效,面板与台账照做。

2026-09-18 实施与自查

实施中自查逮到一处自己引入的真 bug:「已实现但还没取数」的面板被画成 「面板占位 · panelId」(判据错用了「视图算出来没有」而不是注册表成员资格)。 已修为 hasPanelView(panelId) + panelMeta 三态分明,并补 H9 断言与两条变异确认有牙。

t05 扩守卫面时当场抓到一处真违规:新 store 的注释里写了后端裸字段 deviceStatistics 与 monitoringPoints(守卫要求页面/store 零后端裸字段名),已改成领域词。

变异测试第二版才抓到 H12 的守卫比它要守的东西宽(对全文匹配 detailStore.openPoint, 被同文件另一处调用满足),已按函数体截取收窄。

2026-09-28 复核(后端重构后):逮到两条自己交付面里的真缺陷并修掉

隔了一轮回来读现状(后端已从 c5a5b87 推进到 355a8af,cockpit 区 115 个文件大改), 核出两条我之前交付的东西是坏的,两条都是"守卫比它要守的东西宽"的同一族:

缺陷 1:井盖(MANHOLE)已可筛选,我却按旧事实不传它。 git show HEAD 确认 MANHOLE 现是第四个正式行业,且全部 11 个 cockpit 服务都收 (含 map-snapshot,第 39 行),认不出的行业仍 400 但文案已改为 「must be GAS, WATER_SUPPLY, DRAINAGE or MANHOLE」。 而我当初按票 17 的结论故意不传井盖(INDUSTRY_CODES 里没有 manhole), 依据是「井盖在后端正式枚举里没有契约位」——这条依据已被后端推翻。 后果:全局筛选条上那个「井盖」按钮,对面板与地图完全不生效,而它正是验收第 5 条的一个维度。 修:INDUSTRY_CODES 加 manhole: 'MANHOLE';清掉 5 处过时注释;删掉已成死代码的 MANHOLE_INDUSTRY_GAP;样例补一个井盖点位与类型(否则筛选筛出空、看起来像坏了); 补 F0d(筛得出真数据)与走查 D2c/D2d(界面真的筛出东西且地图跟着变)。 票 17 的 E2 断言同步改(它守的正是那个已被推翻的假设,见 Q5 裁定)。

缺陷 2:map.filters.regionMap 是死代码 —— 范围筛选端到端从未生效。 我实测:normalizeConfig({map:{filters:{regionMap:{...}}}}) 的 map.filters === undefined (归一化后的 map 键里根本没有 filters)。外壳读它,所以永远读到 {}。 而我当时的 E4 断言只单独测了 regionQuery() 并手喂映射表,从没验过配置真的送得到 —— 又一次"测了函数、没测通路"。修:把 filters.regionMap 补进配置契约 (builtinConfig + normalizeConfig,校验口径与兄弟项一致、脏值逐项丢弃并记 issue、 值 trim);补 E4c/E4d/E4e 三条从原始配置载荷出发的端到端断言。

变异测试(本轮新增 5 条,全部以断言失败打红): M20 井盖映射退回「无契约位」→ E1/F0/F0d;M21 归一化器不认 filters → E4c/E4d; M22 坏键原样留下 → E4d/E4e;M23 不做 trim → E4d;M24 样例拿掉井盖 → F0c/F0d。 过程中又逮到两处自己的判据缺陷:① E4d 原来只查 length === 1, 放过"坏键被原样留下"(M22 全绿)→ 已加坏键存在性检查; ② F0d 在样例被删时会抛 Cannot read properties of null(崩溃不是有效的红, 本票已三次栽在这上面)→ 已改成防御式取值、干净判失败。 另外 G3 的判据也从"点数又涨了"改成分页终态:分页窗口横跨「监测点 + 未关联设备」, 最后一页可能整页都是设备(点数不涨),原判据会把正常的到底误判成失败。

真机走查复跑 38/38(新增 D2c/D2d 井盖两条)。 期间修掉走查里一处钉在别人票上的断言:A8b 原来断言「切到预警处置 → 恰好 4 块占位」, 而票 19 已把预警处置接上真面板(0 占位)→ 假红。已改为只对运行监测断言 0、 其余模式的数量只作汇报 —— 别人的票推进不该让本票的走查变红(票 17 的 A5 栽过同一个坑)。

另记(非本票):复核期间 t04-config 曾因另一会话在途的票 36/38 改动短暂变红 (空配置应等于内置默认,差在 locate.point/device 预设形状),其改动落地后已恢复绿。 本票未触碰那部分。

2026-09-28 收口(维护者裁定:后端联调由其他票完成,不阻塞后续页面开发)

维护者裁定:本票的交付面是前端页面;后端接口联调与补契约由其他票承接,不阻塞后续页面开发。 据此把两处后端契约边界转交票 25(它已接手并补正口径),本票按交付面收口:

  • 状态筛选不落图 → 票 25 的 I1 / 交付包 BT-7(map-snapshot 补 pointStatus);
  • 「最近采集时间」不在本端点 → 票 25 的 I2 / BT-8(补字段或裁定改取 /overview/monitoring)。

这两条在界面上都是如实呈现的(不是藏着):状态筛选那半在监测分布面板常驻一条说明、 没有在前端对已加载标记做过滤(那只对翻到的页生效,会造成"地图点少了而摘要说已上图 N 个" 的自相矛盾);采集时间以「数据时间」呈现并写明它与「采集时间」的区别,不伪造。

收口动作(已执行):

  1. Status: claimed → resolved、进度 95% → 100%、验收清单七条按前端交付面全部勾选 (两处后端边界在勾选项里写明归属与现状,不含糊);
  2. 票 25 里三处「阻塞票 18 收口 / 阻塞票 18 第五条 / 票 18 收口不可假装达成」的措辞已更正 —— 否则 tracker 会显示一条已经不存在的阻塞边(本票已收口,那些是票 25 自己的后端待办);
  3. map.md 的本票条目与 Notes 状态同步为「已关闭」。
  4. 票 20 随之解锁(其 Blocked by: 17, 18, 19,三张现均已 resolved)。

票面七条验收按前端交付面的判定: 第 2/3/4/6/7 条完全达成;第 1 条(采集时间)与第 5 条(状态筛选落图)各自的后端部分已转交票 25、 前端部分(如实呈现 + 不假装)已达成。本票不再有前端待办; 票 25 的裁定若要求前端改接线或改呈现,按新票/新裁定再开一轮。