Commit 812d1613 by luoqi

merge: 残根/残冠归 K03 → main

parents 57528b58 4d9c6f13
Pipeline #3599 failed in 0 seconds
/**
* reconcileDiagnosisCode — 宿主 std_code 错标 / 截码失真的纠偏规则。
*
* 两条规则各有一个**反向闸**,测试重点全在闸上 —— 闸失效不会报错,
* 只会让一批患者悄悄换场景(K08 缺牙 base 60 ↔ K03 牙体损伤 base 35),
* 表现为矩阵某一列莫名多/少几百格。
*
* 跑:
* pnpm test -- reconcile-diagnosis-code
*/
import { reconcileDiagnosisCode } from '@pac/types';
describe('reconcileDiagnosisCode', () => {
describe('K00 → K08(后天缺失被错标成发育障碍)', () => {
it.each(['牙齿缺少', '牙缺失', '缺牙', '后天性牙齿缺失'])('%s → K08', (name) => {
expect(reconcileDiagnosisCode('K00', name)).toBe('K08');
});
it('带「先天」→ 保持 K00(真发育障碍)', () => {
expect(reconcileDiagnosisCode('K00', '先天性牙齿缺失')).toBe('K00');
expect(reconcileDiagnosisCode('K00', '先天缺牙')).toBe('K00');
});
it('其它 K00 不动', () => {
expect(reconcileDiagnosisCode('K00', '乳牙滞留')).toBe('K00');
expect(reconcileDiagnosisCode('K00', '多生牙')).toBe('K00');
});
});
describe('K08 → K03(K08.3 牙根残留被截成 K08 缺牙)', () => {
// 生产实测:名字恰为这几个的 932 条应翻 K03
it.each(['残根', '残冠', '残根残冠', '残根/残冠'])('%s → K03', (name) => {
expect(reconcileDiagnosisCode('K08', name)).toBe('K03');
});
it('无法保留 / 不能保留 → K03', () => {
expect(reconcileDiagnosisCode('K08', '无法保留')).toBe('K03');
expect(reconcileDiagnosisCode('K08', '患牙不能保留')).toBe('K03');
});
// ⛔ 反向闸:复合诊断(生产 325 条)两个病挤在一个 name,翻 K03 会丢掉缺牙那一半
it.each([
'牙列缺损,残根',
'缺失,残根',
'残根,牙列缺损',
'牙缺失;#44残根',
'牙缺失, 残根残冠',
'16,12,26缺失,17,11,21,22,23,25,27残根',
])('复合诊断「%s」→ 保持 K08', (name) => {
expect(reconcileDiagnosisCode('K08', name)).toBe('K08');
});
it('纯缺牙类 K08 不动', () => {
expect(reconcileDiagnosisCode('K08', '后天性牙齿缺失')).toBe('K08');
expect(reconcileDiagnosisCode('K08', '牙列缺损')).toBe('K08');
expect(reconcileDiagnosisCode('K08', '牙齿缺少')).toBe('K08');
});
it('残根本来就是 K03 的不受影响(医生没填 std_code 的多数派)', () => {
expect(reconcileDiagnosisCode('K03', '残根')).toBe('K03');
expect(reconcileDiagnosisCode('K03', '残冠')).toBe('K03');
});
});
describe('边界', () => {
it('code 为 null → null', () => {
expect(reconcileDiagnosisCode(null, '残根')).toBeNull();
});
it('name 为 null → 原样返回,不误翻', () => {
expect(reconcileDiagnosisCode('K08', null)).toBe('K08');
expect(reconcileDiagnosisCode('K00', null)).toBe('K00');
});
it('无关码不动', () => {
expect(reconcileDiagnosisCode('K05', '慢性牙周炎')).toBe('K05');
expect(reconcileDiagnosisCode('K02', '龋病')).toBe('K02');
});
});
});
......@@ -303,10 +303,25 @@ export const EXTERNAL_TREATMENT_VISIT_NEG_RE =
/// "牙齿缺少/缺失/缺牙"也错标成 K00(全库 1044 例)→ 冒出"萌出异常"误召(关平 BJ0U007377 牙25:
/// 残根拔除后缺失,宿主标 K00 → 误召萌出处置)。区分符=「先天」:有先天 → 真 K00(发育障碍),
/// 无先天的缺牙义 → 后天缺失 = K08。萌出/多生牙等其它 K00 不动。
///
/// 残根/残冠纠偏(2026-08 王丽芳 TS0M013040 牙42):源 ICD 用 **K08.3 牙根残留**(医学上没标错),
/// 但 std_code 截前 3 位后跟 K08.1 后天缺失挤进同一个 K08 → 目标文案变成"邀约启动缺失牙修复
/// (种植/桥/义齿)",而临床路径是**拔除**或根管+桩核冠。K03 的 categories 含 surgical、
/// 目标文案本就写着"残根/残冠 → 充填/嵌体/拔除修复",且 POTENTIAL_LABEL_RULES 的
/// `K03 + EXTRACTION_NAME_KEYWORDS → extraction(拔牙治疗)` 正等着这批名字 —— 归 K03 才对得上。
/// 口径一致性:医生**没填** std_code 时按中文名映射本来就落 K03(全库 97,970 条);
/// 填了 K08.3xx 的这 932 条反而归错,本规则把少数派拉回多数派。
/// ⛔ 区分符=「缺失/缺牙/缺损」:复合诊断("牙列缺损,残根"/"牙缺失;#44残根",全库 325 条)
/// 两个病挤在一个 name 里,翻 K03 会丢掉缺牙那一半 → 保持 K08(基线分更高,且 K08 的已解决
/// 判定是结构家族宽集、含拔除,患者去拔了照样解除)。那是源头把多诊断挤成一行的问题,不在此修。
/// 源码不丢:transaction.rawPayload 原样留着 stdCode(K08.300x002 / K08.302 / K08.300),随时可倒查。
export function reconcileDiagnosisCode(code: string | null, nameZh: string | null): string | null {
if (code === 'K00' && nameZh && /缺(牙|失|少)/.test(nameZh) && !/先天/.test(nameZh)) {
return 'K08';
}
if (code === 'K08' && nameZh && /残根|残冠|无法保留|不能保留/.test(nameZh) && !/缺失|缺牙|缺损/.test(nameZh)) {
return 'K03';
}
return code;
}
......
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