Immunization
→
drug_exposure
documented
primary
One FHIR Immunization maps to one drug_exposure row. Immunizations are point-in-time events: start_date = end_date, no days_supply or refills. Records are distinguished from other drug exposures by drug_concept_id belonging to the CVX vocabulary. Only completed immunizations are mapped; entered-in-error and not-done are skipped.
Conversion profile
omop-immunization-drug-exposure
A FHIR instance converts to drug_exposure iff it validates against this profile.
| Path | Card | Type | Binding / Fixed | Comment |
|---|---|---|---|---|
| Immunization.status | 1..*MS | fixed: completed | Only completed immunizations are converted. entered-in-error and not-done are skipped. | |
| Immunization.vaccineCode | 1..*MS | omop-drug-codesrequired | ||
| Immunization.patient | 1..1 | Reference | Required for drug_exposure.person_id. | |
| Immunization.occurrence[x] | 1..1MS | Required for drug_exposure.drug_exposure_start_date / _end_date. Immunizations are point-in-time events: start_date = end_date. |
ViewDefinition (Stage 1 flattener)
omop-immunization-drug-exposure
19 columns · resource Immunization
| column name | FHIRPath | type |
|---|---|---|
| id | Immunization.id | id |
| vaccine_cvx | Immunization.vaccineCode.coding.where(system='http://hl7.org/fhir/sid/cvx').first().code | code |
| vaccine_ndc | Immunization.vaccineCode.coding.where(system='http://hl7.org/fhir/sid/ndc').first().code | code |
| vaccine_rxnorm | Immunization.vaccineCode.coding.where(system='http://www.nlm.nih.gov/research/umls/rxnorm').first().code | code |
| vaccine_text | Immunization.vaccineCode.text | string |
| subject_id | Immunization.patient | Reference(Patient) |
| drug_exposure_start_date | Immunization.occurrenceDateTime | dateTime |
| drug_exposure_start_datetime | Immunization.occurrenceDateTime | dateTime |
| drug_exposure_end_date | Immunization.occurrenceDateTime | dateTime |
| drug_exposure_end_datetime | Immunization.occurrenceDateTime | dateTime |
| stop_reason | Immunization.statusReason.text | string |
| quantity | Immunization.doseQuantity.value | decimal |
| sig | Immunization.note[*].text | string |
| route | Immunization.route | CodeableConcept |
| route_text | Immunization.route.coding[0].code | code |
| lot_number | Immunization.lotNumber | string |
| performer_id | Immunization.performer[0].actor | Reference(Practitioner) |
| encounter_id | Immunization.encounter | Reference(Encounter) |
| dose_unit_text | Immunization.doseQuantity.unit | string |
Condition: status = completed (skip entered-in-error and not-done)
Fields (23)
-
drug_exposure_id←Immunization.idinteger · idPKrequiredSurrogate key. Hash/sequence/lookup of Immunization.id. HL7 IG FML comments this out as a TODO.3 sources ▾
-
TODO/commented placeholder — no concrete ID strategy
- fhir-omop-ig(fml) refs/refs/fhir-omop-ig/input/maps/ImmunizationMap.fml:12-12
-
Derived from FHIR resource ID (getReferenceElement().getIdPartAsLong())
-
Auto-increment sequence
-
-
person_id←Immunization.patientinteger · Reference(Patient)FK→PERSONrequiredResolve Patient reference to integer. Skip the entire record if the referenced Patient does not exist. -
drug_concept_id←Immunization.vaccineCodeinteger · CodeableConceptFK→CONCEPTrequiredmap:vaccine_codeMap vaccine code (CVX/SNOMED/ATC/RxNorm) to OMOP standard concept. When multiple vaccineCode.coding[] entries are present, pick the first recognized vocabulary. Default 0 when concept lookup unavailable.3 sources ▾
-
CVX vocabulary lookup via OMOP concept table; takes first coding that resolves
- omoponfhir(java) refs/refs/omoponfhir-v54-r4/omoponfhir-omopv5-r4-mapping/src/main/java/edu/gatech/chai/omoponfhir/omopv5/r4/mapping/OmopImmunization.java:525-542
- FhirToCdm(csharp) refs/refs/FhirToCdm/FhirToCdmMappings.cs:609-611 — CVX, SNOMED, RxNorm via LookupCode
-
ATC preferred over SNOMED; date-aware lookup; supports compound SNOMED codes (+ separator)
-
No vocabulary lookup specified; vaccineCode mapping is unspecified/draft
- fhir-omop-ig(fml) refs/refs/fhir-omop-ig/input/maps/ImmunizationMap.fml:13-17
-
-
drug_exposure_start_date←Immunization.occurrenceDateTimedate · dateTimerequiredtransform:date(Immunization.occurrenceDateTime)Vaccination date (date portion). Skip record if occurrenceString is used instead of occurrenceDateTime. -
drug_exposure_start_datetime←Immunization.occurrenceDateTimedatetime · dateTimeFull ISO datetime if available. -
drug_exposure_end_date←Immunization.occurrenceDateTimedate · dateTimerequiredtransform:date(Immunization.occurrenceDateTime)Same as start date -- immunizations are single point-in-time events. -
drug_exposure_end_datetime←Immunization.occurrenceDateTimedatetime · dateTimeSame as start datetime. -
verbatim_end_date← — datenull -- immunizations have no separate verbatim end date. -
drug_type_concept_id← constant integerFK→CONCEPTrequiredmap:drug_type=3281732817 = 'EHR' (recommended default). Implementations diverge: 38000179 (omoponfhir), 32817 (ETL-German, FhirToCdm). When primarySource = false, consider 44787730 (Patient Self-Reported).3 sources ▾
-
38000179 (Physician administered drug) — legacy concept; also conditionally uses 44787730 when primarySource=false
-
32817 (EHR) — OMOP-recommended for CDM v5.4
- ETL-German-FHIR-Core(java) refs/refs/ETL-German-FHIR-Core/src/main/java/org/miracum/etl/fhirtoomop/Constants.java:26-26 — CONCEPT_EHR = 32817
- FhirToCdm(csharp) refs/refs/FhirToCdm/FhirToCdmMappings.cs:389-389
-
32818 (EHR administration record) — used in reverse direction
- mends-on-fhir(whistle) refs/refs/mends-on-fhir/whistle-mappings/synthea/whistle-functions/Drug_Exposure.wstl:13-13
-
-
stop_reason←Immunization.statusReason.textvarchar(20) · stringOnly when status = not-done. omoponfhir writes statusReason.text or coded system:code. Most implementations skip not-done records entirely. -
refills← — integernull -- not applicable to immunizations. -
quantity←Immunization.doseQuantity.valuefloat · decimalDose amount (e.g., 0.5 for 0.5 mL). -
days_supply← — integernull -- single event, no supply duration. -
sig←Immunization.note[*].textvarchar(MAX) · stringomoponfhir concatenates note[].text into sig. Not standard practice -- sig is intended for prescriber instructions, not clinical notes. Most implementations leave null. -
route_concept_id←Immunization.routeinteger · CodeableConceptFK→CONCEPTmap:routeVaccine administration route mapped to OMOP concept. Common: IM->4302612, SC->4302357, PO->4132161, NASINHL->4262914.3 sources ▾
-
Iterates route.coding[] until a matching OMOP concept is found
-
Date-aware concept lookup via findOmopConcepts.getConcepts()
-
Hardcoded mapping lines 30-34; uses route.text as fallback if no coding
- fhir-omop-ig(fml) refs/refs/fhir-omop-ig/input/maps/ImmunizationMap.fml:30-35
-
-
route_source_value←Immunization.route.coding[0].codevarchar(50) · codeRaw route code. omoponfhir uses system:code:display format. HL7 IG FML uses route.text as fallback.3 sources ▾
-
system:code:display composite string
-
route.text used as fallback when coding absent
- fhir-omop-ig(fml) refs/refs/fhir-omop-ig/input/maps/ImmunizationMap.fml:31-31
-
conceptCode from resolved OMOP concept (coding.code)
-
-
lot_number←Immunization.lotNumbervarchar(50) · stringVaccine lot tracking. One of the few FHIR resources that maps to lot_number. Truncate if exceeds 50 characters. -
provider_id←Immunization.performer[0].actorinteger · Reference(Practitioner)FK→PROVIDERAdministering practitioner. Take the first performer[].actor. Optional -- null if unresolvable. HL7 IG FML comments this out; ETL-German does not map it.2 sources ▾
-
getPerformerFirstRep().getActor() — throws if referenced provider does not exist
-
Commented out — not mapped
- fhir-omop-ig(fml) refs/refs/fhir-omop-ig/input/maps/ImmunizationMap.fml:47-53
- ETL-German-FHIR-Core(java) refs/refs/ETL-German-FHIR-Core/src/main/java/org/miracum/etl/fhirtoomop/mapper/ImmunizationMapper.java:528-578 — setUpDrugExposure() does not set provider_id
-
-
visit_occurrence_id←Immunization.encounterinteger · Reference(Encounter)FK→VISIT_OCCURRENCEResolve Encounter reference. Optional -- null if Encounter does not exist.3 sources ▾
-
Throws exception if referenced Encounter does not exist (strict)
-
Null with debug warning if Encounter not found (fault-tolerant)
- ETL-German-FHIR-Core(java) refs/refs/ETL-German-FHIR-Core/src/main/java/org/miracum/etl/fhirtoomop/mapper/ImmunizationMapper.java:730-743
- FhirToCdm(csharp) refs/refs/FhirToCdm/FhirToCdmMappings.cs:396-399
-
Commented out — not mapped
- fhir-omop-ig(fml) refs/refs/fhir-omop-ig/input/maps/ImmunizationMap.fml:39-43
-
-
visit_detail_id← — integerFK→VISIT_DETAILnull -- no standard mapping from Immunization. -
drug_source_value←Immunization.vaccineCode.coding[best].codevarchar(50) · codeOriginal vaccine code string. omoponfhir uses system:code format. ETL-German uses just the code.2 sources ▾
-
system:code composite format (e.g., 'http://hl7.org/fhir/sid/cvx:207')
-
Bare vaccine code string (no system prefix)
-
-
drug_source_concept_id←Immunization.vaccineCodeinteger · CodeableConceptFK→CONCEPTNon-standard source concept ID from vocabulary lookup. ETL-German resolves via findOmopConcepts. Default 0. -
dose_unit_source_value←Immunization.doseQuantity.unit | Immunization.doseQuantity.codevarchar(50) · stringDose unit (e.g., 'mL', 'dose'). HL7 IG FML uses doseQuantity.code. ETL-German uses dose.getUnit().
Vocabularies
drug_type
| Source | Display | Concept ID | Concept Name |
|---|---|---|---|
| EHR | EHR (default) | 32817 | EHR |
| physician-administered | Physician administered drug | 38000179 | Physician administered drug (identified from EHR problem list) |
| ehr-administration | EHR administration record | 32818 | EHR administration record |
| self-reported | Patient Self-Reported (primarySource = false) | 44787730 | Patient Self-Reported |
route
| Source | Display | Concept ID | Concept Name |
|---|---|---|---|
| IM | Intramuscular | 4302612 | Intramuscular route |
| SC | Subcutaneous | 4302357 | Subcutaneous route |
| PO | Oral / Per Oral | 4132161 | Oral |
| NASINHL | Nasal Inhalation | 4262914 | Nasal route |
| IDINJ | Intradermal | 4156706 | Intradermal route |
vaccine_code
| Source | Display | Concept ID | Concept Name |
|---|---|---|---|
| CVX:08 | Hep B, adolescent or pediatric | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
| CVX:10 | IPV | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
| CVX:20 | DTaP | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
| CVX:33 | Pneumococcal polysaccharide PPV23 | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
| CVX:88 | Influenza, unspecified formulation | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
| CVX:115 | Tdap | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
| CVX:141 | Influenza, seasonal, injectable | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
| CVX:207 | COVID-19, mRNA, LNP-S, PF, 100 mcg/0.5mL (Moderna) | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
| CVX:208 | COVID-19, mRNA, LNP-S, PF, 30 mcg/0.3mL (Pfizer-BioNTech) | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
| CVX:212 | COVID-19, viral vector, non-replicating, Ad26 (Janssen) | - | Lookup in OMOP concept table where vocabulary_id = 'CVX' |
status_filter
| Source | Display | Concept ID | Concept Name |
|---|---|---|---|
| completed | Completed | - | Map -- vaccination was administered |
| entered-in-error | Entered in Error | - | Skip -- data quality issue |
| not-done | Not Done | - | Skip (most implementations). omoponfhir writes with stop_reason. |
Edge Cases
occurrenceString instead of occurrenceDateTime
Cannot map -- drug_exposure_start_date is NOT NULL. Skip the record and log a warning.
primarySource = false
Vaccine reported secondhand (patient self-report, school record). Consider using drug_type_concept_id = 44787730 (Patient Self-Reported) instead of the default.
Multiple performer entries
Take the first performer[].actor for provider_id. Only one provider can be stored per drug_exposure row.
Vaccine series tracking (protocolApplied)
Not mapped. protocolApplied.doseNumber, seriesDoses, targetDisease have no OMOP equivalent.
reaction present
Not mapped to drug_exposure. Could create a separate observation row for adverse reactions, but no implementation does this for Immunization.
Missing vaccineCode
Skip the record. All implementations require a vaccine code.
Multiple vaccineCode.coding[] entries
Iterate codings to find one in a recognized vocabulary. ETL-German prefers ATC over SNOMED. omoponfhir takes the first coding that resolves.
SNOMED compound codes (+ separator)
ETL-German splits compound SNOMED codes on '+' and processes each sub-code separately, potentially creating multiple drug_exposure rows.
lotNumber exceeds 50 characters
Truncate to varchar(50). Rare in practice but possible with extended lot identifiers.
site (body site) present
No standard mapping to drug_exposure. Could map to route_source_value but this conflates route and site. Best practice: discard.
manufacturer present
No standard mapping. Could store in drug_source_value alongside the code, but no implementation does this.
expirationDate present
No OMOP target. Lost in translation.
note[].text present
omoponfhir concatenates notes into sig field. Not standard practice -- sig is intended for prescriber instructions.
Domain routing: concept maps to Observation domain
ETL-German checks concept.domain_id; if 'Observation' instead of 'Drug', routes to observation table instead of drug_exposure. Uncommon for CVX codes but possible with certain SNOMED vaccine concepts.
status = entered-in-error
Skip entirely. Data quality issue -- should not enter OMOP.
status = not-done
Most implementations skip. omoponfhir writes the record with stop_reason from statusReason.
Reference Implementations
- fhir-omop-ig(fml) refs/refs/fhir-omop-ig/input/maps/ImmunizationMap.fml — 54 lines. Normative/draft. Maps vaccineCode, occurrence, doseQuantity, route, lotNumber. Comments out person_id, encounter, performer.
- omoponfhir(java) refs/refs/omoponfhir-v54-r4/omoponfhir-omopv5-r4-mapping/src/main/java/edu/gatech/chai/omoponfhir/omopv5/r4/mapping/OmopImmunization.java:1-641 — Bidirectional. Most complete field coverage: CVX vocabulary, lot number, route, dose, notes->sig, status/stop_reason, performer->provider_id. Type concept 38000179. Status: stale (2022).
- ETL-German-FHIR-Core(java) refs/refs/ETL-German-FHIR-Core/src/main/java/org/miracum/etl/fhirtoomop/mapper/ImmunizationMapper.java:1-771 — ATC/SNOMED vaccine codes. Domain routing (Drug vs Observation). Date-aware concept lookup. Compound SNOMED code splitting. No lot_number or performer. Type concept 32817.
- FhirToCdm(csharp) refs/refs/FhirToCdm/FhirToCdmMappings.cs — CreateDrugExposure() lines 376-404. Minimal: person_id, vaccine code, type=32817, encounter. No lot_number, route, dose, or performer.
- mends-on-fhir(whistle) refs/refs/mends-on-fhir/whistle-mappings/synthea/whistle-functions/Drug_Exposure.wstl — Reverse direction (OMOP->FHIR). Routes CVX drug_exposures to FHIR Immunization. Maps lot_number, route, dose, status from stop_reason.
- fhir-x-omop(python) refs/refs/fhir-x-omop/fhir_x_omop/to_fhir/immunization.py — 73 lines. Reverse direction (OMOP->FHIR). Hardcoded route code mapping. Maps lot_number, dose, provider. Early WIP.