std.hl7 — HL7 v2 Messaging

Fünf Units für HL7 Version 2 — den im Gesundheitswesen meistverbreiteten Nachrichtenstandard. std.hl7 deckt den gesamten Stack ab: MLLP-TCP-Framing, MSH/ACK-Engine, Patientenverwaltung (ADT), Auftragswesen (ORM/OML/RAS), Befundübermittlung (ORU/OUL) und Terminplanung (SIU).

Übersicht · Standard Library · Welche Unit?

Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben sind die echten Programmausgaben.


Units

Unit WP Beschreibung
std.hl7.core WP-HL7-00 MLLP-Framing (VT+FS+CR), MSH-Parser, ACK-Generator, Duplikatserkennung (FNV1a-Ring), Versions- und Testmodus-Prüfung
std.hl7.adt WP-HL7-01 Patient Administration: ADT^Axx parsen/schreiben; PID, PV1, MRG, NK1; automatische Merge-Erkennung (A34–A45)
std.hl7.orders WP-HL7-02 Order Management: ORM^O01, OML^O21, RAS^O17 parsen/schreiben; ORC, OBR, RXA; STAT-Erkennung; ORR^O02-Quittung
std.hl7.results WP-HL7-03 Result Reporting: ORU^R01, OUL^R22 parsen/schreiben; OBX (Panikwert- und Korrektur-Flags), NTE-Kommentare
std.hl7.scheduling WP-HL7-04 Terminplanung: SIU^S12S26 parsen/schreiben; SCH, AIS, AIG, AIL; Flags für Absage (S15), Löschung (S17), Nicht-Erscheinen (S26)

Alle vier Fachunits importieren std.hl7.coreimport std.hl7.adt; zieht den Kern also mit.

 
^ in DokuWiki-Tabellen. Der Nachrichtentyp wird in HL7 mit ^ geschrieben (ADT^A01). In einer Wiki-Tabelle trennt genau dieses Zeichen die Zellen — deshalb steht es hier überall in . Wer diese Seite bearbeitet: bitte beibehalten, sonst zerfällt die Tabelle in Spalten.

Was ist HL7 v2?

HL7 (Health Level 7) Version 2 ist der faktische Standard für Datenintegration in Krankenhäusern. Jede Nachricht besteht aus zeilenweise (CR) geschriebenen Segmenten (3 Großbuchstaben + Feldtrenner), die |-getrennte Felder und ^-getrennte Komponenten enthalten:

MSH|^~\&|LIS|HOSPITAL|HIS||20260616083000||ORU^R01|MSG001|P|2.5.1
PID|||PAT123^^^MPI||Muster^Max||19850315|M
OBR|1|ORD042|LAB042|1743-4^Glucose^LN|||20260616082000
OBX|1|NM|1743-4^Glucose^LN||195|mg/dL|70-99||||HH||F

Die Standard-Trennzeichen stehen in MSH-1 und MSH-2 und werden von Hl7MshParse aus der Nachricht gelesen, nicht angenommen:

Ebene Zeichen Beispiel
Feld | MSH|^~\&|LIS
Komponente ^ Muster^Max
Wiederholung ~ ID1~ID2
Escape \ \F\ für ein literales |
Subkomponente & A&B

MLLP-Framing

HL7 v2-Nachrichten werden über TCP immer im MLLP-Frame übertragen (Minimal Lower Layer Protocol):

[VT=0x0B] + HL7-Nachrichtenbytes + [FS=0x1C] + [CR=0x0D]

MllpFrameWrite legt den Rahmen um eine fertige HL7-Nachricht, MllpFrameRead schält die Nachricht aus dem TCP-Empfangspuffer. MllpFrameRead sucht das VT auch mitten im Puffer — Datenmüll davor wird übersprungen.


