SPLG Grouper

REST-Service

Einführung

Der SPLG-Grouper kann als lokaler HTTP-Service gestartet werden und Fälle dann über JSON-Requests gruppieren. Das ist nützlich, um den Grouper aus eigenen Anwendungen heraus aufzurufen, ohne die CLI für jeden Fall neu zu starten.

Der Service ist für lokalen Gebrauch gedacht und nicht dafür, über das Internet erreichbar zu sein. Er bindet sich standardmässig an 127.0.0.1 und stellt keine TLS-Unterstützung bereit. Wird TLS benötigt, kann der Service hinter einen Reverse Proxy gestellt werden.

In den folgenden Beispielen werden die Kommandozeilen-Tools curl und jq verwendet; jeder andere HTTP-Client (z. B. Postman) funktioniert ebenfalls.

Service starten

Der Service wird über die Kommandozeile mit der Option --service gestartet:

splg-grp-cli.exe --service

Bzw. in der Java-Variante:

java -jar app/splg-grouper-cli.jar --service

Welche Definition und ggf. Spitalliste für eine konkrete Anfrage verwendet wird, ergibt sich aus dem Release-Pfad in der URL. Die zugehörigen Dateien werden bei der ersten Anfrage aus dem mitgelieferten Konfigurationsordner geladen und für die Dauer des Service-Laufs im Speicher gehalten.

--service
Aktiviert den Service-Modus.
--cfg-dir <path>
Optional: alternativer Konfigurationsordner. Standardmässig wird der mitgelieferte Ordner verwendet; diese Option wird im Normalbetrieb nicht benötigt.
--host <hostname>
Adresse, auf der der Server lauscht. Standard: 127.0.0.1 (nur lokal erreichbar). Mit 0.0.0.0 wird der Service auf allen Netzwerkschnittstellen erreichbar.
--port <n>
TCP-Port. Standard: 8080.
--max-threads <n>
Maximale Anzahl gleichzeitig bearbeiteter Anfragen. Standard: 32. Übersteigende Verbindungen werden sofort mit 503 Service Unavailable beantwortet, statt unbegrenzt zu queuen — so bleibt der Speicherverbrauch auch unter Last beschränkt.
--read-timeout-ms <n>
Lesetimeout pro Verbindung in Millisekunden. Standard: 30000. Schützt gegen langsame Clients (Slowloris-artige Verbindungen).
--max-body-bytes <n>
Maximale Grösse des Request-Bodies in Bytes. Standard: 4194304 (4 MiB). Grössere Anfragen werden mit 413 Payload Too Large abgelehnt, ohne den Speicher zu allozieren.
--graceful-stop-ms <n>
Wartezeit beim Herunterfahren in Millisekunden. Standard: 10000. Bei Ctrl+C oder SIGTERM stoppt der Service die Annahme neuer Verbindungen und wartet bis zu dieser Zeit, bis laufende Anfragen abgeschlossen sind.
--spitalliste <file>
Optionale Spitallistendatei (mehrfach angebbar). Wird für alle Requests verwendet, sofern angegeben. Ohne diese Option verwendet der Service die zum Release passende Standard-Spitalliste.
--sl <eintrag>
Inline-Spitallisteneintrag (mehrfach angebbar), gleiche Syntax wie auf der Testfall-Seite beschrieben.
--wohnkanton-override <kanton>
Setzt den Wohnkanton aller Fälle auf einen festen Wert.

Beim Start meldet der Service den Hörsocket auf der Standardausgabe. Beendet wird er mit Ctrl+C.

Requests

Sowohl Eingabe als auch Ausgabe der Requests erfolgen grundsätzlich als JSON. Die Ausgabe ist nicht formatiert; in den Beispielen ist sie zur besseren Lesbarkeit mit jq formatiert.

GET /version

Liefert die Version des Service.

> curl -s http://127.0.0.1:8080/version | jq .
{
  "status": 200,
  "text": "OK",
  "result": {
    "version": "2026.2.1"
  }
}

GET /healthz

Liveness-Check für Monitoring und Reverse Proxies. Antwortet immer mit 200 OK, ohne die Konfiguration oder das Dateisystem zu berühren — geeignet für häufige Aufrufe durch systemd, nginx oder externe Health-Checks.

