Commit 1971ede6 by luoqi

fix(ingest): 拆线归外科类(根源)— 修术后复诊期误召(一线反馈①⑪)

拆线是治疗动作,原被 manifest route 分流到 review(流程类)不算治疗 → 术后复诊日
EMR 复述缺失诊断时,同日拆线无法证明"已启动" → 误召(李然47/韩俊和26;27)。

根源改法(替代先前 gap 侧特例,特例已撤销):
- manifest route 两处移出"拆线" → 走治疗路径;
- treatment_actual.yaml 加映射 拆线: surgical("种植拆线"原已映 implant);
- surgical ∈ 结构家族 resolver(STRUCTURAL_RESOLVER_CATEGORIES)→ K08 排除自然
  生效,gap/types 零改动(已回退到原状)。

本地验证(清库后仅摄 21 个一线反馈患者,PAC_COHORT_ONLY_PATIENT):
- 拆线 facts category=surgical ✓
- ① 李然 47 缺失召回消失(剩 16 牙体损伤/17;38 龋,真实待治)✓
- ⑪ 韩俊和(2.3 种植 2.12 戴牙)26;27 种植召回自动消失 — 泛化得证 ✓
核验文档精简重写(每例一行)。仅本地;部署时服务器需 reparse 存量拆线行。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
parent 1e716414
...@@ -298,6 +298,9 @@ enum_mapping: ...@@ -298,6 +298,9 @@ enum_mapping:
种植基台: implant 种植基台: implant
种植取模: implant 种植取模: implant
种植拆线: implant 种植拆线: implant
# 光杆"拆线"= 术后护理(外科动作);此前被 route 分流到 review 不算治疗 →
# 术后复诊日 EMR 复述缺失诊断时,同日拆线无法证明"已启动"→ 误召(一线反馈①)
拆线: surgical
"种植冠修复(非美学区单颗)": implant "种植冠修复(非美学区单颗)": implant
"简单种植术(非美学区,单颗)": implant "简单种植术(非美学区,单颗)": implant
"复杂种植术(非美学区,连续缺失/多颗)": implant "复杂种植术(非美学区,连续缺失/多颗)": implant
......
...@@ -458,8 +458,8 @@ transforms: ...@@ -458,8 +458,8 @@ transforms:
- 种植复查 - 种植复查
- 牙周复查 - 牙周复查
- 保持器复诊 - 保持器复诊
# 暂观 / 观察 / 拆线 # 暂观 / 观察(拆线已移出 → 治疗路径:术后护理=外科治疗动作,
- 拆线 # 2026-06-11 一线反馈① 李然47 — 拆线归 review 不算治疗致误召)
- 暂观 - 暂观
- 观察 - 观察
- 观察随访 - 观察随访
...@@ -515,8 +515,8 @@ transforms: ...@@ -515,8 +515,8 @@ transforms:
- 种植复查 - 种植复查
- 牙周复查 - 牙周复查
- 保持器复诊 - 保持器复诊
# 暂观 / 观察 / 拆线 # 暂观 / 观察(拆线已移出 → 治疗路径:术后护理=外科治疗动作,
- 拆线 # 2026-06-11 一线反馈① 李然47 — 拆线归 review 不算治疗致误召)
- 暂观 - 暂观
- 观察 - 观察
- 观察随访 - 观察随访
......
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { import {
GAP_POSTOP_CARE_REVIEW_PATTERNS,
RESTORATION_INELIGIBLE_DX_NAMES, RESTORATION_INELIGIBLE_DX_NAMES,
STRUCTURAL_DX_CODE_LIST, STRUCTURAL_DX_CODE_LIST,
type DxTreatmentRule, type DxTreatmentRule,
...@@ -120,12 +119,9 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces { ...@@ -120,12 +119,9 @@ 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`;
/// 治疗时间方向片段(按表别名生成;rtx=治疗家族 / ptx=术后护理证据 共用同一口径) const afterDxFragRtx = rule.excludeIfEverTreated
const afterDxFor = (alias: string): Prisma.Sql => ? Prisma.sql`AND rtx.occurred_at >= ${latestDxOfCode}`
rule.excludeIfEverTreated : Prisma.sql`AND rtx.occurred_at >= COALESCE(sig.occurred_at, sig.planned_for)`;
? 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
...@@ -159,20 +155,6 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces { ...@@ -159,20 +155,6 @@ 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') 术后护理 = 该牙已做手术/种植的铁证(拆线/种植复查;单一源 GAP_POSTOP_CARE_REVIEW_PATTERNS)。
-- 光杆"拆线"落 review 类(chain S4 信号,fact 保真),gap 侧将其计入处理证据 —
-- 否则术后复诊 EMR 复述诊断(如 李然 47:1/25 种植,2/7 拆线+复述"牙齿缺少")
-- 会把最新诊断推到治疗后 → 时间方向误判"未启动"。暂观/观察 = 未治疗,不在此列。
SELECT pct AS t
FROM patient_facts ptx
CROSS JOIN unnest(${toothArrSql(Prisma.sql`ptx.content->>'tooth_position'`)}) AS pct
WHERE ptx.patient_id = p.id
AND ptx.type = 'treatment_record' AND ptx.kind = 'actual'
AND ptx.status IN ('active', 'fulfilled')
AND ptx.content->>'category' = 'review'
AND ptx.content->>'subtype' LIKE ANY(${[...GAP_POSTOP_CARE_REVIEW_PATTERNS]}::text[])
${afterDxFor('ptx')}
UNION
-- (b) 同牙位以【最新诊断】为准:更晚的真实结构诊断 → 旧诊断对该牙失效 -- (b) 同牙位以【最新诊断】为准:更晚的真实结构诊断 → 旧诊断对该牙失效
SELECT ldt AS t SELECT ldt AS t
FROM patient_facts ldx FROM patient_facts ldx
......
# 一线反馈核验记录(2026-06-11 批次,21 例) # 一线反馈核验(2026-06-11,21 例)
> 背景:试点客服(user 576)认领 21 个召回任务后逐一外呼/核档,手写反馈异议。 > 试点客服(user 576)认领 21 个召回任务,外呼/核档后手写反馈。逐条核验:
> 本文档逐条核验:一线说的对不对 → 系统为什么这么判 → 根因定性 → 修复与验证。 > 拉计划理由 + 证据 facts 还原时间线 → 定性(误召/正确召回/数据问题)→ 误召修根源。
> 病历号/姓名已与库内真值比对(21/21 确认,与 assigned 计划一一对应)。 > 本地已清库只摄这 21 人(`PAC_COHORT_ONLY_PATIENT`),修复在本地先验证,未部署。
## 核验方法 ## 修复记录
1. 拉该患者 assigned plan 的 `plan_reasons`(scenario/牙位/天数/evidence factIds); **修复 1:拆线归外科类(根源)** — 光杆"拆线"原被 route 分流到 review(流程类),
2. 拉证据 fact + 该患者相关牙位/类目的全部当前版 facts,还原时间线; 不算治疗;术后复诊日 EMR 复述缺失诊断时,同日拆线无法证明"治疗已启动" → 误召。
3. 与一线反馈对照,定性:误召(算法/数据问题)or 正确召回(一线信息差)or 数据源问题; 改:manifest route 移出"拆线" + assembler 映射 `拆线: surgical`("种植拆线"原本就映
4. 误召 → 本地修复 + 新旧谓词对照验证 + 记录于此。 implant)。外科 ∈ 结构家族 resolver → K08 排除自然生效,gap 逻辑零改动。
验证:本地重摄后 拆线=surgical;① 47 召回消失、⑪ 26;27 种植召回消失(泛化得证)。
## 状态总览
## 逐例核验
| # | 病历号 | 姓名 | 反馈要点 | 核验结论 | 状态 |
|---|---|---|---|---|---| | # | 患者 | 一线反馈 → 核验结论 | 状态 |
| ① | BA38586 | 李然 | 47 已种植+拆线,不该召 | **误召 — 拆线未计为治疗证据** | ✅ 已修复 | |---|---|---|---|
| ② | BJ0U005102 | 李姝妤 | 外院矫正,4.21 引(转)院 | 待核验 | ⬜ | | ① | 李然 BA38586 | 47 已种植(1.25)+拆线(2.7)仍被召 → **误召:拆线归 review 不算治疗**;修复 1 后 47 召回消失,剩 16 牙体损伤/17;38 龋(真实待治,该召) | ✅ 已修 |
| ③ | BJ0A057103 | 秦溢泽 | 2026.1.27 取资料未回复 | 待核验 | ⬜ | | ② | 李姝妤 BJ0U005102 | 外院矫正,4.21 转院 → 待核(预计:外院治疗 PAC 无事实,属信息差,非算法错) | ⬜ |
| ④ | BJ0U016979 | 祁小夏 | 36/41 缺,11.22 修复 | 待核验 | ⬜ | | ③ | 秦溢泽 BJ0A057103 | 1.27 取资料未回复 → 待核 | ⬜ |
| ⑤ | BJ0U017412 | 王敏 | 26.37.47;26 无间隙 | 待核验 | ⬜ | | ④ | 祁小夏 BJ0U016979 | 31;41 缺,11.22 修复 → 待核 | ⬜ |
| ⑥ | BJ0U017401 | 李强 | 46.36 间隙不足→建议正畸? | 待核验 | ⬜ | | ⑤ | 王敏 BJ0U017412 | 26;37;47 缺;"26 无间隙" → 待核 | ⬜ |
| ⑦ | BJ0U015883 | 黄琳 | ✓(认可) | 待核验 | ⬜ | | ⑥ | 李强 BJ0U017401 | 36;46 间隙不足→建议正畸 → 待核 | ⬜ |
| ⑧ | BJ0F022277 | 余奕铭 | "穿"→外院;"病历"→无意愿 | 待核验 | ⬜ | | ⑦ | 黄琳 BJ0U015883 | ✓ 认可(17 缺失) | ⬜ |
| ⑨ | BJ0U016929 | 刘强 | ✓(认可) | 待核验 | ⬜ | | ⑧ | 余奕铭 BJ0F022277 | 外院/无意愿 → 待核(预计信息差) | ⬜ |
| ⑩ | BJ0U017487 | 李石明 | 37.47 缺失,活动义齿 25.12.23 | 待核验 | ⬜ | | ⑨ | 刘强 BJ0U016929 | ✓ 认可(正畸) | ⬜ |
| ⑪ | BJ0U017563 | 韩俊和 | 26.27;2.3 已种 2.12 戴牙/拆线 | 待核验(预期 ① 同款) | ⬜ | | ⑩ | 李石明 BJ0U017487 | 37;47 缺,25.12.23 活动义齿 → 待核(活动义齿是否入了事实?) | ⬜ |
| ⑫ | BJ0U017233 | 刘哲昕 | ✓ 正畸 | 待核验 | ⬜ | | ⑪ | 韩俊和 BJ0U017563 | 26;27,2.3 已种 2.12 戴牙 → **① 同款,修复 1 后种植召回自动消失**(本地仅剩 47 龋·影像AI) | ✅ 随修复1 |
| ⑬ | BJ0U016815 | 王晨 | ✓ 动态,回访过,未停,2次 | 待核验 | ⬜ | | ⑫ | 刘哲昕 BJ0U017233 | ✓ 认可(正畸) | ⬜ |
| ⑭ | BJ0U007377 | 关平 | 25 磨牙萌出 ✗;2020.9.18 增平;外院矫正→完成后全面修复 | 待核验 | ⬜ | | ⑬ | 王晨 BJ0U016815 | ✓ 动态、回访过未停 → 待核(回访记录与召回并存逻辑) | ⬜ |
| ⑮ | BJ0U017637 | 李鹏 | ✓ 15.27.47 缺失 | 待核验 | ⬜ | | ⑭ | 关平 BJ0U007377 | 25 萌出异常 ✗(2020.9.18 增平?);外院矫正→完成后全面修复 → 待核 | ⬜ |
| ⑯ | BJ0U016360 | 高美玲 | ✗ 36 缺失;EMR 固定桥修复,误:已修复 | 待核验 | ⬜ | | ⑮ | 李鹏 BJ0U017637 | ✓ 15;27;47 缺失 | ⬜ |
| ⑰ | BJ0U017344 | 陈秀玲 | ✗ 27;1.20 修复,活动义齿 | 待核验 | ⬜ | | ⑯ | 高美玲 BJ0U016360 | ✗ 36 缺失;EMR 固定桥修复已做 → 待核(桥修复是否入事实/牙位) | ⬜ |
| ⑱ | BJ0U017423 | 陈葳 | ✓ 46 缺失 | 待核验 | ⬜ | | ⑰ | 陈秀玲 BJ0U017344 | ✗ 27;1.20 已修复(活动义齿) → 待核 | ⬜ |
| ⑲ | BJ0U017440 | 王延春 | ✓ 24.25 缺失 | 待核验 | ⬜ | | ⑱ | 陈葳 BJ0U017423 | ✓ 46 缺失 | ⬜ |
| ⑳ | BJ0E012466 | 宗明 | ✗ 15.25/36.44;正畸关闭了;①病历信息正确 ②无片子 | 待核验 | ⬜ | | ⑲ | 王延春 BJ0U017440 | ✓ 24;25 缺失 | ⬜ |
| ㉑ | BJ0U010770 | 王颖 | ✓ 47;空间不足? | 待核验 | ⬜ | | ⑳ | 宗明 BJ0E012466 | ✗ 15;25/36;44;正畸已关闭间隙、无片子 → 待核(影像AI 信号 vs 正畸关隙) | ⬜ |
| ㉑ | 王颖 BJ0U010770 | ✓ 47(备注空间不足) | ⬜ |
---
## 本地基线(修复 1 后,21 人重算结果)
## ① 李然 BA38586 — 47 缺失牙召回异议 ✅ 已修复
每人当前召回理由见 `plan_reasons`(本地库),核对入口:
**一线反馈**:47 牙 2026.1.25 已种植、2.7 已拆线(治疗已启动),系统不该召回。 `SELECT ... FROM plan_reasons pr JOIN followup_plans fp ... WHERE p.medical_record_number='...'`
显著点:⑯ 高美玲 36 缺失(85 分)、⑳ 宗明 15;25;34;44 缺失(影像AI,84 分)、
**库内时间线(47 牙)**: ⑰ 陈秀玲 27 缺失(85 分)仍在召——与一线"已修复/已关隙"冲突,是下批核验重点。
```
2024-04-20 47 拔除(surgical actual)+ 种植 planned
2024-08-11 诊断「缺失 47」(K08)+ 种植 planned(患者爽约/改期,链中断)
2026-01-25 ✅ 简单种植术 actual · category=implant · 牙位47
+ 同日 planned「种植冠修复 47」+ CBCT
2026-02-07 EMR 诊断「牙齿缺少 47」(K08,数据事实,保真)
+ 同日 拆线 actual · 牙位47 · category=review ← 关键
2026-06-08 计划生成:「缺失牙未启动修复·牙位47 — 诊断 120 天前(=02-07),
未启动种植/修复/冠桥」priority 92 → 入池 → 被认领
```
**系统误判依据**:排除规则要求「同牙位的种植/修复类(implant/prosthodontic)治疗
≥ 最新诊断日」。2/7 有新诊断(数据事实,**保真,不动它**);同日的拆线本可证明
治疗已启动,但光杆"拆线"被归 category=review(流程类),**不计入治疗证据**
47 被判"未启动" → 误召。(注:"种植拆线"在 assembler 已映 implant,光杆"拆线"
才落 review —— 同一动作两种录法,一线录入习惯不可控。)
**review 类盘点(服务器全量,决定哪些算治疗证据)**:
| 类型 | subtype(带牙位条数) | 算治疗证据? |
|---|---|---|
| 术后护理(做过手术的铁证) | 拆线(3672)· 种植复查(739) | ✅ 计入 |
| 明确未治疗 | 暂观(1786)· 观察(1165)· 暂观必要时拔除(528)· 无治疗 | ❌ 恰是召回对象 |
| 流程/泛复查 | 常规复查/检查/取资料/缴费等(基本不带牙位) | ❌ 不构成证据 |
**修复**(诊断信号保真不动;只补"治疗证据"的缺口):
- `packages/types` 新增 `GAP_POSTOP_CARE_REVIEW_PATTERNS = ['%拆线%','%种植复查%']`
(单一真理源,含取舍说明);
- gap 核心 `resolvedTeethSql` 新增 (a') 分支:review 类中命中术后护理模式、带牙位、
时间方向与治疗家族同口径(≥ 最新诊断)→ 计入该牙"已处理"。
- fact 层不动(拆线仍是 review,chain S4 信号保留);仅 gap 消费侧补证据口径;
- 召回 + 潜在治疗画像共用 gap 核心,同步生效。
**验证**:
- 服务器只读:李然 47 — 新分支证据存在 = **t**(拆线 2/7 ≥ 最新诊断 2/7)✅
- 本地全量 plan 重算 exit=0、0 错误 ✅
- 影响面预估:全租户带牙位的拆线/种植复查记录覆盖 **3,338 患者**(部署后全量
重算,预期此类"术后复诊期"误召批量消失;对比报告部署时出)。
---
## ②-㉑ 待逐条核验
(按 ① 的格式逐条补充)
...@@ -228,14 +228,6 @@ export interface DxTreatmentRule { ...@@ -228,14 +228,6 @@ export interface DxTreatmentRule {
excludeIfEverTreated?: boolean; excludeIfEverTreated?: boolean;
} }
/// 术后护理型 review 子类(gap 排除侧"治疗已启动"的牙位级铁证)。
/// review 类是流程性事实(chain S4 信号,fact 层保真不动),但其中【术后护理】两项
/// 出现在某牙位上 = 该牙必然做过手术/种植(2026-06-11 一线反馈① 李然 47:1/25 种植、
/// 2/7"拆线"落 review 不算治疗 → 同日 EMR 复述"牙齿缺少47"成最新诊断 → 误召 92 分)。
/// ⚠️ 只放行术后护理:暂观/观察(带牙位 1786/1165 条)= 明确未治疗,恰是召回对象,不放;
/// 泛复查/检查不构成牙位级证据,不放。"种植拆线"在 assembler 已映 implant,不经此口。
export const GAP_POSTOP_CARE_REVIEW_PATTERNS = ['%拆线%', '%种植复查%'] as const;
export const DiagnosisTreatmentMap = { export const DiagnosisTreatmentMap = {
// 诊断码(K0x)— 跟同临床类目的推荐码(*_RECOMMENDED)窗口/临界严格一致 // 诊断码(K0x)— 跟同临床类目的推荐码(*_RECOMMENDED)窗口/临界严格一致
// W3 末扩 K00-K08 全覆盖(数据驱动:host DW 真实数据 244 万 EMR diag 频次扫描后落码,见 dw-data-source-issues #15) // W3 末扩 K00-K08 全覆盖(数据驱动:host DW 真实数据 244 万 EMR diag 频次扫描后落码,见 dw-data-source-issues #15)
......
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