Files
bin/docs/superpowers/specs/2026-09-30-ausbaustufe-14-design.md
T

189 lines
10 KiB
Markdown
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.
# 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 aus
> `CLAUDE.md` gilt: 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 Spalte `start_date DATE NULL` („Gültig ab").
Die vorhandene Spalte `end_date` bedeutet künftig bei **allen** Arten
„Gültig bis" (inklusiv); bei `ende` bleibt sie das Enddatum des Postens.
Bestandsdaten: bisherige `percent`/`absolute`/`remove`-Zeilen haben
`end_date = NULL` (API hat es bisher genullt) → gelten unverändert
unbegrenzt. **Keine Datenmigration nötig.**
- Neue Art `set` (GUI-Label „Neuer Betrag"): `value` ist der neue Betrag
**mit Vorzeichen** (wie Fixposten-Beträge, z. B. `-137.50`). Nur mit
`target_type = "recurring"` zulässig.
- Neue Tabelle `scenario_recurring_items`: `id`, `scenario_id` (FK
`scenarios.id`, index), `name String(200)`, `amount MONEY`,
`rhythm String(20)`, `due_day Integer`, `start_date Date NULL`,
`end_date Date NULL`, `category_id` (FK `categories.id`, NULL). Modell
`ScenarioRecurringItem` in `models/tables.py`.
- `scenario_planned_items`: neue Spalte `category_id` (FK `categories.id`,
NULL).
- **Downgrade:** löscht `set`-Zeilen, setzt `end_date = NULL` für alle
Nicht-`ende`-Zeilen (alte Semantik), droppt `start_date`,
`scenario_planned_items.category_id` und `scenario_recurring_items`.
## Teil 2 — Rechenlogik (`engine/scenario.py`, `engine/breakdown.py`)
- `PlainModifier` bekommt `start_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 Art `ende` wird er hier wie
bisher übersprungen — Ende wirkt nur über `effective_end`). Reihenfolge
unverändert = Anlage-Reihenfolge (ID). `set` → `amount = value`; ein
späterer `percent`/`absolute` rechnet auf dem neuen Betrag weiter.
`remove` im Zeitraum → `None` (Fälligkeit entfällt), außerhalb → normal.
- **Projektion** (`build_cashflows`): Betrag **je Fälligkeit** mit
`on = Fälligkeitsdatum` bestimmen (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 wie `recurrence.occurrences`); Einmalposten: `on = due`.
Monatsausschluss über `start_date`/`effective_end` bleibt 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_inputs` als `PlainRecurring` mit **`id = None`** geladen
(Typ `id: int | None`), damit Posten-Einträge (`target_type="recurring"`)
sie nie treffen — die IDs zweier Tabellen könnten kollidieren.
Kategorie-Einträge treffen sie über `category_id`. Sie zählen **immer**,
unabhängig von `include_recurring` (Regel wie Szenario-Einmalzahlungen).
Für „System-Plan" (`scenario=None`) gibt es sie nicht.
- Szenario-Einmalzahlungen werden mit ihrer `category_id` geladen (bisher
fest `None`).
- `BreakdownRow.recurring_id` bleibt für szenario-eigene Zahlungen `None` →
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"]`, neu `start_date: date | None = None`. Validator (422, deutsch):
- `ende` ohne `end_date` → „Art »Ende« erfordert ein Datum" (wie bisher);
bei `ende` wird `start_date` genullt.
- `set` mit `target_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_date` wird **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). Schema `ScenarioRecurringIn` mit den Feldern und
Validierungen von `RecurringIn` (Rhythmus-Literal, `due_day` 1–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`: neu `category_id: int | None = None` mit
Kategorie-Existenzprüfung.
- `delete_scenario` löscht zusätzlich `ScenarioRecurringItem`-Zeilen.
## Teil 4 — GUI (`templates/szenarien.html`, `routers/gui.py`, `formats.py`)
- `de_label`: `absolute → "Kürzung (€)"`, neu `set → "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 „–";
bei `ende` das 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.
- **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` (Muster
`data-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
1. **Nav-Überlauf bei 390 px:** `nav` darf umbrechen (`flex-wrap: wrap`,
Abstände), kein horizontales Scrollen der Seite.
2. **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.
3. **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 Helfer
`schedule_for(loan)` führen — verhaltensneutral.
4. **`.legende-zusatz` ohne CSS:** eigene Regel (normales Gewicht,
gedämpfte Farbe), damit der Zusatz bei gewählter Legendenzeile nicht mit
fett wird.
5. **`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), `set` dann
`percent`, `remove` mit Zeitraum, szenario-eigene Zahlung von
Kategorie-Eintrag getroffen / von Posten-Eintrag mit gleicher ID nicht,
zählt bei `include_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 `/recurring` mit 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.md` falls betroffen, fb-Repo (finanz-api- und
finanzberatung-Skill).
- **Ablauf:** Plan `docs/superpowers/plans/2026-09-30-ausbaustufe-14.md`,
direkt auf `main`; jeder Task mit Fable-Testagent-Gate und
Ledger-Eintrag; Release **v0.14.0** (VERSION, Backup, Redeploy,
Live-Smoke, Fable-Release-Gate).