stale-scan.service.ts
5.17 KB
-
fix(persona): 画像水位对齐到 fact 层 + 写入侧节流 · 889dd6f0
## 事故 画像算的是 patient_facts,stale 判定却只看 patient_transactions 的 event_seq。 reparse 重写事实但不产新事务,于是 2026-07-21「amount_cents 应收→实付」reparse 之后紧跟的全量重算被水位闸整批 noop 掉 —— 生产日志: manual:cli noop=325,979 success=79,010 后果(生产 3000 抽样):reparse 前算的画像 96% 累计消费与实时事实不符,中位高估 91%。 页面上同屏两个数打架 —— 关键事实 ¥154,350(实时)vs 画像 ¥195,750(旧口径)。 而且不只是显示错:monetaryCents 要跟实时算出的 M 分位阈值比大小 → mScore 系统性 偏高 → RFM 分群整体偏「重要」→ 直接抬高召回优先级排序。 ## 修法 水位对齐:新增 personas.fact_watermark = max(patient_facts.updated_at), 与 event_watermark 取「任一落后即重算」。存量行为 NULL → 判 stale,自然回填。 stale-scan 同步加这一维。 闸放宽必然带来「算了但没变」,所以补一道**写入侧的闸**(persona-diff.ts): unchanged 一字未变 → 不写 feature 行,只推水位 refreshed 只有时间派生值变 → 就地刷新,不升版本(记 refreshedAt) semantic 真变了 → 升版本 判据比 data(剔易变键)+ evidence.factIds,不解析 description —— 同 reason-refresh 思路, canonical 序列化抽成 common/canonical-json.ts 两边共用(jsonb 不保留键序)。 顺带:闸里查过的水位不再在正算路径重查(原来 transaction / persona 各查两遍)。 ## 本地验证(friday host,13,268 患者) 首轮回填 noop=0(旧代码这里会大批 noop)· success=48 · refreshed=11,155 · unchanged=2,065 → 写侧节流把 13,268 次升版本压到 48 次 再次运行 noop=13,268 全零其余,142s → 29s,幂等 事故复现 只改 fact 不动 transaction → 触发重算,¥24,994 订正为 ¥15,996,v1 留痕 ## 部署注意 patient_facts 370 万行,新索引请先手动 CREATE INDEX CONCURRENTLY 预建(迁移里是 IF NOT EXISTS,预建后即 no-op);上线后跑一次全量 recompute-persona 补 fact_watermark。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed