Commit d4fbbd45 by luoqi

feat(recall): 召回原因区分 影像AI vs 医生诊断(triggers 加 image_finding)

问题(用户在邵竞红37发现):卡片标'(诊断)'但其实是影像AI分析(code_source=image_ai)。
'(诊断)'只是 fact 类型(diagnosis_record,对应'(医生建议)'=recommendation),不区分诊断内部
医生 vs 影像AI。区分靠 content.code_source,后端打分已用(影像置信度0.85/0.9<医生1.0),但没进 triggers。

- scenario uniqueTriggers + lead triggerType:code_source=image_ai 的诊断 → trigger type='image_finding'
  (医生诊断仍 diagnosis;recommendation 不变);sourceStr 文本同步加 '(影像AI)'(喂 AI 话术更谨慎)。
- canonical-codes triggerTypeLabelZh: image_finding '影像所见'→'影像AI'(强调AI来源/低置信)。
- 前端 reason-line 用 triggerTypeLabelZh 自动渲染 → '(影像AI)' / 混合 '(诊断+影像AI)',零改。
验证(本地清plans重算):image_finding trigger 77 个;姜学英 K03@34(唯一诊断=image_ai)→ type=image_finding。
注:改 scenario 需 TRUNCATE plans + recompute 才生效(增量 upsert 不重写旧 signals);服务器下次部署随之生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent b5ffed78
......@@ -418,11 +418,17 @@ export class TreatmentInitiationRecallScenario implements PlanScenarioPlugin {
// cluster 内可能含两种 sig(诊断 + 医生建议)→ 文案合并显示;只 1 种则保留单一来源
const hasDx = r.cluster_has_diagnosis ?? (r.signal_type === 'diagnosis_record');
const hasRec = r.cluster_has_recommendation ?? (r.signal_type === 'recommendation_record');
const sourceStr = hasDx && hasRec ? '(诊断+医生建议)' : hasRec ? '(医生建议)' : '(诊断)';
// 影像AI 诊断(code_source=image_ai)单独标注 → 话术更谨慎(影像所见 ≠ 医生确诊)
const isImg = hasDx && !hasRec && r.code_source === 'image_ai';
const sourceStr = hasDx && hasRec ? '(诊断+医生建议)' : hasRec ? '(医生建议)' : isImg ? '(影像AI)' : '(诊断)';
// 触发信号类型(原 enum,不语义化;前端用 triggerTypeLabelZh 翻译)
// lead 单一类型 — 想精准列出所有 sig 的话需要把 triggers[] 改成 cluster 全量,目前 lead 代表
const triggerType =
r.signal_type === 'recommendation_record' ? 'recommendation' : 'diagnosis';
r.signal_type === 'recommendation_record'
? 'recommendation'
: r.code_source === 'image_ai'
? 'image_finding'
: 'diagnosis';
hits.push({
patientId,
patientExternalId: r.patient_external_id,
......@@ -650,14 +656,20 @@ function uniqueTriggers(rows: HitRow[]): Array<{ type: string; code: string }> {
const seen = new Set<string>();
const out: Array<{ type: string; code: string }> = [];
for (const r of rows) {
const type = r.signal_type === 'recommendation_record' ? 'recommendation' : 'diagnosis';
// 影像AI 诊断 → image_finding(前端渲染"影像AI"),区别于医生诊断 diagnosis
const type =
r.signal_type === 'recommendation_record'
? 'recommendation'
: r.code_source === 'image_ai'
? 'image_finding'
: 'diagnosis';
const key = `${type}|${r.signal_code}`;
if (!seen.has(key)) {
seen.add(key);
out.push({ type, code: r.signal_code });
}
}
// diagnosis 排前 → 前端 triggers[0] 兜底也是诊断
out.sort((a, b) => (a.type === 'diagnosis' ? -1 : 1) - (b.type === 'diagnosis' ? -1 : 1));
// 诊断/影像AI(带 K 码)排前 → 前端 triggers[0] 兜底展示码;医生建议(rec 码)排后
out.sort((a, b) => (a.type === 'recommendation' ? 1 : 0) - (b.type === 'recommendation' ? 1 : 0));
return out;
}
......@@ -745,7 +745,7 @@ export function subLabelZh(
export const PACTriggerTypeLabels: Record<string, string> = {
diagnosis: '诊断',
recommendation: '医生建议',
image_finding: '影像所见',
image_finding: '影像AI',
emr_signal: '病历信号',
visit_gap: '到诊间隔',
other: '其他',
......
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