diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 96c0242..fa2383b 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -228,6 +228,31 @@ alembic 链自当前线上版本起新增 3 支迁移:`a7d3e9f1c2b4` msg_daily_s 幂等自动补种,无需手插;后续加新词默认只扫增量,要扫历史消息需重跑 `--full`。 +### 管理后台批次A附加步骤(一次性,2026-09-09) + +迁移链新增 `b7c8d9e0f1a2`(经营管理域六表:first_reply_fact / +employee_metric_daily / employee_call_daily / employee_score_config / +employee_score_snapshot / management_followup)。**服务器 backend 容器 +不会自动跑 alembic**,须手动: + +```bash +sudo docker compose -f docker-compose.server.yml exec backend alembic upgrade head +``` + +部署带该迁移后在**夜间低峰**跑两条一次性回填(幂等;本地实测耗时: +fact 72s / 宽表+通话 71s,均分钟级): + +```bash +sudo docker compose -f docker-compose.server.yml exec backend python -m app.sync.cli first-reply-facts +sudo docker compose -f docker-compose.server.yml exec backend python -m app.sync.cli employee-metrics +``` + +- `first-reply-facts`:跨日精确首响事实表(本地实测 11,020 会话 → 6.8 万行)。 +- `employee-metrics`:员工指标宽表 + 通话日小表(本地实测 12,371 宽表行 + + 1,163 通话行);**必须在 first-reply-facts 之后跑**——宽表串类列 + (threads_answered/首响分位)与 fact 表同源,先跑宽表会整体为 0。 +- 两命令均为按日 delete+insert 幂等重建,复跑结果一致,中断重跑无副作用。 + ## 8. 本地收尾(部署验证通过后) ```bash diff --git a/docs/superpowers/plans/2026-09-08-management-batch-a-verification.md b/docs/superpowers/plans/2026-09-08-management-batch-a-verification.md index 830458f..f49d8c2 100644 --- a/docs/superpowers/plans/2026-09-08-management-batch-a-verification.md +++ b/docs/superpowers/plans/2026-09-08-management-batch-a-verification.md @@ -170,3 +170,74 @@ | 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) |