Das Pensionsalter laesst sich neu veraendern: Nicht das Alter wird gesetzt, sondern die Phasengrenze verschoben -- Vorphase laenger, Folgephase kuerzer, Gesamtdauer gleich. Faellt eine Phase dabei weg, werden die beiden Uebergaenge nach Bestaetigung zusammengelegt. - neues reines Modul lib/retirement.ts + POST /api/scenarios/<id>/retirement - Pensionsalter als Tornado-Treiber und Live-Simulations-Regler - AHV-Referenzalter 65: Rente ab 65 unabhaengig vom Pensionsalter; vor 65 Beitrag als Nichterwerbstaetige(r) (neues Feld ahvContribution). AHV wird dafuer jahresweise statt phasenweise gerechnet. - Punkt B: personenzugeordnetes Einkommen faellt bei Pensionierung auf 0 - Punkt A: Wiederkehr-Parameter werden live aus der Vorphase geerbt, sichtbar als "Aus Vorphase uebernehmen" - Punkt C: Kapitalzufluss am Pensions-Uebergang per Quote auf Amortisation, Anlage und Cash verteilbar - Fix: carry.flowBasis wurde vor der Jahresschleife berechnet, effektive Werte kamen deshalb nie in der Folgephase an SPEZIFIKATION 0.26 (neue Kapitel 3.12, 4.4.7, 4.16). 221 -> 261 Tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,9 @@ export interface PhaseData {
|
||||
teuerungsausgleich?: number;
|
||||
// AHV
|
||||
gapYears?: number;
|
||||
// Nur AHV, nur in Phasen NACH einer Fruehpensionierung: jaehrlicher Beitrag als
|
||||
// Nichterwerbstaetige(r) bis zum Referenzalter. Faellt als Kosten an (Kap. 4.4.6).
|
||||
ahvContribution?: number;
|
||||
// AHV, nur wenn die Person bei Planbeginn BEREITS pensioniert ist (dann gibt es keinen
|
||||
// Pensions-Übergang, an dem die Karriere geprüft werden könnte): Beitragskarriere.
|
||||
// avgIncomeBefore ist REAL (heutige Kaufkraft).
|
||||
@@ -135,6 +138,14 @@ export interface CashTransitionData {
|
||||
// Einmalige Kosten (z. B. Poolbau): REAL erfasst (heutige Kaufkraft).
|
||||
outflowLabel?: string;
|
||||
outflowAmount?: number;
|
||||
// Verwendung des Kapitalzuflusses am Pensions-Übergang (Roadmap Nr. 44, Punkt C).
|
||||
// Bewusst in PROZENT und nicht in Franken: Verschiebt man das Pensionsalter, ändert sich
|
||||
// das bezogene Kapital -- eine Franken-Angabe müsste man dann von Hand nachziehen, eine
|
||||
// Quote skaliert mit. Der nicht zugeteilte Rest bleibt Cash.
|
||||
capitalUseAmortizationPct?: number;
|
||||
capitalUseInvestPct?: number;
|
||||
// Ziel der Anlage-Quote; ohne Angabe das erste aktive «Sonstiges Vermögen».
|
||||
capitalUseTargetElementId?: string;
|
||||
}
|
||||
|
||||
// --- Zod-Schemas (nachsichtig: unbekannte Felder werden verworfen) ---
|
||||
@@ -149,6 +160,9 @@ export const cashTransitionSchema = z
|
||||
inflowTaxRate: z.number().min(0).max(100).optional(),
|
||||
outflowLabel: z.string().max(120).optional(),
|
||||
outflowAmount: nonNeg.optional(),
|
||||
capitalUseAmortizationPct: z.number().min(0).max(100).optional(),
|
||||
capitalUseInvestPct: z.number().min(0).max(100).optional(),
|
||||
capitalUseTargetElementId: z.string().max(60).optional(),
|
||||
})
|
||||
.strip();
|
||||
|
||||
@@ -157,6 +171,7 @@ export const phaseDataSchema = z
|
||||
amount: nonNeg.optional(),
|
||||
teuerungsausgleich: z.number().min(-20).max(50).optional(),
|
||||
gapYears: z.number().int().min(0).optional(),
|
||||
ahvContribution: nonNeg.optional(),
|
||||
avgIncomeBefore: nonNeg.optional(),
|
||||
gapYearsBefore: z.number().int().min(0).max(50).optional(),
|
||||
currentValue: nonNeg.optional(),
|
||||
@@ -198,3 +213,41 @@ export const transitionDataSchema = z
|
||||
export function num(value: number | undefined | null, fallback = 0): number {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
||||
}
|
||||
|
||||
// --- Vererbung der Wiederkehr-Parameter (Roadmap Nr. 44, Punkt A) -----------------------
|
||||
//
|
||||
// Raten, Beitraege, Amortisation, Wertsteigerung und Zinssatz gelten weiter, bis man sie
|
||||
// bewusst aendert: Fehlt der Wert in einer Phase, gilt der aus der Vorphase. Genau diese
|
||||
// Regel wendet auch `computePlan` an -- hier steht sie fuer das UI, damit die Anzeige
|
||||
// "Aus Vorphase uebernehmen: <Wert>" nicht raten muss.
|
||||
export const INHERITABLE_KEYS = [
|
||||
"teuerungsausgleich",
|
||||
"expectedReturn",
|
||||
"annualContribution",
|
||||
"annualWithdrawal",
|
||||
"amortization",
|
||||
"valueGrowth",
|
||||
"interestRate",
|
||||
"annualRepayment",
|
||||
] as const;
|
||||
|
||||
export type InheritableKey = (typeof INHERITABLE_KEYS)[number];
|
||||
|
||||
// Wert, der in `phaseId` gelten wuerde, wenn das Feld dort leer bleibt. `orderedPhaseIds`
|
||||
// muss in Phasenreihenfolge vorliegen.
|
||||
export function inheritedPhaseValues(
|
||||
phaseValues: Record<string, PhaseData>,
|
||||
orderedPhaseIds: string[],
|
||||
phaseId: string
|
||||
): Record<string, number> {
|
||||
const out: Record<string, number> = {};
|
||||
for (const id of orderedPhaseIds) {
|
||||
if (id === phaseId) break;
|
||||
const pd = phaseValues[id] ?? {};
|
||||
for (const key of INHERITABLE_KEYS) {
|
||||
const v = pd[key];
|
||||
if (typeof v === "number") out[key] = v;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user