|
<< Klicken Sie, um das Inhaltsverzeichnis anzuzeigen >> Navigation: Scripts > FELIX Scripts |
FELIX ist die Schnittstellenkomponente von EMIL. Sie empfängt Nachrichten in den Formaten HL7, HCM oder xDT, übergibt sie an ein Script und schreibt die darin extrahierten Daten in die Patientenakte zurück. Dieses Kapitel beschreibt die Grundfunktion eines FELIX-Scripts und enthält am Ende eine Befehlsreferenz mit Beispielen.
Die allgemeine Scriptsprache wird im Kapitel Scripting-Tutorial beschrieben - dieses Kapitel setzt das Grundwissen voraus.
Grundsätzliche Verarbeitung: Schleife über alle Segmente
Die Verarbeitung einer Inbound-Nachricht erfolgt im FELIX-Script in einer Schleife über alle Segmente. Eingehende Nachrichten (HL7, HCM, xDT) sind in Segmente zerlegt, die jeweils einen Datensatz tragen - z.B. MSH (Message Header), PID (Patient Identification), OBR (Observation Request), OBX (Observation Result).
Mit NextSegment liest man jeweils das nächste Segment in eine Variable und prüft per if, um welches Segment es sich handelt. Innerhalb der Segmentverarbeitung holt GetData die einzelnen Felder mit einer Positionsangabe im Format Feld-Subfeld-...-Komponente:
var
aSegment: String;
begin
while NextSegment(aSegment) do
begin
if aSegment = 'msh' then
begin
// ... Bearbeitung des MSH-Segments ...
end;
end;
end.
Über die debug-Direktive kann in der Testumgebung überall im Script eine Ausgabe in die Konsole des Kanaleditors erfolgen:
debug(aSegment);
Um Ausgaben im Produktivsystem zu machen, die dann im Protokoll erscheinen, ersetzen Sie debug durch writeln:
writeln(aSegment);
Patientenakte suchen oder anlegen
Um Daten einer EMIL-Patientenakte zuordnen zu können, die eventuell bereits existiert, benötigt man die ID des sendenden Systems (hier immer KIS-ID) und die Angaben Vorname, Nachname und Geburtsdatum. Diese müssen zunächst mit GetData aus dem passenden Nachrichtensegment gelesen werden:
aPID := GetData('3-1-1-1');
aLastName := GetData('5-1-1-1');
aFirstname := GetData('5-1-2-1');
aBirthDate := StrToDateTime(GetData('7-1-1-1'), 'yyyymmdd');
Datumsangaben werden im Script als Fließkommazahlen (Double) verarbeitet und können mit StrToDateTime anhand einer Formatangabe konvertiert werden. Mit diesen Daten kann nun die Patientenakte lokalisiert oder angelegt werden. Da es sein kann, dass diese Akte gerade an einem Arbeitsplatz bearbeitet wird, wird versucht, sie zu sperren. Schlägt das fehl, liefert die Funktion false zurück und der Script wird ohne weitere Aktionen verlassen - die Nachricht bleibt im Puffer und wird bei der nächsten Verarbeitung erneut versucht:
if not FindOrCreateSubject(aPID, aFirstname, aLastName, aBirthDate) then exit;
Diese Funktion sucht zunächst nach einer Patientenakte, in der als KIS-Ident exakt die angegebene PID eingetragen ist. FELIX entfernt automatisch führende Nullen von KIS-Idents, da diese in manchen Systemen unterschiedlich übermittelt werden, was sonst dazu führen würde, dass Nachrichten nicht zugeordnet werden. Auch nicht-numerische Identifikationen werden unterstützt.

