Patient
— maps to 4 OMOP tables| OMOP Table | Status | Mapped | |
|---|---|---|---|
|
person
primary
Core demographics record. One FHIR Patient maps to exactly one OMOP person row. The person table is the central identity record — every OMOP event table carries a person_id FK.
|
implemented | 18 fields | detail → |
|
death
Mortality record. At most one death row per person. Only created when Patient.deceasedDateTime is present — deceasedBoolean=true without a date cannot produce a row because death_date is NOT NULL.
|
documented | 7 fields | detail → |
|
location
Address normalization. One Patient produces at most one location row (most recent home address). The location_id is then set as person.location_id FK.
|
documented | 12 fields | detail → |
|
observation_period
Observation period defines spans during which a person is observable. Not a direct field-level mapping from Patient — derived from event data availability. Most implementations either skip this table or emit a placeholder row at person-creation time.
|
documented | 5 fields | detail → |
Patient → OMOP Mapping
FHIR Patient is the anchor resource for every OMOP record: every event table (visit_occurrence, condition_occurrence, measurement, observation, drug_exposure, procedure_occurrence, death, observation_period) carries a person_id foreign key. Patient must be processed first so other mappers can resolve person_id from the FHIR resource ID.
Target OMOP Tables
| OMOP Table | Purpose | Required? |
|---|---|---|
person | Core demographics (one row per Patient) | Yes |
death | Mortality (one row max per person) | Conditional — only when Patient.deceasedDateTime present |
location | Address, FK from person.location_id | Optional — only when Patient.address present |
observation_period | Window during which the person is observable | Implementation-specific |
Mapping Strategy
The Patient → person mapping is the most stable across implementations: gender, birthDate, and US Core race/ethnicity have a near-universal consensus mapping. The hard problems are off-table:
-
Person ID generation. FHIR uses string identifiers (
urn:uuid:…, MRNs, slug IDs); OMOP requiresintegerperson_id. Strategies vary — autogenerated sequence (NACHC, ETL-German), hash of FHIR ID (FhirToCdm), explicit lookup table maintained per ETL run. There is no normative answer in the HL7 IG (the FML literally has//src.id as id -> tgt.person_id = cast(id, "integer");commented out). Keep the FHIR ID inperson_source_valuefor round-trip traceability. -
Vocabulary lookups. Three standard hardcoded maps are needed: gender (
male→8507,female→8532,other→8521,unknown→8551), OMB race categories (5 codes → 5 concepts), OMB ethnicity (2 codes → 2 concepts). Everything else (locale-specific gender extensions, GermanethnicGroup, marital status) requires a vocabulary database. -
Reference resolution.
Patient.generalPractitioner→person.provider_idandPatient.managingOrganization→person.care_site_idrequire the referencedPractitioner/Organizationto be processed first (or stub IDs reserved). MultiplegeneralPractitioner[]entries — pick first or last; OMOP only stores the most recent primary care provider. -
Deceased semantics.
Patient.deceasedDateTimeproduces adeathrow.Patient.deceasedBoolean = true(no date) cannot be represented becausedeath.death_dateis NOT NULL — most implementations drop the death record and warn. -
Address normalization. Picking which
Patient.address[]entry to write tolocationis implementation-defined. Consensus is the most-recenthomeaddress. OMOPlocationis not history-aware — only the last known address is kept (person.location_id). -
Tenant scoping / deduplication. When multiple FHIR sources contribute the same patient (HIE, multi-EHR), no implementation handles this — most produce duplicate person rows. Out of scope for the mapping; handle upstream.
Reference Implementations
- fhir-omop-ig (HL7) — Normative IG; FML at
refs/refs/fhir-omop-ig/input/maps/PersonMap.fml; logical modelrefs/refs/fhir-omop-ig/input/fsh/Person.fsh. Maps only gender + birthDate; minimal/draft. Status: active ballot. - omoponfhir-omopv5-r4-mapping (Georgia Tech, Java) — Bidirectional;
refs/refs/omoponfhir-omopv5-r4-mapping/src/main/java/edu/gatech/chai/omoponfhir/omopv5/r4/mapping/OmopPatient.java(1338 lines). Most complete field coverage; uses non-standardFPersonextension for name/SSN/contact. Status: stale (2022). - FhirToCdm (OHDSI, C#) —
refs/refs/FhirToCdm/FhirToCdmMappings.csCreatePersonAndLocations(). Authoritative OHDSI sample but no death, no birth_datetime. Status: low activity. - ETL-German-FHIR-Core (OHDSI, Java) —
refs/refs/ETL-German-FHIR-Core/src/main/java/org/miracum/etl/fhirtoomop/mapper/PatientMapper.java(~700 lines). Best example of incremental updates and deferred address/death viapost_process_map. German MII profile-specific. Status: maintained. - NACHC-fhir-to-omop (Java, DSTU3) —
refs/refs/NACHC-fhir-to-omop/src/main/java/org/nachc/tools/fhirtoomop/omop/person/factory/builder/person/OmopPersonBuilder.java; race/ethnicity DB-backed. Auto-creates fixed-window observation_period. Status: active. - fhir-to-omop-demo (jq) —
refs/refs/fhir-to-omop-demo/demo/translate/map/Patient.jq. Notable for using US Core Birthsex (notPatient.gender) as the gender source. Status: maintained. - fhir-x-omop (Python) —
refs/refs/fhir-x-omop/fhir_x_omop/to_fhir/patient.pyandto_omop/person.py. Bidirectional with lossless round-trip viaextrasidecar. Status: early WIP.
Status in This Project
Implemented: src/mapper/patient.ts. Maps gender, birthDate, race/ethnicity (US Core OMB), identifier, address → location, deceasedDateTime → death. Does not handle generalPractitioner → provider_id, managingOrganization → care_site_id, or observation_period derivation.