Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-core
All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m22s
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 37s
CI / test-python-voice (push) Successful in 40s
CI / test-bqas (push) Successful in 39s

This commit is contained in:
Benjamin Admin
2026-04-18 13:03:31 +02:00
17 changed files with 128 additions and 77 deletions

View File

@@ -2,29 +2,29 @@
## Entwicklungsumgebung (WICHTIG - IMMER ZUERST LESEN) ## Entwicklungsumgebung (WICHTIG - IMMER ZUERST LESEN)
### Zwei-Rechner-Setup + Coolify ### Zwei-Rechner-Setup + Orca
| Geraet | Rolle | Aufgaben | | Geraet | Rolle | Aufgaben |
|--------|-------|----------| |--------|-------|----------|
| **MacBook** | Entwicklung | Claude Terminal, Code-Entwicklung, Browser (Frontend-Tests) | | **MacBook** | Entwicklung | Claude Terminal, Code-Entwicklung, Browser (Frontend-Tests) |
| **Mac Mini** | Lokaler Server | Docker fuer lokale Dev/Tests (NICHT fuer Production!) | | **Mac Mini** | Lokaler Server | Docker fuer lokale Dev/Tests (NICHT fuer Production!) |
| **Coolify** | Production | Automatisches Build + Deploy bei Push auf gitea | | **Orca** | Production | Automatisches Build + Deploy bei Push auf gitea |
**WICHTIG:** Code wird direkt auf dem MacBook in diesem Repo bearbeitet. Production-Deployment laeuft automatisch ueber Coolify. **WICHTIG:** Code wird direkt auf dem MacBook in diesem Repo bearbeitet. Production-Deployment laeuft automatisch ueber Orca.
### Entwicklungsworkflow (CI/CD — Coolify) ### Entwicklungsworkflow (CI/CD — Orca)
```bash ```bash
# 1. Code auf MacBook bearbeiten (dieses Verzeichnis) # 1. Code auf MacBook bearbeiten (dieses Verzeichnis)
# 2. Committen und zu BEIDEN Remotes pushen: # 2. Committen und zu BEIDEN Remotes pushen:
git push origin main && git push gitea main git push origin main
# 3. FERTIG! Push auf gitea triggert automatisch: # 3. FERTIG! Push auf gitea triggert automatisch:
# - Gitea Actions: Tests # - Gitea Actions: Tests
# - Coolify: Build → Deploy # - Orca: Build → Deploy
``` ```
**NIEMALS** manuell in Coolify auf "Redeploy" klicken — Gitea Actions triggert Coolify automatisch. **NIEMALS** manuell in Orca auf "Redeploy" klicken — Gitea Actions triggert Orca automatisch.
**IMMER auf `main` pushen** — sowohl origin als auch gitea. **IMMER auf `main` pushen** — sowohl origin als auch gitea.
### Post-Push Deploy-Monitoring (PFLICHT nach jedem Push auf gitea) ### Post-Push Deploy-Monitoring (PFLICHT nach jedem Push auf gitea)
@@ -39,7 +39,7 @@ git push origin main && git push gitea main
``` ```
3. Sobald ALLE Endpoints healthy sind, dem User im Chat melden: 3. Sobald ALLE Endpoints healthy sind, dem User im Chat melden:
**"Deploy abgeschlossen! Du kannst jetzt testen."** **"Deploy abgeschlossen! Du kannst jetzt testen."**
4. Falls nach 5 Minuten noch nicht healthy → Fehlermeldung mit Hinweis auf Coolify-Logs. 4. Falls nach 5 Minuten noch nicht healthy → Fehlermeldung mit Hinweis auf Orca-Logs.
### Lokale Entwicklung (Mac Mini — optional, nur Dev/Tests) ### Lokale Entwicklung (Mac Mini — optional, nur Dev/Tests)
@@ -80,8 +80,8 @@ networks:
| Repo | Deployment | Trigger | | Repo | Deployment | Trigger |
|------|-----------|---------| |------|-----------|---------|
| **breakpilot-core** | Coolify (automatisch) | Push auf gitea main | | **breakpilot-core** | Orca (automatisch) | Push auf gitea main |
| **breakpilot-compliance** | Coolify (automatisch) | Push auf gitea main | | **breakpilot-compliance** | Orca (automatisch) | Push auf gitea main |
| **breakpilot-lehrer** | Mac Mini (lokal) | Manuell docker compose | | **breakpilot-lehrer** | Mac Mini (lokal) | Manuell docker compose |
--- ---
@@ -252,8 +252,8 @@ ssh macmini "/usr/local/bin/docker logs -f bp-core-control-pipeline"
### Deployment (CI/CD — Standardweg) ### Deployment (CI/CD — Standardweg)
```bash ```bash
# Committen und pushen → Coolify deployt automatisch: # Committen und pushen → Orca deployt automatisch:
git push origin main && git push gitea main git push origin main
``` ```
### Lokale Docker-Befehle (Mac Mini — nur Dev/Tests) ### Lokale Docker-Befehle (Mac Mini — nur Dev/Tests)
@@ -278,11 +278,11 @@ ssh macmini "/usr/local/bin/docker ps --filter name=bp-core"
```bash ```bash
# Zu BEIDEN Remotes pushen (PFLICHT!): # Zu BEIDEN Remotes pushen (PFLICHT!):
git push origin main && git push gitea main git push origin main
# Remotes: # Remotes:
# origin: lokale Gitea (macmini:3003)
# gitea: gitea.meghsakha.com
``` ```
--- ---