Designprinzipien

  • Zero-Copy-Parsing: Alle Parse-Funktionen speichern Zeiger und Längen in den Originalnachrichtenpuffer — keine String-Kopien. Der Puffer muss leben, solange die Struct benutzt wird.
  • Caller alloziert: Alle Structs (MSH, PID, PV1, ADT, Order, Report, OBX, Appointment) legt der Aufrufer selbst an. Die Größe steht in den HL7_*_SIZE-Konstanten.
  • Kein rekursiver Besitz: Eine geparste ORU-Nachricht liefert nur den Befundkopf (Hl7OruParse) — die einzelnen OBX-Segmente werden getrennt mit Hl7ObxParse in einer Schleife geparst. Genauso bei NTE, AIS, AIG und AIL.
  • Abgeleitete Flags: HL7_ORD_ISSTAT, HL7_OBX_ISCRIT/HL7_OBX_ISCORR sowie HL7_APPT_ISCANCEL/ISDELETE/ISNOSHOW setzt der Parser selbst — keine manuellen String-Vergleiche nötig.
 
Zeiger aus einer Struct sind nicht nullterminiert. Jedes Feld liegt als Paar vor: Offset X hält den Zeiger, Offset X_LEN die Länge. Wer den Wert an PrintLn oder StrConcat übergibt, druckt den Rest der Nachricht mit. Für die Ausgabe eine terminierte Kopie anlegen — siehe die Funktion Feld() im Quickstart unten.

Nachrichtenflüsse

Patientenaufnahme (ADT)

HIS/ADT-System          HL7-Gateway / Lyx
    |                        |
    |-- ADT^A01 (Aufnahme) ->|  Hl7AdtParse()
    |<-- ACK^AA (Quittung) --|  Hl7AckWrite() + MllpFrameWrite()
    |                        |
    |-- ADT^A08 (Update) --->|  Hl7AdtParse()
    |<-- ACK^AA -------------|
    |                        |
    |-- ADT^A03 (Entlassung)>|  Hl7AdtParse()
    |<-- ACK^AA -------------|

Laborauftrag und Befund (ORM → ORU)

KIS / Station           LIS (Labor)
    |                        |
    |-- ORM^O01 (Auftrag) -->|  Hl7OrmParse()
    |<-- ORR^O02 (OK/ER) ----|  Hl7OrrWrite()
    |                        |  [Analyse laeuft ...]
    |<-- ORU^R01 (Befund) ---|  Hl7OruParse() + Hl7ObxParse() je OBX
    |-- ACK^AA ------------->|

Medikamentengabe (RAS)

Medikamentensystem      Stationssystem
    |                        |
    |-- RAS^O17 ------------>|  Hl7RasParse()
    |<-- ACK^AA -------------|

Terminvergabe (SIU)

Terminplaner            Fachabteilung
    |                        |
    |-- SIU^S12 (neu) ------>|  Hl7SiuParse()
    |-- SIU^S15 (Absage) --->|  HL7_APPT_ISCANCEL = 1
    |-- SIU^S26 (No-Show) -->|  HL7_APPT_ISNOSHOW = 1
    |<-- ACK^AA -------------|


Funktionsübersicht

std.hl7.core

Funktion Rückgabe Zweck
Hl7StateInit(state) Dedup-State nullen; einmalig pro Instanz
MllpFrameWrite(buf, bufMax, msg, msgLen) Bytes oder -1 VT+Nachricht+FS+CR schreiben
MllpFrameRead(buf, bufLen, out, outMax) Länge, -1 (kein Frame), -2 (out zu klein) Nachricht aus dem Frame schälen
Hl7MshParse(msg, len, msh) HL7_OK / HL7_ERR_SYNTAX MSH-Segment in die Struct parsen
Hl7AckWrite(msh, code, text, out, outMax) Bytes oder -1 ACK/NACK erzeugen; code ist "AA"c, "AE"c oder "AR"c als pchar
Hl7DupCheck(state, partnerKey, ctrlId) 1 = schon gesehen, 0 = neu Duplikate je Partner über FNV1a-Ring (64 Einträge)
Hl7VersionCheck(msh) HL7_OK / HL7_ERR_VERSION Unterstützt 2.3.1, 2.4, 2.5, 2.5.1, 2.6, 2.7
Hl7IsTestMsg(msh) 1 = Testnachricht MSH-11 beginnt mit T — nicht in die Produktivdatenbank schreiben
Hl7IsUtf8(msh) 1 = UTF-8 MSH-18 = UNICODE…; ohne MSH-18 gilt ISO-8859-1, nicht UTF-8

