74-warning-efficiency-backend-contract.md 16 KB

后端执行票 —— 预警效率端点缺预警自身处置流程字段(两族回同一套工单口径)

Status: resolved Assignees: codex Type: task Blocked by: None Labels: wayfinder:task, ready-for-agent Category: backend-contract

来源

票 65(前端执行票 —— 预警效率面板按预警/报警分流)收口时记录的契约缺口。 票 65 的验收第 5 条明写:若需要后端新增预警流程字段,在票面记录契约缺口并另开后端票,本票不假装已有数据。 本票就是那张后端票。

缺口(2026-10-21 实测,192.168.110.96:8301 只读)

GET /api/cockpit/v1/events/handling-efficiency?eventFamily=WARNING 与 =ALARM 的响应结构完全相同, 两族都只给工单口径:

字段 语义 预警族实测
summary.totalEvents 窗口内事件条数 5
summary.linkedWorkOrderCount 回联到工单的事件条数 0
summary.missingWorkOrderCount 未回联工单的事件条数 5(=全部)
summary.handledCount / inProgressCount / cancelledCount 工单状态派生(不是预警生命周期) 0 / 1 / 0
summary.handlingEfficiency 工单口径处置率(CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS) num=0 den=1(分母来自 1 张与预警无关的待派单工单)
summary.averageDuration 关联工单结案时长 EMPTY / NO_CLOSED_WORK_ORDERS
workOrderStateCounts 窗口内全部工单八态(scopeWorkOrders,不要求关联事件) 只有 1 张待派单

代码证据(CockpitHandlingEfficiencyService.build(),t30 validation-checkout 同版):

  • stateCounts 由 scopeWorkOrders(...) 累加 —— 范围内全部工单,注释原文「不要求关联报警/事件」;
  • 于是 handledCount / inProgressCount / cancelledCount / handlingEfficiency / workOrderStateCounts 全都是工单读数,两族共用同一份(stateCounts 只算一次,family 只影响 facts 也就是事件集合);
  • 预警自身的生命周期 WarningWorkflow(草稿/待接收/已接收/处置中/已处置/已解除)不在此端点, 只存在于另一个端点 /overview/warning-settings,且那里的分布是全量快照、不随 range 收缩, 该端点还不接受 LAST_90_DAYS(400)。

为什么必须补

预警不挂工单(实测 linkedWorkOrderCount=0、missingWorkOrderCount=总数)。前端若照字面消费 summary 三字段,就会画出「预警处置中 = 1」—— 那个 1 来自一张与预警毫无关系的工单, 正是票 65 票面禁止的「用一个不存在的对象统计预警」。票 65 因此只在预警模式下画 可证实的读数(条数/等级/发生趋势/工单关联实况),前端现在是如实的空缺口,不是已解决的问题。

建议契约(形状供后端评审,不以本票为准)

GET /events/handling-efficiency?eventFamily=WARNING 的 summary 增预警自身流程段, 与 overview/warning-settings 的预警族语义同源(同 WarningWorkflow 六态字典,不另造一套):

  • warningStateCounts: [{ stateCode, stateName, count }] —— 窗口内预警的六态全铺(含 0 桶,脏值进 UNKNOWN);
  • warningHandlingEfficiency: { numerator, denominator, value, state, reason, basis } —— 已解除 ÷ 窗口内预警总数(分母 0 → state=EMPTY + reason=NO_WARNINGS,与票 42 同一纪律);
  • warningDuration: { value, unit, sampleCount, state, reason } —— 预警从发布到已解除的平均时长 (无样本 → state=EMPTY + 明确 reason,不得回 0 时冒充真实读数);
  • warningStatusTrend: [{ timeBucket, total, closed }] —— 窗口内按桶的发布量/已解除量。

两族语义分流要求:eventFamily=WARNING 时 handlingEfficiency/workOrderStateCounts/ averageDuration 三个工单口径字段应显式为 null(而不是照旧回工单读数)—— 与票 41 在 overview/warning-settings 已做的裁定完全同一条纪律(预警族工单口径 null 保真, 不用 0 或别的族的数冒充)。报警族保持现状(工单是报警的处置手段,票 41/42/52 既有裁定)。