View File

@@ -58,7 +58,7 @@ Blocks on: type errors, lint violations, **build failures**.
|-------|-------|------| |-------|-------|------|
| Pre-push (local) | Claude runs | Lint + type check + unit tests + build | | Pre-push (local) | Claude runs | Lint + type check + unit tests + build |
| CI (Gitea Actions) | Automatic on push | Same + integration tests + contract tests | | CI (Gitea Actions) | Automatic on push | Same + integration tests + contract tests |
| Deploy (Coolify) | Automatic after CI | Docker build + health check | | Deploy (Orca) | Automatic after CI | Docker build + health check |
Local checks catch 90% of CI failures in seconds. CI is the safety net, not the first line of defense. Local checks catch 90% of CI failures in seconds. CI is the safety net, not the first line of defense.

View File

@@ -35,6 +35,7 @@ jobs:
cd pitch-deck cd pitch-deck
SHORT_SHA=$(git rev-parse --short HEAD) SHORT_SHA=$(git rev-parse --short HEAD)
docker build \ docker build \
--build-arg GIT_SHA=${SHORT_SHA} \
-t registry.meghsakha.com/breakpilot/pitch-deck:latest \ -t registry.meghsakha.com/breakpilot/pitch-deck:latest \
-t registry.meghsakha.com/breakpilot/pitch-deck:${SHORT_SHA} \ -t registry.meghsakha.com/breakpilot/pitch-deck:${SHORT_SHA} \
. .

View File

@@ -7,10 +7,10 @@ BreakPilot verwendet zwei Umgebungen:
``` ```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Development │───── git push ────▶│ Production │ │ Development │───── git push ────▶│ Production │
│ (Mac Mini) │ │ (Coolify) │ │ (Mac Mini) │ │ (Orca) │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
Lokale Automatisch Lokale Automatisch
Entwicklung via Coolify Entwicklung via Orca
``` ```
## Umgebungen ## Umgebungen
@@ -32,21 +32,21 @@ BreakPilot verwendet zwei Umgebungen:
ssh macmini "cd ~/Projekte/breakpilot-core && /usr/local/bin/docker compose up -d" ssh macmini "cd ~/Projekte/breakpilot-core && /usr/local/bin/docker compose up -d"
``` ```
### Production (Coolify) ### Production (Orca)
**Zweck:** Live-System **Zweck:** Live-System
| Eigenschaft | Wert | | Eigenschaft | Wert |
|-------------|------| |-------------|------|
| Git Branch | `main` | | Git Branch | `main` |
| Deployment | Coolify (automatisch bei Push auf gitea) | | Deployment | Orca (automatisch bei Push auf gitea) |
| Database | Externe PostgreSQL (TLS) | | Database | Externe PostgreSQL (TLS) |
| Debug | Deaktiviert | | Debug | Deaktiviert |
**Deploy:** **Deploy:**
```bash ```bash
git push origin main && git push gitea main git push origin main && git push gitea main
# Coolify baut und deployt automatisch # Orca baut und deployt automatisch
``` ```
## Docker Compose Architektur ## Docker Compose Architektur
@@ -54,10 +54,10 @@ git push origin main && git push gitea main
``` ```
docker-compose.yml ← Basis-Konfiguration (lokal, arm64) docker-compose.yml ← Basis-Konfiguration (lokal, arm64)
└── docker-compose.coolify.yml ← Production Override (amd64) └── docker-compose.orca.yml ← Production Override (amd64)
``` ```
Coolify verwendet automatisch beide Compose-Files fuer den Production-Build. Orca verwendet automatisch beide Compose-Files fuer den Production-Build.
## Secrets Management ## Secrets Management

