- 29 Aug, 2026 9 commits
-
-
2026-08-29 生产 plan 段从 12 分钟涨到 159 分钟仍未跑完,而引擎从「
▶ Running engine」 到最后的统计块之间**什么都不打**,整轮是个黑盒 —— 只能临时起 pg_stat_activity 采样器 反推,才勉强定位到子场景粒度。这组日志就是为了不再重复那个过程。 ① 每个子场景一行(常态开启,每轮 11 行): [recall] sub=missing_tooth code=K08 sql=1234ms rows=567 post=89ms hits=42⭐ SQL 与 Node 后处理**分开计时** —— 今天最大的困难就是知道慢、却分不清 慢在 SQL 还是慢在 Node。 ② runAllForHost 三段(常态开启,每轮 1 行): [plan] 阶段耗时 场景=Xms 预取=Yms 写入=Zms 命中患者=N 总计=Wms 今天一度误以为瓶颈在预取,有这行就不会跑偏。 ③ PAC_RECALL_DUMP_SQL=1(默认关)保留,需要拿 SQL 原文做 EXPLAIN 时才开。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
luoqi committed
-
2026-08-29 用 dump 开关 + pg_stat_activity 采样 + EXPLAIN ANALYZE 测出来的, 结论与直觉相反,写进代码免得后来人重走弯路: ① 11 个子场景只有 2 个慢(K05 牙周 9m13s / K01 阻生牙 13m+),其余 9 个秒回; 且与结果集大小无关 —— K02 理由数最多(49,382)却秒回。 ② 11 条 SQL 逐字节相同,只有绑定参数不同 → 是数据分布 + 执行计划,不是 SQL 写法。 ③ 真实热点:Append(resolvedTeeth 的 UNION) loops=117,256 × 1.71ms ≈ 201 秒, 占 K01 单条 301 秒的 68% —— 每候选行跑一次的 gap 相关子查询。
⛔ 已实测否定,别再试: · 子场景并发=3:24.1 分钟 vs 串行 23.3。瓶颈是共享磁盘 I/O,并行只抢同一批 page。 · 信号码部分索引:EXPLAIN 估算成本降 13×(122 万行 → 5,593 行), 墙钟 24.5 vs 23.3 —— 无效。教训:别拿估算成本当依据,要看 EXPLAIN ANALYZE 的实际时间。 (该索引的迁移已撤回,不上生产。)✅ 真正的方向(独立项目,未做):resolvedTeethSql 从逐行相关子查询改集合式。 它是召回与画像共用的单一真理源,重写必须配等价性验证,否则是拿静默少召赌运气。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
PAC_RECALL_DUMP_SQL=1 时把每个子场景的完整 SQL + 绑定值打到日志。默认关,零开销。 为什么需要:2026-08-29 排查 plan 段耗时,采样抓到某条子场景查询单次跑 6.7 分钟, 但 pg_stat_activity.query 被 track_activity_query_size(默认 1024 字节)截断 —— 拿不到全文就没法 EXPLAIN,排查卡死在这一步。调 track_activity_query_size 要重启 PG, 生产上不划算;做成开关更可控。 实现上主查询从「$queryRaw 标签模板」改为「先建 Prisma.sql 对象再 $queryRaw(obj)」, 两者等价,但对象有 .sql / .values 可检视。 tests/recall-future-return-visit-gate.spec.ts 的 SQL 捕获桩同步兼容两种调用形态。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
修的是 2026-08-29 生产的两个真实故障: ① plan 段被拖过 2 小时 cron 间隔后套圈,雪崩自持(源头消失也不自愈) ② 18 万患者 reparse 跑满 61/61 批后倒在收尾统计的 bind 变量溢出
luoqi committed -
2026-08-29 测试服实测(585K 患者 / 87,661 命中,空闲机): 串行(默认 1):24.6 / 21.7 / 23.5 分钟(三次) 并发 3: 24.1 分钟 —— 不但没快,还略慢 采样显示全程 DataFileRead:本阶段是共享磁盘 I/O 受限,不是查询延迟受限, 并行只让几条查询抢同一批 page,总读取量不变。要提速得减少读取量(索引/收窄扫描), 不是提高并行度。把这个负结果写进注释,免得后来人再拧一次这个旋钮。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
## 一、cron 防重入(sync-incremental.scheduler.ts)
🔴 2026-08-29 生产事故:plan 段耗时涨过 2 小时后,cron(每 2 小时)照常触发下一轮, 两轮 plan 段并发抢同一批 I/O → 都更慢 → 更易被再下一轮套圈 → 雪崩。 实测 08-28 20:15 起连续多轮 plan 段一次都没跑完;到 08-29 11:19(即触发原因 reparse 结束 1.4 小时后)仍有两轮在并行互拖 —— 雪崩是**自持**的,源头消失也不会自愈。 现有的锁都挡不住,这是本修复存在的理由: ① NestJS CronJob **默认不防重入** —— 上一次回调还在 await,下一次照样进; ② sync_logs 的 partial UNIQUE(host_id) WHERE status='running' 只覆盖**摄入段**, 摄入一结束锁就放了,而最慢的 persona / plan 段还在跑。 所以必须在回调入口用进程内 Set 挡。跳过而非排队:摄入是游标增量,下轮自然 catchup; plan 是时间驱动全量,跳一轮只是晚 2 小时评估,远好过雪崩。 释放放在 finally —— 抛异常时不释放会把该 host 锁死到进程重启。 tests/scheduler-reentrancy-guard.spec.ts 锁四条:并发跳过 / 结束后放行 / 按 host 而非全局 / 抛异常也释放。 ## 二、reparse 两处 bind 变量溢出(cold-import.service.ts) `patientId: { in: [...] }` 直接塞完整患者清单会撞 PG 的 32767 上限。 实跑路径(第 3 步统计受影响患者)—— 2026-08-29 生产实测:18 万患者的 reparse 跑满 61/61 批、写完全部事实(superseded=9,062)之后**倒在最后一步**: Assertion violation: too many bind variables ... received 32769 6.6 小时的活全干完,只因收尾统计炸掉而 exit 1。 dry-run 路径同病:>3.2 万患者直接崩,而 --patients-file 的文档恰恰说 「按受影响患者收窄是最有效的提速手段(可达 250 倍)」—— 最需要先 dry-run 探路的 大清单场景,正好是它唯一不工作的场景。 两处都按 3000 分块(与 PAC_REPARSE_BATCH 同款),每块 3001 个变量,离上限很远。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
luoqi committed
-
luoqi committed
-
- 28 Aug, 2026 29 commits
-
-
0366b572 加这条规则时只挡了 restorative(补牙散文含「调合」),放在了 periodontic 之前 —— 漏掉了牙周/预防/外科。「调合/调颌」是咬合微调,几乎任何术式的收尾都可能 带一句,它**单独出现**时才代表修复动作;和别的操作词同句时,那个操作词才是本次治疗。 2026-08-28 测试服 reparse 实测:新写入的 179 条调合/调颌事实里 18 条(10%)判错 「牙周刮治+调合」「根面平整+调合+牙周内上药」「洁治,47调合」→ 应 periodontic(16) 「涂氟,45调合」→ 应 preventive(1);拔牙类 → 应 surgical(1) 两个方向都坏: ① 牙周治疗不再解 perio_no_srp 缺口 = 多召牙周; ② prosthodontic 在结构 resolver 里,反而去解那些牙位的缺牙缺口 = 少召缺牙(不报错)。 修法:挪到本表倒数第二行(只在人群词 pediatric 之前)。原始个案照样接得住 —— 「调合,抛光」「调合抛光」里的「抛光」不在任何含词表,只有「调合」命中,仍判 prosthodontic(陈惠英@16 / 陶美玉@22)。 tests/keyword-case-and-terms.spec.ts 锁住两侧:实测误判串必须归回原类目, 原始个案必须仍是 prosthodontic。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
luoqi committed
-
代码分支 fix/treated-evidence-from-emr-text(已含 fix/polish-on-missing-tooth-implies-restoration)。 摄入期(需 reparse --subject-type=treatment): 67fe9d19 补义齿维护族(卡环/基托/重衬/假牙)+ 扩根/松牙固定/用氟/洁牙 0366b572 补含词「调合/调颌」 6ee7b8e9 姑息处置(调磨/试戴/冲洗/换药/上药)route 到 review,不再整条丢弃 查询期(plan 重算即生效,均只往 resolvedTeeth 加牙位,失败方向为少召): e0a12c44 缺失牙位上的裸「抛光」= 修复体在位 50a9c9a5+362f484e+79e63437 主诉/现病史自证已治疗(仅开缺失牙场景) 96765e1d 检查所见按条目举证修复体在位 aac4e249+1a3916a5 活动义齿「假缺失」按颌判定(限同一份病历,不整颌铺开) 1284675e 抑制改为全有或全无 —— 处置是计划级的
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
上一版有两处过宽,都会把该召的不召(而少召不报错):
🔴 ① 整颌铺开会盖掉同颌里义齿没覆盖到的缺牙位。 上颌局部义齿只补了 14;15;16,而 24 也缺着没补 —— 整颌一刀会把 24 一起解掉。 改为:句子按颌**定范围**,牙位仍取**该条目自己的** tooth_position,不再铺满整颌。 两例待定个案仍成立:王志荣条目 28 颗 ∩ 下颌 = 他的召回牙位; 童然夫条目 17 颗 ∩ 上颌 = 他的召回牙位(13;16;17;21~27)。🔴 ② 作用域从「信号当次或之后的任意病历」收紧到「**信号诊断那一份病历**」。 同一份里"诊断说缺失、检查说有义齿"是医生同一时刻写下的,用后者补前者没争议; 跨次就多了一层"这中间会不会变了"的推断 —— 与「最新诊断第一位」这个大前提不符。 实现:adx.content->>'emr_external_id' = sig.content->>'source_encounter_external_id'。 (已验:王志荣/童然夫的义齿句都与各自的信号诊断同属一份病历。) 影响面 541 → 141 条 / 140 患者(降 74%,是收紧应有的代价)。 测试补 4 例锁住"不越界":条目跨颌时只解句中点名那一颌、只提上颌时下颌牙位不得被解、 不得铺到条目之外。100 suites / 1777 tests,tsc 0 error。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
戴活动义齿的患者,诊断栏**永远**写「缺失牙」—— 天然牙确实没了,这个诊断是对的。 但那个位置已被义齿盖住,是**假缺失**:召回说的「缺失牙未**启动**修复」根本不成立, 治疗早就做了,而且做的就是义齿。义齿松了/紧了/该重衬是**维护**,不是没做过。 ── 判据:按颌,不按牙位 ── 活动义齿是**整颌跨度**的修复体 —— 判出"这一颌有义齿",那一颌的缺牙位就都被它盖住。 「上颌…义齿」→ 上排全部解除;「下颌…义齿」→ 下排;「上下颌/全口…义齿」→ 两排。 不需要逐段判状态,也不需要条目牙位跟召回牙位对上。
🔴 这条**天然绕开了混合句问题**,而混合句正是前一条(RESTORATION_IN_PLACE_*,按条目 牙位 + 同颌闸)卡住两例待定个案的原因: 王志荣 TS0K051842「上下颌吸附性义齿修复,下颌固位可,上颌固位稍差」(28 颗跨颌) 童然夫 TS0M013276「上颌义齿卡环紧,下颌义齿无法完全就位」(17 颗跨颌) 两条检查所见牙位都横跨上下颌 → 同颌闸整条跳过;而句子本身按颌写清了,按颌判直接可用。 此前评估"要先做按颌切分"需要两个新机制(颌作用域粘连 + 存在即在位),按颌判定都不需要。 ── 影响面 ── 在跑计划命中 541 条 / 511 患者(前一条按条目判的是 386 条,两者互补: 按条目那条接的是句中不提颌、但条目牙位单颌的写法,如周燕芬「见活动义齿,基托边缘密合」)。 导出复核:命中句几乎全是真「那一颌有义齿」——「上颌活动义齿修复」「上下颌全口义齿, 咬合接触均匀」「上下颌种植临时义齿存」等。状态不好的(卡环折断/固位欠佳/压痛/易脱落) **按口径同样算已治疗** —— 义齿存在,「未启动」就不成立。⚠ ️ 「≤10 字」距离限制是关键:要求颌词紧挨义齿词,否则「上颌见残根……下次考虑做义齿」 会被误连(已写成测试)。⛔ 未发生词一票否决:「建议上颌活动义齿修复」是还没做。⚠ ️ 同前三条路线:只往 resolved 加牙位、永不新增召回 → 失败方向只有少召且不报错。⛔ 只对缺失牙(K08)开闸。 测试 20 例,含两例待定个案的原句 + 生产真实句 + 距离限制的反例。 100 suites / 1773 tests,tsc 0 error。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
周燕芬 TS0M013273 上颌 11~27:那次来做**下前牙**冠修复,上颌的检查所见记着 「见活动义齿,基托边缘密合,伸展范围良好,固位力良好,无压痛」—— 上颌义齿明明在位, 却因为"这次不是为它来的"而前两条路线(治疗名 / 主诉现病史)都接不住 → 误召。 本分支接的就是「患者为别的牙来、医生顺带记了全口状况」这一类。 ──
🔴 先纠正一个把 12% 当成全部的判断 ── 此前一直认为"检查所见这条路要先解决混合句按颌切分才能动"。实测推翻: 提到义齿的检查所见 81,812 条,**71,832 条(87.8%)牙位本就在单一颌内**; 跨颌的只有 9,980 条(12.2%),其中文中点名了颌、理论上可切分的仅 676 条(上下颌都点名 150)。 即 88% 根本不需要切分,直接加**同颌闸**跳过那 12% 就能做。按颌切分整个生产只值 150 条, 性价比远不如先做这 88%。(王志荣/童然夫恰好落在那 12% 里,是巧合。) ── 判据 ── 修复体名词 ∧ 在位状态词 ∧ ¬失效词,且该条目牙位不跨颌。⛔ 失效词是命门:修复体**做了但不能用**仍然该召回(需要重做的不是"已修复")。 ──🔴 中文否定前缀踩了五轮才收敛,全部由生产命中逐条核出来 ── 每一轮都是把全量命中导出人工过,发现漏就补,再导一次: ① 「密合」子串会匹配它的否定形式 —— 赵河 BA27761「边缘欠密合」/ 陈德家 BA40818 「密合度差,继发龋坏」/ 翟健民 BJ0C021369「修复体松动,边缘欠密合,部分崩瓷」 / 李露 BJ0D049016「松动I,边缘不密合」全被误销。 ② 「无义齿修复」含「义齿」却是说**没有** —— 徐磊 BJ0U001139。同一个坑的第二次。 ③ 反方向:「松动」是坏词,而「无松动 / 未见明显松动 / 松动(-)」是好话,列进 FAIL 会把好的一起否掉 → 改用**先 regexp_replace 抹掉好话、再判坏词** (与 refusal 判定里"先抹掉'拒绝拍片'再判拒绝治疗"同一手法)。 ④ 「密合」的否定形式**逐个列举列不完**(不密合/欠密合/密合度差/密合性不佳/密合度欠佳/ 密合较差/密合度不佳/密合差…)→ 改用模式 `密合[^标点]{0,3}(差|不佳|欠佳|不良)`, 且不许跨标点(否则「边缘密合,…口腔卫生差」会误否)。「固位」同理(周锡英 TS0B001674 「覆盖义齿修复,固位不良」)。 ⑤ 松动度的写法:「11Ⅰ°松」(王秋枫)/「松动二度」(侯永生)/「牙松动2度」(康振英)/ 「有明显松动」(吴实)。 ── 影响面(六轮导出复核后)── 在跑计划命中 386 条,平均解 1.8 颗牙,一次解 ≥10 颗的 6 条(全是全口活动义齿/覆盖义齿在位, 已逐条看过)。周燕芬本人四条件全过(她的计划已 abandoned,故不在"在跑"的导出里)。⚠ ️ 同前两条路线:只往 resolved 加牙位、永不新增召回 → 失败方向只有少召且不报错。⛔ 只对缺失牙(K08)开闸 —— 判据是拿 K08 的检查所见核出来的。 测试 45 例,正反例全部取自生产真实串(含上述五轮踩出来的反例 + 防误否的"尚可/较密合/ 固位良好/无松动"正例)。99 suites / 1753 tests,tsc 0 error。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
季炎萍 TS0B010543 牙41~37:下颌活动义齿戴数年,2026-03-19 因压痛来修整边缘。 主诉「下颌活动义齿戴牙数年余」/ 现病史「…有压痛,今来就诊」 检查所见「义齿密合良好,边缘过长,牙龈略红肿」/ 处置「调磨过长边缘,抛光」 本次治疗 treatName =「调磨」→ 被
⛔ 块丢弃 → **她库里零条治疗事实**, 系统看成"有诊断、从来没治过" → 判「缺失牙未启动修复」,还推种植(75 岁)。 客服反馈:abandon_reasons={inaccurate,other} / inaccurate_treatments={implant} / 备注「BPS已修复」—— 客服抓的是"推种植不准",根子是那条治疗根本没入库。 ── 纠正原⛔ 块的根因分析 ── 原文:「category 同时被①治疗史完整性 ②resolver 判缺口已解 两处消费,只有一个旋钮, 只能二选一(现选都不要=丢弃)。要两全需在 category 之外再加一维"是否根治"」。 **前提不成立** —— 第二个旋钮一直都在,就是 review 类目,且它就是为这件事设计的: PACTreatmentCategories.review:「医生做了某个流程节点 / 临床判断本次不动手」 「不该塞 preventive(会污染已做预防判定),也不该丢弃(丢失事实)」 结构家族注释:「刻意排除 periodontic / orthodontic / preventive / **review**」 实测:review 出现在 0 个 resolver 家族里 即 review 精确地给①不给② —— 冠周炎冲洗上药之后仍照常召拔牙,但治疗史里看得到。 (我中途还错误建议过映射到 preventive —— review 的注释明写着那样会污染"已做预防"判定。) ── 影响面(DW 全量实测)── 含这五词的 EMR 5,184;treat_plan **全是**这五词的 4,336 → 进治疗史,不解任何缺口。 ──⛔ 单独一条 route,不并进 &review_terms ── 那个 anchor 被 C.9 dispose 闸门复用(blank_or_all_in)。并进去会同时打开闸门,而那 4,336 份处置 **100% 非空**:TOP400 处置跑分类器,约 216 条落进结构 resolver 家族, 且含「冠周冲洗,派力奥上药」→prosthodontic(因为含"冠"字)、「去暂封…玻璃离子暂封」 →restorative 这类误判 → 会误销缺口。dispose 闸门维持现状(不开 = 不回退,是放弃 一块带风险的额外收益),要开另行评估。 两个消费方语义本就不同:route 问"哪些名字不是真治疗",闸门问"何时可安全抽处置"。 route 算子支持两条规则指向同一 output(outputs 初始化在推数据之前完成),已实测。 顺带:TREATED_EVIDENCE_COMPLETION_RE 量词补「数/多/几」—— 真实主诉大量写 「戴牙数年余」「多年」而非确切数字,不认就漏(季炎萍即是)。DW 实测 +4 份。 测试新增 palliative-route-to-review.spec.ts(11 例),直接加载真实 manifest 锁住: ① 五词落 review 表、真治疗不受影响、建议仍走 recommendation; ② dispose 闸门词表仍是原 34 词且不含这五词、两条 route 指向同一 output 是手段不是笔误。 98 suites / 1708 tests,tsc 0 error。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
王祖妹 TS0B006805 牙46:4 年前种植戴牙,2026-04-25 复诊。 主诉「种植戴牙4年余按约复查」/ 现病史「4年前在本院种植戴牙,无不适,今按约复诊」 检查所见「冠边缘密合」/ 诊断 K08 牙齿缺少 46 本次治疗「转洁牙中心全口牙洁治」→ **periodontic** 文本证据完美,却因为触发闸硬写成 ['preventive','review'] 而不生效。 牙周洁治对缺牙缺口同样是"什么也没解决",跟宣教/复查没有区别 —— 判据本来就该是 **类目 ∉ 该场景的 resolverCats**,而不是一张写死的清单。改成 treatedEvidenceTriggersFor()。 生产实测放宽后只多 3 条命中(周海峰/马丽/王振权),形状与王祖妹一致,全部正确。
⛔ 但牙周/正畸的牙位是**全口批量**写的(一条洁治记录挂 28 颗),不表达"针对这颗牙", 拿它当牙位锚会大面积误销 → 这两类额外加牙位上限 8。预防/复查类**不设**上限: 那些牙位是医生针对性写的,且全口种植修复复查确实会写满一颌(吴庆安 12 / 高惠霞 14 / 周根娣 15,实测都是对的),一刀切上限反而会砍掉正确命中。 (放宽后的全量复核受限:牙周记录牙位数大,不加上限时导出查询 860s 超时两次; 加上限后可导出,即上面那 3 条。) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
上一版漏了作用域:抛光那条规则用 cfgFlags.polishImpliesRestoration 只对 K08 开, 本分支却没设闸,凡是 resolverCats 含 prosthodontic 的场景(整个结构家族 K00/K01/K02/K03/K04/K08/K09)都会触发。而我那 59 条人工复核**全是 missing_tooth**, 非缺牙场景一条没验过。 想补验,但非缺牙理由量太大(hard_tissue_damage 单独就 18 万条),导出复核的查询 两次 860s 超时 —— 也就是"验不完"。少召不报错,验不完就不该默认开。 改为 GAP_FLAGS_BY_PRIMARY.K08.treatedEvidenceFromEmrText,机制保持场景无关 (判据是牙位级的),开关按场景给。要给别的场景开,先把该场景的命中全量导出人工过一遍。 顺带记下通用性自查的其余结论(都通过): · 关联键 emr_external_id
↔ source_encounter_external_id 都是 PAC 规范字段,非 host 字段名 · 词表在 packages/types 共享层,收的是临床通用词(戴牙/假牙/义齿/修复体/烤瓷冠), 无 host 痕迹 —— 符合「通用→共享,怪癖→宿主」的分层 · 宿主不填主诉(如 FRIDAY)→ join 落空,分支自动失效,不报错 · 类目闸对 K05/K07 天然为空(resolverCats 不含 prosthodontic),全口路径本就不接本表 测试补一例锁住开闸范围。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
王作荣 TS0M002910 牙34:2025-01 戴牙完成,2026-04 复诊重记「牙齿缺少」,当次治疗 只有「常规口腔卫生宣教」(preventive,不解结构缺口) → 误召。而同一份病历的主诉 写着「种植戴牙3个月余按约复查」、现病史写着「3个月前在本院行左下后牙种植戴牙」。 主诉/现病史一直摄在 emr_record 里,只是召回从不读它(召回读过的自由文本只有 exam_findings 一处,且只用于 NO_RESTORATION_GAP_EXAM_PATTERNS 那四个词)。 ── 大前提不动 ── 最新诊断永远第一位。
⛔ 不用「前有治疗、后有诊断」翻案 —— 那是猜疑链(修复体会失败、 会脱落、会重做)。本分支只在【诊断后 · 同牙 · 本次动作是 preventive/review】这个 模糊地带,再审同次病历。治疗类动作不走本表(resolver 已正确处理)。 ── 词表独立,不复用治疗名那几张(实测)── ① 覆盖不足:REVIEW_IMPLIES_TREATMENT 要求「治疗词+复查」,真实主诉一半以上不带"复查" (「种植戴牙3月余」「右侧后牙种植戴牙后一年」)。 ② 时态不同:treat_plan 里的词一定已发生,主诉里可能是诉求(「要求窝沟封闭」)。🔴 ③ 致命反例就在王志荣 TS0K051842 身上:2024-06 现病史「…要求拔除口内剩余牙齿后 全口义齿修复」vs 2026-01「全口假牙1年半前于我院修复」—— 字面同、语义反。 否定侧复用 strip 的「建议/推荐/考虑/择期」语义(剥"未发生",跨字段一致)。 ── 上线前把 59 条命中全量人工过了一遍,四条判据全是踩出来的 ──🔴 只认「戴牙」不认裸「种植」:种植是多阶段(一期→二期→取模→戴牙)。裸「种植」放行会 误销掉最该召的:「种植体拔除后两个月」「种植体及牙冠一起脱落」「三周前行种植手术」 「拔牙后三个月,种植检查」「转诊种植科」「种植导板设计」「种植前检查」。🔴 症状词不当完成标记:原先收「脱落/松动」,理由是"不存在的东西不会掉" —— 那只证明 曾经存在,不证明现在在位;对缺牙修复判定,脱落 = 需要重做 = 该召回。🔴 同颌闸:陈春洪 TS0K070402 主诉明说「右上后牙种植戴牙」,那次宣教牙位却写了 16;17;27;41;42;46;47 跨四象限,会把下前牙一起误销。按颌切比按牙位数设上限有依据。🔴 不扫 disposal:生产实测它夹带 toothPositionBak 的 base64,会制造假命中。 另补「无法就位/无法佩戴」类否定 —— 修复体做了但不能用,仍该召回。 收紧后 59 → 57 条命中,逐条复核无明确误销(1 条边界:上颌义齿"不贴合",按第 17 例 童然夫同口径视为在位)。⚠ ️ 本分支只往 resolved 加牙位,永不新增召回 → 失败方向只有少召,且不报错。⚠ ️ 整颌案例单条命中即解 12~15 颗牙(吴庆安/高惠霞/周根娣),判错代价大,需重点盯。 扩展点:TREATED_EVIDENCE_EMR_FIELDS。要加 exam_findings 得先解决段内按颌切分。 测试 38 例,正反例全部取自生产真实串(含上述七条踩出来的反例)。 97 suites / 1685 tests,tsc 0 error。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
陈惠英 TS0K028757 牙34;35;36;37: 2024-07 起做种植,中间 37 植体失败取出重做,2025-03-21 四颗全部戴牙完成 2026-05-20 复诊,主诉「种植戴牙1年按约复查」,检查所见「冠边缘密合,牙龈未见异常」 诊断「牙齿缺少 34;35;36;37」+ 本次治疗「调合,抛光」(牙位完全一致) 一年多的种植治疗史被这次重记的诊断挡在时间门外;当天那条本可以过门的「调合,抛光」 落 _default 整条丢弃 → 生产库里那天只有诊断没有治疗 → 判「缺失牙未启动修复」误召。 ── 与「卡环加力」不是同一种失效 ── 卡环加力是词表里压根没有;这次两个词**都在词表里**,但都只在**精确表**: treatment_actual.yaml:147 调合: prosthodontic / 调颌: prosthodontic treatment-category-core.yaml:189 抛光: preventive 医生把两个词写在一起 → 精确匹配不上,含词表又没有这两个词 → 一个都不命中。 即"产品早就判定了它算修复,只是接不住组合词"。 ── 影响面(真实引擎代码回放 DW 全量 135,457 个 treatName)── 改前 丢弃 207,209 EMR → 改后 204,670 EMR 救回 2,539 EMR / 1,065 词形,新丢 0 词形 ──⛔ 顺序是这条规则的命门 ── 必须排在 restorative 之后。「去腐净,GIC垫,Z250充填,调合抛光。」这类补牙散文含「调合」, 放进上方 prosthodontic 行会把上千条充填记录改判成修复(两者都在结构 resolver 里, 召回判定不变,但治疗史和标签会错)。放在 restorative 之后让「充填/树脂」先赢 —— 已实测。 种植/正畸语境仍由更前面的规则先赢(种植体调合→implant、正畸调合→orthodontic)。 「调磨」不收,沿用词表文末⛔ 块(姑息调整 ≠ 问题已解决);调合=咬合调整,是宿主已判定的修复动作。 测试新增 9 例(含三条生产真实补牙散文的防回归断言)。96 suites / 1647 tests,tsc 0 error。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
陆雪 TS0K090243: 08-23 客服点一次「已完成治疗」→ 该 plan 名下 4 条理由(缺牙种植 / 牙周 / 影像AI缺牙 / 残根)全部被压,treated 的 suppressDays = 36500 天(永久) 08-26 我们的残根→K03 改判把 subKey 从 missing_tooth@17 换成 hard_tissue_damage@17,它不在抑制集里 → **只有它一条**复活 结果卡片上:病历三类应治未治、画像标签也写着「种植/牙周/拔牙三类待跟进」, 召回理由却只剩一条 —— 读的人只能以为算法漏了。而那次「已完成治疗」本身是错的 (该患者信号后只做过 OHI),等于把两条正确召回永久埋掉。 ── 根因:两个粒度对不上 ── 处置是**计划级**的(schema PlanExecution.inaccurateTreatments 注释写明:客服勾 "只有种植不准"也不会只放过根管那条 —— 业务 2026-07-30 明确),抑制却是**逐 hit** 过滤的。于是产生"部分抑制"这种中间态:一部分理由回来了、另一部分还压着。 而且这一条复活还是**靠巧合** —— 走的是 `if (!anchor) return true`(键查不到就放行), 不是设计好的时间锚逃逸。生产实测:2026-08-01 以来 4 例结案后复活,时间锚逃逸 0 例, 改判逃逸 4 例(王丽芳/孙海燕/吴梅英/陆雪,全部是残根→K03),同一条诊断 subject 未变。 ── 新规则 ── 缺口开没开由**事实**说了算,抑制只是人为覆盖,不改写事实: · 一条都逃不出去 → 整个 plan 不生成(一起抑制) · 有任意一条逃得出去 → 本轮**全部** hit 都进 plan(一起生效) 真治好了的缺口会被 gap 计算自己消掉,轮不到抑制来盖;抑制盖住的从来只是 ①数据没摄到 或 ②人标错了 —— 这两种都不该永久。 改动本身只有一处:逃逸判定的结果从"过滤后的 hits"改成"要不要整体放行"的开关, 放行后用全量 hits。旧的时间锚逃逸口径原样保留。 测试:新增 2 例(部分逃逸→全部复活 / 全不逃逸→整体抑制), 既有 suppressed 用例不变仍绿。96 suites / 1638 tests,tsc 0 error。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
薛希明 TS0K018752 牙14;15;16;17;25;26;27: 2025-01-19 戴活动义齿(10 颗,含本次全部信号牙位) 2026-05-21 复诊,主诉「上颌假牙松2周」,检查所见「活动义齿修复,义齿卡环松」 诊断「牙缺失 14;15;16;17;25;26;27」+ 本次治疗「卡环加力」(牙位完全一致) 2025-01 的戴牙被 2026-05 重记的诊断挡在时间门外;当天那条本可以过门的「卡环加力」 含词表一个词都不含 → 落 _default 整条丢弃 → 生产库里那天只有诊断没有治疗 → 误召。 ── 影响面用真实引擎代码回放 DW 全量得出,不是手工估 ── 把 fact_emr_treatment_out 的 135,457 个 distinct treatName 全量喂给 runDerive(normalize) + resolveDictIncludes(真实字典) + classifyByKeyword(真实分类器),并按 manifest 的 route_by_pattern 先剔掉「建议/推荐」和复查词: 改前 命中 2,146,498 / 分流 501,468 / **丢弃 218,881 EMR(35,817 词形)** 改后 命中 2,158,170 / 分流 501,468 / **丢弃 207,209 EMR(34,493 词形)** → 救回 11,672 EMR / 1,324 词形,新丢 0 词形(无回归) ── 加了什么 ── ① prosthodontic += 卡环, 基托, 重衬, 假牙 478 EMR / 216 词形 四个词临床上只属于活动义齿。基托选磨 49、卡环加力 40、重衬 26、紧卡环 21… ② endodontic += 扩根 604 EMR,与已收的「根备」同一动作 ③ periodontic += 松牙固定 269 EMR,牙周炎松动牙夹板固定 ④ preventive += 用氟 4,470 EMR,与「涂氟」同一动作 (preventive 不在结构 resolver 里 → 只补治疗史,不改召回判定) ⑤ periodontic += 洁牙(独立规则,none: 清洁牙) 5,894 EMR 裸「洁牙」当初被刻意拿掉是怕误命中散文「清洁牙面」。改成单开一条挂 none: 「清洁牙面/清洁牙齿/清洁牙间隙」(332 EMR)照旧丢弃,其余全收。none 不挂原规则 是为了混合句「清洁牙面,龈下刮治」仍由原规则正常命中。 ── 碰撞全部实测,不是推理 ── 活动矫治器也有卡环/基托,但 orthodontic 排在 prosthodontic 之前 → 矫治器卡环调整 / 正畸基托调磨 / 保持器重衬 全部仍归 orthodontic;扫了 DW 里全部不含正畸词的卡环/基托 记录,无一条正畸语义。扩弓不含扩根。洁牙后树脂充填仍归 restorative。 只改 actual 侧(resolver 读的那份);planned 侧本就与 actual 有意分歧(它保留裸洁牙和 裸二期,因计划名无散文),沿用「加牙/活托」只加 actual 的先例。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
原记「抛光隐形义齿只写在处置里」有误:抛光本身就是一条带牙位的治疗记录, 只是归在预防类目。真正的机制是该牙位上的抛光不被当作治疗。已修复。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
reason-temperature.sql 那条注释拿早期 5,034 条证据的观测当依据,量级已差 216 倍, 读的人无从判断这道守卫今天还成不成立。 2026-08-28 生产全量实测: plan=active 1,092,580 条理由 —— 无 active 证据的 0 plan=assigned 1,265 —— 0 plan=completed 41 —— 0 plan=abandoned 282 —— 6 即引擎重算追得上事实换代,这道过滤没有在挡真数据。 顺带记下它的信号价值:这个数掉下来 = 重算落后于摄入,矩阵格位会静默变空且不报错。 (排查缘起:误以为 evidence.factIds 大面积断链。实际 missing_tooth 的 103,980 条 理由里,真断链只有 1,297 条且全在 7 月那周的 superseded 历史快照上;先前看到的 15.4% 是 planned 类信号 occurred_at 本就为空 —— 它们有 planned_for,靠 COALESCE(occurred_at, planned_for) 取锚,并非缺失。) 纯注释,无逻辑改动。tsc 0 error,96 suites / 1602 tests 全绿。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
李钟瑜 TS0M006971 牙31: 2025-03-27 拔除 31 2025-06-26 K08 牙缺失 31 → 活动义齿戴牙 31 ← 真实修复证据 2026-02-12 复诊重记「牙齿缺失 31」+「抛光 31」← 新信号,把上面的戴牙挡在时间门外 当日唯一能过时间门的证据是那条抛光,而它归 preventive —— 不在结构家族 resolver 里 → 判「缺失牙未启动修复」,误召。 31 号牙 2025 年就拔了,牙不存在,抛光的对象只能是那副义齿。这是逻辑蕴含,不是 统计推断;生产实测也印证:裸抛光落在 K08 缺牙位上共 101 个(患者×牙位),94 个 (93.1%)在同一颗牙找得到 prosthodontic/implant 的 actual 治疗,患者级 98.0%。 (当年 REVIEW_IMPLIES_TREATMENT 上线的同口径是 88.2% / 95.2%。) 三条闸,少一条就会静默误销: ① 词形:只收光秃秃一个「抛光」(容尾标点)。生产 36 万条带牙位的含抛光记录里, 绝大多数是「全口龈上洁治,抛光」这类洗牙流程(periodontic,牙位动辄 28 颗)—— 针对天然牙,放行会把洗过牙的患者所有缺牙位一次性解光;其次是「树脂充填…修整 抛光」「试戴全瓷冠…抛光」这类别的治疗里的一个步骤,它们本来就带 restorative/ prosthodontic,本来就解得开。「义齿抛光」同理已是 prosthodontic。真正落单的 只有裸「抛光」→ preventive 这一支(带牙位 2,405 条)。 「抛光,涂氟」刻意不收 —— 涂氟是全牙列预防,针对天然牙。 ② 牙位数 ≤ 6:裸抛光里仍有 56 条挂了 7~32 颗牙,那是洁治语境漏进裸词的。 ③ 只对 K08 开闸(GAP_FLAGS_BY_PRIMARY.polishImpliesRestoration):牙还在时抛光 就是抛天然牙除渍,什么也不蕴含。 时间方向沿用 afterDxFor,与治疗排除同口径。 测试 96 suites / 1602 tests 全绿(新增 1 suite / 38 tests);tsc 0 error。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed
-
- 27 Aug, 2026 2 commits