12-backend-read-model-district.md 17 KB

12: 后端执行票 —— 报警/工单读模型、设备–监测点回联 + district 片区契约

Type: task Status: resolved Label: ready-for-agent Blocked by: 11 Category: backend-execution

来源

状态

已完成并推送(2026-10-08/09)。诊断 + 六项裁定 + 范围 1–4 实施 + 提交推送 + 真实环境复验通过。提交:ee2e1b6(主实施)+ 905f593(被撤回)+ 18db910(撤回)+ 合并 818a6d7 → 已推送 origin/master(89ca989..818a6d7)。验证:组合测试 372/372 通过。基线卡片:报警/工单读模型 + district 片区契约。

本票按其自身交付物 close(原因、证据、下一步均已入正文与基线卡片);真实环境复验记录追加到 票 17(滚动复验票)。

真实环境复验结果(2026-10-08/09,实例 PID 15244 已加载新构建)

复验项 实测 判定
六族端点健康 7/7 全部 HTTP 200(此前 5 端点 500 的故障已由上游修复) 通过
district 字段出现 mapFilter 含 district/districtName/districtFilter;region 已消失 通过
筛选生效 district=0→4、1→2、2→1、UNASSIGNED→6;合计 7+6=13 == 全量 通过
脏值 400 district=9 / -1 / 城区B 全部 400 通过
旧 region 被忽略 region=城南 → 200 且 districtFilter=NONE(不生效、不报错) 通过
管理信息删字段 顶层无 organizations/ownershipDistribution;改 districtIndustry[](7 条,含「未分区」桶) 通过
点位 district 归纳 equipment_point_rel 0 行 → 点位全部「未分区」(如实) 通过
工单显式关联标记 设备维度逐张 associatedWithCurrentEvent=true;报警维度按真实 UUID 回 READY+1 张 通过

一处测量错误与撤回(如实登记)

复验中我一度依据「alarm_data.id 全部是同一个全零占位值」提交了 905f593(改为按 alarm_no 解析)。该观测是错的 —— 它是控制台把 32 位十六进制串统一渲染成 0 的显示假象,md5(id) 也被同样渲染。

用字符直方图与 base64 复核后确认:6 行 id 是互不相同的真实 UUID(非零字符数 30/29/32/29/32/30,count(DISTINCT id)=6;base64 解出 fca8fd6b…、8b5c81fc…,与端点回显一致)。实测 events/ALARM/{真UUID}/work-orders 回 READY+1 张工单,证明原按 id 解析本就是对的。

故以 18db910 撤回该改动:findAlarmByReference 恢复先按主键 getById、查不到时才回退 alarm_no;alarmReference 恢复优先回 id。保留 905f593 中仍正确的部分(lambdaQuery() 链改 list(wrapper) 便于测试 + 新增适配器测试)。

教训:控制台对长十六进制串的渲染不可信;判定「数据是否相同/是否为零」必须用 count(DISTINCT)、字符直方图、base64 等不可被渲染扭曲的方式,不能凭肉眼读控制台。

全量测试中的一个既有失败(非本票引入)

zksy-system 全量跑有 5 个失败,全部在报警/供水域、与本票无关: WaterSupplyDeviceServiceImplTest(2)、AlarmClearServiceTest(1)、AlarmReviewAttachmentServiceTest(2)。 根因是上游 7df9a87 把 setAbnormalReason(alarm.getWarningType()) 硬编码为 "设备报警"(删除了 warningType 用法),但测试仍断言旧值 "供水压力超限"。已核实在基点 89ca989 上实现与测试断言本就冲突,且本票 0 处改动这些文件。该失败需另开票修(不在本票范围)。

实施结果(2026-10-08)

按维护者六项裁定实施,共 48 个文件改动 + 1 个新增(cockpit/foundation/CockpitDistrict.java 片区口径单源)。六个响应族统一改造:

族 改动
地图摘要 map-snapshot?district=0\|1\|2\|UNASSIGNED;点位/设备 district+districtName;mapFilter 加 districtFilter
运行监测 monitoring/distribution、monitoring/situation 的 regionDistribution→districtDistribution,marker 双字段
基础设施 base-archives 同上;管理信息删字段 organizations/ownershipDistribution/unmaintainedCount/organizationCount,改 districtIndustry[](片区×行业)
事件多维分析 维度 REGION/区域→DISTRICT/片区,桶码 0/1/2/UNASSIGNED
工单读模型 每张工单新增显式 associatedWithCurrentEvent + 顶层 unassociatedWorkOrderCount

点位与设施无片区来源(monitor_point 无列、PipeNetworkBase 无字段、equipment_point_rel 实测 0 行)→ 点位按「经其上挂设备归纳」、设施一律未分区,如实呈现,未加数据库列、未用行业/管理单位冒充。

