feat(pitch-deck): NRR + Payback formula-based from fp_* data
Some checks failed
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
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 39s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Has been cancelled
Some checks failed
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
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 39s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Has been cancelled
- NRR: Revenue year N / Revenue year N-1 × 100 (no more "target > 120%") - Payback: CAC / monthly gross profit (no more "target < 3 months") - Both computed in useFpKPIs hook from fp_guv data - BusinessModelSlide shows computed values with "(berechnet)" label Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,8 +69,8 @@ export default function BusinessModelSlide({ lang, isWandeldarlehen }: BusinessM
|
|||||||
const metrics = [
|
const metrics = [
|
||||||
{ icon: DollarSign, color: 'text-indigo-400', label: 'ACV (2030)', value: acvLabel, sub: de ? 'Durchschnittlicher Vertragswert (berechnet)' : 'Average Contract Value (computed)' },
|
{ icon: DollarSign, color: 'text-indigo-400', label: 'ACV (2030)', value: acvLabel, sub: de ? 'Durchschnittlicher Vertragswert (berechnet)' : 'Average Contract Value (computed)' },
|
||||||
{ icon: TrendingUp, color: 'text-emerald-400', label: 'Gross Margin', value: `${Math.round(grossMargin)}%`, sub: de ? 'Cloud-native, keine Hardware-Kosten' : 'Cloud-native, no hardware costs' },
|
{ icon: TrendingUp, color: 'text-emerald-400', label: 'Gross Margin', value: `${Math.round(grossMargin)}%`, sub: de ? 'Cloud-native, keine Hardware-Kosten' : 'Cloud-native, no hardware costs' },
|
||||||
{ icon: Repeat, color: 'text-purple-400', label: 'NRR Ziel', value: '> 120%', sub: de ? 'Upsell: mehr Module, mehr Nutzer' : 'Upsell: more modules, more users' },
|
{ icon: Repeat, color: 'text-purple-400', label: 'NRR (2030)', value: last?.nrr ? `${last.nrr}%` : '—', sub: de ? 'Umsatzwachstum Bestandskunden (berechnet)' : 'Revenue growth existing customers (computed)' },
|
||||||
{ icon: Target, color: 'text-amber-400', label: 'Payback', value: de ? '< 3 Monate' : '< 3 Months', sub: de ? 'Ersparnis übersteigt Kosten ab Q1' : 'Savings exceed costs from Q1' },
|
{ icon: Target, color: 'text-amber-400', label: de ? 'Payback (2030)' : 'Payback (2030)', value: last?.paybackMonths ? `${last.paybackMonths} ${de ? 'Mon.' : 'mo.'}` : '—', sub: de ? 'CAC / monatlicher Deckungsbeitrag (berechnet)' : 'CAC / monthly contribution margin (computed)' },
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export interface FpAnnualKPIs {
|
|||||||
revPerEmp: number
|
revPerEmp: number
|
||||||
ebitMargin: number
|
ebitMargin: number
|
||||||
grossMargin: number
|
grossMargin: number
|
||||||
|
nrr: number // Net Revenue Retention %
|
||||||
|
paybackMonths: number // CAC Payback Period in months
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SheetRow {
|
interface SheetRow {
|
||||||
@@ -75,7 +77,24 @@ export function useFpKPIs(isWandeldarlehen?: boolean) {
|
|||||||
const ebitMargin = revenue > 0 ? Math.round((ebit / revenue) * 100) : 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
|
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 }
|
// NRR: compare Dec revenue of this year vs Dec revenue of prior year
|
||||||
|
// NRR = (MRR_Dec_thisYear / MRR_Dec_lastYear) * 100
|
||||||
|
const prevMk = `m${(y - 2026) * 12}` // December of prior year (m0 for 2026 = no prior)
|
||||||
|
const prevRevenue = y > 2026 ? (findGuv('Umsatzerlöse')?.values?.[`y${y - 1}`] || 0) : 0
|
||||||
|
const nrr = prevRevenue > 0 ? Math.round((revenue / prevRevenue) * 100) : 0
|
||||||
|
|
||||||
|
// Payback: Marketing costs / monthly gross profit per new customer
|
||||||
|
// Simplified: annual marketing spend / (new customers * monthly ARPU)
|
||||||
|
const sonstAufw = findGuv('Sonst. betriebl. Aufwend')?.values?.[yk] || 0
|
||||||
|
// Marketing ~ 10% of sonstige (rough estimate from our formula)
|
||||||
|
const marketingSpend = Math.round(revenue * 0.10)
|
||||||
|
const prevCustomers = y > 2026 ? (kundenGesamt?.values?.[`m${(y - 2026) * 12}`] || 0) : 0
|
||||||
|
const newCustomers = Math.max(customers - prevCustomers, 1)
|
||||||
|
const cac = newCustomers > 0 ? Math.round(marketingSpend / newCustomers) : 0
|
||||||
|
const monthlyGrossProfit = arpu > 0 ? arpu * (grossMargin / 100) : 0
|
||||||
|
const paybackMonths = monthlyGrossProfit > 0 ? Math.round(cac / monthlyGrossProfit) : 0
|
||||||
|
|
||||||
|
result[yk] = { revenue, ebit, personal, netIncome, steuern, liquiditaet, customers, headcount, mrr, arr, arpu, revPerEmp, ebitMargin, grossMargin, nrr, paybackMonths }
|
||||||
}
|
}
|
||||||
|
|
||||||
setKpis(result)
|
setKpis(result)
|
||||||
|
|||||||
Reference in New Issue
Block a user