fix(pitch-deck): fix Liquidität engine label mismatches + MilestonesSlide types
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m38s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 38s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 33s

Engine now uses dynamic row_type-based summation instead of hardcoded label
strings that differed between scenarios (e.g. 'Summe ERTRÄGE' vs
'Summe EINZAHLUNGEN'), fixing stale 9.2M value in Wandeldarlehen scenarios.
Rolling balance now includes all financing cash flows via ÜBERSCHUSS chain.

MilestonesSlide: widen Theme type to union so t.key comparisons compile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-23 22:07:00 +02:00
parent 6694ab84a1
commit 71b6f8f181
2 changed files with 28 additions and 53 deletions

View File

@@ -147,7 +147,7 @@ const THEMES = {
}, },
} }
type Theme = typeof THEMES.dark type Theme = typeof THEMES.dark | typeof THEMES.light
// ── Data ────────────────────────────────────────────────────────────────────── // ── Data ──────────────────────────────────────────────────────────────────────
const TODAY_POSITION = 0.56 const TODAY_POSITION = 0.56

View File

@@ -413,57 +413,42 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
liqInvest.values = totalInvest liqInvest.values = totalInvest
} }
// Compute sums and rolling balance // Compute sums and rolling balance — dynamic row_type-based (handles any label conventions)
// WICHTIG: Überschuss = nur operativer Cashflow (ohne Kapitaleinzahlungen) const findLiqMatch = (options: string[]) => liquid.find(r => options.includes(r.row_label))
const sumEin = findLiq('Summe EINZAHLUNGEN') const sumEin = findLiqMatch(['Summe ERTRÄGE', 'Summe EINZAHLUNGEN'])
const sumAus = findLiq('Summe AUSZAHLUNGEN') const sumAus = findLiqMatch(['Summe AUSZAHLUNGEN'])
const uebVorInv = findLiq('ÜBERSCHUSS VOR INVESTITIONEN') const uebVorInv = findLiqMatch(['ÜBERSCHUSS VOR INVESTITIONEN', 'UEBERSCHUSS VOR INVESTITIONEN'])
const uebVorEnt = findLiq('ÜBERSCHUSS VOR ENTNAHMEN') const uebVorEnt = findLiqMatch(['ÜBERSCHUSS VOR ENTNAHMEN', 'UEBERSCHUSS VOR ENTNAHMEN'])
const ueberschuss = findLiq('ÜBERSCHUSS') const ueberschuss = findLiqMatch(['ÜBERSCHUSS', 'UEBERSCHUSS'])
const kontostand = findLiq('Kontostand zu Beginn des Monats') // Kontostand: label varies per scenario (with/without parentheses)
const liquiditaet = findLiq('LIQUIDITÄT') const kontostand = liquid.find(r => r.row_type === 'kontostand' && !r.row_label.includes('LIQUIDIT'))
const liquiditaet = liquid.find(r => r.row_type === 'kontostand' && r.row_label.includes('LIQUIDIT'))
// Dynamically categorize rows by row_type instead of hardcoded labels // Summe ERTRÄGE = ALL einzahlungen (dynamic — works regardless of how many rows exist)
// Operative Einzahlungen (OHNE Eigenkapital, Fremdkapital, Stammkapital, Wandeldarlehen)
const einzahlungenOperativ = ['Umsatzerlöse', 'Sonst. betriebl. Erträge', 'Anzahlungen']
// Finanzierung: match any row with these keywords (handles renamed labels)
const finanzierungRows = liquid.filter(r =>
r.row_type === 'einzahlung' &&
!einzahlungenOperativ.includes(r.row_label) &&
!r.row_label.includes('Summe')
)
// Operative Auszahlungen
const auszahlungenOperativ = ['Materialaufwand', 'Personalkosten', 'Sonstige Kosten', 'Umsatzsteuer', 'Gewerbesteuer', 'Körperschaftsteuer']
// Finanz-Auszahlungen: any auszahlung not in operativ list
const finanzAuszahlungRows = liquid.filter(r =>
r.row_type === 'auszahlung' &&
!auszahlungenOperativ.includes(r.row_label) &&
!r.row_label.includes('Summe')
)
// Summe EINZAHLUNGEN = nur operativ (für die Zeile "Summe Einzahlungen")
if (sumEin) { if (sumEin) {
const s = emptyMonthly() const s = emptyMonthly()
for (const label of einzahlungenOperativ) { for (const row of liquid) {
const row = findLiq(label) if (row.row_type === 'einzahlung' && row.id !== sumEin.id) {
if (row) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += Math.round(row.values[`m${m}`] || 0) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
}
} }
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), sumEin.id]) await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), sumEin.id])
sumEin.values = s sumEin.values = s
} }
// Summe AUSZAHLUNGEN = nur operativ // Summe AUSZAHLUNGEN = ALL auszahlungen (dynamic)
if (sumAus) { if (sumAus) {
const s = emptyMonthly() const s = emptyMonthly()
for (const label of auszahlungenOperativ) { for (const row of liquid) {
const row = findLiq(label) if (row.row_type === 'auszahlung' && row.id !== sumAus.id) {
if (row) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += Math.round(row.values[`m${m}`] || 0) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
}
} }
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), sumAus.id]) await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(s), sumAus.id])
sumAus.values = s sumAus.values = s
} }
// OPERATIVER ÜBERSCHUSS VOR INVESTITIONEN = operative Einzahlungen - operative Auszahlungen // ÜBERSCHUSS VOR INVESTITIONEN = Summe ERTRÄGE - Summe AUSZAHLUNGEN (total cashflow)
if (uebVorInv && sumEin && sumAus) { if (uebVorInv && sumEin && sumAus) {
const s = emptyMonthly() const s = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = Math.round((sumEin.values[`m${m}`] || 0) - (sumAus.values[`m${m}`] || 0)) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = Math.round((sumEin.values[`m${m}`] || 0) - (sumAus.values[`m${m}`] || 0))
@@ -471,7 +456,7 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
uebVorInv.values = s uebVorInv.values = s
} }
// ÜBERSCHUSS VOR ENTNAHMEN = Operativer Überschuss - Investitionen // ÜBERSCHUSS VOR ENTNAHMEN = ÜBERSCHUSS VOR INVESTITIONEN - Investitionen
if (uebVorEnt && uebVorInv && liqInvest) { if (uebVorEnt && uebVorInv && liqInvest) {
const s = emptyMonthly() const s = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = Math.round((uebVorInv.values[`m${m}`] || 0) - (liqInvest.values[`m${m}`] || 0)) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = Math.round((uebVorInv.values[`m${m}`] || 0) - (liqInvest.values[`m${m}`] || 0))
@@ -479,8 +464,8 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
uebVorEnt.values = s uebVorEnt.values = s
} }
// ÜBERSCHUSS = Überschuss vor Entnahmen - Entnahmen (immer noch rein operativ) // ÜBERSCHUSS = ÜBERSCHUSS VOR ENTNAHMEN - Kapitalentnahmen
const entnahmen = findLiq('Kapitalentnahmen/Ausschüttungen') const entnahmen = findLiqMatch(['Kapitalentnahmen/Ausschüttungen', 'Kapitalentnahmen/Ausschuettungen'])
if (ueberschuss && uebVorEnt && entnahmen) { if (ueberschuss && uebVorEnt && entnahmen) {
const s = emptyMonthly() const s = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = Math.round((uebVorEnt.values[`m${m}`] || 0) - (entnahmen.values[`m${m}`] || 0)) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] = Math.round((uebVorEnt.values[`m${m}`] || 0) - (entnahmen.values[`m${m}`] || 0))
@@ -488,24 +473,14 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
ueberschuss.values = s ueberschuss.values = s
} }
// Rolling Kontostand: Vormonat + Operativer Überschuss + Finanzierung // Rolling balance: LIQUIDITÄT[m] = LIQUIDITÄT[m-1] + ÜBERSCHUSS[m]
// Finanzierung = Eigenkapital + Fremdkapital - Kreditrückzahlungen // ÜBERSCHUSS now includes ALL cash flows (operative + financing + repayments)
if (kontostand && liquiditaet && ueberschuss) { if (kontostand && liquiditaet && ueberschuss) {
// Berechne monatliche Finanzierungs-Cashflows
const finCF = emptyMonthly()
for (const row of finanzierungRows) {
for (let m = 1; m <= MONTHS; m++) finCF[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
}
for (const row of finanzAuszahlungRows) {
for (let m = 1; m <= MONTHS; m++) finCF[`m${m}`] -= Math.round(row.values[`m${m}`] || 0)
}
const ks = emptyMonthly() const ks = emptyMonthly()
const lq = emptyMonthly() const lq = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) { for (let m = 1; m <= MONTHS; m++) {
ks[`m${m}`] = m === 1 ? 0 : Math.round(lq[`m${m - 1}`]) ks[`m${m}`] = m === 1 ? 0 : Math.round(lq[`m${m - 1}`])
// LIQUIDITÄT = Kontostand + Operativer Überschuss + Finanzierung lq[`m${m}`] = Math.round(ks[`m${m}`] + (ueberschuss.values[`m${m}`] || 0))
lq[`m${m}`] = Math.round(ks[`m${m}`] + (ueberschuss.values[`m${m}`] || 0) + (finCF[`m${m}`] || 0))
} }
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(ks), kontostand.id]) await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(ks), kontostand.id])
await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(lq), liquiditaet.id]) await pool.query('UPDATE fp_liquiditaet SET values = $1 WHERE id = $2', [JSON.stringify(lq), liquiditaet.id])