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:
@@ -1,3 +1,5 @@
|
||||
import json
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
@@ -10,6 +12,9 @@ FIXTURES = Path(__file__).parent / "fixtures"
|
||||
CASES = [("vr_beispiel.pdf", "vr"), ("hvb_beispiel.pdf", "hvb"),
|
||||
("dkb_beispiel.pdf", "dkb")]
|
||||
|
||||
NOISE = re.compile(
|
||||
r"(Kontostand|Übertrag|alter Saldo|neuer Saldo|Seite \d|Blatt \d)", re.I)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename,bank", CASES)
|
||||
def test_parse_fixture(filename, bank):
|
||||
@@ -22,3 +27,32 @@ def test_parse_fixture(filename, bank):
|
||||
assert stmt.closing_balance is not None
|
||||
assert len(stmt.transactions) > 0
|
||||
assert balance_difference(stmt) == Decimal("0")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename,bank", CASES)
|
||||
def test_no_noise_in_parsed_fields(filename, bank):
|
||||
path = FIXTURES / filename
|
||||
if not path.exists():
|
||||
pytest.skip(f"Fixture {filename} fehlt")
|
||||
stmt = parse_pdf(path)
|
||||
for t in stmt.transactions:
|
||||
assert not NOISE.search(t.purpose), f"Noise im Verwendungszweck: #{stmt.transactions.index(t)}"
|
||||
assert not NOISE.search(t.counterparty), f"Noise im Empfänger: #{stmt.transactions.index(t)}"
|
||||
assert stmt.period_start <= t.booking_date <= stmt.period_end
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename,bank", CASES)
|
||||
def test_expected_values(filename, bank):
|
||||
path = FIXTURES / filename
|
||||
exp_path = FIXTURES / f"expected_{bank}.json"
|
||||
if not path.exists() or not exp_path.exists():
|
||||
pytest.skip("Fixture oder expected-Datei fehlt")
|
||||
stmt = parse_pdf(path)
|
||||
exp = json.loads(exp_path.read_text())
|
||||
assert len(stmt.transactions) == exp["transaction_count"]
|
||||
for c in exp["spot_checks"]:
|
||||
t = stmt.transactions[c["index"]]
|
||||
assert str(t.booking_date) == c["booking_date"]
|
||||
assert str(t.amount) == c["amount"]
|
||||
assert c["counterparty_contains"].lower() in t.counterparty.lower()
|
||||
assert c["purpose_contains"].lower() in t.purpose.lower()
|
||||
|
||||
Reference in New Issue
Block a user