Commit f85b7680 by luoqi

fix(web): 画像「依据」里的病历不再显示内部 ID,改显命中的临床信号

业务反馈禁忌标签的依据行看不懂:
    2022-03-26  病历  关联接诊:15514      ← 内部 encounter id,对客服毫无意义

根因在 fact-label.ts:97 —— 对 emr_record 取 f.summary,而注释写着
「summary 是命中的那段」。这个注释是错的:emr.parser.ts 实际写进 summary 的是
`关联接诊:${encounterExternalId}`。依据区从上线起显示的就是个内部 ID。

现在按信息量三级降级:
  ① content.clinical_signals 里的阳性信号 label(这条病历到底命中了什么)
     → 「局麻药物过敏」,悬停出医生原话「自述普鲁卡因、利多卡因过敏」
  ② 病历里真正有内容的 SOAP 段落(既往史/主诉/全身情况/医嘱)前 40 字
  ③ 才退到 title
任何情况下都不再出现 `关联接诊:<id>`。

FactLabel 加可选 hover 字段:列表行窄、原话长(「有放射治疗史,三周前进行
颈淋巴结清扫(舌癌淋巴结转移)」),truncate 后必须能悬停看全。
标签是 PAC 的归纳,原话才是事实本身 —— 两者都要够得着。

顺带修好的不止禁忌:治疗敏感 / 不可等候等所有以病历为证据的画像标签,
依据行同样从内部 ID 变成可读内容。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent 51581f58
import { diagnosisCodeNameZh, treatmentCategoryNameZh } from '@pac/types';
import { type ClinicalSignal, diagnosisCodeNameZh, treatmentCategoryNameZh } from '@pac/types';
import type { AdaptedFact } from './adapt-data';
/**
......@@ -12,6 +12,8 @@ export interface FactLabel {
badge: string;
badgeTone: string;
text: string;
/** 悬停补充(医生原话全文等);缺省时调用方用 text 兜底 */
hover?: string;
}
const MODALITY_ZH: Record<string, string> = {
......@@ -94,9 +96,32 @@ export function factLabel(f: AdaptedFact): FactLabel {
return { badge: '就诊', badgeTone: 'bg-teal-50 text-teal-700', text: f.title ?? '到诊' };
case 'visit_registration_record':
return { badge: '挂号', badgeTone: 'bg-teal-50 text-teal-700', text: f.title ?? '挂号' };
case 'emr_record':
// 治疗敏感 / 不可等候的证据来自病历自由文本 —— summary 是命中的那段
return { badge: '病历', badgeTone: 'bg-slate-100 text-slate-600', text: f.summary ?? f.title ?? '病历' };
case 'emr_record': {
const tone = { badge: '病历', badgeTone: 'bg-slate-100 text-slate-600' };
// ⚠️ **不能用 f.summary** —— emr parser 写进去的是 `关联接诊:<encounter_id>`(内部 ID),
// 客服在画像「依据」里看到「关联接诊:15514」完全无从理解。
// (旧注释说"summary 是命中的那段",与 emr.parser.ts 实际写入不符,是错的。)
// ① 优先:摄入时抽出的临床信号 —— 这条病历到底"命中了什么"
const sigs = Array.isArray(c.clinical_signals) ? (c.clinical_signals as ClinicalSignal[]) : [];
const positive = sigs.filter((s) => s?.negated === false);
if (positive.length > 0) {
return {
...tone,
text: [...new Set(positive.map((s) => s.label))].join('、'),
// 悬停给医生原话 —— 标签是 PAC 的归纳,原话才是事实本身
hover: [...new Set(positive.map((s) => s.phrase).filter(Boolean))].join('\n'),
};
}
// ② 退一步:病历里真正有内容的那段(SOAP 按信息量排序),而不是内部 ID
const snippet = [c.past_history, c.illness_desc, c.general_condition, c.doctor_advice]
.map((x) => String(x ?? '').trim())
.find((x) => x.length > 0);
if (snippet) return { ...tone, text: snippet.slice(0, 40), hover: snippet };
return { ...tone, text: f.title ?? '病历' };
}
case 'consultation_record':
return { badge: '咨询', badgeTone: 'bg-amber-50 text-amber-700', text: f.title ?? '咨询' };
default:
......
......@@ -185,7 +185,7 @@ function FeatureCard({
</div>
<ul className="space-y-0.5">
{shown.map((fact) => {
const { badge, badgeTone, text: label } = factLabel(fact);
const { badge, badgeTone, text: label, hover } = factLabel(fact);
return (
<li key={fact.id} className="flex items-baseline gap-1.5 text-[11px] leading-relaxed">
{/* 日期不折行 —— 定宽 68px 装不下 `2026-03-27`(11px 字号)会断成两行;
......@@ -194,7 +194,7 @@ function FeatureCard({
{(fact.occurredAt ?? fact.plannedFor ?? '').slice(0, 10) || '—'}
</span>
<span className={cn('flex-none rounded px-1 text-[10px]', badgeTone)}>{badge}</span>
<span className="min-w-0 flex-1 truncate text-slate-700" title={label}>
<span className="min-w-0 flex-1 truncate text-slate-700" title={hover ?? label}>
{label}
</span>
</li>
......
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