mirror of
https://github.com/Jeuners/Logpy-AgentOne.git
synced 2026-09-09 15:02:33 +02:00
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.
This commit is contained in:
commit
1a1280e79d
24 changed files with 2253 additions and 0 deletions
72
pipe/process_call.py
Normal file
72
pipe/process_call.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"""Orchestrator der Anruf-Pipeline: Audio -> Transkript -> Kategorie -> Ablage.
|
||||
|
||||
Aufruf:
|
||||
python3 -m pipe.process_call <audiodatei> [--nummer 021031234567]
|
||||
|
||||
Gibt am Ende den Ablageort aus. Vollständig lokal, sofern keine Nextcloud-
|
||||
Zugangsdaten konfiguriert sind.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from . import categorize, protokoll, store, transcribe
|
||||
|
||||
|
||||
def verarbeite(audio: str, anrufer_nummer: str | None = None,
|
||||
empfangen: datetime | None = None) -> dict:
|
||||
empfangen = empfangen or datetime.now()
|
||||
|
||||
print(f"▸ Transkribiere {audio} …")
|
||||
protokoll.schreibe("anruf", "Transkribiere …", nummer=anrufer_nummer)
|
||||
t = transcribe.transkribiere(audio)
|
||||
protokoll.schreibe("anruf", (t.text[:200] or "(kein Text erkannt)"),
|
||||
nummer=anrufer_nummer, dauer=t.dauer)
|
||||
print(f" Sprache: {t.sprache} ({t.sprache_wahrscheinlichkeit}), Dauer: {t.dauer}s")
|
||||
print(f" Text: {t.text[:160]}{'…' if len(t.text) > 160 else ''}")
|
||||
|
||||
print("▸ Kategorisiere …")
|
||||
auswertung = categorize.kategorisiere(t.text)
|
||||
print(f" Kategorie: {auswertung['kategorie']} · Dringlichkeit: {auswertung['dringlichkeit']}")
|
||||
protokoll.schreibe(
|
||||
"notfall" if auswertung["dringlichkeit"] == "notfall" else "anruf",
|
||||
f"{auswertung['kategorie']} · {auswertung['dringlichkeit']}"
|
||||
+ (f" · {auswertung['anrufer_name']}" if auswertung.get("anrufer_name") else ""),
|
||||
nummer=anrufer_nummer, kategorie=auswertung["kategorie"],
|
||||
dringlichkeit=auswertung["dringlichkeit"])
|
||||
|
||||
datensatz = {
|
||||
"empfangen": empfangen.isoformat(timespec="seconds"),
|
||||
"anrufer_nummer": anrufer_nummer,
|
||||
"transkript": t.text,
|
||||
"erkannte_sprache": t.sprache,
|
||||
"audio_dauer_s": t.dauer,
|
||||
"auswertung": auswertung,
|
||||
}
|
||||
|
||||
print("▸ Lege ab …")
|
||||
ziel = store.speichere(datensatz, audio)
|
||||
print(f"✔ Fertig: {ziel}")
|
||||
protokoll.schreibe("fertig", f"abgelegt: {ziel.parent.name}/{ziel.name}",
|
||||
nummer=anrufer_nummer)
|
||||
protokoll.kuerze()
|
||||
return datensatz
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
p = argparse.ArgumentParser(description="Anruf verarbeiten und ablegen.")
|
||||
p.add_argument("audio", help="Pfad zur Audiodatei (WAV/OPUS/…)")
|
||||
p.add_argument("--nummer", default=None, help="Rufnummer des Anrufers (CLIP), falls bekannt")
|
||||
args = p.parse_args(argv)
|
||||
try:
|
||||
verarbeite(args.audio, anrufer_nummer=args.nummer)
|
||||
except Exception as fehler:
|
||||
print(f"✖ Fehler: {fehler}", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue