Identifier.system in v2-to-FHIR conversion: who decides the register’s URI
Mapping PID-3 to Identifier.system: what the v2-to-FHIR ConceptMaps really say, why the identifier registry cannot answer, and how the decision gets made on a project.
An ADT message arrives from a patient administration system and has to come out as FHIR. Stripped to the part that matters, and synthetic, the segment carrying the patient identifiers looks like this:
PID|1||441122^^^RIVERLAND^MR~3950000000^^^AUSHIC&2.16.840.1.113883.3.879&ISO^MC||TESTPATIENT^ALEX||19800101|M
Two numbers. The first is a local medical record number whose only clue to provenance is the string RIVERLAND. The second also carries an OID and the code ISO saying what kind of OID it is. One question decides the rest of the conversion: which URI goes into Identifier.system?
In FHIR, system is the namespace of the value. It says which register issued the number. Two records are recognised as carrying the same identifier only if value and system both match. Getting that field wrong produces one of two failures, both quiet. Derive the system from text and one register splits into several the day a sender changes case or abbreviation, so one patient becomes two people. Leave the system out and numbers from different registers land in the same bucket, so two patients become one.
The mistake: urn:oid: in place of the assigning authority
Last week we went through the output of a colleague’s v2-to-FHIR converter in a public discussion on chat.fhir.org. Among other things we told him the second identifier above should become system: "urn:oid:2.16.840.1.113883.3.879", because the OID is right there and the code says it is an ISO OID. He shipped it. We were wrong, and the correction is more interesting than the mistake.
The HD→uri and CX→Identifier ConceptMaps
The mapping rules in the v2-to-FHIR Implementation Guide are published as downloadable ConceptMaps, and reading the JSON is more reliable than reading the rendered page. Two of them apply here.
The first maps the HD datatype to a URI. HD has three components: a local name, a global identifier, and a code saying what kind of global identifier that is — HL7 table 0301, where ISO means an OID, alongside UUID, DNS, URI, L for local and others. The map sends HD.1, the local name, straight into the URI, with no condition attached. It sends HD.2 to urn:oid: plus the value only if HD.1 is not valued and HD.3 is ISO; to urn:uuid: under the same condition when HD.3 is UUID; and to the bare value otherwise.
The second maps CX, the identifier datatype, to Identifier. CX.4, the assigning authority, goes to system only if CX.4 is in the FHIR identifier registry. Otherwise it goes to assigner, a reference to an Organization, with an instruction to work out case by case whether the thing is an organisation or a system.
Applied to the letter to our message: HD.1 is valued (AUSHIC), so the rows that produce the urn:oid: never fire. And AUSHIC is not in the registry, so it does not reach system either. By the letter of the map that number has no system at all — it has an assigner. Our urn:oid: was a better output than the rule prescribes, but it was a deviation. An undeclared deviation in a converter is the problem, not the deviation.
The R4 identifier registry: 57 entries, nearly all US
The condition everything hangs on is “if the assigning authority is in the registry”. It is worth opening. In R4 the identifier registry has 57 rows: the DICOM UID, the GTIN, and otherwise United States identifiers — Social Security Number, Medicare, MBI, National Provider Identifier and some fifty state driver’s licences. No Australian entry. No Italian entry. For any sender outside the United States that condition can only evaluate false. The rule is not wrong; it has nothing to point at.
It is also on its way out. In the continuous build of the specification that page no longer holds a list at all: the content moved to HL7 Terminology, curated by HL7 International, with instructions to follow the Terminology Services Management Group process when a URI is not registered. The ConceptMap still points at the old page.
National base guides: AU Base and HL7 Italia
The right home for this decision is the base implementation guide of the sender’s country, and that is where the country starts to matter a great deal.
Australia has one. AU Base, in the profile for the Medicare card number, fixes Identifier.system to a specific URI and pins the type to code MC from table 0203. For that identifier the answer exists, in writing, in a published document — not in the mapping IG and not in the registry.
Italy is more exposed, and it is a useful contrast because the mechanism is the same and the coverage is not. The HL7 Italia Patient profile slices Patient.identifier by identifier type. The fiscal code slice fixes system to http://hl7.it/sid/codiceFiscale and adds an invariant checking its sixteen characters; the national population register identifier is fixed in the same way. But the slice that receives the number an Italian hospital actually puts in PID-3 is the regional one, and there system is mandatory with a required binding to a value set of URIs. That value set holds five entries: Lazio, Piemonte, Valle d’Aosta, Veneto, Friuli-Venezia Giulia — three written as urn:oid: and two as URLs under hl7.it. A sender in any other region has no conformant value for a mandatory element.
Nobody is at fault here: that profile is marked draft and dates from a 2020 public comment ballot. It is simply the fact a project starts from today — for most Italian senders the answer is not there.

Assigning the system is a project decision
When there is no rule to apply, somebody decides. Better that the decision be explicit and written down. What works for us:
- a table, in configuration rather than in code, from assigning authority to system URI, defined per sender and versioned alongside the interface;
- where a published national or regional URI exists, use it, even when an invented one would be tidier;
- where none exists, define one under a domain we control, and state plainly that it is local;
- never derive the system from the text of the HD: the same authority spelled two ways becomes two registers;
- never emit an identifier with no system: a declared local URI beats none.
And the conversion report has to say, for every identifier, which rule it applied. Because this is one of the few cases where validation does not help: a wrong system is a perfectly valid URI. No validator — not BridgeLab either, the workbench we inspect these messages with — can know whether the register you named is the register that issued the number.
Which gives you the question to ask anyone bringing you a converter, before asking how many message types it supports: which URI do you put in Identifier.system for our assigning authorities, is that list configurable, and does the report say which rule you applied to each identifier?