Browse Source

docs: 恢复票22为已修复待验证,记录我的版本误判错误;裁定无设备点位计入统计

Kazerin 1 week ago
parent
commit
dec55bebc7

+ 53 - 0
.scratch/cockpit-real-data-wayfinding/issues/20-20-0-1.md

@@ -23,6 +23,59 @@ Category: backend-execution
 2. **设施类型命名空间误用**:设备边界只用 `equipment_type`(监测设备类型)建字典,却用同一编码去解释设施 `network_type`,导致真实 3735 条排水设施被标为「供水设备 / WATER_SUPPLY」、996 条供水设施被标为「排水设备 / DRAINAGE」。
 3. **概览强制置空**:`overview/infrastructure` 把所有管网设施行业置 `null`,正式行业统计只得到 `8`(水厂 3 + 供水泵站 5),`excluded.unknownTypeCount=5268`。
 
+## 诊断(2026-10-10)
+
+诊断手段:只读源码核实 + 真实库只读探针(凭据走环境变量、探针放 `%TEMP%` 用完即删)。
+
+### 后端基线
+
+| 项 | 值 |
+| --- | --- |
+| 仓库 / 分支 / 提交 | `origin/master` / `master` / `d8a6b03`(票 13 收口后的基线) |
+| 联调环境 | `http://192.168.110.96:8301` 可达 |
+
+### 数据事实(真实库实测)
+
+| 项 | 实测值 |
+| --- | --- |
+| `pipe_network_base.network_type` 分布 | `0`=537、`1`=3735、`2`=996,**总数 5268**(与票面声称一致) |
+| 权威编码规则 | `GisController.getPipeLines` L43 注释与分支:**`0`=燃气、`1`=排水、`2`=供水**(另兼容 `gas/drainage/water` 等历史串) |
+| `equipment_type` 主键命名空间 | id=`1`→**供水设备**、id=`2`→**排水设备**、id=`3`→燃气设备(parent 均为 0) |
+| `network_type="3"` 实际存在? | **不存在**(分布仅 `0/1/2`);且生产代码无任何 `setNetworkType` 写入路径 |
+
+### 现象逐条核实(票面三个现象均成立)
+
+**现象 1 — 管网数字编码漏计(4272 条丢失)**:三处**各自复制**的映射函数都只认 `2`/`3` 与中文串,遇到 `0`/`1` 返回 `null`,调用方随即 `continue` 静默丢弃:
+
+| 位置 | 函数 | 缺 `0`/`1` 的后果 |
+| --- | --- | --- |
+| `BusinessCockpitInfrastructureBaseArchivesAdapter.pipeType` L149 | 返回 `WATER_PIPE`/`GAS_PIPE`/`DRAINAGE_PIPE` | `addPipes` L123 `typeCode==null` → `continue`,5268 条只剩 996 条进入响应 |
+| `BusinessCockpitInfrastructureManagementInformationAdapter.pipeIndustry` L154 | 返回行业码 | 4272 条 `industry=null` |
+| `BusinessCockpitInfrastructureOperatingInformationAdapter.pipeIndustry` L191 | 返回行业码 | 4272 条 `industry=null` |
+
+三份逻辑重复且同源同错——这正是「单一权威来源」要消除的对象。
+
+**现象 2 — 命名空间碰撞(类型名与行业系统性错位)**:`BusinessCockpitDeviceBoundaryAdapter` L85 把**原始 `network_type`** 当作设施类型码上报,服务层 `mapFacility` L78 用 `types.get(...)` 去查 **`equipment_type` 字典**。两套编码数值域重叠而语义不同,于是:
+
+| `network_type` | 真实含义 | 误查 `equipment_type` | 对外结果 |
+| --- | --- | --- | --- |
+| `1` | 排水(**3735 条**) | id=1 = 供水设备 | 被标「供水设备 / WATER_SUPPLY」 |
+| `2` | 供水(**996 条**) | id=2 = 排水设备 | 被标「排水设备 / DRAINAGE」 |
+| `0` | 燃气(537 条) | 无 id=0 | 类型解析失败 → `UNKNOWN_TYPE` |
+
+适配器类注释 L29-30 声称「管网设施 `network_type` 是自由文本、与 `equipment_type.id` 无对应关系」——**该前提为假**:它不是自由文本,而是有权威编码的字段;「无对应关系」被误实现为「透传原始码」,结果不是隔离而是碰撞。
+
+**现象 3 — 概览强制置空**:`BusinessCockpitOverviewInfrastructureAdapter` L123 构造设施 `SourceType(code, name, industry, drainageScope, objectKind)` 时传 `industry=null, drainageScope=null`,5268 条管网全部落入非正式范围,正式行业统计只剩水厂 3 + 泵站 5。
+
+### 修复方向(单一权威来源)
+
+新增单源持有**管网命名空间**的类型码 / 中文名 / 行业,四处适配器共用;设备边界的设施类型改用该命名空间(不再借用 `equipment_type` 主键),未识别值仍显式 `null` 计入 `UNKNOWN_TYPE`,不静默丢弃。
+
+## 待裁定问题
+
+1. **设备边界的设施类型身份如何表示**(命名空间如何隔离)。
+2. **旧映射 `"3"`→燃气 如何处置**(真实库无此值,且它与 `equipment_type.id=3=燃气设备` 高度疑似同源混淆产物)。
+
 ## 范围
 
 1. 建立管网设施类型与行业的**单一权威来源**,与既有 GIS 规则一致,覆盖 `0/1/2` 与历史字符串。

