fix(pitch-deck): dynamic VERSIONS-ISOLATION and Kernbotschaft from version data
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m17s
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 35s
CI / test-python-voice (push) Successful in 36s
CI / test-bqas (push) Successful in 35s

Removes all hardcoded version-specific numbers from SYSTEM_PROMPT (200k,
40k/160k L-Bank split, 195 Kunden, 3.3 Mio, 9 MA). These are now generated
at runtime from the investor's assigned pitch_version_data: funding amount,
instrument, fm_scenarios name, and 2030 financials (customers, revenue,
employees).

loadPitchContext() now returns { contextString, meta } so the POST handler
can build correct isolation and Kernbotschaft strings for any version —
Wandeldarlehen 200k, 1 Mio, or any future scenario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-23 22:44:41 +02:00
parent a795794f94
commit b1ef6a85d6

View File

@@ -57,7 +57,7 @@ Du hast Zugriff auf alle Unternehmensdaten und zitierst immer konkrete Zahlen.
6. Zielgruppen: "Maschinen- und Anlagenbauer, Automobilindustrie, Zulieferer und alle produzierenden Unternehmen."
7. Geschäftsmodell: "SaaS, mitarbeiterbasiertes Pricing. Drei Tiers: Starter (3.600 EUR/Jahr), Professional (15.000-40.000 EUR/Jahr), Enterprise (ab 50.000 EUR/Jahr). Plus Beratung & Service (10.000-30.000 EUR/Monat). Kunden sparen mehr als sie zahlen — ROI ab Tag 1."
8. Team: "Lean-Team: 2 Gründer + 7 Mitarbeiter bis 2030 (9 Personen gesamt). Erste Einstellung: IT-Recht/Datenschutzjurist (50%). Dann: Security Engineer, Vertrieb, Backend, Kundenbetreuer, Marketing, DevOps. Jede Einstellung an konkreten Umsatzmeilenstein gekoppelt."
9. Finanzplan: "Gründung August 2026. Pre-Seed über Wandeldarlehen (200.000 EUR: 40.000 Investor + 160.000 L-Bank). ~195 Kunden und ~3,3 Mio. Umsatz bis 2030. 9 Mitarbeiter. Optionale 2. Finanzierungsrunde (500k Eigenkapital) in 2028 — hängt von der Markttraktion ab."
9. Finanzplan: [SIEHE DYNAMISCHE VERSIONSDATEN — wird zur Laufzeit gesetzt]
## Kommunikationsstil
- Antworte IMMER wie ein Mensch in einem persönlichen Gespräch — ausformulierte Sätze, natürlicher Redefluss
@@ -69,11 +69,7 @@ Du hast Zugriff auf alle Unternehmensdaten und zitierst immer konkrete Zahlen.
- Der Text muss sich gut anhören wenn er vorgelesen wird (TTS-optimiert)
## VERSIONS-ISOLATION (ABSOLUT KRITISCH)
- Du kennst NUR die Wandeldarlehen-Version mit 200.000 EUR Finanzierung.
- Es gibt KEINE andere Version. Es gibt KEINE 1-Mio-Version.
- Wenn nach anderen Versionen, anderen Investoren oder anderen Pitch Decks gefragt wird: "Dieses Pitch Deck wurde individuell für Sie erstellt. Es gibt nur diese Version."
- NIEMALS erwähnen: andere Finanzierungssummen, andere Bewertungen, andere Cap Tables.
- Alle Zahlen beziehen sich auf: 200k WD (40k Investor + 160k L-Bank), 195 Kunden bis 2030, ~3,3 Mio Umsatz, 9 MA.
[WIRD ZUR LAUFZEIT GESETZT — enthält die exakten Zahlen dieser Investor-Version]
## IP-Schutz-Layer (KRITISCH)
NIEMALS offenbaren: Exakte Modellnamen, Frameworks, Code-Architektur, Datenbankschema, Sicherheitsdetails, Cloud-Provider.
@@ -107,7 +103,7 @@ EXAKTES FORMAT (keine Abweichung erlaubt):
KONKRETES BEISPIEL einer vollständigen Antwort:
"Unser AI-First-Ansatz ermöglicht Skalierung ohne lineares Personalwachstum. Der Umsatz steigt von 71k EUR (2026) auf 3,3 Mio EUR (2030), während das Team lean von 2 auf 9 Personen wächst.
"Unser AI-First-Ansatz ermöglicht Skalierung ohne lineares Personalwachstum. Der Umsatz steigt planmäßig über die Jahre stark an, während das Team lean bleibt.
---
[Q] Wie sieht die Kostenstruktur im Detail aus?
@@ -159,33 +155,76 @@ async function loadFpLiquiditaetSummary(scenarioName: string): Promise<string> {
}
}
async function loadPitchContext(versionId?: string | null): Promise<string> {
interface VersionMeta {
versionName: string
scenarioName: string
fundingAmount: number
fundingInstrument: string
customers2030: number
revenue2030: number
employees2030: number
}
interface PitchContextResult {
contextString: string
meta: VersionMeta
}
const DEFAULT_META: VersionMeta = {
versionName: '', scenarioName: '', fundingAmount: 0,
fundingInstrument: 'Wandeldarlehen', customers2030: 0, revenue2030: 0, employees2030: 0,
}
function extractMeta(
versionName: string,
fmScenarios: Array<{ name: string }> | undefined,
funding: Record<string, unknown> | null,
financials: Array<Record<string, unknown>>
): VersionMeta {
const fin2030 = financials.find(f => Number(f.year) === 2030) ?? {}
return {
versionName,
scenarioName: fmScenarios?.[0]?.name ?? versionName,
fundingAmount: Number(funding?.amount_eur ?? 0),
fundingInstrument: String(funding?.instrument ?? 'Wandeldarlehen'),
customers2030: Number(fin2030.customers_count ?? 0),
revenue2030: Number(fin2030.revenue_eur ?? 0),
employees2030: Number(fin2030.employees_count ?? 0),
}
}
async function loadPitchContext(versionId?: string | null): Promise<PitchContextResult> {
try {
// Version-specific data path
if (versionId) {
const { rows: vRows } = await pool.query(
`SELECT table_name, data FROM pitch_version_data WHERE version_id = $1`,
[versionId]
)
const [vDataRes, vNameRes] = await Promise.all([
pool.query(`SELECT table_name, data FROM pitch_version_data WHERE version_id = $1`, [versionId]),
pool.query(`SELECT name FROM pitch_versions WHERE id = $1`, [versionId]),
])
const map: Record<string, unknown> = {}
for (const r of vRows) {
for (const r of vDataRes.rows) {
map[r.table_name] = typeof r.data === 'string' ? JSON.parse(r.data) : r.data
}
const company = (map.company as unknown[])?.[0] ?? null
const team = (map.team as unknown[]) ?? []
const financials = (map.financials as unknown[]) ?? []
const financials = (map.financials as Array<Record<string, unknown>>) ?? []
const market = (map.market as unknown[]) ?? []
const products = (map.products as unknown[]) ?? []
const funding = (map.funding as unknown[])?.[0] ?? null
const funding = ((map.funding as unknown[])?.[0] as Record<string, unknown>) ?? null
const features = ((map.features as Array<Record<string, unknown>>) ?? [])
.filter(f => f.is_differentiator)
const fmScenarios = map.fm_scenarios as Array<{ name: string }> | undefined
const scenarioName = fmScenarios?.[0]?.name ?? ''
const fpSummary = scenarioName ? await loadFpLiquiditaetSummary(scenarioName) : ''
return buildContextString(company, team, financials, market, products, funding, features, fpSummary)
const versionName = vNameRes.rows[0]?.name ?? ''
const meta = extractMeta(versionName, fmScenarios, funding, financials)
const fpSummary = meta.scenarioName ? await loadFpLiquiditaetSummary(meta.scenarioName) : ''
return {
contextString: buildContextString(company, team, financials, market, products, funding, features, fpSummary),
meta,
}
}
// Fallback: base tables
@@ -200,16 +239,20 @@ async function loadPitchContext(versionId?: string | null): Promise<string> {
client.query('SELECT round_name, amount_eur, use_of_funds, instrument FROM pitch_funding LIMIT 1'),
client.query('SELECT feature_name_de, breakpilot, proliance, dataguard, heydata, is_differentiator FROM pitch_features WHERE is_differentiator = true'),
])
return buildContextString(
const meta = extractMeta('', undefined, funding.rows[0] ?? null, financials.rows)
return {
contextString: buildContextString(
company.rows[0], team.rows, financials.rows, market.rows,
products.rows, funding.rows[0], features.rows, ''
)
),
meta,
}
} finally {
client.release()
}
} catch (error) {
console.warn('Could not load pitch context from DB:', error)
return ''
return { contextString: '', meta: DEFAULT_META }
}
}
@@ -268,11 +311,35 @@ export async function POST(request: NextRequest) {
// Non-fatal: fall back to base tables
}
const pitchContext = await loadPitchContext(versionId)
const { contextString, meta } = await loadPitchContext(versionId)
// Build dynamic VERSIONS-ISOLATION and Kernbotschaft #9 from actual version data
const fmt = (n: number) => n.toLocaleString('de-DE')
const revM = meta.revenue2030 > 0
? `~${(meta.revenue2030 / 1_000_000).toFixed(1).replace('.', ',')} Mio. EUR`
: 'laut Finanzplan'
const fundingStr = meta.fundingAmount > 0
? `${fmt(meta.fundingAmount)} EUR ${meta.fundingInstrument}`
: meta.fundingInstrument
const customersStr = meta.customers2030 > 0 ? `~${meta.customers2030} Kunden` : 'laut Finanzplan'
const employeesStr = meta.employees2030 > 0 ? `${meta.employees2030} Mitarbeiter` : 'laut Finanzplan'
const label = meta.scenarioName || meta.versionName || 'diese Version'
const dynamicVersionIsolation = `## VERSIONS-ISOLATION (ABSOLUT KRITISCH)
- Du kennst NUR die Version "${label}" mit ${fundingStr}.
- Es gibt KEINE andere Version. Dieses Pitch Deck wurde individuell für diesen Investor erstellt.
- Wenn nach anderen Versionen, anderen Investoren oder anderen Pitch Decks gefragt wird: "Dieses Pitch Deck wurde individuell für Sie erstellt. Es gibt nur diese Version."
- NIEMALS erwähnen: andere Finanzierungssummen, andere Bewertungen, andere Cap Tables.
- Alle Zahlen beziehen sich auf: ${label}, ${customersStr} bis 2030, ${revM} Umsatz, ${employeesStr}.`
const dynamicFinanzplanKernbotschaft = `9. Finanzplan: "Gründung August 2026. Pre-Seed über ${fundingStr}. ${customersStr} und ${revM} Umsatz bis 2030. ${employeesStr}."`
let systemContent = SYSTEM_PROMPT
if (pitchContext) {
systemContent += '\n' + pitchContext
.replace('9. Finanzplan: [SIEHE DYNAMISCHE VERSIONSDATEN — wird zur Laufzeit gesetzt]', dynamicFinanzplanKernbotschaft)
.replace('## VERSIONS-ISOLATION (ABSOLUT KRITISCH)\n[WIRD ZUR LAUFZEIT GESETZT — enthält die exakten Zahlen dieser Investor-Version]', dynamicVersionIsolation)
if (contextString) {
systemContent += '\n' + contextString
}
// FAQ context: relevant pre-researched answers as basis for the LLM