4. HL7 v2到FHIR的转换工程

HL7 v2 与 FHIR 的协同集成,核心难点并不在“能不能转”,而在“转得是否稳定、可追溯、可验证”。v2 是面向消息的、以竖线分隔的扁平结构,FHIR 是面向资源的、以 JSON/XML 表达的层次化模型。把一条 ADT^A01 入院消息变成一组 Patient、Encounter、Practitioner 资源,需要一条完整的转换工程链路:解析、提取、构建、映射、验证、异常处理。下面按工程实现顺序展开。

一、消息解析:先把“竖线文本”变成可寻址结构

v2 消息的物理结构是:段(Segment)以 \r 分隔,字段(Field)以 | 分隔,组件(Component)以 ^ 分隔,子组件(Subcomponent)以 & 分隔,重复字段以 ~ 分隔。解析的第一步不是直接取字段,而是先做“段定位 + 字段索引 + 组件拆分”三级展开。

以一条简化的入院消息为例(示例数据,非实测):

MSH|^~\&|HIS|HOSP_A|EMR|HOSP_B|20240512093000||ADT^A01|MSG00001|P|2.5
PID|1||P123456^^^HOSP_A^MR||张^三||19800101|M|||北京市朝阳区...
PV1|1|I|WARD01^101^01|||||DOC001^李^医生|||...

解析器应输出一个可寻址的中间模型,例如 msg["PID"][5][1] 表示 PID 段第 5 字段第 1 组件。这里要特别注意:v2 字段编号从 1 开始,而多数编程语言数组从 0 开始,工程上必须统一约定“字段号 = 数组下标 + 1”,否则 PID-5 会被误读为 PID-6。解析阶段还要处理转义序列(\F\、\S\、\T\)和空字段占位,不能简单按分隔符切分后就丢弃空位。

二、段字段提取:按业务语义抽取,而非按位置硬编码

解析得到的是“位置”,提取要做的是“语义”。例如患者标识:PID-3 是标识符列表,可能包含多个重复项,每项由 ID^AssigningAuthority^IDType 组成。提取时应遍历重复项,优先选择 IDType=MR(病案号)作为 Patient.identifier,而不是取第一个。姓名 PID-5 拆为 Family^Given^Middle,对应 FHIR 的 family 与 given。性别 PID-8 的 M/F/O/U 需映射到 FHIR 的 male/female/other/unknown。

提取层建议输出一个与目标资源字段同名的“扁平字典”,例如 patient_family、patient_given、patient_mrn、encounter_ward,这样后续构建资源时不再关心 v2 位置,只关心业务字段。这一步是转换工程可维护性的关键:位置变化只影响提取层,不影响资源构建层。

三、资源构建:把扁平字段组装成合法 FHIR 资源

构建阶段要保证资源的结构合法性。以 Patient 为例,必须生成 resourceType、id、identifier、name、gender、birthDate 等字段;Encounter 则要关联 subject 指向 Patient 的逻辑 id,period.start 取 PV1-44 或 MSH-7。资源之间的引用建议使用“逻辑 id + 临时引用”策略:先为每个资源分配稳定 id(如 Patient/P123456),再在 Encounter 中写 subject.reference = "Patient/P123456",避免转换过程中出现悬空引用。

构建时还要处理 FHIR 的必填与基数约束。例如 Patient.name 是 0..*,但业务上通常要求至少一个;Encounter.status 是必填,A01 入院应映射为 in-progress,A03 出院映射为 finished。这些规则不应散落在代码里,而应进入映射规则引擎。

四、映射规则引擎:把“转换逻辑”从代码中抽离

映射规则引擎的核心是“声明式规则 + 可执行解释器”。一条规则至少包含:源路径、目标路径、转换函数、默认值、是否必填。例如:

{
  "source": "PID-8",
  "target": "Patient.gender",
  "map": {"M":"male","F":"female","O":"other"},
  "default": "unknown",
  "required": true
}

引擎按规则顺序执行,遇到 required=true 且源为空时,不直接抛异常,而是记录一条“缺失告警”并写入转换报告。规则可以按消息类型分组:ADT^A01 加载入院规则集,ORM^O01 加载医嘱规则集。这样新增字段映射只需改配置,不需要重新编译转换服务。

五、转换验证与异常处理:让每次转换都有“体检报告”

验证分三层:结构验证(FHIR 资源是否符合 Profile/StructureDefinition)、业务验证(必填字段是否齐全、引用是否可解析)、语义验证(性别、状态等枚举值是否在允许集合内)。验证不通过时,不应静默丢弃,而应生成结构化报告,包含消息控制 id、失败资源、失败路径、原因分类。

异常处理策略建议分级:可修复异常(如性别未知)用默认值填充并记录告警;阻断异常(如 Patient 无任何标识符)则整条消息进入重试队列,并保留原始 v2 报文;系统异常(如 FHIR 服务不可达)走指数退避重试。所有异常都要能通过 MSH-10 消息控制 id 反查原始消息,形成闭环。

HL7 v2 → FHIR 转换工程链路 ① 消息解析 段 \r 分隔 字段 | 组件 ^ 重复 ~ 转义 \F\ 输出:可寻址中间模型 ② 段字段提取 PID-3 标识符列表 PID-5 姓名组件 PID-8 性别枚举 输出:业务扁平字典 ③ 资源构建 Patient / Encounter 逻辑 id 分配 引用 subject.reference 输出:FHIR 资源对象 ④ 映射规则引擎 源路径 → 目标路径 转换函数 / 默认值 按消息类型加载规则集 输出:可执行规则 ⑤ 转换验证(三层) 结构验证:Profile / StructureDefinition 合规 业务验证:必填字段齐全、引用可解析 语义验证:性别、状态等枚举值在允许集合内 ⑥ 异常处理与报告 可修复:默认值填充 + 告警记录 阻断:进入重试队列,保留原始 v2 报文 系统:指数退避重试,MSH-10 反查闭环 验证通过 写入 FHIR 服务 示例参数仅用于说明转换链路,非实测数据

六、一个可执行的转换检查清单

把上述链路落到工程上,可以用下面这份检查清单约束每次转换发布:

阶段检查项通过标准
解析字段编号与数组下标换算PID-5 取到姓名,不偏移
提取重复字段选择策略MR 类型标识符优先
构建资源引用完整性Encounter.subject 可解析
映射必填规则是否配置默认值gender 缺失时填 unknown
验证Profile 校验是否启用结构错误数为 0
异常MSH-10 是否写入报告可反查原始消息
工程建议:把“解析—提取—构建—映射—验证”做成可独立测试的五个函数,每个函数输入输出都用 JSON 固定下来。这样当 v2 字段位置变化或 FHIR Profile 升级时,只需替换其中一层,回归测试范围可控。
常见误区:直接在资源构建代码里写 if (sex == "M") gender = "male"。这类硬编码会让映射规则散落各处,后续新增 O、U 或本地性别编码时极易漏改。应统一收敛到映射规则引擎的配置中。

总结来看,v2 到 FHIR 的转换不是一次性的“翻译”,而是一条可观测、可回滚、可扩展的工程流水线。解析保证位置正确,提取保证语义正确,构建保证结构正确,映射保证规则可维护,验证与异常处理保证每次转换都有据可查。把这五层职责分清,转换工程才能从“能跑”走向“敢上生产”。