+ 4 - 2
.scratch/cockpit-real-data-wayfinding/issues/23-23-token-pointstatus.md

@@ -8,7 +8,8 @@
 开始处理:已完整读取正文、评论、标签与依赖。当前 ready-for-agent,未认领、无阻塞,无 needs-triage 前置闸门。先核对分页 token 与真实后端八桶支持现状,区分前端缺陷和后端契约缺口。
 
 Type: task
-Status: ready-for-agent
+Status: claimed
+Assignee: codex
 Label: ready-for-agent
 Category: frontend-execution
 
@@ -40,7 +41,8 @@ Category: frontend-execution
 
 <!-- 做到哪一步了,在这里一行一行记 -->
 
-Status: ready-for-agent
+Status: claimed
+Assignee: codex
 Type: task
 Blocked by:
 Labels: ready-for-agent, frontend-execution, wayfinder:task

+ 25 - 26
.scratch/cockpit-real-data-wayfinding/issues/28-28.md

@@ -1,5 +1,3 @@
-<!-- DSH-IDEMPOTENCY-KEY: t18-defect-monitoring-point-drop -->
-<!-- parentKey: 00 -->
 # 28: 后端执行票 —— 无关联设备监测点被静默丢弃(计数不对称)
 
 Type: task
@@ -11,6 +9,14 @@ Category: backend-execution
 
 票 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:
@@ -21,7 +27,11 @@ Category: backend-execution
 | `GET /monitoring/overview` | **0** |
 | `GET /monitoring/distribution` | **0**(`pagination.total=0`,`pointStatusDistribution.EMPTY=0`) |
 
-更严重的是**静默丢弃**:`excluded` 里 `unknownTypeCount=0`、`typeKindMismatchCount=0`、`invalidCoordinateCount=7`(仅 7 台未关联设备),**6 个监测点既未计入总数,也未计入任何排除计数**,完全消失。
+**静默丢弃**:`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`。因此它们「可统计、暂不上图」是两件独立的事,不应互相阻断。
 
 ## 根因(已定位到行)
 
@@ -41,36 +51,25 @@ if (mismatch != null) {
 
 这 6 个点位无关联设备,`typeCode` 不可推导而 `mismatch != null`,于是进入排除分支;**但排除计数按「设备数」累加,而它们设备数为 0**,所以既没进 marker 列表,也没进任何 excluded 桶。
 
-对比同文件设备循环(约 326–334 行):设备走同样的 `mismatch` 分支时是 `unknownTypeCount += 1`(按设备本身计 1)。**点位按设备数计、设备按自身计,两侧口径不对称**,无设备点位因此被抹掉。
+对比同文件设备循环(约 326–334 行):设备走同样分支时是 `unknownTypeCount += 1`(按设备自身计 1)。**点位按设备数计、设备按自身计,两侧口径不对称**,无设备点位因此被抹掉。
 
-连锁效应:`hasResolved = !deviceCodes.isEmpty()`(约 444 行),而 `deviceCodes` 只在坐标校验通过后收集;坐标全部未校验 → `deviceCodes` 空 → `freshness=EMPTY / NO_RESOLVED_DEVICES`,即使数据本身并非「无设备」而是「设备坐标未校验」。
+连锁效应:`hasResolved = !deviceCodes.isEmpty()`(约 444 行),而 `deviceCodes` 只在坐标校验通过后收集;坐标未校验 → `freshness=EMPTY / NO_RESOLVED_DEVICES`,把「设备坐标未校验」误报成「无设备」。
+
+适配器层(`BusinessCockpitMonitoringDistributionAdapter`)**正确**构建了这 6 个点位,丢失发生在 Service 层。
 
 ## 范围
 
-1. 点位排除计数改为**按点位本身计 1**(或新增明确的「无类型点位」计数),使无关联设备的点位不再静默消失。
-2. 无关联设备的点位按其真实 `pointStatus=EMPTY` 计入状态分布(`EMPTY` 桶当前为 0,与后端自身在 `map-snapshot` 的判定矛盾)。
-3. 修正 `hasResolved` / `NO_RESOLVED_DEVICES` 语义:区分「确实无设备」与「设备坐标未校验」两种不同原因。
-4. `monitoring/overview` 的 `monitoringPointCount` 同源核对:为何 6 个真实监测点计为 0。
-5. 补契约测试锁定「无关联设备点位必须可见(计入 EMPTY 或明确排除)」,防止再次静默丢弃。
+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` 与 `map-snapshot` 的监测点数一致;`monitoring/distribution` 的 `EMPTY` 桶等于无关联设备点位数;`excluded` 各计数之和与「总数 − 纳入数」可对账。编译 + `Cockpit*Test` 全绿 + 提交推送。
+真实环境复验:`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)。
-
-## 进度
-
-<!-- 做到哪一步了,在这里一行一行记 -->
-
-Status: ready-for-agent
-Type: task
-Blocked by:
-Labels: ready-for-agent, backend-execution, wayfinder:task
-
-## Comments
-
-
-## Answer
+发现于票 18;与 [票 20](20-20-0-1.md)(管网编码漏计)同族——均为「未分类对象被静默丢弃而非如实计数」。坐标未校验导致的不可上图属另一问题,归 [票 24](24-24.md)。

+ 1 - 0
.scratch/cockpit-real-data-wayfinding/map.md

