仪表盘可视化
B17 这个 block 的任务是把 AML 系统的「outcome 指标」从一堆散落的 eval 数字,凝成一块能被人一眼读懂的仪表盘。
阶段: B17 · outcome 指标仪表盘 + A/B(Day 161-170) 标签: #observability #metrics-ux #aml-copilot #polarity-coloring
今日导引(由浅入深)
B17 这个 block 的任务是把 AML 系统的「outcome 指标」从一堆散落的 eval 数字,凝成一块能被人一眼读懂的仪表盘。
Day 161-165 我们逐个定义并聚合了四个指标(FPR / SAR 质量 / cost-per-case / p95),Day 165 把它们合成一个 dashboard.ts 的 snapshot JSON。
今天是这条聚合管线的「最后一公里」——可视化:把 snapshot 渲染成 outcome 面板的指标卡。
在 B1→B18 的整条能力曲线上,这一步处在「能观测 → 能呈现」的拐点:前面是后端纯函数的确定性聚合,今天第一次把它接到 AML Copilot 页面(/aml-copilot)上给人看。
今天的最小可判定产出是:4 张指标卡,每张卡的 delta 箭头按指标极性着色,且这块着色逻辑可单测。
1. 机理精读
指标卡的核心难点不在画卡,而在「方向语义」。 一块指标卡通常有三件东西:当前值、与上一次 run 的 delta、以及一个表达「好/坏」的颜色或箭头。 问题在于:delta 的正负,和「这是好事还是坏事」之间,没有恒定映射。 FPR(误报率)下降是好事,delta 为负要标绿; SAR 质量上升是好事,delta 为正才标绿。 如果偷懒按 delta 的正负统一着色(正绿负红),FPR 改善了反而会被涂成红色,把改进当成退步——这是仪表盘最经典的误导。
所以正确的设计是「按指标极性(polarity)着色,而非按 delta 符号着色」。 每个指标要先声明它的极性:lower-is-better(FPR、cost-per-case、p95)还是 higher-is-better(SAR 质量、完成率)。
着色函数的真值表是「极性 × delta 符号」的二维:
| 指标极性 | delta < 0 | delta > 0 |
|---|---|---|
| lower-is-better(FPR/cost/p95) | 绿(改善) | 红(恶化) |
| higher-is-better(SAR 质量/完成率) | 红(恶化) | 绿(改善) |
注意对角线:同样一个绿色向下箭头,对 FPR 是「误报降了,好」,对 SAR 质量是「质量降了,坏」——含义相反。 这就是为什么不能用单一的「绿=涨」直觉。
这一步在设计上和 Day 165 的 dashboard.ts 是严格分层的。 dashboard.ts 只负责算出值和 delta(纯数据,bundler-safe,无 fs、无 React);
今天的面板组件只负责「拿到 snapshot → 决定颜色 → 渲染」。
数据层不关心颜色,呈现层不重算数值。
这种分层让数据可在 Node 单测里验证,颜色逻辑可在组件单测里验证,互不污染。
本日 seed 明确「沿用 dashboard.ts 的 snapshot schema」——也就是说今天不改 schema,只读它。
还有一层常被忽视的设计:箭头方向与颜色是两个独立维度,不能合并。
- 箭头方向反映 delta 的物理方向:值升了朝上、降了朝下。它是「事实」。
- 颜色反映好/坏:绿=改善、红=恶化。它是「评价」。
把两者绑死(一律「向上=绿」)就退回到了「按 delta 符号着色」的错误。 正确的卡是:FPR 下降 → 箭头朝下 + 绿色; SAR 质量下降 → 箭头朝下 + 红色。 同样朝下,颜色相反。 读者先看箭头知道「发生了什么」,再看颜色知道「这是好是坏」,两层信息互不替代。
边界澄清:与 Day 162(FPR 独立金标)、Day 164(cost/p95 采集)相比,今天不产生任何新测量数字——它只是把已聚合好的 snapshot 画出来。
如果某个指标在当前 run 里还没有真值(FPR/SAR 质量/p95 都需要 eval run 才有),面板里显示的就是占位值,必须诚实标「教学模拟」,不能让占位值冒充实测。
这条边界把「可视化」和「测量」严格分开:可视化层永远不创造数字,只搬运 dashboard.ts 已算好的值。
和 B16(治理一页纸 + AI 原型 + 可用性)的衔接点在于「可读性」服务的对象不同。 B16 的可用性迭代(Day 160)面向的是「调查员能不能顺手用这个 AML 原型」; 今天的指标卡面向的是「评审者/架构师能不能一眼读懂这套系统的 outcome 健康度」。 前者是产品用户的可用性,后者是利益相关方的可观测性——两者都用「方向语义 + 改前改后对比」的同一套度量直觉,但受众和呈现层不同。 理解这个区分,能避免把 outcome 仪表盘做成给终端调查员看的操作界面(那是 B16 的事),它应该是给「对系统质量负责的人」看的体检表。
2. 推导 / 手算 / 代码走读
今天 seed 引用的底座是 src/agent/eval/dashboard.ts(Day 165 已 built)。
面板要读它的 snapshot,所以先走读这个数据契约的真实结构:
buildDashboard(reports: EvalReportLike[])是纯函数,入参是一组 eval 报告,出参是DashboardSnapshot。它不读 fs(注释明确「the fs reader lives in scripts/build-dashboard.ts so this stays bundler-safe」)——这正是面板能在前端安全 import 的前提。DashboardSnapshot的真实字段是{ runs, totalTasks, nsm, leaves, note? }。其中nsm(North Star Metric)是{ name: 'task autonomous completion', value, unit: 'rate' },leaves是一个Metric[]数组。leaves数组里真实包含的指标键是:'partial-credit mean'、'code-check pass'、'unknown rate'、'cost (USD/run)'、'judge-human kappa'。诚实校准:本仓dashboard.ts当前的 snapshot 结构是「nsm + leaves 列表」,并非 seed 描述的「FPR/SAR 质量/cost/p95 四键 + delta 字段」那种四指标 outcome 骨架——后者是 B17 在 161-165 规划的 outcome 仪表盘目标形态,与现有通用dashboard.ts的 NSM-tree 形态尚未完全合流。今天的面板若按 outcome 四卡渲染,对应的四个 outcome 指标里只有cost (USD/run)在现有 leaves 中有直接对应键。- 每个
Metric的形状是{ name, value: number | null, unit? },value允许为null(没跑出真值时)。空报告时buildDashboard返回note: 'no eval reports yet — run \pnpm eval:agent` (needs an API key) to populate live data'`——这正是面板「占位/教学模拟」状态的数据来源信号。 - 当前
DashboardSnapshot没有 delta 字段。Day 165 seed 说「加 delta 字段对比上一 run」属于该 block 的规划项,今天若要画 delta 箭头,delta 需在 snapshot 真正带上趋势字段后才有真值;在此之前箭头同样属占位。 - 空报告分支:
buildDashboard([])返回runs: 0, totalTasks: 0, nsm.value: null, leaves: [], note: 'no eval reports yet ...'。面板应把这个note直接渲染成「未跑 eval」的空态提示,而不是画 4 张全 null 的卡——这是「教学模拟/未跑」状态的最干净来源。 cost (USD/run)这个 leaf 的单位是'usd',partial-credit mean无单位,*-rate类用'rate'。面板格式化时要按Metric.unit决定后缀($/%/ 裸数),不能所有卡都套toFixed(2) + '%'。
着色逻辑的最小可手算例子(这是今天「可测断言」的核心):
- FPR 从 0.22 降到 0.18,delta = −0.04,极性
lower-is-better→ 查表落「绿/改善」,箭头朝下。 - SAR 质量从 0.71 升到 0.74,delta = +0.03,极性
higher-is-better→ 落「绿/改善」,箭头朝上。 - cost-per-case 从 $0.012 升到 $0.015,delta = +0.003,极性
lower-is-better→ 落「红/恶化」,箭头朝上。 - p95 从 1800ms 降到 1600ms,delta = −200,极性
lower-is-better→ 落「绿/改善」,箭头朝下。
四张卡里有两个负 delta(FPR、p95)落绿——这正是单测要断言的核心反例:polarityColor('lower-is-better', -0.04) === 'green' 且 polarityColor('higher-is-better', -0.04) === 'red'(同一个负 delta,两种颜色)。
再补一条边界:polarityColor(any, 0) === 'neutral'(delta 恰为 0 不着改善/恶化色)。
这三条断言就完整覆盖了真值表的两条对角线 + 中性态。
3. 今日实战
- 在 AML Copilot 页面(路由
/aml-copilot,对应src/pages/下的 AML Copilot 页面文件)新增一个 outcome 面板组件,从src/agent/eval/dashboard.ts的DashboardSnapshot取数据。 - 组件渲染 4 张指标卡:每卡显示
Metric.name+Metric.value(或在value===null时显示占位 + 「教学模拟」标签)+ delta 箭头 + 极性着色。 - 抽出一个纯函数
polarityColor(polarity: 'higher-is-better' | 'lower-is-better', delta: number)(放在组件旁的工具文件,便于不带 React 依赖地单测),按上文真值表返回'green' | 'red' | 'neutral'(delta===0 → neutral)。 - 给
polarityColor写组件级单测:断言「同一个负 delta 在两种极性下颜色相反」「delta===0 落 neutral」。 - 在
/aml-copilot页面截图,含 4 张卡。占位数据的卡必须可见地带「教学模拟」标注。
测试分层(与本仓「纯函数确定性可测」纪律一致):
polarityColor是纯函数,放工具文件,用 Vitest 直接断言真值表三条(两对角线 + 中性),不需要渲染 React。- 卡片格式化(按
Metric.unit选$/%/裸数)也抽成纯函数formatMetric(metric),同样纯函数单测。 - 真正需要 DOM 的(4 卡布局、截图)才走组件测试/手动截图,归到 UI-tooling-gated。
- 这样「颜色/格式」逻辑的正确性不依赖能否截图,CI 里就能回归——把外部 gated 动作的面积压到最小。
4. 今日实测 / 产出
dashboard.ts的 snapshot 可读(Day 165 已 built,含在 423 测试套件的绿测内)。- outcome 面板 UI 组件 + 截图 = UI-tooling-gated(外部动作:需在 /aml-copilot 加组件并截图)。
- 组件渲染逻辑可单测,4 卡布局 + 极性着色为可测断言。
- 诚实标注:面板数据若用占位值需标「教学模拟」。FPR / SAR 质量 / p95 在当前未跑 eval 时为占位;
cost (USD/run)可填已测真值 V4-Flash $0.0139/run。
5. 常见误区 / 陷阱
- 按 delta 符号统一着色:正绿负红,会把 FPR 下降(好事)涂红。必须按指标极性着色——这是今天的全部要点。
- 占位值冒充实测:FPR/SAR 质量/p95 没跑 eval 就显示一个数还不标注,违反这套笔记的诚信底线。占位必须显式标「教学模拟」。
- 在呈现层重算数值:颜色逻辑该只消费
dashboard.ts已算好的 value/delta,不要在组件里重新跑聚合,否则数据层与呈现层会分叉。 - 箭头方向与颜色混淆:箭头方向应反映 delta 的物理方向(升/降),颜色反映好/坏。把两者绑死(向上一律绿)就是上一条误区的变体。
value===null不做空态处理:Metric.value类型本就允许null,组件若不判空会渲染出NaN/空白卡。空态要显式显示占位 + 「教学模拟」,而非崩在toFixed上。- 把 polarity 硬编进组件:每个指标的极性应作为指标元数据声明(lower/higher-is-better),而非在组件里写 if-FPR-then-绿 这种逐指标分支——后者一加指标就漏。
6. 学习资源(每条带 YYYY-MM)
- 本仓
src/agent/eval/dashboard.ts源码 + 注释(AICAP-180 B5/B9,Pillar P5)——snapshot schema 与 NSM-tree 设计(2026-06)。 - OpenTelemetry GenAI Semantic Conventions(2026-01)——指标命名约定,仪表盘指标键沿用
gen_ai.*而非自造名。 - Nielsen Norman Group, "Usability Metrics"(2024-09)——方向语义/改前改后对比的度量方法论,B16 已引,与本日着色极性同源。
- Stephen Few, "Information Dashboard Design"(经典,作打底)——指标卡极性着色的可视化原则;非主线,仅作通识背景。
SOTA检查 (2026-06 更新)
- 当前主流:指标卡 + 趋势箭头 + 极性着色是稳定的仪表盘 UX 模式,无时效风险,仍 SOTA。
- 需复查点:AML Copilot 页面是否仍引用最新 snapshot schema 版本——
dashboard.ts若加了 delta/experiment 字段(Day 165/169 规划),面板必须跟上,避免字段漂移(snapshot 加了字段但面板还读旧形状)。 - 避免:自造指标名(沿用 OTel
gen_ai.*约定);占位值不标「教学模拟」;按 delta 符号而非指标极性着色。 - 下次复查:跟随 B18(Day 171-180)全局 SOTA 复核时,连带核对 dashboard schema 与面板字段对齐;OTel GenAI 语义约定按执行当周复验版本。
衔接
- 昨天:Day 165 — 活仪表盘聚合(把 4 指标合成 snapshot JSON + delta 字段,纯函数 schema 单测)。
- 今天:把 snapshot 渲染成 outcome 指标卡,按指标极性着色(同样绿箭头对不同指标含义相反),UI 截图 gated、占位标「教学模拟」。
- 明天:Day 167 — M6 A/B 实验设计(V4-Pro vs V4-Flash 配对评测,落两组 per-task transcript)。