Skip to content

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