diff --git a/pitch-deck/app/api/chat/route.ts b/pitch-deck/app/api/chat/route.ts index 5709156..7a52479 100644 --- a/pitch-deck/app/api/chat/route.ts +++ b/pitch-deck/app/api/chat/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import pool from '@/lib/db' +import { getSessionFromCookie } from '@/lib/auth' import { SLIDE_ORDER } from '@/lib/slide-order' 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.` -async function loadPitchContext(): Promise { +async function loadFpLiquiditaetSummary(scenarioName: string): Promise { 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> = {} + 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 { + 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 = {} + 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>) ?? []) + .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() try { const [company, team, financials, market, products, funding, features] = await Promise.all([ @@ -128,31 +200,10 @@ async function loadPitchContext(): Promise { 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 ` -## Unternehmensdaten (für präzise Antworten nutzen) - -### 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)} -` + return buildContextString( + company.rows[0], team.rows, financials.rows, market.rows, + products.rows, funding.rows[0], features.rows, '' + ) } finally { 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) { try { const body = await request.json() @@ -171,7 +253,22 @@ export async function POST(request: NextRequest) { 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 if (pitchContext) {