Groupeur GPPH

Service REST

Introduction

Le groupeur GPPH peut être démarré comme service HTTP local pour grouper des cas via des requêtes JSON. Cela est utile pour appeler le groupeur depuis vos propres applications, sans redémarrer la CLI pour chaque cas.

Le service est destiné à un usage local et n'est pas conçu pour être exposé sur Internet. Il s'attache par défaut à 127.0.0.1 et ne fournit pas de prise en charge TLS. Si TLS est nécessaire, le service peut être placé derrière un reverse proxy.

Les exemples suivants utilisent les outils en ligne de commande curl et jq ; tout autre client HTTP (p. ex. Postman) fonctionne également.

Démarrer le service

Le service se démarre depuis la ligne de commande avec l'option --service :

splg-grp-cli.exe --service

Ou bien, dans la variante Java :

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

La définition et la liste hospitalière effectivement utilisées pour une requête sont déterminées par la version indiquée dans l'URL. Les fichiers correspondants sont chargés depuis le dossier de configuration livré, à la première requête, et conservés en mémoire pour la durée du service.

--service
Active le mode service.
--cfg-dir <path>
Optionnel : dossier de configuration alternatif. Par défaut, le dossier livré avec l'application est utilisé ; cette option n'est pas nécessaire en utilisation normale.
--host <hostname>
Adresse sur laquelle le serveur écoute. Par défaut : 127.0.0.1 (accessible uniquement localement). Avec 0.0.0.0, le service devient accessible sur toutes les interfaces réseau.
--port <n>
Port TCP. Par défaut : 8080.
--max-threads <n>
Nombre maximal de requêtes traitées simultanément. Par défaut : 32. Les connexions excédentaires sont immédiatement rejetées avec 503 Service Unavailable, plutôt que mises en file d'attente sans limite — la consommation mémoire reste ainsi bornée même sous charge.
--read-timeout-ms <n>
Délai de lecture par connexion, en millisecondes. Par défaut : 30000. Protège contre les clients lents (connexions de type Slowloris).
--max-body-bytes <n>
Taille maximale du corps de requête, en octets. Par défaut : 4194304 (4 Mio). Les requêtes plus volumineuses sont rejetées avec 413 Payload Too Large sans allouer de mémoire.
--graceful-stop-ms <n>
Délai d'extinction, en millisecondes. Par défaut : 10000. Sur Ctrl+C ou SIGTERM, le service cesse d'accepter de nouvelles connexions et attend jusqu'à ce délai la fin des requêtes en cours.
--spitalliste <file>
Fichier de liste hospitalière facultatif (peut être indiqué plusieurs fois). S'il est indiqué, il est utilisé pour toutes les requêtes. Sans cette option, le service utilise la liste hospitalière standard de la version.
--sl <entrée>
Entrée de liste hospitalière en ligne (peut être indiquée plusieurs fois). Même syntaxe que sur la page Cas de test.
--wohnkanton-override <kanton>
Force le canton de domicile pour tous les cas.

Au démarrage, le service signale son socket d'écoute sur la sortie standard. Il s'arrête avec Ctrl+C.

Requêtes

L'entrée et la sortie des requêtes se font en JSON. La sortie n'est pas formatée ; dans les exemples, elle est mise en forme avec jq pour la lisibilité.

GET /version

Renvoie la version du service.

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

GET /healthz

Sonde de vivacité (liveness check) pour la supervision et les reverse proxies. Répond toujours 200 OK sans toucher à la configuration ni au système de fichiers — adapté aux appels fréquents par systemd, nginx ou un système de monitoring externe.

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

POST /group/<Version>

Groupe les données de cas transmises avec la définition GPPH indiquée et renvoie le résultat.

> 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"]
    }
  }
}

Même avec un seul traitement défini, le cas est groupé avec succès et la GPPH BEW7.1.1 est renvoyée.

Structure de la réponse

Chaque réponse du service est un objet JSON comportant trois champs :

status (nombre)
Code de statut numérique, miroir du code de statut HTTP (p. ex. 200 en cas de succès).
text (chaîne)
Brève description textuelle du statut (p. ex. "OK" ou un message d'erreur).
result (objet, facultatif)
En cas de succès, le résultat proprement dit. Omis en cas d'erreur.

Lorsqu'un groupage a réussi, result contient les champs suivants :

splg (chaîne)
Groupe de prestations de planification hospitalière déterminé pour le cas. Chaîne vide si aucune GPPH n'a pu être attribuée.
quer (tableau de chaînes)
Groupes de prestations transversales auxquels le cas est également affecté.
mfzs (tableau de chaînes)
Groupes MFZS (nombre minimal de cas par hôpital) auxquels le cas est affecté.
mfzo (tableau de chaînes)
Groupes MFZO (nombre minimal de cas par opérateur) auxquels le cas est affecté.
points (tableau d'objets)
Points des opérateurs. Chaque entrée contient gln (GLN de l'opérateur), splg (GPPH associée) et points (points numériques).
lactrl (nombre)
Statut du contrôle des prestations (p. ex. 0 pour « mandat de prestations présent », 99 pour « mandat inconnu ou cas non groupable »). Liste complète des codes : LACTRL.
lactrlcodes (tableau de chaînes)
Codes ayant contribué à l'évaluation lactrl.
notes (chaîne)
Remarques additionnelles sur le groupage ; souvent vide.
errorcode (chaîne)
Statut de groupage (p. ex. "0" pour un groupage sans erreur, "w31" pour l'avertissement « diagnostic principal manquant »). Liste complète des codes : ERROR. En présence de plusieurs codes, celui ayant la priorité la plus élevée est renvoyé.
configuration (objet)
Documentation de la configuration effectivement utilisée (voir ci-dessous).

Le bloc configuration permet de retracer avec quelle définition et quelle liste hospitalière un résultat précis a été produit. C'est utile lorsque le service traite plusieurs versions, ou lorsque les définitions ou listes hospitalières varient entre exécutions du service :

defversion (chaîne)
Identifiant de version de la définition GPPH utilisée (p. ex. "A_2026_3"). Permet de rattacher sans ambiguïté un résultat à une définition donnée.
spitallisten (tableau de chaînes)
Identifiants de version des listes hospitalières utilisées lors du groupage. Si plusieurs listes sont combinées, plusieurs entrées apparaissent.

Réponses d'erreur

400 Bad Request
Le corps est manquant ou n'est pas un JSON valide.
403 Forbidden
Définition disponible, mais licence manquante ou non valide pour cette version.
404 Not Found
Version non installée (aucun sous-dossier correspondant dans le dossier de configuration) ou URL inconnue.
413 Payload Too Large
Le corps de la requête dépasse la limite configurée par --max-body-bytes.
500 Internal Server Error
Erreur inattendue lors du groupage. Le service continue de fonctionner.
503 Service Unavailable
Le service est saturé (tous les workers --max-threads sont occupés). La requête peut être réessayée ultérieurement.

Données de cas (JSON)

Les données de cas à grouper sont transmises au service en JSON. La structure correspond au fichier JSON SPLG existant (sans le conteneur splg-json) et s'appuie sur le format d'entrée abstrait.

{
  "burnr": "12345678",
  "agey": "45",
  "aged": "0",
  "austritt": "20230227",
  ... autres champs selon le format abstrait ...
  "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"
    }
  ]
}

Tous les champs sont optionnels ; pour qu'un groupage correct soit possible, les éléments pertinents (diagnostics, traitements, âge, etc.) doivent toutefois être présents. À l'exception des champs techniques rang, tous les champs contiennent des chaînes ; les champs vides peuvent aussi être complètement omis. À la place de bewegungen, patientenbewegungen est également accepté pour rester compatible avec le fichier SPLG-JSON existant.

Déploiement sous Linux (systemd + nginx)

Pour un fonctionnement durable, il est recommandé d'exécuter le service comme unité systemd et de placer un reverse proxy nginx devant lui. systemd gère le démarrage, le redémarrage en cas de crash et l'arrêt propre ; nginx s'occupe de TLS, du rate-limiting, du journal d'accès et, le cas échéant, de l'authentification — autant de tâches qui ne sont pas implémentées dans le service lui-même.

Unité systemd

Une unité minimale (à adapter aux chemins et à l'utilisateur) :

[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

Remarques :

Reverse proxy nginx

Bloc serveur nginx simplifié pour la terminaison TLS et la redirection vers le service local :

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;
}

Important :