Commit Graph
146 Commits
Author SHA1 Message Date
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
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
GuidoandClaude Opus 4.8 3948d98273 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>
2026-08-21 14:33:05 +02:00
GuidoandClaude Opus 4.8 ff95bc5344 feat: Backup-Prüfung nennt die neueste vollständige Sicherung
Bei 35 Sicherungen hilft eine blosse Liste wenig. Das Skript zeigt jetzt je
Sicherung Zeitpunkt, Schlagwörter, Budget-Vorlagen und wie viele Buchungen eine
Kategorie tragen – und nennt am Ende die neueste noch vollständige samt der
fertigen Befehle zum Zurückspielen. Tabellen, die es zum Sicherungszeitpunkt
noch nicht gab, erscheinen als '–' statt als Fehler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 14:25:40 +02:00
GuidoandClaude Opus 4.8 33bbc535cd feat: Skript zum Prüfen der Datenbank-Sicherungen
Listet je Backup, wie viele Schlagwörter, Budget-Vorlagen und kategorisierte
Buchungen darin stecken. Nach einem Datenverlust sieht man damit auf einen Blick,
welche Sicherung noch vollständig ist – statt nach Zeitstempel zu raten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 14:23:12 +02:00
GuidoandClaude Opus 4.8 35d89b2b8f fix: Migrationen löschen keine abhängigen Daten mehr
Auf SQLite baut Alembic (render_as_batch) eine Tabelle um, indem es eine Kopie
anlegt und das Original löscht. Bei DROP TABLE führt SQLite ein implizites DELETE
aus – mit aktiven Fremdschlüsseln schlägt das per ON DELETE CASCADE auf abhängige
Tabellen durch.

Migration 0013 fügte `categories` nur ein Feld hinzu und vernichtete dabei alle
Schlagwörter und Budget-Vorlagen und setzte die Kategorie sämtlicher Buchungen
auf NULL. Reproduziert und behoben: Fremdschlüssel werden für die Dauer der
Migration abgeschaltet (vor der Transaktion, sonst ignoriert SQLite den Befehl)
und danach wieder aktiviert. Lässt sich das Abschalten nicht bestätigen, bricht
die Migration ab, statt Daten zu riskieren.

Regressionstest legt Daten auf Stand 0012 an, migriert auf head und prüft, dass
Schlagwörter, Budget-Vorlagen und Kategoriezuordnungen erhalten bleiben.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 14:21:03 +02:00
GuidoandClaude Opus 4.8 af865a5c7b fix: neutrale Kategorien gelten für Ausgabe UND Einnahme
Kategorien erben ihre Art von der Gruppe, und die ist entweder Ausgabe oder
Einnahme. Für einen Kontoübertrag passt beides nicht: Auf dem einen Konto geht
Geld raus, auf dem anderen kommt es rein – man musste also zwei Kategorien in
zwei Gruppen anlegen.

Als „nicht in Auswertungen einrechnen" markierte Kategorien werden jetzt in
beiden Richtungen angeboten (Buchungsformular und Import-Prüfansicht), und ihre
Schlagwörter greifen unabhängig vom Vorzeichen. Eine Kategorie „Kontoübertrag"
in einer Ausgabe-Gruppe genügt damit für beide Seiten.