@@ -61,6 +61,7 @@ Labels: wayfinder:map
 - [12: 后端执行票 —— 报警/工单读模型、设备–监测点回联 + district 片区契约](issues/12-backend-read-model-district.md) — 旧自由串 `region`(实测恒为「未分区」、适配器写死 null)被**片区 `district`** 取代:权威来源=`equipment_base.district`(0城南/1城北/2太常),**点位按「经其上挂设备归纳」**(不一致/无设备→未分区)、设施无来源一律未分区——**不加 DDL、不用行业或管理单位冒充**;响应形态=`district`(Integer,未分区 null)+`districtName`,筛选入参 `district=0|1|2|UNASSIGNED`(**脏值 400**),`mapFilter` 增 `districtFilter`(`NONE`/`ASSIGNED`/`UNASSIGNED`,区分「未筛」与「只看未分区」这两种编码同为 null 的情形);口径单源 `cockpit/foundation/CockpitDistrict.java`。**六族统一改造**(地图摘要、运行监测 distribution+situation、基础设施 base-archives、事件多维分析);**管理信息按 #06 删字段** `organizations`/`ownershipDistribution`/`unmaintainedCount` 改 `districtIndustry[]`(片区×行业)。**工单读模型**:设备维度返回已有全部工单并逐张加**显式** `associatedWithCurrentEvent` + 顶层 `unassociatedWorkOrderCount`(满足「未关联当前报警明确提示」);关联键经真实库核实为 `alarm_data.work_order_id = work_order.order_id`(维护者原述 `alarm_no = order_no` 实测命中 0 条、两列均不存在)。提交 `818a6d7` 已推 `origin/master`,组合测试 **372/372**,**部署后真实环境复验 8 项全通过**(基线卡片 03 至 ③)。**教训**:复验中曾因控制台把 32 位十六进制串统一渲染成 `0` 而误判 `alarm_data.id` 为占位值并提交改动,经 `count(DISTINCT)`/直方图/base64 复核后撤回(`18db910`)——判「相同/为零」不可凭肉眼读控制台。**前端消费侧必须同步**(已通知 [票 15](issues/15-frontend-time-dimension-panel.md))。
 - [13: 后端执行票 —— 报警/工单实时同步](issues/13-backend-realtime-sync.md) — 工单状态增量**从无到有**:新增 `cockpitWorkOrderStateDelta` 帧(`CockpitWorkOrderDeltaPushTask`,定时扫描 `work_order.order_status` **逐单比对**——状态流转不写 `update_time`,靠时间过滤会漏掉全部流转),**变化才推**给驾驶舱全量订阅会话(免鉴权、不按人过滤,与管理端 `workOrderReminderPush` 提醒语义隔离);**首扫只建基线不推送**(REST 才是初始快照);帧带 `scanIntervalMs`(默认 60s 可配)诚实标注延迟上限、不带会误导的 `updateTime`、**不泄漏主键 orderId**(对外键 `workOrderCode`,与 REST 读模型同口径);`stateCode/stateName` 出自票 11 字典单源,脏码名称显式 null。设备帧 `cockpitDeviceStatusDelta` 补 `eventId` 透传(缺省显式 null;zk-api-service 是否真发未证实,防御性透传不臆造)。REST 快照复用票 11/12 读模型**无新端点**;降级纪律(无订阅不查库、失效会话清理、断线重订)有测试锁定。提交 `d8a6b03` 已推 `origin/master`,针对性 **21/21** + 全量 **422/422**,**部署后复验 6 项全通过**——含端到端正面证据(真人提交验收 284 工单 3→4,**40 秒内帧到达**、字段全对)与两项负向证据(无变化不推送;只改 `update_time` 不改状态码的「处置」不触发,证明检测由 `orderStatus` 驱动)。前端消费归 [票 16](issues/16-frontend-alarm-order-consumption.md)(对未知帧静默忽略,不升级不会崩)。
 - [17: 部署后真实环境复验(后端切片 11–13,滚动复验票)](issues/17-postdeploy-live-verification.md) — 按维护者裁定作为**贯穿 11–13 的滚动复验票**单独承载部署后证据:三切片**全部复验通过**(11:处置率新口径 `num/den=2/3` 自洽;12:district 契约 8 项;13:增量帧 6 项含真人触发端到端),三张基线卡片 02/03/04 均升至 **③ 真实数据联调完成**;全量测试自基线 299 增至 **422/422**。滚动使命完成故收口;决策 01 的真人闭环演示归阶段总收口(票 18 上位阶段),上位回写(票 25/Gogs #63)已按决策 01 执行。**复验方法论沉淀**:负向观察必须闭环(说「没收到帧」须同时复查库证明「确实没变化」);临时探针一律放 `%TEMP%` 用完即删(不进 git 跟踪目录);控制台渲染不可作为「值是否相同」的证据。
+- [28: 后端执行票 —— 无关联设备监测点被静默丢弃(计数不对称)](issues/28-28.md) — 维护者裁定**方案 A**(2026-10-10):无关联设备的监测点是**合法监测点**,按其真实「无关联设备 / EMPTY」状态**计入监测点总数与状态分布**(`EMPTY` 桶),不得静默丢弃;方案 B(仅记入 `excluded` 新计数、界面仍显示 0)不采纳。配套澄清两条术语:**无设备点位**是合法点位,类型/片区无法经设备推导时如实显示未分区/未知,不伪造归属;**坐标能否上图只决定呈现,不决定是否计入统计**——坐标未校验的对象仍参与计数、分布与筛选。根因为 `CockpitMonitoringDistributionService` 点位循环按 `devices.size()` 累加排除计数,无设备点位计 0 而被抹除(设备侧同分支按自身计 1,口径不对称);连带 `NO_RESOLVED_DEVICES` 把「设备坐标未校验」误报为「无设备」。`CONTEXT.md` 的「点位状态」「无地图位置」两条已就地澄清。
 
 ## Not yet specified
 

+ 8 - 4
CONTEXT.md

@@ -33,8 +33,12 @@ _Avoid_: 监测点、把设施点并入监测点统计、把聚合 KPI 当设施
 _Avoid_: 设备点位(指监测设备本身时)、把该图层称为新的监测点
 
 **点位状态**:
-由后端解析并发布的监测点业务状态,包括正常、预警、报警、处置中工单、离线和无关联设备;前端只渲染,不重算。
-_Avoid_: 前端聚合状态、按设备记录自行推导
+由后端解析并发布的监测点业务状态,包括正常、预警、报警、处置中工单、离线和无关联设备;前端只渲染,不重算。无关联设备的监测点仍是**合法监测点**,按其「无关联设备」状态计入监测点总数与状态分布——它只是当前没有挂接设备,不是无效对象。
+_Avoid_: 前端聚合状态、按设备记录自行推导、把无设备点位从监测点统计中剔除
+
+**无设备点位**:
+没有任何关联监测设备的监测点。它是点位状态「无关联设备」的具体对象,计入监测点总数与状态分布,并保留在台账与详情中;类型与片区无法经设备推导时如实显示为未分区/未知,不伪造归属。**注意与「未关联设备」方向相反**:无设备点位说的是「这个点位下面没有设备」,未关联设备说的是「这台设备没有挂到任何点位下」——两者是同一个「关系为空」的两侧视角,不是同一类对象。
+_Avoid_: 把它当作无效点位或测试残留、按设备数为零就丢弃它、把「无设备点位」与「未关联设备」当成同一类对象、用「无设备」掩盖「设备坐标未校验」这类不同原因
 
 **未关联设备**:
 未挂接到任何监测点下的监测设备;它以独立对象上地图并参与与监测点相同的全局筛选。它有**自身的设备状态**(报警/预警/工单处置/正常/离线/状态未发布),状态筛选对未关联设备按其设备状态匹配,不借用监测点聚合状态词表。
@@ -85,8 +89,8 @@ _Avoid_: 把它当作与片区并存的第二套范围维度
 _Avoid_: 井盖(旧显示词,作废)、把它当作独立于 MANHOLE 的新行业
 
 **无地图位置**:
-对象因坐标缺失、非法、未通过校验或超出合法范围而不上图的状态;对象仍保留在列表和详情中,并显示明确原因。
-_Avoid_: 默认坐标点、静默隐藏
+对象因坐标缺失、非法、未通过校验或超出合法范围而不上图的状态;对象仍保留在列表和详情中,并显示明确原因。坐标能否上图只决定**呈现**,不决定该对象**是否计入统计**——坐标未校验的对象仍参与计数、分布与筛选。
+_Avoid_: 默认坐标点、静默隐藏、因坐标未校验就把对象从统计中剔除
 
 **两级详情**:
 从地图监测点或监测点台账卡片进入:先查看点位摘要和设备卡片,再选择设备卡片后加载完整设备详情。