From b4cd5b7ca3071a6b2683d41946f4e4747e90bd02 Mon Sep 17 00:00:00 2001 From: Guido Date: Fri, 21 Aug 2026 17:04:11 +0200 Subject: [PATCH] feat: Auszugs-Vergleich nennt die fehlenden Buchungen einzeln MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Tagessummen sagen nur, dass etwas fehlt. Jetzt werden die Betraege je Tag als Menge verglichen: was im Auszug steht und nicht in Certain, und was Certain zusaetzlich hat – Letzteres mit Art und Text. Co-Authored-By: Claude Opus 5 --- backend/scripts/pruefe_gegen_auszug.py | 40 +++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/backend/scripts/pruefe_gegen_auszug.py b/backend/scripts/pruefe_gegen_auszug.py index 6e7cddc..36fd8db 100755 --- a/backend/scripts/pruefe_gegen_auszug.py +++ b/backend/scripts/pruefe_gegen_auszug.py @@ -12,7 +12,7 @@ Aufruf (Spalten heissen wie in der Kopfzeile der Datei): import argparse import csv import sqlite3 -from collections import defaultdict +from collections import Counter, defaultdict from datetime import date NICHT_GEBUCHT = {"PENDING", "AUSSTEHEND", "REVERTED", "STORNIERT", "DECLINED", @@ -59,6 +59,7 @@ def tag(s) -> date | None: # --- Auszug einlesen --------------------------------------------------------- fs, fv = (a.filt.split("=", 1) if a.filt else (None, None)) je_tag_datei: dict[date, int] = defaultdict(int) +zeilen_datei: dict[date, list] = defaultdict(list) zeilen = 0 with open(a.datei, encoding="utf-8-sig", newline="") as f: for r in csv.DictReader(f, delimiter=a.trenner): @@ -71,25 +72,30 @@ with open(a.datei, encoding="utf-8-sig", newline="") as f: continue betrag = zahl(r.get(a.betrag)) - zahl(r.get(a.gebuehr)) if a.gebuehr else zahl(r.get(a.betrag)) je_tag_datei[d] += betrag + zeilen_datei[d].append(betrag) zeilen += 1 # --- Datenbank --------------------------------------------------------------- con = sqlite3.connect(f"file:{a.db}?mode=ro", uri=True) je_tag_db: dict[date, int] = defaultdict(int) -for d, typ, betrag, ziel, zielbetrag in con.execute( - "SELECT date, type, amount_cents, to_account_id, to_amount_cents FROM transactions " +zeilen_db: dict[date, list] = defaultdict(list) +for d, typ, betrag, ziel, zielbetrag, payee, besch in con.execute( + "SELECT date, type, amount_cents, to_account_id, to_amount_cents, " + "COALESCE(payee,''), COALESCE(description,'') FROM transactions " "WHERE account_id = ? OR to_account_id = ?", (a.konto, a.konto)): dd = tag(d) if dd is None: continue if typ == "income": - je_tag_db[dd] += betrag + wert = betrag elif typ == "expense": - je_tag_db[dd] -= betrag + wert = -betrag elif ziel == a.konto: - je_tag_db[dd] += zielbetrag if zielbetrag is not None else betrag + wert = zielbetrag if zielbetrag is not None else betrag else: - je_tag_db[dd] -= betrag + wert = -betrag + je_tag_db[dd] += wert + zeilen_db[dd].append((wert, typ, (payee + " " + besch).strip())) def geld(c): @@ -122,3 +128,23 @@ for d, diff in tage[:25]: f"Differenz {geld(diff):>10}") if not tage: print(" keine – der Auszug ist vollständig und korrekt gebucht.") + + +print("\n=== Welche Buchungen fehlen bzw. sind zu viel ===") +print("(Vergleich der einzelnen Betraege je Tag; der Auszug enthaelt keine Texte)") +for d, diff in tage[:10]: + aus, dbs = Counter(zeilen_datei[d]), Counter(w for w, _, _ in zeilen_db[d]) + fehlt = sorted((aus - dbs).elements()) + zuviel = sorted((dbs - aus).elements()) + if not fehlt and not zuviel: + continue + print(f"\n {d} (Differenz {geld(diff)})") + if fehlt: + print(f" fehlt in Certain: {', '.join(geld(v) for v in fehlt)}") + if zuviel: + print(" zu viel in Certain:") + gezeigt = Counter() + for w, typ, text in sorted(zeilen_db[d], key=lambda x: x[0]): + if gezeigt[w] < Counter(zuviel)[w]: + gezeigt[w] += 1 + print(f" {geld(w):>10} {typ:<9} {text[:52]}")