Wird eine Akte mit dieser Identifikation gefunden und stimmen weder Vor- noch Nachname überein, wird diese Akte verworfen und der Aufruf scheitert. Damit wird das Risiko einer Fehlzuordnung minimiert. Dieser Mechanismus greift nur bei unterschiedlichem Vor- und Nachnamen, damit z.B. eine Namensänderung durch Heirat nicht zur Ablehnung führt.
Wird keine Akte mit dieser Identifikation gefunden, sucht FELIX über Vorname, Nachname, Geschlecht und Geburtsdatum. Bei den üblichen Patientenzahlen einer Einrichtung ist eine Dublette über all diese Parameter extrem unwahrscheinlich, sodass diese Zuordnung als gültig angenommen und die Akte zusätzlich mit der PID versehen wird.
Stammdaten aktualisieren
Wenn die Akte gefunden oder angelegt wurde, können die Stammdaten aus der Nachricht aktualisiert werden. Hier empfiehlt es sich, nicht mit leeren Angaben zu überschreiben, um manuell nachgetragene Daten zu schonen:
if aLastName <> '' then setSubjectInfo('lastname', aLastName);
...
if GetData('11-1-3-1') <> '' then setSubjectInfo('city', GetData('11-1-3-1'));
Befunddaten (OBR/OBX/NTE)
Befunddaten werden in ORU-Nachrichten in OBR-, OBX- und NTE-Segmenten übertragen. Da auf ein Ergebnisfeld OBX mehrere NTE-Freitextelemente folgen können, müssen die Daten gesammelt und beim Segmentwechsel oder am Ende der Verarbeitung an EMIL übergeben werden. Der folgende Scriptcode liest das Befunddatum aus dem OBR-Segment und sammelt dann die Befunddaten in den folgenden OBX- und NTE-Segmenten. Dabei wird zwischen OBX-Befund (in der Regel ein Zahlenwert) und NTE-Segmenttext unterschieden:
...
else if aSegment = 'obr' then
begin
// Falls noch Daten aus dem vorigen OBX/NTE-Block offen sind: schreiben
if (aResultValue <> '') then
SetOrLearnResult(aResultDate, aResultIdent, aResultValue, aResultRange,
aResultUnit, aResultTitle)
else if (aResultText <> '') then
SetOrLearnResult(aResultDate, aResultIdent, aResultText, aResultRange,
aResultUnit, aResultTitle);
aResultDate := StrToDateTime(GetData('7-1-1-1'), 'yyyymmddhhnn');
if aResultDate = 0 then aResultDate := StrToDateTime(GetData('14-1-1-1'), 'yyyymmddhhnn');
if aResultDate = 0 then aResultDate := StrToDateTime(GetData('22-1-1-1'), 'yyyymmddhhnn');
aResultValue := '';
aResultText := '';
end
else if (aSegment = 'obx') and (aResultDate > 0) then
begin
// ggf. vorigen Befund schreiben (siehe oben), dann neuen einsammeln
aResultIdent := GetData('3-1-1-1');
aResultValue := GetData('5-1-1-1');
aResultRange := GetData('7-1-1-1');
aResultUnit := GetData('6-1-1-1');
aResultTitle := GetData('3-1-2-1') + ' ' + GetData('3-1-3-1');
aResultText := '';
end
else if (aSegment = 'nte') and (aResultDate > 0) and (GetData('3-1-1-1') <> '') then
aResultText := aResultText + ' ' + GetData('3-1-1-1');
Die Ausgabe und Rücksetzung erfolgt immer bei Start eines neuen OBR- oder OBX-Segments, da mehrere OBX/NTE-Gruppen auf ein OBR-Segment folgen können. Am Ende der Verarbeitungsschleife muss ein eventuell offener Befund auch noch übertragen werden!
Daten festschreiben und Nachricht aus dem Puffer löschen
Wenn die Verarbeitung fehlerfrei erfolgt ist, werden am Ende des Scripts die Daten in der Datenbank festgeschrieben und die Nachricht aus dem Puffer gelöscht:
SaveAndClose(debugging);
Mit dem Parameter debugging wird bewirkt, dass die Nachricht in der Scriptentwicklungsumgebung nicht gelöscht wird - sonst müsste die Testnachricht nach jedem Lauf erneut eingelesen werden.
Andere Formate (HCM, xDT)
Die obige Beschreibung für HL7-Kanäle gilt analog für HCM und xDT, wobei die Positionsangaben naturgemäß andere sind und durch die passenden ersetzt werden müssen. Der grundsätzliche Aufbau und die Arbeitsweise sind aber identisch.
Die folgenden Funktionen stehen in FELIX-Scripten zur Verfügung. Zu jeder Funktion ist ein kurzes Beispiel angegeben.
Nachrichten- und Segmentverarbeitung
function NextSegment(var aName: String): Boolean;
Liest das nächste Segment aus der Quellnachricht. Die Segmentidentifikation (z.B. msh, pid, obx) wird in aName zurückgegeben. Liefert false, wenn kein Segment mehr verfügbar ist.
while NextSegment(aSegment) do
if aSegment = 'pid' then ...;
function GetData(const aPosition: String): String;
Liefert aus dem aktuell gelesenen Segment die Daten einer Position. Die Position wird als Kette von Indexangaben für Feld, Subfeld, Komponente und Subkomponente angegeben.
aLastName := GetData('5-1-1-1');
procedure First;
Setzt den Segment-Zeiger an den Anfang der Quelldatenmenge zurück. Damit kann die Nachricht ein zweites Mal durchlaufen werden.
First;
while NextSegment(s) do ...;
procedure SaveAndClose(keepMessage: Boolean);
Schreibt die Ergebnisse in die Datenbank und löscht die Nachricht aus dem Puffer, wenn keepMessage = false ist. Im Testmodus wird üblicherweise debugging übergeben, damit die Nachricht erhalten bleibt.
SaveAndClose(debugging);
function Debugging: Boolean;
Liefert in der Testumgebung (Client/Kanaleditor) true zurück und im Produktivbetrieb false. Damit kann das Script Testlogik (z.B. zusätzliche Ausgaben) bedingt aktivieren.
if Debugging then writeln('PID = ' + aPID);
procedure WriteLn(content: Variant);
Schreibt eine Zeile mit dem angegebenen Inhalt in die Log-Datei und in die Debug-Ausgabe. Im Produktivbetrieb erscheint die Zeile im Protokoll.
writeln('Nachricht verarbeitet für: ' + aLastName);
procedure Debug(content: Variant);
Schreibt eine Zeile nur in die Debug-Ausgabe (Konsole des Kanaleditors). Im Produktivbetrieb wirkungslos.
debug(aSegment + ' - Feld 3: ' + GetData('3-1-1-1'));
Hilfsfunktionen
function StrToDateTime(const s, format: string): TDateTime;
Wandelt eine Zeitangabe im angegebenen Format in einen Zeittypen um. Format-Platzhalter: yyyy, mm, dd, hh, nn (Minuten), ss. Bei nicht parsebarem Inhalt wird 0 zurückgegeben.
aBirthDate := StrToDateTime(GetData('7-1-1-1'), 'yyyymmdd');
aTimeStamp := StrToDateTime(GetData('7-1-1-1'), 'yyyymmddhhnn');
function RemoveText(const Source, ToBeRemoved: String): String;
Beseitigt alle Vorkommen einer Teilzeichenkette aus einem String.
s := RemoveText(GetData('5-1-1-1'), 'Dr. ');
function ReplaceText(const Source, ToBeReplaced, ReplaceWith: String): String;
Ersetzt alle Vorkommen einer Teilzeichenkette in einem String durch eine andere.
s := ReplaceText(aText, ',', '.');
function WhichIdentifier(const NewPID, OldPID, firstname, lastname: String): String;
Hilfsfunktion für die Umstellung eines KIS-Systems auf neue Patientennummern. Wenn unter der neuen PID bereits eine Akte mit passendem Namen existiert, wird NewPID geliefert; andernfalls wird auf OldPID zurückgegriffen, sofern dort eine Akte existiert. Sonst immer NewPID.
aPID := WhichIdentifier(GetData('3-1-1-1'), GetData('3-2-1-1'), aFirstName, aLastName);
Patientenakte suchen, anlegen, zusammenführen
function FindSubject(const PID: String): Boolean;
Prüft, ob es einen Patienteneintrag unter der angegebenen PID (KIS-ID) gibt. Wichtig für die Laborzuordnung, um keine unnötigen Akten anzulegen. Wenn die Funktion true liefert, arbeiten alle folgenden Funktionen auf das gefundene Subject.
if not FindSubject(aPID) then exit;
function FindSubjectWithBirthdate(const PID: String; birthdate: TDateTime): Boolean;
Arbeitet wie FindSubject, prüft aber zusätzlich das Geburtsdatum. Kann Zuordnungen über die PID sicherer machen.
if not FindSubjectWithBirthdate(aPID, aBirthDate) then exit;
function FindOrCreateSubject(const PID, firstname, lastname: String; birthdate: TDateTime): Boolean;
Sucht einen Patienteneintrag und legt ihn gegebenenfalls an. PID ist die KIS-ID! Alle folgenden Aktionen beziehen sich auf diesen Patienten. Liefert false, wenn die Akte nicht gesperrt werden kann (z.B. weil sie gerade bearbeitet wird).
if not FindOrCreateSubject(aPID, aFirstname, aLastName, aBirthDate) then exit;
procedure MergeSubject(const aObsoleteKISID: String);
Kopiert die Daten der mit aObsoleteKISID bezeichneten Akte (KIS-ID) zur aktuellen, über FindSubject bzw. FindOrCreateSubject aktivierten Akte. Stammdaten werden zusammengeführt, bei Items werden nur neuere ins Ziel kopiert. Die abgebende Akte wird archiviert. Im Script kann dies direkt aus dem MRG-Segment von Nachrichten wie A34 und A40 aufgerufen werden. Es wird geprüft, ob Geburtsdatum und wenigstens Vor- oder Nachname zusammenpassen, sonst wird das Kommando abgebrochen.
if (aSegment = 'mrg') and (GetData('1-1-1-1') <> '') then
MergeSubject(GetData('1-1-1-1'));
Stammdaten und Versicherung
procedure SetSubjectInfo(const aField: String; aContent: variant);
Setzt ein Feld des Stammdatenblattes über seinen Feldnamen. Gängige Feldnamen sind z.B. lastname, firstname, street, zip, city, phone, email.
setSubjectInfo('lastname', aLastName);
procedure SetSubjectInfoIfEmpty(const aField: String; aContent: variant);
Wie SetSubjectInfo, aber überschreibt nur, wenn das Zielfeld leer ist. Damit bleiben manuell gepflegte Daten erhalten.
setSubjectInfoIfEmpty('phone', GetData('13-1-1-1'));
procedure SetGender(const aValue: String);
Setzt das Geschlecht der aktiven Akte. Übliche Werte: M, W, D.
SetGender(GetData('8-1-1-1'));
procedure SetBirthDate(aValue: TDateTime);
Setzt das Geburtsdatum der aktiven Akte.
SetBirthDate(StrToDateTime(GetData('7-1-1-1'), 'yyyymmdd'));
procedure SetASVStatus(enrolled: Boolean);
Setzt den ASV-Status der aktiven Akte. Das Feld ASV-Status ist per Vorgabe ausgeblendet und muss im Datendictionary sichtbar gemacht werden.
SetASVStatus(true);
procedure AddRelatives(const Line: String);
Fügt eine Zeile zum Stammdaten-Feld Angehörige hinzu. Bestehende Zeilen bleiben erhalten.
AddRelatives('Ehepartner: ' + GetData('3-1-1-1'));
procedure AddDiagnoses(const Line: String);
Fügt eine Zeile zu Dauerdiagnosen hinzu, wenn diese noch nicht enthalten ist bzw. der ICD-10-Code noch nicht in den Dauerdiagnosen vorkommt.
AddDiagnoses('M05.9 G - Rheumatoide Arthritis');
procedure SetInsurance(const IK, aInsNum, MemberNum, InsKind, DMP, VIP, WOP: String);
Setzt die Versicherteninformationen. Vorherige Daten werden ersetzt. Die Versicherung wird wahlweise über das IK oder über die Nummer der Versicherung ermittelt; der Name wird automatisch aus den offiziellen Kostenträger-Stammdaten ausgefüllt, die mit den Programmaktualisierungen mitgepflegt werden.
SetInsurance(GetData('3-1-1-1'), '', GetData('9-1-1-1'), '1', '', '', '');
Verlaufsdaten und Items
function FindOrCreateVisit(aTimeStamp: TDateTime): Boolean;
Sucht oder erstellt einen Verlaufsdaten-Visit zum aktuellen Patienten. Die Uhrzeit des Zeitstempels wird nicht beachtet - so entsteht nie mehr als ein Visit pro Tag. Dieser Visit ist anschließend Basis für alle Aufrufe von InsertOrUpdateItem mit Verlaufsdaten-Items.
if FindOrCreateVisit(aVisitDate) then
InsertOrUpdateItem(12345, '160', true);
function FindOrCreateVisitWithTime(aTimeStamp: TDateTime): Boolean;
Wie FindOrCreateVisit, beachtet aber die Uhrzeit - liefert also bei mehreren Zeitstempeln am selben Tag mehrere Visits.
if FindOrCreateVisitWithTime(aVisitDateTime) then ...
procedure InsertOrUpdateItem(aTypeID: Int64; const aStringValue: String; updateExisting: Boolean);
Fügt ein Item ein oder aktualisiert ein vorhandenes, wenn updateExisting wahr ist. Der Wert wird als aStringValue angegeben und wie eine Eingabe über das Eingabegitter in EMIL behandelt. Wenn der angegebene Typ ein Feste-Daten-Item ist, wird es dort verortet und nicht am letzten durch FindOrCreateVisit erzeugten Visit. Labordaten werden hier ignoriert - diese sind über SetOrLearnResult anzulegen.
InsertOrUpdateItem(40001, GetData('5-1-1-1'), true);
Befunde / Labor
procedure SetOrLearnResult(aDate: TDateTime; const aIdent, aValue, aRange, aUnit, aTitle: String);
Setzt ein Befundergebnis. Wird der Ident keinem EMIL-Feld zugeordnet, wird er gelernt und steht künftig zur Zuordnung bereit.
SetOrLearnResult(aResultDate, aResultIdent, aResultValue,
aResultRange, aResultUnit, aResultTitle);
procedure SetOrLearnOrderResult(aDate: TDateTime; const aIdent, aValue, aRange, aUnit, aTitle, aOrderNum: String);
Analog zu SetOrLearnResult, jedoch mit Auftragsnummer. Dies dient für Fälle, in denen sich die gelieferten Zeitstempel eines Laborauftrags zwischen Initialbefund und Nachlieferung unterscheiden können und Nachlieferungen sonst in einer falschen Spalte landen würden. EMIL speichert mit dem ersten Wert der Auftragsnummer das Datum und nutzt es bei allen folgenden Befunden der Auftragsnummer wieder. Aus Sicherheitsgründen spricht dieser Mechanismus nur an, wenn der gespeicherte und der gelieferte Zeitstempel weniger als 24 h auseinanderliegen. Die Nutzung der Funktion setzt voraus, dass sich Auftragsnummern nie wiederholen! Ggf. sind im Script Merkmale hinzuzufügen, um sie eindeutig zu machen.
SetOrLearnOrderResult(aResultDate, aResultIdent, aResultValue,
aResultRange, aResultUnit, aResultTitle, aOrderNum);