From HL7 Pipes to FHIR APIs: A Deep Dive into Clinical Laboratory Interoperability
From HL7 Pipes to FHIR APIs: A Deep Dive into Clinical Laboratory Interoperability
For clinical informaticists and data scientists: Understanding the translation layer between legacy HL7 v2.x messages and modern FHIR resources is essential for building interoperable healthcare systems. This guide walks through the complete lab workflow—from physician order to clinical decision support—showing exactly how data transforms at each step.
The Interoperability Challenge
Picture this scenario: A physician orders a fasting glucose test at 9:30 AM. By noon, the results are in the patient’s chart, flagged as abnormal, with a clinical decision support alert suggesting follow-up. Behind this seemingly simple workflow lies a complex dance of systems, standards, and semantics.
The challenge isn’t just moving data—it’s preserving meaning.
This is where the healthcare interoperability stack comes into play:
| Standard | Layer | Role |
|---|---|---|
| HL7 v2.x | Messaging | Pipe-delimited transactional backbone |
| FHIR | Structure | RESTful JSON resources for modern APIs |
| LOINC | Identification | Universal codes for what was measured |
| SNOMED CT | Semantics | Clinical meaning for why and interpretation |
A Real-World Scenario: Mr. Smith’s Diabetes Diagnosis
Let’s follow a complete clinical workflow to understand how these standards work together.
The Patient
| Attribute | Value |
|---|---|
| Name | John Q. Smith |
| Age | 55, Male |
| Chief Complaint | Fatigue and increased thirst for 2 weeks |
| Clinical Suspicion | Type 2 Diabetes Mellitus |
| MRN | MRN123456 |
The Timeline
Now let’s examine the data structures that make this workflow possible.
Part 1: HL7 v2.x Message Anatomy
HL7 v2.x messages are the transactional backbone of healthcare. They use a pipe-delimited format that, while dated, remains ubiquitous in production systems worldwide.
Message Delimiters
Understanding the delimiter hierarchy is essential for parsing HL7:
| Character | Symbol | Purpose |
|---|---|---|
<CR> |
Carriage Return | Segment terminator |
\| |
Pipe | Field separator |
^ |
Caret | Component separator |
& |
Ampersand | Subcomponent separator |
~ |
Tilde | Repetition separator |
\ |
Backslash | Escape character |
Core Segments for Laboratory Messaging
| Segment | Name | Clinical Purpose |
|---|---|---|
| MSH | Message Header | Routing metadata, message type, version |
| PID | Patient Identification | Demographics, MRN, name, DOB |
| PV1 | Patient Visit | Encounter context, attending physician |
| ORC | Common Order | Order control, placer/filler numbers |
| OBR | Observation Request | Test ordered, LOINC code, specimen info |
| OBX | Observation/Result | Actual test values and interpretations |
| NTE | Notes | Free-text clinical comments |
Part 2: The Order Flow (ORM^O01)
When Dr. Williams clicks “Sign Order” in the EHR, an ORM^O01 message is generated:
Complete ORM Example
MSH|^~\&|EPIC|CITYCLINIC|LABSYS|CITYLAB|20251130093500||ORM^O01|MSG00001|P|2.5.1|||AL|NE
PID|1||MRN123456^^^CITYCLINIC^MR||Smith^John^Q||19700215|M|||123 Main St^^Chicago^IL^60601||312-555-1234
PV1|1|O|CLINIC^^^CITYCLINIC||||1234567^Williams^Robert^J^MD
ORC|NW|ORD123456^EPIC||||||^^^^^R||20251130093000|||1234567^Williams^Robert^J^MD
OBR|1|ORD123456^EPIC||2345-7^Glucose [Mass/volume] in Serum or Plasma^LN|||20251130093500||||||||1234567^Williams^Robert^J^MD
NTE|1||R/O Diabetes Mellitus - Patient reports fatigue and polyuria x 2 weeks
ORC|NW|ORD123457^EPIC||||||^^^^^R||20251130093000|||1234567^Williams^Robert^J^MD
OBR|2|ORD123457^EPIC||4548-4^Hemoglobin A1c/Hemoglobin.total in Blood^LN|||20251130093500
ORC|NW|ORD123458^EPIC||||||^^^^^R||20251130093000|||1234567^Williams^Robert^J^MD
OBR|3|ORD123458^EPIC||24323-8^Comprehensive metabolic panel^LN|||20251130093500
Segment-by-Segment Breakdown
MSH (Message Header)
The MSH segment is always first and defines the message envelope:
MSH|^~\&|EPIC|CITYCLINIC|LABSYS|CITYLAB|20251130093500||ORM^O01|MSG00001|P|2.5.1
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ └─ HL7 Version
│ │ │ │ │ │ │ │ └─── Processing ID (P=Production)
│ │ │ │ │ │ │ └───────────── Message Control ID
│ │ │ │ │ │ └───────────────────── Message Type (Order)
│ │ │ │ │ └───────────────────────────────────── Timestamp
│ │ │ │ └───────────────────────────────────────────── Receiving Facility
│ │ │ └──────────────────────────────────────────────────── Receiving Application
│ │ └─────────────────────────────────────────────────────────────── Sending Facility
│ └──────────────────────────────────────────────────────────────────── Sending Application
└───────────────────────────────────────────────────────────────────────── Encoding Characters
PID (Patient Identification)
The PID segment carries all patient demographics:
PID|1||MRN123456^^^CITYCLINIC^MR||Smith^John^Q||19700215|M|||123 Main St^^Chicago^IL^60601
│ │ │ │ │ │
│ │ │ │ │ └─ Address (Street^^City^State^ZIP)
│ │ │ │ └───── Sex (M/F)
│ │ │ └────────────── DOB (YYYYMMDD)
│ │ └──────────────────────────── Name (Last^First^Middle)
│ └────────────────────────────────────────────────────── MRN^^^Authority^Type
└───────────────────────────────────────────────────────── Set ID
The patient identifier includes the assigning authority (CITYCLINIC) and identifier type (MR = Medical Record Number).
ORC (Common Order)
The ORC segment controls order management:
ORC|NW|ORD123456^EPIC||FIL789012^LABSYS|||||20251130093000|||1234567^Williams^Robert^J^MD
│ │ │ │ │
│ │ │ │ └─ Ordering Provider (XCN format)
│ │ │ └───────────────────── Transaction Date/Time
│ │ └────────────────────────────────────────── Filler Order Number (LIS assigns)
│ └────────────────────────────────────────────────────────── Placer Order Number (EHR assigns)
└───────────────────────────────────────────────────────────── Order Control (NW = New Order)
Order Control Codes:
| Code | Meaning | Use Case |
|---|---|---|
NW |
New Order | Initial order placement |
CA |
Cancel | Cancel previously placed order |
XO |
Change | Modify existing order |
SC |
Status Changed | Order status update |
RE |
Observations | Results accompanying order |
OBR (Observation Request)
The OBR segment specifies what test to perform using LOINC codes:
OBR|1|ORD123456^EPIC|FIL789012^LABSYS|2345-7^Glucose [Mass/volume] in Serum or Plasma^LN
│ │ │ │
│ │ │ └─ Universal Service ID (LOINC Code^Display^System)
│ │ └───────────────── Filler Order Number
│ └───────────────────────────────── Placer Order Number
└─────────────────────────────────── Set ID
This is where LOINC integration begins—the code 2345-7 universally identifies “Glucose [Mass/volume] in Serum or Plasma.”
Part 3: The Results Flow (ORU^R01)
When the lab completes analysis, an ORU^R01 message carries the results back:
Complete ORU Example
MSH|^~\&|LABSYS|CITYLAB|EPIC|CITYCLINIC|20251130120000||ORU^R01|MSG00002|P|2.5.1|||AL|NE
PID|1||MRN123456^^^CITYCLINIC^MR||Smith^John^Q||19700215|M|||123 Main St^^Chicago^IL^60601
PV1|1|O|CLINIC^^^CITYCLINIC||||1234567^Williams^Robert^J^MD
ORC|RE|ORD123456^EPIC|FIL789012^LABSYS||||^^^^^R||20251130093000|||1234567^Williams^Robert^J^MD
OBR|1|ORD123456^EPIC|FIL789012^LABSYS|2345-7^Glucose [Mass/volume] in Serum or Plasma^LN|||20251130094500|||||||||1234567^Williams^Robert^J^MD||||||20251130120000|||F
OBX|1|NM|2345-7^Glucose^LN||186|mg/dL|70-100|H|||F|||20251130113000||9876543^Johnson^Mary^L^MT
NTE|1||Fasting specimen confirmed
ORC|RE|ORD123457^EPIC|FIL789013^LABSYS
OBR|2|ORD123457^EPIC|FIL789013^LABSYS|4548-4^Hemoglobin A1c/Hemoglobin.total in Blood^LN|||20251130094500|||||||||1234567^Williams^Robert^J^MD||||||20251130120000|||F
OBX|1|NM|4548-4^Hemoglobin A1c/Hemoglobin.total^LN||8.2|%|4.0-5.6|H|||F|||20251130114500
NTE|1||Result indicates poor glycemic control over past 2-3 months
The OBX Segment: Heart of Lab Results
The OBX segment carries individual test results:
OBX|1|NM|2345-7^Glucose^LN||186|mg/dL|70-100|H|||F|||20251130113000
│ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ └─ Observation Date/Time
│ │ │ │ │ │ │ └───── Result Status (F=Final)
│ │ │ │ │ │ └───────── Abnormal Flag (H=High)
│ │ │ │ │ └───────────────── Reference Range
│ │ │ │ └──────────────────────── Units
│ │ │ └──────────────────────────── Result Value
│ │ └──────────────────────────────────────────────── Observation ID (LOINC)
│ └─────────────────────────────────────────────────── Value Type (NM=Numeric)
└───────────────────────────────────────────────────── Set ID
Value Types in OBX:
| Code | Type | Example |
|---|---|---|
NM |
Numeric | 186 |
ST |
String | "Yellow" |
CE |
Coded Entry | 112144000^Blood group A^SCT |
SN |
Structured Numeric | <^5 or >^10000 |
TX |
Text | Free-text narrative |
Abnormal Flags:
| Flag | Meaning | Clinical Significance |
|---|---|---|
N |
Normal | Within reference range |
L |
Low | Below normal |
H |
High | Above normal |
LL |
Critical Low | Panic value - immediate action needed |
HH |
Critical High | Panic value - immediate action needed |
Part 4: Mapping HL7 to FHIR
Here’s where modernization happens. Each HL7 segment maps to a FHIR resource:
HL7 to FHIR Mapping Architecture
Mapping Matrix
| HL7 Segment | FHIR Resource | Key Elements |
|---|---|---|
| PID | Patient |
name, birthDate, gender, identifier, address |
| ORC/OBR | ServiceRequest |
code (LOINC), subject, requester, status |
| OBX | Observation |
code (LOINC), value, interpretation (SNOMED), referenceRange |
| SPM | Specimen |
type, collection, receivedTime |
| ORU (full) | DiagnosticReport |
result references, conclusion, status |
FHIR Observation Example
Here’s how Mr. Smith’s glucose result transforms into FHIR:
{
"resourceType": "Observation",
"id": "glucose-result-mr-smith",
"status": "final",
"category": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "laboratory",
"display": "Laboratory"
}]
}],
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "2345-7",
"display": "Glucose [Mass/volume] in Serum or Plasma"
}]
},
"subject": {
"reference": "Patient/mrn123456",
"display": "John Q. Smith"
},
"effectiveDateTime": "2025-11-30T11:30:00Z",
"valueQuantity": {
"value": 186,
"unit": "mg/dL",
"system": "http://unitsofmeasure.org",
"code": "mg/dL"
},
"interpretation": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "H",
"display": "High"
}]
}],
"referenceRange": [{
"low": { "value": 70, "unit": "mg/dL" },
"high": { "value": 100, "unit": "mg/dL" },
"text": "70-100 mg/dL"
}],
"performer": [{
"reference": "Practitioner/tech-mary-johnson",
"display": "Mary L. Johnson, MT"
}]
}
The Power of Coded Elements
Notice how the FHIR resource embeds multiple coding systems:
- LOINC (
http://loinc.org): Identifies what was tested - UCUM (
http://unitsofmeasure.org): Standardizes units - HL7 Interpretation codes: Flags abnormal values
- SNOMED CT (when needed): Provides clinical semantics
Part 5: LOINC Deep Dive
LOINC (Logical Observation Identifiers Names and Codes) provides universal test identification through a 6-part model:
LOINC Code Structure
| Part | Name | Example (2345-7) |
|---|---|---|
| 1 | Component | Glucose |
| 2 | Property | MCnc (Mass Concentration) |
| 3 | Time | Pt (Point in time) |
| 4 | System | Ser/Plas (Serum or Plasma) |
| 5 | Scale | Qn (Quantitative) |
| 6 | Method | (optional) |
Combined: Glucose.MCnc.Pt.Ser/Plas.Qn = 2345-7
Essential Laboratory LOINC Codes
| LOINC | Test | Category |
|---|---|---|
| 2345-7 | Glucose (Fasting) | Chemistry |
| 4548-4 | Hemoglobin A1c | Diabetes |
| 2951-2 | Sodium | Electrolytes |
| 2823-3 | Potassium | Electrolytes |
| 2160-0 | Creatinine | Renal |
| 718-7 | Hemoglobin | Hematology |
| 6690-2 | WBC Count | Hematology |
| 5902-2 | Prothrombin Time | Coagulation |
| 883-9 | ABO Group | Blood Bank |
LOINC in the Wild
In HL7 OBR/OBX:
OBR|1|...|2345-7^Glucose [Mass/volume] in Serum or Plasma^LN|...
└─────┘ └─┘
LOINC Code LN = LOINC
In FHIR:
{
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "2345-7",
"display": "Glucose [Mass/volume] in Serum or Plasma"
}]
}
}
Part 6: SNOMED CT for Clinical Meaning
While LOINC tells you what was measured, SNOMED CT tells you what it means.
When SNOMED CT Applies
- Qualitative results: Blood type (A, B, AB, O)
- Interpretations: Positive, Negative, Indeterminate
- Clinical findings: Associated conditions
- Diagnoses: Linked clinical diagnoses
Example: Blood Type Result
HL7 OBX (Coded Entry):
OBX|1|CE|882-1^ABO Group^LN||112144000^Blood group A^SCT||||N|||F
└─────────────────────────────┘
SNOMED CT code for "Blood group A"
FHIR Observation:
{
"valueCodeableConcept": {
"coding": [{
"system": "http://snomed.info/sct",
"code": "112144000",
"display": "Blood group A (finding)"
}]
}
}
Common SNOMED CT Codes for Lab
| Code | Display | Use Case |
|---|---|---|
| 260385009 | Negative | Qualitative negative result |
| 10828004 | Positive | Qualitative positive result |
| 112144000 | Blood group A | ABO typing |
| 278147001 | Blood group O | ABO typing |
Part 7: The Complete Integration Architecture
Here’s how it all fits together in production:
Transformation Pipeline
Part 8: Clinical Results for Mr. Smith
Here’s the final results summary with clinical interpretation:
Results Table
| Test | LOINC | Result | Units | Range | Flag |
|---|---|---|---|---|---|
| Glucose, Fasting | 2345-7 | 186 | mg/dL | 70-100 | HIGH |
| Hemoglobin A1c | 4548-4 | 8.2 | % | 4.0-5.6 | HIGH |
| Sodium | 2951-2 | 140 | mmol/L | 136-145 | Normal |
| Potassium | 2823-3 | 4.2 | mmol/L | 3.5-5.0 | Normal |
| Chloride | 2075-0 | 102 | mmol/L | 98-106 | Normal |
| CO2 | 2028-9 | 24 | mmol/L | 23-29 | Normal |
| BUN | 3094-0 | 18 | mg/dL | 7-20 | Normal |
| Creatinine | 2160-0 | 1.0 | mg/dL | 0.7-1.3 | Normal |
| Calcium | 17861-6 | 9.4 | mg/dL | 8.5-10.5 | Normal |
Clinical Interpretation
The elevated fasting glucose (186 mg/dL) and HbA1c (8.2%) are consistent with Type 2 Diabetes Mellitus. Normal renal function (BUN, Creatinine) indicates no diabetic nephropathy at this time. Electrolytes within normal limits.
Diagnosis: Type 2 Diabetes Mellitus (ICD-10: E11.9)
What Makes This Data Interoperable
- LOINC codes ensure the glucose test from City Clinic is recognized identically at any other facility
- Reference ranges enable automatic flagging regardless of which EHR displays results
- Structured data allows CDS rules to fire: “If Glucose > 126 AND HbA1c > 6.5, suggest Diabetes workup”
- FHIR resources enable real-time API queries across systems
Key Takeaways for Implementers
For Data Engineers
- Parse before transforming: Validate HL7 delimiter handling before FHIR mapping
- Preserve identifiers: Placer/filler order numbers link orders to results
- Handle repetitions: OBX segments repeat; create one Observation per OBX
- Validate terminology: LOINC codes must be valid; implement terminology server lookups
For Clinical Informaticists
- Map local codes: Many LIS systems use local codes—map them to LOINC
- Preserve clinical notes: NTE segments contain critical clinical context
- Abnormal flag consistency: Ensure HL7 flags map correctly to FHIR interpretations
- Reference range normalization: Different analyzers may have different ranges
For Data Scientists
- Structured codes enable ML: LOINC/SNOMED coded data is ML-ready
- Temporal data matters: effectiveDateTime enables time-series analysis
- Reference ranges as features: Abnormal flags are pre-computed clinical features
- Linkage is key: Patient/Encounter references enable longitudinal analysis
Resources and Further Reading
Official Standards
| Resource | URL |
|---|---|
| HL7 International | https://www.hl7.org |
| FHIR R4 Specification | https://www.hl7.org/fhir/ |
| LOINC | https://loinc.org |
| SNOMED CT | https://www.snomed.org |
Tools
| Tool | Purpose |
|---|---|
| RELMA | LOINC mapping and search tool |
| HAPI FHIR | Open-source FHIR server (Java) |
| Mirth Connect | HL7 integration engine |
| Synthea | Synthetic patient data generator |
Conclusion
The journey from HL7 pipes to FHIR APIs represents healthcare IT’s transition from systems that talk to systems that understand. By leveraging:
- HL7 v2.x for legacy transactional messaging
- FHIR for modern, API-driven architecture
- LOINC for universal test identification
- SNOMED CT for clinical semantic meaning
We enable clinical systems to not just transfer data, but to reason about it—powering clinical decision support, AI/ML applications, and truly interoperable healthcare.
The pipes are still there. But now they carry meaning.
Have questions about implementing lab interoperability? Connect with me on GitHub or drop a comment below.
Tags: #FHIR #HL7 #LOINC #SNOMEDCT #Interoperability #ClinicalInformatics #HealthcareIT #LIS #EHR #DataStandards #HealthTech