How do you correctly map HL7 v2 messages to FHIR resources?
Sooner or later you have to turn HL7 v2 messages into FHIR resources — to expose a v2 core to apps, satisfy US Core, or feed a FHIR data store. The transformation sounds mechanical but is where integrations accumulate their most subtle bugs: identity mismatches, dropped codes, and structural mismappings that pass validation yet misrepresent the data. This guide walks the field-level mapping for the messages you'll convert most, and the traps — identity, terminology, cardinality — that make or break correctness.
The mental shift: events to resources
HL7 v2 is event-shaped ('an admission happened'); FHIR is resource-shaped ('here is the Encounter, and the Patient it belongs to'). Mapping means decomposing one event message into the set of resources it implies and their references. A single ADT^A01 doesn't map to one FHIR resource — it maps to a Patient, an Encounter, often a Practitioner and Location, wired together with references. Seeing a v2 message as a bundle of resources is the first step to mapping it well.
ADT → Patient, Encounter, and friends
- PID demographics → Patient (name, birth date, gender, identifiers as Patient.identifier with proper systems).
- PV1 visit → Encounter (class, status, period, location, participants).
- Attending/other providers in PV1 → Practitioner and Encounter.participant references.
- The trigger event drives Encounter.status: A01 → in-progress, A03 → finished, A11 → the encounter should be cancelled/removed.
ORU → DiagnosticReport and Observation
A result message maps cleanly if you respect its structure: the OBR becomes a DiagnosticReport (the ordered test and its overall status), and each OBX becomes an Observation (a single measured value with its code, value, units, reference range, and abnormal interpretation). The DiagnosticReport references its Observations, and both reference the Patient and the order. Crucially, the v2 result status (OBX-11) must map to the FHIR Observation.status — preliminary stays preliminary, corrected supersedes — or you reintroduce the very safety bug FHIR was supposed to make explicit.
SIU → Appointment; DFT → charges
Scheduling messages (SIU) map to FHIR Appointment (and Schedule/Slot where relevant), and detailed financial transactions (DFT) map toward billing-oriented resources like ChargeItem or an invoice representation. The pattern is the same: identify the FHIR resource(s) the v2 event describes, map each field to the corresponding resource element, and wire references. Not every v2 field has a FHIR home — decide deliberately what to carry, drop, or place in an extension.
Identity is the number-one trap
The most damaging mapping bugs are identity bugs. A v2 MRN must become a Patient.identifier with the correct system (assigning authority), and the same real patient must resolve to the same FHIR Patient every time — otherwise you fork one person into many resources. Placer/filler order numbers must survive into the FHIR resources so results still correlate to orders. Design a deterministic identity strategy before you map a single demographic field; retrofitting identity after you've created duplicate patients is painful.
Terminology: the second trap
- v2 often carries local codes; FHIR (and US Core) expect LOINC for labs, SNOMED CT for conditions, RxNorm for meds.
- Map codes into CodeableConcept with the proper system URI — a code with no system is nearly useless downstream.
- Preserve the original local code as an additional coding so nothing is lost in translation.
- Terminology mapping, not field copying, is usually the largest and most valuable part of the work.
Validate the output, don't just produce it
A mapping that emits syntactically valid FHIR can still be wrong — missing must-support elements, invalid references, wrong statuses. Validate transformed resources against the profiles you claim to support (e.g. US Core) as part of your pipeline, and test with real, messy production messages, not just clean samples. The mapping is correct when the FHIR it produces would satisfy the system that has to consume it — not merely when it parses.


