Co-Authored-By: Claude Code <noreply@anthropic.com>
33 KiB
批次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。
- 数据快照范围:
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等
与计划默认假设的偏差(重要)
- 计划假设 #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,仅多一次无效查询;收窄属优化非必需)。 call_duration单位=秒、start_call_time=毫秒 epoch:与计划默认一致,Task 5 无需换算(day_bounds_ms直接用)。- 接通判定:计划默认
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:
| 渠道 | 校验行数 | 一致 |
|---|---|---|
| 9,299 | 9,299 | |
| 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 实测形态分布(全表):
| 渠道 | 形态 | 行数 | 占比 |
|---|---|---|---|
无后缀裸 ID(a_515855826、a053136a 等) |
6,779 | 96.3% | |
@openim 后缀 |
261 | 3.7% | |
@s.whatsapp.net |
9,266 | 99.6% | |
@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) |
|---|---|---|---|---|
| friend | 38,343 | 269,074 | 82,879 | |
| room | 16,234 | 609,089 | 26,220 | |
| friend | 11,673 | 77,994 | 14,178 | |
| 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 | ③ 差值 |
|---|---|---|---|---|---|---|---|---|---|---|
| 20260601 | 186 / 177 | 186 / 177 | ✅ | 76 | 76 | ✅ | 76 | 121 | 45 | |
| 20260801 | 0 / 38 | 0 / 38 | ✅ | 0 | 0 | ✅ | 0 | 16 | 16 | |
| 20260815 | 0 / 23 | 0 / 23 | ✅ | 0 | 0 | ✅ | 0 | 17 | 17 | |
| 20260601 | (无行) | 0 / 0 | ✅ | (无行) | 0 | ✅ | 0 | (无行) | — | |
| 20260801 | 0 / 1 | 0 / 1 | ✅ | 1 | 1 | ✅ | 1 | 1 | 0 | |
| 20260815 | 0 / 4 | 0 / 4 | ✅ | 1 | 1 | ✅ | 1 | 2 | 1 |
田瑞志
| 渠道 | 日期 | ① mds sent/recv | ① 宽表 sent/recv | ① 判定 | ② fact 行 | ② 宽表 answered | ② 判定 | ③ 宽表 threads | ③ 日切 threads | ③ 差值 |
|---|---|---|---|---|---|---|---|---|---|---|
| 三日期均 | (无行) | 0 / 0 | ✅ | (无行) | 0 | ✅ | 0 | (无行) | — | |
| 20260601 | 1 / 1 | 1 / 1 | ✅ | 1 | 1 | ✅ | 1 | 1 | 0 | |
| 20260801 | (无行) | 0 / 0 | ✅ | (无行) | 0 | ✅ | 0 | 2 | 2 | |
| 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_worsenedcritical: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 |
批次D走查与验收(Task D4,2026-09-10)
批次D收尾:员工总览(b72e957)+ 员工详情(5776175)+ 驾驶舱环比/异常跳员工闭环(422a726,前置接口/时间预设 ddeda3f)。Playwright 真机七步全链路走查 + v1 §19.1 员工子集与批次D验收清单逐项核对。环境:后端 uvicorn 8000(免鉴权)+ 前端 vite dev 5173,视口 1366px。
0. 四门禁(全绿)
| 门禁 | 结果 |
|---|---|
后端 pytest tests/ -q |
723 passed in 65.29s |
前端 npm run build |
✓ built in 1.03s(exit 0) |
前端 npm run test(vitest) |
Test Files 2 passed / Tests 23 passed |
1. 七步动线走查记录(数字为真机实测,关键值已对 API 复核)
① 驾驶舱默认近30天(20260812~20260910):三 KPI 卡环比箭头 ✓(客户端自 prev_kpis 按 (cur-prev)/prev 计算,API 复核数值吻合)——全渠道:活跃客户 1,992人 ↓12.6%(prev 2,278)/ 活跃员工 40人 →0.0%(40/40)/ 消息量 37,742条 ↓21.0%(prev 47,749);切微信:979人 ↓25.3% / 40人 ↑2.6% / 27,949条 ↓29.6%。方向与量级合理(等长紧邻前窗)。
② 异常卡员工名 → 员工详情:点微信异常榜员工名链接(落点须为 a.el-link;整行点击触发行下钻抽屉,属预期交互)→ 落地 /management/employees/18B5EE6B1AD94B15A5CD1CDABCECAB96?channel=wechat&start=…&end=…(驾驶舱当前筛选 query 随行 ✓)。
③ 员工详情(朱晨曦,综合 21/E):五维条 3 实 + 2 待数据——合规风险/行动执行 pending 灰条占位,服务质量 0 分/客户经营 0 分/有效负载 100 分(API dimensions 逐项一致;行序随接口返回序,见遗留①)。四对比表(当前/上期/基线/中位)✓(cust_threads 62/114/107+中位)。服务质量页签双折线渲染(canvas 1074×300)→「查看超时明细」→ 首响P90 抽屉 Total 11 → 点行进聊天:17 条消息,时间线与所点明细行吻合(客户 08-31 11:59:12 → 回复 17:24:11 ≈ 5小时24分)。
④ 客户经营页签:返回列表再进 → section=customer →「沉睡客户名单」抽屉 Total 89 ✓。
⑤ 员工总览对比:勾选 2 人 → 对比抽屉并列两列指标 ✓(2-5 人为按钮启用门槛),关闭复原。
⑥ 排序+翻页+CSV(微信渠道):综合分降序(服务端 sort)——第 1 页 55→21(林峰 55 领头),第 2 页 11 / 1 / 其余「--」(18 人 insufficient_sample 空值排尾 ✓)。导出 CSV:3 行口径前言(口径/窗口+渠道/试算版本)+ 表头 + 41 行数据,toast「已导出 41 条」。
⑦ 控制台与持久化:走查全程 console 0 errors;修复遗留告警后(见 §3)全新加载 0 warnings。刷新保持 ✓——列表 view=service 刷新还原(Total 41)、详情返回后 section=service 还原。
附:9 档时间预设逐一真机核验:今日 09100910 / 昨日 0909 / 近7 09040910 / 近30 08120910 / 本周 09070910(周一周首 ✓)/ 上周 08310906 / 本月 09010910 / 上月 08010831 / 自定义 09010907。1366px 驾驶舱与员工总览双页截图目检:KPI 3×2 网阵、双趋势图、表格列均无塌陷/无横向溢出。
2. 验收清单核对
v1 §19.1 员工子集(6 项)
| # | 项 | 状态 | 依据 |
|---|---|---|---|
| 1 | 能查看所有员工多维状态 | ✅ | total=41(API=列表=CSV 行数三方一致);四视图(管理概览/工作负载/服务质量/客户经营)切换正常 |
| 2 | 任一分数可展开为指标/公式/样本/事实 | ✅ | 表头「?」→ 指标口径抽屉(19 项定义+公式+样本);行/详情下钻按钮 → 事实明细 → 聊天原文(§1 ③) |
| 3 | 四对比 | ✅ | 详情四对比表逐列可见(§1 ③:62/114/107/中位) |
| 4 | 样本不足不评分 | ✅ | 18 人 insufficient_sample 综合分显「--」(§1 ⑥),CSV 同口径 |
| 5 | 异常两次点击进事实 | ✅ | 异常卡员工名(第1击)→ 详情下钻按钮(第2击)→ 事实明细/聊天 |
| 6 | 默认排序非总分/消息量榜 | ✅ | 默认 sort_by=anomaly_severity(异常严重度优先) |
批次D验收清单(5 项)
| # | 项 | 状态 | 依据 |
|---|---|---|---|
| 1 | 员工总览:41 人四视图/服务端排序分页/keyword/对比 2-5 人/CSV 含口径注释 | ✅ | §1 ⑤⑥;CSV 前言 3 行 |
| 2 | 员工详情:五维条 3+2 占位/五页签懒加载/四对比/评分明细可展开/下钻进抽屉/账号跳聊天 | ✅ | §1 ③④;页签懒加载+loaded 缓存+section URL 还原 |
| 3 | 驾驶舱:三 KPI 环比箭头/异常卡可跳员工详情 | ✅ | §1 ①②(闭环打通) |
| 4 | 时间预设全套 9 档 | ✅ | §1 附 |
| 5 | URL 全参数保持;四门禁绿;后端仅 dashboard 一处小改且测试补齐 | ✅ | §1 ⑦ + §0;后端改动=dashboard prev_kpis(D3,随测试提交) |
3. 走查发现并修复(随本任务提交)
| 问题 | 修复 |
|---|---|
AnomalyList.vue 员工链接 :underline="false" 触发 Element Plus 废弃 API 告警——驾驶舱每次加载刷 31 条 [el-link] underline boolean deprecated(异常榜每行一条) |
改 underline="never"(string API);修复后全新加载 0 errors 0 warnings |
4. 遗留观察项(不阻塞)
| # | 项 | 归档 |
|---|---|---|
| 1 | 五维条行序随接口返回序 | 合规风险/行动执行(待数据)排在三实维之前——非固定语义序;批次E 接线 risk/action 后自然消解,如需固定序前端按 dim 顺序数组排即可 |
| 2 | 走查任务字面 Step6 措辞偏差 | 「切视图服务质量→点综合分列排序」按字面不可执行(综合分列仅管理概览视图有);实际动线=管理概览排序→切服务质量视图导出,已按实际动线完成走查 |
| 3 | 空值显示 - 与 -- 混用 |
format.ts 的 formatMsDuration/formatRatio 按各自注释约定 null 显 -,员工总览本地数值/比率格式化显 --——同表跨列观感不一;纯观感项,批次E 顺手统一即可 |
批次F-调度接入验证(Task F3,2026-09-10)
F1(_analytics_tick 段2 管理域滚动,0dc5993)/ F2(夜间链管理步,2ac63e3)已提交后,本地真机直调 tick 验证 + 本地 scheduler 镜像重建。方法:临时脚本(已删)按 tests/test_scheduler_analytics_tick.py 的构造方式 SyncScheduler(engine=create_async_engine(get_settings().db_url), session_maker=...),不 start、只 await scheduler._analytics_tick(),对六个上游函数挂计时包装(与测试 monkeypatch 同一延迟 import 打点位),连跑两次。
1. tick 两段实测(本地真机库,连跑两次)
| 段 | 内容 | 第1次(冷) | 第2次(暖) |
|---|---|---|---|
| 总计 | _analytics_tick 全程 |
2.78s | 0.74s |
| 段1 | recompute_days(staff 滚动3天) | 0.86s | 0.15s |
| 段1 | scanner.run_incremental(敏感词增量) | 0.14s | 0.02s |
| 段2 | pairs_active_in_days(fact 活跃对发现) | 0.04s | 0.02s |
| 段2 | recompute_pairs(fact 重算) | 0.00s | 0.00s |
| 段2 | recompute_metric_days(宽表滚动3天) | 1.51s | 0.52s |
| 段2 | recompute_call_days(通话滚动3天) | 0.22s | 0.02s |
(段值不含各自 session.commit;日志输出:staff_days=0 scanned=10 hits=0 + mgmt fact_pairs=0 wide_rows=168 call_rows=0,两次一致。)
- 远低于 <60s 预算。本地镜像已停同步 7 天,滚动窗(0908~0910)内无消息/通话活动——
staff_days=0、fact_pairs=0、call_rows=0属停同步后的数据真空而非缺陷;wide_rows=168= 3 天 × 56 行/天(39 员工,wechat 38 + whatsapp 18 行/天,双渠道员工两行),宽表对零活跃员工照常出零值行。段2 主成本=宽表重算,随员工数×天数扩展而非会话数,服务器侧活跃会话数百的形态下同量级。 - 幂等 ✓:三表行数跑前 fact=68,476 / wide=12,371 / calls=1,163 → 第1次后 68,476 / 12,539 / 1,163 → 第2次后 68,476 / 12,539 / 1,163。wide 的 +168 为首跑补齐:0908~0910 三天行在表内原本不存在(本地夜间链停摆 7 天没写过),第1次 DELETE+INSERT 补齐后第2次行数纹丝不动——滚动窗语义下幂等成立。
2. 本地 scheduler 容器重建(范围仅本地;服务器镜像 2026-09-09 已重建不碰)
docker compose up -d --build scheduler:镜像yunke-scheduler:latest(ded96dfc9625,2026-09-10 构建)重建成功,容器 recreate + 起来。- 冒烟:
docker exec yunke-scheduler-1 python -c "import app.sync.scheduler"→ ok;容器日志确认 4 个 job 全注册——3×_safe_page_run(full/messages/full-history)+SyncScheduler._analytics_tick(F1 新 job 已进夜间/4h 链),"Scheduler started" 正常。 - 调度状态:验证完即
docker stop——重建前容器本就 Exited 7 天(本地不跑调度,避免与服务器调度器双跑同刷云客触发风控),维持 stopped 现状;容器已绑新镜像,用户随时docker compose start scheduler即以新代码上线。mysql/redis 未动。
3. 已知边界(归档)
| # | 项 | 说明 |
|---|---|---|
| 1 | phone_call 未进页面同步链 | 通话小表(employee_call_daily)暂从既有静态源重生成——本地 call_rows=0 即此边界的体现;审计 P0-5 由并行会话领地,不在此处理 |
| 2 | 补同步历史日期 | tick 只滚动当日+前2天;更早日期的修正由夜间全量(full_sync 汇总/管理步)覆盖语义兜底 |
| 3 | 本地数据真空 | 本地镜像停摆 7 天,0908~0910 无消息/通话——fact/敏感词扫描的真实活跃量级须在服务器侧(夜间链已跑)观察 |