Commit 4abc7f0c by luoqi

fix(recall): 患者无意愿(治疗 subtype)对本次诊断排除该类目(一线反馈⑧)

余奕铭 BJ0F022277:1.18 三条 planned 治疗 subtype"正畸取资料/洁牙,患者无意愿"
→ 当场拒绝;召回正畸83+牙周62 正是被拒两项 → 误召。

口径(与产品对齐):类目级 + 对本次诊断排除(非永久非时效)——与"治疗已启动"对称,
靠时间方向;新诊断更晚 → 自动重新可召。
- types 词典 TREATMENT_REFUSAL_SUBTYPE_PATTERNS(无意愿/不愿/拒绝);
- gap 全口分支(K05/K07)NOT EXISTS 加拒绝;按牙分支 resolved 加拒绝(种植/修复类);
- planned 记录用 COALESCE(occurred_at, planned_for) 取时间。

验证:余奕铭正畸/牙周召回消失(44→42);其余 6 正畸+1 牙周召回全保留(零误杀);
recompute exit=0。全库 1225 患者有此信号。仅本地,未部署。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
parent 7d5497b5
import { Prisma } from '@prisma/client';
import {
NO_RESTORATION_GAP_EXAM_PATTERNS,
TREATMENT_REFUSAL_SUBTYPE_PATTERNS,
RESTORATION_INELIGIBLE_DX_NAMES,
STRUCTURAL_DX_CODE_LIST,
type DxTreatmentRule,
......@@ -127,6 +128,11 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces {
: Prisma.sql`AND ${Prisma.raw(alias)}.occurred_at >= COALESCE(sig.occurred_at, sig.planned_for)`;
const afterDxFragRtx = afterDxFor('rtx');
const noRestorMsgRe = NO_RESTORATION_GAP_EXAM_PATTERNS.join('|');
const refusalRe = TREATMENT_REFUSAL_SUBTYPE_PATTERNS.join('|');
/// 拒绝记录时间方向:planned 记录 occurred_at 为空 → 用 COALESCE(occurred_at, planned_for)
const refusalAfterDx = rule.excludeIfEverTreated
? Prisma.sql`AND COALESCE(rfx.occurred_at, rfx.planned_for) >= ${latestDxOfCode}`
: Prisma.sql`AND COALESCE(rfx.occurred_at, rfx.planned_for) >= COALESCE(sig.occurred_at, sig.planned_for)`;
// §E (d) 正畸减数位(仅 missing_tooth):该牙有外科拔除 + 患者有正畸语境 → 折进 resolved 减掉。
......@@ -216,6 +222,17 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces {
WHERE (ef->>'message') ~ '缺[牙失]'
AND (ef->>'message') ~ ${Prisma.sql`${noRestorMsgRe}`}
UNION
-- (a'''') 患者无意愿:该 category 治疗被患者拒绝(treatment subtype 含无意愿/不愿/拒绝)
-- → 对应牙位计入 resolved(对本次诊断;新诊断更晚 → 重新可召)。一线反馈⑧ 余奕铭。
SELECT rft AS t
FROM patient_facts rfx
CROSS JOIN unnest(${toothArrSql(Prisma.sql`rfx.content->>'tooth_position'`)}) AS rft
WHERE rfx.patient_id = p.id
AND rfx.type = 'treatment_record' AND rfx.status IN ('active', 'fulfilled')
AND rfx.content->>'subtype' ~ ${Prisma.sql`${refusalRe}`}
AND rfx.content->>'category' = ANY(${resolverCats}::text[])
${refusalAfterDx}
UNION
-- (b) 同牙位以【最新诊断】为准:更晚的真实结构诊断 → 旧诊断对该牙失效
SELECT ldt AS t
FROM patient_facts ldx
......@@ -269,6 +286,16 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces {
${afterDxFrag}
AND COALESCE(NULLIF(trim(${sigToothExpr}), ''), '') = ''
)
-- 全口病(K05/K07):患者拒绝该类治疗 → 排除本次诊断召回(一线反馈⑧)
AND NOT EXISTS (
SELECT 1 FROM patient_facts rfx
WHERE rfx.patient_id = p.id
AND rfx.type = 'treatment_record'
AND rfx.status IN ('active', 'fulfilled')
AND rfx.content->>'subtype' ~ ${Prisma.sql`${refusalRe}`}
AND rfx.content->>'category' = ANY(${resolverCats}::text[])
${refusalAfterDx}
)
ELSE
cardinality(lat.remaining_teeth) > 0
END
......
......@@ -12,9 +12,9 @@
| ③ | 秦溢泽 BJ0A057103 | 1.27 取资料未回复 → **召回正确**:1.27 EMR"今取正畸资料…约日复诊"+ planned 正畸/充填;2.23 回访"微信约看方案",此后无到诊 → 方案悬置,正是该召的人("取资料"归流程类不算治疗,口径正确) | ✅ 正确召回 |
| ④ | 祁小夏 BJ0U016979 | 31;41 缺,11.22 修复 → **误召:固定桥牙位盲点** — 31;41 由 32;33;42;43 基牙桥修复(11.15 备牙/11.22 戴桥),治疗牙位只写基牙不含桥体 → 重叠判定失败。修复 2(桥区间)后召回消失 | ✅ 已修 |
| ⑤ | 王敏 BJ0U017412 | 26;37;47 缺;"26 无间隙" → **误召:无修复指征在检查文本里** — 诊断只写"牙列缺损",检查 exam_findings 写"缺失,缺牙间隙已关闭"。修复 3 后 26 移除,37;47 真缺牙保留 | ✅ 已修 |
| ⑥ | 李强 BJ0U017401 | 36;46 间隙不足→建议正畸 → **误召:同⑤(检查文本无修复指征)** — 检查"缺失,与邻牙间隙不足"(缺牙十年邻牙倾倒,需先正畸找回空间)。词典加"间隙不足"后召回消失 | ✅ 已修 |
| ⑥ | 李强 BJ0U017401 | 36;46 间隙不足**误召:同⑤(检查文本无修复指征)** — 检查"缺失,与邻牙间隙不足"(缺牙十年邻牙倾倒,需先正畸找回空间)。词典加"间隙不足"后召回消失 | ✅ 已修 |
| ⑦ | 黄琳 BJ0U015883 | ✓ 认可(17 缺失) | ⬜ |
| ⑧ | 余奕铭 BJ0F022277 | 外院/无意愿 → 待核(预计信息差) | ⬜ |
| ⑧ | 余奕铭 BJ0F022277 | 外院/无意愿 → **误召:患者无意愿(结构化在 treatment subtype)** — 1.18 三条 planned 治疗"正畸取资料/洁牙,患者无意愿";召回正畸83+牙周62 正是被拒两项。修复 4 后均消失 | ✅ 已修 |
| ⑨ | 刘强 BJ0U016929 | ✓ 认可(正畸) | ⬜ |
| ⑩ | 李石明 BJ0U017487 | 37;47 缺,25.12.23 活动义齿 → 待核(活动义齿是否入了事实?) | ⬜ |
| ⑪ | 韩俊和 BJ0U017563 | 26;27,2.3 已种 2.12 戴牙 → **① 同款,修复 1 后种植召回自动消失**(本地仅剩 47 龋·影像AI) | ✅ 随修复1 |
......@@ -35,3 +35,4 @@
- **修复 2(固定桥牙位盲点)**:桥的治疗记录牙位只写基牙,桥体悬住的缺失牙不在牙位里 → 重叠判定失败 → 误召(④⑯)。改 gap 排除:prosthodontic 同弓 ≥2 牙 → 基牙跨度区间内牙位计入已修复(FDI 转弓位序;连续多冠跨度=颗数,填洞为空操作无副作用)。已知取舍:缺牙两侧独立单冠且未做桥的罕见场景会被误销。
- **修复 3(检查文本无修复指征)**:缺牙"间隙关闭/无修复间隙"在 emr_record.exam_findings 文本里(非诊断字段)→ 召回看不到 → 误召(⑤)。gap 排除加分支:解析 exam_findings JSON,牙位 message 中"缺失/缺牙"与"间隙关闭/无修复间隙"同段共现 → 该牙计入已解决(词典 NO_RESTORATION_GAP_EXAM_PATTERNS 单一源:间隙关闭/已关闭/无修复间隙/间隙不足;共现限定排除正畸"关闭间隙"治疗噪音,全库 104 患者命中)。Layer C 上线后平移给抽取器。
- 21 人本地基线 diff:三修复共修 6 例误召(①47、⑪26;27、④31;41、⑯36、⑤26 收缩、⑥36;46),其余理由零变动、无误杀。
- **修复 4(患者无意愿)**:treatment_record subtype 含"无意愿/不愿/拒绝"(余奕铭⑧:planned"正畸取资料,患者无意愿")→ 患者拒绝该 category,对**本次诊断**排除(类目级,靠时间方向;新诊断更晚→重新可召,非永久非时效)。词典 TREATMENT_REFUSAL_SUBTYPE_PATTERNS;gap 全口分支加 NOT EXISTS、按牙分支加 resolved 拒绝。全库 1225 患者命中。验证:余奕铭正畸/牙周消失,其余 6 正畸+1 牙周召回保留(零误杀)。
......@@ -282,6 +282,12 @@ export const RESTORATION_INELIGIBLE_DX_NAMES = ['废用牙', '无功能牙'] as
/// 「间隙不足」(李强 BJ0U017401 牙36;46:缺牙十年邻牙倾倒,空间不够直接修复 → 需先正畸)
/// 也归此类:对 K08 缺牙召回效果一致(不该推"快去种植修复")。共现"缺牙"护栏已挡掉正畸
/// 拥挤语境(全库 844 段「间隙不足」仅 246 段共现缺牙,均为真·缺牙无空间)。
/// 治疗记录 subtype 里"患者拒绝该类治疗"的说法(余奕铭 BJ0F022277:planned 治疗
/// "正畸取资料,患者无意愿"/"洁牙,患者无意愿"→ 患者当场拒绝该 category)。
/// 语义=对【本次诊断】排除该治疗类目(同"已启动治疗"对称,靠时间方向;新诊断不受影响)。
/// 全库 treatment_record subtype 命中 1225 患者(正畸434/牙周267/种植103…)。
export const TREATMENT_REFUSAL_SUBTYPE_PATTERNS = ['无意愿', '不愿', '拒绝'] as const;
export const NO_RESTORATION_GAP_EXAM_PATTERNS = [
'间隙关闭',
'间隙已关闭',
......
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