Files
certain/backend/scripts/pruefe_salden.py
T
GuidoandClaude Opus 5 7fa15c2b86 feat: Diagnose-Skript schlüsselt Kontostände auf
Zeigt je Konto Anfangssaldo, Einnahmen, Ausgaben und beide Transfer-
Richtungen einzeln, dazu mehrfach gebuchte Auszüge und doppelte
Buchungen. Damit lässt sich ein falscher Saldo auf seine Ursache
zurückführen, statt sie zu erraten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:58:38 +02:00

70 lines
3.4 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Schlüsselt auf, woraus sich jeder Kontostand zusammensetzt.
Saldo = Anfangssaldo + Einnahmen - Ausgaben - Transfers raus + Transfers rein.
Stimmt ein Kontostand nicht, liegt es fast immer an einer dieser fünf Zahlen –
oder an einem doppelt eingelesenen Auszug. Beides zeigt dieses Skript.
Aufruf: pruefe_salden.py [pfad-zur-db]
"""
import sqlite3
import sys
from collections import Counter
DB = sys.argv[1] if len(sys.argv) > 1 else "/var/lib/certain-finance/app.db"
con = sqlite3.connect(f"file:{DB}?mode=ro", uri=True)
con.row_factory = sqlite3.Row
def eins(sql, *p):
return con.execute(sql, p).fetchone()[0] or 0
def geld(c, w=""):
return f"{c / 100:,.2f}".replace(",", "'") + (f" {w}" if w else "")
print(f"Datenbank: {DB}\n")
for a in con.execute("SELECT id, name, currency, opening_balance_cents 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"])
raus = eins("SELECT SUM(amount_cents) FROM transactions WHERE account_id=? AND type='transfer'", a["id"])
rein = eins("SELECT SUM(COALESCE(to_amount_cents, amount_cents)) FROM transactions "
"WHERE to_account_id=? AND type='transfer'", a["id"])
saldo = a["opening_balance_cents"] + ein - aus - raus + rein
anz = eins("SELECT COUNT(*) FROM transactions WHERE account_id=? OR to_account_id=?", a["id"], a["id"])
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}")
print(f" + Einnahmen {geld(ein):>14}")
print(f" - Ausgaben {geld(aus):>14}")
print(f" - Transfers raus {geld(raus):>14}")
print(f" + Transfers rein {geld(rein):>14}")
print(f" = SALDO {geld(saldo, a['currency']):>14}")
print(f" {anz} Buchungen, {spanne[0] or '–'} bis {spanne[1] or '–'}")
# Doppelte Auszüge erkennen: gleicher Dateiname mehrfach gebucht.
batches = con.execute(
"SELECT filename, status, imported_count, created_at FROM import_batches "
"WHERE account_id=? ORDER BY created_at", (a["id"],)).fetchall()
namen = Counter(b["filename"] for b in batches if b["status"] == "committed")
for b in batches:
marke = " <-- Datei mehrfach gebucht!" if b["status"] == "committed" and namen[b["filename"]] > 1 else ""
print(f" [{b['status']:<9}] {b['imported_count']:>4} aus {b['filename'][:44]}{marke}")
# Buchungen, die sich in Datum+Betrag+Empfänger gleichen.
dop = con.execute(
"SELECT date, amount_cents, COALESCE(payee,''), COUNT(*) n FROM transactions "
"WHERE account_id=? GROUP BY 1,2,3 HAVING n > 1 ORDER BY n DESC LIMIT 5", (a["id"],)).fetchall()
if dop:
summe = eins("SELECT SUM(amount_cents * (n-1)) FROM (SELECT amount_cents, COUNT(*) n "
"FROM transactions WHERE account_id=? GROUP BY date, amount_cents, COALESCE(payee,'') "
"HAVING n > 1)", a["id"])
print(f" Mehrfach vorhanden: {len(dop)} Kombinationen, Summe der Überzähligen {geld(summe)}")
for d in dop:
print(f" {d[0]} {geld(d[1]):>10} {d[2][:34]:<34} x{d[3]}")
print()