FHIR Server (+)

<< Klicken Sie, um das Inhaltsverzeichnis anzuzeigen >>

Navigation:  Schnittstellen >

FHIR Server (+)

EMMA erweitert EMIL um einen FHIR-R4-Server, über den externe Systeme (Klinik-Middleware, Statistik-Software, Studien-Datenbanken, Patienten-Apps) lesend und schreibend auf den Patientenbestand zugreifen können. Der Zugriff ist OAuth2-geschützt; pro zugreifendem System wird ein eigener Client mit individuellem Secret eingerichtet. Sie finden die Einstellungen unter Administration|Schnittstellen.

clip1109

1. Client-Liste — alle aktuell eingerichteten FHIR-Clients. Jede Zelle ist direkt editierbar; Änderungen werden ohne Bestätigung sofort gespeichert. Spalten:

Client-ID — eindeutiger technischer Name, den der Client beim Login (client_id) verwendet. Konvention: Kleinbuchstaben, Bindestriche, sprechend („klinik-meinhof", „rhdv-statistik").

AktivJa: Client kann sich anmelden. Nein: Login wird abgelehnt (HTTP 403). Die Zeile bleibt erhalten, der Zugriff ist nur temporär ausgesetzt.

AnonymJa: alle Patient-Resourcen, die dieser Client abruft, werden ohne Klartextnamen, Geburtsdatum und externe Identifier (KIS-ID, PVS-ID etc.) ausgeliefert. Pseudonyme (interne Subject-ID, Studien-Patient-IDs) bleiben enthalten, damit Datensätze referenzierbar bleiben. Nein: Patientendaten kommen mit allen Klartextangaben.

Einschränken auf StudieKeine Einschränkung: Client darf den gesamten Patientenbestand sehen („Statistiker-Modus"). Oder Auswahl einer konkreten Studie: Client sieht ausschließlich die enrollten Subjects dieser Studie, und auch in der Patient-Resource erscheint nur der Studien-Identifier dieser einen Studie.

Consent — relevant nur bei aktivierter Studien-Restriktion. Ja: Aus den enrollten Subjects der Studie werden zusätzlich nur die ausgeliefert, deren Einverständnis (consent_flag) gesetzt ist. Nein: alle enrollten Subjects der Studie, der aktuelle Consent-Status wird über separate Consent-Resourcen mitgeteilt. Hartes Ausschluss-Kriterium ist immer refuse_flag — wer aktiv widersprochen hat, wird unter keinen Umständen exportiert.

Erlaubte ItemTypes (inbound) — Whitelist der ItemType-IDs, die dieser Client per POST/PUT schreiben darf. Default ist leer = kein Schreibrecht. Per Klick öffnet sich ein Auswahldialog mit allen dokumentierten ItemTypes (alphabetisch, mit Suche und Bereichsangabe in Klammern). Reine Berechnungs-Felder (BMI etc.) erscheinen nicht in der Liste, weil sie der Server selbst berechnet und nicht direkt überschrieben werden dürfen. Die Anzeige in der Zelle zeigt eine Kurzform „N Items: Titel1, Titel2, Titel3, …".

clip1107

2. Hinzufügen — Legt einen neuen Client an. Sie werden zur Eingabe einer Client-ID aufgefordert; diese wird als client_id verwendet. Anschließend generiert EMIL ein 48-Zeichen-Zufalls-Secret und zeigt es einmalig in einem Dialog zum Kopieren. Bewahren Sie das Secret sicher auf — im Klartext wird es nie wieder angezeigt, der Server kennt nur den SHA-3-Hash.

clip1106

3. Neues Secret — Erzeugt für den in der Tabelle ausgewählten Client ein neues Secret. Das vorhergehende Secret wird unmittelbar ungültig. Anwendung z. B. bei Verlust oder turnusmäßiger Erneuerung.

4. Löschen — Entfernt den ausgewählten Client unwiderruflich nach Bestätigung. Bereits erteilte Tokens bleiben bis zu ihrer Ablaufzeit (Default eine Stunde) noch gültig.

Audit-Trail. Jeder FHIR-Client wird im EMIL-Audit-Trail wie ein interner Benutzer behandelt: jede Schreibaktion erscheint als „FHIR: <Client-ID>" in den Historien-Einträgen (Subject – History) und in den Datenbank-Audit-Views. Bei der Rückverfolgung von Änderungen sieht der Admin auf einen Blick, welcher Client wann was geschrieben hat.

Schnelleinstieg für Entwickler

Alle Beispiele unten sind generische HTTP-Aufrufe. Sie funktionieren mit jedem HTTP-Werkzeug: curl auf der Kommandozeile, Postman, Insomnia, HTTPie oder jeder Sprache mit HTTP-Client. Die Beispiele zeigen den ausgeführten Request inkl. ggf. Body; ersetzen Sie EMIL-Server:8443 durch Ihren tatsächlichen EMIL-Host plus Port und <token> durch den von Schritt 1 erhaltenen Bearer-Token.  

Schritt 1: Anmeldung (OAuth2 client_credentials)

Vor jedem Datenzugriff besorgt sich der Client einen Bearer-Token vom Token-Endpoint:

POST https://EMIL-Server:8443/fhir/token

Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=klinik-meinhof&client_secret=<plain-secret>

Antwort:

{

"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",

"token_type": "Bearer",

"expires_in": 3600

}

Der Token wird in den Folgeanfragen als HTTP-Header mitgegeben:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Tokens sind Default eine Stunde gültig und werden vom Server selbst signiert (HMAC-SHA-256); eine Online-Verifikation durch Drittsysteme ist nicht erforderlich. Bei Ablauf antwortet der Server mit 401 und WWW-Authenticate: Bearer error="invalid_token" — einfach einen neuen Token holen.

Hinweis — Änderungen in der Konfiguration: Aktiv, Anonym, Studie und Consent sind im Token selbst eingebrannt — dafür ist nach Änderung ein neuer Token nötig. Erlaubte ItemTypes dagegen wird bei jedem Schreibvorgang frisch aus der Datenbank gelesen und greift sofort, auch mit altem Token.

Schritt 2: Was kann der Server? — das Manifest

Der Endpunkt /fhir/metadata liefert eine maschinenlesbare Beschreibung aller verfügbaren Endpunkte, Suchparameter und Operationen — in FHIR-Sprache heißt das ein CapabilityStatement. Wer noch nie mit einem FHIR-Server gesprochen hat, sollte hier anfangen:

GET https://EMIL-Server:8443/fhir/metadata

Accept: application/fhir+json

Kein Auth-Header nötig — das Manifest ist öffentlich, damit Clients die Schnittstelle entdecken können, bevor sie sich anmelden.

Auszug aus einer typischen Antwort:

{

"resourceType": "CapabilityStatement",

"status": "active",

"fhirVersion": "4.0.1",

"format": ["json"],

"software": { "name": "EMIL FHIR Server", "version": "8.40.0" },

"implementation":{ "description": "EMIL FHIR Endpoint (Build 8.40.0)",

"url": "https://EMIL-Server:8443/fhir" },

"rest": [{

"mode": "server",

"security": {

"cors": true,

"service": [{ "coding": [{ "code": "OAuth", "display": "OAuth2 client_credentials" }] }],

"extension": [{ "url": "...oauth-uris",

"extension": [{ "url": "token",

"valueUri": "https://EMIL-Server:8443/fhir/token" }] }]

},

"resource": [

{ "type": "Patient",

"interaction": [{ "code": "read" }, { "code": "search-type" }],

"searchParam": [{ "name": "identifier", "type": "token" },

{ "name": "family", "type": "string" },

{ "name": "given", "type": "string" },

{ "name": "name", "type": "string" }]

},

{ "type": "Observation",

"interaction": [{ "code": "create" }, { "code": "update" }]

},

{ "type": "QuestionnaireResponse",

"interaction": [{ "code": "create" }, { "code": "update" }]

},

{ "type": "Questionnaire", "interaction": [{ "code": "read" }] },

{ "type": "CodeSystem", "interaction": [{ "code": "read" }] }

],

"operation": [{ "name": "bundle", "definition": "...OperationDefinition/system-bundle" }]

}]

}

Was Sie daraus lesen können:

Token-URLrest[0].security.extension[].extension[?(url=='token')].valueUri. Hier holen Sie sich Schritt 1.

Welche Resourcen lese-/schreibbar sind — jeder resource[]-Eintrag mit seinen interaction[]-Codes: read, search-type, create, update.

Welche Such-Parameter es für eine Resource gibt — resource[].searchParam[].

System-Operationen wie $bundleoperation[].

Server-Versionsoftware.version. Bei Support-Anfragen immer mit angeben.

Jeder Eintrag trägt zudem eine documentation-Property mit ausführlicher Erklärung (Mapping-Regeln, Whitelist-Verhalten, Default-Werte etc.) — den Inhalt dieser Hilfe finden Sie dort in Kurzform direkt am Server.

SMART-Discovery. Alternativ liefert GET /fhir/.well-known/smart-configuration eine kompaktere Variante speziell für OAuth-Clients (Token-URL, unterstützte Grant-Types). Standardisiert nach „SMART on FHIR".

Schritt 3: Was bedeuten die internen IDs? — CodeSystems

EMIL-Resourcen verweisen über numerische IDs auf interne Konzepte: ein ItemType ist z. B. 10038 = Körpergewicht, eine Studie ist 1850 = irgendwas, ein Medikament ist 74211 = Methotrexat. Die Bedeutung dieser IDs wird über vier CodeSystem-Resourcen aufgelöst:

GET /fhir/CodeSystem/itemtype # ItemType-Wörterbuch (Felder)

GET /fhir/CodeSystem/study # Studienkatalog

GET /fhir/CodeSystem/drug # Medikamentenkatalog

GET /fhir/CodeSystem/stop-reason # Absetzgründe (1..10)

GET /fhir/CodeSystem/image-category # Bild-Kategorien (IMAGE_CAT)

Alle CodeSystems unterstützen Paging via _count (Default 1000) und _offset (Default 0). Bei geschnittenen Antworten liefert der Server zusätzlich meta.tag mit code="partial" und einen Link-Header mit rel="next"/"prev"/"first"/"last". Die Gesamtanzahl steht immer in der Top-Level-Property count (unabhängig vom Slice).

CodeSystem/itemtype — Felder-Wörterbuch

Das wichtigste CodeSystem: hier finden Sie für jeden EMIL-ItemType (also jedes Feld, das in einer Patientenakte vorkommen kann) Titel, Datentyp und ggf. die Auswahlwerte. Beispiel-Aufruf und gekürzte Antwort:

GET /fhir/CodeSystem/itemtype?_count=3

Authorization: Bearer <token>

{

"resourceType": "CodeSystem",

"id": "itemtype",

"url": "https://emil.itc-ms.de/fhir/sid/itemtype",

"name": "EMILItemType",

"status": "active",

"content": "complete",

"count": 2483,

"property": [

{ "code": "area", "type": "code", "description": "L=laboratory, F=fixed/master data, V=visits" },

{ "code": "unit", "type": "string", "description": "Display unit (UCUM-aligned where applicable)" },

{ "code": "variable", "type": "string", "description": "Internal variable name used in EMIL formulas" },

{ "code": "kind", "type": "code", "description": "Item kind. Determines value cardinality..." },

{ "code": "enabled", "type": "boolean","description": "Only set on child concepts (coded values)..." }

],

"concept": [

{ "code": "10038",

"display": "Körpergewicht",

"property": [ { "code": "area", "valueCode": "V" },

{ "code": "unit", "valueString": "kg" },

{ "code": "variable", "valueString": "weight" },

{ "code": "kind", "valueCode": "number" } ]

},

{ "code": "10042",

"display": "BMI",

"property": [ { "code": "area", "valueCode": "V" },

{ "code": "unit", "valueString": "kg/m2" },

{ "code": "kind", "valueCode": "calc" } ]

},

{ "code": "12345",

"display": "Allergien",

"property": [ { "code": "area", "valueCode": "F" },

{ "code": "kind", "valueCode": "mlist" } ],

"concept": [

{ "code": "12345/POLLEN", "display": "Pollen" },

{ "code": "12345/PEANUT", "display": "Erdnüsse" },

{ "code": "12345/PENICILLIN", "display": "Penicillin",

"property": [ { "code": "enabled", "valueBoolean": false } ] }

]

}

]

}

So lesen Sie das:

concept[].code ist die ItemType-ID. Damit bauen Sie in Observation/QuestionnaireResponse die code.coding[].code-Felder.

property/kind sagt, was für ein Feldtyp das ist und wie der Wert auszusehen hat: number/numberuObservation.valueQuantity mit der Einheit aus property/unit. slistObservation.valueCodeableConcept mit genau einem coding[]. mlistObservation.valueCodeableConcept mit einem coding[] pro gewählter Option. text und Varianten → valueString. date/datefuz/datefuzm (+ u-Varianten) → valueString mit dem Datum als ISO-String. form/calcform/custform/custhform → eigene QuestionnaireResponse statt Observation. calc → berechnetes Feld, nicht direkt schreibbar. Der Server berechnet es selbst aus seinen Quell-Items.

concept[].concept[] — verschachtelte Sub-Concepts. Nur bei kind=slist/mlist: die zulässigen Auswahlwerte. Der Sub-Code hat die Form <typeid>/<rawcode>genau diese Form erscheint später in der Observation. Historische Werte (im EMIL als ausgeblendet markiert) tragen property/enabled = false.

property/area: L = Labor, F = Stammdaten (einmalig pro Patient), V = Verlauf (pro Visit). Bestimmt, in welche Art von Encounter der Wert geschrieben werden muss (Stammdaten landen automatisch in der Fixed-Visit).

property/unit: die EMIL-Stamm-Einheit. Bei Inbound müssen Sie den Wert in dieser Einheit liefern; eine andere Einheit führt zu einem 400er-Fehler.

CodeSystem/study — Studienkatalog

GET /fhir/CodeSystem/study

Authorization: Bearer <token>

{

"resourceType": "CodeSystem",

"id": "study",

"url": "https://emil.itc-ms.de/fhir/sid/study",

"name": "EMILStudy",

"status": "active",

"count": 47,

"concept": [

{ "code": "1850", "display": "RhDV Erwachsene 2024" },

{ "code": "1851", "display": "RABBIT" },

{ "code": "1852", "display": "Kids-RhDV" }

]

}

Die code-Werte sind die internen Studien-IDs, die Sie z. B. in $bundle?study=<id> oder in der Konfiguration des Clients (Einschränken auf Studie) verwenden. Studien-restricted Clients sehen nur Studien, in denen ihre Patienten enrollt sind.

CodeSystem/drug — Medikamentenkatalog

GET /fhir/CodeSystem/drug?_count=5&_offset=0

Authorization: Bearer <token>

{

"resourceType": "CodeSystem",

"id": "drug",

"url": "https://emil.itc-ms.de/fhir/sid/drug",

"name": "EMILDrug",

"status": "active",

"count": 3214,

"concept": [

{ "code": "-1", "display": "Glukokortikoide" },

{ "code": "74211", "display": "Methotrexat" },

{ "code": "74212", "display": "Sulfasalazin" },

{ "code": "74213", "display": "Leflunomid" }

]

}

Wird referenziert von MedicationStatement.medicationCodeableConcept. Der Sonderfall -1 „Glukokortikoide" deckt die strukturierte Kortison-Dokumentation ab (s. Abschnitt MedicationStatement).

CodeSystem/stop-reason — Absetzgründe

GET /fhir/CodeSystem/stop-reason

Authorization: Bearer <token>

{

"resourceType": "CodeSystem",

"id": "stop-reason",

"url": "https://emil.itc-ms.de/fhir/sid/stop-reason",

"status": "active",

"count": 10,

"concept": [

{ "code": "1", "display": "Remission" },

{ "code": "2", "display": "Nebenwirkung" },

{ "code": "3", "display": "Unzureichende Wirkung" },

{ "code": "4", "display": "Kontraindikation" },

{ "code": "5", "display": "Patientenwunsch / Kinderwunsch / Schwangerschaft" },

{ "code": "6", "display": "Compliance" },

{ "code": "7", "display": "Kosten" },

{ "code": "8", "display": "Sonstiges" },

{ "code": "9", "display": "Wirkverlust" },

{ "code": "10", "display": "Fehlendes Ansprechen" }

]

}

Wird referenziert von MedicationStatement.statusReason[].coding[] bei abgesetzten Medikationen.

CodeSystem/image-category — Bild-Kategorien

Die in EMIL gepflegten Bild-Kategorien (Tabelle IMAGE_CAT), auf die Media.modality verweist.

GET /fhir/CodeSystem/image-category

Authorization: Bearer <token>

{

"resourceType": "CodeSystem",

"id": "image-category",

"url": "https://emil.itc-ms.de/fhir/sid/image-category",

"status": "active",

"count": 6,

"concept": [

{ "code": "1", "display": "Befund" },

{ "code": "3", "display": "Röntgen" }

]

}

Lesen

Patient suchen

Bevor Sie etwas zu einem Patienten schreiben können, müssen Sie dessen interne EMIL-Subject-ID kennen. Diese ermittelt man über die Patient-Suche, mit beliebigen Identifiern aus den verknüpften Systemen:

GET /fhir/Patient?identifier=K12345

GET /fhir/Patient?identifier=https://emil.itc-ms.de/fhir/sid/kis|K12345

GET /fhir/Patient?family=mustermann

GET /fhir/Patient?name=schum&given=micha&_count=20

Die Antwort ist ein Bundle vom Typ searchset:

{

"resourceType": "Bundle",

"type": "searchset",

"total": 1,

"entry": [{

"resource": {

"resourceType": "Patient",

"id": "42",

"identifier": [

{ "system": "https://emil.itc-ms.de/fhir/sid/subject", "value": "42" },

{ "system": "https://emil.itc-ms.de/fhir/sid/kis", "value": "K12345" }

],

"name": [{ "use": "official", "family": "Mustermann", "given": ["Max"] }],

"birthDate": "1965-03-12",

"gender": "male"

}

}]

}

Die id (hier 42) ist die EMIL-Subject-ID, die Sie später in Schreibvorgängen als subject.reference: "Patient/42" verwenden. Wenn Ihr Client studien-restricted ist oder das Anonym-Flag gesetzt hat, sehen Sie nur die jeweils erlaubten Daten.

Identifier-Suche akzeptiert mit oder ohne system|-Präfix; intern probiert der Server alle bekannten Quellen durch (interne ID, KIS/PVS/OTHER/PAPER, alle Studien-Pseudonyme).

Patient einzeln lesen

GET /fhir/Patient/42

Authorization: Bearer <token>

Liefert dieselbe Patient-Resource wie im Such-Bundle, aber direkt — nützlich, wenn die ID bereits bekannt ist.

Questionnaire-Definition lesen

Pro Form-ItemType (Score/Custom-Form) gibt es eine schmale Questionnaire-Resource, die per Coding-Link auf den Dictionary-Eintrag im CodeSystem/itemtype verweist. Die Feldsemantik (linkIds, Skalen, Wertelisten) wird zentral in der EMIL-Dictionary-Dokumentation gepflegt:

GET /fhir/Questionnaire/52007

Authorization: Bearer <token>

Patientenbilder lesen (Media / Binary)

Bilder aus der Patientenakte (eingescannte Befunde, Fotos; EMIL-Tabelle IMAGE) werden als FHIR-Media-Resourcen bereitgestellt. Media liefert nur die Metadaten (Datum, Kategorie, Titel) und verweist über content.url auf den eigentlichen Bild-Download (Binary). Die Bytes werden also erst geladen, wenn der Consumer den Link gezielt abruft — eine Galerie-Übersicht bleibt damit schlank. Archivierte Bilder werden nie ausgeliefert.

GET /fhir/Media?patient=42

Authorization: Bearer <token>

Optional einschränkbar über &category=<cat-id> (IMAGE_CAT, siehe CodeSystem/image-category) und &date=ge2024-01-01 / &date=le2024-12-31; Paging via _count (Default 50) und _offset. Die Antwort ist ein searchset-Bundle; ein einzelnes Bild liefert GET /fhir/Media/<image-id>.

{

"resourceType": "Media",

"id": "1007",

"status": "completed",

"type": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/media-type", "code": "image" }] },

"modality": { "coding": [{ "system": "https://emil.itc-ms.de/fhir/sid/image-category", "code": "3", "display": "Röntgen" }] },

"subject": { "reference": "Patient/42" },

"createdDateTime": "2024-03-15",

"content": {

"contentType": "image/jpeg",

"url": "https://EMIL-Server:8443/fhir/Binary/1007",

"title": "Thorax p.a."

}

}

Bild-Bytes laden (Binary)

Die in content.url genannte URL liefert die rohen Bild-Bytes:

GET /fhir/Binary/1007

Authorization: Bearer <token>

Antwort: 200 OK mit Content-Type: image/jpeg und dem JPEG im Body. Studien-restricted Clients sehen nur Bilder von Subjects ihrer Studie; ein fremdes Subject ergibt 404. Fehlt die Bilddatei trotz Datenbankeintrag, antwortet der Server ebenfalls mit 404.

Anonyme Clients: Bei gesetztem Anonym-Flag bleiben die Media-Metadaten sichtbar, der Byte-Abruf über Binary wird aber mit 403 abgelehnt — Bilder sind potenziell identifizierend.

Bundle-Export einer Kohorte ($bundle)

Der Haupt-Endpunkt für Massen-Exporte. Liefert pro Subject die vollständigen Items als Observation (für Lab/Visit-Werte) bzw. QuestionnaireResponse (für Form-Items), gruppiert über Encounter-Resourcen pro Visit. Zu jedem emittierten Encounter zusätzlich ein MedicationStatement-Snapshot pro Präparat. Auf der ersten Seite zusätzlich ResearchStudy-Resourcen und je Subject ResearchSubject. Paging durch _count (Default 50 Subjects pro Seite) und _offset.

MedicationStatement pro Encounter

Pro Visit emittiert der Server für jedes Präparat im damaligen Therapieplan ein MedicationStatement (Quelle: die rheumatologische Medikationshistorie). So sind Folgeverordnungen, Dosisänderungen und Absetzungen in jedem Encounter eindeutig nachvollziehbar.

status = active bei laufender Anwendung, stopped bei Absetzung.

medicationCodeableConcept mit Coding aus dem drug-CodeSystem; id=-1 ist der Sonderfall „Glukokortikoide".

context referenziert den Encounter.

dateAsserted = Datum der letzten Therapieänderung, effectivePeriod aus FBeginDate/FEndDate sofern geführt.

dosage[].text = klartext-Dosis + Applikationsart (nur bei aktiven Präparaten).

statusReason[] bei Absetzungen: alle gesetzten Gründe als coding aus dem stop-reason-CodeSystem (Mehrfachnennung möglich). Eingegebene Freitexte zu Nebenwirkung/Kontraindikation/Sonstiges überschreiben das display.

Beispiel 1 — Voller Kohorten-Export einer Studie

GET /fhir/$bundle?study=1850

Authorization: Bearer <token>

Liefert alle enrolled Subjects der Studie 1850 mit allen ihren Items.

Beispiel 2 — Eingeschränkt auf bestimmte ItemTypes

GET /fhir/$bundle?study=1850&type=52007,52014&from=2024-01-01

Nur die Items dieser zwei TypeIDs aus Visits ab 2024-01-01.

Beispiel 3 — Mit Consent-Resourcen

GET /fhir/$bundle?study=1850&consent=1

Wie Beispiel 1, zusätzlich pro Subject eine Consent-Resource mit dem aktuellen Status (active/rejected/inactive).

Beispiel 4 — Einzelne Patienten gezielt

GET /fhir/$bundle?subject=42,17,8

Drei konkret benannte Patienten. Nur erlaubt für unrestricted Clients (Statistiker-Modus).

Beispiel 5 — Paging einer großen Kohorte

GET /fhir/$bundle?study=1850&_count=20&_offset=0

GET /fhir/$bundle?study=1850&_count=20&_offset=20

Der HTTP-Header Link: rel="next" und Bundle.link[] liefern die nächste/vorige Seite vorgefertigt.

Schreiben (Inbound)

Der FHIR-Server akzeptiert eingehende Daten für Observation (Einzelwerte) und QuestionnaireResponse (Formulare). Drei wichtige Eigenschaften vorweg:

1.Patienten werden nicht angelegt. Stammdaten werden ausschließlich in EMIL gepflegt. POST /Patient und PUT /Patient/<id> antworten beide mit 405 Method Not Allowed. Bitte zuerst per GET /Patient?identifier=... die interne ID ermitteln.

2.Whitelist pro Client. Geschrieben werden dürfen nur ItemType-IDs, die in der Spalte Erlaubte ItemTypes des Clients freigegeben sind. Default ist leer = kein Schreibrecht.

3.Idempotent. Ein POST /Observation mit (Subject, Visit-Datum, ItemType), zu dem schon ein Item existiert, überschreibt das bestehende. Mehrfaches Senden desselben Bodys ist sicher.

Observation schreiben

Komplettes Schritt-für-Schritt-Beispiel: Körpergewicht (TypeID 10038)

Angenommen, Sie wollen für einen Patienten an einem bestimmten Datum das Körpergewicht eintragen. Vorgehen:

Schritt A — ItemType nachschlagen

GET /fhir/CodeSystem/itemtype/?_count=2000

Im Ergebnis nach „Körpergewicht" suchen (oder den ItemType direkt in der EMIL-Dictionary-Doku nachsehen):

{ "code": "10038",

"display": "Körpergewicht",

"property": [

{ "code": "area", "valueCode": "V" },

{ "code": "unit", "valueString": "kg" },

{ "code": "variable", "valueString": "weight" },

{ "code": "kind", "valueCode": "number" }

]

}

Das sagt Ihnen: ItemType 10038, Area „V" (Visit), Einheit kg, Datentyp number. Der Wert muss also als valueQuantity mit Einheit kg kommen.

Schritt B — ItemType in der EMIL-Schnittstellen-Konfig freischalten

In der GUI: Einstellungen → Schnittstellen → FHIR. Beim betreffenden Client in der Spalte Erlaubte ItemTypes klicken, im Picker „Körpergewicht" auswählen, OK. Wirkt sofort, auch ohne neuen Token.

Schritt C — Patient-ID besorgen

GET /fhir/Patient?identifier=K12345

Authorization: Bearer <token>

→ aus der Antwort die id entnehmen, z. B. 42.

Schritt D — Observation senden

POST /fhir/Observation

Authorization: Bearer <token>

Content-Type: application/fhir+json

{

"resourceType": "Observation",

"status": "final",

"subject": { "reference": "Patient/42" },

"code": {

"coding": [{

"system": "https://emil.itc-ms.de/fhir/sid/itemtype",

"code": "10038"

}]

},

"effectiveDateTime": "2026-05-17",

"valueQuantity": {

"value": 72.5,

"unit": "kg",

"system": "http://unitsofmeasure.org"

}

}

Antwort: 201 Created, Header Location: .../Observation/<itemid> und Body:

{

"resourceType": "OperationOutcome",

"issue": [{

"severity": "information",

"code": "informational",

"diagnostics": "Observation gespeichert: Patient/42, Encounter/142862, Observation/2247369."

}]

}

Die EMIL-Item-ID (hier 2247369) können Sie für spätere Updates (per PUT) merken. Der Encounter (142862) wurde entweder an dem Datum gefunden oder neu angelegt. Sofort danach erscheint der Wert im EMIL-Client; im Audit-Trail steht „FHIR: <Client-ID>" als Urheber.

Visit-Auflösung

Wenn keine encounter.reference angegeben ist, sucht der Server eine vorhandene Visit am effectiveDateTime-Datum für das Subject (passender Area, also V für normale Visits, L für Labor, F für Stammdaten). Wenn keine existiert, wird sie automatisch angelegt. Mehrere Observations zum selben Datum landen damit in derselben Visit.

Alternativ können Sie eine bestehende Visit gezielt referenzieren (sofern Sie deren ID kennen, z. B. aus einem vorherigen Bundle-Read):

"encounter": { "reference": "Encounter/142862" }

Coded Value (SLIST/MLIST)

Für ItemTypes mit kind=slist oder kind=mlist: Body verwendet valueCodeableConcept mit einem oder mehreren coding[]-Einträgen. Die Codes haben die Form <typeid>/<rawcode> wie im CodeSystem dokumentiert (das $-Präfix aus der EMIL-Datenbank wird NICHT mitgeschickt).

Beispiel für ein Multi-Select „Allergien" (TypeID 12345) mit zwei gewählten Codes:

POST /fhir/Observation

Authorization: Bearer <token>

Content-Type: application/fhir+json

{

"resourceType": "Observation",

"status": "final",

"subject": { "reference": "Patient/42" },

"code": {

"coding": [{

"system": "https://emil.itc-ms.de/fhir/sid/itemtype",

"code": "12345"

}]

},

"effectiveDateTime": "2026-05-17",

"valueCodeableConcept": {

"coding": [

{ "system": "https://emil.itc-ms.de/fhir/sid/itemtype", "code": "12345/POLLEN" },

{ "system": "https://emil.itc-ms.de/fhir/sid/itemtype", "code": "12345/PEANUT" }

]

}

}

Für kind=slist (Einfachauswahl): genau ein coding[]-Eintrag.

Freitext

POST /fhir/Observation

Authorization: Bearer <token>

Content-Type: application/fhir+json

{

"resourceType": "Observation",

"status": "final",

"subject": { "reference": "Patient/42" },

"code": { "coding": [{ "system": "https://emil.itc-ms.de/fhir/sid/itemtype", "code": "20007" }] },

"effectiveDateTime": "2026-05-17",

"valueString": "Patient klagt über Müdigkeit nach Belastung."

}

Update (PUT)

Den Wert eines bereits geschriebenen Items ändern:

PUT /fhir/Observation/2247369

Authorization: Bearer <token>

Content-Type: application/fhir+json

{ ... gleicher Body wie oben, mit geändertem value ... }

200 OK. Bei nicht existierender ID: 404 Not Found (kein Update-as-Create).

POST oder PUT? Für normale Schreibvorgänge reicht POST — er ist idempotent (gleicher Body → gleicher Endzustand). PUT brauchen Sie nur, wenn Sie explizit ein bestimmtes Item per ID referenzieren wollen.

QuestionnaireResponse schreiben

Für Form-ItemTypes (kind form/calcform/custform/custhform): jedes Subitem wird ein item[]-Eintrag mit linkId + answer[0].value[x]. Schritt-für-Schritt analog zu Observation:

1.ItemType-ID nachschlagen (CodeSystem/itemtype, kind-Property prüfen).

2.In der Schnittstellen-Konfig freischalten.

3.Patient-ID besorgen.

4.POST schicken.

Einfaches Formular (z. B. HAQ-Score, TypeID 52007)

POST /fhir/QuestionnaireResponse

Authorization: Bearer <token>

Content-Type: application/fhir+json

{

"resourceType": "QuestionnaireResponse",

"status": "completed",

"questionnaire": "Questionnaire/52007",

"subject": { "reference": "Patient/42" },

"authored": "2026-05-17",

"item": [

{ "linkId": "anziehen", "answer": [{ "valueInteger": 1 }] },

{ "linkId": "aufstehen", "answer": [{ "valueInteger": 0 }] },

{ "linkId": "essen", "answer": [{ "valueInteger": 2 }] },

{ "linkId": "memo", "answer": [{ "valueString": "leichter Tag" }] }

]

}

Bei kind=calcform berechnet der Server den Score (vor #255 im EMIL-Storage) selbstständig — ein optional mitgesendetes linkId="score" wird beim nächsten Recalc überschrieben.

Multiform: mehrere Einträge in einem Item

Für Form-Items, in denen mehrere strukturierte Einträge nebeneinander stehen (z. B. Medikamentenliste): top-level item[]-Einträge mit linkId="row" und verschachteltem item[] pro Zeile.

POST /fhir/QuestionnaireResponse

Authorization: Bearer <token>

Content-Type: application/fhir+json

{

"resourceType": "QuestionnaireResponse",

"status": "completed",

"questionnaire": "Questionnaire/52126",

"subject": { "reference": "Patient/42" },

"authored": "2026-05-17",

"item": [

{ "linkId": "row", "item": [

{ "linkId": "drug", "answer": [{ "valueString": "Methotrexat" }] },

{ "linkId": "dose", "answer": [{ "valueString": "15" }] },

{ "linkId": "unit", "answer": [{ "valueString": "mg" }] },

{ "linkId": "applic", "answer": [{ "valueString": "SC" }] }

]},

{ "linkId": "row", "item": [

{ "linkId": "drug", "answer": [{ "valueString": "Folsäure" }] },

{ "linkId": "dose", "answer": [{ "valueString": "5" }] },

{ "linkId": "unit", "answer": [{ "valueString": "mg" }] }

]}

]

}

Checkboxen

Checkboxen werden im EMIL-Storage nur emittiert, wenn sie angekreuzt sind. Beim Schreiben:

{ "linkId": "cbprednisolon", "answer": [{ "valueBoolean": true }] }

→ landet als "cbprednisolon": "1" im Storage. false oder Weglassen führt dazu, dass der Eintrag nicht erzeugt wird.

Anonymisierung

Wenn der Client das Anonym-Flag gesetzt hat oder die Anfrage zusätzlich ?anonymize=1 mitgibt, werden alle Patient-Resourcen ohne Klartext-Personendaten ausgeliefert. Pseudonyme (Subject-ID, Studien-Patient-IDs) und alle Items bleiben enthalten — Auswertungen werden so nicht verhindert, der Personenbezug wird aber entfernt.

Für Binary (Bild-Bytes aus der Tabelle IMAGE) gilt zusätzlich: anonyme Clients erhalten die zugehörigen Media-Metadaten, aber nicht die Bilddaten selbst (403), da ein Bild den Personenbezug direkt enthalten kann.

Häufige Fehler

401 „Authorization: Bearer … erforderlich": Kein oder leerer Authorization-Header. Token holen, Header setzen.

401 „Ungültige Credentials": Client-Secret falsch. Notfalls neues Secret über die GUI generieren.

401 „invalid_token" im WWW-Authenticate: Token abgelaufen oder mit falschem Secret signiert. Neuen Token holen.

403 „Client ist deaktiviert": Spalte Aktiv steht auf Nein. In der GUI umstellen.

400 „Client … darf TypeID … nicht schreiben": ItemType nicht in der Erlaubte ItemTypes-Whitelist. Im Picker hinzufügen, dann sofort neu probieren.

400 „berechnetes Feld (kind=calc)": Wert wird vom Server berechnet (z. B. BMI). Schreiben Sie stattdessen die Quell-Items (Größe, Gewicht).

400 „subject.reference muss 'Patient/<id>' sein": Erst GET /Patient?identifier=… machen, die interne ID nehmen.

400 „Subject ist nicht in der studienspezifischen Kohorte": Studien-restricted Client versucht, einen Subject außerhalb seiner Studie zu beschreiben.

400 „Unit … passt nicht zur erwarteten Einheit …": Der Wert muss in der Stamm-Einheit des ItemTypes geliefert werden (siehe property/unit im CodeSystem). Keine automatische Konversion.

404 „Patient/X existiert nicht": Subject-ID falsch — Lookup wiederholen.

404 bei PUT auf Observation/X: Item-ID existiert nicht. Bei Bedarf mit POST neu anlegen (idempotent).

405 bei POST /Patient: Erwartet — Patient-Create ist bewusst gesperrt. Suche nutzen.

403 bei GET /Binary/X: Anonymer Client — Bild-Bytes sind gesperrt (Metadaten über Media bleiben abrufbar).

404 bei GET /Media/X oder /Binary/X: Bild existiert nicht, ist archiviert, liegt außerhalb des Studien-Scopes, oder die Bilddatei fehlt im Dateisystem.

503 „FHIR-Server ist ausgeschaltet": Master-Schalter in der GUI nicht gesetzt. Bis zu 30 Sekunden Cache.