维护者 2026-10-19 反馈第 6 条:预警处置「预警效率」面板没有做预警/报警模式的数据区分;预警模式下没有关联工单数据,应展示预警流程的信息。
实测证据(2026-10-19;2026-10-21 复验一致):/events/handling-efficiency?eventFamily=WARNING 与 =ALARM 的响应结构完全相同——都只有 workOrderStateCounts(工单八态)、工单口径 handlingEfficiency(CLOSED_WORK_ORDERS_OVER_ELIGIBLE_WORK_ORDERS)、averageDuration。预警族实测 linkedWorkOrderCount=0、missingWorkOrderCount=totalEvents(10-19 那次 6/6,10-21 复验 5/5)——预警没有工单,但面板画的仍是工单口径,等于用一个不存在的对象统计预警。
domain/warningResponsePanels.js 的 responseEfficiencyPanel 也只消费 workOrderStates/handlingRate/时长趋势,未按 eventFamily 分支。模式切换(eventStore.mode)只换了请求参数,UI 语义没换。
summary.handledCount/inProgressCount/cancelledCount/totalEvents,实测均存在),并如实说明「预警不挂工单」;不得把工单口径的数字画在预警模式下。missingWorkOrderCount),不画 0% 假读数。eventListSwitchView 单一词表,不新增第二张)。domain/warningResponsePanels.js(toHandlingEfficiency/responseEfficiencyPanel)、services/warningResponseApi.js、EventListPane.vue/WarningResponsePane.vue。../specs/2026-10-18-global-list-pagination.md(本票不涉分页,属同批维护者反馈)
ready-for-agent;Blocked by #59 已 resolved)。summary.handledCount/inProgressCount/cancelledCount 不是预警生命周期字段,而是工单派生
(CockpitHandlingEfficiencyService.build() 的 stateCounts 来自窗口内全部工单,不要求关联事件)。
预警族实测 inProgressCount=1 来自 1 张与预警无关的待派单工单 —— 照票面字面消费就会重新造出
「预警处置中=1」的假读数,正是本票要禁止的东西。## Answer。Status: resolved Assignees: codex Type: task Blocked by: #59 Labels: wayfinder:task, ready-for-agent
认领:状态 ready-for-agent、无认领人;Blocked by #59 已 resolved(票 59 已关闭),本票处于 frontier,可开工。先读代码与既有裁定,再确认方案。
已查阅范围:本票正文/评论/依赖、domain/warningResponsePanels.js、services/warningResponseApi.js、
stores/eventStore.js、components/WarningResponsePane.vue、mocks/contractMocks.js 的 handling-efficiency 载荷、
票 42 与 verify/t42-warning-settings-contract.mjs、后端 CockpitHandlingEfficiencyService(t30 validation-checkout 同版),
以及 2026-10-21 对 192.168.110.96:8301 的实测(载荷落盘 verify/artifacts/t65/)。
Ticket 前提被证伪一条(其余成立):Acceptance 第 2 条把 summary.handledCount/inProgressCount/cancelledCount
当作「后端已有的预警生命周期字段」——实测与源码都表明它们是工单派生:
| 事实 | 证据 |
|---|---|
stateCounts 来自范围内全部工单,不要求关联事件 |
CockpitHandlingEfficiencyService.build() 注释原文「不要求关联报警/事件」,累加源是 scopeWorkOrders(...) |
所以 handled/inProgress/cancelled 与 handlingEfficiency/workOrderStateCounts 同源 |
三者都是 stateCounts 的分项,family 只影响 facts(事件集合),不影响 stateCounts |
预警族实测 inProgressCount=1 与预警无关 |
linkedWorkOrderCount=0、missingWorkOrderCount=5,但 workOrderStateCounts 有一张「待派单」= 1 |
| 号 2 字面实施会重造假读数 | 「预警处置中=1」这个 1 来自一张不挂任何预警的工单 |
提问(已带选项,维护者选定 A):
/overview/warning-settings 的六态生命周期:分布是全量快照不随 range 收缩、且该端点不支持 90d(400),
需降级处理且在 eventStore 增一次取数。summary 三字段:会重新造出票面禁止的假读数。data.family(后端 eventFamily 回显)优先,其次 extra.mode(面板把 eventStore.mode 传下来),
都没有时落报警口径(旧调用点 t19/t16/t44 不因签名变化而变空壳)。historyTrendRows,不另写采样)。responseEfficiencyTitle(mode),词根取 eventListSwitchView(mode).label —— 单一词表。
面板内部标题 + 外壳面板外框标题(CockpitShell.vue 的 panelTitle())都读它,两处由构造一致。WARNING_EFFICIENCY_GAP(说清「预警不挂工单」与「没有预警自身处置流程统计」),
报警族 ALARM_EFFICIENCY_BASIS(工单是报警的处置手段)。走票 56 的折叠摘要(data-panel-note,文本在 DOM 里)。重新开启(维护者 2026-10-21 追加两条修改要求)
eventFamily=WARNING 下到底有没有预警自身的处置流程数据;若确实没有,摘要文案要如实且可理解;若数据其实存在(我上一轮结论有误),则预警族必须改成消费真实数据。第 2 条是对我上一轮「方案 A」结论的复查要求,须重新取证后再动手。
两条追加需求已完成(前端 + 后端一起交付)。完整对账写入 Answer。
要点一:报警模式的工单列表来自新端点,不是 handling-efficiency。 handling-efficiency 只给工单状态计数、不返回工单实体(报警族实测八态合计 0,但同一设备的 /equipments/21119740/work-orders 有 7 张真实工单)。按维护者指示参考后端既有全量实现后,新增 GET /api/cockpit/v1/work-orders:复用 WorkOrderService + CockpitWorkOrderSummary,挂在免鉴权前缀下(运营侧 /api/ops/** 实测 401)。
要点二:预警数据复核结论是「没问题」,改的是文案。 源码 CockpitHandlingEfficiencyService 的 stateCounts 来自 scopeWorkOrders,注释原句「范围内全部工单(不要求关联报警/事件)」;实测预警族 linkedWorkOrderCount=0、八态合计 0。旧文案三处毛病已修:本端点是接口词汇、先讲缺口再讲读数、工单关联实况对值班人员无意义。
真机回归缺陷 + 修复(2026-10-22,维护者反馈「工单列表分页点不到第 2 页」)
根因(不是后端、也不是分页控件的问题):WarningResponsePane.onListPage() 早先只认 historyAnalysis——
if (props.panelId !== 'historyAnalysis') return。工单列表的 serverPage 块画出了翻页控件、
PanelBlocks 的 list-page 事件也正常发出,但在面板这一跳被 return 掉,页码根本没进 store。
修复(3 处):
stores/eventStore.js:新增 workOrderPage 页码状态 + setWorkOrderPage(next) 翻页动作
(只重取工单分区、不牵连另外四块;越界页码按后端回显夹回;迟到响应用独立令牌 workOrderToken 丢弃
—— 不共用全局面板 token,否则翻一个工单页会把四块面板的在途响应整批作废)。stores/eventStore.js:条件变化(mode/range/industry/level/status)时 workOrderPage 一并回第 1 页(条文 5;
定时刷新不带条件 ⇒ 页码保持);return 表暴露 workOrderPage / setWorkOrderPage。components/WarningResponsePane.vue:onListPage 按面板路由到各自分区(历史分析→setHistoryPage,
报警效率→setWorkOrderPage),不再一刀切。判据:新增闸门 t65b 11/11 + 变异 6/6(verify/t65b-work-order-paging.mjs)——
这类缺陷领域层闸门抓不到(块的 serverPage 与渲染层 emit 都是对的,断在「组件 → store」这一跳),
所以该闸门起 pinia + 真实 store + 真实 mock transport,从「点翻页」一路断到「分区换页」:
P1 首屏 5 张/共 12/3 页;P2 翻第 2 页请求带 page=2 且行换成 6–10;P3 只重取工单分区(其它四块引用未变);
P4 重复点同一页零请求;P5 越界夹回最后一页;P6 切范围回第 1 页;P7 连点只认最后一页;P8 预警族仍不出现工单列表。
变异 M27 就是本次缺陷原形(面板退回只认 historyAnalysis)→ S1 红。
等价变异教训(记入 t65b-mutations):假 mock 立即返回时「删掉迟到响应检查」不可观测 —— 必须让第 2 页故意慢回(40ms),P7 才抓得住(M32)。
回归:t13/t19/t20/t42/t46/t61/t64(17/17)/t65(51/51+27/27)/t75(18/18+8/8) 全绿;SFC 编译通过。改动未提交。
再一处真机缺陷:工单列表没有按时间筛选(2026-10-22 维护者反馈)
现象:面板按近 7 / 30 / 90 天切换时,上方「工单状态分布」按窗口统计,下方「当前工单列表」却是全库——
最刺眼的形态就是「分布说 0 张,列表列出 14 张」。根因:GET /api/cockpit/v1/work-orders 只收
page/pageSize/stateCode,没有时间参数;前端也没传窗口。
修法(前后端一起):
后端(已移植到真实仓库 E:\pipe-ner-server 并编译/测试通过):
GET /api/cockpit/v1/work-orders 新增可选 startTime/endTime(ISO-8601 带偏移);BusinessCockpitWorkOrderReadAdapter.loadAll(...) 按 create_time 加 ge/le 条件 ——
刻意用 create_time 而不是 update_time:面板状态分布的分母来自
CockpitHandlingEfficiencyService.scopeWorkOrders(...),那里同样是「按 create_time 落在窗口内」。
两处必须是同一批工单,否则自相矛盾;count(wrapper) 的 createTime >= / <=
与两端实参)、缺省不加时间条件、非法格式 400、起点晚于终点 400。
教训记一笔:MyBatis-Plus LambdaQueryWrapper 的 getSqlSegment() 里是属性名
createTime(列名 create_time 执行期才映射),按列名断言会假红;
而 ORDER BY createTime DESC 也含 createTime,所以「没加时间条件」只能按比较运算符判。前端:
services/detailApi.js:fetchWorkOrderList 透传 startTime/endTime(原样,不掐格式);stores/eventStore.js:loadPanels 取数时把 activeConditions() 的窗口钉进 workOrderWindow,
工单列表与另外四块同一份窗口;setWorkOrderPage 复用 workOrderWindow 而不是重算 ——
P10 断言发现重算会让 endTime 毫秒级后移,翻页时边界工单会在页与页之间重复或消失;
条件变化时窗口随 loadPanels 刷新、页码回第 1 页;domain/warningResponsePanels.js:口径说明从「与工单状态分布是两个粒度」改为
「本窗口内创建的工单共 N 张 · 与上方「工单状态分布」是同一批工单(均按创建时间落在所选范围)」
—— 时间窗对齐后,旧措辞已经是在解释一个不存在的问题。判据:t65b 从 11 条扩到 14/14(新增 P9 首屏请求带当前窗口、P10 翻页沿用同一窗口、 P11 切 30 天换窗口并回第 1 页)+ 变异 9/9(新增 M33 请求丢窗口、M34 翻页重算窗口、 M35 切范围不刷新窗口);t65 H4 随文案更新为「同一批工单」。 回归:t13/t19/t20/t42/t46/t61/t64(17/17)/t65(51/51+27/27)/t75(18/18+8/8) 全绿。 后端:真实仓库编译通过,工单测试 35/35(含新增 4 条时间窗用例),驾驶舱全量 433/433。
当前状态:后端类已编译进 target/classes(12:41/12:45),但运行中的 JVM 仍是旧版 ——
实测非法时间参数仍返回 200 + 全量 14 条(新版应为 400),需重启后端后生效。
先说清数据来源,这是本需求最容易做错的一步。 handling-efficiency 不返回工单实体,只有状态计数:
报警族实测八态合计 0、24 条事件 hasWorkOrder 全 false。但同一台设备的
/equipments/21119740/work-orders 能查到 7 张真实工单(待派单1 / 已结案3 / 待接单1 / 处理中1 / 已取消1,
其中 2 张关联报警)。所以列表必须取自另一个端点,不能从 handling-efficiency 里"想办法"取。
按维护者指示「后端非驾驶舱专用接口当前存在类似的全部工单实现,请先参考」,核对后端后确定做法:
| 层 | 现状与结论 |
|---|---|
| 运营侧 | GET /api/ops/workOrder 有完整分页查询(WorkOrderPageDTO),但需鉴权(实测 401) |
| 免鉴权规则 | 只按路径前缀放行 GET /api/cockpit/v1/**(SecurityConfig:126);且有契约测试断言「只允许一条 cockpit permitAll 规则」——这是刻意的扩展点 |
| 数据层 | WorkOrderService 是标准 service,驾驶舱 adapter 已在直接注入使用;deviceOrders() 去掉 eq(deviceId) 就是全量 |
| 驾驶舱对外 | 原本只有按设备 / 按事件 / 按工单时间线三个入口,没有全量 |
后端(已在 t29 checkout 实现,编译通过) —— 新增 GET /api/cockpit/v1/work-orders:
CockpitWorkOrderReadAdapter.loadAll(stateCode, limit, offset) + CockpitWorkOrderListSnapshot(带 total);CockpitWorkOrderReadService.loadAll():翻页参数在服务端入口夹到合法区间(page≥1、pageSize≤100、stateCode 0–7 否则不筛),
夹取后回显实际生效值;total 走独立 count(wrapper),不由本页条数反推(票 61 纪律);CockpitWorkOrderListResponse:外形与既有 CockpitWorkOrderReadResponse 同一套约定,只把设备/事件维度换成分页四件套;toAlarmSummary:全量列表里「是否关联报警」逐张如实标,不因换入口就一律 true/false。CockpitWorkOrderListContractTest 4/4(全量口径不含 device_id、分页夹取并回显、非法 stateCode 退化为不筛、状态字典给出);
工单相关测试合计 31/31 全绿(含既有 27 条,零回归)。前端:
services/detailApi.js 新增 fetchWorkOrderList()(与既有 fetchWorkOrders 刻意分开:一个回答「某设备/某事件下有哪些工单」,
一个回答「全库有哪些、共多少张」;合并会让「没有编码」与「要全量」变成同一件事);stores/eventStore.js 新增 workOrderList 分区(不叫后端的裸字段名:store 出现后端裸字段名会被 t13 的 I1 守卫判红,票 05 纪律);domain/warningResponsePanels.js 新增 workOrderListBlock() / workOrderCardRows() / workOrderStateTone();components/WarningResponsePane.vue:卡片行复用 PanelBlocks 既有 list 块(row.code 即触发 open-card,不发明第二套卡片协议),
点开走新增的时间线浮层——复用票 12 的 WorkOrderTimeline.vue 与 tracksByOrder,不另写一份轨迹渲染。三条如实处理(都有断言钉住):还没取到 → 说「正在取数」;取失败 → 说「未取到」;真的 0 张 → 说「当前没有工单」。 三句是不同的话,不把缺数据画成 0 张(票 11/18 分区纪律)。
卡片刻意精简(维护者选定):工单号(抬头,可点)+ 状态标签(色键取自 StatusTag 既有封闭词表)+ 来源/创建时间/关联实况(副信息行)+ 故障描述。
长描述由 EllipsisText 省略,完整值在浮层里看——卡片不为一段长文本撑高整块。
分页:报警效率面板的工单列表页大小 5(与事件表 PAGE_SIZE=10 刻意分开:不同端点、不同翻页语义,
共用常量会让「改事件页大小」意外改掉工单列表)。
维护者要求「确认预警数据是否正确」。结论:我上一轮的判断成立,后端确实没有预警自身流程数据。 双证据:
CockpitHandlingEfficiencyService 的 stateCounts 来自 scopeWorkOrders(...),注释原句
「范围内全部工单(不要求关联报警/事件)」;family 只影响 facts(事件集合),不影响 stateCounts。192.168.110.96:8301):预警族 linkedWorkOrderCount=0、八态合计 0、
linkedWorkOrderCount + missingWorkOrderCount = totalEvents(4+0=4;报警族 24+0=24)。但旧文案确实写坏了,三处毛病已修:
| 毛病 | 修法 |
|---|---|
| 「本端点」是接口词汇(违反「接口词汇不上大屏」) | 改成说「后端」「这里」 |
| 先讲"没有字段",读者要读两遍才知道面板上的数能不能信 | 改成先讲读数(这些数是什么)再讲缺口(为什么没有工单口径) |
| 「工单关联实况」对值班人员无意义 | 改成直说「预警不挂工单」 |
新文案:「预警不挂工单,所以这里不画工单口径的处置率、工单状态分布与结案时长;下方的条数、等级分布与发生趋势都是本窗口内预警自身的真实读数。预警的处置流程统计(如「预警→处置动作」流转)后端尚未提供,已在需求池登记,上线后此处会自动换成预警自身的口径」。
/api/cockpit/v1/work-orders 仍 404)。若不在这里钉住前端解析,
等后端上线那天才发现字段对不上,就又要一轮返工。J1–J5 用真实形状载荷 + mock 同构把这个缝提前焊死。全绿:t13(50/50)、t16、t19(16/16)、t20(66/66)、t39(24)、t40(16)、t42、t44(109)+变异(6/6)、t46(54/54)、
t56 变异(9/9)、t61(39/39)、t64 + 变异(17/17)。SFC 编译:WarningResponsePane / CockpitShell / PanelBlocks / WorkOrderTimeline 均 OK。
后端:mvn compile 通过,工单相关测试 31/31。
GET /api/cockpit/v1/work-orders 实测仍 404。需把 t29 checkout 的改动部署到
192.168.110.96:8301 后,面板的工单列表才会从「未取到」变成真实卡片。前端已按最终契约实现并有 J 组守着。eventList.js,含已删除的 latestWorkOrders 符号,重启后正常)——那不是代码缺陷,但说明改完前端需重启 dev server。判据与证据在 t65 闸门,都不启浏览器、不联网;渲染层用 Vue SSR 跑真实 PanelBlocks.vue:
两族语义分流 ✅ 同一个端点、同一份响应类,靠 eventFamily 分流:预警族只出
预警条数 / 已关联工单 / 未关联工单 + 预警等级分布 + 预警发生趋势;报警族保留
处置率 / 响应与关闭时长 / 工单状态分布 / 结案时长趋势。A1–A7c 九条逐项钉住,
其中 A7c 反过来钉「报警模式不出现预警自身 KPI」——分流是对称的,不是只给一边加口径。
预警族不画工单口径 ✅ 三重判据:KPI 键表(A3/A4)、块标题与块类型(A4:不得有 stacked、
不得含「工单状态分布/结案时长/处置率」)、画出来的块序列化后零工单口径词汇与基线(A4b:
CLOSED_WORK_ORDERS/可计入工单/已结算/工单状态 一个都不许出现)。
变异 M1(整支预警分支跳过)、M1b(KPI 换了但图表没换=半分流)、M2(预警族重新长回 stacked 块)
三种形态都被 A4 当场抓住。
如实说明「预警不挂工单」+ 真实关联计数 ✅ KPI 的「已关联/未关联工单」直读
summary.linkedWorkOrderCount/missingWorkOrderCount(实测 0 / 全部),不是 0% 假读数;
两句 KPI hint 与 WARNING_EFFICIENCY_GAP 都明写「预警不挂工单」,A3b 钉住这句必须在。
变异 M3(改用 handledCount/cancelledCount 冒充关联计数)被 A2 当场抓住 —— 这就是票面说
要防的「把工单派生数画成预警数」。
预警族读数是真的,不是空面板 ✅ 等级分布逐级等于真实事件行统计(A5:4/1/0/0 对
events[] 里 level 的分布)、发生趋势按日合并且量不丢(A6:169 个小时桶 → 覆盖天数个点,
非零点数 = 有事件的天数,求和 = 逐桶 eventCount 总和)。变异 M4(等级全铺 0)、
M5(不按日合并、169 桶直接铺折线)分别被 A5/A6 抓住。
标题 / 口径说明 / KPI 提示一起换,单一词表 ✅ responseEfficiencyTitle() 的词根取
eventListSwitchView(mode).label;B1 是行为断言(改词表则标题跟着变,不是比对字面量),
B2/B4/D4 钉面板内标题与窗口读数随族换,B3 钉两族口径说明各自如实。
外壳 panelTitle() 也读同一份,E2/E3 钉住「外壳零第二张标题字面量」
(变异 M12 退回静态 labelForPanel、M13 在外壳另写一份字面量,均当场转红)。
闸门 + 变异 + 回归 ✅ t65 32/32、t65-mutations 16/16 全部按字节还原。
回归全绿:t16、t42、t44(109)+ 变异 6/6、t56(33)、t61(39)、t64(61)+ 变异 17/17、
t19(16)、t13(50)、t20(66)、t46(54)、t48(33)、t39(24)、t40(16)、t01。
两处既有红如实记(与本票 diff 零交集,已核):t17-infrastructure-panels /
t18-monitoring-panels 导入期报 MANAGEMENT_REGION_GAP 缺导出(HEAD 上即无,Grep 验证 0 命中);
t05-adapter 崩溃在 deviceMetricSources.js 读 archive.type(设备档案 mock 路径,票 50/51 记过的同一条断裂)。
t45-state-axis-and-effects 的 4 条红(C3b/C3c/C3d/C3f)正是票 70 记录的既有红。
契约缺口如实记录并另开后端票 ✅ 票 74 后端执行票:
记录了 eventFamily=WARNING 与 =ALARM 回同一套工单口径的实测与源码证据、建议契约
(warningStateCounts/warningHandlingEfficiency/warningDuration/warningStatusTrend,
两族工单口径三字段在预警族显式 null —— 与票 41 在 warning-settings 已做的同一条纪律)、
验收五条与参考实现落点(CockpitOverviewWarningSettingsService.familyMetrics())。
本票没有假装已有数据:预警族的三个 KPI 全是该端点里真实的字段,没有一个来自新契约。
改动文件(工作树混有并行在制品,未提交):
domain/warningResponsePanels.js(toHandlingEfficiency 分族 + responseEfficiencyPanel
分族 + responseEfficiencyTitle / WARNING_EFFICIENCY_GAP / ALARM_EFFICIENCY_BASIS)、
components/WarningResponsePane.vue(panelView(..., { mode: eventStore.mode }))、
CockpitShell.vue(panelTitle() + import domain 单源)、
domain/panelBlocks.js(KPI_ICONS/KPI_ICON_COLORS 登记 warningLinked/warningMissing 两个键)、
mocks/contractMocks.js(handlingEfficiencyPayload 补齐真实两族形状:eventFamily/summary 关联计数/
八态/durationTrend[].eventCount/逐事件行;不把预警族的工单字段藏掉 —— 缺口必须在 mock 上也看得见)。.scratch 侧:本票面、map Decisions so far、新票 74、
verify/t65-warning-efficiency-family-split.mjs、verify/t65-mutations.mjs、
verify/artifacts/t65/handling-warning-live.json / handling-alarm-live.json(2026-10-21 真实载荷,闸门读它)。验证层级如实说明:判据覆盖到「真实落盘载荷 → 领域层 → 共享渲染层(Vue SSR 真实组件)→ mock 同构」, 含两族对照与缺失态;未在真实浏览器里走查(AGENTS.md 项目规则,且本轮未获授权), 因此视觉排布(预警族三 KPI + 分级条 + 折线在面板栏宽内的观感)未取证实物证据。 按项目规则已跳过 build/dev/preview 验证。
入库状态:改动未提交 —— 工作树混有并行在制品(MapCanvas.vue/mapService.js/monitoringPanels.js 等 20+ 文件),
按本仓既有纪律(票 42/48/64)须由维护者裁剪协调。真实走查与推送归票 18 联调窗口。