Automatic HL7v2 to FHIR conversion

Bring every HL7 v2 feed into your FHIR ecosystem with no mapping tables to build or maintain.

Move clinical data from HL7 v2 interfaces into FHIR servers with no mapping tables to build or maintain. One call converts a message that arrived over MLLP or was parsed with parse_hl7:

bundle = msg.to_fhir()

The result is a typed FHIR R4 transaction bundle - a Patient built from PID, an Encounter from PV1, Observations from OBX, and so on for every segment the message contains. All cross-references between the resources are wired up inside the bundle, and data that has no FHIR equivalent - custom Z-segments and unknown code values alike - is preserved as extensions, so nothing is dropped.

A complete service

The service below receives ADT admissions over MLLP, converts each message and posts the resulting bundle to a FHIR server through an outgoing FHIR connection:

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

# ###########################################################################
# ###########################################################################

if 0:
    from zato.hl7v2.base import HL7Message

# ###########################################################################
# ###########################################################################

class ADTToFHIR(Service):
    """ Receives ADT admissions over MLLP and stores them in a FHIR server.
    """
    name = 'hl7-api.adt-to-fhir'

    def handle(self) -> 'None':

        # The MLLP channel already parsed the raw ER7 bytes for us
        msg:'HL7Message' = self.request.input

        # One call converts the whole message to a FHIR transaction bundle
        bundle = msg.to_fhir()

        # Post the bundle to the FHIR server
        client = self.fhir['FHIR.Sample']
        response = client.execute('', method='post', data=bundle.to_dict())

        self.logger.info('Stored %s in FHIR server -> %s',
            msg.msh.message_control_id, response['type'])

The service works without any mapping table or configuration file. When site-specific settings are needed, such as your own identifier system URIs or local code values, they go into an .ini file named in the call, e.g. msg.to_fhir(config='hl7-fhir-demo').

What comes out of a message

The resources produced depend on the segments present. For a typical ADT^A01 admission:

from zato.hl7v2 import parse_hl7

raw = (
    'MSH|^~\\&|SENDER|FACILITY|RECEIVER|FAC|20260315101112||ADT^A01^ADT_A01|CTL001|P|2.9\r'
    'EVN|A01|20260315\r'
    'PID|||12345^^^HOSP^MR||SMITH^JOHN^A||19800115|M|||123 MAIN ST^^BOSTON^MA^02101\r'
    'NK1|1|SMITH^JANE|SPO\r'
    'PV1|1|I|WARD^101^BED1|||||1234^JONES^MARIA|||||||||||V001\r'
    'AL1|1|DA|70618^Penicillin|SV\r'
    'DG1|1||I10^Essential hypertension^I10|||A\r'
)

msg = parse_hl7(raw, validate=False)
bundle = msg.to_fhir()

for entry in bundle.entry:
    print(entry.resource.resource_type)
Organization
Organization
MessageHeader
Patient
RelatedPerson
Location
Practitioner
Encounter
AllergyIntolerance
Condition

The two Organizations are the sending and receiving facilities from MSH-4 and MSH-6, referenced from the MessageHeader. An ORU^R01 lab result additionally produces a ServiceRequest, a DiagnosticReport, one Observation per OBX and a Specimen per SPM, with the report linked to its order and results. The full segment-by-segment breakdown for every message family is in what you get per message.

Dict and JSON output

When the bundle is needed as plain data rather than a typed resource, two shortcuts convert in one step:

data = msg.to_fhir_dict()
text = msg.to_fhir_json()

# Pretty-printed JSON
text = msg.to_fhir_json(indent=2)

Both accept the same config argument as to_fhir.

Validation before sending

Like any typed FHIR resource, the bundle's resources can be validated against the R4 schema before anything leaves your service:

# Zato
from zato.fhir import validate

for entry in bundle.entry:
    result = validate(entry.resource)

    if not result.is_valid:
        self.logger.warning('Validation errors: %s', result.errors)

See also

PageWhat it covers
What you get per messageThe resources produced for each message family, segment by segment
ConfigurationIdentifier systems, timezone, code overrides and the extension base URL
Sending bundles to FHIR serversPosting a bundle, reading the reply and handling rejections
FHIR connectionsThe outgoing connection that delivers bundles to a server

Learn more


Schedule a meaningful demo

Book a demo with an expert who will help you build meaningful systems that match your ambitions

"We evaluated 12 integration platforms and Zato was the only one to score 100%."

Philip Zuñiga, Assistant Professor, University of the Philippines