fix(pitch-deck): chat agent now uses investor's assigned version scenario
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m25s
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 35s
CI / test-bqas (push) Successful in 31s

loadPitchContext() now accepts a versionId and loads data from
pitch_version_data instead of hardcoded base table queries, matching
the pattern used by /api/data and /api/financial-model.

Also pulls fp_liquiditaet yearly summaries (LIQUIDITÄT, Summe ERTRÄGE,
etc.) for the matching fp_scenario so the agent quotes the correct
finanzplan numbers. Falls back to base tables when no version is assigned.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-23 22:24:13 +02:00
parent 71b6f8f181
commit 4e27e05512

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server' import { NextRequest, NextResponse } from 'next/server'
import pool from '@/lib/db' import pool from '@/lib/db'
import { getSessionFromCookie } from '@/lib/auth'
import { SLIDE_ORDER } from '@/lib/slide-order' import { SLIDE_ORDER } from '@/lib/slide-order'
const LITELLM_URL = process.env.LITELLM_URL || 'https://llm-dev.meghsakha.com' const LITELLM_URL = process.env.LITELLM_URL || 'https://llm-dev.meghsakha.com'
@@ -115,8 +116,79 @@ KONKRETES BEISPIEL einer vollständigen Antwort:
WICHTIG: Vergiss NIEMALS die Folgefragen! Sie sind PFLICHT.` WICHTIG: Vergiss NIEMALS die Folgefragen! Sie sind PFLICHT.`
async function loadPitchContext(): Promise<string> { async function loadFpLiquiditaetSummary(scenarioName: string): Promise<string> {
try { try {
const { rows } = await pool.query(
`SELECT l.row_label, l.values
FROM fp_liquiditaet l
JOIN fp_scenarios s ON s.id = l.scenario_id
WHERE s.name = $1
AND l.row_label IN ('LIQUIDITÄT', 'LIQUIDITAET', 'Summe ERTRÄGE', 'Summe EINZAHLUNGEN', 'Summe AUSZAHLUNGEN', 'ÜBERSCHUSS')
ORDER BY l.sort_order`,
[scenarioName]
)
if (rows.length === 0) return ''
const years = [2026, 2027, 2028, 2029, 2030]
const summary: Record<string, Record<number, number>> = {}
for (const row of rows) {
const label = row.row_label === 'LIQUIDITAET' ? 'LIQUIDITÄT' : row.row_label
summary[label] = {}
for (let yi = 0; yi < years.length; yi++) {
const start = yi * 12 + 1
const end = start + 11
// LIQUIDITÄT is a balance (end-of-period): use December value
if (label === 'LIQUIDITÄT') {
summary[label][years[yi]] = Math.round(row.values[`m${end}`] || 0)
} else {
let s = 0
for (let m = start; m <= end; m++) s += row.values[`m${m}`] || 0
summary[label][years[yi]] = Math.round(s)
}
}
}
const lines = [`### Finanzplan-Liquidität (Szenario: ${scenarioName})`]
for (const [label, yearVals] of Object.entries(summary)) {
const vals = years.map(y => `${y}: ${(yearVals[y] || 0).toLocaleString('de-DE')} EUR`).join(' | ')
lines.push(`${label}: ${vals}`)
}
return lines.join('\n')
} catch {
return ''
}
}
async function loadPitchContext(versionId?: string | null): Promise<string> {
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 map: Record<string, unknown> = {}
for (const r of vRows) {
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 market = (map.market as unknown[]) ?? []
const products = (map.products as unknown[]) ?? []
const funding = (map.funding as unknown[])?.[0] ?? 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)
}
// Fallback: base tables
const client = await pool.connect() const client = await pool.connect()
try { try {
const [company, team, financials, market, products, funding, features] = await Promise.all([ const [company, team, financials, market, products, funding, features] = await Promise.all([
@@ -128,31 +200,10 @@ async function loadPitchContext(): Promise<string> {
client.query('SELECT round_name, amount_eur, use_of_funds, instrument FROM pitch_funding LIMIT 1'), 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'), client.query('SELECT feature_name_de, breakpilot, proliance, dataguard, heydata, is_differentiator FROM pitch_features WHERE is_differentiator = true'),
]) ])
return buildContextString(
return ` company.rows[0], team.rows, financials.rows, market.rows,
## Unternehmensdaten (für präzise Antworten nutzen) products.rows, funding.rows[0], features.rows, ''
)
### Firma
${JSON.stringify(company.rows[0], null, 2)}
### Team
${JSON.stringify(team.rows, null, 2)}
### Finanzprognosen (5-Jahres-Plan)
${JSON.stringify(financials.rows, null, 2)}
### Markt (TAM/SAM/SOM)
${JSON.stringify(market.rows, null, 2)}
### Produkte
${JSON.stringify(products.rows, null, 2)}
### Finanzierung
${JSON.stringify(funding.rows[0], null, 2)}
### Differenzierende Features (nur bei ComplAI)
${JSON.stringify(features.rows, null, 2)}
`
} finally { } finally {
client.release() client.release()
} }
@@ -162,6 +213,37 @@ ${JSON.stringify(features.rows, null, 2)}
} }
} }
function buildContextString(
company: unknown, team: unknown, financials: unknown, market: unknown,
products: unknown, funding: unknown, features: unknown, fpSummary: string
): string {
return `
## Unternehmensdaten (für präzise Antworten nutzen)
### Firma
${JSON.stringify(company, null, 2)}
### Team
${JSON.stringify(team, null, 2)}
### Finanzprognosen (5-Jahres-Plan)
${JSON.stringify(financials, null, 2)}
### Markt (TAM/SAM/SOM)
${JSON.stringify(market, null, 2)}
### Produkte
${JSON.stringify(products, null, 2)}
### Finanzierung
${JSON.stringify(funding, null, 2)}
### Differenzierende Features (nur bei ComplAI)
${JSON.stringify(features, null, 2)}
${fpSummary ? '\n' + fpSummary : ''}
`
}
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const body = await request.json() const body = await request.json()
@@ -171,7 +253,22 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Message is required' }, { status: 400 }) return NextResponse.json({ error: 'Message is required' }, { status: 400 })
} }
const pitchContext = await loadPitchContext() // Resolve investor's assigned version so the AI sees the correct scenario data
let versionId: string | null = null
try {
const session = await getSessionFromCookie()
if (session?.sub) {
const inv = await pool.query(
`SELECT assigned_version_id FROM pitch_investors WHERE id = $1`,
[session.sub]
)
versionId = inv.rows[0]?.assigned_version_id ?? null
}
} catch {
// Non-fatal: fall back to base tables
}
const pitchContext = await loadPitchContext(versionId)
let systemContent = SYSTEM_PROMPT let systemContent = SYSTEM_PROMPT
if (pitchContext) { if (pitchContext) {