# 28: 后端执行票 —— 无关联设备监测点被静默丢弃(计数不对称) Type: task Status: resolved Assignee: codex Labels: ready-for-agent, wayfinder:task Category: backend-execution ## 进度 已完成并关闭:七文件修复、新增六项契约回归、八模块编译、423项契约测试、12项真实只读验收、提交推送均完成。提交 [ba0ce076](http://192.168.110.30:3000/shaoy/pipe-ner-server/commit/ba0ce076f31086d2c024b2b4cd93a6c05105b9eb) 已快进合入并推送 `origin/master`;修复分支同步保留,未混入其他会话未提交改动。 已读全票面/评论/标签/依赖并认领;初始 ready-for-agent,无 needs-triage、无阻塞、无历史实施,根因核验属实。初始后端基线 master=740d896;期间并行会话推进到 0a6b108 且有 workorder 在途改动,本票未触碰其文件。因后端目录在沙箱外,第一次 git apply 被拒绝;随后同一命令经扩展权限成功应用七文件补丁。 交付:distribution 保留无设备未知类型点位为 EMPTY,坐标只控制呈现、不控制统计/分页/筛选;类型排除按对象自身计1;invalidCoordinateCount 保留为统计排除兼容字段0,每对象 coordinateValidation 保留真实不可上图原因。按原始关联事实去除全量设备的关联重复,且筛后才发布 deviceCodes。overview 真实适配器发布去重关联数,兼容旧构造器(-1表示未知),点位计数与pointCodes同源;信封freshness不将空点台账误报无数据,设备在线率分母口径保留。 新增六个契约回归,覆盖六个未知空点、分布分页/筛选/未知桶、坐标独立统计、关联去重与回显、按对象排除对账、overview计数/关联事实。既有越界设备总数8→9、NORMAL1→2及增量预期同步。 验证:git apply --check 与应用后 git diff --check 均通过。双线审查:Standards 无硬性违规,Spec 0项可确认问题;命名建议已落实。用户在本轮明确授权编译、契约测试和现有8301只读复验(未包含服务重启)。Maven八模块 BUILD SUCCESS,Cockpit*Test/BusinessCockpit*Test 合计423项(system 2 + admin 421),Failures/Errors/Skipped均0,含新增六项回归。编译第一次因工作区外target写权限失败,同命令扩展权限重试成功。 真实复验最终通过(2026-10-12 10:05–10:08):维护者确认IDE重启后,map-snapshot与overview点位均6;distribution的EMPTY=6、total=13(6点+7未关联设备),13候选=13纳入+0排除,excluded四字段均0;六点仍renderable=false,freshness=CURRENT/READY。EMPTY、未分区与正式行业筛选边界全部正确,真实断言12/12通过。重启前旧实例曾返回overview0/distribution0,历史证据另存[重启前结果](../verify/artifacts/t28/before-reload.json),不覆盖为成功结果。完整记录见[运行验证记录](../verify/t28-validation.md)、[当前只读响应](../verify/artifacts/t28/live-readonly-verification.json)与[验收断言](../verify/artifacts/t28/acceptance-checks.json)。 隔离提交快照复验:从已推送master基线894402a建立独立分支,只应用本票补丁;2026-10-12 10:14再次八模块BUILD SUCCESS,契约测试仍423/423。提交仅七文件(242增/62删),已先推独立分支并随后快进推送到 `origin/master`;远端 `master` 与修复分支当前均指向 ba0ce076。共享后端工作树中的其他并行改动未被纳入本次快进。 资产:[补丁与验收说明](../t28-patch/README.md)、[可应用补丁](../t28-patch/t28-backend.patch)、[只读研究笔记](../research/t28-counting-contract.md)。前端现存坐标统计标签、null行业文案、pagination.total标名风险已写入说明,留阶段联调核对。 本票无待验收项,resolution comment已发布,按票面交付要求关闭;ba0ce076已合入远端master。合并后检查发现共享后端本地master由并行会话新提交八桶契约7fb7c2c,与远端master各有一个提交,且存在其在途修改;本轮未覆盖或重置该工作树,后续八桶集成需保留本票master提交。前端既有展示标签风险留阶段联调核对,八桶状态扩展归其独立执行票。 ## 来源 票 18 真实联调实测(2026-10-10 16:15)+ 用户反馈「监测点没有获取到正确的接口,后端一共有六个未关联设备的监测点」。 ## 口径决策(维护者裁定:方案 A,2026-10-10) **采用方案 A**:无关联设备的监测点是**合法监测点**,按其真实 `EMPTY`(无设备)状态**计入总数与状态分布**,不得静默丢弃。 依据:`CONTEXT.md` 的「点位状态」把「无关联设备 / 无设备」列为六个合法状态之一。既然该状态是合法点位状态,无关联设备的点位就不该从统计中消失;否则界面显示「监测点 0 个」是对真实台账的误报。 (未采用的方案 B:按点位自身计 1 记入 `excluded` 新增的「无类型点位」计数,界面仍显示 0 个。裁定为 B 会继续让合法点位不可见,不采纳。) ## 现象 后端确有 **6 个监测点**(`GS001`/`GS002`/`PS001`/`PS002`/`RQ001`/`RQ002`,均 `pointStatus=EMPTY`、`deviceCodes=[]`、无关联设备),但运行监测模式的两个端点都报 0: | 端点 | 监测点数 | | --- | --- | | `GET /map-snapshot` | **6**(正确返回) | | `GET /monitoring/overview` | **0** | | `GET /monitoring/distribution` | **0**(`pagination.total=0`,`pointStatusDistribution.EMPTY=0`) | **静默丢弃**:`excluded` 里 `unknownTypeCount=0`、`typeKindMismatchCount=0`、`invalidCoordinateCount=7`(仅 7 台未关联设备),**6 个监测点既未计入总数,也未计入任何排除计数**,完全消失。 自相矛盾之处:后端自己在 `map-snapshot` 已判定这 6 个点位为 `EMPTY`,而 `monitoring/distribution` 的 `EMPTY` 桶却是 0。 补充事实:这 6 个点位**有真实经纬度**(如 `GS001` 为 `110.399876, 28.458287`),只是缺可信坐标校核来源而 `renderable=false`。因此它们「可统计、暂不上图」是两件独立的事,不应互相阻断。 ## 根因(已定位到行) `CockpitMonitoringDistributionService` 点位循环(约 258–270 行): ```java String mismatch = classify(type); List devices = safeDevices(point.getDevices()); if (mismatch != null) { int amount = devices.size(); // ← 这 6 个点位 devices.size() = 0 if (UNKNOWN_TYPE.equals(mismatch)) { unknownTypeCount += amount; // ← 加 0,等于没记录 } ... continue; // ← 被丢弃 } ``` 这 6 个点位无关联设备,`typeCode` 不可推导而 `mismatch != null`,于是进入排除分支;**但排除计数按「设备数」累加,而它们设备数为 0**,所以既没进 marker 列表,也没进任何 excluded 桶。 对比同文件设备循环(约 326–334 行):设备走同样分支时是 `unknownTypeCount += 1`(按设备自身计 1)。**点位按设备数计、设备按自身计,两侧口径不对称**,无设备点位因此被抹掉。 连锁效应:`hasResolved = !deviceCodes.isEmpty()`(约 444 行),而 `deviceCodes` 只在坐标校验通过后收集;坐标未校验 → `freshness=EMPTY / NO_RESOLVED_DEVICES`,把「设备坐标未校验」误报成「无设备」。 适配器层(`BusinessCockpitMonitoringDistributionAdapter`)**正确**构建了这 6 个点位,丢失发生在 Service 层。 ## 范围 1. **按方案 A 落实口径**:无关联设备的点位按 `EMPTY` 计入 `monitoring/distribution` 的状态分布与 `pagination.total`;不得因设备数为 0 而丢弃。 2. 坐标未校验与「无设备」是两件事:**统计不因坐标未校验而排除**,只是不上图。坐标校验只作用于「能否上图」,不得影响「是否计入统计」。 3. 修正排除计数口径:点位按点位本身计(或显式区分「无类型点位」计数),消除点位/设备两侧不对称。 4. 修正 `hasResolved` / `NO_RESOLVED_DEVICES` 语义:区分「确实无设备」与「设备坐标未校验」。 5. `monitoring/overview` 的 `monitoringPointCount` 同源核对:修复后应与 `map-snapshot` 的监测点数一致(6)。 6. 补契约测试:无关联设备点位必须可见(计入 `EMPTY` 桶),且统计不受坐标未校验影响。 ## 验收 真实环境复验:`monitoring/overview` 的 `monitoringPointCount` = 6 且与 `map-snapshot` 一致;`monitoring/distribution` 的 `EMPTY` 桶 = 6、`pagination.total` ≥ 6;`excluded` 各计数之和与「总数 − 纳入数」可对账(6 个点位不再凭空消失);`NO_RESOLVED_DEVICES` 不再误报。编译 + `Cockpit*Test` 全绿 + 提交推送。 ## 关联 发现于票 18;与 [票 20](20-20-0-1.md)(管网编码漏计)同族——均为「未分类对象被静默丢弃而非如实计数」。坐标未校验导致的不可上图属另一问题,归 [票 24](24-24.md)。 ## Comments ### local — 2026-10-12T02:21:28.625Z Resolution:无设备未知类型监测点按 EMPTY 保留,坐标校验仅控制上图;排除计数按点位/独立设备自身计1,分布按原始关联事实去重并只回显筛后设备。overview补显式关联事实且点位数与pointCodes同源,台账freshness与设备在线率空态分离。 验收完成:本票新增六项契约回归;共享工作树和隔离提交快照均八模块BUILD SUCCESS,Cockpit*Test/BusinessCockpit*Test合计423/423通过(system2 + admin421),0失败/错误/跳过。维护者IDE重启后,真实只读复验12/12通过:map与overview点位均6;distribution EMPTY=6、total=13(6点+7未关联设备);13候选=13纳入+0排除,六点仍renderable=false,freshness=CURRENT/READY;EMPTY/UNASSIGNED/GAS边界均通过。 交付提交:ba0ce076f31086d2c024b2b4cd93a6c05105b9eb,已推 origin/fix/cockpit-empty-monitoring-points。因共享后端有八桶契约并行改动,使用独立工作树提交,仅包含本票七文件,未改动/切换其master工作树;未声称已合入master。 证据:[运行验证记录](../verify/t28-validation.md)、[12项真实断言](../verify/artifacts/t28/acceptance-checks.json)、[补丁说明](../t28-patch/README.md)。本票交付与验收全部完成,关闭;前端既有展示标签风险和八桶扩展不纳入本票。