维护者裁定(记录)

  1. district 来源=设备级权威 + 点位经其上挂设备归纳;不改 DDL。
  2. 形态=编码 + 中文名双字段;入参 0/1/2/UNASSIGNED,脏值 400。
  3. district 取代 region,region 入参不再接受。
  4. 管理信息字段本票直接删。
  5. 多工单:保留全部明细,不声明规则缺失。
  6. 未关联标注:关联键以数据库事实为准 —— alarm_data.work_order_id → work_order.order_id(维护者原述 alarm_no = order_no 经实测命中 0 条且两列均不存在,已裁定采用 work_order_id)。

诊断(2026-10-08)

后端基线(实施前)

项 值
仓库 / 分支 / 提交 http://192.168.110.30:3000/shaoy/pipe-ner-server / master / 89ca989
远端 origin/master = 89ca989(与本地一致,工作树干净)
联调环境 http://192.168.110.96:8301 实测可达,只读接口免鉴权 HTTP 200

范围 1 —— 报警/工单读模型与设备–监测点回联:已存在实现,但有缺口

已有端点与实现(cockpit/workorder/):

端点 实测
GET /equipments/{deviceCode}/work-orders 真实设备 21119740 回 2 张工单(状态 1 待接单、5 已结案),state=READY
GET /events/{source}/{code}/work-orders 可用;ALARM 走 alarm_data.work_order_id,WARNING 走 alarm_warning_rel → alarm_data
GET /work-orders/{orderNo}/timeline 可用,状态码经 CockpitWorkOrderStateDictionary 0–7 解析

缺口(对照本票「范围 1」逐句):

  1. 「未关联当前报警的工单明确提示」未满足:CockpitWorkOrderSummary.eventAssociation 在无关联时为 null(隐式)。实测两张工单均 eventAssociation=null,但响应没有任何显式字段说明「该工单与当前报警无关」,前端只能靠 null 猜。
  2. 设备–监测点回联在真实库上不可证明:app_user.equipment_point_rel 实测 0 行;monitor_point(6 行)没有 district 列。故「按后端现有关系输出可证明的逐条关系」在当前数据下只能如实输出「无关系 + 原因」,不能编造。
  3. 多工单「当前/有效/最新」规则:后端既有实现是全部返回 + 按 create_time 倒序,未定义「当前工单」规则。按决策 01 应「保留明细并显示规则缺失,不由前端猜选」——当前响应未声明这一点。

范围 2 —— district 片区契约:完全未接入(cockpit 代码零命中 district)

