diff --git a/pitch-deck/components/PitchDeck.tsx b/pitch-deck/components/PitchDeck.tsx
index 7a9215a..720cee9 100644
--- a/pitch-deck/components/PitchDeck.tsx
+++ b/pitch-deck/components/PitchDeck.tsx
@@ -161,7 +161,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
/>
)
case 'executive-summary':
- return
+ return
case 'cover':
return
case 'problem':
@@ -179,7 +179,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
case 'market':
return
case 'business-model':
- return
+ return
case 'traction':
return
case 'competition':
@@ -187,7 +187,7 @@ export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout,
case 'team':
return
case 'financials':
- return
+ return
case 'the-ask':
return
case 'cap-table':
diff --git a/pitch-deck/components/slides/BusinessModelSlide.tsx b/pitch-deck/components/slides/BusinessModelSlide.tsx
index deb4fb5..db3d5fb 100644
--- a/pitch-deck/components/slides/BusinessModelSlide.tsx
+++ b/pitch-deck/components/slides/BusinessModelSlide.tsx
@@ -2,7 +2,7 @@
import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
-import { useFinancialModel } from '@/lib/hooks/useFinancialModel'
+import { useFpKPIs } from '@/lib/hooks/useFpKPIs'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
@@ -13,18 +13,17 @@ interface BusinessModelSlideProps {
products?: unknown[]
investorId?: string | null
preferredScenarioId?: string | null
+ isWandeldarlehen?: boolean
}
-export default function BusinessModelSlide({ lang, investorId, preferredScenarioId }: BusinessModelSlideProps) {
+export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessModelSlideProps) {
const i = t(lang)
const de = lang === 'de'
- const fm = useFinancialModel(investorId || null, preferredScenarioId)
- 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 { last } = useFpKPIs(isWandeldarlehen)
+ const finalCustomers = last?.customers || 0
+ const finalArr = last?.arr || 0
const acv = finalCustomers > 0 ? Math.round(finalArr / finalCustomers) : 0
+ const grossMargin = last?.grossMargin ?? 0
const tiers = [
{
@@ -62,7 +61,7 @@ export default function BusinessModelSlide({ lang, investorId, preferredScenario
},
]
- const grossMargin = lastResult?.gross_margin_pct ?? 0
+ // grossMargin already defined above from useFpKPIs
const acvLabel = acv > 0
? (de ? `${(acv / 1000).toFixed(1).replace('.', ',')}k EUR` : `EUR ${(acv / 1000).toFixed(1)}k`)
: '—'
diff --git a/pitch-deck/components/slides/CompetitionSlide.tsx b/pitch-deck/components/slides/CompetitionSlide.tsx
index c9f0577..fb176d6 100644
--- a/pitch-deck/components/slides/CompetitionSlide.tsx
+++ b/pitch-deck/components/slides/CompetitionSlide.tsx
@@ -193,7 +193,7 @@ const ALL_FEATURES: ComparisonFeature[] = [
// Top 5 Differentiators (isDiff=true) — no other vendor has ANY of these
{ de: 'Self-Hosted / On-Premise', en: 'Self-Hosted / On-Premise', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
{ de: 'Code-Security & DevSecOps (6 Tools)', en: 'Code Security & DevSecOps (6 Tools)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
- { de: '110 Gesetze & Regularien, 25.000+ Sicherheitskontrollen', en: '110 Laws & Regulations, 25,000+ Security Controls', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
+ { de: '380+ Regularien & Normen, 25.000+ Prüfaspekte', en: '380+ Regulations & Standards, 25,000+ Audit Controls', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
{ de: 'Hardware-Moat (Mac Mini/Studio)', en: 'Hardware Moat (Mac Mini/Studio)', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
{ de: 'PII-Redaction LLM Gateway', en: 'PII Redaction LLM Gateway', bp: true, vanta: false, drata: false, sprinto: false, proliance: false, dataguard: false, heydata: false, isDiff: true, isUSP: true },
// More USPs
diff --git a/pitch-deck/components/slides/ExecutiveSummarySlide.tsx b/pitch-deck/components/slides/ExecutiveSummarySlide.tsx
index 27d2cb5..3ff9c32 100644
--- a/pitch-deck/components/slides/ExecutiveSummarySlide.tsx
+++ b/pitch-deck/components/slides/ExecutiveSummarySlide.tsx
@@ -1,10 +1,9 @@
'use client'
-import { useCallback, useMemo } from 'react'
+import { useCallback } from 'react'
import { Language, PitchData } from '@/lib/types'
import { t, formatEur } from '@/lib/i18n'
-import { useFinancialModel } from '@/lib/hooks/useFinancialModel'
-import { computeAnnualKPIs } from '@/lib/finanzplan/annual-kpis'
+import { useFpKPIs } from '@/lib/hooks/useFpKPIs'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import GlassCard from '../ui/GlassCard'
@@ -15,19 +14,16 @@ interface ExecutiveSummarySlideProps {
data: PitchData
investorId?: string | null
preferredScenarioId?: string | null
+ isWandeldarlehen?: boolean
}
-export default function ExecutiveSummarySlide({ lang, data, investorId, preferredScenarioId }: ExecutiveSummarySlideProps) {
+export default function ExecutiveSummarySlide({ lang, data, investorId, preferredScenarioId, isWandeldarlehen }: ExecutiveSummarySlideProps) {
const i = t(lang)
const es = i.executiveSummary
const de = lang === 'de'
- // Financial model for Unternehmensentwicklung
- const fm = useFinancialModel(investorId || null, preferredScenarioId)
- const annualKPIs = useMemo(
- () => computeAnnualKPIs(fm.activeResults?.results || []),
- [fm.activeResults],
- )
+ // Unternehmensentwicklung from fp_* tables (source of truth)
+ const { kpis: fpKPIs } = useFpKPIs(isWandeldarlehen)
const funding = data.funding
const amount = funding?.amount_eur || 0
@@ -538,16 +534,18 @@ export default function ExecutiveSummarySlide({ lang, data, investorId, preferre
{de ? 'Jahr' : 'Year'}MA{de ? 'Kunden' : 'Customers'}ARR
- {annualKPIs.length === 0 ? (
+ {!fpKPIs.y2026 ? (
{de ? 'Lade Finanzplan...' : 'Loading financial plan...'}
- ) : annualKPIs.map((k, idx) => {
+ ) : [2026, 2027, 2028, 2029, 2030].map((year, idx) => {
+ const k = fpKPIs[`y${year}`]
+ if (!k) return null
const arrLabel = k.arr >= 1_000_000
? (de ? `~${(k.arr / 1_000_000).toFixed(1).replace('.', ',')} Mio. EUR` : `~EUR ${(k.arr / 1_000_000).toFixed(1)}M`)
: (de ? `~${Math.round(k.arr / 1000)}k EUR` : `~EUR ${Math.round(k.arr / 1000)}k`)
return (
- {k.year}
- {k.employees}
+ {year}
+ {k.headcount}
~{k.customers.toLocaleString('de-DE')}
= 3 ? 'text-emerald-300 font-bold' : 'text-white/70'}`}>{arrLabel}
diff --git a/pitch-deck/components/slides/FinancialsSlide.tsx b/pitch-deck/components/slides/FinancialsSlide.tsx
index 727adff..e25b4f2 100644
--- a/pitch-deck/components/slides/FinancialsSlide.tsx
+++ b/pitch-deck/components/slides/FinancialsSlide.tsx
@@ -5,6 +5,7 @@ import { Language } from '@/lib/types'
import { t } from '@/lib/i18n'
import ProjectionFooter from '../ui/ProjectionFooter'
import { useFinancialModel } from '@/lib/hooks/useFinancialModel'
+import { useFpKPIs } from '@/lib/hooks/useFpKPIs'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
import FinancialChart from '../ui/FinancialChart'
@@ -23,9 +24,10 @@ interface FinancialsSlideProps {
lang: Language
investorId: string | null
preferredScenarioId?: string | null
+ isWandeldarlehen?: boolean
}
-export default function FinancialsSlide({ lang, investorId, preferredScenarioId }: FinancialsSlideProps) {
+export default function FinancialsSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen }: FinancialsSlideProps) {
const i = t(lang)
const fm = useFinancialModel(investorId, preferredScenarioId)
const [activeTab, setActiveTab] = useState
('overview')
@@ -35,6 +37,18 @@ export default function FinancialsSlide({ lang, investorId, preferredScenarioId
const summary = activeResults?.summary
const lastResult = activeResults?.results[activeResults.results.length - 1]
+ // KPI cards from fp_* tables (source of truth)
+ const { last: fpLast, kpis: fpKPIs } = useFpKPIs(isWandeldarlehen)
+ const kpiArr = fpLast?.arr || summary?.final_arr || 0
+ const kpiCustomers = fpLast?.customers || summary?.final_customers || 0
+ const kpiEbit = fpKPIs?.y2029?.ebit // First profitable year
+ const kpiBreakEven = (() => {
+ for (const y of [2026, 2027, 2028, 2029, 2030]) {
+ if ((fpKPIs[`y${y}`]?.ebit || 0) > 0) return y
+ }
+ return 0
+ })()
+
// Build scenario color map
const scenarioColors: Record = {}
fm.scenarios.forEach(s => { scenarioColors[s.id] = s.color })
@@ -74,9 +88,9 @@ export default function FinancialsSlide({ lang, investorId, preferredScenarioId
= 1_000_000 ? Math.round(kpiArr / 1_000_000 * 10) / 10 : Math.round(kpiArr / 1000)}
+ suffix={kpiArr >= 1_000_000 ? ' Mio.' : 'k'}
+ decimals={kpiArr >= 1_000_000 ? 1 : 0}
trend="up"
color="#6366f1"
delay={0.1}
@@ -84,28 +98,28 @@ export default function FinancialsSlide({ lang, investorId, preferredScenarioId
/>
= 3 ? 'up' : 'down'}
+ trend={(fpLast?.ebit || 0) > 0 ? 'up' : 'down'}
color="#a855f7"
delay={0.25}
+ subLabel="EUR"
/>
diff --git a/pitch-deck/lib/hooks/useFpKPIs.ts b/pitch-deck/lib/hooks/useFpKPIs.ts
new file mode 100644
index 0000000..f19d48b
--- /dev/null
+++ b/pitch-deck/lib/hooks/useFpKPIs.ts
@@ -0,0 +1,90 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+
+export interface FpAnnualKPIs {
+ revenue: number
+ ebit: number
+ personal: number
+ netIncome: number
+ steuern: number
+ liquiditaet: number
+ customers: number
+ headcount: number
+ mrr: number
+ arr: number
+ arpu: number
+ revPerEmp: number
+ ebitMargin: number
+ grossMargin: number
+}
+
+interface SheetRow {
+ row_label?: string
+ values?: Record
+ values_total?: Record
+}
+
+/**
+ * Loads annual KPIs directly from fp_* tables (source of truth).
+ * Returns a map of year keys (y2026-y2030) to KPI objects.
+ */
+export function useFpKPIs(isWandeldarlehen?: boolean) {
+ const [kpis, setKpis] = useState>({})
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ async function load() {
+ try {
+ const param = isWandeldarlehen ? '?scenarioId=c0000000-0000-0000-0000-000000000200' : ''
+ const [guvRes, liqRes, persRes, kundenRes] = await Promise.all([
+ fetch(`/api/finanzplan/guv${param}`, { cache: 'no-store' }),
+ fetch(`/api/finanzplan/liquiditaet${param}`, { cache: 'no-store' }),
+ fetch(`/api/finanzplan/personalkosten${param}`, { cache: 'no-store' }),
+ fetch(`/api/finanzplan/kunden${param}`, { cache: 'no-store' }),
+ ])
+ const [guv, liq, pers, kunden] = await Promise.all([guvRes.json(), liqRes.json(), persRes.json(), kundenRes.json()])
+
+ const guvRows: SheetRow[] = guv.rows || []
+ const liqRows: SheetRow[] = liq.rows || []
+ const persRows: SheetRow[] = pers.rows || []
+ const kundenRows: SheetRow[] = kunden.rows || []
+
+ const findGuv = (label: string) => guvRows.find(r => (r.row_label || '').includes(label))
+ const findLiq = (label: string) => liqRows.find(r => (r.row_label || '').includes(label))
+ const kundenGesamt = kundenRows.find(r => r.row_label === 'Bestandskunden gesamt')
+
+ const result: Record = {}
+
+ for (const y of [2026, 2027, 2028, 2029, 2030]) {
+ const yk = `y${y}`
+ const mk = `m${(y - 2026) * 12 + 12}` // December
+
+ const revenue = findGuv('Umsatzerlöse')?.values?.[yk] || 0
+ const ebit = findGuv('EBIT')?.values?.[yk] || 0
+ const personal = findGuv('Summe Personalaufwand')?.values?.[yk] || 0
+ const netIncome = findGuv('Jahresüberschuss')?.values?.[yk] || findGuv('Jahresueber')?.values?.[yk] || 0
+ const steuern = findGuv('Steuern gesamt')?.values?.[yk] || 0
+ const liquiditaet = findLiq('LIQUIDIT')?.values?.[mk] || findLiq('LIQUIDITAET')?.values?.[mk] || 0
+ const customers = kundenGesamt?.values?.[mk] || 0
+ const headcount = persRows.filter(r => ((r.values_total || r.values)?.[mk] || 0) > 0).length
+ const mrr = revenue > 0 ? Math.round(revenue / 12) : 0
+ const arr = mrr * 12
+ const arpu = customers > 0 ? Math.round(mrr / customers) : 0
+ const revPerEmp = headcount > 0 ? Math.round(revenue / headcount) : 0
+ const ebitMargin = revenue > 0 ? Math.round((ebit / revenue) * 100) : 0
+ const grossMargin = revenue > 0 ? Math.round(((revenue - (findGuv('Summe Materialaufwand')?.values?.[yk] || 0)) / revenue) * 100) : 0
+
+ result[yk] = { revenue, ebit, personal, netIncome, steuern, liquiditaet, customers, headcount, mrr, arr, arpu, revPerEmp, ebitMargin, grossMargin }
+ }
+
+ setKpis(result)
+ } catch { /* ignore */ }
+ setLoading(false)
+ }
+ load()
+ }, [isWandeldarlehen])
+
+ const last = kpis.y2030
+ return { kpis, loading, last }
+}