fix: Beschriftungen unabhaengig von Umlaut und Zitat erkennen

Ohne den Beguenstigten ist ein Eintrag im Eingang wertlos – man sieht einen
Betrag und weiss nicht, wofuer. Genau das passierte bei weitergeleiteten
Mails: Gmail zitiert den Text mit "> " davor, die Beschriftung stand also
nicht mehr am Zeilenanfang, und je nach Kodierung kam "Beguenstigter" statt
"Beguenstigter" mit Umlaut an.

Beschriftungen werden jetzt normalisiert verglichen (Umlaute verschriftlicht,
alles ausser Buchstaben und Ziffern verworfen), und Zitatzeichen fallen vor
der Feldsuche weg. Die Suchbegriffe laufen durch dieselbe Normalisierung,
damit sie lesbar bleiben statt doppelt gepflegt zu werden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-22 20:36:49 +02:00
co-authored by Claude Opus 5
parent 7b087247a5
commit 512dac2b04
2 changed files with 47 additions and 9 deletions
+30 -9
View File
@@ -30,10 +30,9 @@ from decimal import Decimal, InvalidOperation
# Beschriftungen je Feld, längere zuerst (spezifischer schlägt allgemeiner).
LABELS_BETRAG = ("betrag", "gesendet", "empfangen", "erhalten", "amount", "summe")
LABELS_EMPFAENGER = ("begünstigter", "beguenstigter", "empfänger", "empfaenger",
"zahlungsempfänger", "an", "händler", "haendler", "to")
LABELS_DATUM = ("ausführungsdatum", "ausfuehrungsdatum", "valutadatum", "buchungsdatum",
"datum", "am", "date")
LABELS_EMPFAENGER = ("beguenstigter", "zahlungsempfaenger", "empfaenger", "haendler",
"beguenstigte", "an", "to")
LABELS_DATUM = ("ausfuehrungsdatum", "valutadatum", "buchungsdatum", "datum", "am", "date")
LABELS_KONTO = ("konto", "account", "iban")
LABELS_STATUS = ("neuer status", "status")
@@ -100,6 +99,20 @@ def html_zu_text(roh: str) -> str:
return "\n".join(z for z in zeilen if z)
def _schluessel(text: str) -> str:
"""Beschriftung auf eine vergleichbare Form bringen.
Umlaute verschriftlichen und alles ausser Buchstaben und Ziffern
wegwerfen. „Begünstigter", „Beguenstigter" und „BEGÜNSTIGTER :" ergeben
damit denselben Schlüssel – sonst haengt die Erkennung daran, wie das
Mailprogramm den Umlaut kodiert hat.
"""
t = text.strip().lower()
for von, nach in (("ä", "ae"), ("ö", "oe"), ("ü", "ue"), ("ß", "ss")):
t = t.replace(von, nach)
return re.sub(r"[^a-z0-9]", "", t)
def _zahl_zu_cent(roh: str) -> int | None:
s = roh.replace("'", "").replace("’", "").replace(" ", "")
if "," in s and "." in s:
@@ -134,7 +147,9 @@ def _felder(text: str) -> dict[str, list[tuple[str, int, str]]]:
``Gesendet`` / ``324 €`` den Inhalt der Folgezeile.
"""
gefunden: dict[str, list[tuple[str, int, str]]] = {}
zeilen = text.splitlines()
# Weitergeleitete Mails kommen oft mit „> " zitiert an; die Beschriftung
# steht dann nicht mehr am Zeilenanfang.
zeilen = [re.sub(r"^[\s>]+", "", z) for z in text.splitlines()]
pos = 0
for i, zeile in enumerate(zeilen):
start = pos
@@ -142,12 +157,12 @@ def _felder(text: str) -> dict[str, list[tuple[str, int, str]]]:
if ":" in zeile:
links, rechts = zeile.split(":", 1)
beschriftung = links.strip()
schl, wert = beschriftung.lower(), rechts.strip()
schl, wert = _schluessel(beschriftung), rechts.strip()
if wert:
gefunden.setdefault(schl, []).append((wert, start, beschriftung))
continue
beschriftung = zeile.strip().rstrip(":")
schl = beschriftung.lower()
schl = _schluessel(beschriftung)
if schl and len(schl) <= 30 and i + 1 < len(zeilen):
folge = zeilen[i + 1].strip()
if folge and ":" not in folge:
@@ -156,8 +171,14 @@ def _felder(text: str) -> dict[str, list[tuple[str, int, str]]]:
def _erster_treffer(felder: dict, labels: tuple[str, ...]) -> tuple[str, int, str] | None:
"""Wert, Stelle und die Beschriftung in Originalschreibweise."""
for label in labels:
"""Wert, Stelle und die Beschriftung in Originalschreibweise.
Die Suchbegriffe laufen durch dieselbe Normalisierung wie die gefundenen
Beschriftungen – sonst muesste man sie doppelt pflegen, einmal lesbar und
einmal fuer den Vergleich.
"""
for roh in labels:
label = _schluessel(roh)
if label in felder:
wert, stelle, beschriftung = felder[label][0]
return wert, stelle, beschriftung