Commit graph

60 commits

Author SHA1 Message Date
Jeuner
37a5a78b15 feat: Abmelden-Button im Leitstand
HTTP Basic Auth kennt keine Server-Session zum Beenden - der Browser cached
die Zugangsdaten selbst. Der neue Button loescht das Testzugang-Cookie und
schickt einen Request mit falschen Zugangsdaten (per XHR mit explizitem
user/password), was den Basic-Auth-Cache der gaengigen Browser ueberschreibt;
ein Reload danach zeigt wieder den Anmelde-Dialog. Nur sichtbar, wenn
ueberhaupt ein Zugangsschutz aktiv ist (s.nutzer gesetzt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 18:41:51 +02:00
Jeuner
184fb37593 feat: eingeloggter Nutzer im Leitstand-Header sichtbar
Mit mehreren Leitstand-Konten (praxis/notdienst/admin) war im UI selbst
nicht erkennbar, als wer man gerade angemeldet ist. /api/status liefert
jetzt "nutzer" (aus self._nutzer, siehe voriger Commit), das Frontend zeigt
ihn im Header neben dem Stand-Zeitstempel ("👤 notdienst").

Nebenbei behoben: dienste.status() gibt ein gecachtes, ueber Requests
geteiltes dict zurueck - der Handler mutierte es bisher direkt
(testzugang-Feld). Bei gleichzeitigen Anfragen verschiedener Nutzer haette
das den Nutzernamen der einen Anfrage in die Antwort einer anderen
durchsickern lassen koennen. Jetzt eine flache Kopie vor dem Mutieren.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 18:39:26 +02:00
Jeuner
b1cb3471a5 feat: Verlauf zeigt jetzt, wer eine Karte verschoben hat
_angemeldet() merkt sich jetzt den eingeloggten Nutzernamen (self._nutzer) -
bei mehreren Leitstand-Konten (LEITSTAND_ZUGAENGE) war bisher nicht
nachvollziehbar, wer eine Karte zwischen den Stapeln verschoben hat. Der
Verlauf-Eintrag zeigt "(von <nutzer>)" in derselben Zeile, "nutzer" steht
zusaetzlich als eigenes Feld im JSONL (Verlauf + Audit-Log).

Getestet: Verschieben per Testanruf + notdienst-Login, Eintrag im Verlauf
verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 18:32:10 +02:00
Jeuner
8621691054 feat: aktives Branchen-Profil im Leitstand/Dashboard sichtbar machen
Welches Profil (PROFIL) gerade aktiv ist, stand bisher nur in der .env - im
laufenden Leitstand war es unsichtbar, obwohl Kategorien und Ansage sich
je nach Profil komplett unterscheiden (Praxis vs. Aufzug-Notdienst).

kategorien.json bekommt ein "profil_name"-Feld (Anzeigename), neues
kategorien.PROFIL_NAME laedt es. Browsertab-Titel und Header-Untertitel im
Leitstand sowie Titel/Ueberschrift im Dashboard-Export zeigen es jetzt an.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 18:24:28 +02:00
Jeuner
8e54a58f13 ui: Dringlichkeitsfarben getauscht - normal gruen, niedrig grau statt gelb/gruen
Gelb fuer "normal" wirkte zu warnend fuer einen unauffaelligen Regelfall.
normal ist jetzt gruen, niedrig (unwichtig) grau - hoch (orange) und notfall
(rot) unveraendert. Betrifft Leitstand, Dashboard und Deck-Labels.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 18:20:36 +02:00
Jeuner
07010fbfdf feat: echte Aufnahme statt Sprachsynthese als Ansage (ANSAGE_TTS=datei)
ansage_bauen.sh unterstuetzt jetzt eine vierte Quelle neben piper/say/mistral:
"datei" nimmt eine vorhandene Aufnahme (profile/$PROFIL/ansage.<ext>) statt
sie aus ansage.txt zu synthetisieren, durchlaeuft aber dieselbe Aufbereitung
(8kHz mono, Signalton, -3dB Headroom) wie die TTS-Pfade.

profile/aufzug-notdienst/ansage.ogg ist die reale Aufnahme fuer das
Notdienst-Beispielprofil (Pegel geprueft: Peak ~0dB vor, ~-3dB nach der
Verarbeitung - kein Clipping).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 18:16:57 +02:00
Jeuner
795dcf7eeb feat: mehrere Leitstand-Zugaenge gleichzeitig (LEITSTAND_ZUGAENGE)
Bisher gab es genau ein Nutzer/Passwort-Paar (LEITSTAND_USER/LEITSTAND_PASS).
Fuer einen zweiten Einsatzzweck/ein zweites Team am selben Leitstand (z.B.
Notdienst neben Praxis) reicht das nicht. LEITSTAND_ZUGAENGE in der .env
nimmt jetzt zusaetzliche "nutzer:passwort"-Paare (kommagetrennt) an - das
bestehende LEITSTAND_USER/LEITSTAND_PASS bleibt als erstes Konto gueltig,
config.leitstand_konten() buendelt alle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 18:10:31 +02:00
Jeuner
87621e5bec feat: Branchen-Profile - Projekt fuer andere Einsatzzwecke konfigurierbar machen
Kategorien, Prompt und Notfall-Sicherheitsnetz waren fest auf eine
Hausarztpraxis zugeschnitten (categorize.py: KATEGORIEN-Liste, _NOTFALL_MUSTER-
Regex). Fuer einen anderen Einsatzzweck (z.B. 24h-Aufzug-Notdienst) musste
bisher Python-Code geaendert werden.

Neu: profile/<name>/ buendelt prompt.txt, kategorien.json, sicherheitsnetz.txt
und ansage.txt - alles, was fachlich vom Einsatzzweck abhaengt, an einer
Stelle. PROFIL in der .env waehlt den aktiven Ordner (Default "praxis", altes
Verhalten bleibt exakt gleich - per Test verifiziert: identische Kategorien-
Liste und Sicherheitsnetz-Muster). profile/aufzug-notdienst/ liegt als
ausformuliertes Beispielprofil bei (eigene Kategorien, eigenes
Sicherheitsnetz, eigene Ansage).

Dringlichkeitsstufen (niedrig/normal/hoch/notfall) bleiben bewusst fest -
nur die Kategorien sind einsatzzweck-spezifisch. Neues pipe/kategorien.py
laedt das Profil-Schema einmal fuer categorize.py, dashboard.py, deck.py und
server.py (Leitstand-JS: Kategorie-Label kommen jetzt vom Server statt fest
im Frontend zu stehen).

prompts/categorize_de.txt und telefon/ansage.txt sind nach profile/praxis/
umgezogen (git erkennt es als Rename).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 17:07:29 +02:00
Jeuner
d39b360b7b feat: Modell-Dropdown listet lokale Ollama-Modelle live statt fest im Code
Bisher stand nur config.OLLAMA_MODELL zur Auswahl - ein neu per "ollama pull"
installiertes Modell (z.B. ornith-1.5:9b) tauchte nicht im Leitstand auf, ohne
Code-Aenderung. modellwahl.auswahl() fragt jetzt GET /api/tags live ab und
baut die Ollama-Optionen daraus; reine Embedding-Modelle (capabilities ohne
"completion", z.B. nomic-embed-text) werden ausgefiltert, weil sie nicht
kategorisieren koennen. Die festen OpenRouter-Testeintraege bleiben bestehen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 16:53:45 +02:00
Jeuner
2605710818 merge: Linux-Setup-Hinweis (origin) mit Störungswache/FileVault-Doku zusammenführen 2026-08-28 16:48:00 +02:00
Jeuner
6e5fe59c53 feat: Störungswache (pipe.monitor) + Verschlüsselung-at-rest dokumentiert
Dienste-Ampel (Telefon, Verarbeitung, Spracherkennung, Nextcloud) alarmierte
bisher nur im offenen Leitstand. pipe/monitor.py laeuft jetzt als eigener
Dauerprozess, prueft dieselbe Ampel (neu ausgelagert nach pipe/dienste.py)
periodisch und alarmiert bei Stoerung lokal per macOS-Benachrichtigung + Ton
(pipe/alarm.py) - unabhaengig davon, ob pipe.watch oder pipe.server laufen.
Wird ab jetzt von telefon/starten.sh mitgestartet.

Verschluesselung at rest: keine App-eigene Verschluesselung eingebaut,
stattdessen im README als Betreiber-Pflicht dokumentiert (FileVault aktivieren)
- App verlaesst sich auf Festplattenverschluesselung durch das Betriebssystem.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
2026-08-28 16:47:45 +02:00
Jeuner
09be478868 Update README for Linux setup instructions 2026-08-26 19:45:25 +02:00
Jeuner
6458c36d85 img fix 2026-08-25 17:32:44 +02:00
Jeuner
be0252c907 feat: Nextcloud-Profil im Footer
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 02:19:03 +02:00
Jeuner
de9c269e9c fix: Telefonnummer im Footer im internationalen Format anzeigen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 02:15:19 +02:00
Jeuner
441883a073 feat: LinkedIn und Telefonnummer im Footer
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 02:14:15 +02:00
Jeuner
abf54765d8 fix: Rufnummern bei Testzugang serverseitig maskieren, nicht nur per CSS blurren
CSS-Blur (Sicher-Modus) verdeckt nur die Anzeige - im Seitenquelltext,
DevTools oder Netzwerk-Tab standen die echten Rufnummern trotzdem drin,
weil sie ganz normal im JSON von /api/anrufe und /api/verlauf ankamen.

Bei Testzugang (per Token statt Passwort, self._via_token) werden nummer/
rueckrufnummer jetzt vor dem Senden durch einen Platzhalter ersetzt
(_maskiere_anrufe), im Verlauf-Log werden eingebettete Ziffernfolgen ab
9 Stellen ersetzt (_maskiere_verlauf, gleiche Heuristik wie das bestehende
Frontend-telBlur, nur serverseitig und wirklich ersetzend statt nur
verdeckend). Die echten Werte verlassen den Server in diesem Modus nicht
mehr.

Getestet: 13 unmaskierte Rufnummern im Verlauf bei Passwort-Zugriff, 0 bei
Token-Zugriff; _maskiere_anrufe() isoliert verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 02:11:41 +02:00
Jeuner
7dcb90826c feat: Sicher-Modus bei Testzugang erzwungen, nicht abschaltbar
Wer per "agent-one test"-Link kommt, braucht kein Passwort - also duerfen
fremde Tester keine echten Rufnummern sehen. /api/status liefert jetzt
"testzugang" (true, wenn die Anfrage per Zugangs-Token statt Passwort
authentifiziert wurde), das Frontend erzwingt darauf sicherSetzen(true) und
macht den Sicher-Modus-Button zum No-Op (Toast statt Abschalten).

Mit Playwright verifiziert: Laden mit gueltigem Token setzt Sicher-Modus
sofort, Klick auf den Button aendert daran nichts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 01:47:03 +02:00
Jeuner
af67ee89e3 feat: signierte 1h-Testzugangslinks fuer den Leitstand ("agent-one test")
Gegenstueck zum "agent-one test"-Kommando in Alice (dillenberg.net,
dilles-agent.php). Kein Live-Aufruf zwischen den Systemen noetig - beide
teilen sich TESTZUGANG_SECRET, das Token ist ein HMAC-signierter
Ablauf-Zeitstempel ("ablauf.signatur"), stateless geprueft.

pipe/testzugang.py: gueltig(token) prueft Signatur (hmac.compare_digest)
und Ablauf. _angemeldet() in server.py akzeptiert das Token zusaetzlich zum
Passwort - per ?zugang=... in der URL oder per zugang-Cookie fuer
Folgeanfragen (fetch()-Aufrufe der Seite tragen die URL nicht mit). Ein
frisches URL-Token setzt das Cookie (HttpOnly, Secure, SameSite=Lax) fuer
den Rest der Stunde.

End-to-End getestet: PHP-generiertes Token wird von Python akzeptiert
(Cross-Language-HMAC-Kompatibilitaet bestaetigt), ungueltige Signatur und
abgelaufenes Token werden korrekt abgelehnt (401), Cookie-Folgeanfrage ohne
erneutes Token/Passwort funktioniert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 01:33:46 +02:00
Jeuner
fa55204767 release: v1.0.0
Erste stabile Version - Kernpipeline, Telefonie, Leitstand mit Sicher-Modus/
Notfall-Alarm/Bewertungssystem, Notfall-Sicherheitsnetz, Audit-Log +
Loeschfristen, Modell-Vergleich mit Failover. Details in CHANGELOG.md.

Vorher geprueft: Git-Historie sauber (keine Secrets), alle Python-Dateien
syntaktisch valide, Watcher/Leitstand/FreeSWITCH/Ollama live und
funktionsfaehig, telefon/fehler leer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 01:15:20 +02:00
Jeuner
157b3dbad1 docs: Screenshot aktualisiert (Dark Mode, aktuelles Karten-Redesign)
Ersetzt docs/leitstand.png (alter Hell-Modus-Stand) durch einen aktuellen
Dark-Mode-Screenshot nach dem Karten-Redesign.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 01:00:29 +02:00
Jeuner
0d7c92644f feat: Kategorie/Status-Auswahl im Meldung-Modal, vorbelegt mit aktuellen Werten
Modal hat jetzt zwei Dropdowns (Kategorie, Status/Dringlichkeit) oberhalb
des Freitextfelds, vorbelegt mit den aktuellen Karten-Werten - man
korrigiert nur, was falsch ist, statt alles neu waehlen zu muessen.
meldung.oeffnen() nimmt dafuer kategorie/dringlichkeit entgegen, der
generische Rahmen bleibt erhalten (felder:false blendet die Auswahl fuer
kuenftige nicht-kategorisierungsbezogene Meldungen aus).

aufSpeichern bekommt jetzt {text, kategorie, dringlichkeit} statt nur
Freitext - mit Playwright verifiziert (Vorbelegung + tatsaechlicher
POST-Body).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 00:44:04 +02:00
Jeuner
09b8fcabae redesign: Deck-Karte entrümpelt - weniger Chips, klarere Struktur
Ueber die Session angesammelte Badges (3x separates "ungeprüft"-Chip fuer
Name/Rueckrufnummer/E-Mail, je eigene .meta-Zeile) sahen zugemuellt aus.

- Ein einziger dezenter Hinweis (.hinweis-klein, kein Rahmen/Chip, kleiner
  kursiver Text) statt Border-Pill, direkt am Namen.
- Rueckrufnummer + E-Mail jetzt auf einer gemeinsamen Zeile mit einem
  gemeinsamen "ungeprüft"-Hinweis statt zwei einzelnen Chips.
- "vermutete Korrektur" (E-Mail-@-Reparatur) bleibt als eigener, staerkerer
  Warnhinweis (.warn-klein, andere Bedeutung: Daten evtl. falsch, nicht nur
  ungeprueft).
- Titel jetzt eigene Zeile statt im Kopf mit Badges zu teilen - bessere
  Zeilenumbrueche bei langen Namen, Dringlichkeits-Badge + Melde-Button
  bilden eine eigene Kopfzeile oben rechts.

Nebenbei Bug behoben: rueckrufZeile/emailZeile hiessen im Template noch
beim alten Namen (kontaktZeile) - haette beim ersten echten Kartenrendering
gecrasht, mit Playwright vor dem Commit abgefangen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 00:41:35 +02:00
Jeuner
ea178fe7ba fix: Quellen-Label selbsterklaerend statt auf Tooltip angewiesen
"📝 Text" war ohne den title-Tooltip nicht verstaendlich - und title-
Tooltips funktionieren auf Touch-Geraeten meist gar nicht (kein Hover).
Jetzt "🎙️ ungeprüft" direkt sichtbar, Tooltip bleibt als Zusatzinfo für
Maus-Nutzer erhalten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 00:36:50 +02:00
Jeuner
02dbfa5461 feat: Melde-Button + Freitext-Modal statt Daumen-hoch/runter mit Dropdowns
Auf Wunsch umgebaut: kein 👍 mehr, nur noch ein einzelner, dezenter
👎-Melde-Button oben rechts auf der Karte (bei der Dringlichkeits-Badge)
statt der Bewertungszeile unten. Klick oeffnet ein Modal mit Freitext-
Eingabe statt der bisherigen Kategorie/Dringlichkeit-Dropdowns - fuer
Entwicklungs-Feedback ist Prosa nuetzlicher als starre Felder.

Neues kleines, wiederverwendbares JS-"Framework" (Objekt "meldung" im
Frontend): meldung.oeffnen({titel, kontext, aufSpeichern}) - nicht fest an
den Melde-Button gekoppelt, fuer kuenftige weitere "Melden"-Stellen ohne
neues Modal bauen zu muessen. Escape/Klick-außerhalb schliessen, takt()
pausiert waehrend das Modal offen ist (gleiches Muster wie beim Audio-Fix).

Speicherung laeuft weiter durch die bestehende bewertung.json/Export-
Pipeline (korrektur ist jetzt Freitext statt strukturierter Felder).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 00:33:28 +02:00
Jeuner
cc5838621c fix: offene Bewertungs-Korrektur verschwindet nicht mehr beim Auto-Refresh
Gleiches Muster wie der Audio-Refresh-Bug: zeichneBoard() baut die Karten
alle 3s per innerHTML komplett neu, das riss eine gerade geoeffnete
👎-Korrektur-Auswahl (Kategorie/Dringlichkeit-Dropdowns) wieder weg, bevor
man sie ausfuellen konnte - "erscheint, verschwindet wieder". takt()
pausiert jetzt zusaetzlich, solange .bw-korrektur.zeigen offen ist. Mit
Playwright verifiziert: bleibt jetzt auch nach 7s (mehrere Poll-Zyklen)
sichtbar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 00:26:12 +02:00
Jeuner
6aedeafdbf ui: Titel im Header zweizeilig statt einer langen Zeile
"Logpy:AgentOne — Anruf-Leitstand" als eine Zeile war zu lang/unruhig.
Jetzt "Logpy:AgentOne" (h1) mit "Anruf-Leitstand" als kleiner, gedaempfter
Untertitel darunter.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtvcyNkapuDh583643hNs
2026-08-25 00:21:58 +02:00
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