Modul-Review 3: Struktur, Dashboard, CSV-Export und Grafiken
Deploy App / deploy (push) Successful in 1m59s

Versionierung:
- startet neu bei 0.1; erst eine gesetzte Hauptversion macht daraus 1.0
- Szenario-Liste zeigt die echte Version (z.B. 0.17) statt "1.x", plus
  neue Spalte "Phasen"
- Migration 20260724120000_version_zero_start (nur der Default)

Plan-Dashboard:
- Kacheln anklickbar (fuehren in ihren Bereich), neue Kachel "Berichte"
- Plan umbenennen ueber Stift-Symbol
- Ist-Abweichung nennt das Jahr des juengsten Ist-Datensatzes und ist bei
  positiver Abweichung gruen statt rot

Szenario-Liste:
- Klick auf die Zeile oeffnet die Matrix (Matrix-Knopf entfaellt)
- je Zeile Kopie (Vorlage frei waehlbar) und Loeschen
- laedt nach einer Loeschung neu (zeigte vorher den alten Stand)

Seitenleiste:
- Szenarien wieder verschachtelt nach Herkunft
- Effektive Werte / Analysen / Berichte buendig zum Knoten "Szenarien"

CSV-Export (neues Modul lib/csv.ts):
- vier Bloecke: Kopf, Lebensphasen, ganze Matrix (Elemente x Phasen inkl.
  Uebergangs-Entscheide im Klartext), Jahreswerte
- mit BOM (Excel-Umlaute), Dateiname transliteriert Umlaute
- vorher enthielt die Datei kein einziges finanzielles Element

Grafiken:
- Szenario-Waehler gilt fuer alle drei Grafiken
- BUGFIX Szenario-Vergleich: WealthChart nutzte den Namen als Datenschluessel
  -> gleichnamige Szenarien ueberschrieben sich (Legende zeigte beide, Chart
  nur eine). Neu die ID; stille Deckelung auf 4 Serien entfaellt
- eigene Legende mit freier Farbwahl je Serie + Erklaerung des Linienstils
- Vermoegensaufteilung neu: gestapelte Flaeche ueber die Planjahre + Ring
  fuer die relative Aufteilung zu einem waehlbaren Zeitpunkt
- alle Diagrammfarben aus neuen Theme-Tokens (--chart-1..6, --chart-grid)

Doku-Drift bereinigt: Kapitel 2.1, 3.2.2-3.2.7 und 3.10 beschrieben noch den
Stand vor V7 (Grundprofil am Szenario, parentPlanId, Scenario.startYear,
window.confirm, drei Sidebar-Unterpunkte).

Nebenbei: verstuemmelte Hex-Farbe --danger-soft (warm) repariert, deutsche
Plural-/Umlautfehler in den Uebersichts-Kacheln.

