UI-Umbau: Analyse-Bereich, Planstart, Zeitachse mit Phasen
Deploy App / deploy (push) Successful in 1m44s
Deploy App / deploy (push) Successful in 1m44s
Sieben Anpassungen aus dem Feedback: - Grafiken liegen neu im eigenen Bereich "Grafiken" (Dialog, Button oben) statt unter der Matrix. Die Matrix bleibt die ruhige Hauptansicht. - Kennzahl "Geschaetzter Nachlass" entfernt -- sie war rechnerisch identisch mit dem nominalen Endvermoegen und suggerierte eine Zusatzinformation, die es nicht gab. - Monte-Carlo-Button nach oben zu den Szenario-Aktionen verschoben. - Neues Profilfeld "Planstart (Jahr)" (Scenario.startYear + Migration). Rein fuer die Darstellung -- die Berechnung rechnet unveraendert in relativen Jahren. Bestehende Szenarien werden auf das laufende Jahr gesetzt. - Zeitachse zeigt die Lebensphasen als Segmente (Breite = Dauer, Einfaerbung nach Phasentyp) inkl. Jahresspanne, plus Jahres-Beschriftung an den Enden. - Lebensphase bearbeiten neu als Popup statt Panel unter der Tabelle -- konsistent zu allen anderen Eingaben; Speichern schliesst. - Vermoegensverlauf ueber ALLE Jahre statt nur ueber die Phasengrenzen. Dafuer fuehrt computePlan das Vermoegen neu pro Jahr mit (YearPoint.wealthNominal/ wealthReal) -- dieselbe Groesse, die schon die Ruin-Erkennung berechnet. Damit werden Verlaeufe INNERHALB einer Phase sichtbar (z. B. Kapitalverzehr). Zwei Tests fuer das Jahres-Vermoegen (58 -> 60). Spezifikation auf v0.9. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+47
-16
@@ -4,10 +4,10 @@
|
||||
| | |
|
||||
|---|---|
|
||||
| **Dokument** | Funktionale und Technische Spezifikation FPT |
|
||||
| **Version** | 0.8 |
|
||||
| **Version** | 0.9 |
|
||||
| **Datum** | 2026-07-18 |
|
||||
| **Status** | Lebendes Dokument |
|
||||
| **Codestand** | Arbeitsstand nach `71137f7` inkl. Szenario-Hierarchie (V6) (Branch `main`) |
|
||||
| **Codestand** | Arbeitsstand nach `a5d4868` inkl. UI-Umbau und Planstart (Branch `main`) |
|
||||
| **Ersetzt** | `FDD_TDD_FPT.docx` (v1–v5) 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.9 | 2026-07-18 | Claude (Opus 4.8) | **UI-Umbau und Planstart.** (1) Die Grafiken liegen neu im eigenen Bereich **Grafiken** (Dialog, Button oben) statt unter der Matrix. (2) Kennzahl **Geschätzter Nachlass** entfernt – sie war identisch mit dem nominalen Endvermögen. (3) **Monte-Carlo-Button** nach oben zu den Szenario-Aktionen verschoben. (4) Neues Profilfeld **Planstart (Jahr)** (`Scenario.startYear`, Migration; reine Anzeige, Berechnung bleibt in relativen Jahren) – erscheint auf der Zeitachse. (5) Zeitachse zeigt neu die **Lebensphasen als Segmente** (Breite = Dauer, Einfärbung nach Phasentyp, Jahresspanne). (6) **Lebensphase bearbeiten** neu als Popup statt Panel unter der Tabelle. (7) **Vermögensverlauf** über **alle Jahre** statt nur über die Phasengrenzen – dafür führt `computePlan` das Vermögen neu pro Jahr mit (`YearPoint.wealthNominal/wealthReal`). Zwei Tests ergänzt (58 → 60). |
|
||||
| 0.8 | 2026-07-18 | Claude (Opus 4.8) | **Szenario-Hierarchie (V6)** – grösste Umstrukturierung bisher. Der **Plan** ist neu ein schlanker Behälter ohne Finanzdaten; die berechenbare Einheit ist das **Szenario**, das Grundprofil (inkl. **Pensionsalter** → Frühpensionierungs-Szenarien), Phasen und Elemente trägt. Jeder Plan erhält beim Anlegen automatisch ein **Basisszenario**; weitere Szenarien entstehen als vollständige Kopie eines beliebigen Szenarios und hängen als **Baum** darunter (Sidebar zeigt die Verschachtelung). Kopierte Phasen/Elemente tragen Herkunfts-Verweise (`sourcePhaseId`, `sourceElementId`) – darauf beruht die **Abweichungs-Markierung**: geändert = gelb, neu = grün, entfernt = graue Geisterzeile (eigene Theme-Tokens für alle drei Farbschemata). Charts vergleichen neu die Geschwister-Szenarien. Datenmodell: neue Tabelle `Plan`, bisheriger `Plan` → `Scenario` (IDs erhalten), `planId` → `scenarioId` in Person/Phase/FinancialElement. API neu unter `/api/scenarios/*`. Neue Kapitel 2.1, 3.2, 4.13; 9 Diff-Tests + Migrations-Test (48 → 58). **Migration mit echtem Postgres (PGlite) verifiziert**, inkl. verschachtelter Szenarien und Cascade. |
|
||||
| 0.7 | 2026-07-17 | Claude (Opus 4.8) | **Monte-Carlo-Simulation** (Roadmap Nr. 19, Stufe A). Button in der Planansicht → Dialog mit Erklärung, Eingaben und Ergebnis. Statt einer festen Rendite/Inflation werden tausende Zufallspfade gerechnet; ausgewiesen werden **Ruinwahrscheinlichkeit**, **Erfolgswahrscheinlichkeit** (P(Endvermögen ≥ Zielbetrag)) und ein **Fächer** (10 %/Median/90 %) plus die deterministische Linie. Läuft komplett im Browser (`computePlan` ist rein). Modell: fettschwänzige Verteilung (Student-t, ν=5), gemeinsamer Marktschock (ρ=0.7), Böden 0 % für PK/3a und −100 % sonst. Pro renditetragendem Element und für die Inflation je: historischer Ø (Pflicht), Streuungsstufe, σ. Neue Datei `montecarlo.ts` + optionaler `sample`-Parameter in `computePlan` (Inflation neu als kumulatives Array; deterministisch identisch). Neues Kapitel 4.12; 7 MC-Tests + Refactor-Absicherung (41 → 48). Keine Verhaltensänderung, keine DB-Änderung. |
|
||||
| 0.6 | 2026-07-17 | Claude (Opus 4.8) | **Teilverkauf** von Sonstigem Vermögen (Roadmap Nr. 42) und **Sonderamortisation** der Hypothek (Roadmap Nr. 15). `OTHER_ASSET` am Übergang neu: Halten / Verkaufen / **Teilverkauf** – ein Betrag fliesst ins Cash (erscheint als „Kapitalzufluss" im Phasenkopf), der Rest bleibt investiert. `REAL_ESTATE` im Halten-Fall neu mit **Einmaltilgung** aus dem Cash (analog zur Sofort-Tilgung bei Schulden; erscheint als „Kapitalinvestition"). Damit lässt sich die indirekte Amortisation via 3a mechanisch nachbilden. Die eigentliche Roadmap Nr. 15 (Steuerwirkung der indirekten Amortisation) bleibt mit dem Steuer-Bündel 13/14/23 zurückgestellt – siehe 9.14. Kapitel 4.9.3/4.9.4 ergänzt. Fünf Regressionstests (36 → 41). Keine Verhaltensänderung für bestehende Pläne. |
|
||||
@@ -325,6 +326,16 @@ fester Gelbwert würde im Dunkelschema unbrauchbar aussehen.
|
||||
|
||||
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
|
||||
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
|
||||
gesetzt. Kalenderjahr eines Planjahrs: `startYear + (Jahr − 1)`.
|
||||
|
||||
## 3.3 Lebensphasen
|
||||
|
||||
### 3.3.1 Phase anlegen
|
||||
@@ -364,8 +375,9 @@ Referenz: `src/app/api/plans/[planId]/phases/route.ts`.
|
||||
|
||||
### 3.3.2 Phase bearbeiten
|
||||
|
||||
Klick auf einen Phasenkopf öffnet ein Detail-Panel unterhalb der Matrix mit Bezeichnung und
|
||||
Dauer. Die Dauer wird auch hier gekappt. Eine phasenspezifische Inflationsrate gibt es nicht;
|
||||
Klick auf einen Phasenkopf öffnet ein **Popup** („Lebensphase: <Name>") mit Bezeichnung und
|
||||
Dauer – konsistent zu allen anderen Eingaben (Element-Zellen, Übergänge). Speichern schliesst
|
||||
das Popup. Die Dauer wird auch hier gekappt. Eine phasenspezifische Inflationsrate gibt es nicht;
|
||||
das Panel weist darauf hin: „Die Inflationsrate gilt plan-weit und wird in den Plan-Einstellungen
|
||||
gesetzt."
|
||||
|
||||
@@ -727,10 +739,16 @@ Referenz: `src/components/PlanView.tsx` Zeilen 665–699.
|
||||
|
||||
### 3.6.2 Zeitachse
|
||||
|
||||
Horizontaler Balken über das Alter (von jüngster Person bis Planende) mit:
|
||||
Horizontale Achse über das Alter (von jüngster Person bis Planende) mit:
|
||||
- den **Lebensphasen als Segmente**: Breite proportional zur Dauer, Einfärbung nach Phasentyp
|
||||
(Erwerb kräftig, Misch mittel, Pension hell), beschriftet mit Name, Dauer und – sofern ein
|
||||
Planstart gesetzt ist – der **Jahresspanne** (z. B. „2026–2046")
|
||||
- Flaggen-Marker je Person am Pensionsalter (Farbe: Person A indigo, Person B hellblau)
|
||||
- Trennstriche an den Phasengrenzen
|
||||
- rotem „Ruin <Alter>"-Marker, falls zutreffend
|
||||
- Alters- **und Jahres**-Beschriftung an beiden Enden
|
||||
|
||||
Die Kalenderjahre stammen aus dem Profilfeld **Planstart** (`Scenario.startYear`). Ist es nicht
|
||||
gesetzt, zeigt die Achse nur Alter – die Berechnung ist davon nie betroffen (siehe 3.2.7).
|
||||
|
||||
Referenz: `src/components/Timeline.tsx`.
|
||||
|
||||
@@ -760,12 +778,16 @@ Kennzahl falsch beschriften.
|
||||
|
||||
Referenz: `src/components/PlanView.tsx` Zeilen 701–769.
|
||||
|
||||
### 3.6.4 Dashboard
|
||||
### 3.6.4 Analyse-Bereich „Grafiken"
|
||||
|
||||
Erscheint unterhalb der Matrix, sobald mindestens eine Phase existiert.
|
||||
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.
|
||||
|
||||
**Kennzahl-Karten:** Endvermögen nominal, Endvermögen real, Geschätzter Nachlass
|
||||
(= Endvermögen der letzten Phase, „potenziell vererbbar").
|
||||
**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
|
||||
suggerierte eine zusätzliche Information, die es nicht gab.)
|
||||
|
||||
**Grafik 1 – Einkommen vs. Ausgaben pro Jahr** (`SparquoteChart`): Ein Datenpunkt pro Jahr
|
||||
über alle Phasen. Grüne Linie = Einkommen nominal (inkl. Renten), rote Linie = Ausgaben nominal,
|
||||
@@ -774,9 +796,11 @@ 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 Plan eine durchgezogene Linie (nominal) und eine gestrichelte (real). Datenpunkte:
|
||||
Startvermögen Phase 1 plus je ein Endwert pro Phase. Über Checkboxen lassen sich **andere Pläne
|
||||
überlagern** (Szenariovergleich); deren Daten werden bei Bedarf nachgeladen und im Client
|
||||
Person A. Je Szenario eine durchgezogene Linie (nominal) und eine gestrichelte (real).
|
||||
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.
|
||||
|
||||
**Grafik 3 – Vermögensaufteilung pro Phase**: Gestapeltes Balkendiagramm mit zwei Balken je Phase
|
||||
@@ -1224,7 +1248,8 @@ expenseReal = expenseRealBase + interestNominal / inflFactor
|
||||
// 3. Quote
|
||||
quote = incomeFlow − expenseNominal
|
||||
|
||||
// 4. YearPoint für die Grafik anfügen (year, age Person A, income, expenseNominal, expenseReal)
|
||||
// 4. YearPoint anlegen (year, age Person A, income, expenseNominal, expenseReal);
|
||||
// wealthNominal/wealthReal werden nach Schritt 8 nachgetragen
|
||||
|
||||
// 5. Vermögen: verzinsen, Sparbeitrag, Bezugsrate
|
||||
für jedes Asset a:
|
||||
@@ -1252,6 +1277,8 @@ falls cash < 0 → cashNegative = true
|
||||
|
||||
// 8. Ruin prüfen (Gesamtvermögen zum Jahresende)
|
||||
total = cash + Σ asset.value + Σ (re.value − re.mortgage) + Σ (−d.owed)
|
||||
yearPoint.wealthNominal = round(total) // Grundlage des Verlaufs je Jahr
|
||||
yearPoint.wealthReal = round(total / cumInfl[Jahr])
|
||||
falls ruinAge === null und total < 0 → ruinAge = age(Person A) + yearsBefore + t
|
||||
```
|
||||
|
||||
@@ -1437,7 +1464,7 @@ yearsBefore += duration
|
||||
```ts
|
||||
PlanComputed {
|
||||
phases: PhaseComputed[] // alle Kennzahlen je Phase, inkl. elements[]
|
||||
yearly: YearPoint[] // ein Punkt pro Jahr über alle Phasen (Grafik)
|
||||
yearly: YearPoint[] // ein Punkt pro Jahr über alle Phasen (inkl. Vermögen je Jahr)
|
||||
nachlass: number // = endWealthNominal der letzten Phase, sonst 0
|
||||
ruinAge: number | null // Alter Person A beim ersten Gesamtvermögen < 0
|
||||
}
|
||||
@@ -1692,6 +1719,7 @@ PlanComputed ← an den Client geliefert
|
||||
| `householdType` | `HouseholdType` | `SINGLE` \| `COUPLE` |
|
||||
| `inflationRateDefault` | Float | szenario-weite Inflation in % |
|
||||
| `initialCash` | Float | Default 0 |
|
||||
| `startYear` | Int? | Kalenderjahr des Planbeginns – **nur Darstellung** (siehe 3.2.7) |
|
||||
| `createdAt` / `updatedAt` | DateTime | |
|
||||
|
||||
**Phase**
|
||||
@@ -1841,6 +1869,7 @@ sondern zu leeren Werten.
|
||||
| `20260716210000_drop_phase_inflation_rate` | `Phase.inflationRate` entfernt (Inflation ist plan-weit) |
|
||||
| `20260716230000_phase_cash_transition` | `Phase.cashTransition` (JSONB) für einmalige Sonderein-/ausgaben |
|
||||
| `20260718090000_plan_scenario_hierarchy` | **V6**: `Plan` → `Scenario` (IDs erhalten), neuer Behälter `Plan`, `planId` → `scenarioId`, Herkunfts-Verweise |
|
||||
| `20260718140000_scenario_start_year` | `Scenario.startYear` (Kalenderjahr des Planbeginns), bestehende auf das laufende Jahr gesetzt |
|
||||
|
||||
**Zur V6-Migration:** Sie benennt die bisherige `Plan`-Tabelle in `Scenario` um – dadurch
|
||||
bleiben alle IDs und damit sämtliche Kind-Fremdschlüssel gültig. Für jedes bisherige
|
||||
@@ -2112,7 +2141,7 @@ Zielumgebung: Hetzner CX23, Traefik als Reverse Proxy, Domain `fpt.aicds.ch`, Gi
|
||||
Getestet wird ausschliesslich der Berechnungskern – bewusst, da dort die Fachlogik und das
|
||||
Regressionsrisiko liegen. `src/lib/calculations.test.ts` (41 Tests: AHV-Rentenformel, Immobilie,
|
||||
Teilverkauf, Sonderamortisation, AHV einkommensabhängig, „V5 Golden Tests") und
|
||||
`src/lib/montecarlo.test.ts` (7 Tests), `src/lib/diff.test.ts` (9 Tests) und `src/lib/migrations.test.ts` (1 Test, spielt alle Migrationen gegen echtes PostgreSQL ein) ergeben zusammen **58 Tests**, ausgeführt mit Vitest in
|
||||
`src/lib/montecarlo.test.ts` (7 Tests), `src/lib/diff.test.ts` (9 Tests) und `src/lib/migrations.test.ts` (1 Test, spielt alle Migrationen gegen echtes PostgreSQL ein) ergeben zusammen **60 Tests**, ausgeführt mit Vitest in
|
||||
der Node-Umgebung (`vitest.config.ts`, Include `src/**/*.test.ts`). Es gibt **keine** Komponenten-,
|
||||
API- oder E2E-Tests.
|
||||
|
||||
@@ -2141,6 +2170,8 @@ API- oder E2E-Tests.
|
||||
| **MC: Volatilität / Vol-Drag** | σ > 0 spreizt p10<median<p90; Median unter dem deterministischen Wert |
|
||||
| **MC: Erfolg / Reproduzierbarkeit** | P(≥ Ziel) fällt mit steigendem Ziel; gleicher Seed → identisches Ergebnis |
|
||||
| **MC: Boden / Ruin** | 0%-Boden hält PK/3a ≥ Startwert; sicherer Verzehr → Ruinwahrscheinlichkeit 100 % |
|
||||
| **Vermögen je Jahr** | 100k @ 10 % über 3 J. → 110k/121k/133.1k je Jahrespunkt; Endjahr = Phasen-Endvermögen |
|
||||
| **Vermögen real** | 100k bei 10 % Inflation → real 90'909 |
|
||||
| Test 1 – Ansparen | Einkommen +2 % nominal, Ausgaben real flach: Endvermögen 761'654 nominal / 565'928 real (±1 %), kein negatives Cash |
|
||||
| Test 2 – Verzehr/Ruin | Rente nominal fix 60k, Ausgaben real 100k, Vermögen 900k @3 %: `ruinAge === 94` |
|
||||
| Test 3 – Cash-Ausgleich | Sparrate 6'364: `cashEnd === 5472`, nie negativ |
|
||||
|
||||
Reference in New Issue
Block a user