Commit graph

33 commits

Author SHA1 Message Date
Jeuner
9f2429b312 feat: Logo im Header, E-Mail-Adresse auf der Karte gestylt
Logo (ComfyUI/z-image-turbo generiert: Telefonhoerer + KI-Schaltkreis-Motiv,
docs/logo.png) als Base64 direkt im Header eingebettet - konsistent mit dem
bereits inline eingebetteten Favicon, kein neuer Static-Asset-Serving-Pfad
noetig. Funktioniert in Hell und Dunkel (weisse Kreis-Badge).

a.mail-Klasse fuer den mailto:-Link auf der Karte ergaenzt (vorher
unstyled), gleiche gepunktete Unterstreichung wie bei Telefonnummer-Links.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 00:18:25 +02:00
Jeuner
4448e58031 feat: E-Mail-Extraktion + deterministische @-Reparatur
Whisper transkribiert gesprochenes "at" beim Diktieren einer E-Mail manchmal
als Punkt statt "@" (beobachtet: "gdg at dillenberg punkt net" ->
Transkript "gdg.dillenberg.net", komplett ohne @). Zwei Massnahmen:

1. WHISPER_PROMPT um ein Formatierungsbeispiel ergaenzt (max.mueller@
   example.de) - bekannter Whisper-Trick, um Formatierungskonventionen ueber
   den initial_prompt zu primen. Reduziert das Problem an der Quelle, bleibt
   aber modellabhaengig.
2. Neues Schema-Feld "email" (categorize.py, wie rueckrufnummer). Deter-
   ministische Reparatur per Regex: "wort.domain.tld" ohne @ wird zu
   "wort@domain.tld" - aber nicht stillschweigend, sondern im Leitstand als
   "⚠️ vermutete Korrektur" markiert (koennte auch eine genannte Subdomain
   sein, daher pruefbar statt automatisch als Fakt behandelt).

Getestet mit dem realen Fall (Brunski Beat, "gdg.dillenberg.net" ->
"gdg@dillenberg.net" + Korrektur-Flag), bereits gespeicherter Anruf
nachkorrigiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 00:12:42 +02:00
Jeuner
9f64d94b3f feat: Verlauf-Log beim Tag archivieren sichern und leeren
"Tag archivieren" archiviert jetzt zusaetzlich das Verlauf-Log (Kopie nach
telefon/verlauf-archiv/verlauf_JJJJMMTT-HHMMSS.log) und leert es danach -
frische Verlaufsansicht fuer den neuen Tag. Das dauerhafte, unveraenderliche
audit.log ist davon nicht betroffen, das behaelt ohnehin alles fuer immer.

Live getestet: 530 Zeilen archiviert, Log korrekt geleert (nur noch die
Bestaetigungszeile drin).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 00:00:36 +02:00
Jeuner
bc588072bd ui: schlanke, themebewusste Scrollbar fuer den Verlauf
Vorher Standard-Browser-Scrollbar. Jetzt duenn und in --muted eingefaerbt,
sowohl fuer Firefox (scrollbar-width/-color) als auch WebKit/Chromium
(::-webkit-scrollbar).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 23:57:28 +02:00
Jeuner
be16474c82 ui: Dienste-Ampeln unter das Menue verschoben, ueber den Verlauf
Vorher ganz unten nach dem Board - Diensteausfaelle (Telefon/Verarbeitung/
Spracherkennung/Nextcloud) sollten aber auf den ersten Blick sichtbar sein,
nicht erst nach Scrollen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 23:56:24 +02:00
Jeuner
8b4baca5f0 fix: Sicher-Modus blurrt jetzt auch Rufnummern im Verlauf-Log
Anruf-IDs im Log ("23-44-01_004915223062462" - Zeitstempel+Nummer aus
ordner.name) waren bisher vom Sicher-Modus nicht erfasst, nur die
Kartenansicht. telBlur() umschliesst gezielt Ziffernfolgen ab 9 Stellen mit
einem blurrbaren Span - Datum/Uhrzeit-Anteile (<=8 Ziffern in diesem Format)
und der restliche Log-Text bleiben unangetastet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 23:54:05 +02:00
Jeuner
fe8d48932f rebrand: Projekt heisst jetzt Logpy:AgentOne
README-Titel, Leitstand-Browsertitel/-Header und Footer angepasst. Technischer
Name bleibt vorerst fonagent-one (Repo-Slug, Python-Package, Ordnerpfade) -
Rename dort waere ein groesserer, schwerer umkehrbarer Schritt (bricht den
bereits geteilten GitHub-Link), dafuer erst Rueckmeldung einholen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 23:52:05 +02:00
Jeuner
4bd844785a docs: Bewertungssystem und Notfall-Sicherheitsnetz im README dokumentiert
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 23:48:21 +02:00
Jeuner
90548944a2 feat: Bewertungssystem (👍/👎) als Trainingsdaten-Export
Jede Karte im Leitstand bekommt Daumen hoch/runter fuer die Kategorisierung.
Bei 👎 optional Korrektur (richtige Kategorie/Dringlichkeit). Gespeichert
als bewertung.json pro Anruf-Ordner (gleiches Muster wie stapel.json,
pipe/bewertung.py).

Export als JSONL (pipe/export.py, neuer "📤 Bewertungen exportieren"-Knopf
+ automatisch beim "Tag archivieren"): ein Datensatz pro bewertetem Anruf
mit Transkript, Modell-Ausgabe, Backend/Modell, Bewertung und Korrektur -
fuer externe Auswertung (z.B. um Kategorisierungs-Fehler systematisch statt
zufaellig zu finden, wie das Notfall-Sicherheitsnetz vom letzten Fund).

Export-Knopf verlangt Bestaetigung ("Transkripte und Namen verlassen den
Rechner") - bewusst kein automatischer Export ohne Hinweis, das steht sonst
im Widerspruch zum On-Prem-Versprechen. training/ ist neu gitignored (volle
Transkripte/Namen drin, siehe README-Hinweis zu Testdaten).

Nebenbei: process_call.py legt jetzt auch backend/modell/dauer_s der
Kategorisierung dauerhaft in meta.json ab (vorher nur im fluechtigen Log) -
noetig, damit der Export nachvollziehbar ist, welches Modell lief.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 23:47:20 +02:00
Jeuner
7e4505d9b7 feat: Failover auf lokales Ollama-Modell, wenn OpenRouter fehlschlaegt
OpenRouter (v.a. :free-Tier) ist nachweislich instabil (502 "temporarily
overloaded", Latenz-Ausreisser bis 80s, siehe vorherige Tests). Bisher
ist ein Anruf dabei komplett gescheitert und nach telefon/fehler
verschwunden. kategorisiere() faengt KategorisierungsFehler von OpenRouter
jetzt ab und faellt automatisch auf config.OLLAMA_MODELL zurueck - der
Anruf kommt so oder so durch. failover_von landet mit im Live-Log
("⚠ Failover von ..."), damit Ausfaelle sichtbar/auditierbar bleiben.

Getestet mit erzwungenem OpenRouter-Fehler (ungueltige Modell-ID, 400) -
Failover greift, Ergebnis kommt trotzdem korrekt an.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 22:23:07 +02:00
Jeuner
38707b46b6 fix: Footer-Attribution auf "Dilles" statt "Jeuner" (Gaming-Handle)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 22:15:13 +02:00
Jeuner
11bf1a76b4 feat: Footer mit Projekt-Link, GitHub-Icon und Spendenlink
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 22:13:19 +02:00
Jeuner
00176916d7 fix: Notfall-Sicherheitsnetz, Board-Refresh-Bug, Verbindungsverlust-Anzeige
Notfall-Sicherheitsnetz (categorize.py): qwen3:8b stufte "starke
Brustschmerzen ... kriege kaum Luft" reproduzierbar (3/3 Testlaeufe) nur als
"hoch" statt "notfall" ein, obwohl es selbst "Brustschmerzen"+"Atemnot" als
Stichworte erkannte und der Prompt genau diese als Notfall-Beispiele nennt.
Bei Notfall-Erkennung darf man sich nicht allein aufs LLM verlassen -
_notfall_sicherheitsnetz() prueft den rohen Transkripttext deterministisch
gegen die im Prompt selbst genannten Symptome (Atemnot, Brustschmerz,
Bewusstlosigkeit, starke Blutung) und eskaliert nur nach oben, nie runter.

Board-Refresh-Bug (server.py): der Audio-Fix aus einem frueheren Commit
("audio:not([paused])") pruefte ein HTML-Attribut, das <audio> nie gesetzt
hat - der Selektor traf auf JEDES Audio-Element zu, egal ob es lief. Das
Board hat sich seitdem praktisch nie mehr neu gezeichnet, sobald irgendeine
Karte eine Aufnahme hatte (also fast immer) - erklaert "Dashboard
aktualisiert manchmal nicht". Jetzt echte .paused-DOM-Eigenschaft geprueft.

Verbindungsverlust-Anzeige: bisher schluckte hole() Netzwerkfehler still,
das Board blieb unbemerkt eingefroren. Neues Banner ab 2 aufeinanderfolgenden
fehlgeschlagenen Poll-Versuchen (~6s), zeigt Zeitpunkt der letzten
erfolgreichen Aktualisierung, verschwindet automatisch bei Erholung.

Bereits gespeicherter Peter-Fuchs-Fall (Brustschmerz+Atemnot) manuell auf
notfall/notfall nachkorrigiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 22:08:38 +02:00
Jeuner
2afdbc5f42 feat: genannte Rueckrufnummer auf der Karte anzeigen, wenn abweichend von CLIP
auswertung.rueckrufnummer wurde schon immer extrahiert, aber im Leitstand
nie angezeigt - nur die CLIP-Nummer war sichtbar. Ruft jemand von fremdem
Anschluss an und nennt eine andere Rueckrufnummer, ging die im UI unter.
Neue Zeile "☎️ Rückruf an: ..." nur wenn sie sich von der CLIP-Nummer
unterscheidet (sonst Redundanz), klickbar (tel:), mit demselben "Text"-
Quellenlabel wie beim Namen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 21:46:20 +02:00
Jeuner
3e2d829542 feat: OpenRouter-Backend + Modell-Dropdown im Leitstand, Live-Log fuer Dauer/Tokens/Kosten
Zweites Kategorisierungs-Backend neben Ollama (pipe/categorize.py), fuer
Vergleichstests mit staerkeren Cloud-Modellen. Umschaltbar per Dropdown oben
rechts im Leitstand (pipe/modellwahl.py, geteilte telefon/modell.json) - der
laufende Watcher braucht dafuer keinen Neustart, die Auswahl wird bei jedem
Anruf frisch gelesen.

Jede Kategorisierung loggt jetzt Backend/Modell/Dauer/Tokens/Kosten mit ins
Leitstand-Verlauf (protokoll.schreibe), nicht nur versteckt in meta.json.

Testergebnis (Nemotron ueber OpenRouter, :free-Tier): stark schwankende
Latenz (8,5s bis 80s je nach Modell/Auslastung), einmal 502 "temporarily
overloaded" - :free-Modelle sind fuer spaeter geplanten Echtzeit-Dialog
ungeeignet, dafuer braucht es ein bezahltes/dediziertes Tier.

telefon/modell.json ist Laufzeit-Zustand, kein Quellcode - neu ignoriert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 21:43:30 +02:00
Jeuner
4109aee1bb docs+fix: qwen3:8b als durchgaengigen Default statt qwen3.5:latest
qwen3.5:latest traegt unnoetigen Vision-Ballast (CLIP-Projector) fuer eine
reine Text-Kategorisierungsaufgabe. qwen3:8b ist kleiner (5,2 statt 6,6 GB)
und reines Textmodell. Code-Fallback in config.py, .env.example, README und
docs/MITGABE.md konsistent angepasst - HANDOFF.md bleibt unangetastet
(datierter Zeitpunkt-Stand, keine Anleitung).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 21:20:02 +02:00
Jeuner
391eec5f41 feat: Namensquelle auf der Karte sichtbar machen (Text vs. Telefonbuch)
Der Kartenname kam bisher unmarkiert vom LLM aus dem Transkript (Vermutung,
kein Abgleich) - jetzt ein "Text"-Label direkt am Namen, analog zum
bestehenden kontakt-badge fuer den echten Rufnummer-Abgleich gegen das
Adressbuch. Grundlage fuer einen spaeteren Abgleich beider Quellen
gegeneinander (geplant, noch nicht umgesetzt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-24 20:57:43 +02:00
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
0f550be66a feat: Mistral Voxtral als drittes Ansage-TTS-Backend, neuer Ansage-Text
ansage_bauen.sh unterstuetzt jetzt ANSAGE_TTS=mistral neben piper/say -
einmalige Cloud-Erzeugung der ansage.wav (kein Live-Anrufdaten-Transfer,
die fertige Datei wird danach lokal fuer jeden Anruf wiederverwendet).
Inline implementiert statt vom globalen Claude-Skill abhaengig, damit es
auf jeder Maschine mit nur MISTRAL_KEY in der .env laeuft.

Ansage-Text ueberarbeitet ("Demo Praxis", Kollegen im Gespraech, kuerzer
gehalten) und mit gb_oliver_neutral vertont.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuP3zfgYqhnrwyEne2ejG7
2026-08-23 11:38:16 +02:00
Jeuner
1aca65b8e6 docs: Hinweis dass LEITSTAND_HOST=0.0.0.0 alle Interfaces bindet
Aus einem Security-Review: 0.0.0.0 bindet nicht nur ein VPN wie Tailscale,
sondern jedes Netzwerk-Interface des Rechners - laeuft er zusaetzlich in
einem normalen WLAN/LAN, ist der (passwortgeschuetzte) Leitstand auch dort
erreichbar. Fuer diese Installation ist die Tailscale-Erreichbarkeit
gewollt; der Hinweis dokumentiert das Verhalten fuer kuenftige Einrichtungen,
die versehentlich mehr oeffnen als beabsichtigt. Alternative: die konkrete
VPN-IP statt 0.0.0.0 eintragen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuP3zfgYqhnrwyEne2ejG7
2026-08-23 10:41:49 +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
878e49d372 docs: NEXTCLOUD_UPLOAD und NEXTCLOUD_ADRESSBUCH in .env.example ergänzt
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuP3zfgYqhnrwyEne2ejG7
2026-08-23 02:19: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
c027eb0689 fix: einrichten.sh bricht bei Leerzeichen im Projektpfad ab
FreeSWITCHs record-App zerlegt ihre Argumente an Leerzeichen
(record <pfad> <zeitlimit> <stille-schwelle> <stille-treffer>). Liegt das
Projekt unter einem Pfad mit Leerzeichen, nimmt sie den Teil bis zum ersten
Leerzeichen als Pfad und den Rest als Zahlenargumente. Die Aufnahme landet
dann ohne Endung neben dem Projekt (roh im Codec-Format, z. B. Server.PCMA),
der Eingang bleibt leer, der Watcher sieht nie etwas.

Der Fehler tarnt sich: die Ansage laeuft normal, weil playback sein Argument
am Stueck nimmt. Es sieht deshalb nach einem Watcher-Problem aus, nicht nach
einem Pfadproblem.

einrichten.sh prueft das jetzt vorab und nennt den Ausweg (Symlink ohne
Leerzeichen, Skript darueber aufrufen). Aufgetreten auf der zweiten Maschine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013pj3mSmL397czPaCfbL27Z
2026-08-23 00:38:16 +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
da50567d54 fix: starten.sh liest FS_PORT und FS_ETC aus der .env
Der Event-Socket-Port war fest auf 8099 vorgegeben - ein Wert, der nur auf dem
ersten Rechner stimmt. Auf der zweiten Maschine hoert FreeSWITCH auf 8022;
fs_cli antwortet dann nicht, starten.sh sieht den Trunk nie als registriert und
bricht ab, obwohl alles laeuft.

starten.sh uebernimmt die Werte jetzt aus der .env - wie pipe/config.py, mit
Vorrang fuer bereits gesetzte Umgebungsvariablen. FS_PORT und FS_ETC stehen
dokumentiert in .env.example.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013pj3mSmL397czPaCfbL27Z
2026-08-22 23:54:04 +02:00
Jeuner
1d81f88eb2 docs: Mitgabe für die Einrichtung auf einem zweiten Rechner
Sammelt, was beim Aufsetzen auf einer frischen Maschine fehlt: die nicht im
Repo liegenden Teile, der kurze Weg über Stufe 1 ohne Telefonie, eine
Prüfleiter und die Fallen aus dem ersten Volltest.

Dabei drei Lücken geschlossen, die drüben zugeschlagen hätten:

- NEXTCLOUD_DECK fehlte in .env.example. DECK_STAPEL und DECK_TEILEN stehen
  dort, der Hauptschalter nicht — die Pipe legt dann kein Board an, obwohl
  alles richtig aussieht.
- WHISPER_MODELL/WHISPER_COMPUTE galten laut Vorlage allgemein, greifen aber
  nur bei STT_BACKEND=faster. Standard ist whispercpp. Jetzt sind beide Blöcke
  getrennt und STT_BACKEND, WHISPERCPP_BIN/_MODELL, WHISPER_THREADS,
  NEXTCLOUD_USERID und FREESWITCH_LOG dokumentiert.
- README hatte eine doppelte Überschrift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013pj3mSmL397czPaCfbL27Z
2026-08-22 23:14:47 +02:00
Jeuner
2700bcab8f docs: vollständige Einrichtungsanleitung, fehlende Abhängigkeiten ergänzt
Die bisherigen "Voraussetzungen" reichten für eine Neuinstallation nicht: Es
fehlten FreeSWITCH, Piper samt Stimme, der Download des Whisper-Modells und
zwei Python-Pakete. Wer das Repository frisch geklont hat, wäre damit nicht
weit gekommen.

- requirements.txt: piper-tts und certifi ergänzt. certifi ist keine Kür — das
  Python von python.org bringt keinen System-Zertifikatsspeicher mit, ohne das
  Bundle scheitern die HTTPS-Aufrufe zu Nextcloud und der Stimmen-Download.
- README: Einrichtung in sechs Schritten, alle Befehle gegengeprüft (Formeln
  vorhanden, Modell-Links erreichbar).
- Abschnitt "Stolpersteine" mit den vier Dingen, die hier Zeit gekostet haben:
  FreeSWITCH liest aus dem Cellar, belegter Event-Socket-Port, rotierende
  Provider-Adressen als Ursache für abbrechende Registrierungen, und der
  Leitstand, der ohne Passwort nicht ins Netz geht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017V57oXiDLi4nSsyau5qaA2
2026-08-22 23:00:01 +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