mirror of
https://github.com/Jeuners/Logpy-AgentOne.git
synced 2026-09-09 15:02:33 +02:00
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
This commit is contained in:
parent
989d3ba85b
commit
2e767f3737
3 changed files with 65 additions and 7 deletions
52
README.md
52
README.md
|
|
@ -21,7 +21,8 @@ sammelt die Erfahrungswerte, die in dieser Anleitung nicht stehen.
|
||||||
1. **Die Pipe** (fertig): Audio → Transkript → Kategorie → Ablage.
|
1. **Die Pipe** (fertig): Audio → Transkript → Kategorie → Ablage.
|
||||||
2. **Telefonie** (steht): FreeSWITCH nimmt über den Plusnet-Trunk an, spielt die
|
2. **Telefonie** (steht): FreeSWITCH nimmt über den Plusnet-Trunk an, spielt die
|
||||||
Ansage, nimmt auf und legt die Datei im Eingang der Pipe ab.
|
Ansage, nimmt auf und legt die Datei im Eingang der Pipe ab.
|
||||||
3. **Feinschliff** (offen): Löschfristen, Notfall-Alarm, Monitoring.
|
3. **Feinschliff** (teilweise): Löschfristen ✅, Notfall-Alarm ✅ — offen:
|
||||||
|
Monitoring/Alerting bei Diensteausfall, Verschlüsselung at rest.
|
||||||
|
|
||||||
## Bausteine
|
## Bausteine
|
||||||
|
|
||||||
|
|
@ -35,6 +36,8 @@ sammelt die Erfahrungswerte, die in dieser Anleitung nicht stehen.
|
||||||
| `pipe/watch.py` | Beobachtet den Eingang und schiebt Aufnahmen durch die Pipe |
|
| `pipe/watch.py` | Beobachtet den Eingang und schiebt Aufnahmen durch die Pipe |
|
||||||
| `pipe/server.py` | Leitstand: Live-Ansicht mit Status, Anrufen und Verlauf |
|
| `pipe/server.py` | Leitstand: Live-Ansicht mit Status, Anrufen und Verlauf |
|
||||||
| `pipe/protokoll.py` | Ereignisprotokoll (JSONL) für den Leitstand |
|
| `pipe/protokoll.py` | Ereignisprotokoll (JSONL) für den Leitstand |
|
||||||
|
| `pipe/audit.py` | Unveränderliches Audit-Log (append-only, siehe unten) |
|
||||||
|
| `pipe/loeschen.py` | Löscht erledigte Anrufe nach Ablauf der Aufbewahrungsfrist |
|
||||||
| `prompts/categorize_de.txt` | Kategorisierungs-Prompt (deutsch, mehrsprachig-tauglich) |
|
| `prompts/categorize_de.txt` | Kategorisierungs-Prompt (deutsch, mehrsprachig-tauglich) |
|
||||||
| `telefon/` | Ansage, FreeSWITCH-Vorlagen, Start- und Einrichtungsskripte |
|
| `telefon/` | Ansage, FreeSWITCH-Vorlagen, Start- und Einrichtungsskripte |
|
||||||
|
|
||||||
|
|
@ -146,6 +149,15 @@ der Praxis, unabhängig von Nextcloud:
|
||||||
Telefon-App des Rechners; liegt die Karte noch im Eingang, wandert sie dabei
|
Telefon-App des Rechners; liegt die Karte noch im Eingang, wandert sie dabei
|
||||||
nach `In Bearbeitung`
|
nach `In Bearbeitung`
|
||||||
- darunter der laufende Verlauf — vom Klingeln über die Erkennung bis zur Ablage
|
- darunter der laufende Verlauf — vom Klingeln über die Erkennung bis zur Ablage
|
||||||
|
- **Sicher-Modus** (🙈-Knopf oben links): blurrt die Rufnummern auf allen
|
||||||
|
Karten, für Screenshots/Bildschirmaufnahmen. Merkt sich den Zustand pro
|
||||||
|
Browser (`localStorage`), übersteht also einen Reload.
|
||||||
|
- **Notfall-Alarm:** liegt eine Karte mit Dringlichkeit `notfall` nicht im
|
||||||
|
letzten Stapel (`Erledigt`), pulsiert ihr Rahmen, ein rotes Banner erscheint
|
||||||
|
und alle 60s ertönt ein Doppel-Piepton (Web Audio API, keine Datei nötig) —
|
||||||
|
bis die Karte nach `Erledigt` verschoben wird. Browser blocken Autoplay-Audio
|
||||||
|
ohne vorherige Interaktion: ein Klick irgendwo auf die Seite schaltet den Ton
|
||||||
|
frei.
|
||||||
|
|
||||||
Standardmäßig hört der Leitstand nur auf `127.0.0.1`. Soll er von anderen
|
Standardmäßig hört der Leitstand nur auf `127.0.0.1`. Soll er von anderen
|
||||||
Rechnern der Praxis erreichbar sein, `LEITSTAND_HOST=0.0.0.0` setzen — dann ist
|
Rechnern der Praxis erreichbar sein, `LEITSTAND_HOST=0.0.0.0` setzen — dann ist
|
||||||
|
|
@ -168,9 +180,10 @@ Der SIP-Zugang steht in der `.env` (`SIP_USER`, `SIP_DOMAIN`, `SIP_PASS`). Das
|
||||||
Einrichtungsskript trägt das Passwort in die FreeSWITCH-Konfiguration ein und
|
Einrichtungsskript trägt das Passwort in die FreeSWITCH-Konfiguration ein und
|
||||||
setzt deren Rechte auf 600 — im Git liegen nur Vorlagen mit Platzhaltern.
|
setzt deren Rechte auf 600 — im Git liegen nur Vorlagen mit Platzhaltern.
|
||||||
|
|
||||||
Die Ansage nennt den Notruf 112 und weist auf die Aufzeichnung hin, bevor der
|
Text ändern: `telefon/ansage.txt` bearbeiten, `ansage_bauen.sh` erneut
|
||||||
Signalton kommt — beides ist bei einer Arztpraxis Pflicht. Text ändern:
|
ausführen. Für den Praxisbetrieb muss die Ansage den Notruf 112 nennen und auf
|
||||||
`telefon/ansage.txt` bearbeiten, `ansage_bauen.sh` erneut ausführen.
|
die Aufzeichnung hinweisen, bevor der Signalton kommt — beides ist bei einer
|
||||||
|
Arztpraxis Pflicht, siehe „Offener Punkt" unter Datenschutz weiter unten.
|
||||||
|
|
||||||
Gesprochen wird lokal, wahlweise mit **Piper** (natürlicher, Modelle unter
|
Gesprochen wird lokal, wahlweise mit **Piper** (natürlicher, Modelle unter
|
||||||
`~/piper-voices`) oder dem macOS-`say` als Rückfall. Die Stimme
|
`~/piper-voices`) oder dem macOS-`say` als Rückfall. Die Stimme
|
||||||
|
|
@ -199,10 +212,39 @@ leeres Deck. Deshalb `DECK_TEILEN` in der `.env` auf die Nextcloud-Nutzer setzen
|
||||||
(kommagetrennt), die es sehen sollen; die Freigabe wird bei jedem Anruf
|
(kommagetrennt), die es sehen sollen; die Freigabe wird bei jedem Anruf
|
||||||
sichergestellt.
|
sichergestellt.
|
||||||
|
|
||||||
|
## Aufbewahrungsfrist & Audit-Log
|
||||||
|
|
||||||
|
`LOESCHFRIST_TAGE` in der `.env` (0 = aus) legt fest, nach wie vielen Tagen ein
|
||||||
|
**erledigter** Anruf (Aufnahme + Transkript, kompletter Ordner) gelöscht wird —
|
||||||
|
lokal und, falls hochgeladen, auch bei Nextcloud. Nur Anrufe im letzten Stapel
|
||||||
|
(`Erledigt`) werden angefasst; alles andere bleibt unangetastet, egal wie alt —
|
||||||
|
niemand soll einen liegen gebliebenen Anruf verlieren, bevor ihn jemand
|
||||||
|
gesehen hat.
|
||||||
|
|
||||||
|
Kein Cron/launchd nötig: `pipe.watch` (läuft ohnehin dauerhaft, siehe
|
||||||
|
`telefon/starten.sh`) prüft das alle 24h selbst mit. Manueller Aufruf für Tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m pipe.loeschen --pruefen # nur anzeigen, was fällig wäre
|
||||||
|
python3 -m pipe.loeschen # tatsächlich löschen
|
||||||
|
```
|
||||||
|
|
||||||
|
Jede Löschung (und jedes andere Pipe-Ereignis) landet zusätzlich unveränderlich
|
||||||
|
in `telefon/audit.log` — anders als `verlauf.log` (fürs Leitstand-UI, wird auf
|
||||||
|
2000 Zeilen gekürzt) wird diese Datei nie überschrieben, nur angehängt, und
|
||||||
|
zusätzlich per macOS `chflags uappnd` gegen nachträgliches Ändern/Kürzen
|
||||||
|
abgesichert (auch der eigene Prozess kommt danach nur noch per Anhängen rein).
|
||||||
|
|
||||||
## Datenschutz (DSGVO)
|
## Datenschutz (DSGVO)
|
||||||
|
|
||||||
Gesundheitsdaten — Pflichten für den Produktivbetrieb (Stufe 2):
|
Gesundheitsdaten — Pflichten für den Produktivbetrieb (Stufe 2):
|
||||||
- Aufnahme-Ansage am Gesprächsanfang.
|
- Aufnahme-Ansage am Gesprächsanfang.
|
||||||
- Notfall-Ansage zuerst (112 / 116117); der Agent gibt **keine** medizinischen Ratschläge.
|
- Notfall-Ansage zuerst (112 / 116117); der Agent gibt **keine** medizinischen Ratschläge.
|
||||||
- Verschlüsselung at rest, Zugriffskontrolle, Löschfristen.
|
- Verschlüsselung at rest, Zugriffskontrolle.
|
||||||
|
- Löschfristen — siehe oben (`LOESCHFRIST_TAGE`).
|
||||||
- Verarbeitung lokal (Whisper + Qwen) — kein Cloud-Dienst im Datenpfad.
|
- Verarbeitung lokal (Whisper + Qwen) — kein Cloud-Dienst im Datenpfad.
|
||||||
|
|
||||||
|
> **Offener Punkt:** Die aktuelle `telefon/ansage.txt` erfüllt die ersten
|
||||||
|
> beiden Punkte noch nicht (kein Notfall-Hinweis, keine Aufnahme-Einwilligung)
|
||||||
|
> — Text anpassen und `ansage_bauen.sh` neu laufen lassen, bevor die Anlage im
|
||||||
|
> echten Praxisbetrieb ans Netz geht.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ landet unveraenderlich in audit.log (ueber protokoll.schreibe).
|
||||||
python3 -m pipe.loeschen # loescht faellige Anrufe
|
python3 -m pipe.loeschen # loescht faellige Anrufe
|
||||||
python3 -m pipe.loeschen --pruefen # zeigt nur, was faellig waere
|
python3 -m pipe.loeschen --pruefen # zeigt nur, was faellig waere
|
||||||
|
|
||||||
Gedacht fuer taeglichen Cron/launchd-Lauf - siehe README fuer die Einrichtung.
|
Laeuft automatisch alle 24h aus pipe.watch heraus (kein Cron/launchd noetig,
|
||||||
|
solange der Watcher laeuft - siehe telefon/starten.sh). Manueller Aufruf hier
|
||||||
|
ist nur fuer Tests/Ad-hoc-Kontrolle gedacht.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,17 @@ import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from . import config, process_call, protokoll
|
from . import config, loeschen, process_call, protokoll
|
||||||
|
|
||||||
TAKT_S = 3 # Wartezeit zwischen zwei Durchläufen
|
TAKT_S = 3 # Wartezeit zwischen zwei Durchläufen
|
||||||
RUHE_S = 2 # So lange muss die Dateigröße stabil sein (Aufnahme fertig)
|
RUHE_S = 2 # So lange muss die Dateigröße stabil sein (Aufnahme fertig)
|
||||||
ENDUNGEN = {".wav", ".mp3", ".opus", ".ogg", ".m4a", ".aiff"}
|
ENDUNGEN = {".wav", ".mp3", ".opus", ".ogg", ".m4a", ".aiff"}
|
||||||
|
|
||||||
|
# Aufbewahrungsfrist pruefen (pipe.loeschen) - kein Cron noetig, der Watcher
|
||||||
|
# laeuft ohnehin dauerhaft. time.monotonic() statt datetime.now(): unempfindlich
|
||||||
|
# gegen Systemzeit-Spruenge (Zeitzone/NTP), zaehlt einfach vergangene Sekunden.
|
||||||
|
LOESCH_TAKT_S = 24 * 60 * 60
|
||||||
|
|
||||||
# JJJJMMTT-HHMMSS_<nummer>.<ext> - beides optional, damit auch handverlesene
|
# JJJJMMTT-HHMMSS_<nummer>.<ext> - beides optional, damit auch handverlesene
|
||||||
# Dateien verarbeitet werden.
|
# Dateien verarbeitet werden.
|
||||||
_NAME = re.compile(r"^(?:(\d{8})-(\d{6}))?_?(.*)$")
|
_NAME = re.compile(r"^(?:(\d{8})-(\d{6}))?_?(.*)$")
|
||||||
|
|
@ -139,10 +144,19 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
print(f"Beobachte {eingang} — Abbruch mit Strg-C")
|
print(f"Beobachte {eingang} — Abbruch mit Strg-C")
|
||||||
|
if config.LOESCHFRIST_TAGE:
|
||||||
|
print(f"Aufbewahrungsfrist: {config.LOESCHFRIST_TAGE} Tage "
|
||||||
|
f"(Pruefung alle {LOESCH_TAKT_S // 3600}h, kein Cron noetig)")
|
||||||
protokoll.schreibe("system", "Watcher gestartet")
|
protokoll.schreibe("system", "Watcher gestartet")
|
||||||
|
letzter_loeschlauf = 0.0 # 0 => gleich beim ersten Durchlauf pruefen
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
durchlauf(eingang)
|
durchlauf(eingang)
|
||||||
|
if config.LOESCHFRIST_TAGE and time.monotonic() - letzter_loeschlauf >= LOESCH_TAKT_S:
|
||||||
|
anzahl = loeschen.laeuft()
|
||||||
|
if anzahl:
|
||||||
|
print(f"🗑 {anzahl} Anruf(e) nach Aufbewahrungsfrist geloescht.")
|
||||||
|
letzter_loeschlauf = time.monotonic()
|
||||||
time.sleep(TAKT_S)
|
time.sleep(TAKT_S)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nBeobachtung beendet.")
|
print("\nBeobachtung beendet.")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue