Medications to DRUG_EXPOSURE¶
Four FHIR resources, one OMOP table. The table is the easy part. The interesting part is that the four resources represent four different strengths of evidence, and if the transformation flattens that distinction, drug safety and utilization research loses most of what it needs.
The four resources¶
| Resource | What it asserts | Evidence strength |
|---|---|---|
MedicationRequest |
A prescriber ordered this | Intent. The person may never have filled it |
MedicationDispense |
A pharmacy dispensed this | The medication left the pharmacy. The person may not have taken it |
MedicationAdministration |
A dose was given | Strongest. Typically inpatient, with a specific time and route |
MedicationStatement |
Someone reports this is being taken | Weakest and often uniquely valuable, because it captures over-the-counter and outside-system medications |
All four become rows in DRUG_EXPOSURE. The distinction survives in drug_type_concept_id, and that column is the entire point of this chapter.
The type concept¶
drug_type_concept_id records provenance. Type concepts live in the Type Concept vocabulary, and the ones relevant here distinguish prescriptions written, dispensing records, medication administration records, and patient-reported medication.
A researcher studying adherence needs prescriptions and dispensings and needs to tell them apart, because the gap between them is the finding. A researcher studying inpatient dosing needs administrations specifically. A researcher studying over-the-counter aspirin exposure needs patient-reported statements, which are the only place that exposure appears at all.
A pipeline that assigns one type concept to everything has produced a DRUG_EXPOSURE table where none of those studies is possible. The rows are all there. The distinction is gone, and it cannot be recovered without going back to source.
The single most common defect in this table
Uniform type concepts. It passes every structural quality check, produces correct row counts, and silently removes the ability to answer most drug questions well.
The drug concept¶
OMOP standardizes drugs on RxNorm and RxNorm Extension. US Core binds medication codes to RxNorm, so the alignment is usually good, which is a genuine convenience.
The complication is granularity. RxNorm has multiple term types, and the level you map to determines what analyses are possible.
| RxNorm level | Example | What it supports |
|---|---|---|
| Ingredient | metformin | Broad exposure questions |
| Clinical Drug Form | metformin oral tablet | Route-aware questions |
| Clinical Drug | metformin hydrochloride 500 MG oral tablet | Dose-aware questions |
| Branded Drug | Glucophage 500 MG oral tablet | Brand-specific questions |
Map to the most specific level the source supports. Ancestry through CONCEPT_ANCESTOR means a specific concept is still found by an ingredient-level concept set, so specificity costs nothing and generality loses information permanently.
R. Alvarez's metformin arrives as RxNorm 860975, a Clinical Drug. Map it there, not up to the ingredient.
NDC codes are common in dispensing data and are non-standard in OMOP. They map to RxNorm through Maps to, and the mapping is imperfect because NDC is a packaging identifier rather than a clinical one. Preserve the NDC in drug_source_value and drug_source_concept_id.
Local formulary codes need Usagi work like any other local vocabulary.
Dates, and the end date problem¶
DRUG_EXPOSURE requires both drug_exposure_start_date and drug_exposure_end_date. FHIR frequently gives you only a start.
| Resource | Start | End |
|---|---|---|
MedicationRequest |
authoredOn |
Derive from dispenseRequest duration or dosage instruction |
MedicationDispense |
whenHandedOver, else whenPrepared |
Start plus daysSupply |
MedicationAdministration |
effectiveDateTime or effectivePeriod.start |
effectivePeriod.end, else same as start for a single dose |
MedicationStatement |
effectivePeriod.start or effectiveDateTime |
effectivePeriod.end, often absent |
The derivation rules, in order of preference:
- An explicit end in the source
- Start plus
daysSupply, for dispensings - Start plus
expectedSupplyDuration, for requests - Start plus a duration computed from quantity and dosage instruction
- Start plus a documented default
- Start equals end, for single administrations
Rules five and six deserve care. A default duration applied to a chronic medication produces exposure periods that are uniformly wrong, and every persistence, adherence, and time-at-risk analysis inherits the error. If you must use a default, use one that is defensible for the drug class rather than a single global number, and report how many rows received it.
R. Alvarez's aspirin MedicationStatement has effectivePeriod.start of "2019" and no end. Year-only start, open-ended. Populating start as 2019-01-01 and end as the export date asserts a continuous six-year exposure that the source did not claim. Populating end as equal to start asserts a single day. Both are inventions, and the honest handling is to pick one, document it, and flag these rows so an analyst can exclude them.
Quantity, dose, and route¶
| OMOP CDM column | FHIR source |
|---|---|
quantity |
dispenseRequest.quantity, MedicationDispense.quantity |
days_supply |
daysSupply, expectedSupplyDuration |
refills |
dispenseRequest.numberOfRepeatsAllowed |
sig |
dosageInstruction.text |
route_concept_id |
dosage.route, dosageInstruction.route |
lot_number |
Rarely present |
dose_unit_source_value |
dosage.dose.unit |
drug_source_value, drug_source_concept_id |
The original coding |
stop_reason |
statusReason where meaningful |
sig holds the dosing instruction as free text. It is unglamorous and it is frequently the only place the actual regimen survives, since structured dosage elements are inconsistently populated. Keep it.
route_concept_id carries more weight than it looks. The same drug given intravenously and orally are different exposures with different pharmacokinetics, and the route is often the difference between an inpatient and an outpatient exposure pattern.
The Medication resource¶
Medications can be coded inline in medicationCodeableConcept or referenced through medicationReference pointing at a Medication resource, which may be external or contained inside the parent.
A parser that handles only medicationCodeableConcept will silently produce rows with no drug concept for every referenced medication. A parser that handles external references but not contained resources will do the same for the contained ones.
Handle all three paths and count how many of each you encountered. If the counts surprise you, the parser is wrong.
Medication resources can also carry ingredient detail and form, which occasionally lets you resolve a more specific RxNorm concept than the top-level code supports.
Immunization¶
Immunization is a separate resource and its content belongs in DRUG_EXPOSURE, because vaccines are drug exposures in the OMOP model.
Vaccine codes commonly arrive as CVX, which is non-standard in OMOP and maps to RxNorm. Preserve the CVX in the source columns. Use a type concept that distinguishes immunization records, since vaccination data has different completeness characteristics from other medication data.
Where this quietly breaks¶
Uniform type concepts. Stated three times in this chapter, deliberately.
Loading requests as though they were exposures without distinguishing them. A prescription written is not a drug taken. In some populations the gap is very large.
Mapping to ingredient level unnecessarily. Loses dose and form permanently for no benefit.
Silent default durations. Produces uniformly wrong exposure windows.
Dropping contained Medication resources. Produces zero-concept rows for a subset of medications, often a systematic subset such as compounded or non-formulary drugs.
Ignoring status. MedicationRequest.status includes cancelled, draft, and entered-in-error. MedicationAdministration.status includes not-done, which means the dose was specifically not given. Loading a not-done administration as an exposure asserts the opposite of what the source said.
Deduplicating across resource types. A prescription, its dispensing, and its administration describe one clinical intention through three stages. They are three rows with three type concepts, not one row. DRUG_ERA does the collapsing afterward, and it does it correctly because it works from the loaded rows.
Practice¶
R. Alvarez's three medications:
DRUG_EXPOSURE 1
drug_concept_id = 1503327 metformin hydrochloride 500 MG oral tablet
drug_exposure_start_date = 2024-09-12
drug_exposure_end_date = 2024-10-12 start + expectedSupplyDuration 30 d
drug_type_concept_id = prescription written
quantity = 60
days_supply = 30
drug_source_value = '860975'
DRUG_EXPOSURE 2
drug_concept_id = 1596977 insulin regular human 100 UNT/ML injectable
drug_exposure_start_date = 2024-09-10
drug_exposure_end_date = 2024-09-10 single administration
drug_type_concept_id = medication administered
route_concept_id = 4171047 intravenous
visit_occurrence_id = 1
visit_detail_id = 2 the ICU segment
drug_source_value = '311041'
DRUG_EXPOSURE 3
drug_concept_id = 19059056 aspirin 81 MG oral tablet
drug_exposure_start_date = 2019-01-01 year-only start, imputed, flagged
drug_exposure_end_date = 2019-01-01 no end in source, see policy
drug_type_concept_id = patient reported
drug_source_value = '243670'
Three type concepts, because three different things are being claimed. That is the chapter.