diff --git a/docs/projektplan.md b/docs/projektplan.md index 49b1bcc..3e79717 100644 --- a/docs/projektplan.md +++ b/docs/projektplan.md @@ -117,7 +117,7 @@ UI-unabhängige Tests laufen auf der JVM (keine Emulator-Runtime). Importe von ` #### 1a. Pixel Watch App (Wear OS / Kotlin) — primäres Entwicklungsziel -**Ist-Stand (2026-04-23):** Vertikaler PoC end-to-end lauffähig auf Wear-OS-34-AVD. Record-Button → 5 s MediaRecorder (MPEG-4/AAC-LC 16 kHz 64 kbps mono) → Multipart-`POST /api/upload` mit `X-API-Key` → Server schreibt `.m4a`, Whisper transkribiert, Ollama klassifiziert, `document.md` entsteht. Manuell verifiziert mit "Hallo, hallo" → korrekte Transkription + `oneliner.kind = empty` (Silence-Rule). Architektur-Unbekannte aus der vertikalen Scheibe (Audio-Codec-Kompatibilität, Cleartext-HTTP-Policy, Emulator-Mic, Wire-Vertrag) sind **gemessen**, nicht mehr spekulativ. Der PoC hat aber **bewusst** nicht: Fallliste, Marker-Dateien, `unsynced/`-Queue, Foreground Service, Retry-Backoff, Oneliner-Polling, Bluetooth-Headset, Neu/Fortsetzen-Flow — diese Features bleiben laut Plan zu bauen. +**Ist-Stand (2026-04-23):** Vertikaler PoC end-to-end lauffähig auf Wear-OS-34-AVD **und auf realer Pixel Watch 2** (Wear OS 5, ADB-over-WiFi-Pairing). Record-Button → 5 s MediaRecorder (MPEG-4/AAC-LC 16 kHz 64 kbps mono) → Multipart-`POST /api/upload` mit `X-API-Key` → Server schreibt `.m4a`, Whisper transkribiert, Ollama klassifiziert, `document.md` entsteht. Manuell verifiziert mit "Hallo, hallo" → korrekte Transkription + `oneliner.kind = empty` (Silence-Rule). Architektur-Unbekannte aus der vertikalen Scheibe (Audio-Codec-Kompatibilität, Cleartext-HTTP-Policy, Emulator-Mic, Wire-Vertrag) sind **gemessen**, nicht mehr spekulativ. Toolchain unterstützt beide Targets parallel: `build.gradle.kts` liest Gradle-CLI-Property `-Pdoctate.serverUrl=…` vor `local.properties` (damit Emulator auf `10.0.2.2:3000` und Watch auf `http://:3000` ohne Dateitausch wechseln); `network_security_config.xml` whitelistet die LAN-IP zusätzlich zu `10.0.2.2` für Cleartext (Produktions-HTTPS bleibt via `base-config` gesperrt); `run.sh` hat ein `watch`/`emulator`-Target-Prefix plus `connect`/`devices`-Subkommandos und cacht die zuletzt gepairte Watch-Serial in `.watch_serial`. Der PoC hat aber **bewusst** nicht: Fallliste, Marker-Dateien, `unsynced/`-Queue, Foreground Service, Retry-Backoff, Oneliner-Polling, Bluetooth-Headset, Neu/Fortsetzen-Flow — diese Features bleiben laut Plan zu bauen. **Funktion:** - Audioaufnahme direkt auf der Watch (MediaRecorder, AAC/m4a) @@ -317,7 +317,7 @@ Wenn Watch und Handy gekoppelt sind (klassisches Wear-OS-Pairing), *könnten* be #### 1c. Desktop-Clients (Linux / Windows) — erster nativer Client -**Ist-Stand:** Der Linux-Desktop-Client ist gebaut und funktioniert (`client-desktop/`). Er ist der **erste echte native Client überhaupt** — wurde vor der Watch-App gebaut, weil der Nutzer noch keine Pixel-Watch-Hardware hat und ein realer Client für API-Ergonomie-Tests (Retry, Content-Types, Auth-Fehlerpfade) nötig war. +**Ist-Stand:** Der Linux-Desktop-Client ist gebaut und funktioniert (`client-desktop/`). Er ist der **erste echte native Client überhaupt** — wurde vor der Watch-App gebaut, weil zu dem Zeitpunkt noch keine Pixel-Watch-Hardware verfügbar war und ein realer Client für API-Ergonomie-Tests (Retry, Content-Types, Auth-Fehlerpfade) nötig war. (Pixel Watch 2 Hardware eingetroffen am 2026-04-23.) **Features (MVP):** - Config-Panel beim Erststart (Server-URL + API-Key, TOML unter `~/.config/doctate/client.toml`) @@ -1156,6 +1156,7 @@ wiremock = "0.6" - [x] Fall-Übersicht: `GET /web/cases/{case_id}` rendert `case_page.html` mit Oneliner + Aktionen + inline-gerendertem Dokument (via `pulldown-cmark`); IDOR-geschützt via Session-Slug. - [x] Einzel-Transkripte: `GET /web/cases/{case_id}/recordings` rendert `case_recordings.html` (ein Eintrag pro Aufnahme, Audio-Link pro Transkript). - [x] Audio-Streaming: `GET /web/audio/{user}/{case_id}/{filename}` (Cookie-Auth, Arzt eigene Dateien oder Admin) — HTTP-Range-Requests, `Accept-Ranges: bytes`, 206 Partial Content, Duration-Sidecar `{ts}.duration.txt` für HTML5-Player mit Seeking +- [ ] Replay-Gain-Normalisierung für Wiedergabe (nicht destruktiv) — verschiedene Erfassungsgeräte liefern stark unterschiedliche Pegel (Watch `MIC` ~-38 dB mean, Desktop ~-27 dB mean). PoC am 2026-04-23 erfolgreich, aber nicht committed; Re-Implementierung steht aus. Erprobte Architektur: pro `.m4a` ein `.loudness.json`-Sidecar mit statischem `gain_db` (aus `ffmpeg -af volumedetect`, Ziel −16 dB mean, Peak-Cap −1 dB). Lazy-Backfill im bestehenden `scan_recordings`-JoinSet analog zum Duration-Sidecar. Browser appliziert den Gain über Web Audio API `GainNode` (kein Disk-Rewrite, Original + Whisper unberührt). Kombiniert mit `AudioSource.VOICE_RECOGNITION` auf der Watch (besseres SNR, siehe 5b). Verworfene Alternativen: `ffmpeg loudnorm` (pumpt + Artefakte), fixes `volume=+XdB` (client-abhängig). Kern-Einsicht: SNR > Loudness an der Source, solange Post-Gain verfügbar ist. - [x] Fall analysieren — Button in der Fall-Übersicht - [x] Bulk-Aktionen (alle markierten analysieren / löschen) über `POST /web/cases/bulk` — **admin-only** (`AuthenticatedUser::is_admin()` auf `role == "admin"`, Check am Entry-Handler). - [x] Purge-Closed (`POST /web/cases/purge-closed`, `confirm=yes` Pflicht) — **admin-only**, entfernt geschlossene Fälle hart, emittiert `CaseEventKind::CasePurged` pro entferntem Case. @@ -1180,7 +1181,7 @@ wiremock = "0.6" **Hinweis:** Ein früher Proof-of-Concept (minimale Aufnahme + Upload auf echter Pixel Watch) sollte parallel zu Phase 2–3 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 Client-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 native Clients testen. +**Schneller Server-Test ohne Client:** `scripts/dictate.sh` simuliert den Client-Flow via ffmpeg-PulseAudio-Aufnahme + Upload gegen den laufenden Server (Modi: neuer Fall / aktuellen Fall fortsetzen / refresh). State in `/tmp/doctate-current-case`. Ursprünglich als Hardware-Stand-in entstanden; bleibt nützlich für Iterationen am Server, die keinen Watch-/Desktop-Build-Zyklus rechtfertigen. #### 5a — Gemeinsame Code-Basis (vor jeder UI-Arbeit) - [ ] Gradle-Multi-Modul-Projekt aufsetzen (`:core-domain`, `:core-audio`, `:core-sync`, `:core-http`, `:core-storage`, `:app-wear`, `:app-mobile` als Platzhalter) — PoC (2026-04-23) läuft vorerst paket-basiert in einem `:app`-Modul; Split wird spätestens beim Start der Handy-App fällig (siehe Abweichungen → Client-Architektur). @@ -1194,6 +1195,7 @@ wiremock = "0.6" - [ ] Vertikale Fallliste (Neu ganz oben, heutige Fälle darunter) - [ ] Nach Stop: Screen bleibt auf aktuellem Fall (→ "Fortsetzen" direkt sichtbar) - [x] MediaRecorder-Integration via `:core-audio` — in `audio/AudioRecorder.kt` (MPEG-4/AAC-LC, 16 kHz mono, 64 kbps, `context.cacheDir`). Landet bei künftigem Multi-Modul-Split in `:core-audio`. +- [ ] `AudioSource.MIC` → `VOICE_RECOGNITION` umstellen — Wear-OS-DSP (Noise-Suppression, AGC) liefert saubereres Signal bei niedrigerem Rohpegel. Geht nur zusammen mit serverseitigem Replay-Gain (siehe Phase 4), weil VR allein ~9 dB leiser ist als MIC. PoC am 2026-04-23 bestätigt: VR + Replay-Gain klingt deutlich besser als MIC + Replay-Gain (letzteres verstärkt auch Rauschen mit). - [ ] Bluetooth-Headset Erkennung + Indikator - [~] case_id (UUIDv4) Generierung: Neu → neue case_id, Fortsetzen → bestehende — PoC erzeugt `CaseId.new()` pro Aufnahme (`domain/CaseId.kt`). Neu/Fortsetzen-Dualität fehlt, weil keine Fallliste-UI existiert. - [~] Persistente Speicherung im lokalen Dateisystem (UTC-Timestamps in Dateinamen) — PoC speichert in `cacheDir` und **löscht nach Upload**; Datei lebt also nur während der laufenden Pipeline. `unsynced/`-Pattern (überlebt Neustarts) fehlt. @@ -1208,7 +1210,7 @@ wiremock = "0.6" - [ ] (optional) Speicherplatz-Warnung bei >500 MB ungesyncten Daten - [ ] Stop → direkt in Sync-Queue, ✓-Feedback, Screen bleibt auf aktuellem Fall - [ ] Oneliner-Polling (alle paar Sekunden für den sichtbaren Fall, bis Oneliner empfangen) -- [~] Emulator-Tests + Pixel Watch Hardware-Test — vertikaler PoC im Emulator manuell verifiziert (Aufnahme → Upload → Whisper → Ollama → `document.md`). Instrumented MockWebServer-Test (`UploadClientTest`) hängt auf Wear-OS-34-AVD (siehe Abweichungen). Hardware-Test steht aus. +- [~] Emulator-Tests + Pixel Watch Hardware-Test — vertikaler PoC im Emulator **und auf realer Pixel Watch 2** (ADB-over-WiFi, LAN-Dev-Server) manuell verifiziert (Aufnahme → Upload → Whisper → Ollama → `document.md`). Instrumented MockWebServer-Test (`UploadClientTest`) hängt auf Wear-OS-34-AVD (siehe Abweichungen). Vollständige Feldtests (LTE-Modus, Akku-Lauf, Doze-Modus, Langzeit-Foreground-Service) stehen weiter aus. #### 5c — Android-Handy-App (nach Watch-Abschluss) - [ ] `:app-mobile`-Modul auf bestehende `:core-*`-Basis aufsetzen @@ -1244,10 +1246,10 @@ wiremock = "0.6" - [x] Invariante gewahrt: jeder Client = weiterer HTTP-Client, kein Server-Code-Ausbau nötig ### Phase 6 — Integration & Testing -- [ ] End-to-End Test (Watch → Server → Webinterface) +- [~] End-to-End Test (Watch → Server → Webinterface) — PoC-Pfad auf realer Pixel Watch 2 (2026-04-23, ADB-over-WiFi, LAN-Server) einmalig durchgelaufen; automatisierter Testlauf steht aus. - [~] End-to-End Test (Linux-Desktop → Server → Webinterface inkl. Oneliner-Poll-Refresh und Magic-Link-Handoff in den Browser) — im Alltagsbetrieb validiert, aber kein dedizierter automatisierter E2E-Testlauf - [~] SSE Live-Updates testen — Unit-Tests im `events`-Modul (Capacity, Lagged, Subscriber-Fanout), `server/tests/sse_integration.rs` + `server/tests/sse_cleanup_test.rs` (Connection-Pool-Leak) -- [ ] Pixel Watch Hardware-Test +- [~] Pixel Watch Hardware-Test — erster Hardware-Lauf am 2026-04-23 erfolgreich (Pairing, Install, Aufnahme, Upload, Transkript). Ausstehend: LTE-Modus, Doze-Modus, Langzeit-Foreground-Service. - [ ] LTE-Modus testen - [ ] Bluetooth-Headset testen - [ ] Sync-Service testen (Netzwerkausfall, Neustart, Doze)