Commit 368b2a9e by luoqi

fix(plan): 乳牙牙位归一化统一 — 修 K00 召回链显示"暂不召回"自相矛盾

bug(李梦维案例):召回原因有"牙发育/萌出异常 牙位 1;4"(K00),但治疗链那条
显示"已发现·暂不召回"(target=false),自相矛盾。

根因:两套牙位归一化口径不一致(仅乳牙 Palmer 记号分叉):
  - scenario 的 parseToothSet:/^\d+/ 只取前导数字 → "1B"→"1"(剥 Palmer 字母)
    → reason 牙位/sub_key = "1;4"
  - chain-composer:原样保留 → chain 牙位 = "1B;4C"
  - plan-aggregate target 匹配用共享 toothSet:保留 Palmer → "1B;4C"→{1B,4C}
  → toothOverlap({1,4}, {1B,4C}) = 空 → target=false → UI"暂不召回"
  (恒牙 FDI 36/46 两边都 "36" 一致,只乳牙 1B/4C 这种分叉)

修复:scenario 的 parseToothSet 改为委托共享 toothSet(单一真理源),
不再自己剥字母。乳牙 reason 牙位 → "1B;4C",跟 chain + target 匹配口径一致。

验证(本地重算后):active K00 reason 牙位已保留 Palmer(@3E / @2C / @1C;2C
等 209 条),与 chain 对齐 → target=true → ★潜在新链。
(李梦维本例 plan=assigned 被引擎跳过不重算,需 unassign 后才刷新 — 数据状态非 bug)

union-find 合并也受益:1B / 1C 现在是不同乳牙(不再都归"1"误并)。
parent c5ffe5ca
...@@ -14,6 +14,7 @@ import type { ...@@ -14,6 +14,7 @@ import type {
ScenarioScope, ScenarioScope,
} from '../scenario.interface'; } from '../scenario.interface';
import { calcPriority } from '../priority-scorer'; import { calcPriority } from '../priority-scorer';
import { toothSet } from '../../../sync/pipeline/parsers/tooth-position.util';
/** /**
* 潜在治疗新链召回(treatment_initiation_recall)— v2.1 重写 * 潜在治疗新链召回(treatment_initiation_recall)— v2.1 重写
...@@ -543,17 +544,14 @@ interface HitRow { ...@@ -543,17 +544,14 @@ interface HitRow {
cluster_triggers?: Array<{ type: string; code: string }>; // cluster 内 unique (type, code),给 signals.triggers cluster_triggers?: Array<{ type: string; code: string }>; // cluster 内 unique (type, code),给 signals.triggers
} }
/// 牙位字符串 → set of base tooth("15;24 B;24" → {"15","24"}) /// 牙位字符串 → set,**直接委托共享 toothSet**(单一真理源)。
/// 跟 plan-aggregate.service.ts 的 toothSet 同口径(剥牙面 B/L/M/O/D 后缀 + 去重) /// ⚠️ 必须跟 plan-aggregate.service.ts 的 target 匹配口径一致 —— 都用同一个 toothSet。
/// 历史 bug:本函数曾自己用 /^\d+/ 剥成 base 数字("1B"→"1"),而 plan-aggregate 的
/// toothSet 保留 Palmer 乳牙字母("1B"→"1B")→ 乳牙(K00 萌出/发育 等)reason 牙位"1;4"
/// 跟 chain 牙位"1B;4C"对不上 → target=false → UI 把召回中的链显示成"暂不召回"(李梦维案例)。
/// 恒牙(FDI 36/46)两边一致无碍;只乳牙分叉。统一到 toothSet 彻底消除。
function parseToothSet(s: string | null | undefined): Set<string> { function parseToothSet(s: string | null | undefined): Set<string> {
if (!s) return new Set(); return toothSet(s);
const out = new Set<string>();
for (const raw of s.split(';')) {
// base = 数字开头连续数字部分;"24 B" → "24", "1D" → "1"(palmer 乳牙也走 base 数字)
const m = raw.trim().match(/^\d+/);
if (m) out.add(m[0]);
}
return out;
} }
/// union-find 把同 patient 多 sig 按 tooth-overlap 合并成 cluster /// union-find 把同 patient 多 sig 按 tooth-overlap 合并成 cluster
......
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