> curl -s http://127.0.0.1:8080/healthz | jq .
{
  "status": "ok"
}

POST /group/<Release>

Gruppiert die übermittelten Falldaten mit der angegebenen SPLG-Definition und liefert das Resultat zurück.

> curl -s http://127.0.0.1:8080/group/A_2026 \
       -H "Content-Type: application/json" \
       -X POST \
       -d '{"behandlungen":[{"code":"815121"}]}' | jq .
{
  "status": 200,
  "text": "OK",
  "result": {
    "splg": "BEW7.1.1",
    "quer": [],
    "mfzs": ["BEW7.1.1"],
    "mfzo": ["BEW7.1.1"],
    "points": [],
    "lactrl": 99,
    "lactrlcodes": [],
    "notes": "",
    "errorcode": "w31",
    "configuration": {
      "defversion": "A_2026_3",
      "spitallisten": ["A_2026_1_zh"]
    }
  }
}

Auch wenn nur eine einzige Behandlung definiert ist, wird der Fall erfolgreich gruppiert und die SPLG BEW7.1.1 ausgegeben.

Antwortstruktur

Jede Antwort des Service ist ein JSON-Objekt mit drei Feldern:

status (Zahl)
Numerischer Statuscode, gespiegelt aus dem HTTP-Statuscode (z. B. 200 bei Erfolg).
text (String)
Kurze textuelle Beschreibung des Status (z. B. "OK" oder eine Fehlermeldung).
result (Objekt, optional)
Bei Erfolg das eigentliche Ergebnis. Bei Fehlern weggelassen.

Bei einer erfolgreichen Gruppierung enthält result die folgenden Felder:

splg (String)
Die für den Fall ermittelte Spitalplanungs-Leistungsgruppe. Leerer String, falls keine SPLG zugeordnet werden konnte.
quer (Array von Strings)
Querschnittsleistungsgruppen, denen der Fall zusätzlich zugeordnet wird.
mfzs (Array von Strings)
MFZS-Leistungsgruppen (Mindestfallzahlen Spital), denen der Fall zugeordnet wird.
mfzo (Array von Strings)
MFZO-Leistungsgruppen (Mindestfallzahlen Operateur), denen der Fall zugeordnet wird.
points (Array von Objekten)
Operateurspunkte. Jeder Eintrag enthält gln (GLN des Operateurs), splg (zugehörige SPLG) und points (numerische Punktzahl).
lactrl (Zahl)
Leistungscontrolling-Status (z. B. 0 für "Leistungsauftrag vorhanden", 99 für "Leistungsauftrag unbekannt oder Fall nicht gruppierbar"). Vollständige Code-Liste: LACTRL.
lactrlcodes (Array von Strings)
Codes, die zur Lactrl-Beurteilung beigetragen haben.
notes (String)
Zusätzliche Hinweise zur Gruppierung; oft leer.
errorcode (String)
Gruppierungsstatus (z. B. "0" für eine fehlerfreie Gruppierung, "w31" für die Warnung "Hauptdiagnose fehlt"). Vollständige Code-Liste: ERROR. Bei mehreren Codes wird derjenige mit der höchsten Priorität ausgewiesen.
configuration (Objekt)
Dokumentation der tatsächlich verwendeten Konfiguration (siehe unten).

Der Block configuration hilft beim Nachvollziehen, mit welcher Definition und welcher Spitalliste ein konkretes Resultat zustande gekommen ist. Das ist nützlich, wenn der Service mehrere Releases bedient oder wenn sich Definitionen oder Spitallisten zwischen Service-Läufen unterscheiden:

defversion (String)
Versionsbezeichnung der verwendeten SPLG-Definition (z. B. "A_2026_3"). Damit lassen sich Resultate eindeutig auf eine bestimmte Definition zurückführen.
spitallisten (Array von Strings)
Versionsbezeichnungen der bei der Gruppierung verwendeten Spitallisten. Werden mehrere Spitallisten kombiniert, erscheinen entsprechend mehrere Einträge.

Fehlerantworten

