4. 患者主索引与随访档案建模

随访系统的所有业务动作——入组、发消息、填量表、上报不良事件——最终都要落到“同一个人”身上。如果同一个患者在门诊系统叫“张伟”,在住院系统叫“张玮”,在随访小程序里又填成“张伟(家属代填)”,那么随访计划会重复触发、量表会重复发送、统计口径会彻底失真。因此,随访档案建模的第一件事不是设计表单,而是建立患者主索引(EMPI,Enterprise Master Patient Index):用一套稳定的唯一标识,把分散在各业务系统里的患者记录归并到同一个“人”上。

一、患者唯一标识:从业务主键到全局主索引

很多团队会直接拿 HIS 的患者 ID 当随访主键,这在单院区、单系统时可行,但一旦接入多院区、互联网医院或第三方随访渠道就会出问题:同一个自然人在不同系统里有不同 ID,而同一个 ID 在不同系统里可能指向不同的人。正确的做法是分层设计标识:

  • 业务主键(source_id):各来源系统的原始 ID,如 HIS 的 PAT_NO、住院系统的 INP_NO、微信小程序的 openid。它只保证在来源系统内唯一。
  • 全局主索引(empi_id):随访系统内部生成的、跨系统稳定的患者唯一标识,通常用 UUID 或“院区码+序列”生成,一旦分配永不复用、永不修改。
  • 匹配键(match_key):用于判断两条记录是否属于同一个人的字段组合,典型为“姓名+性别+出生日期+证件号后四位+手机号”。

归并逻辑分两步:先做确定性匹配(证件号完全一致、或手机号+姓名+出生日期完全一致),命中即直接合并;未命中的进入概率匹配,按字段权重打分。例如姓名权重 30、手机号 30、出生日期 20、证件号后四位 20,总分 ≥ 85 自动合并,60~84 进入人工复核队列,< 60 视为新患者。人工复核是必须保留的环节——自动合并一旦出错,会把两个真实患者的随访记录混在一起,属于严重的数据事故。

二、联系方式与诊断信息:可变字段与不可变字段

建模时要区分字段的“变化频率”。姓名、出生日期、证件号属于准静态字段,变更需留痕;手机号、地址、紧急联系人属于高频可变字段,且直接决定随访能否触达。建议联系方式不直接存在患者主表,而是拆成独立的 contact 子表,支持一人多号、多联系人,并记录每个号码的可用状态(有效/停机/空号/拒接)和最近验证时间。随访任务发送前先查可用号码,全部失效时自动转入“失联”处理流程,而不是反复重试。

诊断信息同样需要结构化。随访关注的不是完整病历,而是入组所需的诊断要素:诊断编码(ICD-10/ICD-11)、诊断名称、确诊日期、分期/分级、关键检验结果(如某肿瘤标志物、HbA1c、血压分级)。这些字段构成入组条件的判断依据,应单独建 diagnosis 表,保留来源系统与版本,避免后续诊断修正导致历史入组记录被“追溯性改变”。

三、入组条件与随访状态机

入组条件本质是一组布尔表达式,例如“确诊 2 型糖尿病 且 年龄 ≥ 18 且 最近一次 HbA1c ≥ 7.0% 且 未在其它随访项目中”。把条件写成可配置的规则而非硬编码,能让运营人员自行调整病种范围。命中规则后生成一条 enrollment 记录,状态从“待确认”开始流转。

随访状态机是本节最需要可视化的部分。它决定了患者当前处于哪个阶段、下一步该做什么、异常时如何回退。下面这张状态图给出了一个可直接落地的状态机设计,包含正常流转路径与失联、退出、完成三类终态。

随访患者状态机(enrollment.status) 待确认 PENDING 已入组 ENROLLED 随访中 FOLLOWING 已完成 COMPLETED 失联 LOST_CONTACT 退出 WITHDRAWN 暂停 SUSPENDED 确认入组 生成计划 随访期满 连续3次触达失败 确认失访 申请暂停 恢复随访 重新入组 状态迁移规则: 实线 = 正常流转;红色 = 异常终止路径;虚线 = 可逆回退路径。每次迁移写入 status_log(from_status, to_status, operator, reason, ts)。

状态机落地时有三个工程要点。第一,状态迁移必须留痕:每次变更写入 status_log,记录原状态、目标状态、操作人、原因和时间戳,便于事后追溯“这个患者为什么被判定失联”。第二,迁移要有守卫条件:例如“随访中→已完成”必须满足“计划内所有随访节点均已执行或已过期”,不能由人工随意点击。第三,终态不可逆:已完成、退出、失联是终态,若患者重新参与,应新建一条 enrollment 记录而不是复活旧记录,这样历史统计才不会被污染。

四、档案数据模型与隐私字段处理

把上述内容落成表结构,核心是四张表:patient(主索引与准静态字段)、contact(联系方式,一对多)、diagnosis(诊断要素)、enrollment(入组与状态)。它们通过 empi_id 关联,形成以患者为中心的星型结构。下面给出关键字段的建模示例:

patient(
  empi_id        CHAR(36) PRIMARY KEY,   -- 全局主索引,UUID
  name           VARCHAR(64)  NOT NULL,  -- 加密存储
  gender         TINYINT,
  birth_date     DATE,
  id_card_hash   CHAR(64),               -- 证件号哈希,用于匹配
  id_card_enc    VARBINARY(256),         -- 证件号密文,用于展示
  created_at     DATETIME,
  merged_into    CHAR(36)                -- 被合并到哪个 empi_id
)

contact(
  contact_id     BIGINT PRIMARY KEY,
  empi_id        CHAR(36),
  type           ENUM('MOBILE','HOME','EMERGENCY'),
  value_enc      VARBINARY(256),         -- 号码密文
  value_hash     CHAR(64),               -- 号码哈希,用于去重与匹配
  status         ENUM('VALID','INVALID','UNKNOWN'),
  verified_at    DATETIME
)

enrollment(
  enroll_id      BIGINT PRIMARY KEY,
  empi_id        CHAR(36),
  project_code   VARCHAR(32),
  status         ENUM('PENDING','ENROLLED','FOLLOWING',
                      'SUSPENDED','LOST_CONTACT',
                      'WITHDRAWN','COMPLETED'),
  enrolled_at    DATETIME,
  exited_at      DATETIME,
  exit_reason    VARCHAR(128)
)

隐私处理遵循“能哈希的不明文、能脱敏的不展示、能最小化的不多存”三条原则。姓名、手机号、证件号等直接标识符采用 AES-256 加密存储,密钥由 KMS 托管;同时保存一份 SHA-256 哈希值用于匹配和去重,因为哈希不可逆,即使库被拖走也无法还原明文。展示层按角色脱敏:随访专员看到“张*伟 / 138****5678”,只有授权医生在特定场景下才能申请查看完整信息,且查看行为写入审计日志。

匹配用的哈希要加固定盐值(salt),否则攻击者可用常见手机号字典反查哈希。盐值本身也要纳入密钥管理,不能硬编码在代码里。

最后是数据生命周期。随访档案不应无限期保留:项目结束后,按伦理与合规要求设定保留期(如研究类项目通常 5~10 年),到期后对直接标识符做不可逆匿名化,仅保留统计所需的去标识化字段。患者行使删除权时,删除的是 patient 与 contact 中的可识别信息,而 enrollment 中的状态流转记录以匿名形式保留,用于保证统计口径的连续性。这样既满足合规,又不破坏随访数据的分析价值。

合并患者记录是不可逆的高风险操作。生产环境务必先做“软合并”(标记 merged_into 并保留原记录),观察一个随访周期确认无误后再物理清理,切勿直接 DELETE。