# ═══════════════════════════════════════════════════════════
# 共享字典 · 临床信号抽取(既往史 / 诊断原文 → 结构化 clinical_signals)
#
# 【临床通用层】「口服抗凝药 = 出血风险」「颌骨放疗 = 骨坏死风险」这类判断是医学知识,
# 不随宿主变 —— 与 diagnosis-kcode.yaml / recommendation-code.yaml 同层同治理纪律:
#   - 改本文件影响【所有】宿主 → 改完对受影响宿主重衍生,复用既有 reparse(不另造 CLI):
#         pnpm reparse -- --host=jvs-dw --subject-type=emr,diagnosis --dry-run
#     (reparse 拿 transaction.rawPayload 离线重跑装配+解析,不连 DW、不碰账本;
#      信号抽取挂在 ParserPipeline 上,自然跟着重跑)
#   - 新词归层判断:标准临床术语 / 药品通用名 → 这里;某宿主的模板痕迹 → 该宿主 yaml(暂无);
#   - 只收【确定性可判】的类别。需要定量值或状态判断的一律不收,见文末「刻意不收」。
#
# 消费方:ClinicalSignalExtractor(modules/clinical-signals)。抽取结果写回**源 fact 自己的**
#   content.clinical_signals —— 遵守 FactType 的设计纪律「fact 只装源事件,衍生信息留在源
#   fact 的 content JSONB 里」,不新建衍生 fact 类型。
#
# ⭐ valid_until 在**抽取时**就按 ttl 算好写进 signal。时效规则只活在本文件一处:
#   画像(TS)和召回闸门(SQL)各自只做 `valid_until IS NULL OR valid_until > now` 这一个比较,
#   逻辑不会分叉。(教训:⑤g 未来回访闸就是靠"判断下沉到数据"才没分叉。)
# ═══════════════════════════════════════════════════════════

version: clinical-signals@2026-07-27   # 抽取器版本号,写进 signal.extracted_by,便于按版本重扫

# ─────────────────────────────────────────────────────────
# 一、扫哪些 fact 的哪些 content 字段
#
# 这里是 **PAC 标准化后的 fact 字段**,不是宿主原始列 —— 宿主怎么映射是 assembler 的活,
# 到这一层已经是统一语义(emr_record 的 SOAP 字段全集见 fact-content-schemas.ts)。
# 加字段 = 改本段一行,抽取器不动。
# ─────────────────────────────────────────────────────────
scan_fields:
  - fact_type: emr_record
    fields:
      - past_history        # 既往史 —— 主力(生产 88.9 万条,覆盖 34 万患者)
      - general_condition   # 全身情况 / 系统回顾
      - pre_illness         # 现病史(妊娠/哺乳常写在这)
      - doctor_advice       # 医嘱(「术前停抗凝药」这类)
    # ⚠️ 刻意不扫 illness_desc(主诉)/ exam_findings(检查所见)/ disposal(处置):
    #   主诉是患者口述的牙齿问题,检查所见是口内所见,处置是本次操作 —— 都不承载全身病史。
    #   扫了只会引入噪音(「无痛」含"无"、"痛"等),而不会提高召准。

  - fact_type: diagnosis_record
    fields:
      - name_zh             # 诊断原文 —— 严重度只在文本里(生产 severity 列 296 万条 100% null)

# ─────────────────────────────────────────────────────────
# 二、否定判定 —— 本字典最要紧的一段
#
# 中文病历既往史 97% 是否定式(生产实测 top15 高频值里 14 个是否定或新冠筛查模板)。
# naive 含词匹配会把「否认药物过敏」标成麻醉禁忌 —— 那是医疗事故,不是 bug。
#
# 【作用域规则】否定词的作用域 = 从它自己到**句末**,其中:
#   - 断句符 sentence_delimiters(。!?换行)才切断作用域
#   - 并列符(;;、,)**不切断** —— 这是关键:
#       「患者否认有高血压、冠心病等心血管疾病;糖尿病、痛风等代谢性疾病。」
#        ↑否认                                  ↑糖尿病在分号后,仍在否定作用域内
#     生产实测:按分号切 → 糖尿病阳性 1,328 人;按句切 → 141 人。差 9.4 倍,
#     而正确答案是后者。这一条错了,整个禁忌能力就是负资产。
#
# 【再断言】否定词之后若出现 reassert_markers(有/患有/服用/术后…)且更贴近关键词,
#   翻回阳性 —— 覆盖「否认系统病史,有高血压」这类先否后肯的写法。
#
# 【不可判】同句同时出现否定词和「/」模板残留(「患者否认/自述有…」,生产 116 条)→
#   negated=null。这不是 NLP 能力问题,是**源数据本身没有结论**:医生没删模板里那个斜杠。
#   不可判信号照常写进 fact(可查、可人工复核),但**不产禁忌**。PAC 不替宿主兜底。
# ─────────────────────────────────────────────────────────
negation:
  sentence_delimiters: ['。', '.', '!', '！', '?', '？', "\n"]
  markers:   [否认, 无, 未, 不详, 非, 排除, 阴性, 未见, 未诉, 否定]
  reassert:  [有, 患有, 患, 自述有, 主诉, 服用, 口服, 长期服, 术后, 曾, 现]
  # 模板未删干净 → 不可判(negated=null)。命中即整句所有信号都标不可判。
  undecidable_patterns: ['否认/自述', '自述/否认', '否认/有', '有/否认', '否认或']

# ─────────────────────────────────────────────────────────
# 三、信号词典
#
# severity 三档 —— 决定下游怎么用,是本设计的收束点:
#   absolute  绝对禁忌 → 打禁忌标签;将来接召回闸门就是挡这一档
#   relative  相对禁忌 → 打标签 + 话术警示,**不挡召回**(能不能做由医生按身体条件评估)
#   check     核查项   → 只提示客服「预约前确认X」,不叫禁忌
#            ↑ 定量条件无数据的一律落这档(见文末),不硬判成禁忌
#
# contraindicates 用治疗域而非 PAC 治疗 category —— 一个抗凝药同时禁种植/拔牙/牙周手术,
#   按病因建模、按治疗域映射,才不会四份规则互相打架。
#   域:implant 种植 / surgery 外科(拔牙·翻瓣·植骨)/ anesthesia 局麻 / orthodontic 正畸 / xray 拍片
#
# ttl —— 你的行业洞察落地:「禁忌≠终身禁忌」。三种写法:
#   lifelong        终身(valid_until = null)
#   days: N         源 fact occurredAt + N 天后自动失效
#   review_days: N  超过 N 天未再提及 → 降级为 check(状态可能已变,需重新核实),不直接消失
# ─────────────────────────────────────────────────────────
signals:
  # ── 出血风险(手术域绝对禁忌)────────────────────────
  - code: anticoagulant
    label: 长期抗凝/抗血小板药物
    any: [抗凝, 华法林, 波立维, 泰嘉, 氯吡格雷, 利伐沙班, 达比加群, 阿司匹林, 抗血小板, 抗血栓]
    contraindicates: [surgery, implant]
    severity: absolute
    ttl: { review_days: 730 }   # 会停药 → 两年未再提及降级核查
    note: 生产真阳性率 96%(「口服波立维」不可能是否定句);停药需医生指导,PAC 只提示不指导

  - code: coagulopathy
    label: 凝血功能障碍
    any: [血友病, 凝血功能障碍, 凝血障碍, 血小板减少, 再生障碍性贫血]
    contraindicates: [surgery, implant]
    severity: absolute
    ttl: lifelong

  # ── 颌骨坏死风险(种植/外科绝对禁忌)──────────────────
  - code: antiresorptive
    label: 抗骨吸收药物史
    any: [双膦酸盐, 地舒单抗, 唑来膦酸, 阿仑膦酸, 帕米膦酸, 普罗力, 密固达, 福善美]
    contraindicates: [implant, surgery]
    severity: absolute
    ttl: lifelong
    note: MRONJ(药物相关性颌骨坏死)风险不可逆,停药后仍持续 → 终身

  - code: radiotherapy
    label: 放疗史
    any: [放疗, 放射治疗, 放射线治疗]
    contraindicates: [implant, surgery]
    severity: absolute
    ttl: lifelong
    note: 颌骨放射性骨坏死风险。⚠️ 词典判不出放疗部位(乳腺癌放疗 vs 颌面部放疗风险差别很大)
          → 标签文案必须带原文,让医生看到「5年前乳房癌手术,放疗」自行判断,PAC 不替他下结论

  - code: chemotherapy
    label: 化疗史
    any: [化疗, 化学治疗]
    contraindicates: [implant, surgery]
    severity: relative
    ttl: { review_days: 365 }

  # ── 心血管(手术应激,相对)────────────────────────
  - code: cardiac_implant
    label: 心脏植入物/心血管手术史
    any: [起搏器, 心脏支架, 冠脉支架, 心脏搭桥, 冠脉搭桥, 心肌梗死, 心梗, 心脏手术, 瓣膜置换]
    contraindicates: [implant, surgery]
    severity: relative
    ttl: lifelong
    note: ⚠️「支架」裸词会误吞「钛网支架骨增量手术」(口腔术式)→ 必须写全「心脏支架/冠脉支架」

  # ── 妊娠哺乳(强时效)──────────────────────────
  - code: pregnancy
    label: 妊娠期
    any: [妊娠, 怀孕, 孕期, 已孕]
    # ⚠️ 生产全量抽取实测揪出的两类假阳性,别删:
    #   备孕期(78 行)—— 还没怀上,判妊娠禁忌是错的
    #   妊娠期龈炎(58 行)—— 那是**牙龈炎诊断名**,不是当前妊娠状态(多为「曾患妊娠期龈炎」)
    not_any: [备孕, 备孕期, 妊娠期龈炎, 妊娠期牙龈炎, 妊娠性龈炎, 妊娠期牙龈瘤]
    contraindicates: [implant, surgery, xray]
    severity: absolute
    ttl: { days: 280 }
    note: 生产实测这类记录横跨 2016–2026;不设窗口 = 十年前的妊娠今天还挂着

  - code: lactation
    label: 哺乳期
    any: [哺乳]
    contraindicates: [implant, xray]
    severity: absolute
    ttl: { days: 180 }   # 业务洞察:产后 6 个月解除

  # ── 麻醉 ────────────────────────────────
  - code: local_anesthetic_allergy
    label: 局麻药物过敏
    any: [利多卡因过敏, 普鲁卡因过敏, 阿替卡因过敏, 卡波卡因过敏, 布比卡因过敏, 局麻药过敏, 麻药过敏, 麻醉药过敏]
    contraindicates: [anesthesia]
    severity: absolute
    ttl: lifelong
    note: ⚠️ 必须整词匹配「X过敏」。裸「过敏」在生产命中 22,653 条,其中 98.4% 是「否认药物过敏史」

  - code: asthma
    label: 哮喘
    any: [哮喘]
    contraindicates: [anesthesia]
    severity: relative
    ttl: { review_days: 730 }

  # ── 正畸 ────────────────────────────────
  - code: severe_periodontitis
    label: 重度牙周炎
    any: [重度牙周炎, 重度牙周病, 中重度牙周炎, 中重度牙周病]
    contraindicates: [orthodontic]
    severity: relative
    ttl: { review_days: 365 }
    note: 源=diagnosis_record.name_zh(生产 6,674 行 / 约 4,900 人)。牙周炎控制后可正畸 →
          relative 不挡召回,只提示「需牙周科评估后再启动矫治」。一年未复诊则降级核查

  # ── 核查项(有病史但控制状态未知 —— 不叫禁忌)──────────
  # ⭐ 这一档是本设计对业务细则最大的一处偏离,理由见文末「刻意不收」。
  - code: diabetes
    label: 糖尿病史(控制状态未知)
    any: [糖尿病, 糖尿]
    contraindicates: []
    severity: check
    ttl: { review_days: 730 }
    note: 细则要求「HbA1c>7%」→ 生产全量 HbA1c 仅 10 条,判不了。控制达标的糖尿病可以种植
          (业务洞察原话),硬判禁忌医学上是错的 → 降级核查项,把判断权还给医生

  - code: hypertension
    label: 高血压史(控制状态未知)
    any: [高血压]
    contraindicates: []
    severity: check
    ttl: { review_days: 730 }
    note: 细则要求「收缩压>160mmHg」→ 血压数值几乎无人记录,同上降级

  - code: osteoporosis
    label: 骨质疏松
    any: [骨质疏松]
    contraindicates: []
    severity: check
    ttl: { review_days: 730 }
    note: 骨质疏松本身不是禁忌,**用药才是** → 独立成核查项,提示客服确认是否在用抗骨吸收药
          (常与 antiresorptive 同句命中,那条才是绝对禁忌)

  - code: smoking
    label: 吸烟史
    any: [吸烟史, 抽烟史, 烟龄]
    contraindicates: []
    severity: check
    ttl: { review_days: 730 }
    note: 重度吸烟影响种植骨结合,但「重度」无量化数据 → 核查项。
          ⚠️ 必须带「史/龄」,裸「吸烟」在生产 5,010 条里 85% 是「无吸烟史」

# ─────────────────────────────────────────────────────────
# 四、刻意不收(留档,免得下次有人"补全"进来)
#
# ① 急性感染 / 发热 —— 既往史里「发热」几乎全是新冠筛查模板的否定式
#    (「近2周内无体温大于37.3摄氏度」生产 5.3 万条)。假阳性源太危险,且急性期是**就诊当天**
#    判断的事,病历回溯没有意义。放弃。
# ② 颞下颌关节紊乱「急性期」—— 病历不记录急性/缓解期。无数据。
# ③ 严重骨性错颌「拒绝正颌手术」—— 病历不记录患者拒绝。无数据。
# ④ 牙槽骨吸收 >根长1/2 —— 生产 diagnosis 里「牙槽骨吸收」仅 18 行且无程度量化;
#    真正的骨吸收程度在影像报告里,PAC 目前只摄 AI 影像的 10 个病种码,无骨吸收量。
# ⑤ HbA1c / 收缩压 阈值 —— 见上 diabetes / hypertension note。
# ⑥ 肝炎 / 甲亢 / 肾病 —— 生产真阳性率 0.2% / 5.5%,几乎全是「否认肝炎、梅毒、艾滋病」
#    这类传染病否定串;且它们对牙科治疗的禁忌性远弱于上面几类。收益不抵噪音。
#
# 共同判据:**需要定量值或状态判断,而源数据只有一个名词** → 不收,或降级为 check。
# 这是「PAC 不造数据只讲事实」在本模块的具体落法。
# ─────────────────────────────────────────────────────────
