Skip to content

Reading a bundle out loud

This chapter introduces R. Alvarez, the synthetic person who appears in every remaining lesson. Their bundle is deliberately imperfect. Each imperfection is the teaching point for a specific later chapter, so the case gets less mysterious as you proceed.

Read the bundle first. Then read the walk-through. Then, if you want the material to stick, close the page and try to predict the OMOP rows yourself before checking.


The bundle

{
  "resourceType": "Bundle",
  "type": "collection",
  "entry": [
    {
      "resource": {
        "resourceType": "Patient",
        "id": "alvarez-r",
        "identifier": [{
          "system": "urn:oid:2.16.840.1.113883.19.5",
          "value": "MRN-4471902"
        }],
        "name": [{ "family": "Alvarez", "given": ["R"] }],
        "gender": "female",
        "birthDate": "1962",
        "address": [{ "postalCode": "19002", "state": "PA", "country": "US" }],
        "extension": [
          {
            "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race",
            "extension": [
              { "url": "ombCategory",
                "valueCoding": { "system": "urn:oid:2.16.840.1.113883.6.238",
                                 "code": "2106-3", "display": "White" } },
              { "url": "text", "valueString": "White" }
            ]
          },
          {
            "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity",
            "extension": [
              { "url": "ombCategory",
                "valueCoding": { "system": "urn:oid:2.16.840.1.113883.6.238",
                                 "code": "2135-2", "display": "Hispanic or Latino" } },
              { "url": "text", "valueString": "Hispanic or Latino" }
            ]
          }
        ]
      }
    },
    {
      "resource": {
        "resourceType": "Encounter",
        "id": "enc-adm-2024-0908",
        "status": "finished",
        "class": { "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
                   "code": "IMP", "display": "inpatient encounter" },
        "subject": { "reference": "Patient/alvarez-r" },
        "period": { "start": "2024-09-08T14:12:00-04:00",
                    "end":   "2024-09-12T11:30:00-04:00" }
      }
    },
    {
      "resource": {
        "resourceType": "Encounter",
        "id": "enc-ward-2024-0908",
        "status": "finished",
        "class": { "code": "IMP" },
        "partOf": { "reference": "Encounter/enc-adm-2024-0908" },
        "subject": { "reference": "Patient/alvarez-r" },
        "location": [{ "location": { "display": "7 West Medical" } }],
        "period": { "start": "2024-09-08T16:40:00-04:00",
                    "end":   "2024-09-10T08:15:00-04:00" }
      }
    },
    {
      "resource": {
        "resourceType": "Encounter",
        "id": "enc-icu-2024-0910",
        "status": "finished",
        "class": { "code": "IMP" },
        "partOf": { "reference": "Encounter/enc-adm-2024-0908" },
        "subject": { "reference": "Patient/alvarez-r" },
        "location": [{ "location": { "display": "Medical ICU" } }],
        "period": { "start": "2024-09-10T08:15:00-04:00",
                    "end":   "2024-09-12T11:30:00-04:00" }
      }
    },
    {
      "resource": {
        "resourceType": "Condition",
        "id": "cond-t2dm",
        "clinicalStatus": { "coding": [{ "code": "active" }] },
        "verificationStatus": { "coding": [{ "code": "confirmed" }] },
        "category": [{ "coding": [{ "code": "problem-list-item" }] }],
        "code": { "coding": [
          { "system": "http://hl7.org/fhir/sid/icd-10-cm", "code": "E11.9",
            "display": "Type 2 diabetes mellitus without complications" }
        ]},
        "subject": { "reference": "Patient/alvarez-r" },
        "onsetDateTime": "2011-03-00"
      }
    },
    {
      "resource": {
        "resourceType": "Condition",
        "id": "cond-fh-cad",
        "clinicalStatus": { "coding": [{ "code": "active" }] },
        "verificationStatus": { "coding": [{ "code": "confirmed" }] },
        "category": [{ "coding": [{ "code": "problem-list-item" }] }],
        "code": { "coding": [
          { "system": "http://snomed.info/sct", "code": "266897007",
            "display": "Family history of coronary arteriosclerosis" }
        ]},
        "subject": { "reference": "Patient/alvarez-r" }
      }
    },
    {
      "resource": {
        "resourceType": "Observation",
        "id": "obs-a1c",
        "status": "final",
        "category": [{ "coding": [{ "code": "laboratory" }] }],
        "code": { "coding": [
          { "system": "http://loinc.org", "code": "4548-4",
            "display": "Hemoglobin A1c/Hemoglobin.total in Blood" }
        ]},
        "subject": { "reference": "Patient/alvarez-r" },
        "encounter": { "reference": "Encounter/enc-adm-2024-0908" },
        "effectiveDateTime": "2024-09-09T06:15:00-04:00",
        "valueQuantity": { "value": 7.8, "unit": "%",
                           "system": "http://unitsofmeasure.org", "code": "%" },
        "referenceRange": [{ "low": { "value": 4.0 }, "high": { "value": 5.6 } }]
      }
    },
    {
      "resource": {
        "resourceType": "Observation",
        "id": "obs-smoking",
        "status": "final",
        "category": [{ "coding": [{ "code": "social-history" }] }],
        "code": { "coding": [
          { "system": "http://loinc.org", "code": "72166-2",
            "display": "Tobacco smoking status" }
        ]},
        "subject": { "reference": "Patient/alvarez-r" },
        "effectiveDateTime": "2024-09-08T15:02:00-04:00",
        "valueCodeableConcept": { "coding": [
          { "system": "http://snomed.info/sct", "code": "8517006",
            "display": "Former smoker" }
        ]}
      }
    },
    {
      "resource": {
        "resourceType": "Observation",
        "id": "obs-local-frailty",
        "status": "final",
        "code": { "coding": [
          { "system": "http://hospital.example.org/codes", "code": "FRLTY-7",
            "display": "Nursing frailty index, 7-point" }
        ]},
        "subject": { "reference": "Patient/alvarez-r" },
        "effectiveDateTime": "2024-09-08T17:20:00-04:00",
        "valueQuantity": { "value": 4, "unit": "{score}" }
      }
    },
    {
      "resource": {
        "resourceType": "MedicationRequest",
        "id": "medreq-metformin",
        "status": "active",
        "intent": "order",
        "medicationCodeableConcept": { "coding": [
          { "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
            "code": "860975", "display": "metformin hydrochloride 500 MG Oral Tablet" }
        ]},
        "subject": { "reference": "Patient/alvarez-r" },
        "authoredOn": "2024-09-12",
        "dispenseRequest": { "quantity": { "value": 60 },
                             "expectedSupplyDuration": { "value": 30, "unit": "d" } }
      }
    },
    {
      "resource": {
        "resourceType": "MedicationAdministration",
        "id": "medadmin-insulin",
        "status": "completed",
        "medicationCodeableConcept": { "coding": [
          { "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
            "code": "311041", "display": "insulin regular human 100 UNT/ML injectable solution" }
        ]},
        "subject": { "reference": "Patient/alvarez-r" },
        "context": { "reference": "Encounter/enc-icu-2024-0910" },
        "effectiveDateTime": "2024-09-10T22:05:00-04:00",
        "dosage": { "dose": { "value": 6, "unit": "U" }, "route": {
          "coding": [{ "system": "http://snomed.info/sct", "code": "47625008",
                       "display": "Intravenous route" }] } }
      }
    },
    {
      "resource": {
        "resourceType": "MedicationStatement",
        "id": "medstmt-otc",
        "status": "active",
        "medicationCodeableConcept": { "coding": [
          { "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
            "code": "243670", "display": "aspirin 81 MG Oral Tablet" }
        ]},
        "subject": { "reference": "Patient/alvarez-r" },
        "effectivePeriod": { "start": "2019" },
        "informationSource": { "display": "Patient reported" }
      }
    }
  ]
}

