feat: Diagnose zeigt Stichtag und Beispieltexte je Übertrag
Der Stichtag eines Kontos wird von der Saldo-Berechnung ignoriert – das Skript weist jetzt aus, wie viel aus Buchungen bis zum Stichtag stammt und trotzdem mitzählt. Die Übertrags-Matrix nennt dazu Beispieltexte, Erkennungstexte der Konten und Transfer-Schlagwörter, damit sich ein falscher Übertrag seinem Auslöser zuordnen lässt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@ def geld(c, w=""):
|
||||
|
||||
print(f"Datenbank: {DB}\n")
|
||||
|
||||
for a in con.execute("SELECT id, name, currency, opening_balance_cents FROM accounts "
|
||||
for a in con.execute("SELECT id, name, currency, opening_balance_cents, opening_date FROM accounts "
|
||||
"WHERE is_archived = 0 ORDER BY position, id"):
|
||||
ein = eins("SELECT SUM(amount_cents) FROM transactions WHERE account_id=? AND type='income'", a["id"])
|
||||
aus = eins("SELECT SUM(amount_cents) FROM transactions WHERE account_id=? AND type='expense'", a["id"])
|
||||
@@ -38,7 +38,9 @@ for a in con.execute("SELECT id, name, currency, opening_balance_cents FROM acco
|
||||
spanne = con.execute("SELECT MIN(date), MAX(date) FROM transactions WHERE account_id=?", (a["id"],)).fetchone()
|
||||
|
||||
print(f"=== {a['name']} ({a['currency']}, id {a['id']}) ===")
|
||||
print(f" Anfangssaldo {geld(a['opening_balance_cents']):>14}")
|
||||
stichtag = a["opening_date"]
|
||||
print(f" Anfangssaldo {geld(a['opening_balance_cents']):>14}"
|
||||
f" Stichtag: {stichtag or '– keiner –'}")
|
||||
print(f" + Einnahmen {geld(ein):>14}")
|
||||
print(f" - Ausgaben {geld(aus):>14}")
|
||||
print(f" - Transfers raus {geld(raus):>14}")
|
||||
@@ -46,6 +48,22 @@ for a in con.execute("SELECT id, name, currency, opening_balance_cents FROM acco
|
||||
print(f" = SALDO {geld(saldo, a['currency']):>14}")
|
||||
print(f" {anz} Buchungen, {spanne[0] or '–'} bis {spanne[1] or '–'}")
|
||||
|
||||
# Der Stichtag wird von der Saldo-Berechnung derzeit ignoriert: sie addiert
|
||||
# ALLE Buchungen. Gemeint ist aber "an diesem Tag stand so viel drauf", also
|
||||
# dürften nur spätere Buchungen zählen. Hier steht, was das ausmacht.
|
||||
if stichtag:
|
||||
vor = (eins("SELECT SUM(amount_cents) FROM transactions WHERE account_id=? "
|
||||
"AND type='income' AND date <= ?", a["id"], stichtag)
|
||||
- eins("SELECT SUM(amount_cents) FROM transactions WHERE account_id=? "
|
||||
"AND type='expense' AND date <= ?", a["id"], stichtag)
|
||||
- eins("SELECT SUM(amount_cents) FROM transactions WHERE account_id=? "
|
||||
"AND type='transfer' AND date <= ?", a["id"], stichtag)
|
||||
+ eins("SELECT SUM(COALESCE(to_amount_cents, amount_cents)) FROM transactions "
|
||||
"WHERE to_account_id=? AND type='transfer' AND date <= ?", a["id"], stichtag))
|
||||
if vor:
|
||||
print(f" ACHTUNG: {geld(vor)} stammt aus Buchungen BIS zum Stichtag und wird")
|
||||
print(f" trotzdem mitgezählt. Ohne sie: {geld(saldo - vor, a['currency'])}")
|
||||
|
||||
# Doppelte Auszüge erkennen: gleicher Dateiname mehrfach gebucht.
|
||||
batches = con.execute(
|
||||
"SELECT filename, status, imported_count, created_at FROM import_batches "
|
||||
|
||||
@@ -41,6 +41,13 @@ for r in con.execute(
|
||||
print(f"{name(r['a'])[:23]:<24}{name(r['b'])[:23]:<24}{r['n']:>5}"
|
||||
f"{geld(r['raus']) + ' ' + waehrung(r['a']):>14}"
|
||||
f"{geld(r['rein']) + ' ' + waehrung(r['b']):>14}")
|
||||
# Beispieltexte: sie verraten, welches Schlagwort oder welcher Erkennungs-
|
||||
# text den Übertrag ausgelöst hat.
|
||||
for b in con.execute(
|
||||
"SELECT COALESCE(payee,'') p, COALESCE(description,'') d, COUNT(*) n "
|
||||
"FROM transactions WHERE type='transfer' AND account_id=? AND to_account_id=? "
|
||||
"GROUP BY p, d ORDER BY n DESC LIMIT 3", (r["a"], r["b"])):
|
||||
print(f" {b['n']:>3}x {(b['p'] + ' | ' + b['d']).strip(' |')[:66]}")
|
||||
|
||||
offen = con.execute(
|
||||
"SELECT COUNT(*) n, SUM(amount_cents) s FROM transactions "
|
||||
@@ -74,3 +81,20 @@ for r in con.execute(
|
||||
ziel = name(r["b"]) if r["b"] else "– kein Ziel –"
|
||||
print(f" {r['date']} {geld(r['amount_cents']):>10} {waehrung(r['a'])} "
|
||||
f"{name(r['a'])[:18]:<18} -> {ziel[:18]:<18} {r['p'][:30]}")
|
||||
|
||||
|
||||
print("\n=== Erkennungstexte der Konten ===")
|
||||
print("Ein kurzer Text trifft zu viel: er wird als Teilstring gesucht.")
|
||||
for r in con.execute(
|
||||
"SELECT a.name, i.kind, i.value FROM account_identifiers i "
|
||||
"JOIN accounts a ON a.id = i.account_id ORDER BY LENGTH(i.value), a.name"):
|
||||
warn = " <-- sehr kurz, trifft leicht Fremdes" if len(r["value"]) < 8 else ""
|
||||
print(f" {r['name'][:24]:<26}{r['kind']:<10}{r['value']}{warn}")
|
||||
|
||||
print("\n=== Transfer-Schlagwörter ===")
|
||||
for r in con.execute(
|
||||
"SELECT r.pattern, r.target_account_id t FROM categorization_rules r "
|
||||
"WHERE r.is_transfer = 1 ORDER BY LENGTH(r.pattern)"):
|
||||
ziel = name(r["t"]) if r["t"] else "– Ziel wird geraten –"
|
||||
warn = " <-- sehr kurz" if len(r["pattern"]) < 8 else ""
|
||||
print(f" {r['pattern'][:34]:<36}-> {ziel}{warn}")
|
||||
|
||||
Reference in New Issue
Block a user