400 Bad Request
Body fehlt oder ist kein gültiges JSON.
403 Forbidden
Definition vorhanden, aber Lizenz fehlt oder ist für den Release nicht gültig.
404 Not Found
Release nicht installiert (es existiert kein passender Unterordner im Konfigurationsordner) oder unbekannte URL.
413 Payload Too Large
Der Request-Body überschreitet die mit --max-body-bytes konfigurierte Grenze.
500 Internal Server Error
Unerwarteter Fehler bei der Gruppierung. Der Servicelauf bleibt davon unberührt.
503 Service Unavailable
Der Service ist ausgelastet (alle --max-threads Worker beschäftigt). Die Anfrage kann später erneut versucht werden.

Falldaten-JSON

Die zu gruppierenden Falldaten werden als JSON dem Service übermittelt. Die Struktur entspricht der bestehenden SPLG-JSON-Datei (ohne den umschliessenden splg-json-Container) und basiert auf dem abstrakten Eingabeformat.

{
  "burnr": "12345678",
  "agey": "45",
  "aged": "0",
  "austritt": "20230227",
  ... weitere Felder gemäss abstraktem Format ...
  "diagnosen": [
    { "code": "D100", "zusatz": "", "seitigkeit": "", "rang": 0 },
    { "code": "D200", "seitigkeit": "",                "rang": 1 }
    ...
  ],
  "behandlungen": [
    {
      "code": "815121",
      "seitigkeit": "",
      "ambext": "",
      "beginn": "2023022408",
      "rang": 0,
      "operateure": [
        { "gln": "7601234567890", "funktion": "1", "zulassung": "1" }
      ]
    }
    ...
  ],
  "bewegungen": [
    {
      "beginn": "20230224",
      "ende":   "20230227",
      "art":    "1",
      "burnr":  "12345678"
    }
  ]
}

Alle Felder sind optional; damit gruppiert werden kann, müssen jedoch die für die Gruppierung relevanten Angaben (Diagnosen, Behandlungen, Alter usw.) vorhanden sein. Bis auf die technischen rang-Felder enthalten alle Felder String-Werte; leere Felder können auch ganz weggelassen werden. Anstelle von bewegungen wird auch patientenbewegungen akzeptiert, um mit dem bestehenden SPLG-JSON-Dateiformat kompatibel zu bleiben.

Deployment unter Linux (systemd + nginx)

Für einen dauerhaften Betrieb empfiehlt sich, den Service als systemd-Unit zu betreiben und ihm einen nginx-Reverse-Proxy vorzuschalten. systemd übernimmt Start, Neustart bei Absturz und saubere Beendigung; nginx übernimmt TLS, Rate-Limiting, Access-Log und ggf. Authentifizierung — Aufgaben, die nicht im Service selbst implementiert sind.

systemd-Unit

Eine minimale Unit-Datei (anzupassen an Pfade und Benutzer):

[Unit]
Description=SPLG-Grouper REST service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=splg
Group=splg
WorkingDirectory=/opt/splg-grouper
ExecStart=/opt/splg-grouper/app/splg-grp-cli \
    --service \
    --host 127.0.0.1 \
    --port 8080 \
    --max-threads 32 \
    --read-timeout-ms 30000 \
    --max-body-bytes 4194304 \
    --graceful-stop-ms 10000 \
    --cfg-dir /opt/splg-grouper/cfg

KillSignal=SIGTERM
TimeoutStopSec=15
Restart=on-failure
RestartSec=5
RuntimeMaxSec=24h

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target

Bemerkungen:

nginx-Reverse-Proxy

Vereinfachter nginx-Server-Block für die TLS-Terminierung und das Weiterreichen an den lokalen Service:

limit_req_zone $binary_remote_addr zone=splg_grp_rl:10m rate=20r/s;

upstream splg_grouper_backend {
    server 127.0.0.1:8080;
    keepalive 8;
}

server {
    listen 443 ssl http2;
    server_name grouper.example.org;

    ssl_certificate     /etc/letsencrypt/live/grouper.example.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/grouper.example.org/privkey.pem;

    client_max_body_size 4m;
    proxy_connect_timeout 5s;
    proxy_read_timeout    60s;

    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    location = /healthz {
        proxy_pass http://splg_grouper_backend;
    }

    location / {
        limit_req zone=splg_grp_rl burst=40 nodelay;
        limit_req_status 429;
        proxy_pass http://splg_grouper_backend;
    }

    access_log /var/log/nginx/splg-grouper-access.log combined;
}

Wichtig: