Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
10 KiB
Design — Ausbaustufe 14: Szenario-Einträge mit Zeitraum, „Neuer Betrag", szenario-eigene wiederkehrende Zahlungen (v0.14.0)
Status: vom Nutzer im Chat freigegeben (2026-09-30), Abschnitte 1–3. Umsetzungsplan folgt unter
docs/superpowers/plans/. Datenschutz-Regel ausCLAUDE.mdgilt: konkrete Posten-Namen/Beträge aus der Live-Datenbank stehen nur im Chat, nicht in diesem Dokument, nicht in Tests/Commits/Ledger.
Anlass
Der Nutzer wollte in einem Szenario einen bestehenden monatlichen Fixposten ab einem Stichtag auf 50 % senken. Das ging nicht: Szenario-Einträge (Prozent/Absolut/Entfällt) wirken über die gesamte Laufzeit, „Absolut" ist eine Kürzung (nicht ein neuer Betrag), und ein Szenario kann keine eigenen wiederkehrenden Zahlungen haben — nur Einmalzahlungen. Die Lücke ist eine bewusste Auslassung aus A5 („Ende" als eigene Art statt Gültigkeitszeitraum aller Modifikatoren).
Nutzerentscheidungen (Chat 2026-09-30)
- A — Zeitraum: Prozent, Kürzung (€), Neuer Betrag und Entfällt bekommen optionale Grenzen Gültig ab und Gültig bis (beide leer = unbegrenzt). „Ende" bleibt wie bisher.
- Neue Eintragsart „Neuer Betrag" (ersetzt den Betrag eines Postens). „Absolut" heißt in der GUI künftig „Kürzung (€)".
- B — szenario-eigene wiederkehrende Zahlungen mit denselben Feldern wie Fixposten inkl. optionaler Kategorie; Kategorie-Einträge treffen sie.
- Formular geteilt in „Neue Änderung an Bestehendem" und „Neue Zahlung nur in diesem Szenario" (Szenario-Einmalzahlungen bekommen dabei ebenfalls eine optionale Kategorie).
- A14-Kandidaten aus dem Ledger werden mitgenommen, außer „figcaption nicht fett" — die Bildunterschrift der Torte bleibt fett (ausdrücklicher Nutzerwunsch).
- Das Live-Szenario des Nutzers wird nicht vom Tool-Team angepasst; der Nutzer probiert die Neuerung selbst daran aus.
Teil 1 — Datenmodell (eine Alembic-Revision, down_revision = 'd5e8b3c7a410')
scenario_modifiers: neue Spaltestart_date DATE NULL(„Gültig ab"). Die vorhandene Spalteend_datebedeutet künftig bei allen Arten „Gültig bis" (inklusiv); beiendebleibt sie das Enddatum des Postens. Bestandsdaten: bisherigepercent/absolute/remove-Zeilen habenend_date = NULL(API hat es bisher genullt) → gelten unverändert unbegrenzt. Keine Datenmigration nötig.- Neue Art
set(GUI-Label „Neuer Betrag"):valueist der neue Betrag mit Vorzeichen (wie Fixposten-Beträge, z. B.-137.50). Nur mittarget_type = "recurring"zulässig. - Neue Tabelle
scenario_recurring_items:id,scenario_id(FKscenarios.id, index),name String(200),amount MONEY,rhythm String(20),due_day Integer,start_date Date NULL,end_date Date NULL,category_id(FKcategories.id, NULL). ModellScenarioRecurringIteminmodels/tables.py. scenario_planned_items: neue Spaltecategory_id(FKcategories.id, NULL).- Downgrade: löscht
set-Zeilen, setztend_date = NULLfür alle Nicht-ende-Zeilen (alte Semantik), dropptstart_date,scenario_planned_items.category_idundscenario_recurring_items.
Teil 2 — Rechenlogik (engine/scenario.py, engine/breakdown.py)
PlainModifierbekommtstart_date: date | None = None.modified_amount(amount, category_id, recurring_id, modifiers, on: date): ein Modifikator wirkt nur, wenn(start_date is None or on >= start_date) and (end_date is None or on <= end_date)(bei Artendewird er hier wie bisher übersprungen — Ende wirkt nur übereffective_end). Reihenfolge unverändert = Anlage-Reihenfolge (ID).set→amount = value; ein spätererpercent/absoluterechnet auf dem neuen Betrag weiter.removeim Zeitraum →None(Fälligkeit entfällt), außerhalb → normal.- Projektion (
build_cashflows): Betrag je Fälligkeit miton = Fälligkeitsdatumbestimmen (bisher einmal je Posten). Einmalposten:on = due. - Aufteilung (
breakdown_rows): Stichtag je Fixposten = sein Fälligkeitstag im gewählten Monat, auf die Monatslänge geklemmt (gleiche Klemmregel wierecurrence.occurrences); Einmalposten:on = due. Monatsausschluss überstart_date/effective_endbleibt wie bisher. Beispiel: Posten fällig am 14., Eintrag „50 % ab 16.10." → Oktober voll, ab November halbiert. - Szenario-eigene wiederkehrende Zahlungen werden in
load_scenario_inputsalsPlainRecurringmitid = Nonegeladen (Typid: int | None), damit Posten-Einträge (target_type="recurring") sie nie treffen — die IDs zweier Tabellen könnten kollidieren. Kategorie-Einträge treffen sie übercategory_id. Sie zählen immer, unabhängig voninclude_recurring(Regel wie Szenario-Einmalzahlungen). Für „System-Plan" (scenario=None) gibt es sie nicht. - Szenario-Einmalzahlungen werden mit ihrer
category_idgeladen (bisher festNone). BreakdownRow.recurring_idbleibt für szenario-eigene ZahlungenNone→ der Doppelzählungs-Hinweis (Kredit vs. Fixposten) berücksichtigt sie nicht.- Unberührt: Grafana „Planung nach Kategorie", „System-Plan", v_projection.
Teil 3 — API (routers/scenarios.py)
ModifierIn:kind: Literal["percent", "absolute", "set", "remove", "ende"], neustart_date: date | None = None. Validator (422, deutsch):endeohneend_date→ „Art »Ende« erfordert ein Datum" (wie bisher); beiendewirdstart_dategenullt.setmittarget_type != "recurring"→ „Art »Neuer Betrag« ist nur für Posten möglich".start_date > end_date→ „»Gültig ab« liegt nach »Gültig bis«".end_datewird nicht mehr für Nicht-ende-Arten genullt. POST/PATCH bleiben Full-Body.
- Neuer Endpunkt
/api/scenarios/{id}/recurring:GET(Liste, sortiert nach Name),POST(201),PATCH /{item_id}(Full-Body),DELETE /{item_id}(204). SchemaScenarioRecurringInmit den Feldern und Validierungen vonRecurringIn(Rhythmus-Literal,due_day1–31, Ende nicht vor Start); Kategorie-Existenz via_check_category-Muster (404 „Kategorie nicht gefunden"); fremde/fehlende IDs → 404 „Wiederkehrende Zahlung nicht gefunden"; fehlendes Szenario → 404. Keine Doppelzählungs-Prüfung. ScenarioPlannedIn: neucategory_id: int | None = Nonemit Kategorie-Existenzprüfung.delete_scenariolöscht zusätzlichScenarioRecurringItem-Zeilen.
Teil 4 — GUI (templates/szenarien.html, routers/gui.py, formats.py)
de_label:absolute → "Kürzung (€)", neuset → "Neuer Betrag".modifier_kinds=percent, absolute, set, remove, ende.- Tabelle „Einträge", Spalten: Was | Art | Wert/Betrag | Zeitraum | (Aktionen).
- Modifikator-Zeilen: Wert bei
percent/absolute/set; Zeitraum „ab TT.MM.JJJJ", „bis TT.MM.JJJJ", „TT.MM.JJJJ – TT.MM.JJJJ" oder „–"; beiendedas Enddatum. - Neue Zeilen für szenario-eigene wiederkehrende Zahlungen: Name (+ Kategorie), Art „Wiederkehrend (monatlich, am 14.)", Betrag, Zeitraum Start–Ende; Inline-Bearbeiten/Löschen (Muster Einmalzahlung).
- Einmalzahlungs-Zeilen zeigen ggf. die Kategorie; Edit-Zeile bekommt das Kategorie-Feld.
- Modifikator-Zeilen: Wert bei
- Formular „Neue Änderung an Bestehendem" (POST
/modifiers): Art (Prozent | Kürzung (€) | Neuer Betrag | Entfällt | Ende), Ziel-Typ, Kategorie/Posten, Wert, Gültig ab, Gültig bis.- Wert-Label dynamisch: „Prozentsatz (%)" / „Kürzung (€)" / „Neuer Betrag (€)"; bei Entfällt/Ende gesperrt.
- Neuer Betrag: Ziel-Typ fest auf Posten (Option Kategorie gesperrt).
- Ende: „Gültig ab" gesperrt, „Gültig bis" heißt „Endet am".
- Formular „Neue Zahlung nur in diesem Szenario": Art (Einmalzahlung |
Wiederkehrend), Name, Betrag, Kategorie (optional, „(keine)"), Fällig am
(nur Einmal), Rhythmus + Fälligkeitstag + Start + Ende (nur
Wiederkehrend). POST-Ziel wechselt
/planned↔/recurring(Musterdata-planned-url). - Modifikator-Edit-Zeilen bekommen „Gültig ab"/„Gültig bis" und dieselbe Umschaltlogik.
- UX-Regel bleibt: nicht passende Felder sichtbar +
disabled+ Tooltip, Fehler inline über den bestehenden Response-Error-Handler. hilfe.html: Szenario-Absatz um Zeitraum, Neuer Betrag, Kürzung (€) und wiederkehrende Zahlungen ergänzen, mit neutralem Beispiel („ein Posten wird ab 16.10. halbiert").
Teil 5 — A14-Kandidaten aus dem Ledger
- Nav-Überlauf bei 390 px:
navdarf umbrechen (flex-wrap: wrap, Abstände), kein horizontales Scrollen der Seite. - Umriss des gewählten Tortensegments zuletzt zeichnen: gewähltes Segment wird im SVG als letztes Element ausgegeben, damit sein Umriss nicht von Nachbarn überdeckt wird.
- Tilgungsplan-Helfer: die 4 Aufrufe
loan_schedule(Decimal(l.principal), Decimal(l.annual_rate_pct), l.term_months, l.payout_date, l.repayment_type)(planning-Router, services/breakdown, projection_service, planning_chart) über einen Helferschedule_for(loan)führen — verhaltensneutral. .legende-zusatzohne CSS: eigene Regel (normales Gewicht, gedämpfte Farbe), damit der Zusatz bei gewählter Legendenzeile nicht mit fett wird.term_months-Validierung 1..1200 für Kredite (Anlegen + PATCH), 422 inline.
Ausdrücklich nicht: figcaption der Torte entfetten.
Teil 6 — Tests, Doku, Release
- Engine: Grenzen inklusiv (Tag vor/an/nach ab bzw. bis),
setdannpercent,removemit Zeitraum, szenario-eigene Zahlung von Kategorie-Eintrag getroffen / von Posten-Eintrag mit gleicher ID nicht, zählt beiinclude_recurring = false; Aufteilungs-Stichtag (Okt voll / Nov halbiert, Klemmung am Monatsende) — nur erfundene Beträge. - API: alle 422/404-Fälle aus Teil 3, CRUD
/recurring, Szenario-Löschen räumt/recurringmit ab, Planned mit Kategorie,term_months-Grenzen. - Migration: Upgrade/Downgrade in Wegwerf-Postgres; Bestandseinträge bleiben gleich.
- GUI/JS: Umschaltmatrix beider Formulare und der Edit-Zeilen (Stil der bestehenden Template-/JS-Tests), Nav-CSS, Torten-Reihenfolge.
- Suite bleibt grün (Basis: 377 passed / 3 skipped).
- Doku:
docs/ARCHITEKTUR.md(neue Tabelle, Endpunkt, Stichtag-Regel),CLAUDE.mdfalls betroffen, fb-Repo (finanz-api- und finanzberatung-Skill). - Ablauf: Plan
docs/superpowers/plans/2026-09-30-ausbaustufe-14.md, direkt aufmain; jeder Task mit Fable-Testagent-Gate und Ledger-Eintrag; Release v0.14.0 (VERSION, Backup, Redeploy, Live-Smoke, Fable-Release-Gate).