Early Access · beta. Help test and shape the 1.0 release. Where the project stands →

Developer guide

Convert HL7 v2 to FHIR in Python

Receive HL7 v2 over MLLP, map fields to a FHIR resource in a Python handler, and deliver it to a FHIR server.

The FHIR destination sends a FHIR-JSON resource or transaction Bundle built by your handler. It delivers to your server’s base URL as application/fhir+json. The default version is R4B; you can select R5 or STU3.

1. Install the engine

Install MessageFoundry from PyPI. The optional [fhir] extra adds typed validation against the FHIR spec before delivery.

terminal
pip install messagefoundry
# optional — typed FHIR validation before delivery
pip install "messagefoundry[fhir]"

2. Declare an HL7 inbound and a FHIR outbound

Receive HL7 v2 over MLLP. Set the FHIR REST destination to your server’s base URL, such as https://host/fhir. Use env() for the URL and token so each environment resolves its own values.

config/lab_to_fhir.py
import json
from messagefoundry import MLLP, FHIR, Send, env, inbound, outbound, router, handler

# receive HL7 v2 lab results over MLLP
inbound("IB_Lab_ORU", MLLP(port=2575), router="oru_router")

# deliver FHIR resources to your FHIR server (create = POST {base}/{ResourceType})
outbound("OB_FHIR", FHIR(url=env("fhir_base_url"), interaction="create"))

3. Map HL7 fields to a FHIR resource

Write the mapping in your Python handler. Read HL7 fields by path, build the resource, then Send the JSON to the FHIR destination.

config/lab_to_fhir.py
@router("oru_router")
def route(msg):
    if msg["MSH-9.1"] != "ORU":
        return []                       # UNROUTED
    return ["to_fhir"]

@handler("to_fhir")
def to_fhir(msg):
    # code-first mapping: HL7 v2 PID → FHIR Patient
    patient = {
        "resourceType": "Patient",
        "identifier": [{"value": msg["PID-3"]}],
        "name": [{"family": msg["PID-5.1"], "given": [msg["PID-5.2"]]}],
        "birthDate": msg["PID-7"],
        "gender": {"M": "male", "F": "female"}.get(msg["PID-8"], "unknown"),
    }
    return Send("OB_FHIR", json.dumps(patient))

Each HL7 v2 segment maps to the FHIR resource that fits it:

HL7 v2FHIR
PID-3Patient.identifier.value
PID-5.1 / PID-5.2Patient.name.family / .given
PID-7 / PID-8Patient.birthDate / .gender
OBR / OBXObservation (one per result), referencing the Patient

4. Run the engine

Point MessageFoundry at your config and environment. env("fhir_base_url") resolves from environments/<env>.toml; secrets like a bearer token resolve from the environment, never the config file.

terminal
python -m messagefoundry serve --config config --env dev --db messagefoundry.db

FHIR delivery is at-least-once, so server operations must tolerate repeated requests without duplicate writes. Use interaction="update" for PUT by id. Conditional options include if-none-exist, conditional-update, and if-match.

FAQ

Frequently asked questions

Can MessageFoundry convert HL7 v2 to FHIR?

Yes. Receive HL7 v2 over MLLP and map the needed fields to a FHIR resource in a Python handler. Deliver it through a FHIR REST connection. Keep the mapping in version control and test it with your interface code.

Which FHIR version does MessageFoundry target?

The FHIR REST connection defaults to R4B; R5 and STU3 are selectable. It sends application/fhir+json to your server’s base URL using create (POST), update (PUT), or transaction/batch interactions.

Where does the HL7-to-FHIR mapping live?

Write the mapping in your Python handler. Read fields by path, such as PID-5.1 or OBR-25, and build the FHIR resource. Review and test it with the rest of your code.

How does MessageFoundry avoid duplicate FHIR writes?

FHIR delivery is at-least-once, so server operations must tolerate repeated requests without duplicate writes. Use interaction='update' for PUT by id, or a conditional option: if-none-exist, conditional-update, or if-match.