# 工作台看板 · KPI/趋势数据源对照（2026-09 实测）

改看板图表/卡片前先对照这张表，避免「找了个看起来对的字段，值量级差一倍」。

| 展示项 | 数据源 | 字段 / 取法 |
|---|---|---|
| H1 逐月目标 | `workbench_data.h1Monthly.total` | `targets[0..5]` |
| H1 逐月实际 | 同上 | `actuals[0..5]` |
| H2 逐月目标 | `kpiDashboard.depts[].h2_months` | 六项逐月求和（每个部门各一份） |
| H2 逐月实际（公司） | `threeYearMonthly['2026']['7'..'12']` | 已完成月有值 |
| H2 逐月实际（个人） | `kpiDashboard.people[].h2_actual_months` | 六项（7–12 月） |
| 1–12 月实际（整年图） | `threeYearMonthly['2026']['1'..'12']` | 未来月为 null → 0 |
| 当月实际（MCP 实时） | `kpiProgress.total` / `by_salesperson[name]` | 必须校月，见下 |
| 年度目标（KPI 卡） | `kpiDashboard.total_target` | 与 H1+H2 月目标合计**不一定相等** |
| 累计达成（KPI 卡） | `kpiDashboard.total_h1_h2`，退化 `total_h1` | — |
| 个人年度目标 | `kpiDashboard.people[].target` | 个人无 H1 月目标 → 整年图 1–6 月目标为 0 |

## 口径陷阱

1. **H1 月目标合计 + H2 月目标合计 ≠ `total_target`**（实测 8,041 万 vs 6,950 万）。两套来源独立（H1 用 h1Monthly、H2 用 depts 求和、卡片用 total_target）。把分段图合并成整年图前先核对，差异交用户定口径。
2. **命名反直觉**：`people[].h1_months` 是**实际**（1–6 月），`people[].h2_months` 是**目标**（7–12 月）。按字面理解必错。
3. **当月值必须用 `kpiProgress` 覆盖快照**，且要校验 `kpiProgress.month` 等于当前月（月初数据还停在上月，不校验就会误显上月数）；admin 与个人两分支的当月注入逻辑必须对称。
4. **`JOIN_SPLIT` 已停用**（入职拆分口径废弃），个人达成率按全年 `target` 计；残留的 1–7 月入职前拆分逻辑不要再复活。
5. 目标数被污染时用权重规律验证：`h2_months` ≈ `基数 × [0.12,0.15,0.19,0.20,0.19,0.15]`；反推基数要用整数搜索（`round(T×w[i])==int(hm[i])` 全六项成立），只用 `hm[1]/0.15` 会有 ±1 歧义。

## 月度目标：权威源与口径统一（定案）

**权威源 = 年度拆解表（按人 × 12 个月）**，典型文件 `2026年月度销售额拆解-<日期>.xlsx` 的「汇总」表：每人一行，`2026销售额目标` 列 + 紧随其后的 12 个月份列。月份列表头是 **Excel 日期序列**（`46023`=2026-01-01、`46054`=02-01、`46082`=03-01…），用 `datetime.date(1899,12,30)+timedelta(days=serial)` 还原月份；**部门名只在组首行，读取时要前向填充**，`小计` 行要跳过。

**判定哪份可信：逐部门比「12 月合计」与「部门年度目标」**
- 自洽的（月合计 == 年度目标）直接用。实测：大客户部 2,650 万 ✅、销售拓展部 3,400 万 ✅。
- 不自洽的那份就是旧的/没跟着改的。实测：同一份表里「科研销售部」年度写 1,880 万，而现行口径是「生命科学部 900 万」——**部门目标中途下调，月度拆解没同步更新**，这 980 万正好解释了分段目标相加多出的 ~1,091 万。

所以：差异先**按部门**定位到「哪个部门、差多少」，再决定缩哪一侧；不要整体按比例抹平，也不要反过来改成「年度=分段合计」。

**统一做法**：自洽部门按原表月形直接用；其余部门按 `该部门年度目标 ÷ 其月合计` 等比缩放月形，使 12 个月合计 == `kpiDashboard.total_target`。产物两份：
- `数据/月度目标_2026统一.json` —— 含 depts / 缩放系数 / 来源说明（可追溯）
- `数据/year_targets_2026.json` —— 看板直读形状 `{monthly:[12], annual, depts, source, note}`

图表取 `d.yearTargets2026.monthly`，**取不到才退回旧的两段拼接**（降级路径必须保留，否则数据未到就白屏）。

## 派生键防回归（必做）

往 `workbench_data.json` 注入任何新键后，**必须同步把键加进 `脚本/merge_workbench_data.py` 的 `FILES` 映射**（形如 `'yearTargets2026': 'year_targets_2026.json'`）。否则下次管道重跑会重建 `workbench_data.json` 并**静默丢掉该键**，页面悄悄退回旧口径——不报错、不告警。改完立刻重跑一次 `merge_workbench_data.py` 并断言键仍在。

改「数据驱动」的视图要**连 JSON 一起部署**（`tcb hosting deploy 数据/workbench_data.json 数据/workbench_data.json`）；只发 HTML 不够，线上仍是旧数。
