The ten-minute orientation¶
Read this once before anything else. It gives you a frame, and the frame keeps every later detail from feeling arbitrary.
The whole thing in one paragraph¶
FHIR is how health systems hand data to each other about one person at a time, in a format designed for exchange. OMOP is how researchers organize data about many people over time, in a format designed for analysis. Neither can do the other's job well. Getting from FHIR to OMOP means taking a set of loosely connected documents about a person and turning them into dated rows on a timeline, where every clinical idea has been translated into a shared vocabulary so that a query written in Boston returns comparable results in Barcelona. Most of the difficulty is not technical. It is that the two models disagree about what the fundamental unit of health data is.
The three shapes to hold in your head¶
FHIR is a graph¶
A FHIR server holds resources that point at each other. An Observation points at a Patient and maybe at an Encounter. A MedicationRequest points at a Patient, a Practitioner, and possibly a Medication. Pull on any thread and you get a connected web.
The unit is the resource. The organizing question is what do I need to send you.
OMOP is a ledger¶
An OMOP instance holds rows, and every clinical row carries a person, a date, and a concept ID. Conditions go in one table, drugs in another, measurements in a third. The tables connect through the person and through time.
The unit is the event. The organizing question is what happened to this population, and when.
The vocabulary is the hinge¶
This is the part people miss. OMOP does not just restructure the data, it retranslates it. Every clinical code in the source gets mapped to a standard concept from a designated vocabulary: conditions to SNOMED CT, drugs to RxNorm, measurements to LOINC, and so on. The concept ID is the load-bearing element of the whole model, because it is what makes a query portable across institutions.
If you remember nothing else: restructuring is the easy half, retranslation is the hard half.
The transformation in six moves¶
| Move | What happens | Where it gets hard |
|---|---|---|
| 1. Acquire | Pull FHIR resources, usually through a Bulk Data export producing NDJSON | Export completeness varies enormously by server |
| 2. Stage | Land the raw JSON somewhere immutable and queryable | Skipping this makes every later question unanswerable |
| 3. Identify | Resolve people, build PERSON, assign person_id | Identity resolution and the race and ethnicity split |
| 4. Frame time | Build VISIT_OCCURRENCE and derive OBSERVATION_PERIOD | Encounter granularity, and the fact that FHIR has no observation period |
| 5. Route and translate | Send each clinical fact to its table and map its code to a standard concept | Domain routing, unmapped codes, and units |
| 6. Verify | Run quality checks, compare counts, read failures back to decisions | Knowing which failures are real problems and which are expected |
The three things that surprise everyone¶
One. The code decides the table, not the resource type.
A FHIR Condition resource does not automatically become a row in CONDITION_OCCURRENCE. The standard concept that the condition's code maps to has a domain_id, and that domain decides the destination. A SNOMED code that everyone calls a diagnosis can carry a domain of Observation or Measurement, and then that is where the row goes. Newcomers fight this rule for about two weeks and then it becomes second nature.
Two. FHIR has no concept of an observation period.
OMOP requires a record of when each person was actually under observation, because without it you cannot tell the difference between "this person had no heart attack" and "this person was not in our data during that window". FHIR has no equivalent field. You have to derive it, and how you derive it changes the denominator of every study that will ever run against your data.
Three. Encounter does not mean visit.
Different systems model an Encounter at wildly different granularity. One hospital stay might be a single Encounter, or forty of them arranged in a partOf hierarchy, or a scattering of unlinked resources. OMOP wants VISIT_OCCURRENCE to represent a coherent care episode and VISIT_DETAIL to hold the finer grain underneath. Getting from one to the other is often the single largest piece of engineering in the whole pipeline.
What you are actually deciding¶
Every choice in this transformation is a choice about what a future researcher will be allowed to ask.
Collapse encounters too aggressively and length-of-stay studies become impossible. Drop unmapped codes silently and a rare disease disappears from your data set. Round a year-only birth date to January first and pediatric age calculations quietly skew. Discard the source code after mapping and nobody can ever audit your translation.
None of those produce an error. All of them produce a data set that answers some questions correctly and other questions confidently but wrongly. That is why documentation of assumptions is not paperwork in this field. It is the deliverable.
Where to go next¶
- If the vocabulary layer is new to you, go to concepts and domains. It is the highest-leverage chapter on the site.
- If you want to see the models side by side first, go to two shapes, one reality.
- If two people on your team keep talking past each other, go to words that mean two things.