-
feat(cli): seed-assignment 造批次验工程 + 修它抓出的跟踪 bug · efe60136
## 边界写在文件头:只验工程,不许反推规律 造数据能验的:报表 SQL 的查询计划、分子分母口径、样本不足判定、 退回原因聚合有没有漏过滤、边界(全退回/全到期/零转化/单人独吞)。
⛔ **不能**用来调任何默认值(容量 50 / 时效 3 天 / 专属优先 / 批次规模 100)—— T20 要的是"真人在真实压力下怎么反应",而这里每个数都是按 `--release-rate` 编的。 拿它反推 = 自己教自己,且比没数据更危险:假信号会让错的策略顶着"数据支持"的名义跑起来。 伪随机用 mulberry32 固定种子,⛔ 不用 Math.random(): 造完发现报表不对,得能原样重造一遍再看。 批次一律带 `SEED_` 前缀的 requestId,`--clean` 只清自己造的(实测零残留)。 ##🔴 seed 当场抓出一个真 bug `agentStats` 各人 planned 之和 **149 ≠ planned 199** —— 50 条已退回/已到期的 从所有人名下**蒸发**了,而"某人退了几条"恰恰是主管最想看的列。 根因:回捞原始承接人时用了 `assign 事件.createdAt >= 批次头.createdAt` (想排掉这条 plan 在**上一个**批次里的 assign 事件)。生产里事件与批次头同事务、 时间必然在后,**看不出问题**;seed 造的事件在 5 天前,整批当场消失,且**不报任何错**。 正解:plan 当前的 `assignment_id` 就是本批 ⇒ 它**最近一次** assign 必然属于本批。 按 planId 取 latest,不依赖时间窗。修完 199 = 199✅ 这就是造数据的价值 —— 它把一个"生产环境下永远碰不到、但一旦碰到就静默错"的 时序假设逼了出来。 ## 两条不变量已上锁(4 条断言) ① agentStats 之和必须 = planned(含事件时间早于批次头的情形) ② 退回原因分布不得混进系统原因 第 ② 条实测反例:不过滤 `event='release'` 的话,22 条 `assignment_expired` 会被当成退回原因 —— 退回从 28 变 50,**退回率几乎翻倍**,而报表看起来完全正常。 (当前 detail 从 `followup_plans.release_reason` 列读,天然隔离 —— 因为到期**不写**那一列; 但将来若有人改成从事件表聚合,这条断言会拦住。) ## 顺带查清了「结果信号」的时间锚 `appointment_record` 的 fact 层 `occurred_at` = **预约时刻**(assembler 的 `occurredAtField: scheduledAt`),content 里**没有** createdAt → 拿它做归因会把"分配前就约好、分配后才到诊"的算成战果。 但**事务层有**:`patient_transactions.canonical_payload.createdAt` (实测 `"2019-07-08T11:17:08+08:00"`),且事务是 append-only、reparse-proof。 ⇒ 「分配后 N 天内患者建了新预约」**可以客观算出来,客服一个字都不用填**。 本地 74,040 条 appointment_created 事务,按批次患者收窄后查询 439ms(全表扫要 973ms, jsonb 提取用不上索引 —— 报表可接受,别拿它做交互查询)。⭐ 这条对 S3 很关键:`n≥50` 不必卡在 `plan_executions`(回写率 11%)上,**换个分子就行** —— 不用"客服说成了",用"患者真的来了",后者还更硬。 881 单测通过,seed 数据已清理。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| asr-sensevoice | Loading commit data... | |
| pac-docs | Loading commit data... | |
| pac-service | Loading commit data... | |
| pac-web | Loading commit data... |