Status: resolved Assignees: codex Type: task Blocked by: None Labels: wayfinder:task, ready-for-agent Category: backend-contract
票 65(前端执行票 —— 预警效率面板按预警/报警分流)收口时记录的契约缺口。 票 65 的验收第 5 条明写:若需要后端新增预警流程字段,在票面记录契约缺口并另开后端票,本票不假装已有数据。 本票就是那张后端票。
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 既有裁定)。
null、预警流程段齐备且 sum(六态)==窗口内预警数;
报警族工单口径原样保留。eventFamily=WARNING:linkedWorkOrderCount=0 时预警流程段仍给出真实六态分布
(不是全 0 假读数)。state=EMPTY + 原因码,不回 "0.0000" 当真实值。Cockpit*Test 通过;改动推 origin/master 并在票面记录提交哈希。warningEfficiencyKpis 的
三个计数即可,块结构不用重排)。CockpitOverviewWarningSettingsService.familyMetrics() 已有预警族六态/已解除率的
现成聚合逻辑,本票是把它搬进带窗口的 efficiency 端点并分族,不是从零写。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(工单口径,预警族不适用) |
三条与票面建议的偏离(都写理由,可被一句话推翻):
handledCount/inProgressCount/cancelledCount 与 durationTrend 的
handledCount/averageDuration 同样是 scopeWorkOrders 派生的工单读数(票面自己的表格已写明「工单状态派生」)——
留着它们就是同一个谎言换个字段名(「预警处置中 = 1」正是 inProgressCount 产生的)。warningStatusTrend:它的「按桶发布量」与 durationTrend[].eventCount 是同一份的数,
同一响应里放两条序列就是第二份口径;已解除量可由 warningHandlingEfficiency 与逐条状态得出。
若将来确实要按桶的已解除趋势,应替换预警族的 durationTrend(改名换语义),而不是并列再加一条。early_warning.update_time:该表没有单独的解除时间列;clearWarning() 在置 CLOSED 的同一次写入里
显式 setUpdateTime(now) 且 CLOSED 是终态无后续业务动作,故 CLOSED 行的 update_time 即解除时刻。
这是有前提的读数,warningDuration 的 basis 与源码注释都写明来源,不假装是独立采集的时间戳。summary.totalEvents 同一集合、随 industry/level 收敛),不是全库快照;
与 overview/warning-settings(全量快照、不随窗口收缩)刻意不同,因为本端点本就是带窗口的。改动(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 跑法不受影响,该测试源码零改动。)-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 仍是旧构建,新字段上线后才可见。CLOSED 预警的 publish_time 为 null(历史数据),窗口内只 6 条可算、其中 3 条落在行业范围内,
故 warningDuration.sampleCount=3;这是数据事实,不是聚合丢样本。warningEfficiencyKpis 的三个计数、补一块六态分布,块结构不动。已实施、测试、真实环境复验并推送关闭(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。