Hl7DupCheck registriert die Nachricht beim Rückgabewert 0 gleich mit — ein zweiter Aufruf mit derselben Control-ID liefert 1.

std.hl7.adt

Funktion Zweck
Hl7AdtParse(msg, len, adt) Ganze Nachricht: MSH-Kopf, PID, PV1, MRG, NK1 in eine Hl7Adt-Struct
Hl7AdtWrite(adt, event, out, outMax) Nachricht aus der Struct schreiben; event z. B. "A01"c
Hl7PidParse · Hl7Pv1Parse · Hl7MrgParse · Hl7Nk1Parse Einzelsegment parsen (Zeiger auf das Segment, nicht auf die Nachricht)
Hl7AdtIsMerge(eventPtr, eventLen) 1 bei A34–A45 (Merge-/Move-Ereignisse)

std.hl7.orders

Funktion Zweck
Hl7OrmParse · Hl7OmlParse Auftragsnachricht in eine Hl7Order-Struct
Hl7RasParse(msg, len, admin) Medikamentengabe in eine Hl7RxAdmin-Struct
Hl7OrcParse · Hl7ObrParse · Hl7RxaParse Einzelsegmente
Hl7OrmWrite · Hl7OmlWrite Auftrag schreiben
Hl7OrrWrite(order, status, out, outMax) Auftragsquittung ORR&#94;O02

HL7_ORD_MSGTYPE unterscheidet die Herkunft (1 = ORM, 2 = OML), HL7_ORD_ISSTAT meldet einen STAT-Auftrag (OBR-5 = S).

std.hl7.results

Funktion Zweck
Hl7OruParse · Hl7OulParse Befundkopf (OBR + PID) in eine Hl7Report-Struct
Hl7ObxParse(seg, segLen, obx) Ein OBX-Segment; je Beobachtung einmal aufrufen
Hl7NteParse(seg, segLen, note) Kommentarsegment
Hl7OruWrite(report, obx, out, outMax) Befund schreiben

HL7_RPT_MSGTYPE ist HL7_RPT_TYPE_ORU (1) oder HL7_RPT_TYPE_OUL (2). HL7_OBX_ISCRIT markiert Panikwerte (OBX-8 HH/LL), HL7_OBX_ISCORR korrigierte Ergebnisse (OBX-11 C).

std.hl7.scheduling

Funktion Zweck
Hl7SiuParse(msg, len, appt) Terminnachricht in eine Hl7Appointment-Struct
Hl7SiuWrite(appt, out, outMax) Termin schreiben
Hl7SchParse · Hl7AisParse · Hl7AigParse · Hl7AilParse Einzelsegmente: Termin, Leistung, Ressource, Ort

Rückgabewerte

Konstante Wert Bedeutung
HL7_OK 0 erfolgreich
HL7_ERR_TRUNC 1 Nachricht abgeschnitten
HL7_ERR_SYNTAX 2 Segment nicht lesbar (z. B. kein MSH am Anfang)
HL7_ERR_DUP 3 Duplikat
HL7_ERR_OVERFLOW 4 Zielpuffer zu klein
HL7_ERR_VERSION 5 HL7-Version nicht unterstützt

Die Schreib-Funktionen (…Write) melden Überlauf dagegen mit -1 als Rückgabewert, nicht mit HL7_ERR_OVERFLOW — der Rückgabewert ist dort die geschriebene Bytezahl.

HL7_ACK_AA / HL7_ACK_AE / HL7_ACK_AR (1/2/3) sind Kennzahlen für eigene Zustandslogik. Hl7AckWrite erwartet nicht sie, sondern den zweibuchstabigen Code als pchar — seit #1617 ohne Cast: "AA"c.


Struct-Größentabelle

Struct Konstante Bytes Unit Zweck
Hl7Msh HL7_MSH_SIZE 192 core MSH-Header einer beliebigen Nachricht
Hl7Slot HL7_SLOT_SIZE 528 core Ein Partner-Slot des Dedup-Rings (16 + 64×8)
Hl7State HL7_STATE_SIZE 8448 core Dedup-State (16 Partner × 528 Bytes)
Hl7Pid HL7_PID_SIZE 176 adt Patient Identification
Hl7Pv1 HL7_PV1_SIZE 160 adt Patient Visit / Stationsbelegung
Hl7Mrg HL7_MRG_SIZE 48 adt Patient Merge
Hl7Nk1 HL7_NK1_SIZE 48 adt Next of Kin / Angehörige
Hl7Adt HL7_ADT_SIZE 544 adt Vollständige ADT-Nachricht (Kopf + PID + PV1 + MRG + NK1)
Hl7Order HL7_ORD_SIZE 192 orders Auftragsstruktur (ORC + OBR + PID + PV1)
Hl7RxAdmin HL7_RXA_SIZE 112 orders Medikamentengabe (RXA)
Hl7ObxResult HL7_OBX_SIZE 144 results Einzelbeobachtung mit Panikwert-Flag
Hl7NteNote HL7_NTE_SIZE 16 results Kommentar (NTE)
Hl7Report HL7_RPT_SIZE 104 results Befundkopf (OBR + PID, ohne OBX)
Hl7Sch HL7_SCH_SIZE 128 scheduling Terminsegment (SCH)
Hl7Ais HL7_AIS_SIZE 48 scheduling Terminleistung (AIS)
Hl7Aig HL7_AIG_SIZE 48 scheduling Terminressource (AIG)
Hl7Ail HL7_AIL_SIZE 32 scheduling Terminort (AIL)
Hl7Appointment HL7_APPT_SIZE 152 scheduling Vollständige SIU-Nachricht

Die eingebetteten Structs der ADT-Nachricht liegen an festen Offsets: HL7_ADT_PID = 112, HL7_ADT_PV1 = 288, HL7_ADT_MRG = 448, HL7_ADT_NK1 = 496. Ein Feld daraus wird also mit peek64(adt + HL7_ADT_PID + HL7_PID_FAMILY) gelesen.


Quickstart

Empfangen, prüfen, parsen, quittieren — der vollständige Weg einer ADT-Nachricht:

import std.hl7.core;
import std.hl7.adt;
import std.alloc;
import std.string;

// Zero-Copy: die Struct-Felder zeigen in den Nachrichtenpuffer und sind
// NICHT nullterminiert. Fuer die Ausgabe eine Kopie mit \0 anlegen.
fn Feld(ptr: int64, len: int64): pchar {
    var buf: int64 := alloc(len + 1);
    var i: int64 := 0;
    while (i < len) { poke8(buf + i, peek8(ptr + i)); i := i + 1; }
    poke8(buf + len, 0);
    return buf as pchar;
}

fn main(): int64 {
    // Dedup-State: einmalig pro Server-Instanz
    var state: int64 := alloc(HL7_STATE_SIZE);
    Hl7StateInit(state);

    // Eine Nachricht, wie sie aus dem Socket kommt (hier selbst gerahmt)
    var msg: pchar := "MSH|^~\\&|HIS|KH|LIS|LAB|20260813081500||ADT^A01|MSG001|P|2.5.1\rPID|||PAT123^^^MPI||Muster^Max||19850315|M\rPV1||I|ITS^201^A^KH|||||||||||||||V4711\r"c;
    var tcpBuf: int64 := alloc(65536);
    var tcpLen: int64 := MllpFrameWrite(tcpBuf, 65536, msg as int64, StrLen(msg));

    // MLLP-Frame auspacken
    var msgBuf: int64 := alloc(65536);
    var msgLen: int64 := MllpFrameRead(tcpBuf, tcpLen, msgBuf, 65536);
    if (msgLen < 0) { return 1; }

    // MSH parsen und pruefen
    var msh: int64 := alloc(HL7_MSH_SIZE);
    if (Hl7MshParse(msgBuf, msgLen, msh) != HL7_OK) { return 1; }
    if (Hl7VersionCheck(msh) != HL7_OK) { return 1; }
    if (Hl7IsTestMsg(msh) == 1) { PrintLn("Testnachricht - nicht speichern"); }

    // Doppelte Nachricht? (Partnerschluessel: sendende Anwendung)
    var sendApp: int64 := peek64(msh + HL7_MSH_SEND_APP);
    var ctrlId:  int64 := peek64(msh + HL7_MSH_CTRL_ID);
    if (Hl7DupCheck(state, sendApp, ctrlId) == 1) { PrintLn("Duplikat"); return 0; }

    // ADT vollstaendig parsen
    var adt: int64 := alloc(HL7_ADT_SIZE);
    Hl7AdtParse(msgBuf, msgLen, adt);
    var famPtr: int64 := peek64(adt + HL7_ADT_PID + HL7_PID_FAMILY);
    var famLen: int64 := peek64(adt + HL7_ADT_PID + HL7_PID_FAMILY_LEN);
    var bedPtr: int64 := peek64(adt + HL7_ADT_PV1 + HL7_PV1_LBED);
    var bedLen: int64 := peek64(adt + HL7_ADT_PV1 + HL7_PV1_LBED_LEN);
    PrintLn(StrConcat("Nachname: ", Feld(famPtr, famLen)));
    PrintLn(StrConcat("Bett:     ", Feld(bedPtr, bedLen)));

    // ACK bauen und rahmen
    var ackBuf: int64 := alloc(512);
    var ackLen: int64 := Hl7AckWrite(msh, "AA"c, "OK"c, ackBuf, 512);
    var outBuf: int64 := alloc(600);
    var outLen: int64 := MllpFrameWrite(outBuf, 600, ackBuf, ackLen);
    PrintLn(StrConcat("ACK-Bytes: ", IntToStr(outLen)));

    free(outBuf, 600); free(ackBuf, 512); free(adt, HL7_ADT_SIZE);
    free(msh, HL7_MSH_SIZE); free(msgBuf, 65536); free(tcpBuf, 65536);
    free(state, HL7_STATE_SIZE);
    return 0;
}

Nachname: Muster
Bett:     A
ACK-Bytes: 66

Das Bett kommt aus PV1-3.3: die Ortsangabe ITS^201^A^KH zerlegt der Parser in Station (ITS), Zimmer (201), Bett (A) und Einrichtung (KH).


Fallstricke

  • Segmente trennt \r, nicht \n. Eine mit Zeilenumbrüchen aufgebaute Nachricht wird nicht geparst; der Parser sucht CR (0x0D).
  • Zeiger überleben den Puffer nicht. Wird msgBuf freigegeben oder wiederbenutzt, zeigen alle Felder der Structs ins Leere. Werte, die länger gebraucht werden, vorher kopieren.
  • Zeichensatz prüfen. Ohne MSH-18 gilt ISO-8859-1. Umlaute aus einem solchen Strom sind kein UTF-8 — Hl7IsUtf8 fragen, bevor der Text weitergereicht wird.
  • Testnachrichten erkennen. Hl7IsTestMsg vor jedem Schreibzugriff auf die Produktivdatenbank aufrufen; MSH-11 T kommt in Kopplungstests regelmäßig vor.
  • Dedup-Ring ist begrenzt. 16 Partner × 64 Control-IDs. Bei mehr Partnern verdrängt der Ring die ältesten Einträge — die Duplikatserkennung ist eine Nahbereichs-Sicherung, kein Ersatz für eine persistente Nachrichtentabelle.
  • Hl7AckWrite erwartet den Code als Text ("AA"c), nicht die Konstante HL7_ACK_AA.

Weiterführend: std.hl7.core · std.hl7.adt · std.hl7.orders · std.hl7.results · std.hl7.scheduling · std.net · Rohspeicher

Letzte Aktualisierung: 2026-08-13 — Tabellen repariert (^ in Zellen maskiert), „Vier Units„ → fünf, Funktionsübersicht je Unit, Rückgabewerte, Größentabelle um die scheduling-Structs und HL7_SLOT_SIZE ergänzt, Quickstart lauffähig gemacht (er benutzte eine nicht deklarierte tcpBufLen). Alles gegen std/hl7/*.lyx und lyxc 1.0.21A geprüft.

Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).