V4-Rechenmodell: Flow-Indexierung, Cash-Ausgleichstopf, Ruin-Erkennung
Deploy App / deploy (push) Successful in 3m6s
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:
@@ -13,7 +13,15 @@ interface PersonAxis {
|
||||
|
||||
// Horizontale Zeitachse: Alter von links nach rechts, mit deutlich markiertem
|
||||
// Pensionsalter je Person und Trennlinien an den Phasengrenzen.
|
||||
export function Timeline({ phases, persons }: { phases: PhaseComputed[]; persons: PersonAxis[] }) {
|
||||
export function Timeline({
|
||||
phases,
|
||||
persons,
|
||||
ruinAge,
|
||||
}: {
|
||||
phases: PhaseComputed[];
|
||||
persons: PersonAxis[];
|
||||
ruinAge?: number | null;
|
||||
}) {
|
||||
if (phases.length === 0 || persons.length === 0) return null;
|
||||
|
||||
const totalYears = phases.reduce((s, p) => s + p.durationYears, 0);
|
||||
@@ -64,6 +72,20 @@ export function Timeline({ phases, persons }: { phases: PhaseComputed[]; persons
|
||||
) : null
|
||||
)}
|
||||
|
||||
{/* Ruin-Marker (Person A) */}
|
||||
{ruinAge != null && ruinAge > minAge && ruinAge <= maxAge && (
|
||||
<div
|
||||
className="absolute top-0 flex -translate-x-1/2 flex-col items-center"
|
||||
style={{ left: pct(ruinAge) }}
|
||||
title={`Kapital aufgebraucht mit Alter ${ruinAge}`}
|
||||
>
|
||||
<span className="text-[10px] font-semibold" style={{ color: "var(--danger)" }}>
|
||||
Ruin {ruinAge}
|
||||
</span>
|
||||
<div className="mt-0.5 h-4 w-px" style={{ backgroundColor: "var(--danger)" }} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Achse */}
|
||||
<div className="relative h-2 w-full rounded-full bg-gradient-to-r from-accent-soft to-accent">
|
||||
{boundaries.slice(1).map((b) => (
|
||||
|
||||
Reference in New Issue
Block a user