Commit graph

9 commits

Author SHA1 Message Date
Jeuner
cc569d1ecb fix: bekannte Whisper-Halluzinationen filtern statt als Anliegen zu werten
"Untertitelung des ZDF, 2020" (Whisper-Trainingsdaten-Artefakt bei Stille/
sofortigem Auflegen) tauchte 5x identisch auf sehr kurzen Aufnahmen (1,7-4s)
auf und wurde von der Kategorisierung als echtes Anliegen fehlinterpretiert.
transcribe.py erkennt jetzt eine kleine Liste bekannter Whisper-
Halluzinationsphrasen (ZDF/WDR/NDR/SWR-Untertitel-Boilerplate u.ae.) und
leert den Text stattdessen.

Dabei aufgefallen: categorize.kategorisiere() wirft bei leerem Transkript
absichtlich einen Fehler - das griff bisher nie, weil Whisper nie leer
zurueckkam. Ohne Fix waere jedes "Anrufer legt nach dem Signalton sofort
auf" (normales, haeufiges Verhalten) jetzt als Kategorisierungs-Fehler nach
telefon/fehler/ verschwunden statt auf dem Board zu erscheinen.
process_call.verarbeite() faengt das jetzt ab und legt einen regulaeren
"kein Anliegen hinterlassen"-Datensatz an (sonstiges/niedrig).

Die 5 bereits gespeicherten ZDF-Fehleintraege in ablage/ wurden manuell
nachkorrigiert (transkript/auswertung), Stapel-/Archiv-Status blieb
unangetastet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 20:50:30 +02:00
Jeuner
2e767f3737 docs+feat: Loeschung selbstgesteuert statt Cron, README auf aktuellen Stand
pipe.watch prueft die Aufbewahrungsfrist jetzt selbst alle 24h mit (kein
Cron/launchd noetig - der Watcher laeuft ohnehin dauerhaft). README ergaenzt
um Sicher-Modus, Notfall-Alarm, Loeschfristen/Audit-Log und korrigiert eine
falsche Behauptung zur Ansage (Notruf-Hinweis/Aufnahme-Einwilligung fehlen
in der aktuellen telefon/ansage.txt noch tatsaechlich).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 19:32:31 +02:00
Jeuner
989d3ba85b feat: Leitstand-Fixes, Sicher-Modus, Notfall-Alarm, Audit-Log + Loeschfristen
- Audio-Wiedergabe im Leitstand brach durch Auto-Refresh (setInterval) ab,
  Karten-DOM wurde per innerHTML neu gebaut waehrend ein <audio> lief.
  takt() pausiert jetzt, solange eine Aufnahme abgespielt wird. Zusaetzlich
  HTTP-Range-Unterstuetzung fuer /audio/ nachgeruestet (war vorher immer
  volle Datei statt 206 Partial Content).
- Sicher-Modus: Rufnummern per Klick blurren, fuer Screenshots/Aufnahmen.
- Notfall-Alarm: pulsierender Karten-Rahmen + Banner + minuetlicher Ton,
  solange ein Notfall nicht im letzten Stapel (Erledigt) liegt.
- Favicon.
- Neues unveraenderliches audit.log (append-only, macOS uappnd-Flag) als
  Nachweis fuer den Umgang mit Anrufdaten - protokoll.schreibe() spiegelt
  jetzt jedes Ereignis dorthin, zusaetzlich zum rotierenden verlauf.log.
- pipe.loeschen: loescht erledigte Anrufe nach LOESCHFRIST_TAGE (lokal +
  Nextcloud), unbearbeitete Anrufe (nicht im letzten Stapel) bleiben immer
  unangetastet. Gedacht fuer taeglichen Cron-Lauf.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 19:13:09 +02:00
Jeuner
3749d60aa4 fix: Watcher griff Aufnahmen mitten im Schreiben (Race Condition)
Der Fehler "[Errno 2] No such file or directory: '...stt_out_*.json'"
sah wie ein Transkriptions-Bug aus, war aber ein Timing-Problem:
_fertig() prüfte nur, ob die Dateigröße 2 Sekunden (RUHE_S) unveraendert
blieb - eine normale Sprechpause reicht dafuer schon aus, waehrend
FreeSWITCH den Anruf noch aktiv aufnimmt. Belegt im Verlauf-Log: die
Aufnahme wurde um 10:15:56 als "fertig" gegriffen, der Anrufer legte laut
Log aber erst um 10:16:01 auf.

ffmpeg/whisper-cli lasen dabei einen mitten im Schreiben befindlichen
RIFF-Header, produzierten keine Ausgabe, und process_call brach mit einem
FileNotFoundError auf die erwartete JSON-Datei ab. Die Original-Audiodatei
selbst blieb dabei unbeschaedigt (FreeSWITCH schrieb einfach in die schon
nach telefon/fehler/ verschobene Datei weiter) - deshalb sah eine manuelle
Nachpruefung der "kaputten" Datei immer intakt aus und liess sich nicht
reproduzieren.

_fertig() prueft jetzt zusaetzlich per `lsof -t`, ob noch ein Prozess die
Datei offen haelt - nur wenn weder die Groesse waechst noch ein Handle
offen ist, gilt die Aufnahme als abgeschlossen.

Nebenbei: tempfile.mkstemp() in transcribe.py gab einen offenen File-
Descriptor zurueck, der nie geschlossen wurde (fd-Leak in beiden
Tempdatei-Erzeugungen) - behoben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuP3zfgYqhnrwyEne2ejG7
2026-08-23 10:25:22 +02:00
Jeuner
08ca806cda feat: Kontakt-Abgleich gegen Nextcloud-Adressbuch bei eingehenden Anrufen
pipe/kontakte.py: CardDAV-Abgleich (stdlib urllib, gleicher Stil wie
store.py) gegen ein konfigurierbares Adressbuch (NEXTCLOUD_ADRESSBUCH,
Default "contacts"). Rufnummer wird normalisiert (+49/0049/0-Schreibweisen,
Leerzeichen/Bindestriche), Adressbuch 5 Minuten gecacht statt pro Anruf neu
geladen. Ohne Nextcloud-Konfiguration oder bei Ausfall: sauber None, kein
Anruf bricht deswegen ab.

