Files
yunke/docs/superpowers/plans/2026-09-08-management-batch-a-verification.md
T
浅念 9fbd572bb7 docs(management): 批次B终评收尾——validate_config补源名唯一性+遗留观察项归档
批次B终评Approve后两处闭环:
- validate_config补源指标名跨维度唯一校验(comparisons按源名建键的
  静默覆盖防线)+测试
- 观察项归档:call_band样本基数(批次F)/源名唯一(已修)/性能与winsorize(批次F)
2026-09-09 19:28:48 +08:00

306 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 批次A Task 0 核实记录:五项核实 + 回测名单
- **日期**:2026-09-09
- **方式**:`docker exec -i yunke-mysql-1 mysql -uyunke -pyunke yunke -e "<SQL>"`(容器名以 `docker ps` 实测为准,确认为 `yunke-mysql-1`)
- **用途**:本记录的全部结论直接约束批次A(数据底座)后续任务的实现细节;偏差处显式标注「影响 Task N」。任务编号引用 [2026-09-08-management-console-batch-a-data-foundation.md](./2026-09-08-management-console-batch-a-data-foundation.md)。
- **数据快照范围**:`stat_daily_staff` / `msg_daily_stats` 覆盖 20251024 ~ 20260907;`phone_call.start_call_time` 覆盖 2025-10-18 ~ 2026-09-05。
---
## ① 员工主数据(staff 表)
| 指标 | 实测 |
|---|---|
| staff 总数 | **41**(与计划假设 ≈41 一致) |
| `is_on_job=1` | 41(全部在职) |
| `wechat_account` DISTINCT user_id | 41 |
| `whatsapp_account` DISTINCT user_id | 20 |
| 微信幽灵账号(user_id 不在 staff) | **0** |
| WA 幽灵账号(补充核实) | **0** |
| `stat_daily_staff` DISTINCT user_id | 40(其 user_id 全部在 staff 内;员工 `FD685A5620184156BE75A8D73FB72F64` 无任何消息统计行) |
**结论**:账号表 → staff 无幽灵映射,`known_user_ids` 过滤(v2 §1.3 #11)零损耗,不会意外丢行。两个渠道的 account_map 产出 user_id 均能落到 staff。**无偏差,Task 5/6 按计划实现即可。**
---
## ② phone_call 列与单位
表结构关键列:`call_id`(PK)、`channel`、`start_call_time`(bigint)、`call_duration`(int)、`call_status`(int)、`incoming_call`(int)、`user_id`、`call_number`、`fetched_at`、`updated_at`。
### channel 分布
| channel | 行数 |
|---|---|
| `voice_call` | 2,504 |
| `wa_voice_call` | 686 |
| 合计 | **3,190**(与已知 3,190 条一致) |
### call_duration 单位:**秒**(确认)
| 指标 | 实测 |
|---|---|
| 总和 | 263,651 秒 = **73.24 小时**(对照已知约 73 小时,吻合) |
| 均值 | 82.65 秒(对照预期均值 ≈82 秒,吻合) |
### call_status × duration 交叉
| call_status | 行数 | duration=0 | duration>0 | 语义判定 |
|---|---|---|---|---|
| 1 | 1,508 | 31 | 1,477 | **接通**(含 31 条 0 秒即挂) |
| 2 | 1,682 | 1,682 | 0 | **未接通**(未接/拒接/取消,与方向无关) |
### incoming_call 分布
| incoming_call | 行数 | 语义 |
|---|---|---|
| 1 | 1,764 | 呼入 |
| 0 | 1,426 | 呼出 |
### start_call_time:**毫秒 epoch**(确认)
13 位整数;`FROM_UNIXTIME(x/1000)` 解出 max = 2026-09-05 16:00:55(合理近期),按秒解释则超范围。min = 2025-10-18。与 `message.timestamp` 毫秒同口径。
### call_number 形态(按 channel 完全可分)
- `voice_call` → 微信 ID:`wxid_*` 或自定义微信号(`csf7169708`、`yuanye751380`、`Maple_1988` 等)
- `wa_voice_call` → WA JID:`2348182388888@s.whatsapp.net` 等
### 与计划默认假设的偏差(重要)
1. **计划假设 #4「PhoneCall.channel 是通话类型(phone_call/voice_call)非消息渠道」不完全成立**:实测无 `phone_call`(蜂窝)值,且 `wa_voice_call` **能**从 channel 列判别 WA。计划 Task 5 注释「voice_call 行无法从列判别微信/WA」不成立。**影响 Task 5**:`_followup_exists` 可按渠道收窄——`voice_call` 只查该员工 wechat 账号的 1:1 消息、`wa_voice_call` 只查 whatsapp 账号(当前双渠道任一匹配的写法也不会错——wxid 不会撞 WA JID,仅多一次无效查询;收窄属优化非必需)。
2. `call_duration` 单位=秒、`start_call_time`=毫秒 epoch:**与计划默认一致,Task 5 无需换算**(`day_bounds_ms` 直接用)。
3. 接通判定:计划默认 `duration>0`。实测 `call_status=1 AND duration>0` = 1,477 行,`duration>0` 口径与之差 31 行(31 条接通但 0 秒,占 1.0%)。**影响 Task 5**:`answered=dur>0` 口径可用;若追求语义精确可改 `call_status=1`(含 0 秒接通),差异 ≤1%,实现时任选其一并在表注释写明。
---
## ③ customer 归属字段填充率
| 指标 | 实测 |
|---|---|
| 总行数 | 16,339 |
| `user_id` 填充 | 16,339(**100%**) |
| `owner_account_id` 填充 | 16,339(**100%**) |
| `delete_flag=1` | 53 |
分渠道:wechat 7,040(owner 填充 7,040)/ whatsapp 9,299(owner 填充 9,299)。
### 两条归属路径一致性(全表校验,非抽样)
`customer.user_id` vs `owner_account_id → wechat_account.wx_id / whatsapp_account.uid → user_id`:
| 渠道 | 校验行数 | 一致 |
|---|---|---|
| whatsapp | 9,299 | 9,299 |
| wechat | 7,040 | 7,040 |
**结论**:两条路径 100% 等价、零冲突。生命周期归属覆盖面完整。**影响 Task 6**:默认 `owner_account_id → account_map → user_id` 路径确认可用;`customer.user_id` 列可直接当冗余等价路径使用(如需省一次 join,实测安全)。
补充观察(**影响 Task 3/6 的 peer 对齐**):**微信侧主体是无后缀裸 ID,后缀形态是少数**。`customer.friend_id` 实测形态分布(全表):
| 渠道 | 形态 | 行数 | 占比 |
|---|---|---|---|
| wechat | **无后缀裸 ID**(`a_515855826`、`a053136a` 等) | 6,779 | **96.3%** |
| wechat | `@openim` 后缀 | 261 | 3.7% |
| whatsapp | `@s.whatsapp.net` | 9,266 | 99.6% |
| whatsapp | `@lid` 后缀 | 33 | 0.4% |
`message.talker_id` 侧同查(行数口径):wechat 裸 ID 844,287 / `@openim` 16,873 / `@chatroom` 17,003(群);whatsapp `@s.whatsapp.net` 72,357 / `@lid` 4,284 / `@g.us` 9,439(群)。即:微信 peer 主键以裸 ID 为准、`@openim` 为少数共存;WA 侧基本全带后缀。Task 3 实现 peer 匹配时按实测形态对齐(两边原值相等才命中),不做猜测性截断/补后缀。
---
## ④ 群占比(msg_daily_stats,量化混合口径偏差)
| channel | peer_kind | 行数 | 消息量(send+recv) | 会话线程(cust_threads) |
|---|---|---|---|---|
| wechat | friend | 38,343 | 269,074 | 82,879 |
| wechat | room | 16,234 | 609,089 | 26,220 |
| whatsapp | friend | 11,673 | 77,994 | 14,178 |
| whatsapp | room | 866 | 8,086 | 1,402 |
| **合计** | | **67,116** | **964,243** | **124,679** |
占比汇总:
| 维度 | room 占比 | 说明 |
|---|---|---|
| 微信消息量 | **69.4%**(609,089/878,163) | 微信消息近七成来自群 |
| WA 消息量 | 9.4%(8,086/86,080) | WA 几乎纯 1:1 |
| 总消息量 | **64.0%**(617,175/964,243) | |
| 总会话线程 | **22.2%**(27,622/124,679) | |
**结论**:若现有看板首响/回复率不分 `peer_kind`,消息量维度被群污染 64%、线程维度 22%——污染主要来自微信侧(微信消息 69% 是群消息)。批次A `first_reply_fact` / `employee_metric_daily` 强制 `peer_kind='friend'` 过滤是必要的,不能沿用混合口径。
---
## ⑤ call_followup join 键(call_number ↔ message.talker_id,1:1 消息)
| channel | 分母(call_number 非空) | 命中(talker_id 匹配且非群消息) | 命中率 |
|---|---|---|---|
| `voice_call` | 2,504 | 2,378 | **95.0%** |
| `wa_voice_call`(补充核实) | 686 | 681 | **99.3%** |
**结论**:双渠道命中率均远超 50% 阈值,`call_followup_count` 一期**高置信**,无需降级标记、无需在口径中注记低置信。**影响 Task 5**:24h 跟进判定按计划实现。未命中成因已精确解释(复核补充):`voice_call` 的 126 条未命中**恰好等于** `call_number` 带 `@chatroom` 后缀的 126 条群通话(实测这 126 条对 1:1 消息全部 0 命中——join 限定 `room_id` 为空,群通话本就不该匹配);剔除群通话后 1:1 可匹配通话命中率为 **2,378/2,378 = 100%**。`wa_voice_call` 侧后缀分布为 `@s.whatsapp.net` 676 / `@g.us`(群)9 / `@lid` 1,未命中 5 条**全部为** `@g.us` 群通话(逐条列出验证)。
---
## 回测员工名单
选取依据:消息量/WA 量/活跃群排名 + 低样本端(见文末各候选分渠道数据)。覆盖:微信为主、WA 为主、群运营为主、高负载、低样本(前三类与高负载有合并)。
| # | user_id | 名字 | 覆盖类型 | 关键数据(20251024~20260907) |
|---|---|---|---|---|
| 1 | `369D257EEC9A4357935FDE19DDD99CAA` | 王云青 | **高负载 + 群运营为主 + 微信为主**(合并) | 消息量 #1(162,740:微信 162,371 / WA 369);活跃群 #1(4,036);通话 431+9 |
| 2 | `9C0B195A2C234C829FF507E011B4B460` | 田瑞志 | **WA 为主** | WA 消息 9,986 / 总 11,597 = 86%;WA 通话 28 |
| 3 | `F99F6557C2904A7E8F4C5A3BD8A597A1` | 张士蓓 | **双通道混合**(WA 量 #1,群 #2) | 微信 91,936 + WA 12,529;活跃群 2,273;通话最多(471 微信 + 97 WA) |
| 4 | `DC48798A5EA24DBB8B31A89CB858E08B` | 15639270965 | **纯微信无 WA 行**(验证 WA 缺失路径) | 微信 78,680,`stat_daily_staff` 无 whatsapp 行;通话 3 |
| 5 | `946C08639BF84DFEBBF5EC2A607AE1FA` | 18539430257 | **低样本**(验证空数据路径) | 消息 8 条,无群、无通话、无 WA |
> 名单中的 #1/#3 建议作为 Task 8「本地全量回填实测」的 2 名回测员工(数据最厚、双通道+通话+群全齐)。
---
## 偏差汇总(对后续任务的约束清单)
| # | 偏差/确认 | 影响 |
|---|---|---|
| 1 | `phone_call.channel` 实测仅 `voice_call`(微信)/`wa_voice_call`(WA)两值,**无蜂窝 `phone_call` 值**;channel 可判别渠道,计划「无法从列判别微信/WA」结论修正 | Task 5(followup 可按渠道收窄,属优化非必需) |
| 2 | `call_duration` 单位=**秒**(263,651s=73.24h≈已知 73h);`start_call_time`=**毫秒 epoch** | Task 5(默认口径正确,无需换算) |
| 3 | 接通判定 `dur>0` vs `call_status=1` 差 31 行(1.0%),前者少计 31 条 0 秒接通 | Task 5(任选其一,注释写明) |
| 4 | `customer.owner_account_id` 填充 **100%**,与 `user_id` 列全表 100% 等价 | Task 6(默认 owner 路径确认;可直接用 user_id 列省 join) |
| 5 | 微信侧 peer ID **主体为无后缀裸 ID**(customer.friend_id 6,779/7,040=96.3%;message.talker_id 行数口径 844,287 裸 vs 16,873 `@openim`),后缀形态(`@openim` 等)为少数;WA 侧基本全带后缀(`@s.whatsapp.net`/`@lid`) | Task 3/6(peer 对齐按实测形态原值匹配,勿截断/勿补后缀) |
| 6 | 群消息占总消息量 64%(微信侧 69%) | Task 3/6(friend 过滤必须,佐证 spec 口径) |
| 7 | call_number↔talker_id 命中率 voice_call 95.0% / wa_voice_call 99.3% | Task 5(call_followup_count 高置信,不降级) |
| 8 | staff 41 行无幽灵账号;`stat_daily_staff` 仅 40 名员工有行 | Task 5/6(known_user_ids 过滤零损耗) |
---
# 批次A回填实测与对账(Task 8,2026-09-09)
- **环境**:本地 Docker MySQL(`yunke-mysql-1`),数据快照同上(stat/msg 覆盖 20251024~20260907)。
- **方式**:Task 7 真机全量回填后本任务只读对账抽查(未重跑回填);对账前全库同源校验复验一次,确认库状态即 Task 7 收尾状态。
## 1. 回填实测汇总(Task 7 真机数字,Task 8 复验)
| 项目 | 实测 |
|---|---|
| `first-reply-facts` | 11,020 会话 → **68,476** fact 行,**72s**(冷) |
| `employee-metrics` | **12,371** 宽表行(day×channel×user)+ **1,163** 通话行(**314** 天并集),**71s** 冷 / 36s 热 |
| 幂等复跑 | 两命令复跑结果一致(delete+insert 语义,无重复无漂移) |
| 全库同源 | `SUM(employee_metric_daily.threads_answered)` = **68,476** ≡ fact 行数(Task 8 复验通过) |
| 孤儿检查 | 宽表/通话表 user_id 不在 staff = **0**;fact 账号不在对应渠道账号表 = **0**(Task 8 复验通过) |
| 通话日并集修复 | `employee_call_daily` 覆盖 **314** 天(修复前 306):8 个消息空窗日找回 27 通话 / 13 接通 |
## 2. 对账抽查(2 员工 × 3 日期 × 三组)
员工取自本文回测名单:**王云青** `369D257EEC9A4357935FDE19DDD99CAA`(#1,高负载+群运营,混合口径差异最大场景;1 微信 + 2 WA 账号)、**田瑞志** `9C0B195A2C234C829FF507E011B4B460`(#2,WA 为主;1 微信 + 2 WA 账号)。日期 20260815 / 20260801 / 20260601(近/中/远)。跨渠道员工按 channel 分别对账(①②组逐渠道逐日须一致)。
判定标准:**①组(消息计数列 ↔ msg_daily_stats friend 聚合)与 ②组(串类列 ↔ fact 行数)必须逐日一致;③组(宽表串类 cust_threads ↔ 日切 stat_daily_staff.cust_threads)允许差异,差值=跨夜串归属效应。**
### 王云青
| 渠道 | 日期 | ① mds sent/recv | ① 宽表 sent/recv | ① 判定 | ② fact 行 | ② 宽表 answered | ② 判定 | ③ 宽表 threads | ③ 日切 threads | ③ 差值 |
|---|---|---|---|---|---|---|---|---|---|---|
| wechat | 20260601 | 186 / 177 | 186 / 177 | ✅ | 76 | 76 | ✅ | 76 | 121 | 45 |
| wechat | 20260801 | 0 / 38 | 0 / 38 | ✅ | 0 | 0 | ✅ | 0 | 16 | 16 |
| wechat | 20260815 | 0 / 23 | 0 / 23 | ✅ | 0 | 0 | ✅ | 0 | 17 | 17 |
| whatsapp | 20260601 | (无行) | 0 / 0 | ✅ | (无行) | 0 | ✅ | 0 | (无行) | — |
| whatsapp | 20260801 | 0 / 1 | 0 / 1 | ✅ | 1 | 1 | ✅ | 1 | 1 | 0 |
| whatsapp | 20260815 | 0 / 4 | 0 / 4 | ✅ | 1 | 1 | ✅ | 1 | 2 | 1 |
### 田瑞志
| 渠道 | 日期 | ① mds sent/recv | ① 宽表 sent/recv | ① 判定 | ② fact 行 | ② 宽表 answered | ② 判定 | ③ 宽表 threads | ③ 日切 threads | ③ 差值 |
|---|---|---|---|---|---|---|---|---|---|---|
| wechat | 三日期均 | (无行) | 0 / 0 | ✅ | (无行) | 0 | ✅ | 0 | (无行) | — |
| whatsapp | 20260601 | 1 / 1 | 1 / 1 | ✅ | 1 | 1 | ✅ | 1 | 1 | 0 |
| whatsapp | 20260801 | (无行) | 0 / 0 | ✅ | (无行) | 0 | ✅ | 0 | 2 | 2 |
| whatsapp | 20260815 | (无行) | 0 / 0 | ✅ | (无行) | 0 | ✅ | 0 | (无行) | — |
注:「无行」= 源表该 (渠道, 日) 无该员工数据,宽表记 0/0——一致。20260601 无 WA 数据未换日期:田瑞志 whatsapp 该日恰好有 1 条收发(对账有效),王云青 whatsapp 全期仅 369 条消息、无行属真实分布。
**结论:①②组 12/12 逐日全部一致(含渠道拆分);③组宽表全部 ≤ 日切值。**
## 3. 跨夜串差值观察(③组差异解释)
- **归属语义**:宽表串按「串末条客户消息所在上海自然日」归属(`first_reply_facts.compute_conversation`);日切表按「当日有客户消息的 peer 数」计。串末条必在归属日当天 ⇒ 结构上保证 ③组逐日 宽表 ≤ 日切,差值=归属到其他日终了的跨夜/多日串。
- **差值形态**:王云青 wechat 三日差 45/16/17——该员工深夜消息多(20260801/15 发送 0、仅收 38/23 条,客户消息跨零点后串归属顺延);whatsapp 差 0/1。田瑞志 whatsapp 20260801 差 2(2 个 peer 当日来消息但串末条在次日)。
- **串未丢失验证**:王云青 wechat 全期 宽表 12,055 threads(11,930 answered,314 天)vs 日切 20,981(14,647 answered,312 天)——串模型把多日串折叠为 1(比值 57.4%),且抽查 20260814~31 逐日,日切 17 线程的 20260815 对应串终落在真实结束日(0819=8、0821=10、0828=9),无凭空丢失。
- 另:宽表 `cust_threads ≥ threads_answered`(如王云青 wechat 20260602 = 54/52)——未闭合尾串计入分母不产 fact 样本,与算法注释一致。
## 4. 批次A验收清单(Task 8 收尾核对)
| # | 项 | 状态 | 依据 |
|---|---|---|---|
| 1 | 6 张表本地存在,migration 幂等 + downgrade 干净 | ✅ | Task 2(迁移 `b7c8d9e0f1a2`,alembic head 实测一致;表实测在库:first_reply_fact / employee_metric_daily / employee_call_daily / employee_score_config / employee_score_snapshot / management_followup) |
| 2 | first-reply-facts 全量回填成功且复跑幂等 | ✅ | Task 7(本文 §1) |
| 3 | employee-metrics 全量回填成功且复跑幂等 | ✅ | Task 7(本文 §1) |
| 4 | 跨夜串判别测试通过 | ✅ | Task 3(test_first_reply_facts.py 恰 12 例,Task 8 复跑全绿;含跨夜/多日串/同毫秒并列) |
| 5 | friend-only 口径判别测试通过 | ✅ | Task 6(test_all_friend_pairs_only_friend + 宽表 friend 过滤判别例) |
| 6 | 同源判别测试通过 + 全库 SUM 验证 | ✅ | Task 3/6;全库 68,476 ≡ 68,476(本文 §1 复验) |
| 7 | 幽灵员工过滤测试通过 | ✅ | Task 5/6;孤儿=0(本文 §1 复验) |
| 8 | 对账抽查 ①②一致、③差异可解释 | ✅ | 本文 §2/§3 |
| 9 | 全量测试套件绿 | ✅ | **653 passed in 62s**(Task 8 本机实跑,2026-09-09) |
| 10 | 核实文档完整 | ✅ | 本文补齐 |
| 11 | 写钩子零改动 | ✅ | 批次A全部 17 个 commits(27f91e5→0fce21a)逐一核查,无一触碰 storage/summary.py、storage/repos.py(repos.py 最近一次改动 d428953 属 customer_account_link 工作线,先于批次A) |
---
# 批次B冒烟(Task B5,2026-09-09)
经营管理后台六端点真机冒烟 + 性能初测。回测员工王云青 `369D257EEC9A4357935FDE19DDD99CAA`(#1 高负载+群运营),窗口 30 天 `20260810~20260908`。
## 0. 环境与启动方式
- **基线**:`pytest tests/ -q` → **721 passed in 60.9s**(批次B收尾后全绿)。
- **后端启动**:docker 无 `yunke-backend/frontend-1` 容器;冒烟前 8000 端口空闲(连接拒绝,无常驻进程)。本任务自起:`yunke-server/.venv/Scripts/python.exe -m uvicorn app.api.main:app --port 8000`(无 `--reload`),`GET /api/health` → `{"status":"ok"}`;`openapi.json` 含 management 路由(grep 命中)确认新代码已加载。
- **鉴权模式**:`.env` 中 `API_TOKEN=`(空值)→ 鉴权中间件 no-op,**免鉴权直接 curl**(无 Authorization 头)。
- **进程处置**:uvicorn 留跑供用户查看,Git Bash PID **2423**(`kill 2423` 关闭;或任务管理器结束对应 python.exe)。
## 1. 六端点结果(HTTP 码 × 耗时 × 大小)
首次访问含冷启动(连接池/查询编译/InnoDB 冷缓存),dashboard 另测暖态三次:
| 端点 | HTTP | 首次耗时 | 暖态耗时 | 大小 | 判定 |
|---|---|---|---|---|---|
| `/api/management/dashboard` | 200 | 12.03s | **2.92 / 2.39 / 2.18s** | 22,309B | ⚠️ 超 1.5s 目标(慢段定位见 §2) |
| `/api/management/employees?page_size=50` | 200 | 0.34s | 0.30 / 0.31s | 58,865B | ✅ ≤1.5s |
| `/api/management/employees/{王云青}` | 200 | 0.40s | — | 4,739B | ✅ |
| `…/metrics?section=service` | 200 | 0.40s | — | 4,655B | ✅ |
| `…/drilldown?metric_code=first_reply_p90` | 200 | 0.016s | — | 5,268B | ✅ |
| `/api/management/metric-definitions` | 200 | 0.005s | — | 4,081B | ✅ |
## 2. dashboard 慢段定位(分段实测,不做优化——决策归批次F)
用 app 同一 session 复放 dashboard 组装流水线逐段计时(合计 2.685s ≈ 暖态端点耗时):
| 段 | 耗时 |
|---|---|
| load_score_config | 0.044s |
| assemble_employees(评分+异常,2 窗聚合) | 0.262s |
| wide_daily_rows / call_daily_rows | 0.007s / 0.002s |
| service_daily_trend | 0.032s |
| data_as_of | 0.009s |
| **action_counts(unreplied+churn+sensitive)** | **2.328s** |
action_counts 内部:`unreplied(hours=2)` **2.38s**;`churn(real,30d)` 0.12s;sensitive count 0.005s。unreplied 再拆:候选窗口函数查询(row_number over 67,116 行 friend 分区 → 3,123 候选)仅 **0.10~0.16s**;>500 候选走「仅本页决赛圈」分支无 N+1,但**单条尾部消息点查耗时 1.7~1.9s**。EXPLAIN 实证:`channel=None`(全平台)时无法使用 channel 打头的全部索引(`ix_message_channel_owner_talker_time(channel,owner,talker,timestamp)` 等),优化器退化到 `ix_message_room`(`room_id IS NULL/''`)→ **扫 378,202 行 + filesort**(message 表 96.4 万行)。
优化方向(供批次F选择):① 查询改写按渠道 UNION/`channel IN (…)` 绑定前缀走现有索引;② 补 `(owner_account_id, talker_id, timestamp)` 无 channel 前缀索引;③ dashboard 对 action_counts 做短 TTL 缓存。除 unreplied 外全链路 <0.5s,达标无压力。
## 3. 合理性抽查
- **employees 列表**:`total=41`/rows=41(=本文① staff 总数 ✓)。**王云青行与宽表 SQL 聚合逐字段全等**:msg_sent=111 / msg_recv=1003 / cust_threads=89 / threads_answered=57 / fr_sum_ms=698,140,844、days_with_data=29。数量级说明:「千级」预期对应全期全口径(全期 162,740 条消息/12,055 串,群聊为主);本窗 friend 口径 recv=1003 千级 ✓,sent=111 为真实分布——同窗全口径 recv≈2.0 万(stat_daily_staff 实测) vs friend 1,003,差额几乎全是群消息;cust_threads=89 是串模型多日串折叠(本文 §3 已验证折叠比 57.4%)+本窗 friend 活动偏低。
- **dashboard kpis**:active_employees=**40**(~40 ✓,=本文① stat 有数员工数)、active_customers=2,195、msg_total=42,422、cust_threads=9,330、answered=8,460、call 263/接通 113;lifecycle new/cooling/dormant/reactivated=231/1,432/3,353/145。**anomalies=28(critical 3 / warning 25)**,首条 `reply_p90_worsened` critical:P90 324.98min vs 基线期 18.06min(+1700%,样本 62、涉及 51 客户)。dimension_summary:risk/action 全 41 pending(批次C接线,符合预期),service scored 27/insufficient 4/pending 10、均分 56.0。趋势 30 点,末日 20260908 全零——数据止于 20260907(本文快照范围),边界正确。
- **detail(王云青)**:`score.trial=true` ✓、grade=**E**(total=39,scored 非 insufficient)、dimensions:risk/action **pending** ✓(批次C)、service 47.8 / customer 0.0 / workload 100;含 accounts/prev/baseline/anomalies。
- **metrics section=service**:数值与宽表窗聚合一致 + trend 30 点 ✓。
- **drilldown first_reply_p90**:total=**57**=该员工账号窗内 fact 行数(=threads_answered ✓)。首行 fr_ms=**498,242,238ms≈5.8 天**——超「分钟~小时」预期,但与源表全等(fact MAX 同值);窗内全库更极端值达 1,617,406,247ms≈18.7 天(thread_end 08-14 → reply 09-02,沉睡客户复活串),属真实数据形态非计算缺陷。该员工 p50=97,153ms(1.6min)/p90=1,651,758ms(27.5min)分钟级 ✓。极值是否 winsorize/封顶归批次F口径决策。
- **metric-definitions**:19 项定义(msg_sent…call_followup_rate)+ score_config + caliber_notes,`config_source="db"`(评分配置 v1.0 种子生效 ✓)。
**结论:六端点全 200、数据与库逐字段可对;唯一性能缺口=dashboard 的 unreplied 行动计数(channel 无前缀索引可用),暖态 2.2~2.9s,其余端点全部达标。**
## 4. 批次B遗留观察项归档(终评补录,2026-09-09)
| # | 项 | 归档 |
|---|---|---|
| 1 | call_band 样本基数:band 类指标样本量取 active_friends(规格字面),WA 通话量上来后"通话多客户少"员工会被误标 insufficient——当前数据零影响(实测 calls≥1 且 af<10 仅 5 人且 calls≤5,怎么选都不足);归批次F回测前一行修正(sample_basis 字段或按源指标取样本) |
| 2 | 配置源名唯一性:两个 code 指向同一源指标会使 employees comparisons 键静默覆盖——已在 validate_config 加源名唯一校验+测试(批次B终评闭环),默认配置六个源名互异不受影响 |
| 3 | dashboard 性能(2.2~2.9s,unreplied channel=None 无前缀索引)与 fr_ms 天级极值 winsorize:见上文 §2/§3,决策归批次F |