Zweispalter: links Regler, rechts waehlbare Grafik, oben Kennzahlen mit Differenz zum unveraenderten Plan. Keine eigene Rechenlogik -- die Regler nutzen dieselben Transformationen wie der Tornado. Neu: applyElementDriver / tunableElements fuer einzeln regelbare Element-Renditen; livesim.ts; AllocationChart aus dem Dashboard geloest. computePlan misst 0.2 ms auf 60 Jahren -> synchron, ohne Debounce. Spezifikation 0.18 (4.15 und 9.27 neu), 15 Tests (124 -> 139). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+118
-3
@@ -4,7 +4,7 @@
|
||||
| | |
|
||||
|---|---|
|
||||
| **Dokument** | Funktionale und Technische Spezifikation FPT |
|
||||
| **Version** | 0.17 |
|
||||
| **Version** | 0.18 |
|
||||
| **Datum** | 2026-07-18 |
|
||||
| **Status** | Lebendes Dokument |
|
||||
| **Codestand** | Arbeitsstand nach `1d046e9` inkl. zwei Monte-Carlo-Fragestellungen (Branch `main`) |
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
| Version | Datum | Autor | Änderung |
|
||||
|---|---|---|---|
|
||||
| 0.18 | 2026-07-19 | Claude (Opus 4.8) | **Live-Simulation** (Roadmap Nr. 22). Neuer Button und Dialog als Zweispalter: links Schieberegler, rechts eine wählbare Grafik, darüber eine Kennzahlenleiste. Dreht man an einem Regler, wird der Plan **sofort** neu gerechnet – ohne für jede Variante eine Szenario-Kopie anzulegen. **Keine eigene Rechenlogik:** Die Regler benutzen dieselben Transformationen wie der Tornado (`applyDriver`), können also gar nicht etwas anderes zeigen als die Einflussfaktoren-Analyse. Neu ist nur `applyElementDriver` – dieselbe Verschiebung auf ein **einzelnes** Element statt auf eine ganze Kategorie: Standardmässig gibt es einen Sammelregler «Rendite», ein Klick auf «Renditen einzeln aufschlüsseln» ersetzt ihn durch je einen Regler pro Anlage (der Sammelregler wird dabei **entfernt**, nicht ergänzt, sonst zählte eine Bewegung doppelt; ein Test sichert ab, dass beide Ansichten denselben Plan beschreiben). Der unveränderte Plan wird als **Referenzlinie** mitgezeichnet, und die Kennzahlenleiste weist Endvermögen nominal/real **mit Differenz zum Plan** aus sowie – als eigene Karte – ob das Kapital reicht; ein gekippter Plan ist einer Verlaufslinie sonst nicht anzusehen. Gemessene Laufzeit von `computePlan`: **0.2 ms** auf einem 60-Jahres-Plan mit 10 Elementen, also rund 1 % des 16-ms-Frame-Budgets – deshalb wird synchron gerechnet, **ohne Debounce und ohne Worker**. Anders als der Tornado haben die Regler **Standardbereiche** (Begründung des scheinbaren Widerspruchs zu 9.18: neues Kapitel 9.27), beide Enden editierbar. **Das Pensionsalter fehlt weiterhin** (9.18, eigener Roadmap-Punkt); «Als Szenario speichern» ist bewusst zurückgestellt, ersatzweise zeigt der Dialog die aktive Einstellung als lesbare Zeile. Die Vermögensaufteilung wurde als `AllocationChart` aus dem Dashboard herausgelöst, damit beide sie nutzen. Neue Kapitel 4.15 und 9.27; 15 Tests ergänzt (124 → 139). Keine API-, DB- oder Schreib-Änderung – das Feature liest ausschliesslich. Nebenbei dieselbe vom Umlaut-Sweep verstümmelte Hex-Farbe (`#7c3aed`) wie in 0.17, diesmal im Dashboard. |
|
||||
| 0.17 | 2026-07-19 | Claude (Opus 4.8) | **Monte-Carlo: zwei Welten, vier Fälle.** Behebt einen Darstellungs-Widerspruch: Zuvor konnte «Planung 69 % erreicht» neben «Ziel 3 Mio nur 41 %» stehen, obwohl 3 Mio unter dem Plan-Endbetrag von 3.7 Mio lag – die beiden Zahlen stammten aus **verschiedenen simulierten Welten**. Neu läuft die Simulation **immer zweimal** (historische Renditen / geplante Werte, gemeinsamer Seed) und liest aus **jeder** Verteilung **beide** Schwellen ab: Plan-Endbetrag und Zielbetrag. Fall 1 und Fall 3 stammen damit aus derselben Verteilung, wodurch ein tieferes Ziel **nie** unwahrscheinlicher sein kann als ein höheres – der Widerspruch ist strukturell ausgeschlossen (Test). Zweite Korrektur: Der Nullpunkt für das Urteil ist **nicht 50 %**, sondern **Fall 2** (derselbe Schwellwert in der eigenen geplanten Welt); durch den Volatilitäts-Drag liegt der je nach Streuung bei 27–48 %. Verglichen wird Fall 1 gegen Fall 2 mit ± 5 pp Toleranzband → «zurückhaltend / realistisch / zu optimistisch». Darstellung: Fall 1 prominent mit Urteil, Fall 3+4 als Satzpaar untergeordnet, Fall 2 und die Mediane klein als Referenz. Der Drei-Wege-Umschalter aus 0.16 entfällt; historische Mittelwerte **und** Zielbetrag sind jetzt beide Pflicht. Technisch: `MonteCarloResult.finalWealthSorted` (alle Endvermögen sortiert) plus neuer Helfer `probabilityAtLeast` (Binärsuche) – vier Zahlen aus zwei Läufen statt vier Läufen. Kapitel 4.12.7 und 9.26 neu gefasst; 3 Tests ergänzt (121 → 124). Keine Änderung am Rechenkern. |
|
||||
| 0.16 | 2026-07-19 | Claude (Opus 4.8) | **Monte-Carlo mit zwei Fragestellungen** (Roadmap Nr. 46). Ein Umschalter oben trennt: **«Planung prüfen»** (Fall 1, wie bisher) würfelt um die **historischen** Renditen und prüft gegen den **Planungs-Endbetrag** (read-only) – «wie realistisch ist meine Planung?». **«Ziel prüfen»** (Fall 2, neu) würfelt um die **geplanten** Werte aus dem Plan und prüft gegen einen **manuellen Zielbetrag** – «erreiche ich mein Ziel?»; hier sind keine historischen Mittelwerte nötig, nur die Streuung. **«Beides»** rechnet beide Durchgänge gleichzeitig (gemeinsamer Seed). Neue Ergebnis-**Deutungstexte** je Fall (weich formuliert wegen des Volatilitäts-Drags). Der Fächer stammt aus dem historischen Durchgang; der Zielbetrag ist in Fall 2 **einer für alle** Szenarien. `runMonteCarloMulti` nimmt neu die Inflation **je Szenario** (`inflationMeanFor`); neuer Helfer `plannedReturnOf`. Kapitel 4.12 überarbeitet, 4.12.7 und 9.26 neu; 8 Tests ergänzt (119 → 121). Nebenbei eine vom Umlaut-Sweep verstümmelte Hex-Farbe (`#7c3aed`) korrigiert. Keine Änderung am Rechenkern. |
|
||||
| 0.15 | 2026-07-19 | Claude (Opus 4.8) | **Plan-Assistent überarbeitet** (Schritt 2 und 4). Rein an der Oberfläche, keine Änderung an Berechnung, Datenmodell oder API. **(Schritt 2 – Lebensphasen):** Die Lebenslinie zerfällt neu an den **fixen Pensionierungszeitpunkten** in Abschnitte (neues reines Modul `phaseplan.ts`, `planSegments`): Erwerb (alle arbeiten), Misch (eine pensioniert, eine arbeitet), Pension (alle pensioniert) – jeweils mit **kurzer Definition**. In den durch eine Pensionierung **fest begrenzten** Abschnitten verteilt der Nutzer beliebig viele Phasen mit **+/Papierkorb** und **eigenem Namen je Phase**; eine Live-Summe erzwingt, dass die Phasendauern exakt aufgehen («Weiter» ist bis dahin gesperrt). Der letzte Pensions-Abschnitt ist **offen** (Lebensdauer frei). Die Anzahl Abschnitte wird **abgeleitet** – Einzelplan: 2 (Erwerb, Pension); Paar mit unterschiedlichem Pensionsalter: 3. Neue **Zeitachse** mit Pensionierungs-Flaggen und nummerierter Beschriftung **unter** dem Balken (auch kurze Phasen bleiben lesbar). Behebt den Fehler, dass die Erwerbsphase zuvor beliebig über die Pensionierung hinaus gesetzt werden konnte. **(Schritt 4 – Vorsorge & Vermögen):** bei Paaren aufgeteilt in **Gemeinsam / Person A / Person B**; PK und 3a sind je Person, Wertschriften/Wohneigentum/Schulden je Bereich (gemeinsam oder pro Person). Neue Kapitel 3.2.8 überarbeitet; 8 Tests ergänzt (111 → 119). |
|
||||
@@ -2289,6 +2290,90 @@ Serverergebnis.
|
||||
|
||||
Referenz: `src/lib/calculations.ts`, `src/components/DetailView.tsx`.
|
||||
|
||||
## 4.15 Live-Simulation (Was-wäre-wenn-Regler)
|
||||
|
||||
Roadmap Nr. 22. Beantwortet weder «welche Annahme entscheidet» (das ist der Tornado,
|
||||
[4.13](#413-sensitivitätsanalyse-tornado)) noch «wie wahrscheinlich ist das» (das ist
|
||||
Monte-Carlo, [4.12](#412-monte-carlo-simulation)), sondern schlicht: **«Wie sieht mein Plan aus,
|
||||
wenn ich hier drehe?»** – sofort, und ohne für jede Variante eine Szenario-Kopie anzulegen.
|
||||
|
||||
Eigener Button **«Live-Simulation»** in der Szenario-Leiste, Dialog als Zweispalter: links die
|
||||
Regler, rechts die Grafik, darüber eine Kennzahlenleiste.
|
||||
|
||||
### 4.15.1 Keine eigene Rechenlogik
|
||||
|
||||
Die Regler benutzen **dieselben Transformationen wie der Tornado** (`applyDriver`). Damit kann
|
||||
die Live-Simulation gar nicht etwas anderes zeigen als die Einflussfaktoren-Analyse – beide
|
||||
bewegen den Plan identisch. Es entsteht kein zweiter, potenziell abweichender Rechenweg.
|
||||
|
||||
Neu hinzu kommt nur `applyElementDriver(plan, elementId, deltaPp)`: dieselbe Verschiebung, aber
|
||||
auf **ein einzelnes** Element statt auf eine ganze Kategorie (bei Immobilien auf `valueGrowth`
|
||||
statt `expectedReturn`). Element-IDs sind innerhalb eines Szenarios eindeutig; die
|
||||
Herkunfts-Verkettung `sourceElementId` aus der Monte-Carlo-Simulation braucht es hier **nicht**,
|
||||
weil die Live-Simulation immer nur auf **einem** Szenario läuft.
|
||||
|
||||
### 4.15.2 Sammelregler und Aufschlüsselung
|
||||
|
||||
Standardmässig gibt es **einen** Rendite-Regler für alle Anlagen – das hält das Panel ruhig und
|
||||
entspricht dem Tornado. Ein Klick auf **«Renditen einzeln aufschlüsseln»** ersetzt ihn durch je
|
||||
einen Regler pro renditetragendem Element (PK, 3a, Sonstiges Vermögen, Immobilie). Erst dann
|
||||
lässt sich die eigentliche Spielfrage stellen: *Was, wenn mein ETF schlechter läuft, die PK aber
|
||||
wie geplant?*
|
||||
|
||||
Der Sammelregler wird beim Aufklappen **entfernt**, nicht bloss ergänzt – sonst würde eine
|
||||
Bewegung doppelt zählen. Aus demselben Grund werden die Rendite-Regler beim Umschalten
|
||||
zurückgesetzt. Ein Test sichert ab, dass beide Ansichten denselben Plan beschreiben: Alle
|
||||
Elemente einzeln um +1 pp zu heben ergibt exakt dasselbe Endvermögen wie der Sammelregler auf
|
||||
+1 pp.
|
||||
|
||||
**Bewusst nicht aufschlüsselbar sind Ausgaben und Einkommen.** «Alle Ausgaben ±20 %» ist die
|
||||
Frage, die man tatsächlich stellt; Ausgaben-Elemente sind typischerweise viele kleine Posten,
|
||||
deren Einzelregler das Panel fluten würden, ohne eine bessere Frage zu ermöglichen.
|
||||
|
||||
**Das Pensionsalter fehlt weiterhin** – aus demselben Grund wie beim Tornado
|
||||
([9.18](#918-tornado-was-der-chart-nicht-leistet)): Es liesse sich nicht verschieben,
|
||||
ohne die Phasengrenzen mitzuziehen. Ersatzweise gibt es **Lebensdauer** (letzte Phase
|
||||
verlängern/verkürzen), was das Langlebigkeitsrisiko abdeckt, nicht aber die Frühpensionierung.
|
||||
|
||||
### 4.15.3 Referenz und Kennzahlen
|
||||
|
||||
Eine wandernde Linie ohne Anker ist wertlos – «ist 2.9 Mio jetzt viel oder wenig?». Deshalb:
|
||||
|
||||
- Der **unveränderte Plan** wird im Vermögensverlauf als blasse Referenzlinie mitgezeichnet.
|
||||
- Darüber steht eine **Kennzahlenleiste** mit Endvermögen nominal und real, jeweils mit der
|
||||
Differenz zum Plan (`3'660'683 → 2'880'100, −780'583`).
|
||||
- Eine dritte Karte meldet, ob das **Kapital reicht** oder in welchem Alter es aufgebraucht ist.
|
||||
Das ist die wichtigste Einzelinformation und einer Verlaufslinie nicht zuverlässig anzusehen:
|
||||
Ein Plan kann optisch plausibel aussehen und trotzdem zwischendurch unter null fallen.
|
||||
|
||||
Die Grafik zeigt **wann** sich etwas ändert, die Leiste **wie viel**.
|
||||
|
||||
Rechts stehen drei Grafiken zur Wahl: **Vermögensverlauf** (mit Referenzlinie),
|
||||
**Vermögensaufteilung** je Phase und **Einkommen vs. Ausgaben**. Die beiden letzteren zeigen nur
|
||||
den simulierten Stand – ein zweiter gestapelter Balkensatz wäre nicht mehr lesbar; darauf weist
|
||||
der Dialog hin.
|
||||
|
||||
### 4.15.4 Laufzeit: synchron, ohne Debounce
|
||||
|
||||
Gemessen an einem Plan über 60 Jahre mit 10 Elementen braucht `computePlan` rund **0.2 ms**.
|
||||
Bei 60 fps stehen 16 ms je Bild zur Verfügung – die Rechnung kostet also etwa **1 %** des
|
||||
Budgets. Deshalb wird bei **jeder** Reglerbewegung synchron neu gerechnet: kein Debounce, kein
|
||||
Web Worker, keine Ladeanzeige. Der Engpass ist das Neuzeichnen der Grafik, nicht die Mathematik.
|
||||
|
||||
### 4.15.5 Nichts wird gespeichert
|
||||
|
||||
Die Live-Simulation **schreibt nicht** – keine API, keine Datenbank, kein Schreibpfad. Genau das
|
||||
ist der Punkt der Roadmap-Anforderung («ohne für jede Variante eine Szenario-Kopie anzulegen»).
|
||||
|
||||
Ein **«Als neues Szenario speichern»** ist bewusst **noch nicht** umgesetzt: Reglerwerte in echte
|
||||
Element- und Phasenwerte zurückzuschreiben hiesse viele einzelne Schreibvorgänge und einen neuen
|
||||
Schreibpfad – eine eigene Ausbaustufe. Als Behelf zeigt der Dialog die **aktive Einstellung** als
|
||||
lesbare Zeile («Rendite −1.5 pp · Ausgaben +10 % · Lebensdauer +5 J.»), die sich von Hand in ein
|
||||
echtes Szenario übertragen lässt.
|
||||
|
||||
Referenz: `src/lib/livesim.ts`, `src/lib/sensitivity.ts` (`applyElementDriver`,
|
||||
`tunableElements`), `src/components/LiveSimDialog.tsx`, `src/components/AllocationChart.tsx`.
|
||||
|
||||
---
|
||||
|
||||
# 5. Technische Spezifikation
|
||||
@@ -2361,7 +2446,8 @@ PlanComputed ← an den Client geliefert
|
||||
| `types.ts` | Domänentypen für API und Berechnung |
|
||||
| `constants.ts` | Schweizer Systemparameter |
|
||||
| `montecarlo.ts` | Monte-Carlo-Simulation (Sampler + Treiber), Szenario-Vergleich und Element-Gruppierung über die Herkunfts-Kette. Keine I/O, läuft im Browser. |
|
||||
| `sensitivity.ts` | Sensitivitätsanalyse / Tornado: Treiber-Katalog, Parameter-Transformationen, `computeTornado`. Rein, läuft im Browser. |
|
||||
| `sensitivity.ts` | Sensitivitätsanalyse / Tornado: Treiber-Katalog, Parameter-Transformationen, `computeTornado`; zusätzlich `applyElementDriver` / `tunableElements` für die einzeln regelbaren Element-Renditen. Rein, läuft im Browser. |
|
||||
| `livesim.ts` | Live-Simulation: Reglerkatalog mit Standardbereichen, Anwenden mehrerer Regler, Kennzahlen (Kap. 4.15). Rein, läuft im Browser. |
|
||||
| `distribution.ts` | Kapitaltopf und Quoten-Zerlegung, Anwenden von Entwurfswerten für die Verteil-Werkzeuge (Kap. 3.6.9/3.6.10). Rein. |
|
||||
| `phaseplan.ts` | Ableitung der Lebensabschnitte (Erwerb/Misch/Pension) aus den fixen Pensionierungszeitpunkten für den Assistenten (Kap. 3.2.8). Rein. |
|
||||
| `constants.ts` (erweitert) | zusätzlich `SYSTEM_PARAMETERS`: dieselben Werte maschinenlesbar mit Bedeutung, Herleitung, Quelle und Stand – Grundlage der Systemparameter-Ansicht |
|
||||
@@ -2619,6 +2705,8 @@ wird der Plan neu geladen; die Berechnung kommt immer vom Server.
|
||||
| `PhaseDetail` | 95 | Phase bearbeiten/löschen |
|
||||
| `SparquoteChart` | 64 | Einkommen/Ausgaben pro Jahr mit Spar-/Verzehrbändern |
|
||||
| `MonteCarloDialog` | ~600 | Monte-Carlo-Dialog: Erklärung, Szenario-Auswahl, Eingaben, Lauf, Vergleichstabelle + Fächer |
|
||||
| `LiveSimDialog` | ~330 | Live-Simulation: Regler links, Grafikwahl rechts, Kennzahlenleiste mit Differenz zum Plan ([4.15](#415-live-simulation-was-wäre-wenn-regler)) |
|
||||
| `AllocationChart` | ~80 | Gestapelte Vermögensaufteilung je Phase; aus dem Dashboard herausgelöst, damit die Live-Simulation sie mitbenutzen kann |
|
||||
| `SensitivityDialog` | ~330 | Einflussfaktoren: Erklärung, Treiber-Auswahl mit Bandbreiten, Tornado + Tabelle |
|
||||
| `DetailView` | ~470 | Detailansichten Element/Phase, Wasserfall-Darstellung, Rechenweg-Blöcke, plan-weite Rechenwege |
|
||||
| `SystemParametersView` | ~90 | Systemparameter mit Wert, Bedeutung, Herleitung, Quelle und Stand |
|
||||
@@ -2861,11 +2949,12 @@ Include `src/**/*.test.ts`). Es gibt **keine** Komponenten-, API- oder E2E-Tests
|
||||
| `explain.test.ts` | 12 | Verlaufswerte je Element, Vollständigkeit beider Wasserfall-Zerlegungen, Rechenweg-Protokoll, Gültigkeit der Spezifikations-Verweise |
|
||||
| `montecarlo.test.ts` | 18 | Determinismus, Volatilität/Vol-Drag, Böden, Reproduzierbarkeit; Element-Gruppierung, Szenario-Vergleich, Inflation je Szenario, plannedReturnOf; Schwellen-Ablesung (probabilityAtLeast), Monotonie über Schwellen, Fall 2 systematisch unter 50 % |
|
||||
| `distribution.test.ts` | 8 | Kapitaltopf und Quoten-Zerlegung gegen die Cash-Brücke; Entwurfswerte anwenden ohne Verlust bestehender Felder |
|
||||
| `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 |
|
||||
| `phaseplan.test.ts` | 8 | Ableitung der Lebensabschnitte aus den Pensionierungszeitpunkten (Einzel/Paar/bereits pensioniert); letzter Teil immer offen |
|
||||
| `bridges.test.ts` | 10 | Vermögens- und Cash-Brücke gehen über sieben Plankonstellationen ohne Restgrösse auf; Umbuchungen bleiben aus der Vermögensbrücke heraus |
|
||||
| `diff.test.ts` | 9 | Abweichungs-Erkennung gegen das Eltern-Szenario |
|
||||
| `migrations.test.ts` | 1 | spielt alle Migrationen gegen echtes PostgreSQL (PGlite) ein |
|
||||
| **Total** | **124** | |
|
||||
| **Total** | **139** | |
|
||||
|
||||
## 8.2 Testfälle
|
||||
|
||||
@@ -3301,6 +3390,32 @@ dass die Simulation Risiko **um deine Annahmen** misst und nicht deren Richtigke
|
||||
([9.15](#915-monte-carlo-misst-risiko-um-die-annahmen-nicht-deren-richtigkeit)), bleibt bestehen.
|
||||
|
||||
|
||||
## 9.27 Warum die Regler Standardbereiche haben – und der Tornado nicht
|
||||
|
||||
Zwei Kapitel dieser Spezifikation scheinen sich zu widersprechen:
|
||||
[9.18](#918-tornado-was-der-chart-nicht-leistet) begründet, warum die
|
||||
Sensitivitätsanalyse **bewusst keine** Default-Bandbreiten anbietet, während die Live-Simulation
|
||||
([4.15](#415-live-simulation-was-wäre-wenn-regler)) für jeden Regler einen vorbelegten Bereich
|
||||
mitbringt. Das ist kein Versehen.
|
||||
|
||||
**Beim Tornado bestimmt die Bandbreite das Ergebnis.** Die Balkenlänge ist die Spannweite
|
||||
zwischen dem tiefen und dem hohen Wert – wer «Rendite ±3 pp» gegen «Ausgaben ±5 %» stellt,
|
||||
erzeugt eine Rangfolge, die er selbst vorgegeben hat. Ein Default wäre dort eine **frei erfundene
|
||||
Aussage**: Das Werkzeug würde behaupten, ein Treiber sei wichtiger als ein anderer, obwohl der
|
||||
Unterschied nur aus den voreingestellten Bereichen stammt. Deshalb ist die Bandbreite dort
|
||||
Pflichteingabe ohne Vorschlag.
|
||||
|
||||
**Ein Regler vergleicht nichts.** Er zeigt genau einen Zustand: «bei dieser Rendite kommt dieses
|
||||
Endvermögen heraus». Der Bereich bestimmt nur, wie weit sich der Schieber bewegen lässt – er
|
||||
verändert das angezeigte Ergebnis an keiner Stelle. Ein Standardbereich erfindet hier also keine
|
||||
Aussage; er macht den Regler überhaupt erst bedienbar, denn ohne Ober- und Untergrenze gibt es
|
||||
keinen Schieber.
|
||||
|
||||
Die Bereiche sind trotzdem **an beiden Enden editierbar** (Häkchen «Bereiche anpassen»), und
|
||||
neben jedem Regler steht sein **Neutralpunkt** – der Wert, bei dem der Plan unverändert bleibt.
|
||||
Bei allen Verschiebungs-Treibern ist das 0, bei der Inflation der Planwert selbst, weil sie als
|
||||
einzige absolut und nicht als Differenz eingegeben wird.
|
||||
|
||||
---
|
||||
|
||||
# 10. Glossar
|
||||
|
||||
Reference in New Issue
Block a user