Lesbare Wasserfaelle, Verkaufspreis-Abgleich, Erklaerung wirkungsloser Treiber
Deploy App / deploy (push) Successful in 59s

Die Wasserfall-Zahlen waren korrekt (residual exakt 0), die Darstellung nicht
lesbar. Neu als eigene liegende HTML/CSS-Darstellung statt Recharts:
- Verbindungslinien zwischen den Balken (ohne sie zerfaellt der Wasserfall in
  unverbundene Rechtecke)
- Wertbeschriftung an jedem Schritt
- Zwischenstand und Veraenderung optisch unterschieden
- Abschnitte "Am Uebergang" / "Innerhalb der Phase"
- aufklappbare Tabelle mit laufendem Zwischenstand
- Nullposten werden nicht gezeichnet
- Restposten neu als Fehlermeldung statt beilaeufiger Rundungsnotiz

Verkaufspreis einer Immobilie wird beim Wechsel auf "Verkaufen" mit dem
modellierten Verkehrswert vorbelegt (nur wenn noch keiner erfasst ist);
Verkehrswert und Abweichung werden ausgewiesen, ab 10 % rot abgesetzt. Die
beiden Groessen bleiben bewusst entkoppelt -- ein Verkauf unter Verkehrswert
ist ein realer Fall.

Tornado erklaert Nullbalken statt sie stumm zu zeigen. Wichtigster Fall: Wird
die Immobilie vor Planende verkauft, ist die Wertsteigerung nachweislich
wirkungslos, weil der Erloes am erfassten Verkaufspreis haengt und nicht am
Verkehrswert.

