Commit b0b9705a by luoqi

fix(recall): 无牙位 actual 仅 periodontic/orthodontic 当全口覆盖(修误排除)

排除闸 ⑤a 的"actual 无牙位 → 视为覆盖该类全部牙位"对所有 excludeCats 生效,
导致一次无牙位外科处置(如切开引流)被当成"全口 surgical 治疗",误杀同患者所有
含 surgical 类目的 per-tooth 召回(K03/K06 等)。

实测 826790:K03 不良修复体@31;32;41;42;43(2024-11-28)被 2025-05-30 的无牙位
切开引流(surgical)误排除;而 11;12(2025-12-30,晚于引流)正常召回 → 自相矛盾。

修:无牙位 actual 仅当 category ∈ {periodontic, orthodontic} 才视为全口覆盖
(实测 actual 牙位有/无分布:牙周 134:1、正畸 19:1 压倒性无牙位 = 整牙弓治疗;
surgical 1:41、restorative 1:91、prosthodontic 0:19 压倒性 per-tooth,无牙位多是
录入缺失或急性处置)。其余类目要求 actual 有牙位且与诊断牙位交集。

验证:826790 → 新增 hard_tissue_damage@31;32;41;42;43 召回;45;46(K06)仍被
牙周全口洁治正确排除(periodontic 保留全口覆盖)。本地 100 患者 patientsHit 37→39
(救回 2 个被无牙位外科误杀的)。全量 89 测试通过,tsc 0。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
parent 53be5136
......@@ -322,8 +322,15 @@ export class TreatmentInitiationRecallScenario implements PlanScenarioPlugin {
AND (
-- 信号无牙位(全口诊断如 K05)→ patient/category 级排除,跟现状一致
COALESCE(NULLIF(trim(sig.content->>'tooth_position'), ''), '') = ''
-- actual 无牙位(全口洁治/牙周治疗)→ 视为"全口覆盖"→ 排除该 category 所有信号
OR COALESCE(NULLIF(trim(tx.content->>'tooth_position'), ''), '') = ''
-- actual 无牙位 → 仅 periodontic/orthodontic(整牙弓治疗,无牙位是常态:实测
-- 牙周 134:1、正畸 19:1 压倒性无牙位)才视为"全口覆盖"→ 排除该 category 所有信号。
-- surgical/restorative/prosthodontic 等 per-tooth 类目(实测压倒性有牙位)的无牙位 actual
-- 多是录入缺失或急性处置(如切开引流/无牙位拔除),不代表全口都治了 → 不当覆盖,
-- 要求有牙位 + 牙位交集(走下面分支)。否则一次无牙位外科处置会误杀同患者所有同类召回。
OR (
COALESCE(NULLIF(trim(tx.content->>'tooth_position'), ''), '') = ''
AND tx.content->>'category' IN ('periodontic', 'orthodontic')
)
-- 双方都有牙位 → tooth array overlap
-- 牙位字符串形如 "15;24;26" / "15 B;24 B"(B/L/M 等牙面后缀)
-- 1. regexp_replace 把非数字非分号字符(空格/B/L/M 牙面)替换成 ; → "15;24;26;"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment