|
<< Klicken Sie, um das Inhaltsverzeichnis anzuzeigen >> Navigation: Schnittstellen > FHIR Server (+) > Lesen und $bundle-Export |
Bevor Sie etwas zu einem Patienten schreiben können, brauchen Sie dessen interne Subject-ID. Diese ermitteln Sie über die Patient-Suche, mit beliebigen Identifiern aus den verknüpften Systemen oder über den Namen:
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 interne Subject-ID, die Sie beim Schreiben als subject.reference: "Patient/42" verwenden. Ist Ihr Client auf eine Studie eingeschränkt oder anonym, sehen Sie nur die jeweils erlaubten Daten.
Die Identifier-Suche akzeptiert Werte mit oder ohne system|-Präfix; der Server probiert alle bekannten Quellen durch (interne ID, KIS/PVS/OTHER/PAPER, alle Studien-Pseudonyme). family, given und name suchen ohne Rücksicht auf Groß-/Kleinschreibung nach Teilstrings (name auch im Geburtsnamen). Suche nach Geburtsdatum oder Geschlecht wird nicht unterstützt. Paging über _count (Standard 50) und _offset.
GET /fhir/Patient/42
Authorization: Bearer <token>
Liefert dieselbe Patient-Ressource wie im Such-Bundle, aber direkt; nützlich, wenn die ID bereits bekannt ist.
Je Form-ItemType (Score oder Formular) gibt es eine schmale Questionnaire-Ressource, die per Coding-Link auf den Eintrag im CodeSystem/itemtype verweist. Eine item[]-Aufzählung enthält sie nicht; die Feldsemantik (linkIds, Skalen, Wertelisten) steht im Datendictionary. Beispiel Gelenkbogen:
GET /fhir/Questionnaire/52007
Authorization: Bearer <token>
GET /fhir/Questionnaire listet alle Form-ItemTypes (Paging über _count/_offset).
Bilder aus der Patientenakte (eingescannte Befunde, Fotos; Tabelle IMAGE) stellt der Server als Media-Ressourcen bereit. Media liefert nur die Metadaten (Datum, Kategorie, Titel) und verweist über content.url auf den eigentlichen Bild-Download (Binary). Die Bytes lädt der Empfänger also erst, wenn er 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 (Standard 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."
}
}
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: Ist beim Client Anonym gesetzt, bleiben die Media-Metadaten sichtbar, der Abruf der Bilddaten über Binary wird aber mit 403 abgelehnt, weil ein Bild den Personenbezug direkt enthalten kann.
Der Haupt-Endpunkt für Massen-Exporte. Er liefert je Patient die Items als Observation (Labor- und Verlaufswerte) bzw. QuestionnaireResponse (Formulare), gruppiert über Encounter-Ressourcen je Visit. Die Medikation kommt als MedicationStatement in zwei Ausprägungen (vollständige Historie und Status quo je Encounter, siehe unten). Auf der ersten Seite stehen zusätzlich die ResearchStudy-Ressourcen, je Patient ein ResearchSubject. Paging über _count (Standard 50 Patienten je Seite) und _offset.
Welche Werte tatsächlich im Bundle landen, begrenzt die Spalte Erlaubte ItemTypen (outbound) des Clients (siehe Clients verwalten). Ein type=-Filter auf einen nicht freigegebenen Typ wird mit 403 abgelehnt.
Parameter |
Bedeutung |
subject (= patient) |
Komma-Liste interner Subject-IDs. Bei Clients mit Studieneinschränkung ignoriert. |
study |
Komma-Liste interner Studien-IDs. Liefert alle eingeschlossenen Patienten (ohne Widerspruch/ignorieren). Bei Clients mit Studieneinschränkung setzt der Server die zugewiesene Studie selbst ein. |
selection (= _list) |
Komma-Liste von Selektions-IDs, siehe Beispiel 7. |
type |
Komma-Liste von ItemType-IDs (Filter auf Items). |
area |
Buchstaben aus L/F/V (Labor, Stammdaten, Verlauf). Ohne Angabe: alle Bereiche. |
from (= start), to (= end) |
ISO-Datum yyyy-mm-dd, filtert nach Visit-Datum. |
_since (= since) |
Delta-Export, siehe Beispiel 6. |
consent |
1: zusätzlich je Patient und Studie eine Consent-Ressource. |
consented |
1: nur Patienten mit Einverständnis (Standard: alle eingeschlossenen Patienten). |
anonymize |
1: ohne Namen und Geburtsdatum, Pseudonyme bleiben (wie Spalte Anonym). |
bundletype |
collection (Standard), searchset oder transaction. |
baseurl |
Überschreibt die Basis von entry.fullUrl; sonst aus der Anfrage abgeleitet. |
_count, _offset |
Paging je Patient (Standard 50 bzw. 0), siehe Beispiel 5. |
Tipp zur Übertragungsgröße: Der Server komprimiert Antworten, wenn der aufrufende Client dies anfordert. Rufen Sie Massen-Exporte daher immer mit dem Header Accept-Encoding: gzip ab; die übertragene Datenmenge sinkt dadurch typischerweise um ein Vielfaches.
Werten Sie die Medikation immer über die MedicationStatement-Ressourcen aus, nicht über die Observations. Die einzelnen Medikamenten-Felder erscheinen technisch zwar auch als Observation, sind dort aber roh und schwer nutzbar. In den MedicationStatements bereitet der Server die Therapie auf (Wirkstoff, Dosis, Zeitraum, Absetzgrund; Quelle ist die rheumatologische Medikationshistorie).
Der Server liefert MedicationStatements in zwei Ausprägungen, unterscheidbar über meta.tag (System https://emil.itc-ms.de/fhir/CodeSystem/medstatement-source):
•tag.code="history" — die vollständige Medikationshistorie als Verlauf: jede Ansetzung, jede Absetzung (mit Grund) und jede Dosisänderung als eigenes MedicationStatement, über alle Visits hinweg und so weit zurück wie dokumentiert. Das ist 1:1 die Medikationshistorie, die der Anwender in der Akte (Festdaten) sieht. Kein context.
•tag.code="encounter-status" — der Status quo je Encounter: pro Visit für jedes Präparat im damaligen Therapieplan ein MedicationStatement. context referenziert den Encounter.
Wichtig: Die beiden Mengen überschneiden sich inhaltlich. Ein Konsument muss sich für genau eine tag.code-Menge entscheiden und über den Tag filtern — werden beide ungefiltert ausgewertet, wird dieselbe Therapie doppelt gezählt. Faustregel: für den Therapieverlauf / die Anamnese history, für eine visit-bezogene Auswertung „was war zum Zeitpunkt X aktiv" encounter-status.
Felder in beiden Ausprägungen:
•status = active bei laufender Anwendung, stopped bei Absetzung.
•medicationCodeableConcept mit Coding aus dem drug-CodeSystem; id=-1 ist der Sonderfall „Glukokortikoide".
•dateAsserted = Datum, zu dem dieser Stand gilt (bei history das Event-Datum, bei encounter-status das State-Datum zum Visit).
•effectivePeriod.start = Therapiebeginn (bei history der Gesamtbeginn der Therapie, auch für laufende Präparate), effectivePeriod.end = Absetzdatum bei Absetzungen.
•dosage[].text = Klartext-Dosis + Applikationsart (nur bei aktiven Präparaten).
•dosage[].route = Applikationsart. text trägt die deutsche Kurzform („oral", „s.c.", „transdermal" …), coding zusätzlich den passenden SNOMED-CT-Code aus dem FHIR-ValueSet route-codes:
oral = 26643006, s.c. = 34206005, i.v. = 47625008, i.m. = 78421000,
i.a. = 58100008 (intraarteriell), transdermal = 45890007
Für „parenteral" und „Unbekannt" wird kein coding ausgegeben: Beide sind Sammelangaben ohne konkreten Applikationsweg, für die das ValueSet keinen Code kennt. In diesen Fällen steht die Angabe nur in route.text.
Die Glukokortikoid-Einträge (Codes -1 bis -5) tragen nie ein route-Element: sie entstehen aus Formularfeldern ohne Angabe einer Applikationsart. Die intraartikuläre Cortison-Gabe im Schub ist kein Applikationsweg, sondern eine eigene Dokumentationskategorie (Code -5).
•reasonCode[] = die im Medikations-Dialog dokumentierte Verschreibungsindikation: ein CodeableConcept pro ICD-10-Code mit coding aus http://fhir.de/CodeSystem/dimdi/icd-10-gm; display/text = ICD-Klartext, wenn auflösbar. Fortführungen und Absetzungen ohne eigene Codes erben die Indikation der ursprünglichen Verschreibung.
•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.
Form-Items (Scores, Bögen, Custom-Formulare) werden als QuestionnaireResponse geliefert, nicht als Observation. Aufbau der item[]-Liste:
•linkId="score" — der Wert, den die Akte anzeigt (Zelltext). Bei Score-Bögen die berechnete Zahl, bei anderen Bögen der zusammengefasste Anzeigetext.
•Danach je Formularfeld ein item[] mit dem internen Feldnamen als linkId. Formulare mit mehreren Zeilen liefern wiederholte Gruppen mit linkId="row".
•Für einzelne Bögen zusätzlich abgeleitete Werte als eigene linkIds — damit der Empfänger den Anzeigetext nicht selbst zerlegen muss.
Derzeit abgeleitet wird der Gelenkbogen (ItemType 52007, Arztsicht, und 52061, patientenberichtet). Der Zelltext lautet dort z. B. Druckschmerzhaft: 5 (3), Geschwollen: 2 (1). Die Gelenkzahlen kommen zusätzlich in drei eindeutig benannten Ebenen; ein bloßes tjc/sjc gibt es nicht, weil darunter je nach Leser die 28er-, die 66/68- oder die Gesamtzählung verstanden wird:
•tjc28 / sjc28 — DAS28-Teilmenge, also 28 Gelenke.
•tjc68 / sjc66 — die offizielle Ganzkörper-Zählung: 68 für druckschmerzhaft, 66 für geschwollen (Hüften sind auf Schwellung nicht beurteilbar). Die Zusatzgelenke (Zehenendgelenke, Daumenbasis) sind hier immer ausgeschlossen. Das sind die Zahlen, die in DAPSA und SDAI eingehen.
•tjcall / sjcall — alle im Bogen dokumentierten Gelenke inklusive der Zusatzgelenke. Solange keine Zusatzgelenke dokumentiert sind (Normalfall), stimmen sie mit tjc68/sjc66 überein. Sie werden immer mitgeliefert, damit die Feldliste stabil bleibt.
•joints28only — true, wenn im Bogen „nur 28 Gelenke" angehakt wurde. Dann sind auch tjc68/sjc66 und tjcall/sjcall keine echte Ganzkörper-Beurteilung.
Hinweis zum Zelltext: Die Zahlen in score folgen der Einstellung zur Gelenkkorrektur und entsprechen je nach Stand tjc68/sjc66 oder tjcall/sjcall. Verwenden Sie für maschinelle Auswertungen deshalb immer die benannten Felder, nicht den Anzeigetext.
{
"resourceType": "QuestionnaireResponse",
"questionnaire": "Questionnaire/52007",
"item": [
{ "linkId": "score", "answer": [{ "valueString": "Druckschmerzhaft: 5 (3), Geschwollen: 2 (1)" }] },
{ "linkId": "rb3", "answer": [{ "valueInteger": 1 }] },
{ "linkId": "tjc28", "answer": [{ "valueInteger": 3 }] },
{ "linkId": "sjc28", "answer": [{ "valueInteger": 1 }] },
{ "linkId": "tjc68", "answer": [{ "valueInteger": 5 }] },
{ "linkId": "sjc66", "answer": [{ "valueInteger": 2 }] },
{ "linkId": "tjcall", "answer": [{ "valueInteger": 5 }] },
{ "linkId": "sjcall", "answer": [{ "valueInteger": 2 }] },
{ "linkId": "joints28only", "answer": [{ "valueBoolean": false }] }
]
}
Die abgeleiteten Werte stammen aus derselben Berechnung, die auch den Anzeigetext erzeugt, nicht aus dem Text selbst. Sie stehen am Ende der item[]-Liste und überschreiben nie ein echtes Formularfeld gleichen Namens. Weitere Bögen können auf Wunsch ergänzt werden.
GET /fhir/$bundle?study=1850
Authorization: Bearer <token>
Liefert die eingeschlossenen Patienten der Studie 1850 mit allen ihren Items, aber nur die erste Seite, also die ersten 50 Patienten. Das Paging ist immer aktiv, auch ohne _count; ein Aufruf ohne _count ist also kein Vollexport. Die Gesamtzahl der Patienten steht in Bundle.total, die Folgeseite fertig gebaut in Bundle.link[] mit relation="next" (und im HTTP-Link-Header). Wie Sie die ganze Kohorte abrufen, zeigt Beispiel 5.
GET /fhir/$bundle?study=1850&type=52007,52014&from=2024-01-01
Nur die Items dieser zwei ItemTypes (Gelenkbogen 52007 und BASDAI 52014) aus Visits ab 2024-01-01. Beide müssen in der Outbound-Whitelist des Clients freigegeben sein.
GET /fhir/$bundle?study=1850&consent=1
Wie Beispiel 1, zusätzlich pro Subject eine Consent-Resource mit dem aktuellen Status (active/rejected/inactive).
GET /fhir/$bundle?subject=42,17,8
Drei konkret benannte Patienten. Das funktioniert nur für Clients ohne Studieneinschränkung (Statistiker-Modus); bei eingeschränkten Clients ersetzt der Server subject= durch deren Studie.
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 bzw. vorige Seite fertig gebaut. Rufen Sie deshalb die erste Seite ab und folgen Sie danach dem next-Link, bis keiner mehr kommt. Bundle.total nennt immer die Gesamtzahl der Patienten der Kohorte, nicht die der aktuellen Seite. Die ResearchStudy-Ressourcen stehen nur auf der ersten Seite (_offset=0).
Ohne Paging, also die komplette Kohorte in einem einzigen Bundle:
GET /fhir/$bundle?study=1850&_count=0
_count=0 bedeutet „kein Limit“. Für große Kohorten ist das nicht zu empfehlen: Der Server baut die Akte jedes Patienten vollständig im Speicher auf, die Antwort kann sehr groß werden und entsprechend lange dauern (Timeout des Clients beachten). Für Massen-Exporte ist Paging der bessere Weg.
GET /fhir/$bundle?_since=2026-07-01
GET /fhir/$bundle?study=1850&_since=2026-07-01T18:30:00Z
Für Empfänger, die den Datenbestand regelmäßig in ein eigenes System übernehmen: _since liefert nur die Patienten, an denen seit dem angegebenen Zeitpunkt mindestens ein Wert angelegt oder geändert wurde. Erwartet wird ein ISO-8601-Zeitpunkt, entweder nur das Datum (2026-07-01) oder mit Uhrzeit (2026-07-01T18:30:00Z). Ein nicht lesbarer Wert wird mit 400 abgelehnt und nicht als Vollexport behandelt.
•Ohne subject=, study= und selection= ist _since selbst die Datenquelle: Sie bekommen alle geänderten Patienten der Installation, ohne vorher deren IDs ermitteln zu müssen.
•Zusammen mit subject=, study= oder selection= wirkt _since als Filter auf diese Kohorte. Für Clients mit Studieneinschränkung gilt immer dieser Fall, da der Server deren Studie selbst setzt.
•Betroffene Patienten werden vollständig geliefert, nicht nur die geänderten Einzelwerte; der Filter arbeitet patientenweise, weil die Medikations-Ressourcen aus dem gesamten Therapieverlauf berechnet werden. Sie können pro geliefertem Patienten also alles löschen und neu einfügen.
•Bundle.total und die Paging-Links beziehen sich auf die Delta-Menge, _count/_offset funktionieren unverändert.
Wichtig: Löschungen erfasst _since nicht: Ein gelöschter Datensatz trägt keinen Änderungszeitstempel mehr und taucht im Delta nicht auf. Für einen exakten Abgleich ergänzen Sie den Delta-Lauf in größeren Abständen durch einen vollständigen Export.
Wartungsläufe, Neuberechnungen und Importe ändern Werte in vielen Akten gleichzeitig. Der folgende Delta-Abruf kann dann fast den gesamten Bestand umfassen; die aufnehmende Verarbeitung sollte darauf ausgelegt sein.
GET /fhir/$bundle?selection=14
GET /fhir/$bundle?selection=14,21&type=52007
GET /fhir/$bundle?study=1850&selection=14
Statt einer Studie oder einer Liste von Subject-IDs können Sie eine im Client unter Abfragen gepflegte Selektion als Kohorte angeben. Die IDs liefert CodeSystem/selection. Statt selection= akzeptiert der Server auch _list=. Mehrere Selektionen werden untereinander vereinigt.
•Ohne subject= und study= ist die Selektion selbst die Datenquelle: Sie bekommen genau deren Mitglieder.
•Zusammen mit subject= oder study= wirkt sie als Schnittmenge: die Selektion kann die Kohorte nur verengen, nie erweitern. Ebenso lässt sich _since kombinieren, dann filtert es innerhalb der Selektion.
Die Studieneinschränkung eines Clients greift immer und geht der Selektion vor. Ist ein Client auf eine Studie festgelegt (Spalte Erlaubte Studie in der Client-Liste), setzt der Server dessen Studie selbst ein und verwirft ein mitgegebenes subject=; die Selektion filtert dann ausschließlich innerhalb dieser Studie. Patienten der Selektion, die nicht in die Studie eingeschlossen sind (oder Widerspruch/Ignorieren tragen bzw. bei erzwungener Einwilligung nicht eingewilligt haben), werden nicht geliefert. Kein Parameter und keine Einstellung hebt diese Grenze auf.
Aktualität: Geliefert wird der zuletzt im Client berechnete Stand der Selektion; der Export führt dynamische Abfragen nicht selbst aus. Je verwendeter Selektion trägt das Bundle ein meta.tag (System https://emil.itc-ms.de/fhir/CodeSystem/selection-source) mit Titel, Stand und Mitgliederzahl. Für frische Daten führen Sie die Abfrage vorher im Client aus.
Fehlerfälle: eine unbekannte oder archivierte Selektion wird mit 404 abgelehnt, eine noch nie ausgeführte und leere Selektion mit 422. Eine Selektion, die ausgeführt wurde und schlicht keine Treffer hat, liefert dagegen ein leeres Bundle — das ist ein gültiges Ergebnis.