Skip to content

Condition to CONDITION_OCCURRENCE

Usually, but not always. This chapter is largely about the exceptions, because the straightforward cases take one paragraph and the exceptions take the rest.


The straightforward case

A FHIR Condition with a diagnostic code that maps to a standard concept in the Condition domain becomes one row in CONDITION_OCCURRENCE.

R. Alvarez's type 2 diabetes:

condition_occurrence_id     = 1
person_id                   = 1
condition_concept_id        = 201826        SNOMED, Type 2 diabetes mellitus
condition_start_date        = 2011-03-01    from onset, see precision below
condition_start_datetime    = NULL
condition_end_date          = NULL          still active
condition_type_concept_id   = 32840         EHR problem list entry
condition_status_concept_id = 32893         from clinicalStatus, active
visit_occurrence_id         = NULL          onset predates this admission
condition_source_value      = 'E11.9'
condition_source_concept_id = 35208414      ICD-10-CM concept

Everything after this paragraph is about the ways real data departs from that.


Status fields carry meaning

Condition has two status elements and both change what the row means.

clinicalStatus: active, recurrence, relapse, inactive, remission, resolved.

verificationStatus: unconfirmed, provisional, differential, confirmed, refuted, entered-in-error.

Two of those values mean the person does not have the condition.

refuted means it was considered and ruled out. entered-in-error means the record should not exist. Loading either as a condition occurrence asserts something false about a person, and it will not produce an error anywhere downstream. It will produce a cohort with people in it who were specifically determined not to have the condition, which is worse than a random error because it is systematically concentrated in exactly the population a differential diagnosis was performed on.

A defensible policy:

Status Handling
confirmed, or absent Load normally
unconfirmed, provisional, differential Load, with condition_status_concept_id reflecting the uncertainty, or exclude by policy. Choose and document
refuted Do not load as a condition. Consider an OBSERVATION row recording that it was ruled out, if that is useful to your research
entered-in-error Exclude

clinicalStatus maps to condition_status_concept_id where a corresponding concept exists, and resolved or inactive can inform condition_end_date when the source provides an abatement.

Status filtering has to be explicit

A pipeline that loads everything is making a decision, just not a deliberate one. Write the status policy into the ETL specification with the counts affected.


Category and the type concept

Condition.category distinguishes problem-list-item from encounter-diagnosis. This is the primary input to condition_type_concept_id, and the distinction carries more weight than it looks.

A problem list entry is a maintained clinical assertion that persists. An encounter diagnosis is a coded reason attached to a specific visit, often generated for billing, and its accuracy characteristics are quite different. Researchers routinely restrict to one or the other, and they cannot if your pipeline typed them identically.

If the source is a claim rather than an EHR, the type concept should say so, and claim-derived conditions carry additional distinctions such as principal versus secondary diagnosis position that should be preserved where available.


Temporal precision

Condition offers onsetDateTime, onsetAge, onsetPeriod, onsetRange, onsetString, and the corresponding abatement variants.

CONDITION_OCCURRENCE requires condition_start_date. A date. Not a range, not an age, not a string.

Handling:

FHIR form Approach
onsetDateTime full precision Direct
onsetDateTime year or year-month only Populate the date column with a defined convention and leave condition_start_datetime null so the imprecision is detectable
onsetAge Compute from birth date. Note that this compounds with birth date imprecision
onsetPeriod Use the start; consider recording the width somewhere
onsetString Free text like "childhood". Usually unusable for the date column
Absent Fall back to recordedDate, then to the encounter date, then to the record's own creation. Each fallback is weaker; record which one was used

The fallback chain deserves attention. A condition with no onset date, dated instead to when it was recorded, will appear in incidence analyses as a new diagnosis on that date. For a chronic condition entered onto a problem list years after diagnosis, that is materially wrong. It is also unavoidable when the source does not carry onset, which makes it exactly the kind of limitation that belongs in your documentation rather than in a footnote nobody reads.

R. Alvarez's "2011-03-00" is a malformed partial date. Real exports contain these. A pipeline needs a defined behavior: reject the record, coerce to 2011-03, or coerce to a full date. Silent coercion is the default in many JSON parsers and the worst of the three.


When Condition does not become a condition

The domain routing rule applies here in full. Several categories of content routinely arrive in Condition resources and belong elsewhere.

Family history. R. Alvarez's cond-fh-cad, SNOMED 266897007, family history of coronary arteriosclerosis. Domain is Observation. Goes to OBSERVATION.

This happens routinely. Source systems put family history on the problem list because that is where their problem list is, and a pipeline that routes by resource type will load it as a condition. The person then appears in every cardiovascular cohort. In a study of statin effectiveness, they are a case who never had the disease.

Personal history of a resolved condition. Codes like "history of myocardial infarction" carry the Observation domain in some cases and the Condition domain in others, depending on the specific code. Check each one rather than assuming.

Absence assertions. "No known allergies", "no history of smoking". These are Observation domain and they assert absence. Loading them as conditions inverts the meaning completely.

Risk and susceptibility codes. "At risk of falls", "genetic susceptibility to breast cancer". Observation domain. A susceptibility is not a diagnosis, and conflating them in a cancer study would be a serious error.

Findings that route to Measurement. Some SNOMED clinical findings carry the Measurement domain.

Social and administrative content. Housing instability, transportation barriers, and similar social determinant codes are Observation domain.

The practical implication: run every condition code through the routing procedure and report the domain distribution. If a hundred percent of your Condition resources landed in CONDITION_OCCURRENCE, you have almost certainly routed on resource type rather than concept domain.


Multiple codings

A Condition.code can carry several codings. R. Alvarez's carries only ICD-10-CM, but a richer source might carry ICD-10-CM and SNOMED together.

Apply the coding-selection policy from source and standard consistently. Preferring an already-standard coding removes a translation step. Preferring the source system's primary coding preserves a truer picture of local practice. Pick one, apply it everywhere, and record which.


One source, multiple rows

Some source codes map to more than one standard concept, because ICD-10-CM sometimes bundles what SNOMED separates. The convention is to write one CONDITION_OCCURRENCE row per mapping target.

This means your row count will exceed your source resource count, legitimately. A pipeline that takes the first mapping and discards the rest is dropping clinical content silently. A quality check comparing input resources to output rows should account for this rather than flagging it.


Where this quietly breaks

Loading refuted and entered-in-error conditions. The highest-severity issue in this chapter.

Routing on resource type. The second highest, and the one that puts family history into cohorts.

A single type concept for everything. Destroys the problem-list versus encounter-diagnosis distinction that many studies depend on.

Silent coercion of malformed dates. Produces plausible wrong dates rather than visible errors.

Ignoring abatementDateTime. It populates condition_end_date, and without it every condition looks permanent, which distorts prevalence and any analysis of resolution.

Deduplicating too aggressively. The same condition legitimately appears many times: on the problem list, as an encounter diagnosis at three visits, and on a claim. Those are separate pieces of evidence with separate type concepts. Collapsing them to one row loses the recurrence pattern. CONDITION_ERA is the derived table that does the collapsing, and it does it after loading.


Next