Update project plan with transcription pipeline details

Update task status to 'done' for several items in the project plan. This
includes setting up the Axum server, filesystem structure,
configuration, logging, and upload endpoints. It also covers the Docker
setup for Axum and faster-whisper, as well as Ollama configuration.

Additionally, the plan now reflects a dedicated `whisper/` service for
faster-whisper, detailing its endpoints, model configuration, hotword
support, and deployment. Changes to the Ollama client configuration and
the oneliner generation logic are also documented.

The transcription queue and recovery mechanisms are marked as done,
along with the faster-whisper and Ollama health checks and retries.
Several aspects of the case management and UI development, including
askama templates and the detail view, have been updated.

Finally, the project plan includes a decision to use `large-v3-turbo`
for the Whisper model and details the implementation of a dedicated
`whisper/` service due to hallucination issues with previous wrappers.
The Ollama model is updated to `gemma4:latest`, and the GPU phase worker
logic is adjusted to leverage the smaller VRAM footprint of the
`large-v3-turbo` model. A new script `scripts/dictate.sh` is introduced
as a stand-in for testing the watch-flow without actual hardware.
This commit is contained in:
2026-04-14 10:42:14 +02:00
parent 735718512c
commit 3ea8589985
+37 -20
View File
@@ -704,28 +704,37 @@ axum-extra = { version = "0.12", features = ["cookie"] } # passend zu axum 0.8
## Entwicklungsreihenfolge
### Phase 1 — Server-Grundgerüst
- [ ] Axum Setup, Routing, Authentifizierung (API-Key → Arzt-Mapping)
- [ ] Filesystem-Struktur anlegen
- [ ] .env Konfiguration (dotenvy)
- [ ] Logging (tracing + tracing-appender)
- [ ] Upload-Endpunkt mit ACK-Response
- [ ] Docker Container für Axum auf Ubuntu Server (kein GPU nötig)
- [ ] faster-whisper Container mit HTTP-API (NVIDIA Container Toolkit)
- [ ] Ollama einrichten, `gemma3:4b` pullen
- [x] Axum Setup, Routing, Authentifizierung (API-Key → User-Mapping via `users.toml`)
- [x] Filesystem-Struktur anlegen
- [x] .env Konfiguration (dotenvy)
- [x] Logging (tracing + tracing-appender)
- [x] Upload-Endpunkt mit ACK-Response
- [x] Docker Container für Axum auf Ubuntu Server (kein GPU nötig)
- [x] faster-whisper Container mit HTTP-API (NVIDIA Container Toolkit) — jetzt eigener `whisper/`-Service, siehe Phase 2b.5
- [x] Ollama einrichten, Modell gepullt (`gemma4:latest` statt `gemma3:4b`, siehe Abweichungen)
- [ ] nginx auf Unraid + Let's Encrypt + Security Headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
### Phase 2 — Transkriptions-Pipeline
- [ ] Transkriptions-Queue (tokio mpsc channel)
- [ ] GPU-Phasen-Worker (Greedy-Zyklus: Whisper-Phase → Ollama-Phase → Aufräumen)
- [ ] faster-whisper HTTP-Client (reqwest, POST /transcribe, multipart Audio)
- [ ] Ollama HTTP-Client (reqwest, POST /api/chat, keep_alive: 0)
- [ ] Oneliner-Generierung (einmalig beim ersten Transkript, danach unveränderlich)
- [ ] Queue-Recovery bei Serverstart (Filesystem-Scan)
- [x] Transkriptions-Queue (tokio mpsc channel)
- [ ] GPU-Phasen-Worker (Greedy-Zyklus: Whisper-Phase → Ollama-Phase → Aufräumen) — aktuell kein striktes Phasenmodell, siehe Abweichungen
- [x] faster-whisper HTTP-Client (reqwest, POST `/asr`, multipart Audio)
- [x] Ollama HTTP-Client (reqwest, POST /api/chat) — `keep_alive=300` statt `0`, siehe Abweichungen
- [x] Oneliner-Generierung (einmalig, erst geschrieben wenn ein sinnvoller Oneliner vorliegt; sonst Retry beim nächsten Transkript)
- [x] Queue-Recovery bei Serverstart (Filesystem-Scan)
- [ ] faster-whisper Health-Check + Retry
- [ ] Ollama Health-Check + Retry
- [ ] Lazy Cleanup bei Falllisten-Zugriff (zum Entfernen markierte Fälle vom Vortag löschen)
- [ ] Retention-Prüfung bei Zugriff (Audio/Transkripte nach RETENTION_*_DAYS)
### Phase 2b.5 — Eigener faster-whisper-Service (`whisper/`)
- [x] FastAPI-Wrapper um `faster-whisper` mit fest verdrahteten Anti-Halluzinations-Params (`condition_on_previous_text=False`, `temperature=0.0`, `vad_filter=True`)
- [x] Endpoints: `POST /asr` (txt/json), `GET /health`, `GET /info`
- [x] Modell `large-v3-turbo` via `WHISPER_MODEL` umschaltbar, Modelle persistent in Docker-Volume
- [x] Hotwords-Support (faster-whisper 1.2.1) als Form-Feld durchgereicht
- [x] Dockerfile (CUDA 12.1 + cuDNN), `docker-compose.yml`, `deploy.sh` (rsync + `/health`-Polling)
- [x] README mit Begründung (Halluzinations-Befund), Build- und Deploy-Anleitung
- [x] Deployed auf minerva:9001, `WHISPER_URL` in `server/.env` umgestellt
### Phase 3 — Fallverwaltung
- [ ] Drei States: Empfangen → Transkribiert → Ausgewertet
- [ ] State-Übergang: erst "Transkribiert" wenn alle Aufnahmen eines Falls fertig
@@ -747,11 +756,11 @@ axum-extra = { version = "0.12", features = ["cookie"] } # passend zu axum 0.8
- [ ] `AuthenticatedArzt`-Extractor: Session-Token → Arzt auflösen, bei ungültiger/abgelaufener Session → Redirect `/web/login`
- [ ] `ValidCaseId`-Extractor: `case_id` als UUIDv4 validieren (`uuid::Uuid::parse_str`), bei Fehler → 400
- [ ] Login-Seite (`GET /web/login`, `POST /web/login`)
- [ ] askama Templates
- [x] askama Templates (Listenansicht `cases.html`)
- [ ] SSE-Endpunkt (`GET /web/events`), Session-Check bei Aufbau + periodisch bei Heartbeat
- [ ] Vanilla-JS EventSource-Client
- [ ] Übersicht mit drei States (Empfangen/Transkribiert/Ausgewertet)
- [ ] Fall-Detail: Transkripte pro Aufnahme einsehen
- [ ] Übersicht mit drei States (Empfangen/Transkribiert/Ausgewertet) — aktuell nur flache Liste mit Transkript + Oneliner
- [x] Fall-Detail: Transkripte pro Aufnahme einsehen (inline in Listenansicht, Audio-Stream `/web/audio/...`)
- [ ] Fall abschließen / Alle abschließen
- [ ] Dokumentansicht
- [ ] Nachtrag-Hinweis bei Aufnahmen nach Abschluss (⚠ "N neue Aufnahmen seit Abschluss")
@@ -765,6 +774,8 @@ axum-extra = { version = "0.12", features = ["cookie"] } # passend zu axum 0.8
**Hinweis:** Ein früher Proof-of-Concept (minimale Aufnahme + Upload auf echter Pixel Watch) sollte parallel zu Phase 23 stattfinden, um Wear-OS-spezifische Einschränkungen (Doze-Mode, Foreground-Service-Limits, Battery-Optimization) frühzeitig aufzudecken.
**Stand-in, solange keine Hardware:** `scripts/dictate.sh` simuliert den Watch-Flow via ffmpeg-PulseAudio-Aufnahme + Upload gegen den laufenden Server (Modi: neuer Fall / aktuellen Fall fortsetzen / refresh). State in `/tmp/doctate-current-case`. So lassen sich Transkription und Oneliner ohne Watch testen.
- [ ] Wear OS Projekt in Android Studio
- [ ] Jetpack Compose UI (ein Screen pro Eintrag, fullscreen, Swipe-Navigation)
- [ ] Vertikale Fallliste (Neu ganz oben, heutige Fälle darunter)
@@ -823,15 +834,15 @@ axum-extra = { version = "0.12", features = ["cookie"] } # passend zu axum 0.8
| Thema | Status |
|---|---|
| Whisper Modell | large-v3 via faster-whisper (CTranslate2, int8) — ggf. large-v3-turbo oder deutsches Fine-Tuning |
| Ollama Modell | Gemma 3 4B — ggf. Llama 3.1 8B falls Qualität nicht reicht |
| Whisper Modell | **Entschieden:** `large-v3-turbo` via eigener `whisper/`-Service, float16, optional Hotwords per Request. Qualitativer Vergleich gegen `large-v3` zeigte: gleichwertig bei Fachvokabular, nur Hotwords bringen echte Fehlerreduktion. Deutsches Fine-Tuning (primeline) getestet, lieferte schlechtere Ergebnisse. Umschaltbar via `WHISPER_MODEL`. |
| Ollama Modell | **Entschieden (vorläufig):** `gemma4:latest` (läuft). Llama 3.1 8B als Option, wenn Oneliner-Qualität nicht reicht. |
| Ionos Modell | Noch zu evaluieren |
| Watch Hardware | Pixel Watch 2 oder 3 (LTE empfohlen) |
| Authentifizierung Browser | Entschieden: Serverseitiges Session-Token (256-Bit, Cookie mit `Secure`/`HttpOnly`/`SameSite=Strict`), Ablauf 8 h. User-Identität ausschließlich aus Session, nie aus URL (IDOR-Prävention). Login gegen User-Passwort in `users.toml`. |
| Prompt-Qualität | Erfordert Testing mit echten Diktaten |
| Transkript editierbar? | Read-only oder editierbar vor Abschluss — offen |
| Sicheres Löschen | Reicht rm oder Overwrite nötig? — offen |
| faster-whisper HTTP-API | Bestehender Container nutzen oder eigener FastAPI-Wrapper — offen |
| faster-whisper HTTP-API | **Entschieden:** eigener FastAPI-Wrapper (`whisper/`). Grund siehe Phase 2b.5 / Abweichungen. |
| Recovery-UI (Webinterface) | Dedizierte Ansicht für verspätet eingetroffene Aufnahmen (Anhören / Ins Dokument / Verwerfen) — optional, Bedarf im Echtbetrieb evaluieren |
---
@@ -858,3 +869,9 @@ axum-extra = { version = "0.12", features = ["cookie"] } # passend zu axum 0.8
| m4a-Preprocessing | nicht erwähnt | Server remuxt m4a mit `-movflags faststart` vor Whisper-Aufruf | Bekannter Bug in whisper-asr-webservice ([Issue #97](https://github.com/ahmetoner/whisper-asr-webservice/issues/97)): m4a-Dateien >20s liefern leere Transkripte, weil Android `MediaRecorder` das `moov atom` ans Dateiende schreibt. `faststart`-Remuxing (verlustfrei, <1s) verschiebt die Metadaten an den Anfang und behebt das Problem. ffmpeg wird dafür im Axum-Container mitgeliefert. |
| Terminologie | "Arzt/Ärzte" | "User" | Rollen-System: nicht nur Ärzte, auch MTAs und ggf. Admins |
| Erster Upload neue case_id | "Fall unbekannt → ACK gone" | Neuer Fall anlegen, ACK "received" | Sonst würde der allererste Upload einer neuen case_id immer abgelehnt. "gone" nur für gelöschte Fälle. |
| STT-Container | `onerahmet/openai-whisper-asr-webservice` | Eigener `whisper/`-FastAPI-Wrapper auf Port 9001 | Kein verfügbarer Wrapper (ahmetoner, speaches, linuxserver, hwdsl2) reicht `condition_on_previous_text=False` und feste `temperature=0.0` durch. Tests zeigten 2/13 Runs mit katastrophalen Halluzinationen (russisch/chinesisch) bei `large-v3` mit Wrapper-Defaults. Eigener Service hardcodet die Anti-Halluzinations-Params. |
| Whisper-Modell | `large-v3` int8 | `large-v3-turbo` float16 + optional Hotwords | Turbo: gleicher 32-Layer-Encoder wie large-v3, Decoder destilliert auf 4 Layer — gleiche Qualität auf europäischen Sprachen, ~1.6 GB VRAM statt ~3 GB. Ermöglicht Koexistenz mit Ollama im 12-GB-VRAM. Hotwords (faster-whisper 1.2.1) bringen den größten Qualitätssprung bei Fachvokabular (in Experiment 8/8 korrekt vs. 3/8 ohne). |
| Ollama-Modell | `gemma3:4b` | `gemma4:latest` | Neuere Version beim Aufsetzen des Ollama-Containers verfügbar. Llama 3.1 8B als Fallback, falls Oneliner-Qualität nicht reicht. |
| GPU-Phasen-Worker | Strikter Whisper↔Ollama-Wechsel mit Ollama `keep_alive=0` | Turbo (~1.6 GB) + Ollama gemma4 (~9 GB) bleiben parallel geladen, `OLLAMA_KEEP_ALIVE=300` | Dank Turbo-VRAM-Footprint passen beide gleichzeitig in die GPU. Spart das Modell-Reload (~2 s pro Oneliner). Strikter Phasenwechsel bleibt als Fallback, falls später größere Modelle (`large-v3`, Llama 3.1 8B) zurückkehren. |
| faster-whisper Port | 10300 (ahmetoner) | 9001 (eigener Service) | Neuer Service auf freiem Port; alter Container optional parallel belassen. |
| Test-Client für Watch-Flow | Erst ab Phase 5 mit echter Hardware | `scripts/dictate.sh` ab Phase 2/3 als Stand-in (ffmpeg + curl + interaktiver c/n/r/q-Loop) | Erlaubt End-to-End-Tests der Server-Pipeline ohne Watch-Hardware, solange die Pixel Watch nicht verfügbar ist. |