feat: Diagnose-Skript für fehlende Schlagwörter

Die Kategorie-Liste zeigt nur manuelle, einer bestehenden Kategorie zugeordnete
Regeln. Bleibt sie leer, obwohl Regeln in der Datenbank stehen, sagt das Skript
warum: Es schlüsselt den Bestand nach source, kind und is_transfer auf, zählt
Regeln ohne Kategorie sowie solche, deren Kategorie nicht mehr existiert, und
nennt die Zahl der tatsächlich sichtbaren.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-21 14:33:05 +02:00
co-authored by Claude Opus 4.8
parent ff95bc5344
commit 3948d98273
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Zeigt, warum Schlagwörter in der Oberfläche fehlen könnten.
Die Liste einer Kategorie zeigt nur Regeln mit ``source='manual'``, die einer
Kategorie zugeordnet und keine Transfer-Regel sind. Automatisch gelernte Regeln
tauchen dort bewusst nicht auf. Dieses Skript schlüsselt den Bestand danach auf.
Aufruf: pruefe_schlagwoerter.py [pfad/zur/app.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 spalten(tabelle: str) -> set[str]:
return {r["name"] for r in con.execute(f"PRAGMA table_info({tabelle})")}
vorhanden = spalten("categorization_rules")
print(f"Datenbank: {DB}")
print(f"Spalten in categorization_rules: {', '.join(sorted(vorhanden))}\n")
gesamt = con.execute("SELECT COUNT(*) FROM categorization_rules").fetchone()[0]
print(f"Regeln insgesamt: {gesamt}")
for feld in ("source", "kind", "is_transfer", "match_field"):
if feld not in vorhanden:
print(f" {feld:<12} – Spalte fehlt")
continue
verteilung = Counter(
r[0] for r in con.execute(f"SELECT {feld} FROM categorization_rules")
)
print(f" {feld:<12} {dict(verteilung)}")
ohne_kat = con.execute(
"SELECT COUNT(*) FROM categorization_rules WHERE category_id IS NULL"
).fetchone()[0]
verwaist = con.execute(
"SELECT COUNT(*) FROM categorization_rules r WHERE r.category_id IS NOT NULL"
" AND NOT EXISTS (SELECT 1 FROM categories c WHERE c.id = r.category_id)"
).fetchone()[0]
print(f" ohne Kategorie: {ohne_kat}")
print(f" Kategorie zeigt ins Leere: {verwaist} <-- solche erscheinen nirgends")
# Genau die Bedingung, die die Oberfläche für die Kategorie-Liste nutzt.
sichtbar = con.execute(
"SELECT COUNT(*) FROM categorization_rules r"
" JOIN categories c ON c.id = r.category_id"
" WHERE r.source = 'manual' AND r.is_transfer = 0"
).fetchone()[0]
print(f"\nIn der Kategorie-Liste sichtbar: {sichtbar} von {gesamt}")
print("\nBeispiele (die ersten 12):")
print(f" {'Muster':<26}{'source':<10}{'kind':<10}{'transfer':<9}{'Kategorie'}")
for r in con.execute(
"SELECT r.pattern, r.source, r.kind, r.is_transfer, c.name AS kat"
" FROM categorization_rules r LEFT JOIN categories c ON c.id = r.category_id"
" ORDER BY r.id LIMIT 12"
):
print(f" {r['pattern'][:24]:<26}{str(r['source']):<10}{str(r['kind']):<10}"
f"{str(r['is_transfer']):<9}{r['kat'] or '— fehlt —'}")
con.close()