Skip to content

Synthmas — clinical events

visit_occurrence, visit_detail, condition_occurrence, and procedure_occurrence all live in the OMOP CDM as records of something that happened to a patient at a point in time. They share a small handful of patterns:

Plus one pattern that's specific to visit_occurrence: Selector used as a type-based dispatcher.

The universal *_type_concept_id

Every clinical-event table has a _type_concept_id field that records how the row got there (EHR, claim, registry, derived…). For Synthmas, every event came from EHR-equivalent data, so they all get the same hard-coded value:

logic: SetValue(32810)         # "EHR" — visit, visit_detail, procedure, observation, …
logic: SetValue(32867)         # "EHR Chief complaint" — condition_occurrence
logic: SetValue(32817)         # "EHR prescription" — drug_exposure

The exact value comes from Athena — pick the concept id that best describes the provenance of your source data.

Cross-table foreign keys

Every clinical-event table has a person_id pointing back to person. The pattern is always the same — look up the source UUID in the already-loaded person table and demand a hit:

logic: GetCDMTableId(@person)
logic: AssertNot(NULL)

GetCDMTableId(@person) defaults to looking up person_source_value and returning person_id. The AssertNot(NULL) makes the missing-person case a logged failure rather than a silent NULL foreign key.

visit_occurrence_id and visit_detail_id follow the same pattern, but with a NULL default rather than a hard fail (a procedure may be unattached to a visit):

logic: GetCDMTableId(@visit_occurrence, NULL)

For provider_id on a child table, an explicit key/value form points the lookup at the right column:

logic: GetCDMTableId(@visit_occurrence, :visit_occurrence_source_value, :provider_id)

visit_occurrence — type-based dispatch with Selector

Synthmas's encounters.csv carries an encounterclass column that is sometimes an integer concept id and sometimes a class string (ambulatory, emergency, …). Two CDM fields draw from it differently:

visit_concept_id:
  logic: Selector(:encounterclass, "=", 1, :code, :encounterclass)

visit_source_value:
  logic: Selector(:encounterclass, "=", 0, :code, :encounterclass)
  logic: AsString()

Read the Selector calls as: "if :encounterclass equals 1, return :code; otherwise return :encounterclass". So the engine picks between the source code and encounterclass columns depending on which side of the source data this row came from. The AsString() after the second Selector flattens the chosen value to a string for the source-value field.

Vocabulary-coded concepts (SNOMED, RxNorm, LOINC)

condition_occurrence, procedure_occurrence, and the other event-y tables all carry SNOMED-coded code columns in the source. The CDM wants both a standard concept id and a source concept id — the engine has rules for each:

condition_concept_id:
  logic: AsString()
  logic: VocabMap("SNOMED")

condition_source_concept_id:
  logic: AsString()
  logic: VocabSourceId("SNOMED")

VocabMap returns the standard concept id mapped from a vocabulary code (cross-walked through OMOP's mapping tables). VocabSourceId returns the OMOP concept id of the source code itself, without crosswalk.

For codes that aren't in the standard vocabulary — Synthea occasionally emits SNOMED codes that have been retired or replaced — a small inline Map ahead of VocabMap re-routes them:

logic: AsString()
logic: Map([
logic:   ["425525006", "782555009"],
logic:   ["300916003", "419412007"]
logic: ])
logic: VocabMap("SNOMED")

Map only fires for keys it recognises; everything else passes through unchanged to VocabMap. See Useful patterns → pre-map then vocabulary for variations on this theme.

Recap

The four event tables share:

  • AutoGenId for the primary key.
  • SetValue(<concept_id>) for *_type_concept_id.
  • GetCDMTableId(@person) + AssertNot(NULL) for the mandatory FK.
  • GetCDMTableId(@visit_occurrence, NULL) for optional FKs.
  • AsString() + VocabMap("SNOMED") + VocabSourceId("SNOMED") for vocabulary lookups, with an optional pre-Map for out-of-vocabulary codes.

visit_occurrence adds the Selector-as-dispatcher pattern, which becomes a recurring trick once you start handling source data with multi-format columns.

Next: Values and dates — observations, measurements, and the date-arithmetic patterns for drug and device exposures.