diff --git a/.gitignore b/.gitignore index 9685e95..17b974d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ server/target/ users.toml *.snap.new repomix-output.xml +tmpdata/ diff --git a/docs/projektplan.md b/docs/projektplan.md index 8d54596..de7d070 100644 --- a/docs/projektplan.md +++ b/docs/projektplan.md @@ -35,6 +35,22 @@ Webinterface (Browser) → nginx → Axum (SSE für Live-Updates) --- +## Design-Prinzipien + +### Filesystem ist Source of Truth (SoT) + +Der Zustand eines Falls wird **ausschließlich aus dem Dateisystem** abgeleitet, nicht aus einer parallelen Sidecar- oder Metadaten-Datei. Ordnerlage (`open/` vs. `done/`) plus Dateiexistenz (`.m4a`, `.transcript.txt`, `oneliner.txt`, `document_vN.md`, `.remove`) bestimmen jederzeit eindeutig, was als Nächstes zu tun ist. + +**Konsequenzen**: +- Keine State-Duplizierung (kein `state.json`, keine DB). Der zu synchronisierende Zweitstand fehlt ersatzlos — also kann er auch nicht drift. +- Recovery-Scan nach Restart ist trivial: finde `.m4a` ohne `.transcript.txt` → transkribieren. Natürliche Idempotenz, kein Crash-Recovery-Protokoll. +- Crash mitten in Whisper = `.transcript.txt` fehlt = nächster Lauf macht's nochmal. Keine Intermediate-States, die rückwärts abgewickelt werden müssten. +- Manuelle Reparatur möglich: Datei löschen, neu triggern. Kein „state irgendwie auf Received setzen". + +**Bewusst verzichtet** auf: retry-Budget, Failed-Kategorie mit Error-Details. Wenn ein Audio dauerhaft an Whisper scheitert, loggt der Server im Crash-Loop — das fällt sofort auf und wird manuell entfernt. Diese Kategorie wird erst eingeführt, wenn ein realer Bedarf entsteht (z.B. hochvolumiger Betrieb). + +--- + ## Komponenten ### 1. Pixel Watch App (Wear OS / Kotlin) @@ -882,3 +898,4 @@ axum-extra = { version = "0.12", features = ["cookie"] } # passend zu axum 0.8 | 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. | | Admin-Log vs. Arzt-UI | Nur Arzt-UI geplant | Zusätzlich frühes Admin-Log unter `/web/` (flache Liste aller Fälle, Transkripte, Oneliner) | Gebaut, bevor das Arzt-UI (Session, States, Fall-Detail) existiert, um die Pipeline während Entwicklung inspizieren zu können. Soll bleiben, aber später hinter `role = "admin"` geschützt; das Arzt-UI wird separat entwickelt. | | Hotwords | Nicht vorgesehen | Per-User-Feld `[user.whisper].hotwords` bleibt **im Code**, wird aber **nicht als Feature angeboten** | Erstes Ad-hoc-Experiment sah 8/8 vs. 3/8 aus. Regress-Lauf über 10 kuratierte Fixtures (`scripts/regress_whisper.sh`) widerlegte das: ohne Hotwords 12/257 Wortfehler (~4.7% WER), mit Hotwords 14/257 — leicht schlechter. Hotwords schluckten sogar Funktionswörter wie „Beginn mit" und „mittels". Kein belegter Nutzen, dafür zusätzliche Bedienkomplexität pro Arzt. KISS: wir lassen die Leitung durchverdrahtet (kein Code-Rückbau), aber bewerben/konfigurieren es nicht. Re-evaluiert, sobald ein konkreter Fachvokabular-Bedarf auftaucht. | +| Case-State-Machine | Nicht vorgesehen | **Verworfen**: explizite State-Machine + `state.json`-Sidecar (Received/Transcribing/Transcribed/Evaluating/Evaluated/Closed/Failed) wurde gebaut und nach Live-Test wieder entfernt. FS bleibt einzige Wahrheitsquelle. | Der Sidecar führte sofort zu Datendrift: bei jeder Schema-/Code-Änderung waren Bestandsfälle inkonsistent (State sagt „fertig", aber `.m4a` ohne `.transcript.txt` vorhanden). Recovery hätte Self-Healing-Checks gegen die FS-Invariante gebraucht — womit `state.json` zur bloßen Cache-Kopie wird und ihre eigentliche Daseinsberechtigung verliert. Erkenntnis in `docs/projektplan.md` → „Design-Prinzipien: Filesystem ist SoT" festgehalten. |