fix: derive fp_scenario IDs from version snapshot, eliminate hardcoded UUIDs
Build pitch-deck / build-push-deploy (push) Successful in 1m30s
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 33s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 31s

The fm_scenarios array in each pitch version snapshot already stores the
fp_scenario IDs directly (same pattern 1 Mio used). Wandeldarlehen snapshots
were missing Bear/Bull entries — updated in DB to add them.

- /api/data: include fp_scenarios in version response (was omitted)
- PitchDeck: derive fpBaseScenarioId from data.fp_scenarios
- useFpKPIs: accept fpBaseScenarioId instead of isWandeldarlehen boolean
- AssumptionsSlide: find Bear/Base/Bull by name from fpScenarios prop
- FinanzplanSlide: initialize from fpBaseScenarioId, use version scenarios for selector
- FinancialsSlide / ExecutiveSummarySlide: pass fpBaseScenarioId to hook
- types: add FpScenarioRef + fp_scenarios field to PitchData

No UUID hardcoded in any component. Adding a new pitch version only
requires setting the correct fp_scenario IDs in its fm_scenarios snapshot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-05-04 15:00:06 +02:00
parent 6c022d1a79
commit 06014d57b3
8 changed files with 52 additions and 35 deletions
@@ -1,7 +1,7 @@
'use client'
import { useEffect, useState } from 'react'
import { Language } from '@/lib/types'
import { Language, FpScenarioRef } from '@/lib/types'
import { t } from '@/lib/i18n'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
@@ -13,6 +13,7 @@ interface AssumptionsSlideProps {
investorId?: string | null
preferredScenarioId?: string | null
isWandeldarlehen?: boolean
fpScenarios?: FpScenarioRef[]
}
interface SheetRow {
@@ -72,7 +73,7 @@ async function loadScenarioKPIs(scenarioId: string | null): Promise<ScenarioKPIs
}
}
export default function AssumptionsSlide({ lang, isWandeldarlehen }: AssumptionsSlideProps) {
export default function AssumptionsSlide({ lang, isWandeldarlehen, fpScenarios }: AssumptionsSlideProps) {
const i = t(lang)
const de = lang === 'de'
@@ -80,14 +81,19 @@ export default function AssumptionsSlide({ lang, isWandeldarlehen }: Assumptions
useEffect(() => {
async function load() {
const baseId = isWandeldarlehen ? 'c0000000-0000-0000-0000-000000000200' : null
const bearId = isWandeldarlehen ? 'd0000000-0000-0000-0000-000000000201' : 'd0000000-0000-0000-0000-000000000301'
const bullId = isWandeldarlehen ? 'd0000000-0000-0000-0000-000000000202' : 'd0000000-0000-0000-0000-000000000302'
const scenarios = fpScenarios || []
const find = (role: 'bear' | 'bull' | 'base') => {
if (role === 'base') return scenarios.find(s => s.is_default)?.id ?? null
return scenarios.find(s => s.name.toLowerCase().includes(role))?.id ?? null
}
const baseId = find('base')
const bearId = find('bear')
const bullId = find('bull')
const [bear, base, bull] = await Promise.all([loadScenarioKPIs(bearId), loadScenarioKPIs(baseId), loadScenarioKPIs(bullId)])
setScenarioData({ bear, base, bull })
}
load()
}, [isWandeldarlehen])
}, [fpScenarios])
const bear = scenarioData?.bear || { arr: 0, customers: 0, headcount: 0, cash: 0, breakEvenYear: '—' }
const base = scenarioData?.base || { arr: 0, customers: 0, headcount: 0, cash: 0, breakEvenYear: '—' }
@@ -16,15 +16,16 @@ interface ExecutiveSummarySlideProps {
investorId?: string | null
preferredScenarioId?: string | null
isWandeldarlehen?: boolean
fpBaseScenarioId?: string | null
}
export default function ExecutiveSummarySlide({ lang, data, investorId, preferredScenarioId, isWandeldarlehen }: ExecutiveSummarySlideProps) {
export default function ExecutiveSummarySlide({ lang, data, investorId, preferredScenarioId, isWandeldarlehen, fpBaseScenarioId }: ExecutiveSummarySlideProps) {
const i = t(lang)
const es = i.executiveSummary
const de = lang === 'de'
// Unternehmensentwicklung from fp_* tables (source of truth)
const { kpis: fpKPIs } = useFpKPIs(isWandeldarlehen)
const { kpis: fpKPIs } = useFpKPIs(fpBaseScenarioId)
// Pipeline stats from DB
const [pipelineStats, setPipelineStats] = useState<Record<string, { value: number }>>({})
@@ -25,9 +25,10 @@ interface FinancialsSlideProps {
investorId: string | null
preferredScenarioId?: string | null
isWandeldarlehen?: boolean
fpBaseScenarioId?: string | null
}
export default function FinancialsSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen }: FinancialsSlideProps) {
export default function FinancialsSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen, fpBaseScenarioId }: FinancialsSlideProps) {
const i = t(lang)
const fm = useFinancialModel(investorId, preferredScenarioId)
const [activeTab, setActiveTab] = useState<FinTab>('overview')
@@ -38,7 +39,7 @@ export default function FinancialsSlide({ lang, investorId, preferredScenarioId,
const lastResult = activeResults?.results[activeResults.results.length - 1]
// KPI cards from fp_* tables (source of truth)
const { last: fpLast, kpis: fpKPIs } = useFpKPIs(isWandeldarlehen)
const { last: fpLast, kpis: fpKPIs } = useFpKPIs(fpBaseScenarioId)
const kpiArr = fpLast?.arr || summary?.final_arr || 0
const kpiCustomers = fpLast?.customers || summary?.final_customers || 0
const kpiEbit = fpKPIs?.y2029?.ebit // First profitable year
@@ -1,7 +1,7 @@
'use client'
import { useCallback, useEffect, useState } from 'react'
import { Language } from '@/lib/types'
import { Language, FpScenarioRef } from '@/lib/types'
import ProjectionFooter from '../ui/ProjectionFooter'
import GradientText from '../ui/GradientText'
import FadeInView from '../ui/FadeInView'
@@ -20,14 +20,16 @@ interface FinanzplanSlideProps {
investorId?: string | null
preferredScenarioId?: string | null
isWandeldarlehen?: boolean
fpBaseScenarioId?: string | null
fpScenarios?: FpScenarioRef[]
}
export default function FinanzplanSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen }: FinanzplanSlideProps) {
export default function FinanzplanSlide({ lang, investorId, preferredScenarioId, isWandeldarlehen, fpBaseScenarioId, fpScenarios }: FinanzplanSlideProps) {
const [sheets, setSheets] = useState<SheetMeta[]>([])
const [scenarios, setScenarios] = useState<FpScenario[]>([])
const [openCats, setOpenCats] = useState<Set<string>>(new Set())
const toggleCat = (cat: string) => setOpenCats(prev => { const n = new Set(prev); n.has(cat) ? n.delete(cat) : n.add(cat); return n })
const [selectedScenarioId, setSelectedScenarioId] = useState<string>('')
const [selectedScenarioId, setSelectedScenarioId] = useState<string>(fpBaseScenarioId ?? '')
const [activeSheet, setActiveSheet] = useState<string>('guv')
const [rows, setRows] = useState<SheetRow[]>([])
const [loading, setLoading] = useState(false)
@@ -96,18 +98,19 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId,
loadKPIs()
}, [selectedScenarioId])
// Load sheet list + scenarios
// Load sheet list; populate scenario selector from version data or API fallback
useEffect(() => {
fetch('/api/finanzplan', { cache: 'no-store' })
.then(r => r.json())
.then(data => {
setSheets(data.sheets || [])
const scens: FpScenario[] = data.scenarios || []
// Use version fp_scenarios if available, else fall back to API list
const scens: FpScenario[] = fpScenarios && fpScenarios.length > 0
? fpScenarios.map(s => ({ id: s.id, name: s.name, is_default: s.is_default ?? false, color: s.color ?? '#6366f1', description: s.description ?? '' }))
: data.scenarios || []
setScenarios(scens)
// Pick scenario: Wandeldarlehen version → WD scenario, otherwise default
if (!selectedScenarioId) {
const wdScenario = isWandeldarlehen ? scens.find(s => s.name.toLowerCase().includes('wandeldarlehen') && !s.name.toLowerCase().includes('bear') && !s.name.toLowerCase().includes('bull')) : null
const def = wdScenario ?? scens.find(s => s.is_default) ?? scens[0]
const def = scens.find(s => s.is_default) ?? scens[0]
if (def) setSelectedScenarioId(def.id)
}
})