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

@@ -413,57 +413,42 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
liqInvest.values = totalInvest
}
// Compute sums and rolling balance
// WICHTIG: Überschuss = nur operativer Cashflow (ohne Kapitaleinzahlungen)
const sumEin = findLiq('Summe EINZAHLUNGEN')
const sumAus = findLiq('Summe AUSZAHLUNGEN')
const uebVorInv = findLiq('ÜBERSCHUSS VOR INVESTITIONEN')
const uebVorEnt = findLiq('ÜBERSCHUSS VOR ENTNAHMEN')
const ueberschuss = findLiq('ÜBERSCHUSS')
const kontostand = findLiq('Kontostand zu Beginn des Monats')
const liquiditaet = findLiq('LIQUIDITÄT')
// Compute sums and rolling balance — dynamic row_type-based (handles any label conventions)
const findLiqMatch = (options: string[]) => liquid.find(r => options.includes(r.row_label))
const sumEin = findLiqMatch(['Summe ERTRÄGE', 'Summe EINZAHLUNGEN'])
const sumAus = findLiqMatch(['Summe AUSZAHLUNGEN'])
const uebVorInv = findLiqMatch(['ÜBERSCHUSS VOR INVESTITIONEN', 'UEBERSCHUSS VOR INVESTITIONEN'])
const uebVorEnt = findLiqMatch(['ÜBERSCHUSS VOR ENTNAHMEN', 'UEBERSCHUSS VOR ENTNAHMEN'])
const ueberschuss = findLiqMatch(['ÜBERSCHUSS', 'UEBERSCHUSS'])
// Kontostand: label varies per scenario (with/without parentheses)
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
// 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")
// Summe ERTRÄGE = ALL einzahlungen (dynamic — works regardless of how many rows exist)
if (sumEin) {
const s = emptyMonthly()
for (const label of einzahlungenOperativ) {
const row = findLiq(label)
if (row) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
for (const row of liquid) {
if (row.row_type === 'einzahlung' && row.id !== sumEin.id) {
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])
sumEin.values = s
}
// Summe AUSZAHLUNGEN = nur operativ
// Summe AUSZAHLUNGEN = ALL auszahlungen (dynamic)
if (sumAus) {
const s = emptyMonthly()
for (const label of auszahlungenOperativ) {
const row = findLiq(label)
if (row) for (let m = 1; m <= MONTHS; m++) s[`m${m}`] += Math.round(row.values[`m${m}`] || 0)
for (const row of liquid) {
if (row.row_type === 'auszahlung' && row.id !== sumAus.id) {
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])
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) {
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))
@@ -471,7 +456,7 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
uebVorInv.values = s
}
// ÜBERSCHUSS VOR ENTNAHMEN = Operativer Überschuss - Investitionen
// ÜBERSCHUSS VOR ENTNAHMEN = ÜBERSCHUSS VOR INVESTITIONEN - Investitionen
if (uebVorEnt && uebVorInv && liqInvest) {
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))
@@ -479,8 +464,8 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
uebVorEnt.values = s
}
// ÜBERSCHUSS = Überschuss vor Entnahmen - Entnahmen (immer noch rein operativ)
const entnahmen = findLiq('Kapitalentnahmen/Ausschüttungen')
// ÜBERSCHUSS = ÜBERSCHUSS VOR ENTNAHMEN - Kapitalentnahmen
const entnahmen = findLiqMatch(['Kapitalentnahmen/Ausschüttungen', 'Kapitalentnahmen/Ausschuettungen'])
if (ueberschuss && uebVorEnt && entnahmen) {
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))
@@ -488,24 +473,14 @@ export async function computeFinanzplan(pool: Pool, scenarioId: string): Promise
ueberschuss.values = s
}
// Rolling Kontostand: Vormonat + Operativer Überschuss + Finanzierung
// Finanzierung = Eigenkapital + Fremdkapital - Kreditrückzahlungen
// Rolling balance: LIQUIDITÄT[m] = LIQUIDITÄT[m-1] + ÜBERSCHUSS[m]
// ÜBERSCHUSS now includes ALL cash flows (operative + financing + repayments)
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 lq = emptyMonthly()
for (let m = 1; m <= MONTHS; m++) {
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) + (finCF[`m${m}`] || 0))
lq[`m${m}`] = Math.round(ks[`m${m}`] + (ueberschuss.values[`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(lq), liquiditaet.id])