Reference

# HL7 v2 to FHIR mapping reference

The segment-to-resource and field-to-field tables for mapping HL7 v2 messages to FHIR resources, aligned with the HL7 v2-to-FHIR implementation guide, with Python code for each mapping.

10 segments mapped

ADT, ORU message types

R4 FHIR version

adt\_to\_patient.py

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

# Zato
from zato.server.service import Service

class ADTToPatient(Service):
    name = 'demo.mapping.adt-to-patient'

    def handle(self) -> 'None':

        # An ADT^A01, parsed by the MLLP channel
        msg = self.request.input

        # PID-3 -> identifier, PID-5 -> name
        mrn = msg.pid.patient_identifier_list.id_number
        family_name = msg.pid.patient_name.family_name

        client = self.fhir['FHIR.Sample']

        patient = client.resource('Patient',
            identifier = [{'system': 'urn:mrn', 'value': mrn}],
            name = [{'family': family_name}],
        )

        patient.save()
```

Automatic

## Can the whole mapping be done automatically?

Yes - every parsed HL7 v2 message converts itself to a FHIR bundle with one call, msg.to\_fhir(), and no configuration. The tables below still matter, because they describe what that call produces, but you do not have to implement them - the converter builds the Patient, Encounter, Observations and the other resources, wires the references between them and reports anything it could not map. Site-specific settings, such as identifier system URIs or local code values, go into an .ini file - see the [automatic conversion documentation](https://zato.io/docs/dev/healthcare/hl7/to-fhir/index.html).

adt\_to\_fhir\_auto.py

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

# Zato
from zato.server.service import Service

class ADTToFHIRAuto(Service):
    name = 'demo.mapping.adt-to-fhir-auto'

    def handle(self) -> 'None':

        # An ADT^A01, parsed by the MLLP channel ..
        msg = self.request.input

        # .. one call maps the whole message to a FHIR bundle ..
        bundle = msg.to_fhir()

        # .. and the bundle is ready to post to a FHIR server.
        client = self.fhir['FHIR.Sample']
        client.execute('', method='post', data=bundle.to_dict())
```

Master table

## Which FHIR resource does each HL7 v2 segment map to?

The table below is the top-level map, aligned with the HL7 "v2-to-FHIR" implementation guide. A segment rarely maps to a whole resource one to one - it contributes fields to one - but a given segment's data is written to the resource in the second column.

| HL7 v2 segment | FHIR resource | What it contains |
| --- | --- | --- |
| MSH | MessageHeader, Bundle | Message metadata, sender, receiver, timestamps |
| EVN | Encounter, Provenance | The trigger event and when it was recorded |
| PID | Patient | Demographics, identifiers, names, addresses |
| PV1 | Encounter | Visit class, location, attending practitioner |
| OBX | Observation | Results - values, units, reference ranges, status |
| OBR | DiagnosticReport, ServiceRequest | The order and report the observations belong to |
| AL1 | AllergyIntolerance | Allergen type, code, severity, reaction |
| DG1 | Condition | Diagnoses with coding and type |
| IN1 | Coverage | Insurance plan, subscriber, payor |
| NK1 | RelatedPerson | Next of kin and contact relationships |

ADT mapping

## How do I map an ADT^A01 to a FHIR Patient and Encounter?

An ADT^A01 admission holds the patient in PID and the visit in PV1. The field-level map for the resources they become:

| HL7 v2 field | FHIR element | Notes |
| --- | --- | --- |
| PID-3 patient identifier list | Patient.identifier | Repeating - each repetition becomes one identifier |
| PID-5 patient name | Patient.name | XPN components map to family and given |
| PID-7 date/time of birth | Patient.birthDate | YYYYMMDD reformatted to YYYY-MM-DD |
| PID-8 administrative sex | Patient.gender | M to male, F to female, coded values translated |
| PID-11 patient address | Patient.address | XAD components map to line, city, postalCode |
| PV1-2 patient class | Encounter.class | I to inpatient, O to outpatient, E to emergency |
| PV1-3 assigned location | Encounter.location | Ward, room and bed from the PL components |
| PV1-44 admit date/time | Encounter.period.start | Timestamp reformatted to ISO 8601 |

adt\_to\_patient\_encounter.py

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

# Zato
from zato.server.service import Service

class ADTToPatientEncounter(Service):
    name = 'demo.mapping.adt-to-patient-encounter'

    def handle(self) -> 'None':

        # The MLLP channel already parsed the ADT^A01 ..
        msg = self.request.input

        # .. PID fields, by their semantic names ..
        mrn = msg.pid.patient_identifier_list.id_number
        family_name = msg.pid.patient_name.family_name
        given_name = msg.pid.patient_name.given_name
        date_of_birth = msg.pid.date_time_of_birth
        sex = msg.pid.administrative_sex

        # .. translate the coded values to FHIR ..
        gender_map = {'M': 'male', 'F': 'female', 'O': 'other', 'U': 'unknown'}
        gender = gender_map[sex]

        birth_date = f'{date_of_birth[0:4]}-{date_of_birth[4:6]}-{date_of_birth[6:8]}'

        # .. build and save the Patient ..
        client = self.fhir['FHIR.Sample']

        patient = client.resource('Patient',
            identifier = [{'system': 'urn:mrn', 'value': mrn}],
            name = [{'family': family_name, 'given': [given_name]}],
            birthDate = birth_date,
            gender = gender,
        )

        patient.save()

        # .. and the Encounter that points back at the Patient.
        class_map = {'I': 'IMP', 'O': 'AMB', 'E': 'EMER'}
        encounter_class = class_map[msg.pv1.patient_class]

        encounter = client.resource('Encounter',
            status = 'in-progress',
            subject = patient,
        )

        # "class" is a Python keyword, dict syntax sets the field
        encounter['class'] = {'code': encounter_class}

        encounter.save()