View File

@@ -6,8 +6,8 @@ Uebersicht ueber den Deployment-Prozess fuer BreakPilot.
| Repo | Deployment | Trigger | Compose File | | Repo | Deployment | Trigger | Compose File |
|------|-----------|---------|--------------| |------|-----------|---------|--------------|
| **breakpilot-core** | Coolify (automatisch) | Push auf `coolify` Branch | `docker-compose.coolify.yml` | | **breakpilot-core** | Orca (automatisch) | Push auf `orca` Branch | `docker-compose.orca.yml` |
| **breakpilot-compliance** | Coolify (automatisch) | Push auf `main` Branch | `docker-compose.yml` + `docker-compose.coolify.yml` | | **breakpilot-compliance** | Orca (automatisch) | Push auf `main` Branch | `docker-compose.yml` + `docker-compose.orca.yml` |
| **breakpilot-lehrer** | Mac Mini (lokal) | Manuell `docker compose` | `docker-compose.yml` | | **breakpilot-lehrer** | Mac Mini (lokal) | Manuell `docker compose` | `docker-compose.yml` |
## Deployment-Architektur ## Deployment-Architektur
@@ -16,7 +16,7 @@ Uebersicht ueber den Deployment-Prozess fuer BreakPilot.
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
│ Entwickler-MacBook │ │ Entwickler-MacBook │
│ │ │ │
│ breakpilot-core/ → git push gitea coolify │ breakpilot-core/ → git push gitea orca
│ breakpilot-compliance/ → git push gitea main │ │ breakpilot-compliance/ → git push gitea main │
│ breakpilot-lehrer/ → git push + ssh macmini docker ... │ │ breakpilot-lehrer/ → git push + ssh macmini docker ... │
│ │ │ │
@@ -26,11 +26,11 @@ Uebersicht ueber den Deployment-Prozess fuer BreakPilot.
│ │ │ │
▼ ▼ ▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐ ┌───────────────────────────┐ ┌───────────────────────────┐
Coolify (Production) │ │ Mac Mini (Lokal/Dev) │ Orca (Production) │ │ Mac Mini (Lokal/Dev) │
│ │ │ │ │ │ │ │
│ Gitea Actions │ │ breakpilot-lehrer │ │ Gitea Actions │ │ breakpilot-lehrer │
│ ├── Tests │ │ ├── studio-v2 │ │ ├── Tests │ │ ├── studio-v2 │
│ └── Coolify API Deploy │ │ ├── klausur-service │ │ └── Orca API Deploy │ │ ├── klausur-service │
│ │ │ ├── backend-lehrer │ │ │ │ ├── backend-lehrer │
│ Core Services: │ │ └── voice-service │ │ Core Services: │ │ └── voice-service │
│ ├── consent-service │ │ │ │ ├── consent-service │ │ │
@@ -47,23 +47,23 @@ Uebersicht ueber den Deployment-Prozess fuer BreakPilot.
└───────────────────────────┘ └───────────────────────────┘ └───────────────────────────┘ └───────────────────────────┘
``` ```
## breakpilot-core → Coolify ## breakpilot-core → Orca
### Pipeline ### Pipeline
```yaml ```yaml
# .gitea/workflows/deploy-coolify.yml # .gitea/workflows/deploy-orca.yml
on: on:
push: push:
branches: [coolify] branches: [orca]
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Deploy via Coolify API - name: Deploy via Orca API
# Triggert Coolify Build + Deploy ueber API # Triggert Orca Build + Deploy ueber API
# Secrets: COOLIFY_API_TOKEN, COOLIFY_RESOURCE_UUID, COOLIFY_BASE_URL # Secrets: ORCA_API_TOKEN, ORCA_RESOURCE_UUID, ORCA_BASE_URL
``` ```
### Workflow ### Workflow
@@ -74,13 +74,13 @@ jobs:
git push origin main && git push gitea main git push origin main && git push gitea main
# 3. Fuer Production-Deploy: # 3. Fuer Production-Deploy:
git push gitea coolify git push gitea orca
# 4. Status pruefen: # 4. Status pruefen:
# https://gitea.meghsakha.com/Benjamin_Boenisch/breakpilot-core/actions # https://gitea.meghsakha.com/Benjamin_Boenisch/breakpilot-core/actions
``` ```
### Coolify-deployed Services ### Orca-deployed Services
| Service | Container | Beschreibung | | Service | Container | Beschreibung |
|---------|-----------|--------------| |---------|-----------|--------------|
@@ -91,7 +91,7 @@ git push gitea coolify
| paddleocr-service | bp-core-paddleocr | OCR Engine (x86_64) | | paddleocr-service | bp-core-paddleocr | OCR Engine (x86_64) |
| health-aggregator | bp-core-health | Health-Check Aggregator | | health-aggregator | bp-core-health | Health-Check Aggregator |
## breakpilot-compliance → Coolify ## breakpilot-compliance → Orca
### Pipeline ### Pipeline
@@ -111,7 +111,7 @@ jobs:
### Workflow ### Workflow
```bash ```bash
# Committen und pushen → Coolify deployt automatisch: # Committen und pushen → Orca deployt automatisch:
git push origin main && git push gitea main git push origin main && git push gitea main
# CI-Status pruefen: # CI-Status pruefen:
@@ -154,8 +154,8 @@ Workflows liegen in jedem Repo unter `.gitea/workflows/`:
| Repo | Workflow | Branch | Aktion | | Repo | Workflow | Branch | Aktion |
|------|----------|--------|--------| |------|----------|--------|--------|
| breakpilot-core | `deploy-coolify.yml` | `coolify` | Coolify API Deploy | | breakpilot-core | `deploy-orca.yml` | `orca` | Orca API Deploy |
| breakpilot-compliance | `ci.yaml` | `main` | Tests + Coolify Deploy | | breakpilot-compliance | `ci.yaml` | `main` | Tests + Orca Deploy |
### Runner-Token erneuern ### Runner-Token erneuern
@@ -181,7 +181,7 @@ ssh macmini "/usr/local/bin/docker logs -f bp-core-gitea-runner"
## Health Checks ## Health Checks
### Production (Coolify) ### Production (Orca)
```bash ```bash
# Core PaddleOCR # Core PaddleOCR
@@ -229,14 +229,14 @@ ssh macmini "docker compose build --no-cache <service>"
## Rollback ## Rollback
### Coolify ### Orca
Ein Redeploy mit einem aelteren Commit kann durch Zuruecksetzen des Branches ausgeloest werden: Ein Redeploy mit einem aelteren Commit kann durch Zuruecksetzen des Branches ausgeloest werden:
```bash ```bash
# Branch auf vorherigen Commit zuruecksetzen und pushen # Branch auf vorherigen Commit zuruecksetzen und pushen
git reset --hard <previous-commit> git reset --hard <previous-commit>
git push gitea coolify --force git push gitea orca --force
``` ```
### Lokal (Mac Mini) ### Lokal (Mac Mini)

