Files
certain/backend/scripts/pruefe_transfers.py
T
GuidoandClaude Opus 5 e2387102c1 feat: Diagnose-Skript für Übertrag-Flüsse
Zeigt als Matrix, welches Konto an welches überträgt, dazu Überträge
ohne Zielkonto und Währungswechsel ohne Zielbetrag – Letztere schreiben
den Fremdbetrag unkonvertiert gut und verfälschen den Saldo.

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

77 lines
3.1 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
"""Zeigt, welches Konto an welches überträgt – und wo dabei etwas fehlt.
Ein falscher Kontostand kommt selten von den Ausgaben, fast immer von den
Überträgen: falsche Richtung, fehlendes Zielkonto oder ein Währungswechsel
ohne Zielbetrag (dann wird der Fremdbetrag 1:1 gutgeschrieben).
Aufruf: pruefe_transfers.py [pfad-zur-db]
"""
import sqlite3
import sys
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
konten = {r["id"]: r for r in con.execute("SELECT id, name, currency FROM accounts")}
def name(i):
return konten[i]["name"] if i in konten else f"?{i}"
def waehrung(i):
return konten[i]["currency"] if i in konten else "?"
def geld(c):
return f"{(c or 0) / 100:,.2f}".replace(",", "'")
print(f"Datenbank: {DB}\n")
print("=== Wer überträgt an wen ===")
print(f"{'von':<24}{'nach':<24}{'Anz':>5}{'Summe raus':>14}{'Summe rein':>14}")
print("-" * 81)
for r in con.execute(
"SELECT account_id a, to_account_id b, COUNT(*) n, SUM(amount_cents) raus, "
"SUM(COALESCE(to_amount_cents, amount_cents)) rein FROM transactions "
"WHERE type='transfer' AND to_account_id IS NOT NULL GROUP BY a, b ORDER BY raus DESC"
):
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}")
offen = con.execute(
"SELECT COUNT(*) n, SUM(amount_cents) s FROM transactions "
"WHERE type='transfer' AND to_account_id IS NULL").fetchone()
print(f"\nÜberträge OHNE Zielkonto: {offen['n']}, Summe {geld(offen['s'])}"
" <-- gehen vom Quellkonto ab und kommen nirgends an")
print("\n=== Währungswechsel ohne Zielbetrag ===")
schief = con.execute(
"SELECT t.id, t.date, t.amount_cents, COALESCE(t.payee,'') p, a.name qn, a.currency qw, "
"z.name zn, z.currency zw FROM transactions t "
"JOIN accounts a ON a.id = t.account_id JOIN accounts z ON z.id = t.to_account_id "
"WHERE t.type='transfer' AND a.currency <> z.currency AND t.to_amount_cents IS NULL "
"ORDER BY t.amount_cents DESC").fetchall()
if not schief:
print(" keine – gut.")
else:
summe = sum(r["amount_cents"] for r in schief)
print(f" {len(schief)} Stück, Summe {geld(summe)}"
" <-- der Betrag wird ohne Umrechnung gutgeschrieben")
for r in schief[:12]:
print(f" {r['date']} {geld(r['amount_cents']):>10} {r['qw']} -> {r['zw']} "
f"{r['qn'][:16]:<16} -> {r['zn'][:16]:<16} {r['p'][:26]}")
print("\n=== Die 12 grössten Überträge ===")
for r in con.execute(
"SELECT t.date, t.amount_cents, t.to_amount_cents, COALESCE(t.payee,'') p, "
"t.account_id a, t.to_account_id b FROM transactions t WHERE t.type='transfer' "
"ORDER BY t.amount_cents DESC LIMIT 12"
):
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]}")