Commit 5462fc71 by luoqi

fix(recall): 固定桥牙位盲点 — 基牙跨度区间计入已修复(一线反馈④⑯)

桥的治疗记录牙位只写基牙(④ 31;41 缺失由 32;33;42;43 基牙桥修复,treat 名
"冠修复(美学区)"无"桥"字),桥体悬住的缺失牙不在牙位里 → 重叠判定失败 → 误召。

修复(gap 核心 resolvedTeethSql 加 (a'') 桥区间分支):
- prosthodontic actual 且同一牙弓 ≥2 颗 → FDI 转弓位线性序(上颌 18..11|21..28,
  下颌 48..41|31..38),基牙跨度区间内全部牙位计入 resolved;
- 不依赖 subtype 含"桥"(实测桥的 treat 名不带桥字):连续多冠跨度=颗数,填洞为
  空操作无副作用;跨度>颗数(夹缺牙)即桥语义;
- 时间方向与治疗家族同口径(afterDxFor 别名化复用)。
已知取舍:缺牙两侧各做独立单冠且未做桥的罕见场景会被误销,接受。

验证(21 人本地基线 diff):④ 31;41、⑯ 36 召回消失;其余 45 条理由零变动(零误杀);
recompute exit=0。仅本地,未部署。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
parent 0758a8f6
...@@ -119,9 +119,12 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces { ...@@ -119,9 +119,12 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces {
: Prisma.sql`sig.content->>'tooth_position'`; : Prisma.sql`sig.content->>'tooth_position'`;
const wholeMouthFlag = rule.wholeMouth ? Prisma.sql`TRUE` : Prisma.sql`FALSE`; const wholeMouthFlag = rule.wholeMouth ? Prisma.sql`TRUE` : Prisma.sql`FALSE`;
const afterDxFragRtx = rule.excludeIfEverTreated /// 治疗时间方向片段(按表别名生成;rtx=治疗家族 / btx=桥区间 共用同一口径)
? Prisma.sql`AND rtx.occurred_at >= ${latestDxOfCode}` const afterDxFor = (alias: string): Prisma.Sql =>
: Prisma.sql`AND rtx.occurred_at >= COALESCE(sig.occurred_at, sig.planned_for)`; rule.excludeIfEverTreated
? Prisma.sql`AND ${Prisma.raw(alias)}.occurred_at >= ${latestDxOfCode}`
: Prisma.sql`AND ${Prisma.raw(alias)}.occurred_at >= COALESCE(sig.occurred_at, sig.planned_for)`;
const afterDxFragRtx = afterDxFor('rtx');
// §E (d) 正畸减数位(仅 missing_tooth):该牙有外科拔除 + 患者有正畸语境 → 折进 resolved 减掉。 // §E (d) 正畸减数位(仅 missing_tooth):该牙有外科拔除 + 患者有正畸语境 → 折进 resolved 减掉。
const orthoExtractBranch = cfgFlags.excludeOrthoExtractionSites const orthoExtractBranch = cfgFlags.excludeOrthoExtractionSites
...@@ -155,6 +158,41 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces { ...@@ -155,6 +158,41 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces {
AND rtx.content->>'category' = ANY(${resolverCats}::text[]) AND rtx.content->>'category' = ANY(${resolverCats}::text[])
${afterDxFragRtx} ${afterDxFragRtx}
UNION UNION
-- (a'') 桥类修复的牙位盲点(2026-06-11 一线反馈④ 祁小夏:31;41 缺失由 32;33;42;43
-- 基牙固定桥修复,治疗记录牙位只写基牙 → 与缺失牙位不重叠 → 误召)。
-- 判定:prosthodontic actual 且【同一牙弓 ≥2 颗】→ 基牙跨度区间内全部牙位计入
-- resolved(FDI 转弓位线性序:上颌 18..11|21..28,下颌 48..41|31..38)。
-- 不依赖 subtype 含"桥"(④ 实测桥的 treat 名是"冠修复(美学区)",桥字只在 EMR 文本):
-- 连续多冠(贴面/联冠)跨度=颗数 → 填洞为空操作,无副作用;跨度>颗数(夹着缺牙)
-- 即桥语义。已知取舍:缺牙两侧各做独立单冠且未做桥的罕见场景会被误销,接受。
SELECT cov.arch_tooth AS t
FROM patient_facts btx
CROSS JOIN LATERAL (
SELECT min(z.idx) AS mi, max(z.idx) AS ma, z.arch
FROM (
SELECT CASE substr(bt,1,1)
WHEN '1' THEN 9 - substr(bt,2,1)::int
WHEN '2' THEN 8 + substr(bt,2,1)::int
WHEN '4' THEN 9 - substr(bt,2,1)::int
WHEN '3' THEN 8 + substr(bt,2,1)::int END AS idx,
CASE WHEN substr(bt,1,1) IN ('1','2') THEN 'U' ELSE 'L' END AS arch
FROM unnest(${toothArrSql(Prisma.sql`btx.content->>'tooth_position'`)}) AS bt
WHERE bt ~ '^[1-4][1-8]$'
) z GROUP BY z.arch HAVING count(*) >= 2
) span
CROSS JOIN LATERAL (
SELECT CASE WHEN span.arch = 'U'
THEN CASE WHEN gi <= 8 THEN '1' || (9 - gi)::text ELSE '2' || (gi - 8)::text END
ELSE CASE WHEN gi <= 8 THEN '4' || (9 - gi)::text ELSE '3' || (gi - 8)::text END
END AS arch_tooth
FROM generate_series(span.mi, span.ma) AS gi
) cov
WHERE btx.patient_id = p.id
AND btx.type = 'treatment_record' AND btx.kind = 'actual'
AND btx.status IN ('active', 'fulfilled')
AND btx.content->>'category' = 'prosthodontic'
${afterDxFor('btx')}
UNION
-- (b) 同牙位以【最新诊断】为准:更晚的真实结构诊断 → 旧诊断对该牙失效 -- (b) 同牙位以【最新诊断】为准:更晚的真实结构诊断 → 旧诊断对该牙失效
SELECT ldt AS t SELECT ldt AS t
FROM patient_facts ldx FROM patient_facts ldx
......
...@@ -5,27 +5,32 @@ ...@@ -5,27 +5,32 @@
## 逐例核验 ## 逐例核验
| # | 患者 | 一线反馈 → 核验结论 | 状态 | | # | 患者 | 一线反馈 → 核验结论 | 状态 |
| --- | -------------- | --------------------------------------------------------------------------------------------- | ------ | | --- | -------------- | ------------------------------------------------------------------------------------------------------------------- | ------ |
| ① | 李然 BA38586 | 47 已种植(1.25)+拆线(2.7)仍被召 → **误召:拆线归 review 不算治疗**;修复 1 后 47 召回消失,剩 16 牙体损伤/17;38 龋(真实待治,该召) | ✅ 已修 | | ① | 李然 BA38586 | 47 已种植(1.25)+拆线(2.7)仍被召 → **误召:拆线归 review 不算治疗**;修复 1 后 47 召回消失,剩 16 牙体损伤/17;38 龋(真实待治,该召) | ✅ 已修 |
| ② | 李姝妤 BJ0U005102 | 外院矫正,4.21 转院 → **回访记录有提到外院**(4.21 常规回访,结果"已在外院矫正中",已入 PAC 回访表/历史联系可见,但召回未消费回访文本);客户动态数据与数仓核对中 | ⏸ 待定 | | ② | 李姝妤 BJ0U005102 | 外院矫正,4.21 转院 → **回访记录有提到外院**(4.21 常规回访,结果"已在外院矫正中",已入 PAC 回访表/历史联系可见,但召回未消费回访文本);客户动态数据与数仓核对中 | ⏸ 待定 |
| ③ | 秦溢泽 BJ0A057103 | 1.27 取资料未回复 → **召回正确**:1.27 EMR"今取正畸资料…约日复诊"+ planned 正畸/充填;2.23 回访"微信约看方案",此后无到诊 → 方案悬置,正是该召的人("取资料"归流程类不算治疗,口径正确) | ✅ 正确召回 | | ③ | 秦溢泽 BJ0A057103 | 1.27 取资料未回复 → **召回正确**:1.27 EMR"今取正畸资料…约日复诊"+ planned 正畸/充填;2.23 回访"微信约看方案",此后无到诊 → 方案悬置,正是该召的人("取资料"归流程类不算治疗,口径正确) | ✅ 正确召回 |
| ④ | 祁小夏 BJ0U016979 | 31;41 缺,11.22 修复 → 待核 | ⬜ | | ④ | 祁小夏 BJ0U016979 | 31;41 缺,11.22 修复 → **误召:固定桥牙位盲点** — 31;41 由 32;33;42;43 基牙桥修复(11.15 备牙/11.22 戴桥),治疗牙位只写基牙不含桥体 → 重叠判定失败。修复 2(桥区间)后召回消失 | ✅ 已修 |
| ⑤ | 王敏 BJ0U017412 | 26;37;47 缺;"26 无间隙" → 待核 | ⬜ | | ⑤ | 王敏 BJ0U017412 | 26;37;47 缺;"26 无间隙" → 待核 | ⬜ |
| ⑥ | 李强 BJ0U017401 | 36;46 间隙不足→建议正畸 → 待核 | ⬜ | | ⑥ | 李强 BJ0U017401 | 36;46 间隙不足→建议正畸 → 待核 | ⬜ |
| ⑦ | 黄琳 BJ0U015883 | ✓ 认可(17 缺失) | ⬜ | | ⑦ | 黄琳 BJ0U015883 | ✓ 认可(17 缺失) | ⬜ |
| ⑧ | 余奕铭 BJ0F022277 | 外院/无意愿 → 待核(预计信息差) | ⬜ | | ⑧ | 余奕铭 BJ0F022277 | 外院/无意愿 → 待核(预计信息差) | ⬜ |
| ⑨ | 刘强 BJ0U016929 | ✓ 认可(正畸) | ⬜ | | ⑨ | 刘强 BJ0U016929 | ✓ 认可(正畸) | ⬜ |
| ⑩ | 李石明 BJ0U017487 | 37;47 缺,25.12.23 活动义齿 → 待核(活动义齿是否入了事实?) | ⬜ | | ⑩ | 李石明 BJ0U017487 | 37;47 缺,25.12.23 活动义齿 → 待核(活动义齿是否入了事实?) | ⬜ |
| ⑪ | 韩俊和 BJ0U017563 | 26;27,2.3 已种 2.12 戴牙 → **① 同款,修复 1 后种植召回自动消失**(本地仅剩 47 龋·影像AI) | ✅ 随修复1 | | ⑪ | 韩俊和 BJ0U017563 | 26;27,2.3 已种 2.12 戴牙 → **① 同款,修复 1 后种植召回自动消失**(本地仅剩 47 龋·影像AI) | ✅ 随修复1 |
| ⑫ | 刘哲昕 BJ0U017233 | ✓ 认可(正畸) | ⬜ | | ⑫ | 刘哲昕 BJ0U017233 | ✓ 认可(正畸) | ⬜ |
| ⑬ | 王晨 BJ0U016815 | ✓ 动态、回访过未停 → 待核(回访记录与召回并存逻辑) | ⬜ | | ⑬ | 王晨 BJ0U016815 | ✓ 动态、回访过未停 → 待核(回访记录与召回并存逻辑) | ⬜ |
| ⑭ | 关平 BJ0U007377 | 25 萌出异常 ✗(2020.9.18 增平?);外院矫正→完成后全面修复 → 待核 | ⬜ | | ⑭ | 关平 BJ0U007377 | 25 萌出异常 ✗(2020.9.18 增平?);外院矫正→完成后全面修复 → 待核 | ⬜ |
| ⑮ | 李鹏 BJ0U017637 | ✓ 15;27;47 缺失 | ⬜ | | ⑮ | 李鹏 BJ0U017637 | ✓ 15;27;47 缺失 | ⬜ |
| ⑯ | 高美玲 BJ0U016360 | ✗ 36 缺失;EMR 固定桥修复已做 → 待核(桥修复是否入事实/牙位) | ⬜ | | ⑯ | 高美玲 BJ0U016360 | ✗ 36 缺失;EMR 固定桥修复已做 → **④ 同款(固定桥牙位盲点)**,修复 2 后召回消失 | ✅ 随修复2 |
| ⑰ | 陈秀玲 BJ0U017344 | ✗ 27;1.20 已修复(活动义齿) → 待核 | ⬜ | | ⑰ | 陈秀玲 BJ0U017344 | ✗ 27;1.20 已修复(活动义齿) → 待核 | ⬜ |
| ⑱ | 陈葳 BJ0U017423 | ✓ 46 缺失 | ⬜ | | ⑱ | 陈葳 BJ0U017423 | ✓ 46 缺失 | ⬜ |
| ⑲ | 王延春 BJ0U017440 | ✓ 24;25 缺失 | ⬜ | | ⑲ | 王延春 BJ0U017440 | ✓ 24;25 缺失 | ⬜ |
| ⑳ | 宗明 BJ0E012466 | ✗ 15;25/36;44;正畸已关闭间隙、无片子 → 待核(影像AI 信号 vs 正畸关隙) | ⬜ | | ⑳ | 宗明 BJ0E012466 | ✗ 15;25/36;44;正畸已关闭间隙、无片子 → 待核(影像AI 信号 vs 正畸关隙) | ⬜ |
| ㉑ | 王颖 BJ0U010770 | ✓ 47(备注空间不足) | ⬜ | | ㉑ | 王颖 BJ0U010770 | ✓ 47(备注空间不足) | ⬜ |
## 修复记录
- **修复 1(拆线归外科)**:光杆"拆线"原被分流到流程类不算治疗 → 术后复诊日复述缺失诊断时无法证明已启动 → 误召(①⑪)。改 manifest route + assembler 映射 `拆线: surgical`,根源生效。
- **修复 2(固定桥牙位盲点)**:桥的治疗记录牙位只写基牙,桥体悬住的缺失牙不在牙位里 → 重叠判定失败 → 误召(④⑯)。改 gap 排除:prosthodontic 同弓 ≥2 牙 → 基牙跨度区间内牙位计入已修复(FDI 转弓位序;连续多冠跨度=颗数,填洞为空操作无副作用)。已知取舍:缺牙两侧独立单冠且未做桥的罕见场景会被误销。
- 21 人本地基线 diff 验证:两修复共消 4 条误召理由(①47、⑪26;27、④31;41、⑯36),其余 45 条零变动。
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