fix(pitch-deck): all financial slides now read from fp_* tables via useFpKPIs
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m18s
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 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 32s
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m18s
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 34s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 32s
New shared hook: useFpKPIs — loads annual KPIs from fp_guv/liquiditaet/personal/kunden. Replaces useFinancialModel (simplified model) for KPI display on all slides. Slides updated: - CompetitionSlide: "110 Gesetze" → "380+ Regularien & Normen" - BusinessModelSlide: ACV + Gross Margin from fp_* (was useFinancialModel) - ExecutiveSummarySlide: Unternehmensentwicklung from fp_* (was useFinancialModel) - FinancialsSlide: KPI cards from fp_* (ARR, Customers, Break-Even, EBIT 2030) All slides now show consistent numbers from the same source of truth (fp_* tables). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
90
pitch-deck/lib/hooks/useFpKPIs.ts
Normal file
90
pitch-deck/lib/hooks/useFpKPIs.ts
Normal file
@@ -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<string, number>
|
||||
values_total?: Record<string, number>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Record<string, FpAnnualKPIs>>({})
|
||||
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<string, FpAnnualKPIs> = {}
|
||||
|
||||
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 }
|
||||
}
|
||||
Reference in New Issue
Block a user