Commit 96765e1d by luoqi

feat(召回): 检查所见写着修复体在位 —— 缺牙缺口的第三条举证路线

周燕芬 TS0M013273 上颌 11~27:那次来做**下前牙**冠修复,上颌的检查所见记着
「见活动义齿,基托边缘密合,伸展范围良好,固位力良好,无压痛」—— 上颌义齿明明在位,
却因为"这次不是为它来的"而前两条路线(治疗名 / 主诉现病史)都接不住 → 误召。
本分支接的就是「患者为别的牙来、医生顺带记了全口状况」这一类。

── 🔴 先纠正一个把 12% 当成全部的判断 ──
此前一直认为"检查所见这条路要先解决混合句按颌切分才能动"。实测推翻:
  提到义齿的检查所见 81,812 条,**71,832 条(87.8%)牙位本就在单一颌内**;
  跨颌的只有 9,980 条(12.2%),其中文中点名了颌、理论上可切分的仅 676 条(上下颌都点名 150)。
即 88% 根本不需要切分,直接加**同颌闸**跳过那 12% 就能做。按颌切分整个生产只值 150 条,
性价比远不如先做这 88%。(王志荣/童然夫恰好落在那 12% 里,是巧合。)

── 判据 ──
修复体名词 ∧ 在位状态词 ∧ ¬失效词,且该条目牙位不跨颌。
 失效词是命门:修复体**做了但不能用**仍然该召回(需要重做的不是"已修复")。

── 🔴 中文否定前缀踩了五轮才收敛,全部由生产命中逐条核出来 ──
每一轮都是把全量命中导出人工过,发现漏就补,再导一次:
 ① 「密合」子串会匹配它的否定形式 —— 赵河 BA27761「边缘欠密合」/ 陈德家 BA40818
   「密合度差,继发龋坏」/ 翟健民 BJ0C021369「修复体松动,边缘欠密合,部分崩瓷」
   / 李露 BJ0D049016「松动I,边缘不密合」全被误销。
 ② 「无义齿修复」含「义齿」却是说**没有** —— 徐磊 BJ0U001139。同一个坑的第二次。
 ③ 反方向:「松动」是坏词,而「无松动 / 未见明显松动 / 松动(-)」是好话,列进 FAIL
   会把好的一起否掉 → 改用**先 regexp_replace 抹掉好话、再判坏词**
   (与 refusal 判定里"先抹掉'拒绝拍片'再判拒绝治疗"同一手法)。
 ④ 「密合」的否定形式**逐个列举列不完**(不密合/欠密合/密合度差/密合性不佳/密合度欠佳/
   密合较差/密合度不佳/密合差…)→ 改用模式 `密合[^标点]{0,3}(差|不佳|欠佳|不良)`,
   且不许跨标点(否则「边缘密合,…口腔卫生差」会误否)。「固位」同理(周锡英 TS0B001674
   「覆盖义齿修复,固位不良」)。
 ⑤ 松动度的写法:「11Ⅰ°松」(王秋枫)/「松动二度」(侯永生)/「牙松动2度」(康振英)/
   「有明显松动」(吴实)。

── 影响面(六轮导出复核后)──
在跑计划命中 386 条,平均解 1.8 颗牙,一次解 ≥10 颗的 6 条(全是全口活动义齿/覆盖义齿在位,
已逐条看过)。周燕芬本人四条件全过(她的计划已 abandoned,故不在"在跑"的导出里)。

️ 同前两条路线:只往 resolved 加牙位、永不新增召回 → 失败方向只有少召且不报错。
 只对缺失牙(K08)开闸 —— 判据是拿 K08 的检查所见核出来的。

测试 45 例,正反例全部取自生产真实串(含上述五轮踩出来的反例 + 防误否的"尚可/较密合/
固位良好/无松动"正例)。99 suites / 1753 tests,tsc 0 error。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent 6ee7b8e9
...@@ -18,6 +18,10 @@ import { ...@@ -18,6 +18,10 @@ import {
TREATED_EVIDENCE_COMPLETION_RE, TREATED_EVIDENCE_COMPLETION_RE,
TREATED_EVIDENCE_INTENT_EXCLUDE_RE, TREATED_EVIDENCE_INTENT_EXCLUDE_RE,
TREATED_EVIDENCE_SINGLE_ARCH_ONLY, TREATED_EVIDENCE_SINGLE_ARCH_ONLY,
RESTORATION_IN_PLACE_TERMS_RE,
RESTORATION_IN_PLACE_STATE_RE,
RESTORATION_IN_PLACE_FAIL_RE,
RESTORATION_IN_PLACE_NEG_STRIP_RE,
} from '@pac/types'; } from '@pac/types';
/** /**
...@@ -42,6 +46,7 @@ export interface GapCfgFlags { ...@@ -42,6 +46,7 @@ export interface GapCfgFlags {
deferToToothDx?: boolean; // 「建议拔除」场景专用:有同牙编码病种诊断 → 让位给该病种场景(避免双召) deferToToothDx?: boolean; // 「建议拔除」场景专用:有同牙编码病种诊断 → 让位给该病种场景(避免双召)
polishImpliesRestoration?: boolean; // 缺失牙专用:缺牙位上的裸「抛光」= 修复体在位的证据(牙都没了,抛的是义齿) polishImpliesRestoration?: boolean; // 缺失牙专用:缺牙位上的裸「抛光」= 修复体在位的证据(牙都没了,抛的是义齿)
treatedEvidenceFromEmrText?: boolean; // 本次动作是预防/复查类时,再审同次病历主诉/现病史找"已治疗"证据 treatedEvidenceFromEmrText?: boolean; // 本次动作是预防/复查类时,再审同次病历主诉/现病史找"已治疗"证据
restorationInPlaceFromExam?: boolean; // 检查所见(单颌条目)写着修复体在位 → 该牙无新修复需求
} }
/// §E gap 修正 flag 的单一真理源(按 primaryCode 查)。召回 SUB_SCENARIOS 与画像标签都引此。 /// §E gap 修正 flag 的单一真理源(按 primaryCode 查)。召回 SUB_SCENARIOS 与画像标签都引此。
...@@ -54,6 +59,7 @@ export const GAP_FLAGS_BY_PRIMARY: Record<string, GapCfgFlags> = { ...@@ -54,6 +59,7 @@ export const GAP_FLAGS_BY_PRIMARY: Record<string, GapCfgFlags> = {
excludeCongenitalName: true, excludeCongenitalName: true,
polishImpliesRestoration: true, polishImpliesRestoration: true,
treatedEvidenceFromEmrText: true, treatedEvidenceFromEmrText: true,
restorationInPlaceFromExam: true,
}, },
// 「建议拔除」独立场景:有同牙编码病种诊断 → 让位给病种场景,不重复召(残根=K03、阻生=K01…) // 「建议拔除」独立场景:有同牙编码病种诊断 → 让位给病种场景,不重复召(残根=K03、阻生=K01…)
EXTRACTION_RECOMMENDED: { EXTRACTION_RECOMMENDED: {
...@@ -323,6 +329,42 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces { ...@@ -323,6 +329,42 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces {
${afterDxFor('tvx')}` ${afterDxFor('tvx')}`
: Prisma.empty; : Prisma.empty;
// (a'''''''') 检查所见写着修复体在位 —— 第三条举证路线(见 [[RESTORATION_IN_PLACE_TERMS_RE]])。
// 前两条(治疗名 / 主诉现病史)都要求患者**为那副修复体而来**;这条接的是"为别的牙来、
// 医生顺带记了全口状况"的那一类。周燕芬 TS0M013273 那次来做下前牙冠修复,上颌 11~27
// 记着「见活动义齿,基托边缘密合,伸展范围良好,固位力良好,无压痛」→ 上颌义齿明明在位。
// 🔴 同颌闸是前提:一条记录常挂一整排牙位,句子里上下颌状态可能相反(王志荣「下颌固位可,
// 上颌固位稍差」/ 童然夫「上颌卡环紧,下颌无法就位」)→ 跨颌条目一律跳过。
// 代价很小:生产 81,812 条提到义齿的检查所见里 87.8% 牙位本就单颌。
// ⛔ 失效词一票否决:做了但不能用(无法就位/脱落/折断)仍然该召回。
const restorationInPlaceBranch = cfgFlags.restorationInPlaceFromExam
? Prisma.sql`
UNION
SELECT ript AS t
FROM (
SELECT rix.content->>'exam_findings' AS ef_text, rix.occurred_at, rix.planned_for
FROM patient_facts rix
WHERE rix.patient_id = p.id
AND rix.type = 'emr_record' AND rix.status IN ('active', 'fulfilled')
AND rix.content->>'exam_findings' ~ '^\\['
AND rix.content->>'exam_findings' ~ ${RESTORATION_IN_PLACE_TERMS_RE}
${afterDxFor('rix')}
) rsrc
CROSS JOIN LATERAL jsonb_array_elements(rsrc.ef_text::jsonb) AS rief
CROSS JOIN unnest(${toothArrSql(Prisma.sql`rief->>'toothPosition'`)}) AS ript
WHERE (rief->>'message') ~ ${RESTORATION_IN_PLACE_TERMS_RE}
AND (rief->>'message') ~ ${RESTORATION_IN_PLACE_STATE_RE}
-- 🔴 先抹掉"否定式好话"(无松动/松动(-)…)再判失效词,否则裸「松动」会把好的一起否掉
AND regexp_replace(rief->>'message', ${RESTORATION_IN_PLACE_NEG_STRIP_RE}, '', 'g')
!~ ${RESTORATION_IN_PLACE_FAIL_RE}
-- 🔴 同颌闸:该条目牙位跨上下颌 → 整条跳过(句内可能上下颌状态相反)
AND NOT (
EXISTS (SELECT 1 FROM unnest(${toothArrSql(Prisma.sql`rief->>'toothPosition'`)}) ru WHERE ru ~ '^[12]')
AND
EXISTS (SELECT 1 FROM unnest(${toothArrSql(Prisma.sql`rief->>'toothPosition'`)}) rl WHERE rl ~ '^[34]')
)`
: Prisma.empty;
const resolvedTeethSql = Prisma.sql` const resolvedTeethSql = Prisma.sql`
(SELECT COALESCE(array_agg(DISTINCT t), ARRAY[]::text[]) FROM ( (SELECT COALESCE(array_agg(DISTINCT t), ARRAY[]::text[]) FROM (
-- (a) 治疗家族 resolver(afterDx):同牙诊断后做了 resolverCats 家族里任一治疗 -- (a) 治疗家族 resolver(afterDx):同牙诊断后做了 resolverCats 家族里任一治疗
...@@ -423,6 +465,7 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces { ...@@ -423,6 +465,7 @@ export function buildGapCore(input: GapCoreInput): GapCorePieces {
${reviewImpliesBranch} ${reviewImpliesBranch}
${polishImpliesBranch} ${polishImpliesBranch}
${treatedEvidenceBranch} ${treatedEvidenceBranch}
${restorationInPlaceBranch}
${orthoExtractBranch} ${orthoExtractBranch}
${deferDxBranch} ${deferDxBranch}
) u)`; ) u)`;
......
/**
* RESTORATION_IN_PLACE_* —— 检查所见写着修复体在位(缺牙缺口的第三条举证路线)。
*
* 前两条(治疗名 / 主诉现病史)都要求患者**为那副修复体而来**;这条接的是
* "为别的牙来、医生顺带记了全口状况"的那一类 —— 周燕芬 TS0M013273 即是。
*
* 判据 = 修复体名词 ∧ 在位状态词 ∧ ¬失效词,且条目牙位不跨颌。
* 🔴 失效词是命门:修复体**做了但不能用**仍然该召回。
*
* 跑:
* pnpm test -- restoration-in-place-from-exam
*/
import {
RESTORATION_IN_PLACE_TERMS_RE,
RESTORATION_IN_PLACE_STATE_RE,
RESTORATION_IN_PLACE_FAIL_RE,
RESTORATION_IN_PLACE_NEG_STRIP_RE,
} from '@pac/types';
import { GAP_FLAGS_BY_PRIMARY } from '../src/modules/clinical-gap/potential-treatment-gap.sql';
const TERMS = new RegExp(RESTORATION_IN_PLACE_TERMS_RE);
const STATE = new RegExp(RESTORATION_IN_PLACE_STATE_RE);
const FAIL = new RegExp(RESTORATION_IN_PLACE_FAIL_RE);
const NEG = new RegExp(RESTORATION_IN_PLACE_NEG_STRIP_RE, 'g');
/** 模拟三条件(不含同颌闸 —— 那是牙位层面的,由 SQL 负责) */
const inPlace = (msg: string): boolean =>
TERMS.test(msg) && STATE.test(msg) && !FAIL.test(msg.replace(NEG, ''));
describe('正例 —— 生产真实检查所见,判为修复体在位', () => {
it.each([
// 🔴 周燕芬 TS0M013273 上颌 11~27(单颌条目)—— 本条路线的触发个案
'见活动义齿,基托边缘密合,伸展范围良好,固位力良好,无压痛',
'下颌覆盖牙齿在位,无松动,就位可,边缘贴合;杆卡无松动,牙龈无红肿',
'可见全瓷冠修复,边缘密合邻接良好,咬合合适,牙龈及黏膜未见明显异常',
'见全冠修复体,叩(-),无松动,牙龈未见明显异常',
'义齿密合良好,牙龈无红肿',
// ⛔ 「固位良好/固位稳定」不得被「固位…不良」那条贬义模式误否
'铸造活动义齿,固位稳定,咬合关系良好。', // 邢五一 BJ0A078837
'缺失,粘膜无异常,牙槽嵴有明显吸收。活动义齿固位良好。', // 孙玉娟 GZ0A031993
// 🔴 「无松动」类是好话,抹掉后不得被裸「松动」否掉
'金属烤瓷冠修复桥体,边缘密合,未见明显松动,冷叩诊无不适', // 陈铭燕 GZ0A016429
'烤瓷冠,边缘密合,探痛(—),冷热(—),叩(—)松动(—)', // 马琦 SC10794
'活动义齿在位,牙龈粘膜未见明显异常,37牙体缺损至平龈,叩(-),未见明显松动', // 李太花
'46口内未探及,45-47烤瓷固定桥修复,边缘密合度一般,无明显松动。', // 丁亚茹
// ⛔ 「尚可/尚密合/较密合」是可接受状态,不得被贬义模式误否
'已行固定桥修复,边缘密合度尚可,冷不敏,叩(-),不松动', // 陈晓晖 BJ0D033690
'冠桥修复体,边缘较密合,叩诊无不适', // 李科伟 BD13007
'全瓷冠桥修复,近中单端悬臂,25#缺失,冠边缘位于龈上尚密合,叩-,龈-,松-', // 周斯男 SH0L005714
// ⛔ 贬义词跨标点不得误否:「边缘密合」后面另起一句说别的差
'种植冠修复,边缘密合,牙龈无红肿,口腔卫生差'
])('%s', (msg) => {
expect(inPlace(msg)).toBe(true);
});
});
describe('🔴 反例 —— 做了但不能用 / 还没做,必须仍然召回', () => {
it.each([
// 王茜 BJ0F028868:外院假牙做好了却装不上
'外院制作右下活动假牙无法就位一周',
'活动义齿基托折断,无法佩戴',
'烤瓷冠脱落,牙体缺损,边缘尚密合', // 有"密合"但也有"脱落" → 失效词一票否决
'义齿破损,固位力良好但影响进食',
'牙列缺损,建议活动义齿修复,患者考虑中',
'缺牙区黏膜良好,要求义齿修复',
'上颌活动义齿不贴合,需重衬', // 童然夫/周根娣同类:不贴合 = 需处理
// 🔴 生产实测第一版误销的那批 —— 「密合」子串会匹配它的否定形式
'固定桥修复,边缘欠密合,龈缘红肿', // 赵河 BA27761
'锤造冠固定桥修复,密合度差,继发龋坏', // 陈德家 BA40818
'固定桥修复,边缘密合度欠佳,牙龈(-)叩(-)松动(-)', // 张华 BJ0A054066
'固定桥修复,边缘密合性不佳,46近中可探入', // 王竞男 BJ0A090886
'金属烤瓷冠桥修复长桥,修复体松动。边缘欠密合,部分崩瓷,咬合欠佳', // 翟健民 BJ0C021369
'种植修复体,松动I,边缘不密合', // 李露 BJ0D049016
'缺失,活动义齿修复,不可摘除,不密合', // 王秀梅 BJ0D030624
'胶连活动义齿修复,义齿与软组织不密合,客人自觉舌侧基托厚,漏风,影响发音', // 朱少宇 BJ0A081328
'固定桥,崩瓷,边缘不密合,叩(-),不松,龈水肿', // 余健 BJ0D024381
// 🔴 否定前缀第二次踩到:「无义齿修复」含「义齿」却是说没有
'缺失,无义齿修复,46,47均向近中移位,46烤瓷冠修复,边缘密合,无异常', // 徐磊 BJ0U001139
// 基牙松动度的另两种写法
'烤瓷冠修复体,边缘较密合,11Ⅰ°松,21Ⅱ°松,22Ⅰ-Ⅱ°松', // 王秋枫 BJ0D053568
'可见全冠,边缘密合,松动二度,叩(+)', // 侯永生 BJ0R008817
'瓷修复体存,边缘密合,牙松动2度,PD:8mm', // 康振英 GZ0A019990
'固定桥完好,边缘密合较差,叩痛(-),有明显松动', // 吴实 SH0T009924
// 🔴 第四轮:「密合」的否定形式列不完,改用模式匹配
'单端固定桥;44-47烤瓷牙+树脂基托;牙冠形态不良,密合度不佳,局部牙龈红肿', // 张顺宝 SH0K018368
'金属全冠修复体,边缘密合差,卡探针', // 姜华 ZJ0B010604
'覆盖义齿修复,固位不良,port基台稳定无松动,牙龈未见异常', // 周锡英 TS0B001674
])('%s → 不作数', (msg) => {
expect(inPlace(msg)).toBe(false);
});
it('没有修复体名词,只是牙龈好 → 不作数', () => {
expect(inPlace('牙龈未见明显异常,无压痛')).toBe(false);
});
it('有修复体但没说状态 → 不作数', () => {
expect(inPlace('见活动义齿')).toBe(false);
});
});
describe('表自身自洽', () => {
it('⛔ 不收裸「冠」—— 天然牙冠也叫冠', () => {
expect(RESTORATION_IN_PLACE_TERMS_RE).not.toMatch(/(^|\|)(\||$)/);
expect(inPlace('牙冠完好,无松动')).toBe(false);
// 代价:省略定语的「冠边缘密合」接不住。那类患者通常另有治疗名/主诉证据
// (陶美玉 TS0K070812 就被「种植复查」和处置「调合」两条路各接住一次),
// 不值得为它放宽到裸「冠」。
expect(inPlace('冠边缘密合,牙龈未见异常')).toBe(false);
});
it('🔴 失效词必须含"无法就位/脱落/折断" —— 做了但不能用仍该召回', () => {
for (const w of ['无法就位', '脱落', '折断', '不贴合', '不密合', '欠密合', '崩瓷']) {
expect(RESTORATION_IN_PLACE_FAIL_RE).toContain(w);
}
});
it('🔴 只对缺失牙(K08)开闸 —— 判据是拿 K08 的检查所见核出来的', () => {
const on = Object.entries(GAP_FLAGS_BY_PRIMARY)
.filter(([, f]) => f.restorationInPlaceFromExam === true)
.map(([code]) => code);
expect(on).toEqual(['K08']);
});
it('三个正则都可编译', () => {
for (const re of [
RESTORATION_IN_PLACE_TERMS_RE,
RESTORATION_IN_PLACE_STATE_RE,
RESTORATION_IN_PLACE_FAIL_RE,
]) {
expect(() => new RegExp(re)).not.toThrow();
}
});
});
...@@ -605,6 +605,99 @@ export const TREATED_EVIDENCE_INTENT_EXCLUDE_RE = ...@@ -605,6 +605,99 @@ export const TREATED_EVIDENCE_INTENT_EXCLUDE_RE =
'|手术|拔除|拔牙|导板|转诊|种植科|种植前|取出|失败|重新种' + '|手术|拔除|拔牙|导板|转诊|种植科|种植前|取出|失败|重新种' +
'|脱落|无法就位|不能就位|未就位|无法佩戴|不能佩戴|无法使用'; '|脱落|无法就位|不能就位|未就位|无法佩戴|不能佩戴|无法使用';
/**
* 「检查所见写着修复体在位」—— 缺牙缺口的**第三条举证路线**(前两条:治疗名 / 主诉现病史)。
*
* ── 为什么需要第三条 ──
* 前两条都要求患者**为那副修复体而来**:治疗名要当次动了它,主诉要当次是为它复查。
* 但一大类患者是**为别的牙来的**,那副义齿只出现在医生的全口检查所见里:
* 周燕芬 TS0M013273:那次来做**下前牙**冠修复,上颌 11~27 的记录是
* 「见活动义齿,基托边缘密合,伸展范围良好,固位力良好,无压痛」—— 上颌义齿明明在位,
* 却因为"这次不是为它来的"而两条路都接不住 → 判「缺失牙未启动修复」误召。
*
* ── 🔴 同颌闸是这条路线成立的前提 ──
* 检查所见一条记录常挂一整排牙位,句子里可能上下颌状态相反:
* 王志荣 TS0K051842:「上下颌吸附性义齿修复,**下颌固位可**,**上颌固位稍差**,义齿易脱落」
* 童然夫 TS0M013276:「口内活动义齿修复,**上颌**义齿卡环紧,**下颌**义齿无法完全就位」
* 整条牙位一起解会误销该召的那一半。所以**牙位跨上下颌的条目一律跳过**。
* 生产实测这个代价很小:提到义齿的 81,812 条检查所见里,**71,832 条(87.8%)牙位本就单颌**,
* 跨颌的只有 9,980 条(12.2%),其中文中点名了颌、理论上可切分的仅 676 条(上下颌都点名 150 条)。
* ⛔ 即"先做按颌切分再动这条路"是把 12% 当成了全部 —— 88% 根本不需要切分。
* (王志荣/童然夫恰好落在那 12% 里,是巧合,不代表切分值得做:整个生产只值 150 条。)
*
* ── 判据 ──
* 修复体名词 ∧ 在位状态词 ∧ ¬失效词,且该条目牙位不跨颌。
* ⛔ 失效词是这条路线的命门:修复体**做了但不能用**仍然该召回 ——
* 「无法就位」(王茜 BJ0F028868 外院假牙)/「脱落」/「折断」都表示需要重做,不是已修复。
*
* ⚠️ 只往 resolved 加牙位,永不新增召回 → 失败方向只有少召,且不报错。
* 消费方:potential-treatment-gap.sql 的 resolvedTeethSql(召回与画像共用)。
*/
/// 修复体名词 —— 出现即说明"有个修复体在讨论"。
/// ⛔ 不收裸「冠」:天然牙冠也叫冠(「牙冠完好」说的是真牙)。代价是「冠边缘密合」这类
/// 省略了定语的串接不住 —— 那类患者通常另有治疗名/主诉证据(陶美玉 TS0K070812 即是),
/// 不值得为它放宽到裸「冠」。
/// 「杆卡」= 种植覆盖义齿的固位杆,只可能属于修复体,无歧义。
export const RESTORATION_IN_PLACE_TERMS_RE =
'义齿|假牙|修复体|全冠|烤瓷冠|全瓷冠|种植冠|固定桥|杆卡';
/// 在位/状态良好 —— 医生描述这副修复体现在是好的
export const RESTORATION_IN_PLACE_STATE_RE = '密合|良好|在位|无松动|无压痛|固位可|完好|无异常';
/// 🔴 判失效词**之前**先抹掉的"否定式好话" —— 否则子串匹配分不开正反。
///
/// 中文否定前缀在这段文本里出现了三种方向,堆词堆不完:
/// ① 好词被否定:「密合」← 「**不**密合 / **欠**密合 / 密合度**差**」
/// ② 存在被否定:「义齿」← 「**无**义齿修复」(徐磊 BJ0U001139)
/// ③ 坏词被否定:「松动」← 「**无**松动 / **未见明显**松动 / 松动(-)」
/// ①② 的否定形式有限,逐个列进 FAIL 即可;③ 反过来 —— 坏词才是主形,好话是它的否定,
/// 列进 FAIL 会把好的一起否掉。所以对 ③ 采用本表:**先抹掉好话,再判坏词**。
/// 这跟 refusal 判定里「先 regexp_replace 抹掉'拒绝拍片'再判是否拒绝治疗」是同一手法。
export const RESTORATION_IN_PLACE_NEG_STRIP_RE = [
'无明显松动', '未见明显松动', '未见松动', '无松动', '不松动', '未松动',
'松动(-)', '松动\\(-\\)', '松动(—)', '松(-)', '松\\(-\\)', '不松',
].join('|');
/// 🔴 失效/未完成 —— 命中即不作数。做了但不能用,仍然该召回(需要重做的修复体不是"已修复")。
///
/// ⛔ **否定前缀必须逐个列全**:状态词是子串匹配,「密合」会匹配「**不**密合」「**欠**密合」
/// 「密合度**差**」「密合性**不佳**」—— 生产实测第一版就这么误销了一大批:
/// 赵河 BA27761「固定桥修复,边缘欠密合」/ 陈德家 BA40818「密合度差,继发龋坏」/
/// 翟健民 BJ0C021369「修复体松动,边缘欠密合,部分崩瓷」/ 李露 BJ0D049016「松动I,边缘不密合」。
/// 这跟「清洁牙面 vs 洁牙」是同一类坑(中文否定前缀让子串匹配失效),只能靠 FAIL 一票否决兜。
/// ⛔ 「松动」同理:STATE 收的是「无松动」,但「松动I」「修复体松动」「冠松动」是坏的 ——
/// PG ARE 不支持后顾断言,只能把坏形式逐个列出来。
export const RESTORATION_IN_PLACE_FAIL_RE = [
// 🔴 修复体**根本不存在**的否定说法 —— 与「不密合」同类的否定前缀坑,第二次踩到:
// 徐磊 BJ0U001139「缺失,**无义齿修复**,46,47均向近中移位」—— 含「义齿」却是说没有。
'无义齿', '无修复体', '未行义齿', '未行修复', '未见修复',
// 装不上 / 用不了
'无法就位', '不能就位', '未就位', '无法佩戴', '不能佩戴', '无法使用', '不可摘除', '漏风',
// 密合类的否定形式(子串匹配防不住,必须列全)
// ⚠️ 「密合」的否定形式**逐个列举列不完**(实测四轮才收敛:不密合/欠密合/密合度差/
// 密合性不佳/密合度欠佳/密合较差/密合度不佳/密合差…)。改用模式:密合 + ≤3 字 + 贬义词,
// 且中间不许跨标点(否则「边缘密合,龈缘红肿…咬合差」会误否)。
// ⛔ 「密合度尚可 / 尚密合 / 较密合」是可接受状态,刻意不收进贬义词。
// 「固位」同理(周锡英 TS0B001674「覆盖义齿修复,固位不良」)—— 用同一套模式,
// ⛔ 注意 STATE 里收的是「固位可 / 固位良好 / 固位稳定」,贬义只由本模式判。
'不密合', '欠密合', '不贴合', '密合[^,,。;;]{0,3}(差|不佳|欠佳|不良)',
'固位[^,,。;;]{0,3}(差|不佳|欠佳|不良)', '形态不良',
// 破坏 / 松动的坏形式
'脱落', '折断', '破损', '崩瓷', '绷瓷', '裂',
// ⚠️ 括号与 + 必须转义(PG ARE 与 JS 同):'松动(+' 里的 ( 会开组、+ 无物可重复 → 正则编译失败
'修复体松动', '冠松动', '桥松动', '义齿松动', '松动I', '松动Ⅰ', '松动Ⅱ', '松动Ⅲ',
'松动\\(\\+', '松动(\\+', '松动度',
// 基牙松动度的各种写法(王秋枫「11Ⅰ°松」/ 侯永生「松动二度」/ 康振英「牙松动2度」/
// 吴实「有明显松动」)。⚠️ 收裸「松动」是**安全的** —— 好话形式已由
// [[RESTORATION_IN_PLACE_NEG_STRIP_RE]] 在判定前抹掉。
'°松', '度松', '松动',
// 还有问题要处理
'继发龋', '缝隙', '可探入',
// 还没做
'未修复', '待修复', '建议', '要求', '拟', '考虑', '择期',
].join('|');
export function resolverCategoriesFor(code: string): readonly PACTreatmentCategory[] { export function resolverCategoriesFor(code: string): readonly PACTreatmentCategory[] {
if (STRUCTURAL_DX_CODES.has(code)) return STRUCTURAL_RESOLVER_CATEGORIES; if (STRUCTURAL_DX_CODES.has(code)) return STRUCTURAL_RESOLVER_CATEGORIES;
const rule = lookupDxTreatment(code); const rule = lookupDxTreatment(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