事实 证据
权威来源已存在 EquipmentBase.district(Integer,注释「0-城南 1-城北 2-太常,见 DistrictEnum」);DistrictEnum(base/enums/)含 SOUTH_CITY(0,"城南")/NORTH_CITY(1,"城北")/TAI_CHANG(2,"太常") 与 getByCode/getDescByCode/isValid
真实数据齐备 equipment_base 7 行,district 全部非空:0→4、1→2、2→1
cockpit 代码零使用 district 对 cockpit/** 全量 grep district 0 命中;现有实现只用旧自由串 region
region 实际恒为 null BusinessCockpitMapSnapshotAdapter 第 234/251 行硬编码传 null,故服务层 regionOf() 一律归一为 未分区。实测地图首 3 个点位 region=未分区
设施与点位没有 district PipeNetworkBase(管网设施)无 district 字段;monitor_point 无 district 列。只有设备(EquipmentBase)有
district 入参未被接受 实测 map-snapshot?district=0 被忽略(mapFilter 无该字段,入参不在控制器签名里);region=城南 被接受(自由文本、无校验、无白名单)

涉及 region/regionDistribution 的响应族实测(4 族):

端点 region 类字段 现状
/map-snapshot mapFilter.region、点位/设备 region 入参可为自由串;值恒 未分区
/monitoring/distribution regionDistribution[]、marker region 有;适配器注释明写「无可靠来源 → null → 未分区」
/monitoring/situation 对象内 region 有(同 null 归一)
/infrastructure/base-archives regionDistribution[]、selectedFilters.region、mapFilter.region 有(设施无 district 来源)
/events/multidimensional-analysis dimensions 内 REGION 桶 有(UNASSIGNED_REGION/未分区 双常量)

范围 3 —— 管理信息响应字段删除:未删除,且前端仍在消费

CockpitInfrastructureManagementInformationResponse 仍含 organizations、ownershipDistribution、Totals.unmaintainedCount、Organization.unmaintained/ownershipDistribution;实测 management-information 顶层字段含 organizations、ownershipDistribution。

耦合风险(实施时序需要裁定):前端 src/modules/cockpit/domain/infrastructurePanels.js 第 213–232 行正在读 organizations、ownershipDistribution、totals.unmaintainedCount,并用于面板渲染(第 433、441–446 行)。按决策 06「管理信息面板改片区×行业视图、后端删这三个字段」,后端先删会让前端面板读到空,需与前端票 15(管理面板片区化)对齐落地时序。

判定

本票不能直接按字面实施:范围 2 的「统一按 district 过滤与回显」在点位与设施缺 district 来源的现实下无法字面达成(只有设备有);范围 3 的删除与前端存在时序耦合;范围 1 有 3 处语义缺口。故先请维护者裁定,不猜。

维护者裁定(2026-10-08)

  1. district 来源:设备级权威(equipment_base.district)+ 点位经其上挂设备归纳;多设备不一致/无设备 → 未分区 并计数。不改 DDL、不给点位/设施加列(另开迁移票)。
  2. 形态:编码 + 中文名双字段 —— 回 district(Integer,null=未分区)+ districtName(未分区时 "未分区");筛选入参 district=0|1|2,不传=全部,UNASSIGNED=只看未分区。符合决策 06「统计保留数字、展示层映射中文」。
  3. region 去留:district 取代 region,region 入参不再接受;响应中 region/regionDistribution 改为 district/districtDistribution。
  4. 管理信息删字段:本票直接删 organizations / ownershipDistribution / unmaintained(含 Totals.unmaintainedCount);需回写前端票 15 同步改面板。
  5. 多工单:保留全部明细即可,不需要声明规则缺失(维护者明确不要「规则缺失」声明)。
  6. 未关联标注:关联键以数据库事实为准 —— alarm_data.work_order_id → work_order.order_id。维护者原述 alarm_data.alarm_no = work_order.order_no 经实测命中 0 条(work_order 无 alarm_no 列、alarm_data 无 order_no 列,order_no 为建单时独立生成),维护者已裁定采用 work_order_id。据此实现每张工单的显式关联标记。

待裁定问题(已全部裁定,见上)

  1. district 的来源边界:只有 equipment_base 有 district;monitor_point 无该列、PipeNetworkBase 无该字段、equipment_point_rel 0 行。点位与设施的片区如何取?
  2. district 的参数与回显形态:入参/回显用编码(0/1/2 + 「未分区」哨兵)还是中文(城南/城北/太常)?
  3. 旧 region 入参的去留:直接以 district 取代(region 不再接受),还是并存过渡?
  4. 管理信息字段删除的时序:后端立即删(前端票 15 落地前面板会空),还是等 15 一并落地?
  5. work_order 多张工单的「当前工单」规则:后端无此规则,是否按决策 01「保留明细并声明规则缺失」实现(不引入新规则)?
  6. 设备–监测点回联在 0 关系数据下的输出:如实回「无关系 + 原因」,还是本票只交付契约与测试、真实关系待数据补齐?

范围(做什么)

  1. 报警/工单读模型与设备–监测点回联:按后端现有报警、工单、设备和监测点关系输出可证明的逐条关系;设备详情展示该设备已有全部工单(未关联当前报警的工单明确提示);报警详情只展示与该报警实际关联的工单;多工单遵循后端既有规则或如实说明规则缺失。
  2. district 片区契约:
    • 相关响应族(地图摘要、运行监测、基础设施、事件/预警分析)统一按 district(0/1/2 → 城南/城北/太常)过滤与回显;空值/乱码/未识别 → 未分区。
    • 旧自由串 region 不再作为正式范围契约(regionMap 过渡失效)。
    • 无独立「责任区域」字段;不用管理单位、权属单位、行业或其它字段冒充(责任区域=片区别名)。
  3. 管理信息响应字段删除:删除 organizations / ownershipDistribution / unmaintained(按 #06 决策)。
  4. 契约测试与文档:回联逐条关系、district 枚举与未分区归一、删除字段后的响应形状。

非目标

  • 不修改工单操作逻辑;不新增工单写接口。
  • 不实现实时推送帧(归执行票 13)。

验收

代码提交 + 编译 + 针对性契约测试 + 联调基线登记;本地联调通过后提交推送 Gogs。

项 状态 证据
编译 通过 mvn -o -pl pipe-network-service/zksy-admin -am compile
组合契约测试 通过 -Dtest=Cockpit*Test,Business*AdapterTest → 372/372
联调基线登记 已产出 基线卡片 03
代码提交 + 推送 已完成 818a6d7 → origin/master(89ca989..818a6d7)
真实环境复验 通过 见「状态」节复验表(8 项全通过)

下一步(本票已完成;以下为遗留项与下游)

  1. 六项裁定 / 实施 / 契约测试 / 基线卡片 / 提交推送 / 真实环境复验 —— 全部完成。
  2. 前端消费侧同步(未完成,阻塞前端票):已通知 执行票 15 —— regionMap 配置映射失效、管理信息三字段消失、NO_ORGANIZATIONS 改名 NO_RELATIONS。
  3. 两个 demo html 仍是 region:'城区A' fixtures(离线样例,不阻塞契约,低优先)。
  4. 下游 执行票 13:本票已 close,其阻塞解除,可开工。
  5. 既有测试失败需另开票 —— 已直接修复(2026-10-09,维护者裁定:过期断言改当前契约、无法表达现行契约的测试类删除)。提交 4ef2159 已推 origin/master;全量 8 模块 414/414 通过。明细:WaterSupplyDeviceServiceImplTest 两处断言更新为「设备报警」、AlarmClearServiceTest 摘要断言更新为「监测报警」、AlarmReviewAttachmentServiceTest 整类删除(其断言的内容嗅探/伪造扩展名拦截为生产从未实现的策略,且测试环境必然 NPE)。
  6. 票 13 落地部署后,其复验项追加到 票 17 的复验进度表。

关联