Test für beide Richtungen; 94 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 14:09:54 +02:00
GuidoandClaude Opus 4.8 7674e47f43 fix: Test für neutrale Kategorien misst relativ statt absolut
Die Testdatenbank lebt über den ganzen Lauf; im laufenden Monat liegen also
schon Buchungen anderer Tests. Absolute Erwartungswerte gingen allein durch,
im Gesamtlauf nicht. Jetzt zählt die Differenz vorher/nachher. 93 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 10:18:15 +02:00
GuidoandClaude Opus 4.8 5c8b53a995 feat: Kategorien aus Auswertungen ausnehmen (z. B. „Kontoübertrag")
Umbuchungen zwischen eigenen Konten sind keine Ausgabe – das Geld ist nicht weg,
es liegt woanders. Bisher blieb nur die Wahl zwischen einer echten Transfer-
Buchung (bildet die Wirklichkeit ab, braucht aber auf beiden Seiten passende
Regeln und schätzt bei Fremdwährung den Gegenwert) oder einer gewöhnlichen
Ausgabe, die Budget und Statistik verfälscht.

Kategorien haben jetzt den Schalter „Nicht in Auswertungen einrechnen": Die
Buchung wirkt weiter auf den Kontostand, bleibt aber aus Budget, Dashboard und
Auswertungen heraus. Gefiltert wird zentral in actual_expense_by_category und
income_by_category – damit greift es zugleich für Budget-Donut, Budget-
Fortschritt, Einnahmen/Ausgaben, Ausgaben-Anteile und die Budget-Seite.

Das ersetzt die Transfer-Paarung nicht, es ist der einfachere Weg: kein
Gegenkonto, keine Kursschätzung, dafür ohne Verbindung zwischen den beiden Seiten.

Migration 0013, Schalter im Kategorie-Dialog, Kennzeichnung in der Liste, Test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 10:17:20 +02:00
GuidoandClaude Opus 4.8 511cf1abe0 fix: Gegenbuchung nimmt den besten statt des erstbesten Treffers
Liegen mehrere Währungstausche im selben Zeitfenster, schnappte der zuerst
geprüfte Transfer der eigentlich gemeinten Zeile das Gegenstück weg – diese blieb
dann ohne Gegenkonto stehen. Jetzt gewinnt der Kandidat mit der geringsten
Datums- und Betragsabweichung.

Toleranz von 10 % auf 20 % erhöht: Wir schätzen die Gegenseite mit dem EZB-Kurs,
die Bank rechnet mit ihrem eigenen – und fehlt uns der Kurs, schätzen wir 1:1.
Ein echter Tausch lag damit schnell über 10 % daneben (CHF 150.00 -> EUR 165.60
sind 10,4 %), eine fremde Buchung trifft das Zeitfenster praktisch nie.

An den echten Auszügen: offene Zeilen im Revolut-EUR-Import von 3 auf 1 gesunken.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 09:56:09 +02:00
GuidoandClaude Opus 4.8 e10ccdf185 feat: Dashboard-Karte „Ausgaben-Anteile"
Zeigt für den laufenden Budget-Monat, wie sich die tatsächlichen Ausgaben
prozentual auf die Kategorien verteilen – absteigend sortiert, mit Balken in der
Kategoriefarbe, Prozentwert und Betrag.

Anders als der Budget-Donut hängt sie nicht am geplanten Budget: Sie zeigt reine
Ist-Ausgaben, auch wenn für den Monat nichts geplant wurde. Umschaltbar zwischen
Kategorien (Standard) und Gruppen.

Die Anteile rechnet die Oberfläche aus Betrag und Summe – gerundete Prozente vom
Server addierten sich sonst nicht auf 100 %. Wird die Karte schmal, gibt zuerst
der Betrag nach; ohne Kategorienamen sagt die Zeile nichts mehr aus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 09:30:14 +02:00
GuidoandClaude Opus 4.8 8d767e728a feat: Buchen-Knopf auch unter der Prüfliste + Verlauf aufräumen
- Bei mehreren hundert Zeilen ist der Knopf oben längst aus dem Bild gescrollt;
  unter der Liste steht jetzt derselbe.
- Rückgängig gemachte Importe lassen sich aus dem Verlauf entfernen. Nur diese:
  solange ein Import steht, ist sein Eintrag die einzige Möglichkeit, seine
  Buchungen wieder loszuwerden – der Server lehnt das sonst ab.

Test für beide Fälle des neuen Endpunkts; 92 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 09:20:29 +02:00
GuidoandClaude Opus 4.8 ab5410ca6f fix: Saldo-Abgleich meldet Zeilen, die auf ein Gegenkonto warten
Eine Zeile mit „Gegenkonto wählen" wird nicht gebucht, der Abgleich zählte ihren
Betrag aber trotzdem mit und meldete „stimmt überein" – das Geld fehlte im Konto,
ohne dass es auffiel.

Duplikate und Gegenbuchungen zählen weiterhin mit (ihr Betrag steckt bereits in
einer früher gebuchten Buchung); wartende Zeilen zählen jetzt als 0 und tauchen
als erste Abweichung mit Datum und Empfänger auf.

Nachgestellt am echten Raiffeisen-Auszug: Zeigt die Transfer-Regel „Übertrag" auf
das importierte Konto selbst, bleibt die Gutschrift über 92.00 ungebucht; der
Abgleich nennt jetzt genau diese Zeile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 09:17:22 +02:00
GuidoandClaude Opus 4.8 c438d1ec50 feat: Bank-Vorlage für N26
Spaltenzuordnung aus einem echten Export (Betrag in Spalte „Amount (EUR)",
Partner als Empfänger, Payment Reference als Zweck, Partner-IBAN für die
Transfer-Erkennung). Eine Kontostand-Spalte liefert N26 nicht, dort bleibt der
Saldo-Abgleich also aus.

Nachgerechnet: 246 Zeilen, Summe -719.51; mit Anfangssaldo 872.95 ergibt das
exakt die tatsächlichen 153.44 EUR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 09:07:21 +02:00
GuidoandClaude Opus 4.8 4a6f7a0814 feat: Zeilenfilter für Auszüge mit mehreren Pockets + Gebühren-Richtung
Der Revolut-Auszug enthält ALLE Produkte in einer Datei – im CHF-Export stehen
533 Giro-Zeilen und 255 Zeilen des Ersparten, jedes mit eigenem Kontostand-
Verlauf. Ohne Trennung landeten beide auf demselben Konto und die Pocket-
Bewegungen zählten doppelt.

- Import-Vorlage kennt einen Zeilenfilter („nur Zeilen aus Spalte X mit Wert Y"),
  Migration 0012; damit bekommt jedes Pocket sein eigenes Konto
- Gebührenzeilen (Betrag 0.00) können Belastung ODER Erstattung sein – beide
  sehen identisch aus. Die Richtung kommt jetzt aus der Kontostand-Spalte;
  ohne sie bleibt die Belastung der Normalfall
- Tests für beides; 90 grün

Nachgerechnet an den echten Auszügen: Raiffeisen 223.37, Revolut CHF 61.88,
Erspartes 6.71, Revolut EUR 0.68 – alle vier stimmen zeilengenau mit der
Kontostand-Spalte überein.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 09:01:07 +02:00
GuidoandClaude Opus 4.8 595662e515 feat: ein Schlagwort darf mehrere Beträge haben
Ein Wort war je Art nur einmal erlaubt, also ließ sich genau ein Preis
hinterlegen. Im Personalrestaurant zahlt man aber mal 5.00 und mal 7.50 – der
zweite Preis ließ sich nicht erfassen.

Der Betrag gehört jetzt in den Unique-Schlüssel: „sv gruppe = 5.00" und
„sv gruppe = 7.50" sind zwei Regeln derselben Kategorie, dasselbe Wort OHNE
Betrag bleibt der Auffang für alle übrigen Beträge (und darf in einer anderen
Kategorie liegen). Betrags-Regeln greifen vor dem Auffang.

- Migration 0011 (Unique-Schlüssel um amount_cents erweitert)
- Konflikt nur noch bei gleichem Wort UND gleichem Betrag; gilt auch beim
  Umbenennen und für Transfer-Schlagwörter
- Test: zwei Preise treffen die Kategorie, ein dritter Betrag den Auffang; 88 grün

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 08:34:24 +02:00
GuidoandClaude Opus 4.8 187a1119a2 fix: Gegenbuchungs-Erkennung beachtet die Richtung
Die Suche prüfte nur Betrag und Zeitfenster. Ein erwarteter Transfer-EINGANG
passte damit auch auf eine gleich hohe AUSGABE: Die echte Ausgabe galt als
Gegenbuchung, wurde nicht gebucht und der Saldo lief um ihren Betrag zu hoch.

Jetzt muss das Vorzeichen zur Seite des Transfers passen (wo Geld ankam, kann
keine Ausgabe die Gegenseite sein). Test deckt genau diesen Fall ab; 87 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 08:31:18 +02:00
GuidoandClaude Opus 4.8 62eab06bdc feat: Gegenseite eines Übertrags braucht keine eigene Transfer-Regel
Bisher musste die Regel auf BEIDEN Auszügen ein fremdes Gegenkonto liefern. Beim
Empfangs-Auszug zeigt sie aber auf das Konto, das gerade importiert wird – damit
blieb die Zeile ohne Gegenkonto und wurde still als Einnahme gebucht.

- Gegenbuchungen werden über die Konto-Seite bestehender Transfers gesucht, nicht
  über das Konto-Paar. Der Empfangs-Auszug braucht damit keine Regel mehr; eine
  Regel auf der abgehenden Seite genügt für beide Dateien.
- Trifft eine Transfer-Regel, ohne dass ein Gegenkonto feststeht, wird die Zeile
  nicht mehr als Einnahme/Ausgabe gebucht, sondern in der Prüfansicht mit
  „Gegenkonto wählen" markiert – sonst zählt die Bewegung je nach Import-
  Reihenfolge doppelt.
- Tests für beide Reihenfolgen; 86 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 08:20:13 +02:00
GuidoandClaude Opus 4.8 cb4f3a8909 feat: Währungstausch wird über beide Auszüge als ein Transfer erkannt
Revolut tauscht Geld zwischen Währungskonten; im CHF-Auszug steht -100.00 CHF,
im EUR-Auszug +106.42 EUR. Die Gegenbuchungs-Erkennung verglich exakte Beträge
und griff deshalb nie – die Bewegung wurde zweimal gezählt.

- _find_counterpart erkennt die Gegenseite bei unterschiedlichen Währungen über
  Konto-Paar, Zeitfenster und einen plausiblen Betrag (10 % Toleranz, weil wir
  mit dem EZB-Kurs schätzen und die Bank ihren eigenen nutzt)
- die Gegenbuchung nennt den TATSÄCHLICHEN Betrag: er ersetzt beim Buchen die
  Schätzung im bereits gebuchten Transfer, damit beide Salden exakt stimmen
- Test über beide Auszüge: ein Transfer, keine Doppelzählung, EUR-Seite exakt

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 08:13:59 +02:00
GuidoandClaude Opus 4.8 d97e08e913 fix: Datumsspalten mit Uhrzeit werden auch ohne passendes Format erkannt
Der Revolut-Export führt "2026-01-06 23:11:14" in der Datumsspalte. Wählte man
im Dropdown JJJJ-MM-TT, scheiterte das Parsen an der Uhrzeit – jede Zeile hatte
kein Datum und galt als ungültig ("137 Zeilen erkannt, 0 werden gebucht"),
obwohl die Auswahl richtig aussah.

- parse_date schneidet eine überzählige Uhrzeit ab und kommt auch damit klar,
  wenn das Format eine Uhrzeit erwartet, der Wert aber keine hat (ISO-T ebenso)
- Dropdown um die Varianten mit Uhrzeit ergänzt, damit die Bank-Vorlagen ihren
  echten Eintrag zeigen statt eines leeren Feldes
- Test: reines Datumsformat auf Datei mit Uhrzeit -> alle Zeilen erkannt, 84 grün

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 08:02:18 +02:00
GuidoandClaude Opus 4.8 5a747fbef4 feat: Saldo-Abgleich beim CSV-Import gegen die Kontostand-Spalte
Bisher merkte man einen Importfehler erst am falschen Endsaldo, ohne Hinweis auf
die Ursache. Enthält der Auszug eine laufende Kontostand-Spalte, gleicht der
Import jetzt jede Zeile dagegen ab: Die Spalte sagt, wie sich der Saldo
tatsächlich verändert hat, unsere Erkennung muss dazu passen.

- neue Spaltenzuordnung "Kontostand (laufend)"; Presets Raiffeisen/Revolut ergänzt
- Prüfansicht zeigt: stimmt überein ODER Differenz + erste abweichende Zeile
  (Datum, Empfänger, Betrag laut Bank vs. erkannt)
- meldet zusätzlich den Anfangssaldo laut Datei – häufigste Fehlerquelle
- rechnet mit dem tatsächlich gebuchten Betrag, damit auch nicht gebuchte Zeilen
  auffallen (z. B. nicht zugeordnete Gebührenspalte); Fremdwährungs-Zeilen
  gehören zu einem anderen Saldo-Verlauf und bleiben außen vor
- erkennt absteigend sortierte Auszüge
- Tests: stimmiger Auszug (ok) und fehlende Gebührenspalte (zeigt genau die
  Zeile). 83 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 06:58:33 +02:00
GuidoandClaude Opus 4.8 4520c7318f fix: Status-Prüfung beim Import ist sprachunabhängig
Die neue Statusprüfung ließ nur „COMPLETED" durch. Deutsche Bank-Exporte
schreiben aber „ABGESCHLOSSEN", wodurch keine einzige Zeile mehr buchbar war.

Statt einer Positivliste jetzt eine Sperrliste (storniert/ausstehend/abgelehnt/
fehlgeschlagen, jeweils deutsch und englisch); unbekannte Status werden gebucht,
damit keine echten Buchungen still verloren gehen.

Test mit echtem deutschem Revolut-Export inkl. reiner Gebührenzeile
(Betrag 0.00, Gebühr 10.99): Endsaldo trifft die Kontostand-Spalte. 81 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 06:41:32 +02:00
GuidoandClaude Opus 4.8 d725f5cf57 fix: Revolut-Import wertet Gebühr, Status und Währung aus
Das Revolut-Preset kannte die Spalten Gebühr/Währung/Status laut Kommentar,
der Parser hat sie aber nie gelesen. Folgen: Gebühren fehlten im Saldo,
stornierte (REVERTED) und schwebende Zeilen wurden mitgebucht, und Zeilen
fremder Währungs-Pockets landeten 1:1 im Konto – der Saldo lief dadurch zu hoch.

- Gebührenspalte mindert den Betrag (immer als Kosten)
- nur Zeilen mit Status COMPLETED (oder ohne Statusspalte) sind buchbar
- Zeilen in anderer Währung als das Zielkonto werden nicht gebucht
- Grund wird in der Prüfansicht als Badge angezeigt (storniert / Währung)
- Gebühr/Währung/Status in der Spaltenzuordnung wählbar; Revolut-Preset ergänzt
- Test mit echtem Revolut-Format (Gebühr, Storno, Fremdwährung), 80 grün

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 06:30:22 +02:00
GuidoandClaude Opus 4.8 01059b2bb2 fix: Buchungsliste zeigt alle Buchungen (Default-Limit 200 -> 5000)
Die Liste lud nur die 200 neuesten Buchungen, wodurch ältere Monate fehlten
(bei 254 importierten Zeilen reichte die Ansicht nur bis Juni zurück). Da die
Tabelle im Browser filtert und sortiert, werden nun standardmäßig alle Buchungen
geladen. Verifiziert am echten Auszug: 254 von 254, ab 05.01.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 06:02:22 +02:00
GuidoandClaude Opus 4.8 14b3e53685 fix: Duplikat-Erkennung als Multiset – echte gleich aussehende Buchungen bleiben
Bisher wurde eine zweite Buchung mit gleichem Tag/Betrag/Text (z. B. zwei
Gutschriften à 324 am selben Tag) fälschlich als Duplikat verworfen. Jetzt gilt
eine Zeile nur als Duplikat, wenn bereits GENAU SO VIELE identische Buchungen in
der DB stehen – ein erneuter Import derselben Datei bleibt weiterhin Duplikat-sicher.

- csv_import.detect_rows: existing/seen als Counter (Multiset) statt Set
- Tests angepasst + Re-Import-Test ergänzt; an echtem Raiffeisen-Auszug verifiziert
  (254 Zeilen, 0 fälschliche Duplikate, Saldo == Bank-Endsaldo)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 05:40:03 +02:00
GuidoandClaude Opus 4.8 338c21935b feat: Transfer-Erkennung symmetrisch (Eingänge) + Gegenbuchungs-Erkennung
- Enthält der Buchungstext den Erkennungs-Text (Art „Text") eines ANDEREN
  Kontos, wird auch ein Eingang als Transfer VON diesem Konto gesetzt – nicht
  nur die Ausgabe. Richtung ergibt sich aus dem Vorzeichen.
- Gegenbuchungs-Erkennung: existiert schon ein Transfer zwischen denselben
  Konten mit passendem Betrag (±5 Tage), wird die Gegenseite als „Gegenbuchung"
  markiert und nicht angehakt – so zählt dieselbe Bewegung beim Import beider
  Auszüge nicht doppelt (überschreibbar in der Prüfansicht).
- Tests: Eingang übers Konto-Tag, Gegenbuchung übersprungen (78 grün)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-20 20:52:31 +02:00
GuidoandClaude Opus 4.8 1f03483e5d fix: Konto umbenennen scheitert nicht mehr an Identifikator-UNIQUE
_set_identifiers glich die Liste per clear()+neu-Anlegen ab; SQLAlchemy fügte
dabei die (unveränderten) Identifikatoren vor dem Löschen ein und verletzte so
UNIQUE(account_id, kind, value_norm) -> Internal Server Error beim Speichern.
Jetzt als Diff: unveränderte behalten, nur fehlende hinzufügen / überzählige
entfernen. Betrifft alle Identifikator-Arten (IBAN/Nummer/Text).

Regressionstest: Umbenennen mit unverändertem Identifikator + Entfernen (76 grün).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-20 20:00:09 +02:00
GuidoandClaude Opus 4.8 c44892ff72 feat: Transfer-Schlagwort mit festem Zielkonto (z. B. REVOLUT -> Konto Revolut)
- Transfer-Regel kann direkt ein Zielkonto tragen (target_account_id); ohne ->
  weiterhin Auto-Erkennung aus dem Buchungstext (Konto-Texte)
- Migration 0010; Endpunkt aktualisiert bestehendes Wort statt Konflikt
- Import bevorzugt das feste Zielkonto, sonst _guess_target_account
- Konten-Seite: Transfer-Erkennung mit Zielkonto-Auswahl + Anzeige am Chip
- Test: festes Zielkonto greift ohne Konto-Texte (75 grün)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-20 19:06:24 +02:00
GuidoandClaude Opus 4.8 5f88660bcc feat: PDF-Beleg-Parser blockbasiert (mehrzeilig) + Seiten als Bild
- Neuer Parser erkennt mehrzeilige Auszüge (N26 & Co.): Beschreibung → Value
  Date → Buchungsdatum → Betrag stehen auf eigenen Zeilen; Betrags-Zeile schließt
  eine Buchung ab, Kopf-/Fußzeilen (Name/Adresse/eigene IBAN/Seitenzahl/
  Spaltenköpfe) werden gefiltert, Gegen-IBAN und Zweck werden übernommen
- Alter Ein-Zeilen-Heuristik-Parser bleibt als Fallback
- /documents/preview liefert zusätzlich die Seiten als PNG-Data-URLs
  (PyMuPDF get_pixmap, kein poppler nötig)
- Tests: N26-artige mehrzeilige Fixture + Seiten-Rendering (74 grün);
  am echten 34-Seiten-Auszug 207 Buchungen erkannt

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-20 17:14:12 +02:00
GuidoandClaude Opus 4.8 49b927e2eb feat: Budget – mehrere Monate nebeneinander + Kopieren Monat<->Vorlage
- Umschalter 1·2·3: Monate nebeneinander (responsives Raster, auf schmalem
  Schirm gestapelt); prev/next verschiebt das ganze Fenster
- Je Monat: „Als Standard festlegen" (Monat -> Vorlage) und „Standard
  übernehmen" (Vorlage -> Monat, überschreibt), plus Zurücksetzen
- Backend: copy_month_to_template / copy_template_to_month + Endpunkte
  POST /budget/template/from-month/{label} und /budget/{label}/from-template
- Test fürs Kopieren; globale .grow-Utility ergänzt

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-20 17:07:49 +02:00
GuidoandClaude Opus 4.8 8d0fbdfe2b feat: Dashboard – Donut ohne Überlauf, Achse lesbar, Karten-Optionen
- Budget-Donut: Mittelbetrag passt sich der Länge an (kein Hineinlaufen in den
  Ring bei 4-stelligen Beträgen)
- Vermögensverlauf: Y-Achse ohne Währungssymbol (formatAmount) + etwas mehr
  linker Rand → „CHF …" wird nicht mehr abgeschnitten
- Karten-Optionen: im Anordnen-Modus je Karte einstellbar; Budget-Donut und
  Budget-Fortschritt können nach Gruppen ODER Kategorien aufschlüsseln
  (?group_by=), Auswahl wird im Layout (props) mitgespeichert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-20 16:58:32 +02:00
GuidoandClaude Opus 4.8 d1cab19264 feat: gleiches Schlagwort für Einnahme UND Ausgabe erlaubt (Art je Regel)
- CategorizationRule bekommt `kind` (expense|income|transfer); Unique jetzt
  (owner, match_field, pattern, kind) statt ohne kind
- Konfliktprüfung/Anlage (add/update Keyword, Transfer-Keyword, gelernte Regeln)
  arbeiten kind-bewusst: dasselbe Wort je einmal pro Art möglich
- Migration 0009 mit Backfill (kind aus Kategorie-Art bzw. is_transfer)
- Tests angepasst: quer über Arten erlaubt, gleiche Art weiterhin Konflikt (71 grün)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-20 16:37:35 +02:00
ScarriffleandClaude Opus 5 64cbbc8cf6 feat: Kategorien mit Schlagwoertern sind in der Liste erkennbar
Der Schlagwort-Knopf sah bei jeder Kategorie gleich aus - man musste jede
einzeln oeffnen, um zu sehen, wo ueberhaupt etwas hinterlegt ist. Belegte
Kategorien tragen den Knopf jetzt in Akzentfarbe und mit der Anzahl daneben,
der Tooltip nennt sie ausgeschrieben.

Die Zahl kommt als keyword_count an der Kategorie mit, ermittelt in einer
Aggregat-Abfrage fuer die ganze Liste. Ein Abruf je Kategorie waere dafuer
unverhaeltnismaessig gewesen. Gefuellt wird das Feld nur beim Auflisten;
beim Anlegen und Aendern einer Gruppe bleibt es bei 0, wo es ohnehin nichts
aussagt.

Das Popup laedt beim Schliessen die Liste neu, damit die Zahl nach dem
Bearbeiten stimmt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:19:42 +02:00
ScarriffleandClaude Opus 5 cdf21474cf feat: Schlagwort per Doppelklick oben in der Zeile bearbeiten
Bisher oeffnete der Doppelklick ein Eingabefeld im Chip selbst. Dort war nur
Platz fuer das Wort - der Betrag liess sich gar nicht aendern, weil die
Inline-Bearbeitung bewusst nur { word } geschickt hat.

Jetzt laedt der Doppelklick Wort und Betrag in dieselbe Zeile, in der auch
neue Schlagwoerter entstehen. Der Knopf wechselt auf ein Haekchen, daneben
steht Abbrechen (auch per Escape), und der bearbeitete Chip ist markiert.
Gespeichert wird per PATCH mit ausdruecklichem amount_cents, damit ein
geleertes Betragsfeld den Betrag auch wirklich loescht; ein geparstes 0 wird
zu null, weil das Schema gt=0 verlangt. Ueber POST ginge es nicht: das
antwortet bei vorhandenem Wort mit geaendertem Betrag mit 409.

Tests decken jetzt ab, dass Betrag mitgeaendert wird, beim Weglassen stehen
bleibt und bei ausdruecklichem null verschwindet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 13:49:43 +02:00
ScarriffleandClaude Opus 5 e503baa8a6 fix: sprechende Dateinamen fuer alle Exporte
Im Downloads-Ordner lagen "certain-finance-export_....json" und
"certain-finance-backup_....json" nebeneinander, ohne dass man sah, welche
Datei sich zurueckladen laesst. Alle Downloads heissen jetzt
certain-finance_<art>_<datum>_<uhrzeit>.<endung>, wobei die Art es sagt:
vollbackup, vollbackup-system, datenuebersicht, buchungen, datenbank. Das
persoenliche Vollbackup traegt zusaetzlich den Benutzernamen.

Den Namen vergibt jetzt allein der Server im Content-Disposition-Kopf; das
Frontend liest ihn von dort. Vorher erfanden beide Seiten ihn getrennt und
kamen auf unterschiedliche Ergebnisse (Bindestrich gegen Unterstrich, UTC
gegen Ortszeit). Zeitstempel ist Ortszeit: wer abends um 20 Uhr exportiert,
soll keine Datei von 18 Uhr finden.

Passend dazu heisst der Knopf "Alle Daten (JSON)" nun "Datenuebersicht
(JSON)", und der Hinweis darueber sagt ausdruecklich, dass sich diese
Dateien nicht zurueckladen lassen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 13:13:39 +02:00
ScarriffleandClaude Opus 5 7fc8763d57 feat: 2FA, Passwortrichtlinie und wiederherstellbares JSON-Backup
Zwei-Faktor per TOTP mit zehn Einmal-Wiederherstellungscodes. Der
Zwischenzustand "Passwort stimmt, Code fehlt" haengt an der vorhandenen
Sitzungstabelle (pending_2fa, zehn Minuten Laufzeit) und gilt nirgends als
angemeldet; nach dem Code wird das Cookie getauscht statt weiterverwendet.
Der Wiederverwendungsschutz merkt sich das tatsaechlich getroffene
Zeitfenster, nicht die aktuelle Uhrzeit - sonst liesse sich ein Code aus dem
naechsten Fenster gleich darauf nochmal einloesen.

Passwortrichtlinie (Laenge, Zeichenklassen, MFA-Pflicht) in der neuen
globalen Tabelle app_settings, durchgesetzt beim Anlegen, Zuruecksetzen und
Aendern von Passwoertern. Bestehende Passwoerter lassen sich nicht gegen eine
neue Richtlinie pruefen - der Hash gibt nichts her. Beim Anmelden liegt das
Passwort aber kurz im Klartext vor: dort wird geprueft und bei Verstoss ein
Wechsel verlangt, mit passendem Grund fuer den Hinweistext.

Nebenbei geschlossen: must_change_password war bisher nur im Frontend
erzwungen, wer die API direkt ansprach kam vorbei. enforce_account_state
haengt jetzt am /api-Sammelrouter und laesst nur noch durch, was man zum
Erfuellen der Pflicht braucht.

Das JSON-Backup liess sich bisher grundsaetzlich nicht zurueckladen: keine
Version, Kategorien nur ueber mehrdeutige Namen, sieben Tabellen fehlten.
Daneben steht nun ein Format v2, das Beziehungen ueber lokale IDs abbildet,
je Benutzer oder systemweit. Zurueckgeladen wird nach einer Pruefansicht,
wahlweise ersetzend oder ergaenzend; vor dem Ersetzen legt der Server einen
Schnappschuss der Datenbank an. Der alte Ansichts-Export bleibt fuer
Tabellenkalkulationen bestehen und wird beim Hochladen ausdruecklich
abgewiesen, statt halb zu raten.

Frontend: wiederverwendbares Passwortfeld mit Augensymbol, animierte
Anforderungsliste, 2FA-Seite, Admin-Karte fuer die Richtlinie und Karte zum
Zurueckladen. Neue Bewegung ist in prefers-reduced-motion hinterlegt; Secret
und Codes sind markierbar, sonst verhinderte das globale user-select das
Kopieren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 10:11:22 +02:00
GuidoandClaude Opus 4.8 204a24d0df feat: Regeln (Betrag/Transfer/Konto-Texte) + Münz-Icon + pastellgrünes Theme
Regeln:
- Schlagwörter mit optionaler Betragsbedingung (z. B. „SV = 5.00 → Restaurant");
  Betrags-Regeln gewinnen vor betragslosen
- Transfer-Schlagwörter (Text → Transfer) auf der Konten-Seite
- Konto-Identifikator-Art „Text": erkennt das Zielkonto eines Transfers aus dem
  Buchungstext (längster passender Text gewinnt)
- Migration 0007, Endpunkte, Tests (Betrag/Transfer/Konflikt), gesamt 30 grün

Optik:
- App-Icon: Münzhaufen (aus Nutzer-SVG) weiß auf grünem Grund, klein gehalten
- Farbschema auf pastelliges Grün (Akzent hell/dunkel + Favicon)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 22:41:38 +02:00
GuidoandClaude Opus 4.8 37f64999cd feat: Schlagwörter je Kategorie + auf-/zuklappbares Kategorie-Dropdown
- Manuelle Schlagwörter je Kategorie (Popup mit Chips, Doppelklick zum Bearbeiten)
- Treffer über Empfänger ODER Verwendungszweck; längstes Schlagwort gewinnt;
  Kind-Filter (Ausgabe/Einnahme); wirken nur bei künftigen Importen
- Lernende Korrektur-Erkennung: ab 5 Anwendungen und >= 1/3 Korrekturen
  erscheint eine Prüf-Meldung in den Einstellungen (Behalten/Entfernen)
- Neue CategorySelect-Komponente (Baum + Suche, per Portal) ersetzt die
  nativen optgroup-Selects in Buchungen und Import-Prüfansicht
- Migration 0006 (source/applied_count/overridden_count/confirmed, match_field any)
- Endpunkte, i18n DE/EN, Tests (test_keywords: 7 neu, gesamt 27 grün)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 21:31:29 +02:00
GuidoandClaude Opus 4.8 f375fc5eaa feat: Backup/Export + Import-UX klarer + Raiffeisen-Preset-Fix
Backup & Export:
- routers/backup: /backup/db (Admin, konsistentes SQLite-Online-Backup),
  /export/transactions.csv|json und /export/all.json (owner-gescoped).
- Einstellungen: Bereich „Backup & Export" mit Download-Buttons. docs/BACKUP.md
  (Wiederherstellung + LXC-Sicherung). pytest für Export + Admin-Schutz.

Import-Verbesserungen (Nutzer-Feedback zum Ablauf):
- Klarer Hinweis, dass die Zuordnungstabelle nur Beispielzeilen zeigt und
  „Vorschau erstellen" die vollständige Prüfliste + Buchen-Button erzeugt.
- Zusammenfassung (erkannt/gebucht) und Warnung, wenn keine gültige Zeile erkannt
  wurde (Hinweis auf Format/Bank-Vorlage).
- Fix: Raiffeisen-Preset hat has_header=true (der Export hat eine Kopfzeile).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 20:35:02 +02:00
GuidoandClaude Opus 4.8 7461fddb7a feat: Phase 5 – PDF/Bild-Beleg-Import (lokal, Tesseract-OCR)
Backend:
- services/ocr: PDF-Text via PyMuPDF, OCR (Tesseract) für Scans/Bilder,
  heuristischer Parser (Datum + Betrag je Zeile, CH/DE/US-Beträge).
- routers/documents: /documents/preview (Upload -> Text -> Kandidaten ->
  gemeinsame Prüf-/Buchungslogik des CSV-Imports). csv_import refaktoriert:
  detect_rows() von CSV und Beleg gemeinsam genutzt.
- requirements: PyMuPDF, pytesseract, pillow; install.sh installiert tesseract-ocr
  (+ deu). pytest: Heuristik-Parser und PDF-Vorschau.

Frontend:
- Import-Seite: CSV/PDF-Umschalter; PDF-Modus mit Beleg-Auslesen, extrahiertem
  Text zur Kontrolle und Best-Effort-Hinweis; gleiche Prüfansicht + Buchen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 20:25:32 +02:00
GuidoandClaude Opus 4.8 ddf4f1f5ed feat: vorgefertigte Import-Presets für Raiffeisen (CH) und Revolut
- services/csv_import: PRESETS-Liste (aus echten Beispiel-Exporten des Nutzers),
  GET /imports/presets.
- Frontend: Preset-Auswahl im Import füllt Format/Spalten automatisch.
Beide Presets per TestClient gegen echte Zeilenformate verifiziert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 20:14:39 +02:00
GuidoandClaude Opus 4.8 1381967aae feat: Phase 4 – CSV-Import (Templates, Prüfansicht, Duplikat/Transfer-Erkennung, Undo)
Backend:
- Modelle + Migration 0005: import_templates, import_batches, categorization_rules;
  transactions um import_batch_id + import_hash erweitert.
- services/csv_import: Datei parsen (Trennzeichen, Dezimal-/Datumsformat, Kodierung,
  Header/Skip, Vorzeichen vs. Soll/Haben), Vorschau mit Duplikat-Erkennung (Hash),
  interner-Transfer-Erkennung via IBAN, lernender Auto-Kategorisierung; Buchen mit
  Batch und Undo.
- routers/imports: Templates (CRUD), Roh-Vorschau, Prüf-Vorschau, Buchen, Verlauf,
  Rückgängig. pytest: Parsing, Duplikate, Transfer, Buchen, Undo, Lernen.

Frontend:
- Import-Seite: Konto+Datei, Format/Spaltenzuordnung (mit Roh-Vorschau),
  editierbare Prüfansicht (Kategorie/Typ/Zielkonto, Duplikate markiert),
  Buchen, Import-Verlauf mit Undo; Vorlagen speichern; i18n DE/EN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 20:06:50 +02:00
GuidoandClaude Opus 4.8 2dd8f9cd35 fix: systemd ohne ProtectHome (venv unter /root sichtbar) + Phase-3-Backend
Deployment-Fixes (Dienst startete nicht -> 203/EXEC "uvicorn not found"):
- Unit ohne ProtectHome/ProtectSystem: Repo/venv dürfen unter /root oder /home
  liegen (ProtectHome=true blendete diese Pfade für den Dienst aus).
- install.sh: Python-3.11+-Prüfung mit klarer Meldung; prüft nach der Installation,
  dass uvicorn wirklich in der venv liegt.
- EZB-Kursabruf läuft beim Start jetzt im Hintergrund (blockiert den Start nie).

Phase-3-Backend (Dashboard):
- Modell + Migration 0004: dashboard_layouts (JSON-Layout je Nutzer).
- routers/dashboard: Layout speichern/laden/zurücksetzen + Kartendaten
  (Budget-Donut, Budget-Fortschritt, Einnahmen/Ausgaben, Kontostände,
  Vermögensverlauf, letzte Buchungen).
- Frontend-Infrastruktur: Chart-Komponenten (Donut/Line), palette, API-Methoden,
  react-grid-layout. (Dashboard-Seite folgt im nächsten Commit.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 19:31:55 +02:00
GuidoandClaude Opus 4.8 8ed4bf5254 feat: Phase 2 – Budget-Engine + Direktzugriff (http://IP:Port)
Budget:
- Modelle + Migration 0003: budget_template_lines, planned_income_lines,
  budget_periods, budget_period_lines.
- services/budget: Perioden-Grenzen (einstellbarer Starttag, Label nach Endmonat),
  Standard-Vorlage vs. Monats-Override (Snapshot-on-Customize), Soll/Ist inkl.
  budgetierter Sparen-Transfers, geplante Einnahmen (Soll/Ist).
- routers/budgets: Vorlage, geplante Einnahmen (CRUD), Monats-Soll/Ist,
  Monats-Override, Zurücksetzen. pytest: Perioden, Soll/Ist, Override/Reset, Einnahmen.
- Frontend: Budget-Seite (Monat + Standard-Budget), Fortschrittsbalken grün/rot,
  Monats-Navigation, editierbare Soll-Beträge; Sparen-Option im Transfer-Formular.

Deployment-Fix (Nutzer-Anforderung: http://IP:Port muss funktionieren):
- Dienst bindet an APP_BIND_HOST=0.0.0.0 (direkt im LAN erreichbar).
- APP_COOKIE_SECURE=false als Standard (sonst kein Login über reines HTTP);
  für reinen HTTPS-Betrieb umstellbar. Doku + install.sh entsprechend angepasst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 19:16:27 +02:00
GuidoandClaude Opus 4.8 1b7107b9bd feat: Phase 1 – Konten, Kategorien, Transaktionen, EZB-Währungsumrechnung
Backend:
- Modelle + Migration 0002: accounts, account_identifiers, category_groups,
  categories, transactions, exchange_rates (alle owner-gescoped).
- EZB-Kursdienst (90-Tage/Historie) + Backfill-Skript; Umrechnung EUR<->CHF zum
  Buchungsdatum mit lokalem Cache und manueller Übersteuerung; EUR-Pivot
  (amount_eur_cents) eingefroren.
- Kontostände + Netto-Vermögen (Umrechnung in Hauptwährung).
- Router: accounts (inkl. IBAN-Identifikatoren), categories (Gruppen+Kategorien),
  transactions (Ausgabe/Einnahme/Transfer, cross-currency), rates.
- Gruppierte Standard-Kategorien je Nutzer beim Anlegen.
- pytest-Suite: Währung, Salden, Cross-Currency-Transfer, Nutzer-Isolation.

Frontend:
- Seiten Konten, Kategorien, Buchungen; Icon-Chip, Geldbetrag mit Umrechnung,
  Icon-/Farbwähler, Modal; erweitertes SVG-Icon-Set (Konto-/Kategorie-Icons).
- Währungs-/Datumsformatierung (EUR de-DE, CHF de-CH), i18n DE/EN erweitert.
- Live-Umrechnungs-Vorschau im Transfer-Formular.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 18:59:24 +02:00
GuidoandClaude Opus 4.8 2c795a57c8 feat: Phase 0 – Gerüst (FastAPI+SQLite, Multi-User-Login, React-App-Shell, Deploy)
Grundgerüst für Certain Finance:
- Backend: FastAPI + SQLAlchemy + SQLite, Alembic-Migrationen (Batch-Mode),
  argon2-Passwort-Hashing, server-seitige Sitzungen im Cookie, Erst-Admin,
  Benutzerverwaltung mit Temporär-Passwort, Einstellungen je Benutzer.
- Ein Prozess, ein Port: uvicorn liefert API (/api) und gebautes SPA aus,
  inkl. SPA-Deeplink-Fallback.
- Frontend: React 18 + Vite + JSX, Design-Tokens (Light/Dark), i18n DE/EN,
  Inline-SVG-Icons (keine Emojis), Login, Passwort-ändern, Dashboard-Platzhalter,
  Benutzer- und Einstellungsseiten.
- Deploy: nativer LXC-Installer + Update-Skript (systemd), env-Vorlage, Doku.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 18:27:42 +02:00