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 { 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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user