V5-Modell: Einkommen nominal, Ausgaben real->nominal, Inflation plan-weit, Sparquoten-Grafik
Deploy App / deploy (push) Successful in 57s
Deploy App / deploy (push) Successful in 57s
- Einkommen: nominale Basis + nominale Lohnerhoehung (Default 0). Real read-only als Info. - Ausgaben: REALE Basis (heutige Kaufkraft) + reale Mehrausgaben (Default 0); nominal = real x plan-weite Inflation, read-only. Loest die "nominale Ausgaben verwirren"-Problematik. - Inflation nur noch plan-weit (Phasen-Override entfernt). - Engine liefert flowDeflatorEnd (Flow-Realwerte) + yearly-Serie (Jahr/Alter/Einkommen/ Ausgabe nominal/real). Nominal/Real-Umschalter nutzt Flow- vs. Bestands-Deflatoren. - Neue Grafik (Dashboard): Einkommen vs. nominale Ausgabe mit eingefaerbter Sparquoten- Flaeche (gruen/rot) + reale Ausgabe als Referenzlinie (Inflationskeil). - In-Tool-Erklaerungen in den Einkommen/Ausgaben-Popups. - Golden Tests aufs neue Modell hergeleitet (8 gruen, u.a. Ausgaben real->nominal, Ruin 94). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -39,48 +39,48 @@ function plan(opts: {
|
||||
|
||||
const within = (actual: number, expected: number, pct: number) => Math.abs(actual - expected) <= Math.abs(expected) * pct;
|
||||
|
||||
describe("V4 Golden Tests", () => {
|
||||
it("Test 1 – Ansparphase, Flows wachsen (Cash 0%)", () => {
|
||||
// V5-Modell: Einkommen = nominale Basis + nominale Lohnerhoehung; Ausgaben = REALE Basis +
|
||||
// reale Mehrausgaben, nominal = real x (plan-weite Inflation).
|
||||
describe("V5 Golden Tests", () => {
|
||||
it("Test 1 – Ansparen (Einkommen +2% nominal, Ausgaben real flach -> nominal +2%)", () => {
|
||||
const p = plan({
|
||||
age: 40,
|
||||
retirementAge: 60,
|
||||
phases: [{ id: "p1", durationYears: 15 }],
|
||||
elements: [
|
||||
el("INCOME", "PERSON_A", { p1: { amount: 120000, teuerungsausgleich: 2 } }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 100000, teuerungsausgleich: 2 } }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 100000, teuerungsausgleich: 0 } }),
|
||||
el("OTHER_ASSET", "HOUSEHOLD", { p1: { startValue: 200000, expectedReturn: 5, annualContribution: 0 } }),
|
||||
],
|
||||
});
|
||||
const r = computePlan(p);
|
||||
const ph = r.phases[0];
|
||||
const ph = computePlan(p).phases[0];
|
||||
expect(within(ph.endWealthNominal, 761654, 0.01)).toBe(true);
|
||||
expect(within(ph.endWealthReal, 565928, 0.01)).toBe(true);
|
||||
expect(ph.cashNegative).toBe(false);
|
||||
});
|
||||
|
||||
it("Test 2 – Verzehr/Ruin: Gesamtvermoegen kippt, Ruin Alter 94", () => {
|
||||
it("Test 2 – Verzehr/Ruin: Ausgaben nominal +2%, Rente fix -> Ruin Alter 94", () => {
|
||||
const p = plan({
|
||||
age: 65,
|
||||
retirementAge: 65,
|
||||
phases: [{ id: "p1", durationYears: 35 }],
|
||||
elements: [
|
||||
el("INCOME", "PERSON_A", { p1: { amount: 60000, teuerungsausgleich: 0 } }), // Rente nominal fix
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 100000, teuerungsausgleich: 2 } }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 100000, teuerungsausgleich: 0 } }),
|
||||
el("OTHER_ASSET", "HOUSEHOLD", { p1: { startValue: 900000, expectedReturn: 3, annualContribution: 0 } }),
|
||||
],
|
||||
});
|
||||
const r = computePlan(p);
|
||||
expect(r.ruinAge).toBe(94);
|
||||
expect(computePlan(p).ruinAge).toBe(94);
|
||||
});
|
||||
|
||||
it("Test 3 – Cash-Ausgleich, Rate 6'364 (unter Anfangsquote), Cash nie negativ", () => {
|
||||
it("Test 3 – Cash-Ausgleich, Rate 6'364, Cash nie negativ", () => {
|
||||
const p = plan({
|
||||
age: 40,
|
||||
retirementAge: 60,
|
||||
phases: [{ id: "p1", durationYears: 3 }],
|
||||
elements: [
|
||||
el("INCOME", "PERSON_A", { p1: { amount: 100000, teuerungsausgleich: 0 } }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 90000, teuerungsausgleich: 2 } }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 90000, teuerungsausgleich: 0 } }),
|
||||
el("OTHER_ASSET", "HOUSEHOLD", { p1: { startValue: 0, expectedReturn: 0, annualContribution: 6364 } }),
|
||||
],
|
||||
});
|
||||
@@ -89,14 +89,14 @@ describe("V4 Golden Tests", () => {
|
||||
expect(ph.cashNegative).toBe(false);
|
||||
});
|
||||
|
||||
it("Test 4 – Cash-Ausgleich, Rate 10'000 (ueber Endquote), Cash wird negativ", () => {
|
||||
it("Test 4 – Cash-Ausgleich, Rate 10'000, Cash wird negativ", () => {
|
||||
const p = plan({
|
||||
age: 40,
|
||||
retirementAge: 60,
|
||||
phases: [{ id: "p1", durationYears: 3 }],
|
||||
elements: [
|
||||
el("INCOME", "PERSON_A", { p1: { amount: 100000, teuerungsausgleich: 0 } }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 90000, teuerungsausgleich: 2 } }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 90000, teuerungsausgleich: 0 } }),
|
||||
el("OTHER_ASSET", "HOUSEHOLD", { p1: { startValue: 0, expectedReturn: 0, annualContribution: 10000 } }),
|
||||
],
|
||||
});
|
||||
@@ -105,23 +105,19 @@ describe("V4 Golden Tests", () => {
|
||||
expect(ph.cashNegative).toBe(true);
|
||||
});
|
||||
|
||||
it("Cash-Anfangswert fliesst in die erste Phase ein", () => {
|
||||
it("Ausgaben real -> nominal: reale Basis 100'000, 2% Inflation, 5 J.", () => {
|
||||
const p = plan({
|
||||
age: 40,
|
||||
retirementAge: 60,
|
||||
initialCash: 50000,
|
||||
phases: [{ id: "p1", durationYears: 3 }],
|
||||
elements: [
|
||||
el("INCOME", "PERSON_A", { p1: { amount: 100000, teuerungsausgleich: 0 } }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 100000, teuerungsausgleich: 0 } }),
|
||||
],
|
||||
phases: [{ id: "p1", durationYears: 5 }],
|
||||
elements: [el("EXPENSE", "HOUSEHOLD", { p1: { amount: 100000, teuerungsausgleich: 0 } })],
|
||||
});
|
||||
const ph = computePlan(p).phases[0];
|
||||
expect(ph.cashStart).toBe(50000);
|
||||
expect(ph.cashEnd).toBe(50000); // Quote 0, keine Raten -> Cash unveraendert
|
||||
expect(ph.expenseStart).toBe(100000); // Jahr 1 nominal = real
|
||||
expect(ph.expenseEnd).toBe(Math.round(100000 * Math.pow(1.02, 4))); // 108'243
|
||||
});
|
||||
|
||||
it("indexRate 0 -> Einkommen bleibt nominal flach", () => {
|
||||
it("Einkommen nominal flach bei Lohnerhoehung 0%", () => {
|
||||
const p = plan({
|
||||
age: 40,
|
||||
retirementAge: 60,
|
||||
@@ -133,7 +129,24 @@ describe("V4 Golden Tests", () => {
|
||||
expect(ph.incomeEnd).toBe(80000);
|
||||
});
|
||||
|
||||
it("Phasen-Fortschreibung: indexierter Endwert Phase 1 = Startwert Phase 2; Cash laeuft fort", () => {
|
||||
it("Cash-Anfangswert fliesst in die erste Phase ein", () => {
|
||||
const p = plan({
|
||||
age: 40,
|
||||
retirementAge: 60,
|
||||
inflation: 0,
|
||||
initialCash: 50000,
|
||||
phases: [{ id: "p1", durationYears: 3 }],
|
||||
elements: [
|
||||
el("INCOME", "PERSON_A", { p1: { amount: 100000, teuerungsausgleich: 0 } }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 100000, teuerungsausgleich: 0 } }),
|
||||
],
|
||||
});
|
||||
const ph = computePlan(p).phases[0];
|
||||
expect(ph.cashStart).toBe(50000);
|
||||
expect(ph.cashEnd).toBe(50000);
|
||||
});
|
||||
|
||||
it("Fortschreibung: nominaler Einkommens-Basiswert Phase 1 -> Startwert Phase 2; Cash laeuft fort", () => {
|
||||
const p = plan({
|
||||
age: 40,
|
||||
retirementAge: 70,
|
||||
@@ -143,7 +156,7 @@ describe("V4 Golden Tests", () => {
|
||||
],
|
||||
elements: [
|
||||
el("INCOME", "PERSON_A", { p1: { amount: 100000, teuerungsausgleich: 2 }, p2: {} }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 80000, teuerungsausgleich: 2 }, p2: {} }),
|
||||
el("EXPENSE", "HOUSEHOLD", { p1: { amount: 80000, teuerungsausgleich: 0 }, p2: {} }),
|
||||
],
|
||||
});
|
||||
const r = computePlan(p);
|
||||
|
||||
Reference in New Issue
Block a user