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
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:
@@ -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<string> {
|
||||
async function loadFpLiquiditaetSummary(scenarioName: string): Promise<string> {
|
||||
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()
|
||||
try {
|
||||
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 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) {
|
||||
|
||||
Reference in New Issue
Block a user