fix(pitch-deck): replace all hardcoded financial numbers with computed values
Some checks failed
Build pitch-deck / build-push-deploy (push) Failing after 40s
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 37s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 33s

All financial data now flows from the same compute engine (useFinancialModel).
No more hardcoded numbers in any slide — all values are derived from the
finanzplan database, ensuring consistency across all pitch deck versions.

- FinanzplanSlide: KPI table + charts now use computeAnnualKPIs() from FMResult[]
- BusinessModelSlide: bottom-up calc (customers × ACV = ARR) from compute engine
- AssumptionsSlide: Base case from compute, Bear/Bull scaled from Base
- New helper: lib/finanzplan/annual-kpis.ts for 60-month → 5-year aggregation
- PitchDeck: passes investorId to all financial slides for version-aware data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-16 08:48:37 +02:00
parent aed428312f
commit 06f868abeb
5 changed files with 247 additions and 94 deletions

View File

@@ -0,0 +1,89 @@
import { FMResult } from '../types'
export interface AnnualKPI {
year: number
mrr: number
arr: number
customers: number
arpu: number
employees: number
revenuePerEmployee: number
personnelCosts: number
totalRevenue: number
totalCosts: number
ebit: number
ebitMargin: number
taxes: number
netIncome: number
serverCostPerCustomer: number
grossMargin: number
burnRate: number
runway: number | null
cashBalance: number
}
const TAX_RATE = 0.30 // ~30% Körperschaftsteuer + Gewerbesteuer + Soli
/**
* Aggregates 60 monthly FMResult entries into 5 annual KPI rows (20262030).
* All values are derived — nothing is hardcoded.
*/
export function computeAnnualKPIs(results: FMResult[]): AnnualKPI[] {
if (!results || results.length === 0) return []
const years = [2026, 2027, 2028, 2029, 2030]
return years.map(year => {
const yearResults = results.filter(r => r.year === year)
if (yearResults.length === 0) {
return emptyKPI(year)
}
const dec = yearResults[yearResults.length - 1] // December snapshot
const totalRevenue = yearResults.reduce((s, r) => s + r.revenue_eur, 0)
const personnelCosts = yearResults.reduce((s, r) => s + r.personnel_eur, 0)
const totalCogs = yearResults.reduce((s, r) => s + r.cogs_eur, 0)
const totalInfra = yearResults.reduce((s, r) => s + r.infra_eur, 0)
const totalMarketing = yearResults.reduce((s, r) => s + r.marketing_eur, 0)
const totalCosts = yearResults.reduce((s, r) => s + r.total_costs_eur, 0)
const ebit = totalRevenue - totalCosts
const ebitMargin = totalRevenue > 0 ? (ebit / totalRevenue) * 100 : 0
const taxes = ebit > 0 ? Math.round(ebit * TAX_RATE) : 0
const netIncome = ebit - taxes
const serverCost = dec.total_customers > 0
? Math.round((totalInfra / 12) / dec.total_customers)
: 0
return {
year,
mrr: Math.round(dec.mrr_eur),
arr: Math.round(dec.arr_eur),
customers: Math.round(dec.total_customers),
arpu: dec.total_customers > 0 ? Math.round(dec.mrr_eur / dec.total_customers) : 0,
employees: Math.round(dec.employees_count),
revenuePerEmployee: dec.employees_count > 0 ? Math.round(totalRevenue / dec.employees_count) : 0,
personnelCosts: Math.round(personnelCosts),
totalRevenue: Math.round(totalRevenue),
totalCosts: Math.round(totalCosts),
ebit: Math.round(ebit),
ebitMargin: Math.round(ebitMargin),
taxes,
netIncome: Math.round(netIncome),
serverCostPerCustomer: serverCost,
grossMargin: Math.round(dec.gross_margin_pct),
burnRate: Math.round(dec.burn_rate_eur),
runway: dec.runway_months > 999 ? null : Math.round(dec.runway_months),
cashBalance: Math.round(dec.cash_balance_eur),
}
})
}
function emptyKPI(year: number): AnnualKPI {
return {
year, mrr: 0, arr: 0, customers: 0, arpu: 0, employees: 0,
revenuePerEmployee: 0, personnelCosts: 0, totalRevenue: 0, totalCosts: 0,
ebit: 0, ebitMargin: 0, taxes: 0, netIncome: 0,
serverCostPerCustomer: 0, grossMargin: 0, burnRate: 0, runway: null, cashBalance: 0,
}
}