Commit 21ae9e68 by luoqi

feat(web): 牙位事实视图补'无修复指征'注记 — 缺牙间隙关闭的牙显灰行(一线反馈⑤)

牙位事实(tooth-timeline)是原始事实流,不展示 exam_findings → 26 这类'缺牙但间隙
关闭无需修复'看着像未处理。补:从 facts 解析 exam_findings,'缺牙+间隙关闭/无修复
间隙'的牙位在泳道末尾加灰色'非修复'行+检查原文。同后端 gap/前端 oracle 词典口径。
web build 过。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
parent 38199a65
'use client';
import { diagnosisCodeNameZh, treatmentCategoryNameZh, lookupDxTreatment } from '@pac/types';
import {
diagnosisCodeNameZh,
treatmentCategoryNameZh,
lookupDxTreatment,
NO_RESTORATION_GAP_EXAM_PATTERNS,
} from '@pac/types';
import { cn } from '@/lib/utils';
import type { AdaptedFact } from './adapt-data';
......@@ -29,6 +34,24 @@ export function ToothTimeline({ facts }: { facts: AdaptedFact[] }) {
return <div className="text-center py-12 text-sm text-slate-400">无牙位事实</div>;
}
// 检查所见"缺牙间隙关闭/无修复间隙"→ 每颗牙的无修复注记(同后端 gap a''' 口径)。
// 信息在 emr_record.exam_findings 文本里(本视图诊断/治疗都看不到)→ 补一行让客服懂为何不跟进。
const noRestorNote = new Map<string, string>();
const noRestorRe = new RegExp(NO_RESTORATION_GAP_EXAM_PATTERNS.join('|'));
for (const f of facts) {
if (f.type !== 'emr_record') continue;
for (const seg of parseJsonArrayLoose((f.content ?? {}).exam_findings)) {
const msg = String(seg.message ?? '');
if (!/缺[牙失]/.test(msg) || !noRestorRe.test(msg)) continue;
for (const t of String(seg.toothPosition ?? '')
.split(/[;,]+/)
.map((x) => toothBase(x.trim()))
.filter(Boolean)) {
if (!noRestorNote.has(t)) noRestorNote.set(t, msg.trim());
}
}
}
// 分桶
const lanes = new Map<string, AdaptedFact[]>();
const push = (k: string, f: AdaptedFact) => {
......@@ -95,6 +118,17 @@ export function ToothTimeline({ facts }: { facts: AdaptedFact[] }) {
{rows.map((f) => (
<ToothFactRow key={`${k}-${f.id}`} fact={f} />
))}
{!isWhole && noRestorNote.has(k) && (
<div className="flex items-baseline gap-2 px-2.5 py-1.5 text-[12px] bg-slate-50/60">
<span className="w-[74px] flex-none" />
<span className="flex-none px-1.5 py-px rounded text-[10px] font-medium bg-slate-100 text-slate-500">
非修复
</span>
<span className="flex-1 min-w-0 text-slate-500 leading-snug" title={noRestorNote.get(k)}>
检查所见:{noRestorNote.get(k)}(无修复指征)
</span>
</div>
)}
</div>
</div>
);
......@@ -178,6 +212,17 @@ const MODALITY_ZH: Record<string, string> = {
};
/// 牙位归一(剥面/方位后缀,保留牙位 base);跟 facts-timeline 同义
function parseJsonArrayLoose(raw: unknown): Array<{ toothPosition?: string; message?: string }> {
if (Array.isArray(raw)) return raw as Array<{ toothPosition?: string; message?: string }>;
if (typeof raw !== 'string' || !raw.trim() || raw === 'null') return [];
try {
const p = JSON.parse(raw);
return Array.isArray(p) ? p : [];
} catch {
return [];
}
}
function toothBase(t: string): string {
const m = /^(\d{1,2}[A-E]?)/i.exec(t.trim());
return m ? m[1]!.toUpperCase() : t.trim().toUpperCase();
......
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