Commit 06ae44f0 by luoqi

merge: 含词大小写 + 补四词 → test

parents a37e654e 66ed2e46
Pipeline #3601 failed in 0 seconds
...@@ -23,7 +23,11 @@ keyword_mapping: ...@@ -23,7 +23,11 @@ keyword_mapping:
# 修复/备牙/取模:收 C.10 治疗痕迹(修复后/修复术后/全瓷桥修复后/备牙后/重新取模后)。 # 修复/备牙/取模:收 C.10 治疗痕迹(修复后/修复术后/全瓷桥修复后/备牙后/重新取模后)。
# 排在 implant/endo/ortho/cosmetic 之后 → 种植修复→implant、根管…修复→endo 仍被前面抢走。 # 排在 implant/endo/ortho/cosmetic 之后 → 种植修复→implant、根管…修复→endo 仍被前面抢走。
# "嵌体修复"等 restorative 已由上方精确 enum 先命中,不受裸"修复"影响。 # "嵌体修复"等 restorative 已由上方精确 enum 先命中,不受裸"修复"影响。
- { value: prosthodontic, any: [, , 义齿, 修复体, 修复, 备牙, 取模, 桩核, 桩冠, 戴牙, 全瓷, 烤瓷, 重新粘接] } # 加牙/活托(2026-08-27 过碧霞 TS0B006258 牙12;13;21):活动义齿在原基托上加牙 —— DW 实测
# 裸「加牙」58 条(50 带牙位)全部落 _default 被丢,患者整次修复在系统里不存在 → 误召缺失牙。
# 变体「活动义齿加牙/义齿加牙/取模加牙」已被 义齿/取模 收,只有裸词和「活托X」漏。
# 碰撞已验:「根管治疗加牙周治疗」含"加牙"但被上方 ② endodontic 的「根管」先吃,安全。
- { value: prosthodontic, any: [, , 义齿, 修复体, 修复, 备牙, 取模, 桩核, 桩冠, 戴牙, 全瓷, 烤瓷, 重新粘接, 加牙, 活托] }
# ⭐ 明确充填动作(充填/去腐/备洞/嵌体/垫底/补牙)压过 preventive: # ⭐ 明确充填动作(充填/去腐/备洞/嵌体/垫底/补牙)压过 preventive:
# "去腐,备洞,...树脂充填,窝沟封闭"(dispose 散文)是补牙(restorative),不能因含"窝沟封闭" # "去腐,备洞,...树脂充填,窝沟封闭"(dispose 散文)是补牙(restorative),不能因含"窝沟封闭"
# 被误判 preventive → 非 resolver → 误召(马思煦@46)。注:"开髓去腐"等根管语境已被上方 endodontic 先收。 # 被误判 preventive → 非 resolver → 误召(马思煦@46)。注:"开髓去腐"等根管语境已被上方 endodontic 先收。
...@@ -33,11 +37,25 @@ keyword_mapping: ...@@ -33,11 +37,25 @@ keyword_mapping:
# 材料弱词:放 preventive 后 —— "玻璃离子窝沟封闭" 已被上面 preventive 收;裸"树脂/玻璃离子"→ restorative # 材料弱词:放 preventive 后 —— "玻璃离子窝沟封闭" 已被上面 preventive 收;裸"树脂/玻璃离子"→ restorative
- { value: restorative, any: [树脂, 玻璃离子] } - { value: restorative, any: [树脂, 玻璃离子] }
# 牙周去裸"洁牙"(避开自由文本"清洁牙面"误命中);洁牙/全口洁牙等精确词由上方 enum_mapping 兜 # 牙周去裸"洁牙"(避开自由文本"清洁牙面"误命中);洁牙/全口洁牙等精确词由上方 enum_mapping 兜
- { value: periodontic, any: [洁治, 洗牙, 龈上, 龈下, 刮治, 牙周, 喷砂, 细洁] } # SRP = scaling & root planing(龈下刮治+根面平整)的国际通用缩写,正是 perio_no_srp 子场景
# 要认的那个治疗。DW 实测 SRP 648 + 局部SRP 40 + SRP+冲洗上药 76,此前全部落 _default 被丢 ——
# 患者做完牙周基础治疗仍被召「牙周炎未做基础治疗」。含 SRP 的 DW 全量已扫,无非牙周语义。
- { value: periodontic, any: [洁治, 洗牙, 龈上, 龈下, 刮治, 牙周, 喷砂, 细洁, SRP] }
- { value: preventive, any: [涂氟, 防龋, OHI, 口腔卫生宣教] } - { value: preventive, any: [涂氟, 防龋, OHI, 口腔卫生宣教] }
- { value: surgical, any: [拔除, 拔牙, 切开, 翻瓣, 切除, 系带, 脓肿, 囊肿, 植骨] } # 缝线:「拆线」已在宿主精确表(拆线→surgical,37,253 条正常落库),但措辞变体
# 「拆除缝线」699 条(665 带牙位)漏了 —— 同一临床动作。收「缝线」一并覆盖变体。
# 碰撞已验:「拆除」不含「拔除/切除」,不会被本行前面的词误命中。
- { value: surgical, any: [拔除, 拔牙, 切开, 翻瓣, 切除, 系带, 脓肿, 囊肿, 植骨, 缝线] }
- { value: pediatric, any: [乳牙, 儿童, 年轻恒牙] } - { value: pediatric, any: [乳牙, 儿童, 年轻恒牙] }
# ⛔ 刻意不收(2026-08-27 评估过,别"顺手补全"):
# 冲洗 / 换药 / 上药(~3,500 条)—— **对症处置 ≠ 根治**。阻生牙冠周炎「冲洗上药」之后那颗牙
# 仍然要拔;归 surgical 会把 K01 阻生牙缺口消掉 = 该召的不召(少召不报错,最难发现)。
# 调磨(817)/ 试戴(213)—— 姑息调整 / 修复流程中途,都不代表问题已解决。
# ⚠️ 根因:本表的 category 同时被两处消费 —— ①治疗史完整性 ②resolver 判"缺口已解"。
# 对这几个词我们想要①不想要②,而现在只有一个旋钮,只能二选一(现选"都不要"=丢弃)。
# 要两全需在 category 之外再加一维"是否根治",那是更大的改动,不在本表解决。
# actual 语义:这些词开头的从句 = 本次没做(条件/未来/建议)→ 切段丢弃后再匹配。 # actual 语义:这些词开头的从句 = 本次没做(条件/未来/建议)→ 切段丢弃后再匹配。
# 例 "充填,必要时根管治疗" → 丢"必要时根管治疗" → "充填" → restorative(不误判成 endodontic)。 # 例 "充填,必要时根管治疗" → 丢"必要时根管治疗" → "充填" → restorative(不误判成 endodontic)。
keyword_strip_clauses: [必要时, 如需, 择期, 建议, 推荐, 考虑, ] # 若…=条件从句(若牙面脱矿,建议充填),非本次实际治疗 keyword_strip_clauses: [必要时, 如需, 择期, 建议, 推荐, 考虑, ] # 若…=条件从句(若牙面脱矿,建议充填),非本次实际治疗
/**
* classifyByKeyword 大小写不敏感 + 2026-08-27 补的四个词(SRP / 加牙 / 活托 / 缝线)。
*
* 规则表按序裁决(首个命中即用),所以新词最大的风险不是"没命中",而是**抢了前面该赢的**。
* 下面每个新词都锁一条碰撞用例(生产 DW 里真实存在的串)。
*
* 跑:
* pnpm test -- keyword-case-and-terms
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as yaml from 'js-yaml';
import { classifyByKeyword } from '../src/modules/sync/assembler/assembler-engine';
import type { KeywordRule } from '../src/modules/sync/assembler/assembler.schema';
/// 直接读生产字典,不在测试里抄一份(抄了必漂)
const DICT = path.join(__dirname, '../data/_shared/dict/treatment-category-actual-rules.yaml');
const doc = yaml.load(fs.readFileSync(DICT, 'utf8')) as {
keyword_mapping: { category: KeywordRule[] };
keyword_strip_clauses?: string[];
};
const RULES = doc.keyword_mapping.category;
const STRIP = doc.keyword_strip_clauses;
const cat = (s: string) => classifyByKeyword(s, RULES, STRIP);
describe('大小写不敏感', () => {
it('RCT 大小写混写都归 endodontic(生产:RCT 3,122 落库 / rct 725 曾全丢)', () => {
for (const s of ['RCT', 'rct', 'Rct', 'rCT']) expect(cat(s)).toBe('endodontic');
});
it('复合串也不敏感', () => {
expect(cat('rct+冠修复')).toBe('endodontic'); // 根管优先于冠
expect(cat('局部srp')).toBe('periodontic');
});
it('中文不受影响(toLowerCase 对中文恒等)', () => {
expect(cat('根管治疗')).toBe('endodontic');
expect(cat('全口龈上洁治')).toBe('periodontic');
});
});
describe('SRP → periodontic', () => {
it.each(['SRP', '局部SRP', 'SRP+冲洗上药'])('%s', (s) => {
expect(cat(s)).toBe('periodontic');
});
});
describe('加牙 / 活托 → prosthodontic', () => {
it.each(['加牙', '活托加牙', '加牙后初戴', '活托加牙后初戴'])('%s', (s) => {
expect(cat(s)).toBe('prosthodontic');
});
// ⛔ 碰撞:「加牙」是「加牙周治疗」的子串,必须让根管先赢
it('「根管治疗加牙周治疗或酌情拔除」→ endodontic,不被加牙抢走', () => {
expect(cat('根管治疗加牙周治疗或酌情拔除')).toBe('endodontic');
});
it('已被旧词命中的变体行为不变', () => {
expect(cat('活动义齿加牙')).toBe('prosthodontic'); // 义齿
expect(cat('取模加牙')).toBe('prosthodontic'); // 取模
});
});
describe('缝线 → surgical', () => {
it.each(['拆除缝线', '拆除缝线,局部冲洗'])('%s', (s) => {
expect(cat(s)).toBe('surgical');
});
it('种植拆线仍归 implant(种植优先)', () => {
expect(cat('种植拆线')).toBe('implant');
});
});
describe('⛔ 刻意不收的词仍然不命中(收了会误销召回)', () => {
it.each(['冲洗', '换药', '上药', '调磨', '试戴', '观察', '定期观察'])('%s → undefined', (s) => {
expect(cat(s)).toBeUndefined();
});
it('「冲洗上药」不归 surgical —— 阻生牙冠周炎处置后那颗牙仍要拔', () => {
expect(cat('冲洗上药')).toBeUndefined();
});
});
describe('既有裁决顺序不被新词打乱', () => {
it.each([
['种植二期', 'implant'],
['根管治疗(磨牙)', 'endodontic'],
['二期隐形矫正', 'orthodontic'],
['冠修复(非美学区)', 'prosthodontic'],
['非美学区树脂充填', 'restorative'],
['玻璃离子窝沟封闭', 'preventive'],
['牙拔除术(松动乳牙)', 'surgical'],
])('%s → %s', (s, want) => {
expect(cat(s)).toBe(want);
});
});
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