UI-Umbau: Analyse-Bereich, Planstart, Zeitachse mit Phasen
Deploy App / deploy (push) Successful in 1m44s
Deploy App / deploy (push) Successful in 1m44s
Sieben Anpassungen aus dem Feedback: - Grafiken liegen neu im eigenen Bereich "Grafiken" (Dialog, Button oben) statt unter der Matrix. Die Matrix bleibt die ruhige Hauptansicht. - Kennzahl "Geschaetzter Nachlass" entfernt -- sie war rechnerisch identisch mit dem nominalen Endvermoegen und suggerierte eine Zusatzinformation, die es nicht gab. - Monte-Carlo-Button nach oben zu den Szenario-Aktionen verschoben. - Neues Profilfeld "Planstart (Jahr)" (Scenario.startYear + Migration). Rein fuer die Darstellung -- die Berechnung rechnet unveraendert in relativen Jahren. Bestehende Szenarien werden auf das laufende Jahr gesetzt. - Zeitachse zeigt die Lebensphasen als Segmente (Breite = Dauer, Einfaerbung nach Phasentyp) inkl. Jahresspanne, plus Jahres-Beschriftung an den Enden. - Lebensphase bearbeiten neu als Popup statt Panel unter der Tabelle -- konsistent zu allen anderen Eingaben; Speichern schliesst. - Vermoegensverlauf ueber ALLE Jahre statt nur ueber die Phasengrenzen. Dafuer fuehrt computePlan das Vermoegen neu pro Jahr mit (YearPoint.wealthNominal/ wealthReal) -- dieselbe Groesse, die schon die Ruin-Erkennung berechnet. Damit werden Verlaeufe INNERHALB einer Phase sichtbar (z. B. Kapitalverzehr). Zwei Tests fuer das Jahres-Vermoegen (58 -> 60). Spezifikation auf v0.9. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -686,6 +686,33 @@ describe("V5 Golden Tests", () => {
|
||||
expect(r.nachlass).toBe(5000);
|
||||
});
|
||||
|
||||
it("Vermoegen wird pro JAHR mitgefuehrt (Grundlage des Verlaufs ueber alle Jahre)", () => {
|
||||
const p = plan({
|
||||
age: 40, retirementAge: 70, inflation: 0, initialCash: 0,
|
||||
phases: [{ id: "p1", durationYears: 3 }],
|
||||
elements: [el("OTHER_ASSET", "HOUSEHOLD", { p1: { startValue: 100000, expectedReturn: 10, annualContribution: 0 } })],
|
||||
});
|
||||
const r = computePlan(p);
|
||||
expect(r.yearly).toHaveLength(3); // ein Punkt je Jahr, nicht nur je Phase
|
||||
expect(r.yearly[0].wealthNominal).toBe(110000);
|
||||
expect(r.yearly[1].wealthNominal).toBe(121000);
|
||||
expect(r.yearly[2].wealthNominal).toBe(133100);
|
||||
// Ohne Inflation sind real und nominal identisch; Endjahr = Phasen-Endvermoegen.
|
||||
expect(r.yearly[2].wealthReal).toBe(133100);
|
||||
expect(r.yearly[2].wealthNominal).toBe(r.phases[0].endWealthNominal);
|
||||
});
|
||||
|
||||
it("Vermoegen real ist bei Inflation kaufkraftbereinigt", () => {
|
||||
const p = plan({
|
||||
age: 40, retirementAge: 70, inflation: 10, initialCash: 100000,
|
||||
phases: [{ id: "p1", durationYears: 1 }],
|
||||
elements: [],
|
||||
});
|
||||
const y = computePlan(p).yearly[0];
|
||||
expect(y.wealthNominal).toBe(100000);
|
||||
expect(y.wealthReal).toBe(Math.round(100000 / 1.1)); // 90'909
|
||||
});
|
||||
|
||||
it("Fortschreibung: nominaler Einkommens-Basiswert Phase 1 -> Startwert Phase 2; Cash laeuft fort", () => {
|
||||
const p = plan({
|
||||
age: 40,
|
||||
|
||||
+13
-2
@@ -94,6 +94,10 @@ export interface YearPoint {
|
||||
income: number; // Einkommen inkl. Renten (nominal)
|
||||
expenseNominal: number;
|
||||
expenseReal: number;
|
||||
// Gesamtvermoegen am Jahresende (inkl. Cash, abzgl. Schulden) -- fuer den
|
||||
// Vermoegensverlauf ueber ALLE Jahre statt nur ueber die Phasengrenzen.
|
||||
wealthNominal: number;
|
||||
wealthReal: number;
|
||||
}
|
||||
|
||||
export interface PlanComputed {
|
||||
@@ -589,13 +593,18 @@ export function computePlan(plan: PlanInput, sample?: PlanSample): PlanComputed
|
||||
const expenseReal = expenseRealBase + interestNominal / (inflFactor || 1);
|
||||
const quote = incomeFlow - expenseNominal;
|
||||
|
||||
yearly.push({
|
||||
// Der Vermoegenswert wird erst nach Verzinsung/Cash-Fortschreibung bekannt und weiter
|
||||
// unten nachgetragen (gleiche Groesse, die auch die Ruin-Erkennung verwendet).
|
||||
const yearPoint: YearPoint = {
|
||||
year: yearsBefore + t,
|
||||
age: personA.age + yearsBefore + t,
|
||||
income: Math.round(incomeFlow),
|
||||
expenseNominal: Math.round(expenseNominal),
|
||||
expenseReal: Math.round(expenseReal),
|
||||
});
|
||||
wealthNominal: 0,
|
||||
wealthReal: 0,
|
||||
};
|
||||
yearly.push(yearPoint);
|
||||
|
||||
if (t === 1) {
|
||||
incomeStart = incomeFlow;
|
||||
@@ -645,6 +654,8 @@ export function computePlan(plan: PlanInput, sample?: PlanSample): PlanComputed
|
||||
for (const a of assets) total += a.value;
|
||||
for (const re of realEstates) total += re.value - re.mortgage;
|
||||
for (const d of debts) total += -d.owed;
|
||||
yearPoint.wealthNominal = Math.round(total);
|
||||
yearPoint.wealthReal = Math.round(total / (cumInfl[yearsBefore + t] || 1));
|
||||
if (ruinAge === null && total < 0) ruinAge = personA.age + yearsBefore + t;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ export function toPlanInput(plan: PlanWithRelations): PlanInput {
|
||||
householdType: plan.householdType,
|
||||
inflationRateDefault: plan.inflationRateDefault,
|
||||
initialCash: plan.initialCash,
|
||||
startYear: plan.startYear,
|
||||
persons: plan.persons.map((p) => ({
|
||||
id: p.id,
|
||||
role: p.role,
|
||||
|
||||
@@ -67,6 +67,9 @@ export interface PlanInput {
|
||||
householdType: HouseholdType;
|
||||
inflationRateDefault: number;
|
||||
initialCash: number; // Anfangswert des Cash-Kontos in der ersten Lebensphase
|
||||
// Kalenderjahr des Planbeginns (Jahr 1) -- nur fuer die Darstellung, nicht fuer die
|
||||
// Berechnung. Optional, damit Berechnungs-Fixtures es nicht setzen muessen.
|
||||
startYear?: number | null;
|
||||
persons: PersonInput[];
|
||||
phases: PhaseInput[];
|
||||
elements: ElementInput[];
|
||||
|
||||
Reference in New Issue
Block a user