clinical-signals.yaml
14.6 KB
-
feat(persona): 禁忌能力落地 —— 病历自由文本抽临床信号 + 三档裁决 · 99e01d42
业务方细则(种植/正畸/麻醉/手术禁忌)按「病因建模、按治疗域映射」实现。 原来只有一条「年龄≤18 = 种植禁忌」的规则,而且跟召回侧的年龄闸重复, evidence.factIds 是空数组 —— 客服看到标签不知道凭什么,只会当噪音。 ── 分层 ── 摄入层 抽取(这句话提到抗凝药吗) per-row、确定性、可重放 ← ParserPipeline hook ↓ 写回**源 fact 自己的** content.clinical_signals 衍生层 裁决(他现在是不是手术禁忌) 跨记录、跨时间 ← persona 特征 不新建衍生 fact 类型 —— 遵守 FactType 的设计纪律「fact 只装源事件,衍生信息留在源 fact 的 content JSONB 里」。信号跟着源病历生命周期走(病历订正→重放→自动重抽), 不必额外维护一致性;召回闸门将来也只需一行 content->'clinical_signals' @> ...。 ── 三档严重度(收束点)── absolute 绝对禁忌 → 打标签,将来接召回闸门挡的是这一档 relative 相对禁忌 → 打标签 + 话术警示,不挡召回(由医生按身体条件评估) check 核查项 → 只提示「预约前确认X」,不叫禁忌 细则里三条**刻意降级为 check**:糖尿病要 HbA1c(生产全量仅 10 条)、高血压要收缩压 (几乎无人记录)、TMJ 要急性期(病历不记)。控制达标的糖尿病是可以种植的,硬判禁忌 医学上就是错的 —— 判断权还给医生,PAC 只把事实摆出来。 ── 时效(「禁忌≠终身禁忌」)── 妊娠 280 天 / 哺乳 180 天(产后半年)/ 放疗·抗骨吸收药终身 / 抗凝药等设复评期 (超期降级为 check 而非消失 ——「消失」会让客服以为患者从没有过这个病史)。 到期日在抽取时按源病历 occurredAt 算好写进 signal,画像 TS 与召回 SQL 各自只做一次 比较,时效规则不分叉。nextBoundaryAt 报出最近到期日 → 到期当晚自动重算。 ── 否定作用域:全模块最要紧的一条 ── 「患者否认有高血压、冠心病等心血管疾病;糖尿病、痛风等代谢性疾病。」 分号后的糖尿病仍在否定作用域内。生产 88.9 万条既往史实测:按分号切 → 阳性 1,328 人; 按句切 → 141 人,差 9.4 倍。这条错了整个模块就是负资产。 再断言(有/患/服用)只有隔了并列符才翻回阳性,否则「否认有」这个常见搭配会被整片判反。 「患者否认/自述有…」(生产 116 条,医生没删模板斜杠)→ negated=null,入库供人工复核但不产禁忌。 ── 生产实测(702 万行文本全量跑过)── 命中任一信号 0.75%(→ 首次 reparse 的 fact 版本波规模可控) 阳性率:抗凝 94.5% / 心脏植入物 95.2% / 哺乳 92.1% / 放疗 91.0% / 重度牙周炎 99.9% 上线后:3,790 人带禁忌标签,10,823 人仅带待核查(另有年龄禁忌 7 万人不变) 全量跑还揪出两类假阳性并已修:「备孕期」⊂「孕期」(78 行)、「妊娠期龈炎」是牙龈炎 诊断名不是妊娠状态(58 行)—— 中文无词边界,加了 not_any 反向清单。 ── 前端 ── 关键事实卡新增安全提示栏(⚠ + rose),禁忌与待核查分行显示,hover 出医生原话 + 记录日期。 不混进标签云:那里禁忌跟急迫等级同色同排平铺,客服扫不出来。 召回闸门本次**不接**,按预留处理。字典改动复用既有 reparse,不另造 CLI: pnpm reparse -- --host=jvs-dw --subject-type=emr,diagnosis --dry-run 测试 545 passed(新增 44 抽取 + 18 裁决,用例全部抄自生产真实文本) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed