====== 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). → [[lyx_-_programmiersprache:start|Übersicht]] · [[lyx_-_programmiersprache:units|Standard Library]] · [[lyx_-_programmiersprache:guides:welche-unit|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 ^ | [[lyx_-_programmiersprache:units:hl7:core|std.hl7.core]] | WP-HL7-00 | MLLP-Framing (VT+FS+CR), MSH-Parser, ACK-Generator, Duplikatserkennung (FNV1a-Ring), Versions- und Testmodus-Prüfung | | [[lyx_-_programmiersprache:units:hl7:adt|std.hl7.adt]] | WP-HL7-01 | Patient Administration: ''%%ADT^Axx%%'' parsen/schreiben; PID, PV1, MRG, NK1; automatische Merge-Erkennung (A34–A45) | | [[lyx_-_programmiersprache:units:hl7:orders|std.hl7.orders]] | WP-HL7-02 | Order Management: ''%%ORM^O01%%'', ''%%OML^O21%%'', ''%%RAS^O17%%'' parsen/schreiben; ORC, OBR, RXA; STAT-Erkennung; ''%%ORR^O02%%''-Quittung | | [[lyx_-_programmiersprache:units:hl7:results|std.hl7.results]] | WP-HL7-03 | Result Reporting: ''%%ORU^R01%%'', ''%%OUL^R22%%'' parsen/schreiben; OBX (Panikwert- und Korrektur-Flags), NTE-Kommentare | | [[lyx_-_programmiersprache:units:hl7:scheduling|std.hl7.scheduling]] | WP-HL7-04 | Terminplanung: ''%%SIU^S12%%''–''%%S26%%'' parsen/schreiben; SCH, AIS, AIG, AIL; Flags für Absage (S15), Löschung (S17), Nicht-Erscheinen (S26) | Alle vier Fachunits importieren ''std.hl7.core'' — ''import 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^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 [[https://github.com/SEOLizer/LyX-Compiler/issues/1617|#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:** [[lyx_-_programmiersprache:units:hl7:core|std.hl7.core]] · [[lyx_-_programmiersprache:units:hl7:adt|std.hl7.adt]] · [[lyx_-_programmiersprache:units:hl7:orders|std.hl7.orders]] · [[lyx_-_programmiersprache:units:hl7:results|std.hl7.results]] · [[lyx_-_programmiersprache:units:hl7:scheduling|std.hl7.scheduling]] · [[lyx_-_programmiersprache:units:net|std.net]] · [[lyx_-_programmiersprache:sprache:rohspeicher|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).