Early Access · beta. We're building toward 1.0 — and we want people involved now, while it can still be shaped. Where the project stands →

Developer guide

Convert HL7 v2 to FHIR in Python

Bridge HL7 v2 feeds to a FHIR server with MessageFoundry: receive HL7 over MLLP, map the fields you need to a FHIR resource in a code-first Python handler, and deliver it over FHIR REST. The mapping is ordinary Python — reviewable, testable, and yours, never buried in a connector.

MessageFoundry ships a first-class FHIR destination. Your handler builds a FHIR-JSON resource (or a transaction Bundle) and the connection delivers it to your server's base URL as application/fhir+json — defaulting to R4B (R5 and STU3 are selectable).

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, and declare a FHIR REST destination pointed at your server's base URL (e.g. https://host/fhir). Keep the URL and any token in env() so each environment resolves its own.

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

The mapping lives in your handler, as plain Python. Read HL7 fields by path and build the resource you want, then Send the JSON to the FHIR outbound.

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

Idempotency, built in. FHIR delivery is at-least-once, so the server operation should be idempotent. The connection's native levers cover it: interaction="update" (PUT by id), or a conditional knob — if-none-exist, conditional-update, or if-match.

FAQ

Frequently asked questions

Can MessageFoundry convert HL7 v2 to FHIR?

Yes. You receive HL7 v2 (for example over MLLP), map the fields you need into a FHIR resource in a Python handler, and deliver it to your FHIR server with a FHIR REST connection. The mapping is code-first — ordinary Python you can version-control and test.

Which FHIR version does MessageFoundry target?

The FHIR REST connection defaults to R4B, with R5 and STU3 selectable. Resources are delivered as application/fhir+json to your FHIR server's base URL using the standard create (POST), update (PUT), or transaction/batch interactions.

Where does the HL7-to-FHIR mapping live?

In your handler, as plain Python — never hidden inside a connector. You read HL7 fields by path (PID-5.1, OBR-25, …) and build the FHIR resource explicitly, so the mapping is reviewable, testable, and yours.

How does MessageFoundry avoid duplicate FHIR writes?

FHIR delivery is at-least-once, so the server operation must be idempotent. The FHIR connection exposes the native levers: interaction='update' (PUT by id) or a conditional knob (if-none-exist, conditional-update, or if-match).