V4-Rechenmodell: Flow-Indexierung, Cash-Ausgleichstopf, Ruin-Erkennung
Deploy App / deploy (push) Successful in 3m6s

Behebt die Nominal/Real-Inkonsistenz (Bug #1): Einkommen/Ausgaben werden neu Jahr fuer
Jahr indexiert (eigenes Feld teuerungsausgleich je Element, Default = Phaseninflation;
Renten nominal fix = 0%). Vermoegen verzinst weiterhin nominal.

Cash: neues systemseitiges, immer sichtbares Element (0% Verzinsung, kein DB-Row -
synthetisch in computePlan). Ist der Ausgleichstopf = "verfuegbares Kapital fuer
Investments": cash_delta(t) = quote(t) - geplante flache Jahresraten (3a/Vermoegen/Amort./
Tilgung); Cash laeuft ueber Phasen fort, darf negativ werden (rot). Der Verteilzwang und
die harten Sparraten-Caps entfallen.

Ruin: Gesamtvermoegen (inkl. Cash) je Jahr; erstes Unterschreiten von 0 -> Ruin-Alter
(Person A), Anzeige als Banner + Zeitachsen-Marker.

Phasenkopf neu: Einkommen/Ausgaben Start->Ende, Quote Beginn/Ende, Cash Start->Ende,
Vermoegen inkl. Cash, Realwert. Inflation-Deflator neu pro Jahr (Math.pow ^Dauer).

Golden Tests (vitest) 1-4 + Renten-0% + Fortschreibung gruen (Test1 761'654/565'928,
Test2 Ruin Alter 94).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 09:24:13 +02:00
parent 9506f43b67
commit 5e17ce4afb
10 changed files with 1349 additions and 299 deletions
+8
View File
@@ -52,6 +52,12 @@ export const CATEGORY_ORDER: ElementCategory[] = [
export interface PhaseData {
// INCOME / EXPENSE
amount?: number;
// INCOME / EXPENSE: jaehrlicher Teuerungsausgleich (%). Indexiert den Flow ueber die
// Phasenjahre (Jahr t = Basis x (1+idx)^(t-1)). Eigenes Feld je Element (Einkommen und
// Ausgaben unabhaengig). Default = Phaseninflation.
teuerungsausgleich?: number;
// INCOME / EXPENSE ab Phase 2: uebersteuert den fortgeschriebenen Basiswert dieser Phase.
amountOverride?: number;
// AHV
gapYears?: number;
// PENSION_FUND / PILLAR_3A / OTHER_ASSET
@@ -98,6 +104,8 @@ const nonNeg = z.number().min(0);
export const phaseDataSchema = z
.object({
amount: nonNeg.optional(),
teuerungsausgleich: z.number().min(-20).max(50).optional(),
amountOverride: nonNeg.optional(),
gapYears: z.number().int().min(0).optional(),
currentValue: nonNeg.optional(),
startValue: nonNeg.optional(),