验收

  1. 契约测试覆盖两族:预警族工单口径三字段为 null、预警流程段齐备且 sum(六态)==窗口内预警数; 报警族工单口径原样保留。
  2. 真实环境实测 eventFamily=WARNING:linkedWorkOrderCount=0 时预警流程段仍给出真实六态分布 (不是全 0 假读数)。
  3. 分母 0 / 无样本的回 state=EMPTY + 原因码,不回 "0.0000" 当真实值。
  4. 全量 Cockpit*Test 通过;改动推 origin/master 并在票面记录提交哈希。
  5. 前端消费侧另立前端票(票 65 的预警分支已预留口径:只要新字段到位,替换 warningEfficiencyKpis 的 三个计数即可,块结构不用重排)。

关联

  • 上游:票 65 —— 预警效率面板按预警/报警分流(前端已按「诚实分流 + 记缺口」收口)。
  • 同族既有裁定:票 41(预警族改预警自身生命周期、工单口径 null 保真)、 票 42(前端消费两族分叉)、票 52(报警的单设备工单优先级)、 票 11(处置率=状态 5 ÷ 状态 0–6 工单数)。
  • 参考实现:CockpitOverviewWarningSettingsService.familyMetrics() 已有预警族六态/已解除率的 现成聚合逻辑,本票是把它搬进带窗口的 efficiency 端点并分族,不是从零写。
  • 后端仓库/负责人/权限按本图 Notes:需在仓库、负责人和权限明确后由维护者启动。

进度

  • 2026-10-21 由票 65 收口时另立(前端侧已完成诚实分流并记录缺口;本票未动工)。
  • 2026-10-23 开工(本会话)。认领;基点=后端 origin/master f36f10dd(本地 master 与远端一致)。 按本图既有成例(票 29/30)在工作区内建隔离副本 .scratch/cockpit-real-data-wayfinding/implementation/t74/validation-checkout/(含 .git,reset --hard origin/master 得干净树;宿主沙箱不允许写工作区外路径,故不用 git worktree add E:\...,共享工作树的并行在制品也未触碰)。

方案(落地口径)

读路径:BusinessCockpitEventAdapter.loadHandlingEfficiency() 的 SourceWarning 原先只带编号/等级/发布时间/类型/设备, 预警自身流程在快照里整条缺失。新增 status(WarningWorkflow 六态)与 updateTime(解除时刻来源)两个字段, 旧 6 参构造器保留为委托重载(票 41 成例),由适配器从 EarlyWarning.getStatus() / getUpdateTime() 填。

响应形状(按票面建议契约,落点对称于既有工单口径字段):

位置 报警族 预警族
workOrderStateCounts(8 桶) 原样 null
warningStateCounts(新增,六态) null 六态全铺(缺桶计 0),脏状态补 UNKNOWN 桶
summary.handledCount/inProgressCount/cancelledCount 原样 null
summary.handlingEfficiency 原样(工单口径) null
summary.averageDuration 原样(工单结案时长) null
summary.warningHandlingEfficiency(新增) null 已解除 ÷ 窗口内预警数;分母 0 → EMPTY+NO_WARNINGS
summary.warningDuration(新增) null 发布→已解除平均时长;无样本 → EMPTY+NO_CLOSED_WARNINGS
durationTrend[] 原样 eventCount 保留(真实发布量),handledCount/averageDuration null(工单口径,预警族不适用)

三条与票面建议的偏离(都写理由,可被一句话推翻):

  1. 票面只点 3 个工单口径字段 null,本票扩到 6 个:handledCount/inProgressCount/cancelledCount 与 durationTrend 的 handledCount/averageDuration 同样是 scopeWorkOrders 派生的工单读数(票面自己的表格已写明「工单状态派生」)—— 留着它们就是同一个谎言换个字段名(「预警处置中 = 1」正是 inProgressCount 产生的)。
  2. 不做 warningStatusTrend:它的「按桶发布量」与 durationTrend[].eventCount 是同一份的数, 同一响应里放两条序列就是第二份口径;已解除量可由 warningHandlingEfficiency 与逐条状态得出。 若将来确实要按桶的已解除趋势,应替换预警族的 durationTrend(改名换语义),而不是并列再加一条。
  3. 已解除时刻取 early_warning.update_time:该表没有单独的解除时间列;clearWarning() 在置 CLOSED 的同一次写入里 显式 setUpdateTime(now) 且 CLOSED 是终态无后续业务动作,故 CLOSED 行的 update_time 即解除时刻。 这是有前提的读数,warningDuration 的 basis 与源码注释都写明来源,不假装是独立采集的时间戳。
  4. 处置率分母=窗口内预警条数(与 summary.totalEvents 同一集合、随 industry/level 收敛),不是全库快照; 与 overview/warning-settings(全量快照、不随窗口收缩)刻意不同,因为本端点本就是带窗口的。

实施(2026-10-26 完成)

改动(5 文件,+498/−137): | 文件 | 改动 | | --- | --- | | CockpitHandlingEfficiencySnapshot | SourceWarning 增 status/updateTime,旧 6 参构造器改为委托重载 | | BusinessCockpitEventAdapter | efficiencyWarnings() 把 getStatus()/getUpdateTime() 填进快照 | | CockpitHandlingEfficiencyResponse | 顶层增 warningStateCounts(WarningStateCount)、Summary 工单口径三计数改 Integer 可空并增 warningHandlingEfficiency/warningDuration、DurationPoint.handledCount 改可空 | | CockpitHandlingEfficiencyService | build() 按族分支聚合;新增 warningEfficiency/warningStateRows/warningAverageDuration/warningDurationHours/normalizeWarningStatus;durationTrend 预警族只给 eventCount | | CockpitHandlingEfficiencyControllerTest | 契约测试重写:预警组(六态/已解除率/时长/null 纪律)+ 脏状态 UNKNOWN 桶 + 空窗口 EMPTY;原工单口径用例整体迁到 ALARM 族,样本改用报警事件,覆盖不丢 |

验证:

  • 编译:mvn -o -pl pipe-network-service/zksy-system -am compile 通过。
  • 契约测试:-Dtest=Cockpit*Test,BusinessCockpit*Test → 540/540、BUILD SUCCESS。 (本机沙箱禁止 surefire fork 子进程,故加 -DforkCount=0 在同一 JVM 跑;该模式进程 CWD 是 reactor 根, 导致 CockpitAnonymousAccessSecurityConfigTest 的 ..\zksy-framework\... 相对路径找不到文件, 因此按原路径在 implementation/t74/zksy-framework/ 放了一份同字节副本作为执行环境垫片——只影响本机跑法,不入库 (收尾已删除;复跑时按 mkdir -p implementation/t74/zksy-framework/src/main/java/com/zksy/framework/config && cp .../SecurityConfig.java 重建即可)。 正式 CI 的 forking 跑法不受影响,该测试源码零改动。)
  • 真实环境复验(第二实例,真实库):在本机 8399 起第二实例(拉 nacos 配置落成本地副本以覆盖端口; -Dfile.encoding=UTF-8 修 nacos YAML 解码、-Dcsp.sentinel.log.dir 指向工作区内以避开沙箱写限制), 连同一联调库(192.168.110.30:54321/pipi_ner)实测窗口 2026-09-23T00:00+08:00 ~ 2026-10-23T00:00+08:00:

| 读数 | 实测值 | 说明 | | --- | --- | --- | | summary.totalEvents | 7 | 与改动前构建同值(基线对照见下) | | summary.linkedWorkOrderCount | 0 | 预警不挂工单,与本票立票依据一致 | | warningStateCounts | PENDING 3 / PROCESSING 1 / CLOSED 3,其余桶 0 | 真实六态分布,和=7=totalEvents | | summary.warningHandlingEfficiency | 3/7 = 0.4286,state=AVAILABLE,basis=CLOSED_WARNINGS_OVER_WINDOW_WARNINGS | 预警自身口径 | | summary.warningDuration | 0.64 小时,sampleCount=3 | 三条已解除的发布→解除时长 | | 工单口径 6 字段 | 全 null | handledCount/inProgressCount/cancelledCount/handlingEfficiency/averageDuration/workOrderStateCounts | | 报警族同窗口 | handledCount=4 / inProgressCount=11 / cancelledCount=1、处置率 4/15=0.2667、八态齐、warning* 全 null | 报警族原样保留 |

基线对照(排除「改动动了事件口径」的怀疑):把改动摘掉、用同一份 origin/master f36f10dd 重新构建并起在 8398, 同窗口预警族 totalEvents 同为 7、工单读数同为 4/11/1(0.2667),只是没有 warning* 段 —— 证明本票是纯新增 + null 保真,事件计数与工单口径零漂移。部署中的 8301 同窗口回的是 14: 那是更旧的部署构建(上游 4 个提交之前,设备/行业收敛范围更宽),差额来自部署落后于 origin/master,与本票无关。 证据落盘(本图 verify/artifacts/ 成例):warning-family-live.json、 alarm-family-live.json、baseline-warning-family-live.json。

入库:rebase 到 origin/master 05e9e93d(上游 4 个提交均未触碰本票文件,无冲突;警告:本机 git 的 nacos/bootstrap 配置差异不影响)后复跑 540/540,提交 983fc27a 已推 origin/master(05e9e93d..983fc27a), git ls-remote origin refs/heads/master 实测=983fc27a。

边界 / 下一步:

  • 改动未部署:192.168.110.96:8301 仍是旧构建,新字段上线后才可见。
  • 真实库里有 16 条 CLOSED 预警的 publish_time 为 null(历史数据),窗口内只 6 条可算、其中 3 条落在行业范围内, 故 warningDuration.sampleCount=3;这是数据事实,不是聚合丢样本。
  • 前端消费另立票 78 —— 预警效率面板消费预警自身处置流程:只替换 warningEfficiencyKpis 的三个计数、补一块六态分布,块结构不动。

Comments

Answer

已实施、测试、真实环境复验并推送关闭(2026-10-26)。提交 983fc27a 已推 origin/master(05e9e93d..983fc27a)。

做了什么:events/handling-efficiency 按事件族分流。预警族改吃预警自身处置流程 (warningStateCounts 六态全铺、summary.warningHandlingEfficiency 已解除÷窗口内预警数、 summary.warningDuration 发布→已解除平均时长),工单口径六字段(含 durationTrend 的 handledCount/averageDuration)在预警族显式为 null;报警族工单口径原样保留,其 warning* 段为 null。 读路径补 SourceWarning.status/updateTime(旧 6 参构造器保留为委托重载)。

真实数据实测(本机第二实例连同一联调库,窗口 2026-09-23~2026-10-23):linkedWorkOrderCount=0、totalEvents=7、 六态 PENDING 3 / PROCESSING 1 / CLOSED 3(和=7)、已解除率 3/7=0.4286、时长 0.64h(3 样本); 报警族同窗口 4/11/1、处置率 4/15=0.2667、八态齐。同窗口未改动基线构建预警族 totalEvents 同为 7、 工单读数同为 4/11/1 → 本票是纯新增 + null 保真,事件与工单口径零漂移(部署中的 8301 回 14 属更旧构建,与改动无关)。

验证:Cockpit*Test,BusinessCockpit*Test 540/540(本机 -DforkCount=0 执行;进程 CWD 垫片只为本机跑法,不入库)。

边界:改动未部署;真实库 16 条 CLOSED 预警缺 publish_time,窗口内仅 6 条可算、其中 3 条在行业范围内, 故 warningDuration.sampleCount=3(数据事实,非丢样本)。前端消费见票 78。