Infrastruktur als Code:
infra/main.bicep— lokal visualisierbar via VS Code BefehlBicep: Open Visualizer.
1-Click Deployment: Klick auf den blauen Button öffnet das Azure-Portal mit einem geführten Wizard. Alle Ressourcen werden in Ihrem Tenant angelegt, Authentifizierung läuft ausschließlich über Managed Identity + RBAC, Storage hängt an einem dedizierten VNet mit Private Endpoints. Keine Connection-Strings, keine Shared Keys.
Azure Function App zur Klassifikation klinischer Dokumente (Text, PDFs, Bilder, Audio) gegen die DVMD KDL CodeSystem — Klinische Dokumentenklassen-Liste — Seite für Seite, mit Dual-Call GPT-Verifikation.
- 557 KDL-Codes (13 Klassen → 64 Sub-Klassen → 480 Leaf-Codes), live aus der DVMD-FHIR-Package-Registry (
dvmd.kdl.r4) gecached, mit gebündeltem Fallback. - Dual-Call-Verifikation: zwei parallele GPT-Aufrufe pro Dokument (context-aware + independent), Agreement →
verified: true, Disagreement → höhere Confidence gewinnt + beide Opinions transparent. - Multi-Modal: Text direkt, Bilder/PDFs über Azure AI Vision Read, Audio über Azure AI Speech.
| Was | Wie prüfen / einrichten |
|---|---|
Azure-Subscription mit Rechten auf Subscription-Ebene (Owner oder Contributor + User Access Administrator) |
Portal → Subscriptions → Access control (IAM). Ohne diese Rolle scheitert das Subscription-scoped Deployment, weil RBAC-Rollen angelegt werden. |
| Azure-OpenAI-Quota für das gewünschte Modell in der gewählten Region | AOAI Quotas & Limits. Standard: gpt-5.4-mini / 50k TPM. Bei zu wenig Quota: Wizard-Slider runterdrehen oder anderes Modell wählen. |
- Auf Deploy to Azure klicken.
- Subscription + (neue) Resource Group + Region wählen.
nameSuffixsetzen (3-8 lowercase, eindeutig — z. B.klinik1).- AOAI-Tab: Modell + Capacity an Ihre Quota anpassen.
- Code-Tab: Package-URL stehen lassen (= GitHub-Release-ZIP) oder leeren, um den Code später per
deploy.ps1 -SkipInfrazu publishen. - Review + create. Deployment-Dauer: ~10-15 Min.
# Function Key abrufen
az functionapp function keys list -g <RG> -n func-kdl-<suffix> --function-name classify --query default -o tsv
# Smoke-Test
curl -X POST "https://func-kdl-<suffix>.azurewebsites.net/api/classify?code=<KEY>" `
-H "Content-Type: application/json" `
-d '{"text":"CT-Befund Schädel mit Kontrastmittel"}'
# UI öffnen (anonym)
start https://func-kdl-<suffix>.azurewebsites.net/api/debugWenn Sie noch nie etwas in Azure deployed haben, sollten Sie vor dem 1-Click-Button einmalig eine minimale Landing Zone einrichten. Microsoft liefert Blaupausen:
| Szenario | Pfad | Aufwand |
|---|---|---|
| Solo-Klinik / kleine Praxis | Azure Setup Guide (Quick) | < 1 h |
| Kleines Krankenhaus — Sandbox + Prod, Cost Alerts | Azure Landing Zone Accelerator | 2-4 h |
| Klinik-Verbund / Konzern — Hub-Spoke, ExpressRoute | Enterprise-Scale Landing Zone (ALZ) | 1-2 Tage |
| Souveränität & BSI/KRITIS | Sovereign Landing Zone | 1-3 Tage |
Pflicht-Bausteine vor produktivem Patientendaten-Workload:
- Microsoft Entra ID Tenant mit MFA für alle Admins.
- Cost Management Budget (Alert bei z. B. 100 € / Monat).
- Microsoft Defender for Cloud (Free Tier reicht zum Start).
- Diagnostic Settings → Log Analytics Workspace (zentral).
- Tagging-Policy (
owner,costCenter,dataClassification).
Weiterführend: Cloud Adoption Framework · BSI C5 & Azure · Azure Health Data Services.
┌──────────────────────────┐
Event Grid topic │ │
vidaugment.moments.extracted──► classifyMoments │
│ (Event Grid Trigger) │
│ │ │ vidaugment.kdl.classified
POST /api/classify │ ▼ ├──► Event Grid topic
{ text | moments | file }──►│ shared/classifyGpt.js │
│ (GPT Dual-Call) │
POST /api/classify │ ▲ │
Content-Type: image/* ────►│ shared/extract.js │
Content-Type: audio/* │ (Vision / Speech / DI) │
Content-Type: application/pdf │ │
└──────────────────────────┘
| Function | Trigger | Beschreibung |
|---|---|---|
classify |
HTTP POST /api/classify |
On-demand Klassifikation (Text, Files, Moments) |
classifyMoments |
Event Grid (vidaugment.moments.extracted) |
Klassifiziert und re-publisht |
debug |
HTTP GET /api/debug |
Interaktive Debug-Konsole |
/api/classify benötigt einen Function Key (?code=...).
# Plain text
curl -X POST "https://<host>/api/classify?code=KEY&languageHint=de" \
-H "Content-Type: application/json" \
-d '{"text":"CT-Befund Schädel mit Kontrastmittel"}'
# Moments array
curl -X POST "https://<host>/api/classify?code=KEY" \
-H "Content-Type: application/json" \
-d '{"moments":[{"text":"CT-Befund Schädel","page":1}]}'
# Base64-encoded PDF
curl -X POST "https://<host>/api/classify?code=KEY&languageHint=de" \
-H "Content-Type: application/json" \
-d "{\"file\":\"$(base64 -w0 document.pdf)\",\"fileContentType\":\"application/pdf\"}"Response (gekürzt):
{
"classifications": [
{
"page": 1,
"code": "DG020103",
"display": "CT-Befund",
"classId": "DG",
"confidence": 0.95,
"verified": true,
"verificationMethod": "dual-call-agree"
}
],
"codeSystem": {
"url": "http://dvmd.de/fhir/CodeSystem/kdl",
"version": "2025",
"publisher": "DVMD",
"codeCount": 480
},
"inputType": "pdf"
}| Typ | Content-Types | Azure-AI-Dienst |
|---|---|---|
| Bilder | image/jpeg, image/png, image/tiff, image/bmp, image/webp |
Vision v4.0 Image Analysis |
| PDFs | application/pdf |
Vision v3.2 Read (async, multi-page) |
| Audio | audio/wav, audio/mpeg, audio/ogg, audio/flac |
Speech fast transcription |
npm install
func startErfordert local.settings.json mit:
| Setting | Wofür |
|---|---|
AZURE_OPENAI_ENDPOINT + AZURE_OPENAI_DEPLOYMENT |
Klassifikation |
AZURE_AI_VISION_ENDPOINT |
Bilder/PDFs |
AZURE_AI_SPEECH_ENDPOINT |
Audio |
EVENT_GRID_TOPIC_ENDPOINT |
Event-Grid-Modus (optional) |
Auth via DefaultAzureCredential (Managed Identity in Azure, az login lokal).
classify/ HTTP POST /api/classify
classifyMoments/ Event Grid Trigger
config/ Gebündelter KDL-CodeSystem-Fallback
debug/ Debug-Konsole
infra/ Bicep + ARM + uiFormDefinition.json
schemas/ JSON Schema für Event-Grid-Events
scripts/ eval-classify.ps1 (Validierungs-Runner)
shared/ classifyGpt.js, extract.js, eventGrid.js, kdlCodeSystem.js
tests/eval/ Synthetisches Gold-Set + Reports
Synthetisches Mini-Gold-Set (tests/eval/kdl-gold.jsonl) mit 32 Fällen über alle 13 KDL-Hauptklassen (inkl. 1 Multi-Page-Dokument). Evaluiert wird gegen das live geladene dvmd.kdl.r4-CodeSystem.
Skript: scripts/eval-classify.ps1 — postet jede Probe an POST /api/classify, bewertet Precision/Recall/F1 auf drei Hierarchie-Ebenen (Leaf 8-stellig · Sub-Klasse 6-stellig · Klasse 2-stellig) plus Dual-Call-Metriken.
| Ebene | Avg Precision | Avg Recall | Avg F1 |
|---|---|---|---|
Leaf (8-stellig, z. B. AD010104) |
0.75 | 0.75 | 0.75 |
Sub-Klasse (6-stellig, z. B. AD0101) |
0.94 | 0.94 | 0.94 |
Klasse (2-stellig, z. B. AD) |
0.94 | 0.94 | 0.94 |
- Exact-Set-Match (Leaf): 24/32 (75 %)
- Primary-Code-Hit: 24/32 (75 %)
- Dual-Call verified-Rate: 100 % (alle parallelen Calls einig)
- ⌀ Confidence: 0.99 · ⌀ Latenz: 1.4 s/Fall
- Hohe Klassen-/Sub-Klassen-Treffer (94 % F1) zeigen: das Modell erkennt den Dokumenttyp zuverlässig grob. Verwechslungen passieren primär innerhalb einer Sub-Klasse (z. B.
AD010115Entlassungsbericht vs.AD010104Entlassungsbericht extern). - Synthetic-Bias: die 32 Testtexte sind kurz und prototypisch. Echte Krankenhausdokumente sind länger, fehlerhaft OCRed, mehrsprachig — F1 auf Leaf-Ebene wird in der Praxis niedriger liegen.
- Versionsdrift: einige Gold-Codes stammen aus dem gebündelten Fallback; live geladene Codes können abweichen. Sub-Klassen/Klassen sind stabiler.
- Kein Vergleich zu Literatur — Stand der Wissenschaft (Mai 2026): wir kennen keine publizierte automatisierte KDL-Klassifikations-Benchmark. Wer eine hat: bitte PR.
- Vollständig synthetisch: alle 32 Testtexte wurden generiert; kein realer Patientenbezug, keine PHI im Repo.
# Voraussetzung: deployte Function App + AOAI-Quota >= 100k TPM für 32 Dual-Calls
./scripts/eval-classify.ps1 `
-BaseUrl 'https://func-kdl-<suffix>.azurewebsites.net' `
-ResourceGroup 'rg-kdl-<suffix>' `
-FunctionApp 'func-kdl-<suffix>'
# → tests/eval/reports/eval-kdl-<timestamp>.{json,md}Token-Budget pro GPT-Call (gemessen aus shared/kdlCodeSystem.js + shared/classifyGpt.js):
| Komponente | ca. Tokens |
|---|---|
| KDL-Codeliste (480 Codes × ~22 Tokens) | ~10.500 |
| System-Prompt + Format-Instruktionen | ~250 |
| Seiten-Text (Mittelwert klin. Dokument, ~1.500 Zeichen) | ~500 |
| Output (JSON-Antwort, 1 Seite) | ~150 |
| Input gesamt / Call | ~11.250 |
| Output gesamt / Call | ~150 |
Listenpreis Azure OpenAI gpt-5.4-mini GlobalStandard (Sweden Central, Mai 2026, aka.ms/aoaipricing):
| Token-Typ | $ / 1M Tokens |
|---|---|
| Input | ~$0,15 |
| Output | ~$0,60 |
Preise variieren je Region/SKU und ändern sich regelmäßig — bitte aktuelle Werte im Azure-Pricing-Rechner prüfen.
Kosten pro klassifiziertem Dokument:
| Dokument-Typ | GPT-Calls | Input-Tk | Output-Tk | Kosten |
|---|---|---|---|---|
| 1-Seiter (Single-Call, kein Dual) | 1 | 11.250 | 150 | ~$0,0018 |
| 1-Seiter mit Dual-Call¹ | 2 | 22.500 | 300 | ~$0,0036 |
| 3-Seiter (Dual-Call) | 2 | 23.500 | 900 | ~$0,0041 |
| 10-Seiter (Dual-Call) | 2 | 27.000 | 3.000 | ~$0,0059 |
| 50-Seiter (Dual-Call, Max) | 2 | 47.000 | 12.000 | ~$0,0143 |
¹ Aktueller Code: Single-Page-Dokumente überspringen Dual-Call (siehe classifyGpt.js).
Zusätzliche Azure-Kosten (nicht in obiger Tabelle):
- Document Intelligence
prebuilt-read: ~$1,50 / 1.000 Seiten → ~$0,0015/Seite - AI Vision Read (bei Bildern): ~$1,50 / 1.000 Transaktionen
- AI Speech transcribe (bei Audio): ~$1,00 / Stunde
- Function App EP1: Fixkosten ~$180/Monat (egal wie viele Calls)
- Storage / Private Endpoint: ~$10-15/Monat
Faustregel für 10.000 PDF-Dokumente à 5 Seiten:
- GPT: 10.000 × $0,0045 ≈ $45
- DocIntelligence: 50.000 × $0,0015 ≈ $75
- Function/Storage Fixkosten/Monat: ~$200
- Gesamt: ~$320 / 10k Docs (variable Kosten dominieren ab ~15-20k Docs/Monat)
Eval-Run-Kosten (n=32, fast alle Single-Page): ≈ 33 Calls × $0,0018 ≈ $0,06.
MIT — siehe LICENSE.