# HL7 v2 to FHIR terminology: OBX to Observation with LOINC and SNOMED

> Map a de-identified ORU/OBX fragment onto Observation.code and value[x] using HL7 v2-to-FHIR 1.0.0. LOINC binds observations; SNOMED binds problems and allergies as the IG maps them. Local codes fail first, not PID.

Author: Ala Ben Aicha

Canonical page: https://alabenaicha.me/insights/hl7v2-fhir-loinc-snomed-mapping

Updated: 2026-09-19

## Direct answer

Map OBX to Observation using the v2-to-FHIR IG 1.0.0: OBX-3 becomes Observation.code, OBX-5 becomes value\[x]. Bind LOINC on observations and SNOMED on problems and allergies. Local codes, not PID, are the usual failure.

## Pin the published maps, then the bindings

Pin the published [HL7 Version 2 to FHIR IG 1.0.0 (STU 1)](https://hl7.org/fhir/uv/v2mappings/) (generated 7 October 2025, package `hl7.fhir.uv.v2mappings#1.0.0` on FHIR R4). Prefer that package over the CI build. The hybrid engine pattern is the [v2-to-FHIR migration playbook](https://alabenaicha.me/insights/hl7v2-to-fhir-migration-strategy); the order/result choreography is the [radiology and lab workflow](https://alabenaicha.me/insights/radiology-lab-workflow-integration). **This page is the terminology contract inside that engine.**

The segment map to pin is [OBX → Observation](https://hl7.org/fhir/uv/v2mappings/ConceptMap-segment-obx-to-observation.html). Problems and allergies are not OBX: the IG maps [AL1 → AllergyIntolerance](https://hl7.org/fhir/uv/v2mappings/ConceptMap-segment-al1-to-allergyintolerance.html) (`AL1-3` → `AllergyIntolerance.code`) and [DG1 → Condition](https://hl7.org/fhir/uv/v2mappings/ConceptMap-segment-dg1-to-condition.html) (`DG1-3` → `Condition.code`), also via EpisodeOfCare.diagnosis.

| v2 field                               | FHIR path                 | Binding / type map                                                                     | Usual failure                                                                  |
| -------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| **OBX-3** Observation Identifier (CWE) | `Observation.code`        | [LOINC](https://loinc.org/) `http://loinc.org` when CWE.3 is `LN`                      | Local mnemonic (`HGB^^^L`) with no system                                      |
| **OBX-2** + **OBX-5**                  | `value[x]`                | NM → `valueQuantity`; ST/FT/TX → `valueString`; CWE/CE/CNE/CF → `valueCodeableConcept` | Value type and value\[x] disagree                                              |
| **OBX-6** Units                        | `valueQuantity` unit/code | UCUM when the sender actually sends it                                                 | Unit on the string, not on the Quantity                                        |
| **OBX-11** Result status               | `Observation.status`      | Table 0085 → status (`F` → `final`)                                                    | Treating `C` (corrected) as a new Observation                                  |
| **AL1-3** Allergen                     | `AllergyIntolerance.code` | [SNOMED CT](http://snomed.info/sct) when CWE.3 is `SCT`                                | Free-text allergen with no code system                                         |
| **DG1-3** Diagnosis                    | `Condition.code`          | SNOMED CT (or the national diagnosis system the CWE names) as the IG maps CWE          | Assuming every DG1 is a problem-list Condition without reading the message map |

LOINC and SNOMED are **bindings**. They are not a licence I sell, not a code-system I host, and not implied by a valid FHIR JSON document. The v2-to-FHIR IG maps CWE → CodeableConcept; it does not translate a local table into LOINC or SNOMED for you.

Primary sources: [v2-to-FHIR 1.0.0](https://hl7.org/fhir/uv/v2mappings/), [OBX → Observation ConceptMap](https://hl7.org/fhir/uv/v2mappings/ConceptMap-segment-obx-to-observation.html), [LOINC](https://loinc.org/).

## Identifier on the observation: OBX-3, not PID

PID still becomes Patient — that is the [ADT article](https://alabenaicha.me/insights/hl7-adt-to-fhir-patient-encounter). Terminology failures show up when **OBX-3 has no assigning coding system**. A second Patient is the wrong fix.

De-identified ORU/OBX fragment (specification fixture, not a person):

```
MSH|^~\&|LIS|LAB-A|FHIR-BRIDGE|HOSP-A|20260919120000||ORU^R01^ORU_R01|MSG0007|P|2.5
PID|1||HOSP-MRN-0001^^^HOSP-A^MR||EXAMPLE^JANE^Q
OBR|1||FILL-2026-0042|58410-2^CBC panel - Blood by Automated count^LN
OBX|1|NM|718-7^Hemoglobin [Mass/volume] in Blood^LN||13.2|g/dL|12.0-16.0|N|||F
```

Projected Observation (truncated):

```json
{
  "resourceType": "Observation",
  "status": "final",
  "code": {
    "coding": [
      {
        "system": "http://loinc.org",
        "code": "718-7",
        "display": "Hemoglobin [Mass/volume] in Blood"
      }
    ]
  },
  "valueQuantity": {
    "value": 13.2,
    "unit": "g/dL",
    "system": "http://unitsofmeasure.org",
    "code": "g/dL"
  }
}
```

The local-code variant `OBX|1|NM|HGB^Hemoglobin^^L||13.2|g/dL` parses as v2 and as FHIR and still fails every semantic check that expects LOINC. That is the usual production defect — not a missing PID-3.

## What to implement first

1. Inventory OBX-3 coding systems on **real de-identified** messages (LN, SCT, local `L`, empty). Count how many Observation.code values would have no `system`.
2. Pin `hl7.fhir.uv.v2mappings#1.0.0`. Implement OBX → Observation including OBX-2 → value\[x] branching; do not hard-code `valueQuantity` for every OBX.
3. Bind **LOINC** on lab/clinical observations and **SNOMED CT** on AL1/DG1 as the IG maps those segments. Keep national code systems as additional `coding` entries; do not drop them.
4. Test corrections (`OBX-11 = C`) and cancels, not only the happy-path `F`. Report context still needs DiagnosticReport where the [radiology/lab guide](https://alabenaicha.me/insights/radiology-lab-workflow-integration) says so.

## What this is not

This article is not a LOINC or SNOMED CT licence, not a UMLS subscription, not a patient-identity matcher, and not clinical advice. Mapping one OBX in a lab does not certify an interface. I do not sell terminology licences. PID mismatches belong on the ADT/MPI pages. Values above are fixtures, not a person.

## Related reading

* [HL7 v2 to FHIR migration](https://alabenaicha.me/insights/hl7v2-to-fhir-migration-strategy)
* [Radiology and lab workflows](https://alabenaicha.me/insights/radiology-lab-workflow-integration)
* [HL7 ADT to Patient and Encounter](https://alabenaicha.me/insights/hl7-adt-to-fhir-patient-encounter)

For engine work that preserves OBX-3 systems into Observation.code, use [HL7 FHIR integration](https://alabenaicha.me/services/hl7-fhir-integration) or [start a project](https://alabenaicha.me/contact?intent=project).
