V5-Modell: Einkommen nominal, Ausgaben real->nominal, Inflation plan-weit, Sparquoten-Grafik
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:
2026-07-15 22:20:06 +02:00
parent 310ebc66fb
commit 6402583e0c
7 changed files with 212 additions and 80 deletions
+11 -7
View File
@@ -176,6 +176,7 @@ export function PlanView({
carried: ce?.carried ?? false,
derivedStart: ce?.baseValue ?? 0,
phaseInflation: phaseInflationFor(phase.id),
deflatorStart: phase.cumulativeInflationStart,
};
}
@@ -192,6 +193,7 @@ export function PlanView({
carried: ce?.carried ?? false,
derivedStart: 0,
phaseInflation: phaseInflationFor(fromPhase.id),
deflatorStart: fromPhase.cumulativeInflationStart,
};
}
@@ -617,7 +619,6 @@ export function PlanView({
phase={phaseInput}
maxDurationYears={phase.maxDurationYears}
isLast={isLast}
inflationDefault={plan.inflationRateDefault}
onSaved={onChanged}
onDeleted={() => {
setSelected(null);
@@ -681,8 +682,9 @@ function phaseCellContent(
): React.ReactNode {
if (!ce) return "";
if (ce.note) return ce.note;
const isFlow = ce.category === "INCOME" || ce.category === "EXPENSE";
const dS = phase.cumulativeInflationStart;
const dE = phase.cumulativeInflationEnd;
const dE = isFlow ? phase.flowDeflatorEnd : phase.cumulativeInflationEnd;
if (START_END_CATEGORIES.includes(ce.category) && (ce.startValue !== 0 || ce.endValue !== 0)) {
return (
<span className="whitespace-nowrap">
@@ -711,7 +713,8 @@ function PhaseHeader({
}) {
const quotaLabel = phase.isConsumption ? "Verzehr" : "Quote";
const dS = phase.cumulativeInflationStart;
const dE = phase.cumulativeInflationEnd;
const dE = phase.cumulativeInflationEnd; // Bestandswerte (Cash, Vermoegen)
const dF = phase.flowDeflatorEnd; // Flow-Werte (Einkommen, Ausgaben, Quote)
return (
<th
onClick={onClick}
@@ -739,10 +742,10 @@ function PhaseHeader({
{personLabel(p.role)} {p.startAge} {p.endAge}
</div>
))}
<div>Einkommen {valStr(phase.incomeStart, dS, mode)} {valStr(phase.incomeEnd, dE, mode)}</div>
<div>Ausgaben {valStr(phase.expenseStart, dS, mode)} {valStr(phase.expenseEnd, dE, mode)}</div>
<div>Einkommen {valStr(phase.incomeStart, dS, mode)} {valStr(phase.incomeEnd, dF, mode)}</div>
<div>Ausgaben {valStr(phase.expenseStart, dS, mode)} {valStr(phase.expenseEnd, dF, mode)}</div>
<div>
{quotaLabel} {valStr(phase.quotaStart, dS, mode)} {valStr(phase.quotaEnd, dE, mode)}
{quotaLabel} {valStr(phase.quotaStart, dS, mode)} {valStr(phase.quotaEnd, dF, mode)}
</div>
<div className={phase.cashNegative ? "text-danger font-medium" : ""}>
Cash {valStr(phase.cashStart, dS, mode)} {valStr(phase.cashEnd, dE, mode)}
@@ -842,7 +845,8 @@ function AddElementDialog({
carriedEndValue: 0,
carried: false,
derivedStart: 0,
phaseInflation: plan.phases.find((p) => p.id === firstPhase.id)?.inflationRate ?? plan.inflationRateDefault,
phaseInflation: plan.inflationRateDefault,
deflatorStart: firstPhase.cumulativeInflationStart,
};
async function create() {