mirror of
https://github.com/Jeuners/Logpy-AgentOne.git
synced 2026-09-09 15:02:33 +02:00
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
This commit is contained in:
parent
9f64d94b3f
commit
4448e58031
4 changed files with 43 additions and 2 deletions
|
|
@ -61,6 +61,7 @@ _SCHEMA = {
|
|||
"anrufer_name": {"type": ["string", "null"]},
|
||||
"rueckrufnummer": {"type": ["string", "null"]},
|
||||
"rueckruf_gewuenscht": {"type": "boolean"},
|
||||
"email": {"type": ["string", "null"]},
|
||||
"sprache": {"type": "string"},
|
||||
"anliegen_kurz": {"type": "string"},
|
||||
"stichworte": {"type": "array", "items": {"type": "string"}},
|
||||
|
|
@ -70,10 +71,34 @@ _SCHEMA = {
|
|||
# sie im Transkript standen. Der Typ erlaubt weiterhin null, wenn nichts da ist.
|
||||
"required": [
|
||||
"kategorie", "dringlichkeit", "anrufer_name", "rueckrufnummer",
|
||||
"rueckruf_gewuenscht", "sprache", "anliegen_kurz", "stichworte",
|
||||
"rueckruf_gewuenscht", "email", "sprache", "anliegen_kurz", "stichworte",
|
||||
],
|
||||
}
|
||||
|
||||
# Deterministische Reparatur, wenn Whisper gesprochenes "at" beim Diktieren
|
||||
# einer E-Mail-Adresse als Punkt transkribiert statt als "@" (beobachtet:
|
||||
# "gdg at dillenberg punkt net" -> Transkript "gdg.dillenberg.net", komplett
|
||||
# ohne @ - kein Kategorisierungs-, sondern ein Spracherkennungs-Problem).
|
||||
# Nur ein Repair-Vorschlag, kein stiller Ersatz: das erste Segment (ohne
|
||||
# eigenen Punkt) vor einer domain.tld-Form wird zu einem @ - mehrdeutig
|
||||
# (koennte auch eine genannte Subdomain sein), deshalb im UI als "vermutete
|
||||
# Korrektur" markiert statt das Transkript selbst zu veraendern.
|
||||
_EMAIL_OHNE_AT = re.compile(
|
||||
r"^([a-z0-9_-]+)\.([a-z0-9-]+\.[a-z]{2,})$", re.IGNORECASE)
|
||||
|
||||
|
||||
def _repariere_email(wert: str | None) -> tuple[str | None, bool]:
|
||||
"""Gibt (email, vermutete_korrektur) zurueck."""
|
||||
if not wert:
|
||||
return wert, False
|
||||
wert = wert.strip()
|
||||
if "@" in wert:
|
||||
return wert, False
|
||||
treffer = _EMAIL_OHNE_AT.match(wert)
|
||||
if not treffer:
|
||||
return wert, False
|
||||
return f"{treffer.group(1)}@{treffer.group(2)}", True
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _prompt() -> str:
|
||||
|
|
@ -228,9 +253,11 @@ def _bereinige(d: dict) -> dict:
|
|||
d.setdefault("anrufer_name", None)
|
||||
d.setdefault("rueckrufnummer", None)
|
||||
d.setdefault("rueckruf_gewuenscht", False)
|
||||
d.setdefault("email", None)
|
||||
d.setdefault("sprache", "de")
|
||||
d.setdefault("anliegen_kurz", "")
|
||||
d.setdefault("stichworte", [])
|
||||
if not isinstance(d["stichworte"], list):
|
||||
d["stichworte"] = []
|
||||
d["email"], d["email_vermutete_korrektur"] = _repariere_email(d.get("email"))
|
||||
return d
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ WHISPER_PROMPT = os.environ.get(
|
|||
"WHISPER_PROMPT",
|
||||
"Anruf auf dem Anrufbeantworter einer Hausarztpraxis. Der Anrufer nennt "
|
||||
"seinen Namen, sein Anliegen und eine Rückrufnummer. Häufig geht es um "
|
||||
"Termin, Rezept, Überweisung, Befund, Krankschreibung oder Schmerzen.",
|
||||
"Termin, Rezept, Überweisung, Befund, Krankschreibung oder Schmerzen. "
|
||||
"Nennt der Anrufer eine E-Mail-Adresse, wird sie mit @-Zeichen "
|
||||
"geschrieben, zum Beispiel max.mueller@example.de.",
|
||||
).strip()
|
||||
|
||||
# faster-whisper (nur bei STT_BACKEND=faster)
|
||||
|
|
|
|||
|
|
@ -141,6 +141,8 @@ def anrufe() -> list[dict]:
|
|||
"name": e.get("anrufer_name"),
|
||||
"nummer": d.get("anrufer_nummer") or e.get("rueckrufnummer"),
|
||||
"rueckrufnummer": e.get("rueckrufnummer"),
|
||||
"email": e.get("email"),
|
||||
"email_korrigiert": bool(e.get("email_vermutete_korrektur")),
|
||||
"rueckruf": bool(e.get("rueckruf_gewuenscht")),
|
||||
"anliegen": e.get("anliegen_kurz") or "",
|
||||
"stichworte": e.get("stichworte") or [],
|
||||
|
|
@ -641,11 +643,20 @@ function karte(d){
|
|||
const abweichend=d.rueckrufnummer&&waehlbar(d.rueckrufnummer)!==waehlbar(d.nummer);
|
||||
const rueckrufZeile=abweichend?`<div class="meta">☎️ Rückruf an: ${nummerLink({...d,nummer:d.rueckrufnummer})}
|
||||
<span class="tag" title="Nummer aus dem Sprach-Transkript erkannt (Ollama) - nicht verifiziert">📝 Text</span></div>`:'';
|
||||
// email_korrigiert: Whisper transkribiert gesprochenes "at" beim Diktieren
|
||||
// manchmal als Punkt statt "@" (z.B. "gdg.dillenberg.net" statt
|
||||
// "gdg@dillenberg.net") - categorize.py repariert das als Vorschlag, aber
|
||||
// mehrdeutig (koennte auch eine genannte Subdomain sein), deshalb hier
|
||||
// sichtbar als Vermutung markiert statt stillschweigend uebernommen.
|
||||
const emailZeile=d.email?`<div class="meta">📧 <a href="mailto:${esc(d.email)}">${esc(d.email)}</a>
|
||||
${d.email_korrigiert?`<span class="tag" title="Whisper hat das @ vermutlich als Punkt transkribiert - automatisch repariert, bitte pruefen">⚠️ vermutete Korrektur</span>`
|
||||
:`<span class="tag" title="E-Mail aus dem Sprach-Transkript erkannt (Ollama) - nicht verifiziert">📝 Text</span>`}</div>`:'';
|
||||
return `<article class="karte${notfallOffen?' notfall-offen':''}" draggable="true" data-id="${esc(d.id)}" style="--akzent:${f}">
|
||||
<div class="kopf"><span class="titel">${esc(KAT[d.kategorie]||d.kategorie)} · ${esc(d.name||'unbekannt')}</span> ${quelleTag}
|
||||
<span class="dring" style="background:${f}">${esc(d.kategorie==='notfall'?'notfall':d.dringlichkeit)}</span></div>
|
||||
<div class="meta">${esc(d.empfangen.replace('T',' '))} · ${nummerLink(d)}${d.rueckruf?' · 📞 Rückruf':''}${d.kontakt?`<span class="kontakt-badge" title="Name via Rufnummer-Abgleich gegen das Adressbuch bestaetigt">✓ ${esc(d.kontakt)}</span>`:''}</div>
|
||||
${rueckrufZeile}
|
||||
${emailZeile}
|
||||
<p class="anliegen">${esc(d.anliegen)}</p>
|
||||
<div>${(d.stichworte||[]).map(s=>`<span class="tag">${esc(s)}</span>`).join('')}</div>
|
||||
${d.audio?`<audio controls preload="none" src="${d.audio}"></audio>`:''}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ Gib ein JSON-Objekt mit genau diesen Feldern zurück:
|
|||
"anrufer_name": "<Name oder null>",
|
||||
"rueckrufnummer": "<Telefonnummer aus dem Text oder null>",
|
||||
"rueckruf_gewuenscht": <true|false>,
|
||||
"email": "<E-Mail-Adresse aus dem Text oder null, im Format name@domain.tld>",
|
||||
"sprache": "<ISO-Code der Anrufersprache, z. B. de, en, ar>",
|
||||
"anliegen_kurz": "<1-2 Sätze, neutrale Zusammenfassung auf Deutsch>",
|
||||
"stichworte": ["<kurze Schlagworte>"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue