What FHIR actually is¶
FHIR is usually taught as an API. That framing is accurate and unhelpful for our purposes, because the API is not the part that determines what your OMOP instance will look like. The part that determines that is the data model: what a resource contains, what is optional, and where the meaning lives.
So this chapter teaches FHIR as a document model.
The one-sentence version¶
FHIR is a specification for exchanging health data as a set of independently addressable documents called resources, each covering one clinical or administrative idea, connected to each other by references.
Resources¶
A resource is a self-contained chunk of health data covering a single idea. There are roughly 150 of them in the specification, but a FHIR to OMOP project usually cares about fifteen.
The ones that carry most of the weight:
| Resource | What it holds | Usual OMOP destination |
|---|---|---|
Patient |
Demographics and identifiers | PERSON |
Encounter |
An interaction with the health system | VISIT_OCCURRENCE, VISIT_DETAIL |
Condition |
A diagnosis, problem, or health concern | CONDITION_OCCURRENCE, sometimes elsewhere |
Observation |
Vital signs, laboratory results, social history, survey responses | MEASUREMENT or OBSERVATION, decided by code |
MedicationRequest |
A prescription or order | DRUG_EXPOSURE |
MedicationDispense |
A pharmacy dispensing event | DRUG_EXPOSURE |
MedicationAdministration |
A dose actually given | DRUG_EXPOSURE |
MedicationStatement |
A report that someone is taking something | DRUG_EXPOSURE |
Procedure |
Something done to a person | PROCEDURE_OCCURRENCE, sometimes elsewhere |
Immunization |
A vaccination | DRUG_EXPOSURE |
AllergyIntolerance |
A recorded allergy | OBSERVATION |
DiagnosticReport |
A grouping of results with interpretation | Usually its component Observations, plus NOTE |
DocumentReference |
A pointer to a clinical document | NOTE |
Coverage |
Insurance information | PAYER_PLAN_PERIOD |
Organization, Location, Practitioner |
Health system context | CARE_SITE, LOCATION, PROVIDER |
Notice the "sometimes elsewhere" entries. Those are not sloppiness in the table. They are the domain routing rule doing its work, and they are covered in domain routing.
What a resource looks like¶
Here is a minimal Observation, trimmed to the parts that carry meaning for the transformation.
{
"resourceType": "Observation",
"id": "obs-a1c-0912",
"status": "final",
"category": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "laboratory"
}]
}],
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "4548-4",
"display": "Hemoglobin A1c/Hemoglobin.total in Blood"
}]
},
"subject": { "reference": "Patient/alvarez-r" },
"encounter": { "reference": "Encounter/enc-2024-0912" },
"effectiveDateTime": "2024-09-12T09:40:00-04:00",
"valueQuantity": {
"value": 7.8,
"unit": "%",
"system": "http://unitsofmeasure.org",
"code": "%"
},
"referenceRange": [{
"low": { "value": 4.0, "unit": "%" },
"high": { "value": 5.6, "unit": "%" }
}]
}
Read that as a set of answers rather than a record layout:
- What kind of thing is this?
code, a LOINC code. This single element drives the routing decision and the concept mapping. - About whom?
subject, a reference. - When?
effectiveDateTime, with timezone and second-level precision. It will not always be this precise. - What was the answer?
valueQuantity, with a UCUM unit. The value could instead have been a code, a string, a boolean, a ratio, or absent entirely. - Compared to what?
referenceRange, which maps cleanly torange_lowandrange_highinMEASUREMENT. - How reliable?
status, which is easy to ignore and should not be.
References¶
Resources point at each other by reference. "subject": { "reference": "Patient/alvarez-r" } is a pointer.
Three things about references matter to you:
They can be relative, absolute, or logical. A relative reference like Patient/alvarez-r is resolved against the server it came from. An absolute reference points at another server entirely. A logical reference identifies a thing by business identifier without a resolvable URL. A pipeline that assumes all references are relative will break on real data.
They can be unresolvable. A resource can reference something the server will not give you, either because it was not exported, because you lack permission, or because the reference was always aspirational. Your pipeline needs a defined behavior for this, and "throw an exception" is rarely the right one at scale.
They can be contained. A resource can carry a complete other resource inside itself rather than referencing it externally. Medication is commonly contained inside MedicationRequest. If your parser only handles external references, contained resources become invisible.
Profiles and implementation guides¶
The base FHIR specification is deliberately permissive. Almost everything is optional, cardinalities are loose, and terminology bindings are frequently example strength, meaning a server can use whatever codes it likes.
That permissiveness is what makes FHIR workable across a heterogeneous world, and it is also what makes a naive FHIR to OMOP pipeline fail. You cannot write a transformation against the base specification and expect it to work, because the base specification permits far too much variation.
Profiles fix this. A profile constrains a resource: it makes optional elements required, narrows cardinalities, and binds terminology more tightly. An implementation guide is a published, coherent collection of profiles for a specific purpose.
The ones you will meet:
- US Core is the one with the most reach in the United States. Certified EHR technology must support it under the ONC certification program flowing from the 21st Century Cures Act, which is the single largest reason usable FHIR data exists at scale. When someone says "we have FHIR", they usually mean "we have US Core".
- International Patient Summary and various national IGs play the equivalent role elsewhere.
- mCODE constrains oncology data and has been a proving ground for FHIR to OMOP work.
- The FHIR to OMOP IG is the guide specifically about this transformation, covered in its own chapter.
The practical version
Ask early: which implementation guide does this server claim to conform to, and has anyone validated that claim? "US Core 6.1" and "US Core, mostly" are very different starting conditions for a project plan.
Versions¶
FHIR R4 (4.0.1) is the version with overwhelming production deployment, largely because it is the version regulation pointed at. R5 is published and adopted in some places. R6 is progressing through the standards process.
For a FHIR to OMOP project the practical guidance is: expect R4, confirm it, and check whether your source has R4B or R5 elements bleeding in. Version differences between R4 and R5 are meaningful in specific resources and irrelevant in others, so ask the question per resource rather than globally.
Where this quietly breaks¶
Status fields are meaningful and routinely ignored. An Observation with status of entered-in-error is not a result, it is a retraction. A Condition with verificationStatus of refuted means the person does not have that condition. Loading these as facts introduces errors that are invisible downstream and extremely hard to trace back. Decide your status handling explicitly and write it down.
Optional means optional. Two servers can both be conformant and give you very different completeness. Profile a sample of real data before you commit to a transformation design, rather than writing against the specification and discovering the gaps in integration testing.
The display string is not the code. coding.display is a human convenience and is frequently wrong, locally customized, or absent. Map on system plus code. Never map on display text, however tempting it looks when you are debugging.
A bundle is not a database. A search result is a page of a query, shaped by the search parameters you sent. Missing data in a bundle may mean the data does not exist, or may mean you did not ask correctly.
Next¶
- What OMOP actually is, the other half of the picture
- Reading a bundle out loud, a guided walk through the running example