process_call.py speichert den Treffer als "bekannter_kontakt" in meta.json;
server.py zeigt ihn als gruenen Badge neben der Rufnummer im Board - fuer den
Abgleich "Anrufer sagt X, Nummer gehoert laut Adressbuch Y" auf einen Blick.

Nextcloud-Zugangsdaten aktivierten bisher automatisch auch den Datei-Upload
(store.hochladen) - fuer den reinen Kontakt-Abgleich waere das ein unge-
wollter Nebeneffekt, der das "Vollstaendig on-prem"-Versprechen des Projekts
bricht. Neuer Schalter NEXTCLOUD_UPLOAD (Default an, bestehende Installa-
tionen unveraendert) trennt beides.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuP3zfgYqhnrwyEne2ejG7
2026-08-23 02:18:51 +02:00
Jeuner
f77bc6e558 feat: "Tag archivieren"-Button im Leitstand (simuliert)
Raeumt Anrufe aus dem letzten Stapel ("Erledigt") vom Board - markiert sie
per archiviert.json im Anrufordner (nur Zeitstempel, simuliert: true), keine
Datei wird verschoben, exportiert oder geloescht. anrufe() filtert markierte
Anrufe aus der Board-Ansicht, ablage/ bleibt unveraendert. Reversibel durch
Loeschen der Marker-Datei.

Bestaetigung vor dem Archivieren, Toast mit Anzahl danach, Eintrag im
Verlauf-Log. Echtes Archivieren (Export/Retention/Loeschfristen) ist noch
offen - siehe MITGABE.md Abschnitt 6.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuP3zfgYqhnrwyEne2ejG7
2026-08-23 01:35:22 +02:00
Jeuner
a089ffe4d6 fix: FS_PORT konfigurierbar statt hardcoded, DECK_STAPEL-Quoting, Test-Audios
pipe/server.py rief fs_cli mit fest verdrahtetem Port 8099 auf (Wert vom
Ursprungsrechner) statt den Wert aus der Config zu lesen - auf einer Maschine
mit abweichendem Event-Socket-Port (hier 8022) zeigte der Leitstand "Telefon:
unklar", obwohl der Trunk laengst registriert war. Neue FS_PORT-Variable in
pipe/config.py (Default 8021, wie starten.sh es ueber die .env erwartet).

DECK_STAPEL in .env.example war unquotiert - das Leerzeichen in "In
Bearbeitung" bricht das `source .env` in telefon/freeswitch/einrichten.sh
(bash interpretiert es als zweites Kommando). Wert jetzt in Anführungszeichen.

test/erzeuge_beispiele.sh erzeugt die im README/MITGABE referenzierten
Test-Anrufe (rezept/termin/notfall) lokal per macOS "say" - vorher liefen die
Beispielbefehle aus einem frischen Klon ins Leere, weil test/*.wav bewusst
gitignoriert ist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuP3zfgYqhnrwyEne2ejG7
2026-08-23 00:04:12 +02:00
Jeuner
49e40c252b docs: Screenshot des Leitstands und Hinweis auf erfundene Daten
Der Screenshot zeigt das Board mit allen vier Spalten, dem Verlauf darüber und
den Dienste-Ampeln darunter. Die Anrufe darin sind erfunden — er wurde aus einem
eigens angelegten Beispieldatensatz erzeugt, nicht aus dem Betrieb, damit keine
echten Rufnummern ins Repository gelangen.

Dazu ein Hinweis in der README, dass alle Namen, Nummern und Anliegen im
Repository Platzhalter sind, "Praxis Musterhausen" eingeschlossen.

Außerdem: Der Pfad zum Log der Telefonanlage ist jetzt über FREESWITCH_LOG
konfigurierbar statt fest verdrahtet — nötig, um den Leitstand gegen einen
anderen Datenbestand laufen zu lassen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017V57oXiDLi4nSsyau5qaA2
2026-08-22 22:55:29 +02:00
Jeuner
1a1280e79d Praxis-Telefon-Agent: Anrufannahme, Transkription, Triage — vollständig on-prem
Nimmt Anrufe einer Hausarztpraxis entgegen, transkribiert sie lokal,
kategorisiert das Anliegen und legt es strukturiert ab. Keine Patientendaten
verlassen den Rechner: Spracherkennung, Sprachmodell und Sprachsynthese laufen
lokal, im Datenpfad steht kein Cloud-Dienst.

Aufbau:
- FreeSWITCH nimmt über einen SIP-Trunk an, spielt die Ansage und nimmt auf.
- pipe.watch erkennt fertige Aufnahmen und schiebt sie durch die Pipe.
- whisper.cpp transkribiert, ein lokales Sprachmodell (Ollama) ordnet in
  Kategorie und Dringlichkeit ein — mit erzwungenem JSON-Schema.
- Ablage lokal, optional Nextcloud (WebDAV) und ein Deck-Board.
- Leitstand (pipe.server): Board mit Eingang/In Bearbeitung/Rückfragen/Erledigt,
  Aufnahmen zum Anhören, Verlauf und Ampeln für alle beteiligten Dienste.

Zugangsdaten und Anrufdaten liegen außerhalb des Repositorys (.env, ablage/,
telefon/). Konfiguration siehe .env.example, Einrichtung siehe README.
2026-08-22 22:37:17 +02:00