2. HL7 v2消息结构与FHIR资源模型对照

在协同集成工程中,最常见的落地场景之一,就是让一条 HL7 v2 消息与一组 FHIR 资源之间建立可解释、可验证的映射。理解这种映射,不能停留在“MSH 对应 MessageHeader、PID 对应 Patient”这种口号式对应上,而要看清两种模型在结构组织、字段语义、数据类型和取值方式上的根本差异。只有把差异讲清楚,接口引擎里的转换规则、校验逻辑和异常处理才有依据。

两种模型的根本差异:消息驱动 vs 资源驱动

HL7 v2 是“消息驱动”的。一条消息(Message)由若干段(Segment)按顺序组成,段之间用段名区分,段内字段用竖线分隔,字段内还可以有成分(Component)、子成分(Subcomponent)和重复(Repetition)。消息的语义高度依赖上下文:同一个字段在不同消息类型、不同触发事件下含义可能不同。FHIR 则是“资源驱动”的。每个资源(Resource)是一个独立、自描述、可寻址的实体,资源之间通过引用(Reference)建立关系,而不是靠位置和顺序。FHIR 资源有明确的基数(0..1、1..* 等)、数据类型和术语绑定,天然适合做校验和持久化。

因此,v2 到 FHIR 的映射本质上是“把一条上下文相关的扁平消息,拆解成若干上下文自洽的资源,并重建它们之间的引用关系”。这个拆解和重建的过程,就是本节要重点讨论的内容。

MSH 段与 MessageHeader 资源

MSH 是每条 v2 消息的第一个段,承载消息的“信封”信息。它最关键的字段包括:MSH-1 字段分隔符、MSH-2 编码字符、MSH-3/4/5/6 发送与接收应用、设施,MSH-7 消息日期时间,MSH-9 消息类型(如 ADT^A01),MSH-10 消息控制 ID,MSH-11 处理 ID,MSH-12 版本号。

在 FHIR 中,这些信息主要落在 MessageHeader 资源里。MessageHeader.event 对应 MSH-9 的消息类型与触发事件;MessageHeader.source 对应发送方应用与设施;MessageHeader.destination 对应接收方;MessageHeader.timestamp 对应 MSH-7;MessageHeader.id 通常由 MSH-10 的消息控制 ID 派生,用于幂等去重。需要特别注意的是,MSH-9 在 v2 中是“消息类型^触发事件^消息结构”三段式,而 FHIR 的 eventCoding 是一个编码值,映射时需要把三段组合成一个可识别的编码,或分别保留在扩展中。

PID 段与 Patient 资源

PID 段是患者标识与人口学信息的核心。常见字段有:PID-3 患者标识列表(可重复,含 ID、分配机构、ID 类型)、PID-5 患者姓名、PID-7 出生日期、PID-8 性别、PID-11 地址、PID-13 电话、PID-16 婚姻状况等。

映射到 Patient 资源时,PID-3 的每个重复项通常对应一个 Patient.identifier,其中 ID 值对应 identifier.value,分配机构对应 identifier.assigner,ID 类型对应 identifier.type。PID-5 对应 Patient.name,v2 的姓名成分(姓、名、中间名、后缀)需要按 FHIR HumanName 的 family/given/prefix/suffix 重新拆分。PID-7 对应 Patient.birthDate,但 v2 允许只精确到年或年月,而 FHIR date 类型也支持这种精度,映射时要保留原始精度而不是强行补全。PID-8 对应 Patient.gender,但 v2 的性别代码表与 FHIR 的 administrative-gender 取值集并不完全一致,需要做术语映射。

这里有一个容易被忽视的差异:v2 的 PID-3 是“标识列表”,一个患者可以有多个 ID;FHIR 的 Patient.identifier 也是 0..*,看似一致,但 FHIR 要求每个 identifier 有明确的 system,否则无法保证全局唯一性。工程上通常把分配机构或命名空间映射为 system,把 ID 值映射为 value。

PV1 段与 Encounter 资源

PV1 段描述就诊信息。关键字段包括:PV1-2 患者类别(住院/门诊/急诊)、PV1-3 分配患者位置(病房、床位、设施)、PV1-7/8 主治/转诊医生、PV1-10 服务类型、PV1-19 就诊编号、PV1-44 入院时间、PV1-45 出院时间。

映射到 Encounter 资源时,PV1-19 的就诊编号对应 Encounter.identifier;PV1-2 对应 Encounter.class;PV1-44/45 对应 Encounter.period.start/end;PV1-3 的位置信息对应 Encounter.location,其中病房、床位、设施需要拆成 location.location 引用和 location.status。PV1-7/8 的医生信息对应 Encounter.participant,并通过 Practitioner 引用关联。PV1-10 的服务类型对应 Encounter.serviceType。

与 PID 类似,PV1 的字段也带有强烈的“位置语义”,而 FHIR 的 Encounter 更强调“时间区间 + 参与方 + 位置”的结构化表达。映射时不能只做字段搬运,还要判断哪些信息应该成为独立资源(如 Location、Practitioner),哪些应该内联在 Encounter 中。

字段与元素对应中的数据类型差异

