diff --git a/pitch-deck/components/PitchDeck.tsx b/pitch-deck/components/PitchDeck.tsx
index 1b18fb9..24cb361 100644
--- a/pitch-deck/components/PitchDeck.tsx
+++ b/pitch-deck/components/PitchDeck.tsx
@@ -169,7 +169,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
case 'market':
return
case 'business-model':
- return
+ return
case 'traction':
return
case 'competition':
@@ -188,7 +188,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
case 'ai-qa':
return
case 'annex-assumptions':
- return
+ return
case 'annex-architecture':
return
case 'annex-gtm':
@@ -204,7 +204,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
case 'annex-strategy':
return
case 'annex-finanzplan':
- return
+ return
case 'annex-glossary':
return
case 'legal-disclaimer':
diff --git a/pitch-deck/components/slides/AssumptionsSlide.tsx b/pitch-deck/components/slides/AssumptionsSlide.tsx
index 3ebce50..f3f2372 100644
--- a/pitch-deck/components/slides/AssumptionsSlide.tsx
+++ b/pitch-deck/components/slides/AssumptionsSlide.tsx
@@ -6,15 +6,67 @@ import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
import { TrendingUp, TrendingDown, Minus } from 'lucide-react'
+import { useFinancialModel } from '@/lib/hooks/useFinancialModel'
interface AssumptionsSlideProps {
lang: Language
+ investorId?: string | null
}
-export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
+function fmtArr(v: number, de: boolean): string {
+ if (v >= 1_000_000) {
+ const m = (v / 1_000_000).toFixed(1)
+ return de ? `~${m.replace('.', ',')} Mio. EUR` : `~EUR ${m}M`
+ }
+ return de ? `~${Math.round(v / 1000)}k EUR` : `~EUR ${Math.round(v / 1000)}k`
+}
+
+function fmtCash(v: number, de: boolean): string {
+ if (Math.abs(v) >= 1_000_000) {
+ const m = (v / 1_000_000).toFixed(1)
+ return de ? `~${m.replace('.', ',')} Mio. EUR` : `~EUR ${m}M`
+ }
+ return de ? `~${Math.round(v / 1000)}k EUR` : `~EUR ${Math.round(v / 1000)}k`
+}
+
+function breakEvenYear(month: number | null): string {
+ if (!month || month <= 0) return '—'
+ const year = 2026 + Math.floor((month - 1) / 12)
+ return String(year)
+}
+
+export default function AssumptionsSlide({ lang, investorId }: AssumptionsSlideProps) {
const i = t(lang)
const de = lang === 'de'
+ // Load computed financial data for Base Case
+ const fm = useFinancialModel(investorId || null)
+ const summary = fm.activeResults?.summary
+ const results = fm.activeResults?.results || []
+ const lastResult = results.length > 0 ? results[results.length - 1] : null
+
+ // Base case from compute engine
+ const baseCustomers = summary?.final_customers || 0
+ const baseArr = summary?.final_arr || 0
+ const baseEmployees = lastResult?.employees_count || 0
+ const baseCash = lastResult?.cash_balance_eur || 0
+ const baseBreakEven = breakEvenYear(summary?.break_even_month || null)
+
+ // Bear/Bull derived from Base (scaling factors)
+ const bearCustomers = Math.round(baseCustomers * 0.5)
+ const bearArr = baseArr * 0.42
+ const bearEmployees = Math.round(baseEmployees * 0.7)
+ const bearCash = baseCash * 0.08
+ const bearBreakEvenMonth = summary?.break_even_month ? Math.round(summary.break_even_month * 1.3) : null
+ const bearBreakEven = breakEvenYear(bearBreakEvenMonth)
+
+ const bullCustomers = Math.round(baseCustomers * 1.7)
+ const bullArr = baseArr * 1.8
+ const bullEmployees = Math.round(baseEmployees * 1.4)
+ const bullCash = baseCash * 2.3
+ const bullBreakEvenMonth = summary?.break_even_month ? Math.round(summary.break_even_month * 0.75) : null
+ const bullBreakEven = breakEvenYear(bullBreakEvenMonth)
+
// 3 Cases abgeleitet aus dem Finanzplan (Base Case = aktuelle DB-Daten)
const cases = [
{
@@ -37,11 +89,11 @@ export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
'Server costs €150 per customer',
],
kpis: {
- kunden2030: '~600',
- arr2030: de ? '~4,2 Mio. EUR' : '~EUR 4.2M',
- ma2030: '25',
- breakEven: '2030',
- cash2030: de ? '~0,5 Mio. EUR' : '~EUR 0.5M',
+ kunden2030: `~${bearCustomers.toLocaleString('de-DE')}`,
+ arr2030: fmtArr(bearArr, de),
+ ma2030: String(bearEmployees),
+ breakEven: bearBreakEven,
+ cash2030: fmtCash(bearCash, de),
},
},
{
@@ -64,11 +116,11 @@ export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
'Break-even mid 2029',
],
kpis: {
- kunden2030: '~1.200',
- arr2030: de ? '~10 Mio. EUR' : '~EUR 10M',
- ma2030: '35',
- breakEven: '2029',
- cash2030: de ? '~6,4 Mio. EUR' : '~EUR 6.4M',
+ kunden2030: `~${baseCustomers.toLocaleString('de-DE')}`,
+ arr2030: fmtArr(baseArr, de),
+ ma2030: String(baseEmployees),
+ breakEven: baseBreakEven,
+ cash2030: fmtCash(baseCash, de),
},
},
{
@@ -91,11 +143,11 @@ export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
'EU expansion from 2028',
],
kpis: {
- kunden2030: '~2.000',
- arr2030: de ? '~18 Mio. EUR' : '~EUR 18M',
- ma2030: '50',
- breakEven: '2028',
- cash2030: de ? '~15 Mio. EUR' : '~EUR 15M',
+ kunden2030: `~${bullCustomers.toLocaleString('de-DE')}`,
+ arr2030: fmtArr(bullArr, de),
+ ma2030: String(bullEmployees),
+ breakEven: bullBreakEven,
+ cash2030: fmtCash(bullCash, de),
},
},
]
@@ -168,11 +220,11 @@ export default function AssumptionsSlide({ lang }: AssumptionsSlideProps) {
{[
- { label: de ? 'Kunden' : 'Customers', bear: '~600', base: '~1.200', bull: '~2.000' },
- { label: 'ARR', bear: de ? '~4,2 Mio.' : '~4.2M', base: de ? '~10 Mio.' : '~10M', bull: de ? '~18 Mio.' : '~18M' },
- { label: de ? 'Mitarbeiter' : 'Employees', bear: '25', base: '35', bull: '50' },
- { label: 'Break-Even', bear: '2030', base: '2029', bull: '2028' },
- { label: 'Cash', bear: de ? '~0,5 Mio.' : '~0.5M', base: de ? '~6,4 Mio.' : '~6.4M', bull: de ? '~15 Mio.' : '~15M' },
+ { label: de ? 'Kunden' : 'Customers', bear: `~${bearCustomers.toLocaleString('de-DE')}`, base: `~${baseCustomers.toLocaleString('de-DE')}`, bull: `~${bullCustomers.toLocaleString('de-DE')}` },
+ { label: 'ARR', bear: fmtArr(bearArr, de), base: fmtArr(baseArr, de), bull: fmtArr(bullArr, de) },
+ { label: de ? 'Mitarbeiter' : 'Employees', bear: String(bearEmployees), base: String(baseEmployees), bull: String(bullEmployees) },
+ { label: 'Break-Even', bear: bearBreakEven, base: baseBreakEven, bull: bullBreakEven },
+ { label: 'Cash', bear: fmtCash(bearCash, de), base: fmtCash(baseCash, de), bull: fmtCash(bullCash, de) },
].map((row, idx) => (
| {row.label} |
diff --git a/pitch-deck/components/slides/BusinessModelSlide.tsx b/pitch-deck/components/slides/BusinessModelSlide.tsx
index 675e26f..304c1e8 100644
--- a/pitch-deck/components/slides/BusinessModelSlide.tsx
+++ b/pitch-deck/components/slides/BusinessModelSlide.tsx
@@ -2,6 +2,7 @@
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
+import { useFinancialModel } from '@/lib/hooks/useFinancialModel'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
@@ -10,11 +11,19 @@ import { ArrowRight, TrendingUp, Target, Repeat, DollarSign, Users, BarChart3 }
interface BusinessModelSlideProps {
lang: Language
products?: unknown[]
+ investorId?: string | null
}
-export default function BusinessModelSlide({ lang }: BusinessModelSlideProps) {
+export default function BusinessModelSlide({ lang, investorId }: BusinessModelSlideProps) {
const i = t(lang)
const de = lang === 'de'
+ const fm = useFinancialModel(investorId || null)
+ const summary = fm.activeResults?.summary
+ const results = fm.activeResults?.results || []
+ const lastResult = results[results.length - 1]
+ const finalCustomers = summary?.final_customers || 0
+ const finalArr = summary?.final_arr || 0
+ const acv = finalCustomers > 0 ? Math.round(finalArr / finalCustomers) : 0
const tiers = [
{
@@ -145,8 +154,8 @@ export default function BusinessModelSlide({ lang }: BusinessModelSlideProps) {
{de
- ? '1.200 Kunden × 8.400 EUR ACV = ~10 Mio. EUR ARR (2030)'
- : '1,200 customers × EUR 8,400 ACV = ~EUR 10M ARR (2030)'}
+ ? `${finalCustomers.toLocaleString('de-DE')} Kunden × ${acv.toLocaleString('de-DE')} EUR ACV = ~${(finalArr / 1_000_000).toFixed(1).replace('.', ',')} Mio. EUR ARR (2030)`
+ : `${finalCustomers.toLocaleString('en-US')} customers × EUR ${acv.toLocaleString('en-US')} ACV = ~EUR ${(finalArr / 1_000_000).toFixed(1)}M ARR (2030)`}
diff --git a/pitch-deck/components/slides/FinanzplanSlide.tsx b/pitch-deck/components/slides/FinanzplanSlide.tsx
index 08d44d2..286b984 100644
--- a/pitch-deck/components/slides/FinanzplanSlide.tsx
+++ b/pitch-deck/components/slides/FinanzplanSlide.tsx
@@ -1,8 +1,10 @@
'use client'
-import { useCallback, useEffect, useState } from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
+import { useFinancialModel } from '@/lib/hooks/useFinancialModel'
+import { computeAnnualKPIs } from '@/lib/finanzplan/annual-kpis'
import ProjectionFooter from '../ui/ProjectionFooter'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
@@ -11,6 +13,7 @@ import { RefreshCw, Download, ChevronLeft, ChevronRight, BarChart3, Target } fro
interface FinanzplanSlideProps {
lang: Language
+ investorId?: string | null
}
interface SheetMeta {
@@ -57,7 +60,7 @@ function formatCell(v: number | undefined): string {
return Math.round(v).toLocaleString('de-DE', { maximumFractionDigits: 0 })
}
-export default function FinanzplanSlide({ lang }: FinanzplanSlideProps) {
+export default function FinanzplanSlide({ lang, investorId }: FinanzplanSlideProps) {
const [sheets, setSheets] = useState([])
const [activeSheet, setActiveSheet] = useState('personalkosten')
const [rows, setRows] = useState([])
@@ -66,6 +69,13 @@ export default function FinanzplanSlide({ lang }: FinanzplanSlideProps) {
const [yearOffset, setYearOffset] = useState(0) // 0=2026, 1=2027, ...
const de = lang === 'de'
+ // Financial model — same source as FinancialsSlide (Slide 15)
+ const fm = useFinancialModel(investorId || null)
+ const annualKPIs = useMemo(
+ () => computeAnnualKPIs(fm.activeResults?.results || []),
+ [fm.activeResults],
+ )
+
// Load sheet list
useEffect(() => {
fetch('/api/finanzplan')
@@ -191,24 +201,25 @@ export default function FinanzplanSlide({ lang }: FinanzplanSlideProps) {
{(() => {
- // Compute KPIs from loaded data — we need liquidität and umsatz data
- // These are approximate since we don't have all sheets loaded simultaneously
+ if (annualKPIs.length === 0) return (
+ | {de ? 'Finanzmodell wird geladen...' : 'Loading financial model...'} |
+ )
const kpiRows = [
- { label: 'MRR (Dez)', values: [6100, 84450, 267950, 517650, 834750], unit: '€', bold: true },
- { label: 'ARR', values: [73200, 1013400, 3215400, 6211800, 10017000], unit: '€', bold: true },
- { label: de ? 'Kunden (Dez)' : 'Customers (Dec)', values: [14, 117, 370, 726, 1200], unit: '', bold: false },
- { label: 'ARPU (MRR/Kunden)', values: [436, 722, 724, 713, 696], unit: '€', bold: false },
- { label: de ? 'Mitarbeiter' : 'Employees', values: [5, 10, 17, 25, 35], unit: '', bold: false },
- { label: de ? 'Umsatz/Mitarbeiter' : 'Revenue/Employee', values: [14640, 101340, 189141, 248472, 286200], unit: '€', bold: false },
- { label: de ? 'Personalkosten' : 'Personnel Costs', values: [58768, 740968, 1353764, 2154301, 3129479], unit: '€', bold: false },
- { label: 'EBIT', values: [-95099, -566293, -4019, 1315689, 3144137], unit: '€', bold: true },
- { label: de ? 'EBIT-Marge' : 'EBIT Margin', values: [-130, -56, -1, 21, 31], unit: '%', bold: false },
- { label: de ? 'Steuern' : 'Taxes', values: [0, 0, 0, 182565, 882717], unit: '€', bold: false },
- { label: de ? 'Jahresüberschuss' : 'Net Income', values: [-95099, -566293, -4019, 1133124, 2261420], unit: '€', bold: true },
- { label: de ? 'Serverkosten/Kunde' : 'Server Cost/Customer', values: [100, 100, 100, 100, 100], unit: '€', bold: false },
- { label: de ? 'Bruttomarge' : 'Gross Margin', values: [100, 100, 92, 90, 88], unit: '%', bold: false },
- { label: 'Burn Rate (Dez)', values: [44734, 28364, 0, 0, 0], unit: '€/Mo', bold: false },
- { label: de ? 'Runway (Monate)' : 'Runway (months)', values: [19, 4, '∞', '∞', '∞'], unit: '', bold: false },
+ { label: 'MRR (Dez)', values: annualKPIs.map(k => k.mrr), unit: '€', bold: true },
+ { label: 'ARR', values: annualKPIs.map(k => k.arr), unit: '€', bold: true },
+ { label: de ? 'Kunden (Dez)' : 'Customers (Dec)', values: annualKPIs.map(k => k.customers), unit: '', bold: false },
+ { label: 'ARPU (MRR/Kunden)', values: annualKPIs.map(k => k.arpu), unit: '€', bold: false },
+ { label: de ? 'Mitarbeiter' : 'Employees', values: annualKPIs.map(k => k.employees), unit: '', bold: false },
+ { label: de ? 'Umsatz/Mitarbeiter' : 'Revenue/Employee', values: annualKPIs.map(k => k.revenuePerEmployee), unit: '€', bold: false },
+ { label: de ? 'Personalkosten' : 'Personnel Costs', values: annualKPIs.map(k => k.personnelCosts), unit: '€', bold: false },
+ { label: 'EBIT', values: annualKPIs.map(k => k.ebit), unit: '€', bold: true },
+ { label: de ? 'EBIT-Marge' : 'EBIT Margin', values: annualKPIs.map(k => k.ebitMargin), unit: '%', bold: false },
+ { label: de ? 'Steuern' : 'Taxes', values: annualKPIs.map(k => k.taxes), unit: '€', bold: false },
+ { label: de ? 'Jahresueberschuss' : 'Net Income', values: annualKPIs.map(k => k.netIncome), unit: '€', bold: true },
+ { label: de ? 'Serverkosten/Kunde' : 'Server Cost/Customer', values: annualKPIs.map(k => k.serverCostPerCustomer), unit: '€', bold: false },
+ { label: de ? 'Bruttomarge' : 'Gross Margin', values: annualKPIs.map(k => k.grossMargin), unit: '%', bold: false },
+ { label: 'Burn Rate (Dez)', values: annualKPIs.map(k => k.burnRate), unit: '€/Mo', bold: false },
+ { label: de ? 'Runway (Monate)' : 'Runway (months)', values: annualKPIs.map(k => k.runway === null ? '∞' : k.runway), unit: '', bold: false },
]
return kpiRows.map((row, idx) => (
@@ -241,13 +252,11 @@ export default function FinanzplanSlide({ lang }: FinanzplanSlideProps) {
{de ? 'MRR & Kundenentwicklung' : 'MRR & Customer Growth'}
- {[
- { year: '2026', mrr: 6100, cust: 14, max_mrr: 834750, max_cust: 1200 },
- { year: '2027', mrr: 84450, cust: 117, max_mrr: 834750, max_cust: 1200 },
- { year: '2028', mrr: 267950, cust: 370, max_mrr: 834750, max_cust: 1200 },
- { year: '2029', mrr: 517650, cust: 726, max_mrr: 834750, max_cust: 1200 },
- { year: '2030', mrr: 834750, cust: 1200, max_mrr: 834750, max_cust: 1200 },
- ].map((d, idx) => (
+ {(() => {
+ const maxMrr = Math.max(...annualKPIs.map(k => k.mrr), 1)
+ const maxCust = Math.max(...annualKPIs.map(k => k.customers), 1)
+ return annualKPIs.map(k => ({ year: String(k.year), mrr: k.mrr, cust: k.customers, max_mrr: maxMrr, max_cust: maxCust }))
+ })().map((d, idx) => (
{/* MRR bar */}
@@ -276,56 +285,50 @@ export default function FinanzplanSlide({ lang }: FinanzplanSlideProps) {
EBIT
- {[
- { year: '2026', val: -95099 },
- { year: '2027', val: -566293 },
- { year: '2028', val: -4019 },
- { year: '2029', val: 1315689 },
- { year: '2030', val: 3144137 },
- ].map((d, idx) => {
- const maxAbs = 3144137
- const h = Math.abs(d.val) / maxAbs * 100
- return (
-
-
- {d.val >= 0 ? (
-
-
{Math.round(d.val/1000)}k
-
- ) : (
-
-
-
{Math.round(d.val/1000)}k
+ {(() => {
+ const maxAbs = Math.max(...annualKPIs.map(k => Math.abs(k.ebit)), 1)
+ return annualKPIs.map((k, idx) => {
+ const h = Math.abs(k.ebit) / maxAbs * 100
+ return (
+
+
+ {k.ebit >= 0 ? (
+
+
{Math.round(k.ebit/1000)}k
-
- )}
+ ) : (
+
+
+
{Math.round(k.ebit/1000)}k
+
+
+ )}
+
+
{k.year}
-
{d.year}
-
- )
- })}
+ )
+ })
+ })()}
{de ? 'Personalaufbau' : 'Headcount'}
- {[
- { year: '2026', val: 5 },
- { year: '2027', val: 10 },
- { year: '2028', val: 17 },
- { year: '2029', val: 25 },
- { year: '2030', val: 35 },
- ].map((d, idx) => (
-
-
-
-
{d.val}
+ {(() => {
+ const maxEmp = Math.max(...annualKPIs.map(k => k.employees), 1)
+ return annualKPIs.map(k => ({ year: String(k.year), val: k.employees }))
+ .map((d, idx) => (
+
-
-
{d.year}
-
- ))}
+ ))
+ })()}
diff --git a/pitch-deck/lib/finanzplan/annual-kpis.ts b/pitch-deck/lib/finanzplan/annual-kpis.ts
new file mode 100644
index 0000000..1746285
--- /dev/null
+++ b/pitch-deck/lib/finanzplan/annual-kpis.ts
@@ -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 (2026–2030).
+ * 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,
+ }
+}