11 Tests ergaenzt (92 -> 103), darunter residual === 0 ueber sieben
Plankonstellationen. SPEZIFIKATION auf 0.12, neue Kapitel 3.5.8, 4.13.5,
4.14.2.1, 9.22. Keine Aenderung an der Berechnung.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 21:21:37 +02:00
parent 4791dccf93
commit e1f74fca95
8 changed files with 671 additions and 61 deletions
+108 -9
View File
@@ -4,10 +4,10 @@
| | |
|---|---|
| **Dokument** | Funktionale und Technische Spezifikation FPT |
| **Version** | 0.11 |
| **Version** | 0.12 |
| **Datum** | 2026-07-18 |
| **Status** | Lebendes Dokument |
| **Codestand** | Arbeitsstand nach `1836cad` inkl. Detailansichten, Wasserfall-Zerlegungen und vollständiger Rechenweg-Offenlegung (Branch `main`) |
| **Codestand** | Arbeitsstand nach `4791dcc` inkl. lesbarer Wasserfälle und Verkaufspreis-Abgleich (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.12 | 2026-07-18 | Claude (Opus 4.8) | **Lesbarkeit der Wasserfälle, Verkaufspreis-Abgleich und Erklärung wirkungsloser Tornado-Treiber.** (1) Die beiden Wasserfälle werden **nicht mehr mit Recharts** gezeichnet, sondern als eigene liegende Darstellung: Verbindungslinien zwischen den Balken, Wertbeschriftung an jedem Schritt, Abschnitts-Überschriften („Am Übergang" / „Innerhalb der Phase") und eine aufklappbare Tabelle mit **laufendem Zwischenstand**. Anlass war, dass die bisherige Darstellung faktisch nicht lesbar war die Zahlen waren korrekt, die Grafik nicht. (2) Der Restposten beider Brücken wird bei Abweichung neu als **Fehlermeldung** ausgewiesen statt als beiläufige „Rundungsdifferenz"; eine nicht aufgehende Zerlegung ist ein Rechenfehler und kein Schönheitsproblem. (3) **Verkaufspreis einer Immobilie** wird beim Wechsel auf „Verkaufen" neu mit dem **modellierten Verkehrswert** vorbelegt; der Dialog weist Verkehrswert und Abweichung aus und warnt ab 10 % Differenz (Kap. 3.5.8, 9.22). Damit fällt auf, wenn angenommene Wertsteigerung und erwarteter Verkaufspreis nicht zusammenpassen. (4) Der Tornado erklärt neu **Nullbalken** statt sie stumm zu zeigen insbesondere den Fall, dass die Immobilien-Wertsteigerung bei einem Verkauf nachweislich wirkungslos ist (`ineffectiveReason`, Kap. 4.13.5). Neue Kapitel 3.5.8, 4.13.5, 9.22; 11 Tests ergänzt (92 → 103), darunter die Invariante `residual === 0` über sieben Plankonstellationen. Keine DB-Änderung, keine Änderung an der Berechnung. |
| 0.11 | 2026-07-18 | Claude (Opus 4.8) | **Detailansichten (Roadmap Nr. 43)** und **vollständige Offenlegung der Berechnungslogiken (Roadmap Nr. 41)**. (1) Neue **Systemparameter-Ansicht** in der Seitenleiste: alle fest hinterlegten Grössen mit Wert, Bedeutung, Herleitung, Quelle und Stand als strukturierte Daten aus `constants.ts`, also aus derselben Quelle, aus der gerechnet wird. (2) **Nur-Lese-Detailansicht** je Element und je Lebensphase über ein Expand-Icon: Element mit Verlaufsgrafik über **alle Planjahre** (dafür führt `computePlan` neu `ElementPhaseComputed.yearly` je Element mit), Phase mit Vermögensaufteilung und **zwei Wasserfällen**. (3) Die **Wasserfälle** sind bewusst getrennt: Der Vermögens-Wasserfall zeigt nur echte Zu- und Abgänge (Quote, Kapitalerträge, Wertsteigerung, PK-Beiträge, Steuern, Verrentung, Einmalposten); Sparraten, Amortisationen und Investitionen sind **Umbuchungen** und erscheinen ausschliesslich im Cash-Wasserfall als Vermögensabgang gezeichnet würden sie einen Verlust vortäuschen, den es nicht gibt. Neue Strukturen `WealthBridge` / `CashBridge` inkl. Restposten als Kontrollgrösse. (4) **Rechenweg-Protokoll**: `computePlan(plan, sample?, { explain })` protokolliert die Schritte, die es ohnehin ausführt Formel, eingesetzte Zahlen, Ergebnis und Hinweis auf geltende Vereinfachungen. Abdeckung über **alle** Ebenen (Element je Phase, Element je Übergang, Phasen-Kennzahlen, Plan-Ebene). Standardmässig aus, damit die Monte-Carlo-Simulation unberührt bleibt. Jeder Rechenweg verlinkt in das passende Kapitel dieser Spezifikation; ein Test prüft, dass alle Verweise eine existierende Überschrift treffen. Neue Kapitel 3.6.7, 3.6.8, 4.14, 9.20, 9.21; 12 Tests ergänzt (80 → 92). Keine DB-Änderung; die 43 Golden Tests laufen unverändert. |
| 0.10 | 2026-07-18 | Claude (Opus 4.8) | **Szenario-Vergleich in der Monte-Carlo-Simulation** und **Sensitivitätsanalyse / Tornado** (Roadmap Nr. 20). (1) Die MC-Simulation rechnet neu **mehrere Szenarien desselben Plans in einem Lauf**. Die historischen Annahmen werden dabei nur **einmal je logischem Element** erfasst die Zuordnung über die Herkunfts-Kette `sourceElementId`, dieselbe Grundlage wie beim Diff (neue Funktionen `resolveRootElementId`, `buildElementGroups`, `paramsForScenario`, `runMonteCarloMulti`). Alle Szenarien laufen mit **demselben Seed** (Common Random Numbers), damit Unterschiede strukturell und nicht zufällig sind. Der **Zielbetrag bleibt szenario-eigen** (vorbelegt mit dem jeweils geplanten Endvermögen) nur so misst die Erfolgswahrscheinlichkeit, wie oft ein Szenario sein *eigenes* Versprechen hält. Ergebnis als Vergleichstabelle plus Median-Linien je Szenario; bei einem einzelnen Szenario unverändert der bisherige Fächer. (2) Neuer Bereich **Einflussfaktoren** (eigener Button, eigener Dialog) mit einem **Tornado-Chart** nach dem One-at-a-time-Verfahren: neues reines Modul `sensitivity.ts` mit sieben Treibern, je Treiber an-/abwählbar und mit **pflichtiger, frei definierbarer Bandbreite ohne Default**. Das **Pensionsalter ist bewusst nicht enthalten** (Begründung: 9.18). Neue Kapitel 3.6.6, 4.12.6, 4.13, 9.18; 20 Tests ergänzt (60 → 80). Keine DB-Änderung, keine Verhaltensänderung bestehender Pläne. **Ausserdem vier Dokumentationsfehler korrigiert:** Kap. 1.2 nannte noch Monte-Carlo, Hypothekarzinsen und Immobilien-Wertsteigerung als nicht umgesetzt (seit 0.5/0.7 vorhanden); Kap. 4.6.5 endete mit einem widersprüchlichen Restsatz zur fehlenden Wertsteigerung; Kap. 5.2/5.3/5.5.2/8.1 waren bei Migrationszahl, Dateiliste, Komponentenliste und Testzahlen veraltet; der Glossar-Eintrag „Szenario" beschrieb noch das Modell vor V6. |
| 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). |
@@ -733,6 +734,32 @@ beim Anlegen eines Elements (dort gibt es noch nichts zu überschreiben).
Referenz: `src/components/ElementDetail.tsx` (`CarryWarning`).
### 3.5.8 Verkaufspreis und modellierter Verkehrswert
Das Modell führt zwei Immobilienwerte getrennt: den **Verkehrswert**, der mit `valueGrowth`
wächst, und den **ursprünglichen Kaufpreis** als Basis der Grundstückgewinnsteuer
([4.6.5](#465-real_estate-immobilie)). Beim Verkauf zählt jedoch ausschliesslich der vom
Benutzer **erfasste Verkaufspreis** ([4.9.4](#494-real_estate)).
Daraus ergab sich eine stille Inkonsistenz: Man konnte 2 % jährliche Wertsteigerung annehmen
und die Immobilie trotzdem zum Kaufpreis verkaufen, ohne dass das Tool widersprach.
Deshalb gilt seit Version 0.12:
- Beim Wechsel auf **Verkaufen** wird der Verkaufspreis mit dem **modellierten Verkehrswert
am Phasenende** vorbelegt aber nur, wenn noch keiner erfasst ist (bestehende Pläne bleiben
unverändert).
- Der Dialog zeigt den Verkehrswert daneben read-only an und beziffert die **Abweichung** in
Franken und Prozent.
- Ab **10 %** Abweichung wird der Hinweis rot abgesetzt, mit der Aufforderung zu prüfen, ob
Wertsteigerungsannahme und erwarteter Verkaufspreis zusammenpassen.
Der erfasste Preis bleibt **massgebend** die Vorbelegung ist eine Hilfe, keine Bevormundung.
Ein bewusst abweichender Preis (Notverkauf, Liebhaberpreis, Verkauf an Nachkommen) bleibt
möglich. Die Berechnung ist unverändert.
Referenz: `src/components/ElementDetail.tsx` (`ElementTransitionFields`, `REAL_ESTATE`).
## 3.6 Auswertung und Visualisierung
### 3.6.1 Anzeigemodus nominal / beide / real
@@ -1806,6 +1833,26 @@ spannt deshalb über `min…max`; welche Eingabe zu welchem Ende gehört, zeigt
Referenz: `src/lib/sensitivity.ts`, `src/components/SensitivityDialog.tsx`.
### 4.13.5 Wirkungslose Treiber werden erklärt
Ein Balken mit Spannweite 0 ohne Erklärung ist die schlechteste Antwort der Benutzer hält
ihn für einen Fehler. `computeTornado` hängt deshalb an jeden Nullbalken eine Begründung
(`ineffectiveReason`).
Der wichtigste Fall ist die **Immobilien-Wertsteigerung bei einem Verkauf**. Der Verkaufserlös
ist `Verkaufspreis Hypothek Grundstückgewinnsteuer` und hängt damit am erfassten
Verkaufspreis, **nicht** am modellierten Verkehrswert. Wird die Immobilie vor Planende
verkauft, wird die aufgelaufene Wertsteigerung an dieser Stelle verworfen der Treiber kann
das Endvermögen dann rechnerisch nicht mehr beeinflussen.
Erkannt wird das daran, dass in der letzten Phase **alle** `REAL_ESTATE`-Elemente den Status
`SOLD` tragen. Andernfalls greift ein allgemeiner Hinweis. Durch Tests abgedeckt: gehalten →
Spannweite > 0 ohne Hinweis; verkauft → Spannweite 0 mit Begründung.
Verwandt: Der Verkaufspreis-Abgleich im Übergangs-Dialog ([3.5.8](#358-verkaufspreis-und-modellierter-verkehrswert))
setzt an derselben Stelle an, nur früher er verhindert, dass die Annahmen überhaupt
auseinanderlaufen.
## 4.14 Verlaufswerte, Brücken und Rechenwege
Dieses Kapitel beschreibt, was `computePlan` über die reinen Ergebniswerte hinaus mitführt
@@ -1871,10 +1918,38 @@ Cash Ende Vorphase (Phase 1: Cash-Anfangswert)
```
Beide Strukturen führen einen **Restposten** (`residual`) mit: die Differenz zwischen dem
gerechneten Endwert und der Summe der Summanden. Er entsteht nur durch die Rundung der einzelnen
Posten auf ganze Franken und liegt im einstelligen Bereich; ein grösserer Wert wäre ein Hinweis
auf eine unvollständige Zerlegung. Zwei Tests prüfen ihn über einen Plan, der alle Element-Arten
und Übergangs-Entscheide enthält.
gerechneten Endwert und der Summe der Summanden. Er ist die eingebaute Selbstkontrolle ist die
Zerlegung vollständig und richtig, muss er **exakt 0** sein.
`src/lib/bridges.test.ts` nagelt das über **sieben Plankonstellationen** fest (Ansparen mit 3a
und Schuldentilgung, Pensionierung mit Verrentung und 3a-Bezug, PK-Kapitalbezug, Immobilie
gehalten, Immobilie verkauft, einmalige Sonderein-/ausgaben, Sofort-Tilgung mit
Sonderamortisation) je Phase für beide Brücken, zusätzlich der Abgleich der Kontrollpunkte
gegen `startWealthNominal` / `endWealthNominal` / `cashStart` / `cashEnd`.
Im UI wird ein Restposten über 2 Franken als **Fehlermeldung** ausgewiesen, nicht als beiläufige
Rundungsnotiz: Eine Brücke, die nicht aufgeht, ist ein Rechenfehler und kein Darstellungsproblem.
### 4.14.2.1 Darstellung der Wasserfälle
Die Wasserfälle werden **nicht mit Recharts** gezeichnet. Ein Wasserfall lebt von drei Dingen,
die dort nicht ohne Weiteres zu bekommen sind:
- **Verbindungslinien** zwischen den Balken ohne sie sieht man nicht, dass jeder Balken dort
ansetzt, wo der vorherige aufhört, und die Grafik zerfällt in unverbundene Rechtecke.
- **Wertbeschriftung** an jedem Schritt, statt Beträge aus der Achse zu schätzen.
- **Unterscheidung von Zwischenstand und Veränderung.** Ein Zwischenstand („Vermögen
Phasenbeginn") ist ein absoluter Wert ab Null, eine Veränderung („Kapitalerträge") setzt auf dem
laufenden Saldo auf. Sehen beide gleich aus, ist die Grafik nicht lesbar.
Die Darstellung ist deshalb eine eigene HTML/CSS-Konstruktion und **liegend** statt stehend die
Beschriftungen sind lang und müssten stehend gedreht werden; liegend ist es ausserdem konsistent
zum Tornado. Abschnitts-Überschriften trennen „Am Übergang in diese Phase" von „Innerhalb der
Phase". Posten mit Wert 0 werden gar nicht erst gezeichnet.
Darunter steht aufklappbar eine **Tabelle mit laufendem Zwischenstand**. Bei sieben bis zwölf
Schritten mit stark unterschiedlichen Grössenordnungen ist sie der Grafik schlicht überlegen
die Grafik zeigt das Verhältnis, die Tabelle die Zahl.
### 4.14.3 Rechenweg-Protokoll
@@ -2487,12 +2562,13 @@ Include `src/**/*.test.ts`). Es gibt **keine** Komponenten-, API- oder E2E-Tests
| Datei | Tests | Schwerpunkt |
|---|---|---|
| `calculations.test.ts` | 43 | AHV-Rentenformel, Immobilie, Teilverkauf, Sonderamortisation, AHV einkommensabhängig, „V5 Golden Tests" |
| `sensitivity.test.ts` | 14 | Treiber-Transformationen (Reinheit, Einheiten, Kappung), Verfügbarkeit, Tornado-Sortierung und -Richtung |
| `sensitivity.test.ts` | 15 | Treiber-Transformationen (Reinheit, Einheiten, Kappung), Verfügbarkeit, Tornado-Sortierung und -Richtung, Erklärung wirkungsloser Treiber |
| `explain.test.ts` | 12 | Verlaufswerte je Element, Vollständigkeit beider Wasserfall-Zerlegungen, Rechenweg-Protokoll, Gültigkeit der Spezifikations-Verweise |
| `montecarlo.test.ts` | 13 | Determinismus, Volatilität/Vol-Drag, Böden, Reproduzierbarkeit; Element-Gruppierung und Szenario-Vergleich |
| `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** | **92** | |
| **Total** | **103** | |
## 8.2 Testfälle
@@ -2528,6 +2604,11 @@ Include `src/**/*.test.ts`). Es gibt **keine** Komponenten-, API- oder E2E-Tests
| **Tornado: Lebensdauer** | verschiebt nur die letzte Phase; Kappung bei mindestens 1 Jahr |
| **Tornado: Verfügbarkeit** | Treiber ohne passende Elemente werden ausgeblendet (z. B. Immobilien-Wertsteigerung ohne Immobilie) |
| **Tornado: Sortierung/Richtung** | absteigend nach Spannweite; Treiber ohne Bandbreite hat Spannweite 0 und steht zuunterst; höhere Ausgaben → tieferes, höhere Rendite → höheres Endvermögen |
| **Tornado: wirkungslose Treiber** | jeder Nullbalken trägt eine Begründung; verkaufte Immobilie → Wertsteigerung wirkungslos mit konkretem Hinweis, gehaltene Immobilie → Spannweite > 0 ohne Hinweis |
| **Brücken: Restgrösse** | `residual === 0` je Phase für Vermögens- **und** Cash-Brücke über sieben Plankonstellationen (Ansparen, Verrentung, Kapitalbezug, Immobilie gehalten/verkauft, Einmalposten, Sofort-Tilgung mit Sonderamortisation) |
| **Brücken: Kontrollpunkte** | `startWealth`/`endWealth`/`cashStart`/`cashEnd` der Brücken stimmen mit den offiziellen Phasen-Kennzahlen überein |
| **Brücken: Umbuchungen** | Sparraten und Amortisationen erscheinen nur in der Cash-Brücke; die Vermögensänderung erklärt sich exakt aus Quote + Erträgen + Wertsteigerung + PK-Beiträgen |
| **Brücken: Verrentung/Verkauf** | verrentetes PK-Kapital erscheint als Vermögensabgang am Übergang; Verkaufsdifferenz und Grundstückgewinnsteuer nur beim Verkauf, nicht beim Halten |
| **Verlauf: ein Punkt je Jahr** | je aktivem Element genau `durationYears` Punkte pro Phase |
| **Verlauf: Konvexität** | 200'000 @ 5 % + 10'000 Sparbeitrag: Jahr 1 = 220'000, und die **Jahreszuwächse wachsen** eine Gerade zwischen den Phasengrenzen hätte konstante Zuwächse |
| **Verlauf: Immobilie** | Verkehrswert, Restschuld und Eigenkapital werden getrennt geführt; Eigenkapital = Verkehrswert Restschuld |
@@ -2835,6 +2916,24 @@ Konkret uneindeutig sind zwei Fälle:
Der Restposten (`residual`) ist die Kontrollgrösse dafür, dass die gewählte Zerlegung wenigstens
**vollständig** ist nicht dafür, dass sie die einzig sinnvolle ist.
## 9.22 Verkaufspreis und Verkehrswert bleiben unabhängig
Seit Version 0.12 wird der Verkaufspreis mit dem modellierten Verkehrswert vorbelegt und die
Abweichung ausgewiesen ([3.5.8](#358-verkaufspreis-und-modellierter-verkehrswert)). Die beiden
Grössen bleiben aber **entkoppelt** das Tool erzwingt keine Konsistenz.
Das ist bewusst so: Ein Verkauf unter dem Verkehrswert ist ein realer Fall (Notverkauf, Verkauf
an Nachkommen, Liebhaberobjekt ohne Markt). Eine Zwangskopplung würde diese Fälle unmöglich
machen. Der Preis dafür ist, dass eine unplausible Kombination weiterhin eingebbar bleibt neu
aber nicht mehr unbemerkt.
Eine Folge bleibt bestehen und ist nicht offensichtlich: **Wird die Immobilie vor Planende
verkauft, hat die angenommene Wertsteigerung keinen Einfluss mehr auf das Endvermögen.** Der
Erlös folgt allein dem erfassten Verkaufspreis. Im Tornado führt das zu einem Nullbalken, der
seit 0.12 erklärt wird ([4.13.5](#4135-wirkungslose-treiber-werden-erklärt)); in der
Vermögensbrücke erscheint stattdessen die Differenz `Verkaufspreis Verkehrswert` als eigener
Posten.
---
# 10. Glossar
@@ -2877,4 +2976,4 @@ Der Restposten (`residual`) ist die Kontrollgrösse dafür, dass die gewählte Z
---
*Ende der Spezifikation v0.11*
*Ende der Spezifikation v0.12*