View File

@@ -16,8 +16,8 @@ BreakPilot besteht aus drei unabhaengigen Projekten:
| Repo | Deployment | Trigger | | Repo | Deployment | Trigger |
|------|-----------|---------| |------|-----------|---------|
| **breakpilot-core** | Coolify (automatisch) | Push auf gitea main | | **breakpilot-core** | Orca (automatisch) | Push auf gitea main |
| **breakpilot-compliance** | Coolify (automatisch) | Push auf gitea main | | **breakpilot-compliance** | Orca (automatisch) | Push auf gitea main |
| **breakpilot-lehrer** | Mac Mini (lokal) | Manuell docker compose | | **breakpilot-lehrer** | Mac Mini (lokal) | Manuell docker compose |
## Core Services ## Core Services

View File

@@ -12,6 +12,10 @@ RUN npm install
# Copy source code # Copy source code
COPY . . COPY . .
# Embed git commit hash into build
ARG GIT_SHA=dev
ENV GIT_SHA=$GIT_SHA
# Build the application # Build the application
RUN npm run build RUN npm run build

View File

@@ -6,7 +6,7 @@ import { finanzplanToFMResults } from '@/lib/finanzplan/adapter'
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const body = await request.json() const body = await request.json()
const { scenarioId, source } = body const { scenarioId, source, force } = body
// If source=finanzplan, use the Finanzplan engine instead // If source=finanzplan, use the Finanzplan engine instead
if (source === 'finanzplan') { if (source === 'finanzplan') {
@@ -28,8 +28,8 @@ export async function POST(request: NextRequest) {
const client = await pool.connect() const client = await pool.connect()
try { try {
// Fast path: return cached results if they exist (avoid expensive recompute + 60 inserts) // Fast path: return cached results if they exist (skip when force=true)
const cached = await client.query( const cached = force ? { rows: [] } : await client.query(
'SELECT * FROM pitch_fm_results WHERE scenario_id = $1 ORDER BY month', 'SELECT * FROM pitch_fm_results WHERE scenario_id = $1 ORDER BY month',
[scenarioId] [scenarioId]
) )

View File

@@ -39,7 +39,9 @@ export async function GET(
query += ' ORDER BY sort_order' query += ' ORDER BY sort_order'
const { rows } = await pool.query(query, params) const { rows } = await pool.query(query, params)
return NextResponse.json({ sheet: sheetName, rows }) return NextResponse.json({ sheet: sheetName, rows }, {
headers: { 'Cache-Control': 'no-store' },
})
} catch (error) { } catch (error) {
return NextResponse.json({ error: String(error) }, { status: 500 }) return NextResponse.json({ error: String(error) }, { status: 500 })
} }

View File

@@ -25,6 +25,8 @@ export async function GET() {
sheets, sheets,
scenarios: scenarios.rows, scenarios: scenarios.rows,
months: { start: '2026-01', end: '2030-12', count: 60, founding: '2026-08' }, months: { start: '2026-01', end: '2030-12', count: 60, founding: '2026-08' },
}, {
headers: { 'Cache-Control': 'no-store' },
}) })
} catch (error) { } catch (error) {
return NextResponse.json({ error: String(error) }, { status: 500 }) return NextResponse.json({ error: String(error) }, { status: 500 })

View File

@@ -3,7 +3,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import Link from 'next/link' import Link from 'next/link'
import { ArrowLeft, Save } from 'lucide-react' import { ArrowLeft, RefreshCw, Save } from 'lucide-react'
interface Assumption { interface Assumption {
id: string id: string
@@ -36,6 +36,7 @@ export default function EditScenarioPage() {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [edits, setEdits] = useState<Record<string, string>>({}) const [edits, setEdits] = useState<Record<string, string>>({})
const [savingId, setSavingId] = useState<string | null>(null) const [savingId, setSavingId] = useState<string | null>(null)
const [recomputing, setRecomputing] = useState(false)
const [toast, setToast] = useState<string | null>(null) const [toast, setToast] = useState<string | null>(null)
function flashToast(msg: string) { function flashToast(msg: string) {
@@ -56,6 +57,17 @@ export default function EditScenarioPage() {
useEffect(() => { if (scenarioId) load() }, [scenarioId]) useEffect(() => { if (scenarioId) load() }, [scenarioId])
async function forceRecompute() {
setRecomputing(true)
const res = await fetch('/api/financial-model/compute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ scenarioId, force: true }),
})
setRecomputing(false)
flashToast(res.ok ? 'Recomputed successfully' : 'Recompute failed')
}
function setEdit(id: string, val: string) { function setEdit(id: string, val: string) {
setEdits(prev => ({ ...prev, [id]: val })) setEdits(prev => ({ ...prev, [id]: val }))
} }
@@ -108,6 +120,7 @@ export default function EditScenarioPage() {
<ArrowLeft className="w-4 h-4" /> Back to scenarios <ArrowLeft className="w-4 h-4" /> Back to scenarios
</Link> </Link>
<div className="flex items-start justify-between gap-4">
<div> <div>
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: scenario.color }} /> <div className="w-4 h-4 rounded-full" style={{ backgroundColor: scenario.color }} />
@@ -120,6 +133,16 @@ export default function EditScenarioPage() {
</div> </div>
{scenario.description && <p className="text-sm text-white/50">{scenario.description}</p>} {scenario.description && <p className="text-sm text-white/50">{scenario.description}</p>}
</div> </div>
<button
onClick={forceRecompute}
disabled={recomputing}
className="flex items-center gap-2 text-sm px-3 py-1.5 rounded-lg bg-white/[0.06] hover:bg-white/[0.1] text-white/70 hover:text-white disabled:opacity-40 disabled:cursor-wait transition-colors"
title="Clear cache and recompute financial model results"
>
<RefreshCw className={`w-3.5 h-3.5 ${recomputing ? 'animate-spin' : ''}`} />
{recomputing ? 'Computing…' : 'Force Recompute'}
</button>
</div>
<div className="space-y-6"> <div className="space-y-6">
{Object.entries(byCategory).map(([cat, items]) => ( {Object.entries(byCategory).map(([cat, items]) => (

View File

@@ -92,6 +92,11 @@ export default function AdminShell({ admin, children }: AdminShellProps) {
<div className="px-3 py-2 mb-2"> <div className="px-3 py-2 mb-2">
<div className="text-sm font-medium text-white/90 truncate">{admin.name}</div> <div className="text-sm font-medium text-white/90 truncate">{admin.name}</div>
<div className="text-xs text-white/40 truncate">{admin.email}</div> <div className="text-xs text-white/40 truncate">{admin.email}</div>
<div className="mt-1.5 flex items-center gap-1.5">
<span className="text-[9px] font-mono bg-white/[0.06] text-white/30 px-1.5 py-0.5 rounded">
{process.env.NEXT_PUBLIC_GIT_SHA ?? 'dev'}
</span>
</div>
</div> </div>
<button <button
onClick={logout} onClick={logout}

View File

@@ -178,8 +178,8 @@ export default function AIPipelineSlide({ lang }: AIPipelineSlideProps) {
color: 'text-purple-400', color: 'text-purple-400',
title: de ? 'CI/CD & Testing' : 'CI/CD & Testing', title: de ? 'CI/CD & Testing' : 'CI/CD & Testing',
items: de items: de
? ['Gitea Actions: Lint → Tests → Validierung bei jedem Push', 'Go-Tests (AI SDK) + Python-Tests (Backend + Pipeline)', 'Coolify Auto-Deploy mit Health-Check-Monitoring', 'arm64 → amd64 Cross-Build für Hetzner Production'] ? ['Gitea Actions: Lint → Tests → Validierung bei jedem Push', 'Go-Tests (AI SDK) + Python-Tests (Backend + Pipeline)', 'Orca Auto-Deploy mit Health-Check-Monitoring', 'arm64 → amd64 Cross-Build für Hetzner Production']
: ['Gitea Actions: Lint → Tests → Validation on every push', 'Go tests (AI SDK) + Python tests (Backend + Pipeline)', 'Coolify auto-deploy with health check monitoring', 'arm64 → amd64 cross-build for Hetzner production'], : ['Gitea Actions: Lint → Tests → Validation on every push', 'Go tests (AI SDK) + Python tests (Backend + Pipeline)', 'Orca auto-deploy with health check monitoring', 'arm64 → amd64 cross-build for Hetzner production'],
}, },
{ {
icon: Zap, icon: Zap,

View File

@@ -61,8 +61,12 @@ function formatCell(v: number | undefined): string {
return Math.round(v).toLocaleString('de-DE', { maximumFractionDigits: 0 }) return Math.round(v).toLocaleString('de-DE', { maximumFractionDigits: 0 })
} }
interface FpScenario { id: string; name: string; is_default: boolean }
export default function FinanzplanSlide({ lang, investorId, preferredScenarioId }: FinanzplanSlideProps) { export default function FinanzplanSlide({ lang, investorId, preferredScenarioId }: FinanzplanSlideProps) {
const [sheets, setSheets] = useState<SheetMeta[]>([]) const [sheets, setSheets] = useState<SheetMeta[]>([])
const [scenarios, setScenarios] = useState<FpScenario[]>([])
const [selectedScenarioId, setSelectedScenarioId] = useState<string>('')
const [activeSheet, setActiveSheet] = useState<string>('personalkosten') const [activeSheet, setActiveSheet] = useState<string>('personalkosten')
const [rows, setRows] = useState<SheetRow[]>([]) const [rows, setRows] = useState<SheetRow[]>([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@@ -77,19 +81,26 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId
[fm.activeResults], [fm.activeResults],
) )
// Determine fp_scenario_id from the active FM scenario name // Load sheet list + scenarios
const fpScenarioParam = fm.activeScenario?.name?.toLowerCase().includes('wandeldarlehen')
? '?scenarioId=c0000000-0000-0000-0000-000000000200'
: ''
// Load sheet list
useEffect(() => { useEffect(() => {
fetch('/api/finanzplan') fetch('/api/finanzplan', { cache: 'no-store' })
.then(r => r.json()) .then(r => r.json())
.then(data => setSheets(data.sheets || [])) .then(data => {
setSheets(data.sheets || [])
const scens: FpScenario[] = data.scenarios || []
setScenarios(scens)
// Pick default scenario on first load
if (!selectedScenarioId) {
const def = scens.find(s => s.is_default) ?? scens[0]
if (def) setSelectedScenarioId(def.id)
}
})
.catch(() => {}) .catch(() => {})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const scenarioParam = selectedScenarioId ? `?scenarioId=${selectedScenarioId}` : ''
// Load sheet data // Load sheet data
const loadSheet = useCallback(async (name: string) => { const loadSheet = useCallback(async (name: string) => {
if (name === 'kpis' || name === 'charts') { if (name === 'kpis' || name === 'charts') {
@@ -99,12 +110,12 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId
} }
setLoading(true) setLoading(true)
try { try {
const r = await fetch(`/api/finanzplan/${name}${fpScenarioParam}`) const r = await fetch(`/api/finanzplan/${name}${scenarioParam}`, { cache: 'no-store' })
const data = await r.json() const data = await r.json()
setRows(data.rows || []) setRows(data.rows || [])
} catch { /* ignore */ } } catch { /* ignore */ }
setLoading(false) setLoading(false)
}, [fpScenarioParam]) }, [scenarioParam])
useEffect(() => { loadSheet(activeSheet) }, [activeSheet, loadSheet]) useEffect(() => { loadSheet(activeSheet) }, [activeSheet, loadSheet])
@@ -112,7 +123,7 @@ export default function FinanzplanSlide({ lang, investorId, preferredScenarioId
const handleCompute = async () => { const handleCompute = async () => {
setComputing(true) setComputing(true)
try { try {
await fetch('/api/finanzplan/compute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }) await fetch('/api/finanzplan/compute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scenarioId: selectedScenarioId || undefined }) })
await loadSheet(activeSheet) await loadSheet(activeSheet)
} catch { /* ignore */ } } catch { /* ignore */ }
setComputing(false) setComputing(false)

View File

@@ -692,8 +692,8 @@ export const PRESENTER_FAQ: FAQEntry[] = [
keywords: ['syseleven', 'hetzner', 'cloud', 'hosting', 'infrastruktur', 'infrastructure', 'server', 'rechenzentrum', 'data center', 'wo laufen', 'where hosted'], keywords: ['syseleven', 'hetzner', 'cloud', 'hosting', 'infrastruktur', 'infrastructure', 'server', 'rechenzentrum', 'data center', 'wo laufen', 'where hosted'],
question_de: 'Auf welcher Infrastruktur laeuft die Plattform?', question_de: 'Auf welcher Infrastruktur laeuft die Plattform?',
question_en: 'What infrastructure does the platform run on?', question_en: 'What infrastructure does the platform run on?',
answer_de: 'Unsere Plattform laeuft zu 100 Prozent auf europaeischer Cloud-Infrastruktur — ohne einen einzigen US-Anbieter. Fuer LLM-Inferenz und KI-Workloads nutzen wir SysEleven, einen BSI-C5-zertifizierten deutschen Cloud-Provider mit GPU-Kapazitaet. Fuer Datenbanken, Vektorspeicher und Anwendungslogik setzen wir auf Hetzner — ebenfalls deutsch, ISO 27001-zertifiziert und deutlich kostenguenstiger als AWS oder Azure. Das CI/CD laeuft ueber Gitea Actions mit automatischem Deploy via Coolify auf Hetzner. Diese Kombination gibt uns einen strukturellen Kostenvorteil bei voller EU-Datensouveraenitaet.', answer_de: 'Unsere Plattform laeuft zu 100 Prozent auf europaeischer Cloud-Infrastruktur — ohne einen einzigen US-Anbieter. Fuer LLM-Inferenz und KI-Workloads nutzen wir SysEleven, einen BSI-C5-zertifizierten deutschen Cloud-Provider mit GPU-Kapazitaet. Fuer Datenbanken, Vektorspeicher und Anwendungslogik setzen wir auf Hetzner — ebenfalls deutsch, ISO 27001-zertifiziert und deutlich kostenguenstiger als AWS oder Azure. Das CI/CD laeuft ueber Gitea Actions mit automatischem Deploy via Orca auf Hetzner. Diese Kombination gibt uns einen strukturellen Kostenvorteil bei voller EU-Datensouveraenitaet.',
answer_en: 'Our platform runs 100 percent on European cloud infrastructure — without a single US provider. For LLM inference and AI workloads we use SysEleven, a BSI C5-certified German cloud provider with GPU capacity. For databases, vector storage and application logic we rely on Hetzner — also German, ISO 27001-certified and significantly more cost-effective than AWS or Azure. CI/CD runs via Gitea Actions with automatic deploy via Coolify on Hetzner. This combination gives us a structural cost advantage with full EU data sovereignty.', answer_en: 'Our platform runs 100 percent on European cloud infrastructure — without a single US provider. For LLM inference and AI workloads we use SysEleven, a BSI C5-certified German cloud provider with GPU capacity. For databases, vector storage and application logic we rely on Hetzner — also German, ISO 27001-certified and significantly more cost-effective than AWS or Azure. CI/CD runs via Gitea Actions with automatic deploy via Orca on Hetzner. This combination gives us a structural cost advantage with full EU data sovereignty.',
goto_slide: 'annex-architecture', goto_slide: 'annex-architecture',
priority: 8, priority: 8,
}, },

View File

@@ -534,8 +534,8 @@ export const PRESENTER_SCRIPT: SlideScript[] = [
duration: 40, duration: 40,
paragraphs: [ paragraphs: [
{ {
text_de: 'Engineering Deep Dive: Über 500.000 Zeilen Code, 45 Container, 65 Compliance-Module. Tech-Stack: Go, Python, TypeScript mit Next.js. CI/CD über Gitea Actions mit automatischem Deploy via Coolify auf Hetzner.', text_de: 'Engineering Deep Dive: Über 500.000 Zeilen Code, 45 Container, 65 Compliance-Module. Tech-Stack: Go, Python, TypeScript mit Next.js. CI/CD über Gitea Actions mit automatischem Deploy via Orca auf Hetzner.',
text_en: 'Engineering deep dive: Over 500,000 lines of code, 45 containers, 65 compliance modules. Tech stack: Go, Python, TypeScript with Next.js. CI/CD via Gitea Actions with automatic deploy via Coolify on Hetzner.', text_en: 'Engineering deep dive: Over 500,000 lines of code, 45 containers, 65 compliance modules. Tech stack: Go, Python, TypeScript with Next.js. CI/CD via Gitea Actions with automatic deploy via Orca on Hetzner.',
pause_after: 2000, pause_after: 2000,
}, },
{ {

View File

@@ -1,6 +1,9 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: 'standalone', output: 'standalone',
env: {
NEXT_PUBLIC_GIT_SHA: process.env.GIT_SHA || 'dev',
},
reactStrictMode: true, reactStrictMode: true,
typescript: { typescript: {
ignoreBuildErrors: true, ignoreBuildErrors: true,