The walk-through

Patient/alvarez-r

Destination: one row in PERSON.

Four things to notice.

birthDate is "1962". FHIR dates support partial precision, and this one carries only a year. PERSON wants year_of_birth, month_of_birth, day_of_birth, and birth_datetime. You have the year. What do you do with the other three? Every choice you can make is an assumption, and the one most pipelines make silently is January first. See Patient to PERSON.

gender is "female". This is FHIR's administrative-gender, which the specification is explicit is an administrative concept rather than a clinical or biological one. PERSON.gender_concept_id expects a concept from the OMOP Gender domain. The mapping is mechanically easy and conceptually deserves a pause.

The race and ethnicity extensions carry OMB category codes. PERSON has separate race_concept_id and ethnicity_concept_id columns, and OMOP's ethnicity domain has essentially two values: Hispanic or Latino, and Not Hispanic or Latino. Here the split works. Frequently it does not, because source systems commonly collapse race and ethnicity into one field.

The address gives you a postal code and state, which populates LOCATION and links from PERSON.location_id. Note that PERSON.location_id holds one location, so a person who moved has a modeling decision waiting.

The three Encounters

Destination: this is the hard one.

There is an admission encounter spanning 8 September to 12 September, and two child encounters linked by partOf: a ward stay and an ICU stay that together tile the parent period.

The natural OMOP reading is one VISIT_OCCURRENCE for the admission and two VISIT_DETAIL rows for the ward and ICU segments. That reading is correct here because the source happened to model the hierarchy explicitly.

Now imagine the same stay from a system that emits one Encounter per clinical service consultation with no partOf at all, or one that emits a single Encounter for the whole admission with no internal structure. Same clinical reality, three completely different source shapes, and your pipeline needs a defensible rule. See Encounter to VISIT.

Also note enc-icu-2024-0910 ends at exactly the moment the parent ends. Discharge from the ICU and discharge from the hospital are the same event here. That is common and it means naive interval logic will produce zero-length gaps and off-by-one errors at boundaries.

Condition/cond-t2dm

Destination: one row in CONDITION_OCCURRENCE.