```

ORU mapping

## How do I map an ORU^R01 lab result to FHIR Observation?

An ORU^R01 contains the order in OBR and the results in OBX segments. Each OBX becomes one Observation - OBX-3 is the code, OBX-5 the value, OBX-6 the units, OBX-7 the reference range and OBX-11 the status.

oru\_to\_observation.py

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

# Zato
from zato.server.service import Service

class ORUToObservation(Service):
    name = 'demo.mapping.oru-to-observation'

    def handle(self) -> 'None':

        msg = self.request.input
        client = self.fhir['FHIR.Sample']

        # OBX-11 status codes translated to FHIR
        status_map = {'F': 'final', 'P': 'preliminary', 'C': 'corrected'}

        # OBX-3, the observation code, e.g. a LOINC code
        code = msg.get('OBX.3.1')
        code_text = msg.get('OBX.3.2')

        # OBX-5 value and OBX-6 units
        value = msg.get('OBX.5')
        unit = msg.get('OBX.6.1')

        observation = client.resource('Observation',
            status = status_map[msg.get('OBX.11')],
            code = {
                'coding': [{'system': 'http://loinc.org', 'code': code}],
                'text': code_text,
            },
            valueQuantity = {
                'value': float(value),
                'unit': unit,
            },
        )

        observation.save()
```

Repetitions

## How do I handle repeating fields, components and sub-components?

HL7 v2 packs multiple values into one field with repetition, component and subcomponent separators. [Path expressions](https://zato.io/docs/dev/healthcare/hl7/v2/field-access/index.html#path-expressions) address all of them - repetitions with an index in square brackets, components and subcomponents with further dotted positions.

repetitions.py

```
# PID-3 with two repetitions:
#     12345^^^HOSP^MR~67890^^^CLINIC^PI

# The first repetition, then the second
mrn = msg.get('PID.3[0]')
clinic_id = msg.get('PID.3[1]')

# A component of the second repetition - the identifier type
id_type = msg.get('PID.3[1].5')

# A subcomponent - for PID-3 of PT1^^^ACME&1.2.3&ISO^MR,
# the universal ID inside the assigning authority
universal_id = msg.get('PID.3.4.2')

# Every repetition of an identifier becomes one FHIR identifier
identifiers = []

for repetition in msg.pid.patient_identifier_list:
    identifiers.append({
        'system': 'urn:mrn',
        'value': repetition.id_number,
    })
```

Scope

## What does this page cover and what does it not?

Zato performs the transformation in Python - parsing HL7 v2 into typed objects, reading fields, building FHIR resources and saving them to a FHIR server. It does not perform FHIR profile validation and it does not generate FHIR narrative - validate against profiles on the FHIR server side if your project requires it.

This page is the field-by-field map. For the platform mechanics of the same flow - channels, connections, deployment - see the [HL7 v2 to FHIR transformation](https://zato.io/docs/dev/healthcare/transformation/index.html) page.

FAQ

## Frequently asked questions

Patient. PID-3 becomes Patient.identifier, PID-5 Patient.name, PID-7 Patient.birthDate, PID-8 Patient.gender and PID-11 Patient.address.

Yes - the HL7 "v2-to-FHIR" implementation guide, which the tables on this page align with. It defines message-level, segment-level and datatype-level mappings, and projects adapt it to their local profiles.

No. Map what the receiving side needs - a minimal Patient with an identifier and a name is often enough to start, and more fields can be added as consumers ask for them.

The same services work in reverse - read FHIR resources with self.fhir, build segments with the typed classes from zato.hl7v2 and send the serialized message through an outgoing MLLP connection.

Related

## Keep reading

[Auto Automatic HL7 v2 to FHIR conversion](https://zato.io/docs/dev/healthcare/hl7/to-fhir/index.html) [MLLP How to receive HL7 v2 messages in Python](https://zato.io/integrations/healthcare/receive-hl7-messages-python/) [Facade Building a FHIR facade over legacy HL7 v2](https://zato.io/integrations/healthcare/fhir-facade-over-hl7-v2/) [Docs HL7 v2 to FHIR transformation](https://zato.io/docs/dev/healthcare/transformation/index.html)

## Ready to transform HL7 v2 into FHIR?

Get started with Zato and map your first message in minutes.

[Get started ▶](https://zato.io/docs/admin/guide/install/index.html) [Read the docs](https://zato.io/docs/index.html)

Open source In Python
