fix: Parser-Noise-Filter, Sammelstopp am Schlusssaldo, Audit-Werkzeug
Alle drei Bank-Parser gegen die realen Fixtures gehaertet: - DKB/VR/HVB: Sammlung endet am Schlusssaldo (break), damit Summen, Rechnungsabschluss und Hinweisseiten nicht mehr in die letzte Buchung rutschen. - Generische, rein strukturelle Noise-Filter fuer wiederkehrende Seitenfuss-/Kopfbloecke und Blattwechsel-Reste (keine Kontodaten hart codiert); Kopf-/Hinweisbloecke werden als Sequenz uebersprungen. - VR: Rekonstruktion harter Zeilenumbrueche im Verwendungszweck unter konservativen Schutzbedingungen (nur purpose, nicht counterparty). - Neue Noise- und expected-Value-Tests; expected_*.json gitignored. - Audit-Werkzeug scripts/parser_audit.py ergaenzt (nur lokale Nutzung). Transaktionszahlen und Saldo-Differenzen unveraendert; max. Laenge des Verwendungszwecks je Bank deutlich unter 300 Zeichen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,11 +49,24 @@ RE_PAGE_NOISE = re.compile(
|
||||
r"|Seite\s+\d+\s+von\s+\d+)",
|
||||
re.I,
|
||||
)
|
||||
# Weitere strukturelle Kopf-/Fußzeilen am Seitenwechsel (keine Kontodaten hart
|
||||
# codiert): Formularkennung, Ansprechpartner-Zeile, zusammengesetzte IBAN+BIC-Zeile.
|
||||
RE_HEADER_EXTRA = re.compile(
|
||||
r"(?:Smart Banking Team" # Ansprechpartner-/Kopfzeile
|
||||
r"|^\d{4}\.\d{2}/[A-Z]\d" # Formularkennung (z. B. 5202.70/C0..)
|
||||
r"|^DE\d{2}\s\d{8}\s\d{10}\s[A-Z]{8}" # IBAN + BIC in einer Zeile
|
||||
r")"
|
||||
)
|
||||
# Ganzseitiger Hinweis-/Rückseitenblock zwischen den Buchungsseiten: als
|
||||
# Sequenz vom Startsatz bis zur nächsten "Seite N von M"-Zeile überspringen.
|
||||
RE_HINT_START = re.compile(r"^Wir bitten Sie")
|
||||
RE_SEITE = re.compile(r"^Seite\s+\d+\s+von\s+\d+")
|
||||
|
||||
|
||||
def _is_noise(line: str) -> bool:
|
||||
return bool(RE_OPENING.search(line) or RE_CLOSING.search(line)
|
||||
or RE_CARRYOVER.match(line) or RE_PAGE_NOISE.match(line))
|
||||
or RE_CARRYOVER.match(line) or RE_PAGE_NOISE.match(line)
|
||||
or RE_HEADER_EXTRA.search(line) or RE_PERIOD.search(line))
|
||||
|
||||
|
||||
def parse(path: Path) -> ParsedStatement:
|
||||
@@ -71,10 +84,22 @@ def parse(path: Path) -> ParsedStatement:
|
||||
iban_m = RE_IBAN.search(text)
|
||||
txs: list[ParsedTransaction] = []
|
||||
pending: dict | None = None
|
||||
in_hint = False
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
# Ganzseitigen Hinweisblock als Sequenz überspringen.
|
||||
if in_hint:
|
||||
if RE_SEITE.match(line):
|
||||
in_hint = False
|
||||
continue
|
||||
if RE_HINT_START.match(line):
|
||||
in_hint = True
|
||||
continue
|
||||
# Schlusssaldo erreicht: laufende Buchung abschließen und Sammlung beenden.
|
||||
if RE_CLOSING.search(line):
|
||||
break
|
||||
m = RE_TX_HEAD.match(line)
|
||||
if m:
|
||||
if pending:
|
||||
|
||||
Reference in New Issue
Block a user