Clinical Data Mapping

Clinical Data Mapping

Mapping clinical data to FHIR is where most implementations encounter their hardest problems. The structural challenges — 1:1, 1:many, and many:1 resource patterns, ConceptMap construction, identifier management — are covered in Legacy-to-FHIR Mapping. This article is the clinical layer on top of that: the specific decisions you must make when mapping Observation, Condition, and Procedure data, and the failure modes that are unique to clinical content.

The source data you will receive has local codes, free text, structured-but-non-standard formats, and ambiguous fields. FHIR profiles require standard vocabulary and specific structural choices. The gap between source and target is semantic, not just structural. Getting the category wrong, using the wrong vocabulary, or omitting required clinical qualifiers produces resources that pass basic validation but fail in clinical use.


Observation mapping

The category decision

Observation.category is not just metadata — it determines which US Core profile the Observation must conform to. Assigning the wrong category means the resource will be validated against the wrong profile, producing validation failures or, worse, silently incorrect data.

Category codeUS Core profileRequired code system for codeExample observations
laboratoryUS Core Laboratory Result ObservationLOINCSerum sodium, CBC, HbA1c, blood culture
vital-signsUS Core Vital Signs ObservationLOINC (specific vital signs value set)Blood pressure, heart rate, body weight, SpO2
imaging(base Observation; check your IG)LOINC or localRadiology findings narrative
procedure(base Observation; check your IG)LOINC or SNOMEDIntraoperative finding
surveyUS Core Survey Observation (if present)LOINC (instrument codes)PHQ-9 score, GAD-7 item
social-historyUS Core Social History ObservationLOINCSmoking status, housing stability
exam(base Observation; check your IG)SNOMED or LOINCPhysical exam finding

The category code system is http://terminology.hl7.org/CodeSystem/observation-category. Do not define custom categories — use the standard codes. If your observation fits multiple categories (e.g., a laboratory result that is also a vital sign), the primary category determines the profile; include additional categories as secondary codings in the array.

Code selection

Use LOINC for laboratory results and vital signs. This is not optional for US Core compliance — US Core Vital Signs profiles bind to specific LOINC codes for each vital sign type.

Use SNOMED CT for clinical findings when LOINC does not have an appropriate code (e.g., physical exam findings, intraoperative observations, clinical assessment conclusions). If a LOINC code exists, prefer it over SNOMED for Observation.code.

A common confusion: the LOINC code on an Observation represents what was measured, not what the result means. The result meaning is in value[x]. An Observation for serum sodium uses LOINC 2951-2 for the code (serum sodium concentration) and a valueQuantity of 138 mmol/L for the result. The LOINC code is not the “sodium is normal” finding — it is the observation method.

Also critical: the result code and the order code are different LOINC codes. The order code represents what the clinician requested (e.g., 24320-4 Basic Metabolic Panel). The result code represents what was measured (e.g., 2951-2 Serum Sodium). Map the source’s test result code to LOINC, not the order identifier.

value[x] type selection

The choice of value[x] type is determined by the nature of the result:

Result typeFHIR elementExample
Numeric measurementvalueQuantitySodium 138 mmol/L — must include UCUM unit code
Coded resultvalueCodeableConceptBlood type A+; MRSA positive; organism identification
RatiovalueRatioINR 2.3 (prothrombin time ratio)
Ordinal / semi-quantitativevalueCodeableConceptTrace, 1+, 2+, 3+ (urine protein dipstick)
Text only (last resort)valueStringPathology narrative with no discrete code available
RangevalueRangeReference interval when reporting a range rather than a point value

The most critical rule: when using valueQuantity, always include the system (http://unitsofmeasure.org) and code (the UCUM unit code). A valueQuantity without a unit code is ambiguous and will cause downstream processing failures even if it passes basic FHIR validation. Validators do not always enforce UCUM unit presence; downstream systems will choke on it regardless.

Local-to-LOINC mapping

A lab system has its own test catalog with local codes. Mapping this catalog to LOINC is a real project that requires domain expertise.

The mapping process:

  1. Export the complete local test catalog (code, name, specimen type, units)
  2. Search LOINC by component name and specimen type
  3. For each candidate LOINC code, verify all six axes match the local test definition
  4. Assign mapping relationship: exact, narrower-than, or unmappable
  5. Document unmappable codes and handle with dataAbsentReason or a local extension
  6. Validate the mapping with a clinical laboratory expert
  7. Establish a process for mapping new codes as the local catalog changes

The LOINC Mapping Tool (available at loinc.org) assists with candidate search. The Regenstrief Institute also provides a paid LOINC mapping service. Do not skip clinical review — mapping errors produce incorrect result interpretation in the receiving system.

When no LOINC match exists for a local test, include the local code alongside a text-only entry in the coding array and document the gap:

"code": {
  "coding": [
    {
      "system": "http://example-lab.org/local-tests",
      "code": "LOCAL-99902",
      "display": "Experimental Biomarker Panel"
    }
  ],
  "text": "Experimental Biomarker Panel (no LOINC mapping available)"
}

Component vs hasMember

When a source result is a panel (multiple related results reported together), FHIR offers two structural options:

component: sub-results embedded within a single parent Observation. Use this when the components do not have independent clinical meaning apart from the parent — for example, systolic and diastolic blood pressure as components of a Blood Pressure observation.

hasMember: the parent Observation references separate child Observations. Use this when components are individually meaningful and may be queried, trended, or referenced independently. A CBC panel uses hasMember to reference individual Observations for WBC, RBC, Hgb, Hct, MCV, etc.

This is a correctness question, not a preference. If a downstream system queries GET /Observation?code=2951-2 for serum sodium and you modelled sodium as a component of a BMP Observation instead of as a standalone hasMember Observation, the query returns nothing. The sodium result is invisible to any system using standard FHIR search.

Rule of thumb: if the component can be queried, referenced, or trended independently, use hasMember with separate Observation resources.


Condition mapping

ICD-10-CM vs SNOMED for condition coding

ICD-10-CM and SNOMED CT represent conditions for different purposes. Both have a place in FHIR Condition resources; the decision is which to use as the primary code and when to include both.

Use casePreferred code systemReason
Encounter diagnosis (billing)ICD-10-CMRequired by payers and for claim submission
Clinical problem listSNOMED CTClinical precision; supports subsumption queries; preferred for CDS
Condition in care planSNOMED CTClinical system of record
Interoperability with payer systemsICD-10-CMPayers understand ICD-10, not SNOMED
Quality measure reportingICD-10-CM or SNOMED depending on the measureCheck the measure’s value set

When both are needed, include both codings in Condition.code. FHIR allows multiple codings in a CodeableConcept. The receiving system uses whichever coding it understands:

{
  "resourceType": "Condition",
  "id": "cond-t2dm-001",
  "clinicalStatus": {
    "coding": [
      {
        "system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
        "code": "active"
      }
    ]
  },
  "verificationStatus": {
    "coding": [
      {
        "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status",
        "code": "confirmed"
      }
    ]
  },
  "category": [
    {
      "coding": [
        {
          "system": "http://terminology.hl7.org/CodeSystem/condition-category",
          "code": "problem-list-item",
          "display": "Problem List Item"
        }
      ]
    }
  ],
  "code": {
    "coding": [
      {
        "system": "http://snomed.info/sct",
        "code": "44054006",
        "display": "Type 2 diabetes mellitus"
      },
      {
        "system": "http://hl7.org/fhir/sid/icd-10-cm",
        "code": "E11.9",
        "display": "Type 2 diabetes mellitus without complications"
      }
    ],
    "text": "Type 2 diabetes mellitus"
  },
  "subject": {
    "reference": "Patient/pat-001"
  },
  "onsetDateTime": "2018-04-01",
  "recordedDate": "2023-10-15"
}

The category decision

Condition.category is not cosmetic. The two primary US Core values drive different workflows:

Category codeUS Core profileClinical context
problem-list-itemUS Core Condition Problems and Health ConcernsActive condition on the patient’s ongoing problem list
encounter-diagnosisUS Core Condition Encounter DiagnosisDiagnosis documented for a specific encounter; drives billing
health-concernUS Core Condition Problems and Health ConcernsPatient concern or social determinant

A condition cannot be simultaneously a problem-list-item and an encounter-diagnosis in the same Condition resource — they represent different clinical assertions. The diabetes that has been on the patient’s problem list for five years is a problem-list-item. The “Type 2 diabetes mellitus, uncontrolled” diagnosis that appears on the encounter claim for today’s visit is an encounter-diagnosis. These may be two separate Condition resources linked by the same clinical concept.

Mapping all source conditions to encounter-diagnosis is the most common category error in clinical implementations. Receiving care coordination and care plan systems expect problem-list-item; they may not surface encounter-diagnosis conditions in clinical workflows.

Clinical status transitions

Condition.clinicalStatus must be maintained across the condition lifecycle: activeinactiveresolved (or remission for chronic conditions that are controlled but not cured).

These transitions are business process decisions. Define them explicitly:

  • Who is authorised to mark a condition as resolved? Only the treating provider? Any clinician?
  • At what event does a condition transition? Hospital discharge? Follow-up visit with confirmed resolution?
  • Does a problem resolve immediately, or does it transition through inactive first?

These rules must be embedded in your transformation logic or surfaced for clinician confirmation in your UI. Setting all conditions to active and never updating them is a data quality failure — a problem list full of conditions that resolved years ago is not a usable problem list.

onset/abatement

onset[x] and abatement[x] accept multiple types: DateTime, Age, Period, Range, String.

Legacy data commonly carries onset as free text: “3 years ago,” “childhood,” “since birth,” “unknown.” Do not truncate these — use onsetString to preserve the source value. Do not guess a date when the source is imprecise — an incorrect onsetDateTime is worse than an accurate onsetString.

For abatement (resolution), use abatementDateTime when you have an exact date, abatementString for free text, and omit the element entirely when the condition is still active. Setting abatementDateTime on an active condition is a logic error that receiving systems will misinterpret as resolved.


Procedure mapping

CPT vs SNOMED for procedure coding

The same dual-coding principle applies to procedures:

Use caseCode systemSystem URI
Outpatient billingCPThttp://www.ama-assn.org/go/cpt
Inpatient billingICD-10-PCShttp://www.cms.gov/Medicare/Coding/ICD10
Clinical system of record, CDSSNOMED CThttp://snomed.info/sct
Cross-system interoperabilitySNOMED CT (prefer) or CPT depending on receiving system

CPT codes are licensed by the AMA. Including CPT codes in a FHIR server’s responses requires a license. If your implementation distributes CPT codes via an API, verify your license covers redistribution. This is a legal question, not a technical one.

Include both CPT and SNOMED codings in Procedure.code when the procedure has a billing context (which most do). If the receiving system is clinical only and does not process billing, SNOMED alone is sufficient.

performed[x]

Procedure.performed[x] records when the procedure occurred. This is one of the most commonly missing or incorrect fields in clinical data mapping. Common failures:

  • Omitting performed entirely because the source system stores only an encounter date
  • Using the encounter date when the procedure actually occurred on a different date
  • Using performedDateTime for a procedure with a meaningful duration (use performedPeriod for these)

If the procedure date is not explicitly recorded in the source and the only date available is the encounter date, use the encounter date as a fallback but flag it as an approximation in your interface documentation. Do not omit performed — it is required by US Core.

The Procedure vs Observation ambiguity

Some clinical activities produce both a Procedure record and one or more Observation records. A venipuncture (blood draw) generates a Procedure (Procedure.code = SNOMED 28520004 | Venipuncture |). The resulting laboratory test generates one or more Observations. Both records should be created when both are present in the source.

The Procedure and Observation can be linked:

  • Observation.basedOn references the ServiceRequest that triggered both
  • Observation.partOf can reference the Procedure that produced the specimen

Do not collapse a Procedure and its resulting Observations into a single resource. They represent different clinical events and are queried separately.


Anti-patterns specific to clinical data

The following anti-patterns appear regularly in clinical FHIR implementations. Each is followed by the correct approach.

1. Coding everything as SNOMED when the profile requires LOINC

US Core Vital Signs profiles bind Observation.code to a LOINC value set. Using SNOMED for blood pressure, heart rate, or body weight violates the required binding and will fail validation against US Core. Check the profile’s code binding before choosing a code system.

Correct approach: use LOINC for laboratory results and vital signs as required by US Core profiles. Use SNOMED for clinical findings where LOINC does not have a code.

2. Mapping all source conditions to encounter-diagnosis

When a source EHR has a problem list, those problems are problem-list-item. Only conditions documented specifically as diagnoses for a particular encounter are encounter-diagnosis. Mapping everything to encounter-diagnosis hides the problem list from care coordination systems.

Correct approach: inspect the source data for the condition’s context — is it on an ongoing problem list or is it a diagnosis for a specific encounter? Map accordingly.

3. Omitting verificationStatus on Condition

Condition.verificationStatus is required by US Core. The distinction between confirmed, provisional, differential, unconfirmed, refuted, and entered-in-error is clinically meaningful. An unconfirmed suspected diagnosis is very different from a confirmed diagnosis. Omitting this field is not a minor oversight — it prevents receivers from distinguishing suspected from confirmed conditions.

Correct approach: always populate verificationStatus. Map the source’s confirmation status field. Default to unconfirmed if no confirmation status exists in the source and the condition has not been explicitly confirmed.

4. Missing UCUM unit on valueQuantity

A valueQuantity without a unit code (UCUM) is ambiguous. 138 what? Milliequivalents per litre? Millimoles per litre? Milligrams per decilitre? Downstream systems cannot aggregate, trend, or alert on results with missing units. Some validators do not enforce UCUM presence; clinical systems will reject or misflag the result.

Correct approach: every valueQuantity on a laboratory or vital sign Observation must include system = http://unitsofmeasure.org and code = the appropriate UCUM unit code. Map units from the source system’s unit string to UCUM as part of your mapping specification.

5. Free text in note when coded data is available

Using Observation.note or Condition.note for data that has a standard code is a semantic downgrade. A result of “positive” for a blood culture should be valueCodeableConcept with a SNOMED code for the organism — not a note field with the string “positive for MRSA.”

Correct approach: code data that has standard codes. Use note only for genuinely unstructured clinical commentary that has no coded equivalent.

6. Using performedDateTime for a procedure with meaningful duration

A one-second performedDateTime for a six-hour surgery is semantically wrong. The start and end times of a procedure are clinically meaningful for anaesthesia billing, surgical complication monitoring, and resource accounting.

Correct approach: use performedPeriod with start and end for any procedure with a meaningful duration. Use performedDateTime only for point-in-time procedures.


QA checklist

Before releasing a clinical data mapping to production, verify:

CheckObservationConditionProcedure
Coding system matches profile bindingLOINC for lab/vital-signs; SNOMED for clinical findingsSNOMED for clinical; ICD-10 for billingCPT for billing; SNOMED for clinical
Category code matches resource profilelaboratory, vital-signs, etc. correctly assignedproblem-list-item vs encounter-diagnosis correctly assigned(single category; verify applicable profile)
Status field populatedstatus requiredclinicalStatus and verificationStatus requiredstatus required
Date fields presenteffectiveDateTime or effectivePeriodonsetDateTime or onsetString; recordedDateperformedDateTime or performedPeriod
Units on quantitiesUCUM system and code on every valueQuantityN/AN/A
Profile conformance validatedValidated against applicable US Core profileValidated against US Core Condition profileValidated against US Core Procedure profile
Unmappable source codes documentedDocumented in interface spec; handled with local code + textDocumentedDocumented
panel structure correcthasMember vs component decision made explicitlyN/AObservation linkage via partOf when applicable
Section: interop Content Type: pattern Audience: technical
Interoperability Level:
Semantic
Published: 29/07/2024 Modified: 16/10/2025 14 min read
Keywords: FHIR Observation FHIR Condition FHIR Procedure clinical mapping LOINC SNOMED CT ICD-10 CPT US Core clinical coding
Sources: