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.
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.
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.
@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 v2 | FHIR |
|---|---|
PID-3 | Patient.identifier.value |
PID-5.1 / PID-5.2 | Patient.name.family / .given |
PID-7 / PID-8 | Patient.birthDate / .gender |
OBR / OBX | Observation (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.
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.
Related guides
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).