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:
Jeuner 2026-08-25 00:12:42 +02:00
parent 9f64d94b3f
commit 4448e58031
4 changed files with 43 additions and 2 deletions

View file

@ -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