Migrate a hospital feed to FHIR and the first message you'll meet is the ADT^A01, "patient admit." It carries the demographics every downstream system needs, which makes it the natural first mapping target. Here's how the fields line up with a US Core Patient, and the places it quietly goes wrong.
The source: a minimal ADT^A01
MSH|^~\&|SendApp|HospA|RecvApp|HospA|20240115120000||ADT^A01^ADT_A01|MSG00001|P|2.5.1
EVN|A01|20240115120000
PID|1||MRN12345^^^HospA^MR||Smith^John^A||19850315|M|||123 Main St^^Springfield^IL^62701||(217)555-1234|||M
PV1|1|I|3N^302^A^HospA||||1234^Jones^Robert^M^MD
The demographics we care about live in the PID segment. MSH and PV1 carry routing and visit context, but US Core Patient is built almost entirely from PID.
The target: US Core Patient
US Core (the US FHIR profile baseline) constrains the base FHIR Patient resource. At minimum it expects an identifier (with a system), a name, and gender. birthDate is a must-support element, and US Core also defines extensions for race, ethnicity, and birth sex that real-world payers and registries look for.
Field-by-field
| HL7v2 | FHIR (US Core Patient) | Notes |
|---|---|---|
PID-3 (MRN12345^^^HospA^MR) | identifier[0] | The MR code maps to identifier.type (v2-0203 code system). Set identifier.system to your org's MRN URI. A missing system is the single most common US Core failure. |
PID-5.1 / PID-5.2 (Smith / John) | name[0].family / name[0].given[0] | PID-5.3 (A) is a middle name, so append it as a second given entry. |
PID-7 (19850315) | birthDate | Reformat YYYYMMDD → YYYY-MM-DD → 1985-03-15. |
PID-8 (M) | gender | Value-set gotcha: v2 uses M/F/O/U, FHIR wants male/female/other/unknown. A pass-through "M" is invalid FHIR. |
PID-11 (123 Main St^^Springfield^IL^62701) | address[0] | Map line / city / state / postalCode components individually. |
PID-13 ((217)555-1234) | telecom[0] (system=phone) |
A correctly mapped result looks like this:
{
"resourceType": "Patient",
"identifier": [{
"type": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "MR" }] },
"system": "urn:oid:2.16.840.1.113883.19.5",
"value": "MRN12345"
}],
"name": [{ "family": "Smith", "given": ["John", "A"] }],
"gender": "male",
"birthDate": "1985-03-15",
"address": [{ "line": ["123 Main St"], "city": "Springfield", "state": "IL", "postalCode": "62701" }],
"telecom": [{ "system": "phone", "value": "(217)555-1234" }]
}
The two gotchas that fail US Core on "valid" v2
-
Missing identifier system. A v2 MRN is just a value in PID-3.1 with an assigning authority in PID-3.4. It has no URI. US Core requires
identifier.system, so you have to inject your org's canonical system URI (typically an OID or a URL you own) during the map. Skip that and the resource fails conformance even though the v2 message was perfectly valid. -
Unmapped code values.
genderis the classic example. PID-8 carriesM/F/O/U(HL7 table 0001), but the FHIRadministrative-gendervalue set ismale/female/other/unknown. PassMstraight through and you get a resource that parses as JSON but fails the required binding. The same trap applies to marital status, administrative sex, and any other coded field. Always translate the value set, never pass through.
Validate both ends
These bugs survive because people validate the v2 or the FHIR, never both against the same expectation. Check the v2 conforms, run the map, then score the output against US Core. Skip it and you ship resources that pass JSON schema but fail the profile, and you find out from a downstream partner instead of from your own pipeline.
You can do the whole loop in the browser, which matters for PHI because nothing leaves your machine. Paste the ADT, see the segment tree, run the mapping, and score the resulting Patient against US Core.