Fundament: Element-Stammdaten, Fixpunkte, Horizont in Jahren, PK-Bezugsalter

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 22:06:04 +02:00
parent cc61a120ee
commit 2f762175d2
17 changed files with 515 additions and 146 deletions
+21 -22
View File
@@ -49,18 +49,24 @@ export function sortedPhases(plan: PlanInput) {
// --- Planungshorizont -----------------------------------------------------------------------
//
// Bis 0.33 ergab sich das Planende stillschweigend als Summe der Phasendauern. Zwei Szenarien
// Bis 0.35 ergab sich das Planende stillschweigend als Summe der Phasendauern. Zwei Szenarien
// konnten dadurch unbemerkt verschieden weit rechnen -- und waren dann nicht vergleichbar,
// obwohl genau das ihr Zweck ist. Neu ist der Horizont eine erfasste Zahl, und die LETZTE
// Phase folgt ihr (dieselbe Richtung wie beim Pensionsalter: Zahl stellen, Struktur folgt).
// obwohl genau das ihr Zweck ist.
//
// Referenzperson ist die aelteste: Sie erreicht ihren Horizont zuerst, und der Plan muss so
// lange laufen, bis die LETZTE Person ihren erreicht hat.
// Seit 0.36 ist der Horizont eine Zahl in JAHREN am Szenario. Vorher stand er als Endalter je
// PERSON -- bei einem Paar zwei Zahlen, die auseinanderlaufen konnten und aus denen sich das
// Planende erst per Maximum ergab. Eine Laufzeit ist eine Eigenschaft der PLANUNG, nicht der
// Person; die Endalter sind die Ableitung davon, nicht umgekehrt.
export function planEndYear(plan: PlanInput): number | null {
const years = plan.persons
.filter((p) => typeof p.planningHorizonAge === "number")
.map((p) => (p.planningHorizonAge as number) - p.age);
return years.length > 0 ? Math.max(...years) : null;
const y = plan.planningHorizonYears;
return typeof y === "number" && y > 0 ? y : null;
}
// Alter, das eine Person am Ende der Planung erreicht. Read-only-Anzeige neben dem Horizont.
export function endAgeOf(plan: PlanInput, role: PersonRole): number | null {
const years = planEndYear(plan);
const person = plan.persons.find((p) => p.role === role);
return years !== null && person ? person.age + years : null;
}
export interface HorizonChange {
@@ -70,23 +76,15 @@ export interface HorizonChange {
blocked: string | null;
}
// Was muesste an der letzten Phase geschehen, damit der Plan bis zum Horizont laeuft?
export function planHorizonChange(plan: PlanInput, horizonAge: number, role: PersonRole): HorizonChange | null {
// Was muesste an der letzten Phase geschehen, damit der Plan genau bis zum Horizont laeuft?
// Dieselbe Richtung wie beim Pensionsalter: Zahl stellen, Struktur folgt.
export function planHorizonChange(plan: PlanInput, horizonYears: number): HorizonChange | null {
const phases = sortedPhases(plan);
const last = phases[phases.length - 1];
if (!last) return null;
const person = plan.persons.find((p) => p.role === role);
if (!person) return null;
// Zielgesamtdauer aus SICHT DIESER Person; die uebrigen Horizonte bleiben unberuehrt und
// koennen laenger sein -- deshalb das Maximum ueber alle.
const wish = Math.max(
horizonAge - person.age,
...plan.persons.filter((p) => p.role !== role && typeof p.planningHorizonAge === "number")
.map((p) => (p.planningHorizonAge as number) - p.age)
);
const before = phases.slice(0, -1).reduce((s, p) => s + p.durationYears, 0);
const newDuration = wish - before;
const newDuration = horizonYears - before;
const change: HorizonChange = {
lastPhaseId: last.id,
oldDuration: last.durationYears,
@@ -94,7 +92,8 @@ export function planHorizonChange(plan: PlanInput, horizonAge: number, role: Per
blocked: null,
};
if (newDuration < 1) {
change.blocked = `Der Horizont liegt vor dem Ende der zweitletzten Lebensphase. Die letzte Phase muss mindestens ein Jahr dauern kürze zuerst eine frühere Phase.`;
change.blocked =
"Der Horizont liegt vor dem Ende der zweitletzten Lebensphase. Die letzte Phase muss mindestens ein Jahr dauern kürze zuerst eine frühere Phase oder verlängere den Horizont.";
}
return change;
}