v2 与 FHIR 在数据类型上的差异,是映射中最容易出错的地方。v2 的字段值通常是字符串,语义靠位置和分隔符承载;FHIR 的元素有明确的数据类型,如 code、Coding、CodeableConcept、HumanName、Address、Period、Reference 等。

以性别为例,v2 的 PID-8 是一个单字符代码(M/F/O/U),而 FHIR 的 Patient.gender 是 code 类型,取值集为 male/female/other/unknown。映射时需要一张术语对照表,而不是直接复制。再以姓名为例,v2 的 XPN 类型用成分分隔符拆分,FHIR 的 HumanName 用 family/given 等命名元素表达,映射时要处理“姓在前还是名在前”的文化差异。再以地址为例,v2 的 XAD 类型包含街道、城市、州、邮编、国家等成分,FHIR 的 Address 有 line、city、state、postalCode、country 等元素,但 line 是字符串数组,需要决定如何拆分多行地址。

时间类型也值得注意。v2 的 DTM 支持 YYYY、YYYYMM、YYYYMMDD、YYYYMMDDHHMMSS 等多种精度,FHIR 的 dateTime 同样支持不同精度,但要求时区信息用 ±HH:MM 表示。如果 v2 消息中没有时区,映射时不能随意假设,通常应保留为无时区或按机构约定补充。

映射关系的可视化

下面这张图把 MSH/PID/PV1 三个段与 MessageHeader/Patient/Encounter 三个资源之间的对应关系、关键字段和数据类型差异集中呈现出来,便于在实现映射规则时快速对照。

HL7 v2 段 → FHIR 资源映射对照 MSH / PID / PV1 与 MessageHeader / Patient / Encounter 的字段对应与类型差异 HL7 v2 消息(段 / 字段) FHIR 资源(元素 / 类型) MSH 消息头段 MSH-9 消息类型(ADT^A01) MSH-7 消息日期时间 MSH-10 消息控制 ID MSH-3/4/5/6 发送接收方 MessageHeader 资源 event : Coding timestamp : instant id : id(幂等去重) source / destination : Reference PID 患者标识段 PID-3 患者标识列表(可重复) PID-5 患者姓名(XPN) PID-7 出生日期(DTM) PID-8 性别(单字符代码) PID-11 地址(XAD) Patient 资源 identifier : Identifier(需 system) name : HumanName birthDate : date gender : code(术语映射) address : Address PV1 就诊段 PV1-2 患者类别(住院/门诊) PV1-3 分配患者位置 PV1-19 就诊编号 PV1-44/45 入院/出院时间 Encounter 资源 class : Coding location : Reference(Location) identifier : Identifier period : Period 直接字段映射 需术语/类型转换 橙色箭头表示存在数据类型或取值集差异,需显式转换规则

映射实现中的关键判断

在实际接口引擎中,映射规则通常以配置或代码形式存在。一个稳健的实现需要处理三类问题。第一类是“一对多”和“多对一”。例如 PID-3 的多个重复项要展开成多个 Patient.identifier,而 MSH-9 的三段式要合并成一个 eventCoding。第二类是“缺失与默认值”。v2 字段允许为空,FHIR 元素有基数约束,映射时要明确哪些空值可以省略、哪些必须补默认值、哪些应触发校验告警。第三类是“术语映射”。性别、患者类别、婚姻状况等代码表在 v2 和 FHIR 中并不一一对应,必须维护显式的映射表,并对未匹配代码记录日志。

一个实用的做法是:为每个段到资源的映射建立一张规则表,列出源字段、目标元素、转换函数、是否必填、异常处理策略。这样在消息量增大、消息类型增多时,映射逻辑仍然可维护、可测试、可审计。下面给出一个简化的规则表示例,用于说明这种组织方式。

源字段 目标元素 转换说明 异常策略
MSH-9 MessageHeader.event 三段式合并为 Coding,system 使用本地消息类型命名空间 无法解析时拒绝消息
MSH-10 MessageHeader.id 直接赋值,用于幂等去重 为空时生成 UUID 并告警
PID-3 Patient.identifier 每个重复项生成一个 Identifier,分配机构映射为 system 无 system 时记录告警
PID-8 Patient.gender M→male,F→female,O→other,U→unknown 未匹配代码置 unknown 并记录
PV1-2 Encounter.class I→IMP,O→AMB,E→EMER 未匹配代码保留原文并告警
PV1-44/45 Encounter.period DTM 转 dateTime,保留原始精度 出院早于入院时拒绝
映射不是简单的字段搬运。v2 的“位置语义”和 FHIR 的“命名语义”之间,需要一层显式的转换规则;这层规则的质量,直接决定了集成系统在真实消息流下的稳定性和可维护性。

总结来看,MSH/PID/PV1 与 MessageHeader/Patient/Encounter 的对照,核心不在于记住哪几个字段对应哪几个元素,而在于理解两种模型的组织哲学差异,并据此设计出可配置、可校验、可追溯的映射规则。掌握了这一点,后续面对 ORU、ORM、ADT 等更多消息类型时,就能用同一套方法论去拆解和落地。