Wozu diese Seite

Eine Kausalkette lässt sich nicht vorwärts rechnen, wenn man das Verhalten der Glieder nur aus Beschreibungen kennt. Diese Seite sammelt Sequenzen, die die Mechanismen der Konzeptseiten an einem laufenden Server zeigen — jede in wenigen Minuten ausführbar, jede mit einer erwarteten Ausgabe, an der man merkt, ob man richtig lag.

Regel für diese Seite: Erwartete Ausgaben sind aus der Spezifikation abgeleitet. Weicht ein Server davon ab, ist die Abweichung der interessantere Befund — Serververhalten jenseits der Spezifikation ist genau das, was ein CapabilityStatement abbilden soll und oft nicht tut.


Setup

docker pull hapiproject/hapi:latest
docker run -p 8080:8080 hapiproject/hapi:latest

Base-URL http://localhost:8080/fhir, Default-Version R4, Browser-UI unter http://localhost:8080/. Der erste Start braucht etwa eine Minute.

export FHIR=http://localhost:8080/fhir
export SYS=https://beispiel.example/sid/patientennummer

jq erleichtert alles Folgende erheblich. Alle Beispieldaten sind synthetisch — das gilt auch dann, wenn der Server lokal läuft.


Sequenz 1 — Die logische Id gehört dem Server

Zeigt: Resource.id gegen Resource.identifier. Dauer: fünf Minuten.

curl -s -X POST "$FHIR/Patient" -H "Content-Type: application/fhir+json" -d '{
  "resourceType":"Patient",
  "identifier":[{"system":"https://beispiel.example/sid/patientennummer","value":"100045"}],
  "name":[{"use":"official","family":"Ehrlich","given":["Marlene"]}],
  "gender":"female","birthDate":"1962-03-14"
}' | jq '{id, identifier}'

Die zurückkommende id hat der Server vergeben. Sie steht in keinem Zusammenhang mit 100045.

export PID=<die id aus der Antwort>

Die Gegenprobe — eine mitgeschickte id bei POST:

curl -s -X POST "$FHIR/Patient" -H "Content-Type: application/fhir+json" -d '{
  "resourceType":"Patient","id":"selbst-vergeben-123",
  "name":[{"family":"Testfall"}]
}' | jq '.id'

Erwartung: eine andere Id. „If an id is provided, the server SHALL ignore it.”

Der Zeichenvorrat:

curl -s -o /dev/null -w "%{http_code}\n" -X PUT "$FHIR/Patient/SUED_778123" \
  -H "Content-Type: application/fhir+json" \
  -d '{"resourceType":"Patient","id":"SUED_778123","name":[{"family":"Testfall"}]}'

Der Unterstrich verletzt [A-Za-z0-9\-\.]{1,64}. Erwartung: ein 4xx. Der Wert ist als Patientennummer einwandfrei und als Adresse ungültig — derselbe Wert, verschiedene Positionen.


Sequenz 2 — Literale und logische Referenz

Zeigt: Reference.reference gegen Reference.identifier. Dauer: zehn Minuten. Setzt Sequenz 1 voraus ($PID).

Encounter A — literale Referenz:

curl -s -X POST "$FHIR/Encounter" -H "Content-Type: application/fhir+json" -d "{
  \"resourceType\":\"Encounter\",\"status\":\"in-progress\",
  \"class\":{\"system\":\"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\"code\":\"IMP\"},
  \"subject\":{\"reference\":\"Patient/$PID\"}
}" | jq '{id, subject}'
export ENC_A=<die id>

Encounter B — nur logische Referenz:

curl -s -X POST "$FHIR/Encounter" -H "Content-Type: application/fhir+json" -d '{
  "resourceType":"Encounter","status":"in-progress",
  "class":{"system":"http://terminology.hl7.org/CodeSystem/v3-ActCode","code":"IMP"},
  "subject":{"type":"Patient",
             "identifier":{"system":"https://beispiel.example/sid/patientennummer","value":"100045"},
             "display":"Ehrlich, Marlene"}
}' | jq '{id, subject}'

Statuscode 201. Keine Warnung, kein OperationOutcome. Der Defekt entsteht ohne jedes Artefakt.

Die Suche, die eine Anwendung typischerweise stellt:

curl -s -G "$FHIR/Encounter" --data-urlencode "subject=Patient/$PID" \
  | jq '{total, ids: [.entry[]?.resource.id]}'

Erwartung: nur Encounter A. B ist vorhanden, vollständig, trägt den Patientennamen im Klartext — und ist über diesen Weg unsichtbar.

Der andere Suchweg:

curl -s -G "$FHIR/Encounter" --data-urlencode "subject:identifier=$SYS|100045" \
  | jq '{total, ids: [.entry[]?.resource.id]}'

Erwartung: nur Encounter B. Zwei disjunkte Ergebnismengen für dieselbe fachliche Frage.

_include, der Teil, den man leicht übersieht:

curl -s -G "$FHIR/Encounter" --data-urlencode "subject=Patient/$PID" \
  --data-urlencode "_include=Encounter:subject" | jq '[.entry[].resource.resourceType]'
 
curl -s -G "$FHIR/Encounter" --data-urlencode "subject:identifier=$SYS|100045" \
  --data-urlencode "_include=Encounter:subject" | jq '[.entry[].resource.resourceType]'

Der erste Aufruf liefert Encounter und Patient, der zweite nur den Encounter. Das ist der Spezifikationssatz, ausgeführt: „nor are chaining, includes or reverse includes supported for reference elements that do not have a reference element.”

Was ein Update mit der bestehenden Referenz macht:

curl -s -X PUT "$FHIR/Encounter/$ENC_A" -H "Content-Type: application/fhir+json" -d "{
  \"resourceType\":\"Encounter\",\"id\":\"$ENC_A\",\"status\":\"in-progress\",
  \"class\":{\"system\":\"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\"code\":\"IMP\"},
  \"subject\":{\"type\":\"Patient\",
               \"identifier\":{\"system\":\"$SYS\",\"value\":\"100045\"},
               \"display\":\"Ehrlich, Marlene\"}
}" > /dev/null
 
curl -s -G "$FHIR/Encounter" --data-urlencode "subject=Patient/$PID" | jq '.total'

Erwartung: 0. Der Kontakt ist aus der Ergebnismenge verschwunden, ohne dass etwas gelöscht wurde — ein Update überschreibt das ganze subject-Element samt der literalen Referenz, die dort stand.

Das ist der Grund, warum ein solcher Defekt Bestandsdaten einholt, die vor der Umstellung korrekt geschrieben wurden. Sie überleben genau bis zur ersten Fortschreibung.


Sequenz 3 — Das CapabilityStatement als Bezugsdokument

Zeigt: Konformität und Validatoren. Dauer: zwei Minuten.

curl -s "$FHIR/metadata" | jq '{fhirVersion, software: .software.name}'
 
curl -s "$FHIR/metadata" \
  | jq '.rest[0].resource[] | select(.type=="Encounter") | {referencePolicy, searchInclude}'
 
curl -s "$FHIR/metadata" \
  | jq '.rest[0].resource[] | select(.type=="Patient")
        | {updateCreate, conditionalUpdate, conditionalCreate, referencePolicy}'

Jedes Ergebnis ist ein Befund — auch ein leeres. Steht referencePolicy gar nicht da, ist die Kardinalität 0..* ausgeschöpft und der Server hat zu diesem Punkt nichts zugesagt.

Die fünf Codes und was sie bedeuten:

CodeZusage
literalunterstützt und befüllt Reference.reference
logicalerlaubt Reference.identifier
resolvesversucht, logische in literale Referenzen aufzulösen
enforcederzwingt referentielle Integrität
localkeine Referenzen auf fremde Server

logical und resolves sind zwei verschiedene Zusagen. Ein Server mit logical, aber ohne resolves, nimmt logische Referenzen entgegen und lässt sie liegen. Das ist konform und der Normalfall.


Aufräumen und Fallstricke

Der Container hält seine Daten in einer eingebetteten Datenbank; ein Neustart ohne Volume setzt alles zurück — für eine Sandbox ein Vorteil.

FallstrickAbhilfe
| im Suchparameter wird von der Shell interpretiertcurl -G --data-urlencode benutzen, nicht die URL selbst zusammenbauen
Content-Type vergessenapplication/fhir+json setzen, sonst antworten manche Server mit 415
Suchergebnis wirkt unvollständigBundle.total gegen die Zahl der entry halten; _include-Treffer zählen nicht als match
Alte Testdaten verfälschen das ErgebnisContainer neu starten oder je Sequenz einen eigenen identifier-Wert verwenden

Lektüre & Belege