SPEZIFIKATION 0.31. 267 -> 275 Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 12:16:07 +02:00
parent 96fdd00d5c
commit b42f9b385f
22 changed files with 1199 additions and 289 deletions
+134 -67
View File
@@ -4,10 +4,10 @@
| | |
|---|---|
| **Dokument** | Funktionale und Technische Spezifikation FPT |
| **Version** | 0.30 |
| **Version** | 0.31 |
| **Datum** | 2026-07-24 |
| **Status** | Lebendes Dokument |
| **Codestand** | Arbeitsstand nach `efcc04c` inkl. Tour-Korrekturen (Branch `main`) |
| **Codestand** | Arbeitsstand nach `96fdd00` inkl. Modul-Review 3 (Struktur & Grafiken) (Branch `main`) |
| **Ersetzt** | `FDD_TDD_FPT.docx` (v1v5) im Ordner `Info Dateien` diese sind ab Version 0.1 dieses Dokuments obsolet |
| **Geltungsbereich** | Gesamter Code im Verzeichnis `FPT` |
@@ -17,6 +17,7 @@
| Version | Datum | Autor | Änderung |
|---|---|---|---|
| 0.31 | 2026-07-25 | Claude (Opus 5) | **Modul-Review 3 (Plan-/Szenario-Struktur, Dashboard, Grafiken).** (1) **Versionierung startet bei 0.1** statt 1.0 (Kap. 3.8): Ein Szenario läuft in der 0er-Reihe (0.1, 0.2, … 0.137), bis eine **Hauptversion** gesetzt wird erst dann entsteht 1.0. Vorher begann jedes Szenario bereits bei 1.0, wodurch die Hauptversion ihre Bedeutung verlor. Die Szenario-Liste zeigt neu die **echte** Version statt «1.x», dazu eine Spalte **Phasen**. (2) **Plan-Dashboard:** Kacheln sind **anklickbar** und führen in ihren Bereich, neu inkl. **Berichte**; der Plan lässt sich über ein Stift-Symbol **umbenennen**; die Ist-Abweichung nennt das **Jahr** des jüngsten Ist-Datensatzes und ist bei einer positiven Abweichung **grün** statt rot. (3) **Szenario-Liste:** Ein Klick auf die **Zeile** öffnet die Matrix (der «Matrix»-Knopf entfällt), dazu je Zeile **Kopie** und **Löschen**; die Kopiervorlage ist damit frei wählbar und nicht mehr auf das Basisszenario festgelegt. Nach einer Löschung lädt die Liste neu (zeigte vorher den alten Stand). (4) **Seitenleiste:** Szenarien werden wieder **verschachtelt** dargestellt (Tiefe = Herkunftskette); «Effektive Werte», «Analysen» und «Berichte» stehen neu **bündig zum Knoten «Szenarien»** statt auf Höhe der einzelnen Szenarien. (5) **CSV-Export vollständig neu** (Kap. 3.6.5, neues Modul `csv.ts`): vier Blöcke Kopf, Lebensphasen, **die ganze Matrix** (Elemente × Phasen mit Beginn/Ende und den Übergangs-Entscheiden im Klartext) und **Jahreswerte**; mit BOM, damit Excel die Umlaute erkennt. Vorher enthielt die Datei kein einziges finanzielles Element. (6) **Grafiken:** Ein **Szenario-Wähler** gilt neu für **alle drei** Grafiken (vorher nur der Vermögensverlauf, und der nur additiv). Der **Vergleichs-Fehler** ist behoben: `WealthChart` benutzte den Szenario-**Namen** als Datenschlüssel, wodurch sich gleichnamige Szenarien gegenseitig überschrieben (Legende zeigte beide, der Chart nur eine) neu die **ID**; die stille Deckelung auf vier Serien entfällt. Die **Legende** ist eigenständig, erlaubt eine **freie Farbwahl je Serie** und erklärt den Linienstil (gestrichelt = Plan, durchgezogen = effektiv). Die **Vermögensaufteilung** ist neu eine **gestapelte Fläche über alle Planjahre** plus ein **Ring** für die relative Aufteilung zu einem wählbaren Zeitpunkt (vorher gestapelte Balken je Phase mit schräger Beschriftung). **Alle Diagrammfarben** kommen aus neuen Theme-Tokens (`--chart-1``--chart-6`, `--chart-grid`) statt fester Hex-Werte. (7) **Dokumentation nachgezogen:** Die Kapitel 2.1, 3.2.23.2.7 und 3.10 beschrieben noch den Stand **vor V7** (Grundprofil am Szenario, `parentPlanId`, `Scenario.startYear`, `window.confirm`, drei Sidebar-Unterpunkte). (8) Nebenbei: verstümmelte Hex-Farbe `--danger-soft` im Warm-Schema repariert, deutsche Plural-/Umlautfehler in den Übersichts-Kacheln, Dateiname des CSV-Exports transliteriert Umlaute statt sie zu `_` zu machen. 8 Tests ergänzt (267 → 275). |
| 0.30 | 2026-07-24 | Claude (Opus 4.8) | **Tour-Korrekturen und 3a-Verschiebung.** (1) Der Tour-**Spotlight** wird neu aus **vier fixed-Flächen** um die Bounding-Box des Ziels gezeichnet (Kap. 9.24) statt aus einem `box-shadow`-Trick. Grund: Der Schatten liess sticky Matrix-Köpfe (hoher z-index) hell durchscheinen und wurde im Matrix-Scrollbereich abgeschnitten (dann blieb fast alles hell). Die vier Flächen funktionieren unabhängig von z-index und overflow und folgen dem Ziel per `requestAnimationFrame`. (2) **Tour-Schritte** überarbeitet: neu erklärt sind **Zeitachse** und **Endvermögen**; der vormals «Analysen»-Schritt beschreibt jetzt korrekt die **obere Funktions-Leiste** (die Analyse-Werkzeuge sind dort nicht mehr), und ein neuer Schritt zeigt das **linke Menü** (Analysen, Berichte, Effektive Werte). Unsichtbare Ziele (z. B. das Menü auf schmalen Screens) werden übersprungen. (3) Der Schalter **«Selbstständig grosse Säule 3a»** wandert im Assistenten von Schritt 4 zu **Schritt 5**, weil er die 3a-Einzahlung (also die Sparraten-Verteilung) betrifft. Kein Eingriff in den Rechenkern; 267 Tests unverändert grün. |
| 0.29 | 2026-07-24 | Claude (Opus 4.8) | **Modul-Review 2, Feinschliff (Assistent, Tour, Ansicht).** (1) **Assistent:** neuer **Willkommens-Screen** vor Schritt 1 mit dem Gesamtbild der fünf Schritte, danach eine **persistente Schritt-Leiste** (links im breiten Modal, mobil als Fortschrittsbalken) der aktuelle Schritt hervorgehoben, erledigte mit Haken, kommende gedämpft. Schritt «Vorsorge & Vermögen» fragt neu auch die **erwartete Rendite** bei PK, 3a und Wertschriften ab (vorher fest verdrahtet). Im Schritt «Sparen & Verteilen» rechnet die Sparquote-Vorschau **Hypothekarzinsen**, die als «noch nicht in den Ausgaben» markiert sind, korrekt zu den Ausgaben dazu (wie der Rechenkern bei `interestHandling: ADD`); der **noch nicht verteilte Rest** steht neu **prominent oben** (zwischen PK und Verteilung), und die Verteilung ist nach **Gemeinsam / Person A / Person B** gruppiert. (2) **Tour** (Kap. 9.24): statt der bisher bewusst schlichten Hervorhebung jetzt ein **Spotlight** der Rest der Ansicht wird abgedunkelt (ein 9999-px-Kastenschatten, kein separates Overlay), der pulsierende Rahmen ist deutlich stärker, die Karte ist grösser und **springt** auf die dem Ziel gegenüberliegende Bildschirmhälfte (verdeckt es nie); neu mit **«Überspringen»**-Knopf. (3) **Szenario-Ansicht** (Kap. 3.7.7): Grundprofil, Zeitachse und die Kennzahlen (Endvermögen nominal + real, Ruinalter) bilden neu **einen kompakten Block** aus drei Spalten (25 / 50 / 25 %) statt zweier über die ganze Breite gezogener Zeilen. Neue Modal-Grösse `xwide`. Kein Eingriff in den Rechenkern; 267 Tests unverändert grün. |
| 0.28 | 2026-07-24 | Claude (Opus 4.8) | **Modul-Review 2 (Onboarding & Ansicht): Layout-Umbau, Tour und Assistenten-Redesign.** (1) **Szenario-Ansicht neu geordnet** (Kap. 3.7.7): oben eine schlanke Funktions-Leiste (Änderungshistorie · Tour · Neues Szenario · Rechenwege · CSV-Export · Löschen · Abweichungs-Badge), darunter Nächste Schritte → Grundprofil → Zeitachse → Anzeige-Umschalter → Matrix. Grafiken, Effektive Werte, Live-Simulation, Monte-Carlo und Einflussfaktoren sind aus der Leiste **entfernt** sie laufen über die eigenen Menüpunkte (Analysen / Effektive Werte). (2) **Tour** liegt neu in `AppShell` (statt `PlanView`) und startet nach **jeder** Plan-Erstellung Assistent, Beispielplan **und** leerer Plan unabhängig davon, ob sie schon einmal beendet wurde (Kap. 3.7.8); die Erfolgsmeldung hält damit ihr Versprechen. (3) **Assistent durchgehend in Du-Form** im Einzelmodus (Paarmodus weiter «ihr» / je Person). (4) **Schritt-Redesign** (Kap. 3.2.8): Schritt «Vorsorge & Vermögen» erfasst nur noch **Bestandswerte**; ein **neuer Schritt «Sparen & Verteilen»** zeigt die Sparquote (Nettoeinkommen Ausgaben) und lässt sie auf 3a, Wertschriften, Amortisation und Schuldtilgung verteilen der Rest bleibt sichtbar auf dem Cash-Konto. Die **PK-Einzahlung** steht dort bewusst separat, mit dem Hinweis, dass sie vom Bruttolohn kommt und die Sparquote **nicht** schmälert (deckt sich mit dem Rechenkern). Immobilien fragen im Assistenten neu **Wertsteigerung** und den **Zins-in-Ausgaben-Schalter** ab; der Lohn bekommt 1 % Default-Erhöhung. (5) **«Grosse Säule 3a» für Selbstständige** (Roadmap-Feedback): ein Schalter am 3a-Element (und im Assistenten) hebt die Beitrags-Obergrenze von 7258 auf ca. 36288 CHF an (neue Konstante `PILLAR_3A_MAX_SELF_EMPLOYED`, **2026-Wert zu verifizieren**; neues Feld `selfEmployed3a`). (6) Nebenbei: `POST /plans` liefert bei Validierungsfehlern eine lesbare Meldung statt eines rohen zod-Objekts. Keine Änderung am Rechenkern; 267 Tests unverändert grün. |
@@ -127,11 +128,12 @@ Referenz: `prisma/schema.prisma` Zeilen 810, `src/lib/types.ts` Zeilen 384
```
User
└── Plan (Behälter: nur Name — KEINE Finanzdaten)
── Scenario[] (die berechenbare Einheit)
│ Grundprofil: Haushaltsform, Personen, Inflation, Cash-Startwert
└── Plan (der HAUSHALT: Name, Haushaltsform, Startjahr)
── PlanPerson[] (Rolle, Name, Alter — gelten für ALLE Szenarien)
└── Scenario[] (die berechenbare Einheit = eine Planungsvariante)
│ szenario-eigen: Inflation, Cash-Startwert
│ isBase = genau eines je Plan · parentScenarioId = Baum + Vergleichsbasis
├── Person[] (1 bei SINGLE, 2 bei COUPLE — je Szenario eigen!)
├── Person[] (nur das PENSIONSALTER je Rolle — szenario-eigen)
├── Phase[] (Kette 1..n; sourcePhaseId = Gegenstück in der Vorlage)
└── FinancialElement[] (szenario-weit; sourceElementId = Gegenstück in der Vorlage)
├── ElementPhaseValue[] (Werte je Phase, JSON)
@@ -146,14 +148,15 @@ und pro Phasenübergang einen Entscheid-Satz. Dadurch bleibt die Identität eine
Vermögensgegenstands über die Zeit erhalten Voraussetzung für die Fortschreibung (Carry) und
die Vermögensaufteilungs-Grafik.
**(2) Das Grundprofil liegt am Szenario, nicht am Plan** (V6). Nur so lassen sich die
wertvollsten Szenario-Fragen abbilden allen voran ein abweichendes **Pensionsalter**
(„Was, wenn ich mit 62 statt 65 aufhöre?"), das in `Person` steckt. Wäre das Profil geteilt,
wären Frühpensionierungs-Szenarien unmöglich.
**(2) Der Haushalt liegt am Plan, die Annahmen am Szenario** (V7, siehe
[9.30](#930-warum-der-haushalt-am-plan-hängt)). Haushaltsform, Personen (Name, Alter) und
Planstartjahr beschreiben den **Haushalt** sie gelten für alle Szenarien. Unterscheiden sie
sich, ist es ein anderer **Plan**. Szenario-eigen sind die **Annahmen**: Inflation,
Cash-Startwert und am wichtigsten das **Pensionsalter** je Person. Nur deshalb sind
Frühpensionierungs-Szenarien überhaupt möglich („Was, wenn ich mit 62 statt 65 aufhöre?").
Der **Plan** ist damit ein reiner Behälter: Er bündelt Szenarien und trägt den Eigentümer
(`userId`). Ownership von Szenario/Phase/Element läuft über die Kette
`Scenario → Plan → User`.
Der **Plan** trägt zudem den Eigentümer (`userId`). Ownership von Szenario/Phase/Element läuft
über die Kette `Scenario → Plan → User`.
Referenz: `prisma/schema.prisma`.
@@ -317,49 +320,59 @@ Der Dialog «Leer starten» fragt Name plus das vollständige Grundprofil:
Konsistenzregel: `SINGLE` erfordert genau eine Person, `COUPLE` genau zwei (Person A und B).
Verletzung → HTTP 400 mit Klartextmeldung.
Referenz: `src/components/PlanProfileFields.tsx` Zeilen 10121018, `src/app/api/plans/route.ts`
Zeilen 635.
Referenz: `src/components/PlanProfileFields.tsx`, `src/app/api/plans/route.ts`.
Ein neu erstellter Plan hat **keine Phasen und keine Elemente**; `initialCash` ist 0.
### 3.2.2 Grundprofil ändern
Über „Einstellungen" in der Planansicht. Wichtig: Die Personen werden serverseitig in einer
Transaktion **gelöscht und neu angelegt** (`deleteMany` + `create`). Die Person-IDs ändern sich
dadurch. Da Elemente über `ownerRole` (nicht über `personId`) zugeordnet sind, bleibt die
Zuordnung erhalten.
Über **«Profil bearbeiten»** im Grundprofil-Block der Szenario-Ansicht (Panel «Szenario-Profil»).
Der Endpunkt ist `PATCH /api/scenarios/<scenarioId>`: Er schreibt Haushaltsform, Personen (Name,
Alter) und Startjahr an den **Plan**, Inflation und Pensionsalter ans **Szenario** die Trennung
aus V7 ([9.30](#930-warum-der-haushalt-am-plan-hängt)). Das **Pensionsalter** ist dort nur noch
Anzeige; verschoben wird es über «Pensionsalter anpassen» ([3.12](#312-pensionsalter-anpassen)),
weil es auf einer Phasengrenze liegt.
Wichtig: Die Personen werden serverseitig in einer Transaktion **gelöscht und neu angelegt**
(`deleteMany` + `create`). Die Person-IDs ändern sich dadurch. Da Elemente über `ownerRole`
(nicht über `personId`) zugeordnet sind, bleibt die Zuordnung erhalten.
Ein Wechsel von `COUPLE` auf `SINGLE` entfernt Person B. Elemente mit `ownerRole = PERSON_B`
bleiben in der Datenbank bestehen, finden aber keinen Owner mehr siehe
[9.2](#92-verwaiste-person_b-elemente).
Referenz: `src/app/api/plans/[planId]/route.ts` Zeilen 5570.
**Plan umbenennen:** über das Stift-Symbol neben dem Plannamen im Plan-Dashboard
(`PATCH /api/plans/<planId>`, nur das Feld `name`).
Referenz: `src/app/api/scenarios/[scenarioId]/route.ts`, `src/app/api/plans/[planId]/route.ts`.
### 3.2.3 Cash-Anfangswert
Klick auf die erste Zelle der Cash-Zeile öffnet den Dialog „Cash-Anfangswert". Wertebereich
0 bis 1'000'000'000, wird auf ganze Franken gerundet.
Referenz: `src/components/PlanView.tsx` Zeilen 11301156, `src/app/api/plans/[planId]/route.ts` Zeile 36.
Referenz: `src/components/PlanView.tsx`, `PATCH /api/scenarios/<scenarioId>` (Feld `initialCash`).
### 3.2.4 Plan löschen
Aus der Übersichtskachel oder der Planansicht, mit Browser-`confirm()`. Löscht per Datenbank-Cascade
Personen, Phasen, Elemente und alle Werte. Szenarien, die auf diesen Plan als `parentPlanId`
zeigen, werden **nicht** gelöscht ihre `parentPlanId` wird auf `NULL` gesetzt
(`onDelete: SetNull`).
Aus der Übersichtskachel oder der Seitenleiste, mit dem eigenen Bestätigungs-Dialog
(`useConfirm`, siehe [3.7.6](#376-sprache-und-ui-primitiven) kein Browser-`confirm()` mehr).
Löscht per Datenbank-Cascade **alle Szenarien** des Plans und darüber Personen, Phasen, Elemente
und sämtliche Werte, dazu die Ist-Datensätze, gespeicherten Analysen und Berichte des Plans.
Referenz: `prisma/schema.prisma` Zeile 82.
Referenz: `prisma/schema.prisma`, `src/app/api/plans/[planId]/route.ts`.
### 3.2.5 Szenarien
Beim Anlegen eines Plans entsteht **automatisch das Basisszenario** (`isBase = true`,
Name „Basisszenario"). Der beim Anlegen erfasste Profilteil (Haushaltsform, Personen,
Inflation) landet dort, der Name am Plan.
Name „Basisszenario"). Vom erfassten Profil landen **Haushaltsform, Personen und Startjahr am
Plan**, **Inflation und Pensionsalter am Basisszenario** (V7).
Ein weiteres Szenario ist eine **vollständige Kopie eines beliebigen bestehenden Szenarios**
(nicht nur des Basisszenarios). Kopiert werden Grundprofil, alle Personen, alle Phasen, alle
Elemente sowie sämtliche Phasen- und Übergangswerte.
(nicht nur des Basisszenarios die Vorlage ist in der Szenario-Liste je Zeile über «Kopie»
wählbar). Kopiert werden die szenario-eigenen Angaben (Inflation, Cash-Startwert,
Pensionsalter), alle Phasen, alle Elemente sowie sämtliche Phasen- und Übergangswerte. Der
Haushalt wird **nicht** kopiert er liegt am Plan und gilt ohnehin für alle Szenarien.
Gesetzt werden dabei:
- `parentScenarioId` = das kopierte Szenario → ergibt den **Baum** in der Seitenleiste **und**
@@ -404,11 +417,11 @@ Referenz: `src/lib/diff.ts`.
### 3.2.7 Planstart (Kalenderjahr)
Das Grundprofil enthält das Feld **Planstart (Jahr)** das Kalenderjahr, in dem Jahr 1 der
Planung liegt (`Scenario.startYear`). Es dient **ausschliesslich der Darstellung**: Zeitachse
Planung liegt (`Plan.startYear`, seit V7 plan-weit). Es dient **ausschliesslich der Darstellung**: Zeitachse
und Grafiken beschriften damit Jahre statt nur Alter. Die Berechnung rechnet unverändert in
**relativen** Jahren ab Planbeginn `startYear` fliesst in keine Formel ein.
Beim Anlegen wird das laufende Jahr vorbelegt; bestehende Szenarien wurden per Migration darauf
Beim Anlegen wird das laufende Jahr vorbelegt; bestehende Pläne wurden per Migration darauf
gesetzt. Kalenderjahr eines Planjahrs: `startYear + (Jahr 1)`.
### 3.2.8 Geführter Assistent und Beispielplan
@@ -978,10 +991,13 @@ Referenz: `src/components/PlanView.tsx` Zeilen 701769.
### 3.6.4 Analyse-Bereich „Grafiken"
Die Auswertungen liegen **nicht** unter der Matrix, sondern in einem eigenen Bereich: Der Button
**Grafiken** in der oberen Aktionsleiste (neben „Neues Szenario aus diesem" und
„Monte-Carlo-Simulation") öffnet sie als breiten Dialog. So bleibt die Matrix die ruhige
Hauptansicht.
Die Auswertungen liegen **nicht** unter der Matrix, sondern in einem eigenen Bereich: Die Kachel
**Grafiken** unter dem Menüpunkt **Analysen** ([3.10.2](#3102-analysen-vier-kacheln)) öffnet sie
als breiten Dialog. So bleibt die Matrix die ruhige Hauptansicht.
**Szenario-Wahl:** Ganz oben steht ein Auswahlfeld, welches Szenario die Grafiken zeigen es
gilt für **alle drei**. Vorher war der Bereich fest an das geöffnete Szenario gebunden, und nur
der Vermögensverlauf konnte weitere überlagern.
**Kennzahl-Karten:** Endvermögen nominal und Endvermögen real. (Die frühere Karte „Geschätzter
Nachlass" ist entfallen sie war rechnerisch identisch mit dem nominalen Endvermögen und
@@ -994,32 +1010,57 @@ ist grün (Sparquote) oder rot (Verzehr) eingefärbt. Der Keil zwischen roter un
anschaulich „das, was die Inflation frisst".
**Grafik 2 Vermögensverlauf nach Alter** (`WealthChart`): Liniendiagramm über das Alter von
Person A. Je Szenario eine durchgezogene Linie (nominal) und eine gestrichelte (real).
Datenpunkte: **jedes Planjahr** (nicht nur die Phasengrenzen) dadurch werden Verläufe
Person A. Datenpunkte: **jedes Planjahr** (nicht nur die Phasengrenzen) dadurch werden Verläufe
*innerhalb* einer Phase sichtbar, etwa das Abschmelzen im Kapitalverzehr. Grundlage ist
`YearPoint.wealthNominal/wealthReal` (4.10). Über Checkboxen lassen sich die **Geschwister-
Szenarien überlagern**; deren Daten werden bei Bedarf nachgeladen und im Client
zwischengespeichert.
`YearPoint.wealthNominal/wealthReal` (4.10). Über Checkboxen lassen sich weitere **Szenarien
überlagern**; deren Daten werden bei Bedarf nachgeladen und im Client zwischengespeichert.
**Grafik 3 Vermögensaufteilung pro Phase**: Gestapeltes Balkendiagramm mit zwei Balken je Phase
(Beginn / Ende). Gestapelt werden alle Elemente der Kategorien PK, 3a, Immobilie, Sonstiges
Vermögen, die irgendwann einen positiven Wert haben. Stapelung nach `elementId` (nicht Name),
damit gleichnamige Elemente nicht kollidieren. Negative Werte werden auf 0 geklammert.
Der Datenschlüssel je Serie ist die **Szenario-ID**, nicht der Name. Bis 0.30 diente der Name als
Schlüssel zwei gleichnamige Szenarien überschrieben sich dadurch gegenseitig, und die Legende
zeigte eine Linie an, die es im Diagramm gar nicht gab. Die **Legende** ist eigenständig (nicht
die von Recharts): Sie erlaubt je Serie eine **freie Farbwahl** und erklärt den Linienstil
(gestrichelt = reine Planwerte, durchgezogen = mit effektiven Werten).
Referenz: `src/components/Dashboard.tsx`, `src/components/WealthChart.tsx`, `src/components/SparquoteChart.tsx`.
**Grafik 3 Vermögensaufteilung im Zeitverlauf**: Links eine **gestapelte Fläche** über alle
Planjahre, die Phasengrenzen als feine gestrichelte Senkrechte; rechts ein **Ring**, der die
**relative** Aufteilung zu einem wählbaren Zeitpunkt zeigt (Klick in die Fläche wählt das Jahr,
Vorgabe ist das Planende). Gestapelt werden alle Elemente der Kategorien PK, 3a, Immobilie,
Sonstiges Vermögen, die irgendwann einen positiven Wert haben. Stapelung nach `elementId` (nicht
Name), damit gleichnamige Elemente nicht kollidieren. Negative Werte werden auf 0 geklammert.
Bis 0.30 waren das gestapelte Balken mit zwei Säulen je Phase und schräg gestellter
Beschriftung ab drei Phasen kaum mehr lesbar, und die relative Aufteilung war gar nicht
ablesbar. Die Fläche zeigt den Verlauf statt zweier Stichproben je Phase; die Jahreswerte dafür
liegen seit 0.11 ohnehin vor (`ElementPhaseComputed.yearly`), es wird nichts neu gerechnet.
Referenz: `src/components/Dashboard.tsx`, `src/components/WealthChart.tsx`,
`src/components/SparquoteChart.tsx`, `src/components/AllocationChart.tsx`.
### 3.6.5 CSV-Export
`GET /api/plans/<planId>/export` liefert eine semikolon-getrennte CSV, eine Zeile pro Phase:
`GET /api/scenarios/<scenarioId>/export` liefert die **vollständige Matrix** als
semikolon-getrennte CSV. Bis 0.30 war das eine reine Phasen-Zusammenfassung zehn Spalten, eine
Zeile je Phase, **kein einziges finanzielles Element**; was am Bildschirm stand, liess sich damit
nicht weiterverarbeiten.
```
Phase;Typ;Dauer;Einkommen (Beginn);Ausgaben (Beginn);Quote (Beginn);Quote (Ende);Cash (Ende);Endvermoegen (nominal);Endvermoegen (real)
```
Die Datei enthält **vier Blöcke** untereinander (Excel kommt damit zurecht, und es ist sofort
erkennbar, was zusammengehört):
Bei vorhandenem Ruin folgt eine Schlusszeile `Ruin: Kapital aufgebraucht mit Alter <N>`.
Dateiname = Planname, nicht-alphanumerische Zeichen durch `_` ersetzt.
| Block | Inhalt |
|---|---|
| **Kopf** | Szenario, Haushaltsform, Personen (Alter/Pensionsalter), Planstart, Inflation, Cash-Anfangswert, Erzeugungszeitpunkt |
| **LEBENSPHASEN** | je Phase: Typ, Dauer, Altersspanne, Einkommen/Ausgaben, Quote (Beginn/Ende), Cash (Beginn/Ende), Vermögen (Beginn/Ende, nominal + real) |
| **MATRIX** | Zeilen = Cash + alle Elemente; je Phase **zwei** Spalten (Beginn/Ende), dazwischen je Übergang **eine** Spalte mit dem Entscheid im Klartext (z. B. «Kapitalbezug (Steuer 8 %)») |
| **JAHRESWERTE** | je Planjahr: Kalenderjahr, Alter, Vermögen nominal/real, Cash, Einkommen, Ausgaben, Sparquote |
Referenz: `src/lib/calculations.ts` Zeilen 620648.
Der Jahreswerte-Block ist der, mit dem sich in Excel selbst weiterrechnen lässt; die Matrix
bildet 1:1 die Bildschirm-Ansicht ab.
Zwei Details, die vorher Ärger machten: Die Datei beginnt mit einem **BOM** ohne dieses
Zeichen zerlegt Excel unter Windows alle Umlaute , und der **Dateiname** transliteriert Umlaute
(ä→ae) statt sie durch `_` zu ersetzen. Bei vorhandenem Ruin folgt eine Schlusszeile.
Referenz: `src/lib/csv.ts` (rein, ohne I/O), `src/app/api/scenarios/[scenarioId]/export/route.ts`.
### 3.6.6 Analyse-Bereich „Einflussfaktoren"
@@ -1058,7 +1099,7 @@ Jede Element-Zeile und jeder Phasenkopf trägt oben rechts ein **Expand-Icon** (
| Reiter | Inhalt |
|---|---|
| Übersicht | Typ, Dauer, Vermögen Beginn/Ende; Vermögensaufteilung als gestapelter Balken (Beginn und Ende) |
| Übersicht | Typ, Dauer, Vermögen Beginn/Ende; Vermögensaufteilung als Fläche über die Planjahre mit Ring für den gewählten Zeitpunkt |
| Wasserfall | Vermögens- und Cash-Wasserfall ([4.14.2](#4142-die-beiden-wasserfälle)) |
| Rechenweg | Quote, Spar-/Verzehrrate, Cash-Fortschreibung, Vermögen, Phasentyp, Cash-Übergang |
@@ -1289,6 +1330,12 @@ die oberste. Animationen (150220 ms) respektieren `prefers-reduced-motion`.
je Farbschema abgestimmt) statt der Akzentfarbe «hier fehlt eine Eingabe» und «hier kannst
du klicken» sind damit unterscheidbar.
**Diagramm-Palette:** Seit 0.31 kommen auch die Farben aller Diagramme aus Tokens
(`--chart-1``--chart-6`, dazu `--chart-grid`) statt aus festen Hex-Werten je Farbschema
gedämpft abgestimmt. Recharts bekommt die Variablen direkt als Attributwert (`var(--chart-1)`),
der Browser löst sie im SVG auf. Vorher stachen die Diagramme mit greller Standardpalette aus
dem Schema heraus und sahen im Dunkelmodus falsch aus.
### 3.7.7 Inspector-Panel statt Modals
Alle **Einzel-Bearbeitungen** Phasenzelle, Übergangszelle, Cash-Übergang, Cash-Anfangswert,
@@ -1387,6 +1434,12 @@ sie. Zwei Sicherungen ergänzen das:
Eine **Hauptversion** wird nie zusammengefasst und nie nachträglich verändert; die nächste
Änderung beginnt bei A.1.
**Ein Szenario startet bei 0.1.** Die Nebenversion zählt beliebig weit hoch (0.1, 0.2, … 0.137);
die **0** bedeutet «noch kein verabschiedeter Stand». Erst eine bewusst gesetzte **Hauptversion**
macht daraus **1.0**. Bis 0.30 begann jedes Szenario bereits bei 1.0 damit war die Hauptversion
bedeutungslos, weil jeder Plan schon eine hatte. Bestehende Historien werden **nicht**
umnummeriert; nur der Startwert ist neu (Migration `20260724120000_version_zero_start`).
### 3.8.2 Was die Historie zeigt
Je Version: **wer** (Benutzername), **wann** (Zeitpunkt der letzten Änderung dieser Sitzung)
@@ -1575,15 +1628,20 @@ Referenz: `src/lib/actuals.ts`, `src/lib/dataview.ts`, `src/components/ActualsDi
## 3.10 Navigation auf Plan-Ebene und gespeicherte Analysen
Die Seitenleiste ist zweistufig: Unter jedem **Plan** liegen drei leicht eingezogene
Unterpunkte. Ein Klick auf den **Plan-Namen** öffnet dessen Dashboard.
Die Seitenleiste ist zweistufig: Unter jedem **Plan** liegen vier Unterpunkte. Ein Klick auf den
**Plan-Namen** öffnet dessen Dashboard.
| Ort | Führt zu |
|---|---|
| Plan-Name | **Plan-Dashboard** (Kennzahlen) |
| Szenarien | **Szenario-Liste** darunter bleibt der Szenario-Baum, dessen Einträge direkt in die Matrix führen |
| Plan-Name | **Plan-Dashboard** (Kennzahlen, Umbenennen) |
| Szenarien | **Szenario-Liste** darunter der Szenario-Baum, **verschachtelt** nach Herkunft; seine Einträge führen direkt in die Matrix |
| Effektive Werte | Liste + Wizard der Ist-Werte ([3.9](#39-effektive-werte-plan-ist-vergleich)) |
| Analysen | Vier Werkzeug-Kacheln + Liste gespeicherter Analysen |
| Berichte | PDF-Berichte erzeugen und wieder herunterladen ([3.11](#311-pdf-berichte)) |
Die vier Unterpunkte stehen **bündig zum Knoten «Szenarien»**; nur die einzelnen Szenarien
darunter sind eingerückt und zwar je nach Tiefe ihrer Herkunftskette, sodass in der
Seitenleiste sichtbar ist, woraus ein Szenario entstanden ist.
### 3.10.1 Plan- vs. Szenario-Ebene der Kennzahlen
@@ -1591,13 +1649,19 @@ Seit V7 ([9.30](#930-warum-der-haushalt-am-plan-hängt)) liegen Haushaltsform, P
Startjahr am Plan; Endvermögen, Ruinalter, Phasen und Elemente sind dagegen **szenario-eigen**.
Das Plan-Dashboard zeigt deshalb die Haushaltsdaten direkt, alle gerechneten Kennzahlen aber
ausdrücklich als **«laut Basisszenario»** das Basisszenario ist der kanonische Vertreter.
Falls Ist-Werte erfasst sind, weist es zusätzlich die **Abweichung** des Endvermögens gegenüber
dem Plan aus.
Die vier Kennzahl-Kacheln (Szenarien, Effektive Werte, Analysen, Berichte) sind **anklickbar**
und führen in ihren Bereich. Über ein **Stift-Symbol** neben dem Plannamen lässt sich der Plan
umbenennen.
Die **Szenario-Liste** zeigt je Szenario Name, aktuelle Hauptversion, Anzahl Elemente, das
Endvermögen und ob das Kapital reicht; das Basisszenario ist farblich hervorgehoben, und die
Herkunft (aus welchem Szenario kopiert) steht darunter. Zwei Aktionen je Zeile: **Historie**
und **Matrix**.
Falls Ist-Werte erfasst sind, weist das Dashboard die **Abweichung** des Endvermögens gegenüber
dem Plan aus benannt mit dem **Jahr des jüngsten Ist-Datensatzes** («Mit den effektiven Werten
von 2032 …») und **grün**, wenn die Realität besser ist als der Plan, sonst rot.
Die **Szenario-Liste** zeigt je Szenario Name, die **aktuelle Version** (z. B. «0.17»), Anzahl
**Phasen** und **Elemente**, das Endvermögen und ob das Kapital reicht; das Basisszenario ist
farblich hervorgehoben, und die Herkunft (aus welchem Szenario kopiert) steht darunter. Ein
Klick auf die **Zeile** öffnet die Matrix; je Zeile gibt es zusätzlich **Historie**, **Kopie**
(macht dieses Szenario zur Vorlage) und ausser beim Basisszenario **Löschen**.
### 3.10.2 Analysen: vier Kacheln
@@ -3233,7 +3297,8 @@ PlanComputed ← an den Client geliefert
| Datei | Verantwortung |
|---|---|
| `calculations.ts` | Berechnungskern + CSV-Export. Keine I/O. |
| `calculations.ts` | Berechnungskern. Keine I/O. |
| `csv.ts` | CSV-Export der vollständigen Matrix (vier Blöcke, Kap. 3.6.5). Rein. |
| `elements.ts` | Kategorien, Labels, Reihenfolge, JSON-Payload-Typen, Zod-Schemas, `num()` |
| `types.ts` | Domänentypen für API und Berechnung |
| `constants.ts` | Schweizer Systemparameter |
@@ -3475,6 +3540,7 @@ sondern zu leeren Werten.
| `20260720090000_actuals` | Tabelle `ActualsSet` (effektive Werte je Plan, Werte als JSONB, Cash separat) |
| `20260720140000_plan_level_profile` | **V7**: Haushaltsform, Personen (Name/Alter) und Startjahr vom Szenario auf den Plan; neue Tabelle `PlanPerson`; `Person` behält nur das Pensionsalter. Datenübernahme aus dem Basisszenario. |
| `20260721090000_reports` | Tabelle `Report`: gewählte Parameter, eingefrorenes Modell und die PDF-Datei als `BYTEA` |
| `20260724120000_version_zero_start` | `Scenario.currentMajor` startet bei **0** statt 1 die Versionierung beginnt bei 0.1 (Kap. 3.8). Nur der Default; bestehende Zeilen bleiben |
| `20260720160000_saved_analyses` | Tabelle `SavedAnalysis` (Eingaben + Ergebnis als JSONB, denormalisierte Kerndaten) |
**Zur V6-Migration:** Sie benennt die bisherige `Plan`-Tabelle in `Scenario` um dadurch
@@ -3846,7 +3912,7 @@ Include `src/**/*.test.ts`). Es gibt **keine** Komponenten-, API- oder E2E-Tests
| `actuals.test.ts` | 24 | Zuordnung über Kopie-Ketten, Sprung im Ist-Jahr, Weiterrechnen ab dem Ist-Wert, geschlossene Brücken, Fluss-Rückrechnung, Wirkung über die Phasengrenze hinaus |
| `dataview.test.ts` | 7 | beide Sichten in einem Zug, Rückfall ohne Ist-Werte, Abweichungsmessung |
| `ratefields.test.ts` | 17 | Erkennung geänderter Raten, Reichweite (diese/folgende/alle), Erhalt der übrigen Werte in den Zielphasen |
| `versioning.test.ts` | 22 | Nummerierung und Zusammenfassung je Sitzung, unveränderte Stände, Benutzertrennung, Bauplan des Wiederherstellens, Auswirkung auf Kind-Szenarien |
| `versioning.test.ts` | 23 | Nummerierung und Zusammenfassung je Sitzung, unveränderte Stände, Benutzertrennung, Bauplan des Wiederherstellens, Auswirkung auf Kind-Szenarien |
| `versioning-coverage.test.ts` | 3 | statischer Wächter: jeder schreibende Endpunkt löst eine Version aus |
| `report.test.ts` | 9 | Zusammenfassung, Basis-Angabe zu JEDER Kennzahl, Szenario-Deckelung, Plan/Ist-Block, Haftungsausschluss, gültige PDF-Datei |
| `livesim.test.ts` | 15 | Element-Regler bewegt genau ein Element; Aufschlüsselung deckt sich mit dem Sammelregler; neutrale Stellung verändert den Plan nicht; Ruinmeldung |
@@ -3857,7 +3923,8 @@ Include `src/**/*.test.ts`). Es gibt **keine** Komponenten-, API- oder E2E-Tests
| `diff.test.ts` | 9 | Abweichungs-Erkennung gegen das Eltern-Szenario |
| `migrations.test.ts` | 3 | spielt alle Migrationen gegen echtes PostgreSQL (PGlite) ein; prüft zusätzlich die V7-**Datenübernahme** (Basisszenario gewinnt, Pensionsalter bleiben szenario-eigen) |
| `rate-limit.test.ts` | 6 | Fixed-Window: erlaubt bis Limit, blockt danach, startet nach Fensterablauf neu, trennt je Schlüssel; Client-IP aus X-Forwarded-For / X-Real-IP |
| **Total** | **267** | |
| `csv.test.ts` | 7 | BOM, alle vier Blöcke, jedes Element als Zeile, Beginn-/Ende-/Übergangsspalten, Entscheid im Klartext, ein Eintrag je Planjahr, Maskierung von `;` und `"` |
| **Total** | **275** | |
## 8.2 Testfälle