HL7 v2 is not dead, it is just pipes and carets
The message format that still carries most of a hospital, and how not to lose lab results with it.
Contents
Last time I promised something about HL7 v2. Here it is. If FHIR is the modern REST API, v2 is the message format that has been running inside hospitals since the late eighties, and it still carries most of the admissions, lab orders and results today. You will meet it, even in a "FHIR project", because something behind the FHIR facade still speaks v2.
What a message looks like
MSH|^~\&|LAB|HOSP_A|EHR|HOSP_A|202607091130||ORU^R01|MSG00042|P|2.5.1
PID|1||556677^^^HOSP_A^MR||TEST^PATIENT||19800101|F
OBR|1||LAB-991|24331-1^Lipid panel^LN
OBX|1|NM|2093-3^Cholesterol^LN||182|mg/dL|<200|N|||F
Every line is a segment, named by its first three letters. Segments are separated by a carriage return (\r, not \n, which is the first bug everybody writes). Fields are separated by |, components by ^, repetitions by ~, subcomponents by &, and \ is the escape character. MSH defines these characters itself in MSH-1 and MSH-2, so in theory a sender could choose different ones. In practice nobody does, but a good parser reads them from the message anyway.
The message types you will see first
ADT^A01admit,ADT^A03discharge,ADT^A08update patient information. The hospital telling everybody who is where.ORM^O01orOML^O21, orders going to the lab or radiology.ORU^R01, results coming back, like the example above.
Acknowledgements are not optional
The receiver answers every message with an ACK, and MSA-1 says AA (accepted), AE (error) or AR (rejected). Most interface engines resend, or stop the whole queue, until they get an AA. If your system returns AA before the data is safely stored, you have built a very efficient way to lose lab results. Store first, then acknowledge.
Transport: MLLP
Over TCP the messages are framed with MLLP: a start byte 0x0B, the message, then 0x1C 0x0D. Simple framing, that is the good news. The bad news is that connections stay open for days, firewalls kill idle connections silently, and the sender only notices when no ACK comes back. Monitor the age of the last received message per interface, not only whether the port is open.
Things that will hurt
- Splitting on
|with a string function works until a field contains an escaped separator like\F\. Use a real parser. - Z-segments (
ZPD,ZINand friends) are local extensions. Every hospital has some. Ask for the interface specification and expect it to be outdated. - The same "version 2.5" from two vendors is not the same. Fields that are optional in the standard are required by one side and always empty from the other.
- Timestamps very often come without a time zone offset. This deserves its own post.
It is old, it is ugly, and it works surprisingly well. Treat it with respect, and with a proper parser.
Written by
Hannah Weiss
Clinical informatics engineer in Berlin. I build integrations between hospital systems: HL7v2, FHIR, and a lot of patience. Formerly a nurse, which explains my opinions about alert fatigue.
Get the best of #vitals_and_variables weekly
One email each Monday with the most useful posts. No spam; unsubscribe in one click.
Comments
Sign in to join the conversation.
Sign in to comment