Plan-Assistent: abschnittsbasierte Phasenplanung + Vermoegen pro Person
Deploy App / deploy (push) Successful in 58s
Deploy App / deploy (push) Successful in 58s
Rein an der Oberflaeche, keine Aenderung an Berechnung, Datenmodell oder API. Schritt 2 (Lebensphasen) an den fixen Pensionierungszeitpunkten ausgerichtet: - neues reines Modul phaseplan.ts (planSegments) leitet die Abschnitte ab: Erwerb (alle arbeiten) / Misch (eine pensioniert, eine arbeitet) / Pension (alle pensioniert), je mit kurzer Definition - feste Abschnitte (durch Pensionierung begrenzt): beliebig viele Phasen mit +/Papierkorb und eigenem Namen; Live-Summe erzwingt exaktes Aufgehen, "Weiter" bis dahin gesperrt - offener Pensions-Abschnitt: Dauer frei - Anzahl Abschnitte wird abgeleitet (Einzel: 2, Paar unterschiedl. Ret: 3, bereits pensioniert: Misch/Pension zuerst) - neue Zeitachse mit Pensionierungs-Flaggen; Phasen nummeriert und UNTER dem Balken beschriftet (kurze Phasen bleiben lesbar) - behebt den Fehler, dass die Erwerbsphase beliebig ueber die Pensionierung hinaus gesetzt werden konnte Schritt 4 (Vorsorge & Vermoegen) bei Paaren aufgeteilt in Gemeinsam / Person A / Person B; PK und 3a je Person, Wertschriften/ Wohneigentum/Schulden je Bereich. 8 Tests (111 -> 119). SPEZIFIKATION auf 0.15 (Kap. 3.2.8 neu gefasst). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
// Ableitung der Lebensabschnitts-"Teile" aus den fixen Pensionierungszeitpunkten.
|
||||
//
|
||||
// Die Pensionsalter der Personen sind Fixpunkte auf der Lebenslinie. Zwischen Planbeginn und
|
||||
// diesen Punkten entstehen Abschnitte mit konstantem Erwerbsstatus -- Erwerb (alle arbeiten),
|
||||
// Misch (eine Person pensioniert, eine arbeitet) oder Pension (alle pensioniert). Genau diese
|
||||
// Abschnitte muss der Assistent respektieren: Eine Phase darf keinen Pensionierungspunkt
|
||||
// überspannen (die Berechnung leitet den Phasentyp am Phasenbeginn ab und kappt entsprechend,
|
||||
// siehe SPEZIFIKATION 2.3 / maxPhaseDuration).
|
||||
//
|
||||
// Reine Funktion, ohne UI und ohne I/O -- nur vom Assistenten verwendet.
|
||||
|
||||
export type SegmentType = "ERWERB" | "MIXED" | "PENSION";
|
||||
|
||||
export interface PlanSegment {
|
||||
type: SegmentType;
|
||||
fromYear: number; // Jahre ab Planbeginn (Beginn des Abschnitts)
|
||||
// Feste Länge in Jahren (durch den nächsten Pensionierungspunkt bestimmt) ODER null für den
|
||||
// letzten, OFFENEN Abschnitt (Pension bis Lebensende -- Dauer frei wählbar).
|
||||
fixedYears: number | null;
|
||||
}
|
||||
|
||||
// Ein Person-Erwerbsstatus wechselt genau einmal: im Jahr `retirementAge - age` (auf 0 gekappt,
|
||||
// falls bei Planbeginn bereits pensioniert). Die distinkten dieser Zeitpunkte sind die
|
||||
// Abschnittsgrenzen; der Abschnitt nach dem letzten Zeitpunkt ist offen.
|
||||
export function planSegments(persons: { age: number; retirementAge: number }[]): PlanSegment[] {
|
||||
if (persons.length === 0) return [{ type: "PENSION", fromYear: 0, fixedYears: null }];
|
||||
|
||||
const retYears = persons.map((p) => Math.max(0, p.retirementAge - p.age));
|
||||
const events = [...new Set(retYears.filter((r) => r > 0))].sort((a, b) => a - b);
|
||||
const boundaries = [0, ...events];
|
||||
|
||||
const segments: PlanSegment[] = [];
|
||||
for (let i = 0; i < boundaries.length; i++) {
|
||||
const from = boundaries[i];
|
||||
const next = i + 1 < boundaries.length ? boundaries[i + 1] : null;
|
||||
// Eine Person arbeitet in einem Abschnitt, der bei Jahr `from` beginnt, genau dann, wenn
|
||||
// ihr Pensionierungszeitpunkt echt später liegt.
|
||||
const working = retYears.filter((r) => r > from).length;
|
||||
const type: SegmentType = working === persons.length ? "ERWERB" : working > 0 ? "MIXED" : "PENSION";
|
||||
segments.push({ type, fromYear: from, fixedYears: next === null ? null : next - from });
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
// Vorgeschlagene Dauer des offenen Pensions-Abschnitts: bis das älteste Haushaltsmitglied
|
||||
// ungefähr 90 ist, mindestens aber 5 Jahre.
|
||||
export function defaultOpenDuration(persons: { age: number }[], fromYear: number): number {
|
||||
const oldestNow = persons.length > 0 ? Math.max(...persons.map((p) => p.age)) : 65;
|
||||
return Math.max(5, 90 - (oldestNow + fromYear));
|
||||
}
|
||||
Reference in New Issue
Block a user