The code is ICD-10-CM E11.9. That is a source code, not a standard concept. OMOP standardizes conditions on SNOMED CT, so the transformation looks up the concept for E11.9 in the ICD10CM vocabulary, follows its Maps to relationship, and writes the resulting SNOMED concept into condition_concept_id. The original ICD-10-CM concept goes into condition_source_concept_id and the string E11.9 goes into condition_source_value.

onsetDateTime is "2011-03-00". That is not a valid FHIR date; a real bundle would carry "2011-03". It is here to make a point, because malformed partial dates do appear in production exports and your pipeline needs a behavior for them that is not silent coercion.

category is problem-list-item, which is the input to condition_type_concept_id. A problem list entry and a billing diagnosis are different kinds of evidence and researchers need to distinguish them.

Condition/cond-fh-cad

Destination: not CONDITION_OCCURRENCE.

The code is SNOMED 266897007, family history of coronary arteriosclerosis. The source system put it in a Condition resource because that is where its problem list lives. But the person does not have coronary arteriosclerosis; their relative does.

In OMOP, this concept's domain is Observation, so the row goes to the OBSERVATION table. The vocabulary knows this is a family history assertion and routes accordingly.

This is the single most important pattern in the transformation, and it is why "Condition goes to CONDITION_OCCURRENCE" is a rule that will hurt you. Loading this into CONDITION_OCCURRENCE would make R. Alvarez appear in every cohort for coronary artery disease. See domain routing.

Observation/obs-a1c

Destination: one row in MEASUREMENT.

LOINC 4548-4 has domain Measurement, there is a numeric value, and there is a reference range. Everything maps cleanly: value_as_number gets 7.8, unit_concept_id gets the concept for percent, range_low and range_high get 4.0 and 5.6, and measurement_type_concept_id records that this came from a laboratory result.

This is what an easy case looks like. Roughly half your volume will look like this, which is why the other half deserves most of your attention.

Observation/obs-smoking

Destination: one row in OBSERVATION, not MEASUREMENT.

LOINC 72166-2, tobacco smoking status, has domain Observation. The value is a coded concept rather than a number, so value_as_concept_id gets the standard concept for former smoker and value_as_number stays null.

Notice that both this and the A1c arrived as FHIR Observation resources with LOINC codes, and they went to different OMOP tables. The FHIR resource type told you nothing about the destination. The code told you everything.

Observation/obs-local-frailty

Destination: one row somewhere, with concept_id = 0.

The code is from http://hospital.example.org/codes, a local code system. There is no standard concept for it, because the OMOP vocabularies do not contain this hospital's internal nursing scale.

You have three real options and they have different consequences. Load it with concept_id = 0 and preserve the source value, which keeps the data present and analytically invisible. Build a local mapping in SOURCE_TO_CONCEPT_MAP to the closest standard concept, which makes it queryable and introduces an interpretation. Or leave it out, which is honest and loses information permanently.

There is no universally right answer. There is a right process, which is to decide deliberately, document the decision, and count how many rows it affects. See Athena and Usagi.

The three medication resources

Destination: three rows in DRUG_EXPOSURE, distinguished by type concept.

MedicationRequest for metformin is an order. It records intent, not administration. drug_type_concept_id should say so.

MedicationAdministration for insulin is a dose actually given, in the ICU, at a specific minute, by a specific route. This is the strongest evidence of exposure in the bundle and it should be typed differently from the order.

MedicationStatement for aspirin is a report, sourced from the person themselves, with an effective period starting in 2019 and no end. It is over-the-counter, was never prescribed in this system, and would be entirely invisible in a claims-based data set. It is also real information about a real exposure.

All three become DRUG_EXPOSURE rows. If your pipeline assigns them the same type concept, a researcher can no longer distinguish "was prescribed" from "actually received" from "says they take", and those distinctions carry most of the meaning in drug safety work. See medications to DRUG_EXPOSURE.

Note also the aspirin effectivePeriod.start of "2019", another year-only date, and the absence of an end. DRUG_EXPOSURE requires drug_exposure_start_date and drug_exposure_end_date. You will have to invent an end date, and how you invent it affects every persistence and adherence analysis.


What is not in this bundle

Worth noticing, because absence is data.

There is no Coverage, so PAYER_PLAN_PERIOD stays empty. There is no Procedure, though an inpatient stay of this kind usually generates several. There is no death information. There is nothing that tells you when R. Alvarez became a person this health system could observe, or when they stopped being one, which means OBSERVATION_PERIOD has to be derived from the events themselves. See the tables FHIR does not give you.


Practice

Before moving on, write down your answers to these. The workbook has space for it, and the Bundle Reader tool will check some of them.

  1. How many rows does this bundle produce in PERSON? In VISIT_OCCURRENCE? In VISIT_DETAIL?
  2. Which two resources of the same FHIR type end up in different OMOP tables, and what decides it?
  3. Which single row, if loaded to the wrong table, would put R. Alvarez in a cardiovascular disease cohort incorrectly?
  4. Name three fields in this bundle where temporal precision is lower than the OMOP CDM column expects.
  5. If your pipeline dropped every resource with an unmappable code, what would be lost, and would anyone notice?

Next