Critical fix: All financial slides now use the version's preferred scenario
instead of always defaulting to Base Case (1M). This ensures the
Wandeldarlehen version shows its own lean financial plan.
- useFinancialModel: add preferredScenarioId parameter
- PitchDeck: extract default scenario from previewData.fm_scenarios
- Pass preferredScenarioId to all 5 financial slides
- FinancialsSlide layout: remove empty right column, full-width charts
- Remove ScenarioSwitcher + unused slider from FinancialsSlide
- Fix COMPLEI → COMPLAI in presenter script (only TTS pronunciation differs)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows "Lade Finanzplan..." when annualKPIs is empty (data not yet loaded)
instead of rendering nothing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Investors should not be able to modify business case assumptions.
Questions should be directed to founders via the AI chat agent.
Scenario switcher is kept for viewing different scenarios.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DevSecOps, Onepager, SaaS, deployed, TypeScript, RegTech, OpenAI,
PostgreSQL, NVIDIA, GitLab, Full Compliance GPT, ERPNext — all marked
for English voice synthesis in German presenter script and FAQ.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When jwtVerify fails (JWT expired), decode the token without expiry check
to recover sessionId, validate it against the DB, and reissue a fresh 24h
JWT. Fixes investors with old 1h JWTs being locked out on magic link re-click.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace '25.000+' with 'über 25 Tausend' in DE text so Edge TTS speaks
it correctly instead of 'fünfundzwanzig Punkt null null null plus'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JWT was set to 1h while the session cookie lived 24h. After 1 hour the
cookie persisted but jwtVerify failed, making /api/auth/me return 401
and the re-click redirect fall through to the already-used token error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
German permanently routes to compliance TTS service (Edge TTS neural
voice, Piper fallback). OVH DE path removed — no env var can flip it
back accidentally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without the flag, German routes to the compliance TTS service which uses
Edge TTS (de-DE-ConradNeural) with Piper as fallback — easier to A/B
between OVH and compliance/Edge TTS without code changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OVH honours sample_rate_hz and returns data at exactly the requested
rate, so synthesis and WAV header rates must always match. Decoupled
22050/16000 caused 22050 Hz PCM wrapped in a 16000 Hz header → slow
bloated playback. Both back to 16000 Hz.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OVH uses sample_rate_hz in the request for internal synthesis quality
but always outputs raw PCM at 16000 Hz. Sending 22050 for synthesis
gives better pronunciation; declaring 16000 in the WAV header gives
correct playback speed. Previously both were the same value, forcing
a tradeoff between quality and speed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OVH Riva ignores the sample_rate_hz request param and always returns at
its native 16000 Hz. Declaring a higher rate in the WAV header causes
proportionally slower/deeper playback. 16000 Hz matches the actual output.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
22050 Hz declared in WAV header while Riva returns 44100 Hz native PCM
causes playback at half speed — deep, bloated voice. Aligning the
declared rate with the actual output fixes pitch and speed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If an investor clicks the magic link again after already being logged in,
check /api/auth/me first — valid session → redirect to / immediately
instead of showing the 'link already used' error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Client component (investors/new page) imported DEFAULT_MESSAGE etc. from
lib/email.ts which also top-level initialises nodemailer — webpack tried
to bundle fs/net/dns into the client chunk and failed.
Extract the pure constants + getDefaultGreeting into lib/email-templates.ts
(client-safe), keep nodemailer in lib/email.ts (server-only), update the
page to import from email-templates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add nodemailer to serverExternalPackages so webpack doesn't try to
bundle fs/net/dns built-ins (was fatal build error)
- Import jwtVerify from jose/jwt/verify instead of the full jose index
to avoid pulling in JWE deflate code incompatible with Edge Runtime
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ExecutiveSummarySlide:
- Unternehmensentwicklung: hardcoded table → useFinancialModel + computeAnnualKPIs
(MA, Kunden, ARR now computed from finanzplan DB for all versions)
- Pricing: aligned with BusinessModelSlide tiers (Starter/Professional/Enterprise)
Enterprise: 40k → 50k (matching Folie 11)
BusinessModelSlide:
- ACV: hardcoded "15–50k" → computed from summary.final_arr / final_customers
- Gross Margin: hardcoded "> 80%" → computed from lastResult.gross_margin_pct
All financial numbers on all slides now flow from the same compute engine.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All financial data now flows from the same compute engine (useFinancialModel).
No more hardcoded numbers in any slide — all values are derived from the
finanzplan database, ensuring consistency across all pitch deck versions.
- FinanzplanSlide: KPI table + charts now use computeAnnualKPIs() from FMResult[]
- BusinessModelSlide: bottom-up calc (customers × ACV = ARR) from compute engine
- AssumptionsSlide: Base case from compute, Bear/Bull scaled from Base
- New helper: lib/finanzplan/annual-kpis.ts for 60-month → 5-year aggregation
- PitchDeck: passes investorId to all financial slides for version-aware data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Email template (email.ts):
- Bilingual: German body + DE/EN legal footer
- Customizable greeting, message body, and closing
- Magic Link explanation box (hardcoded)
- Confidentiality & Disclaimer footer (hardcoded, bilingual)
Invite page (investors/new):
- Name is now required, Company is optional
- Editable fields: greeting, message, closing (with defaults)
- Live email preview panel (right side)
- Shows full email content before sending
- German UI labels
API (invite/route.ts):
- Passes greeting, message, closing to email function
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds legal footer to the investor invite email with:
- Confidentiality obligation (3 years, purpose limitation)
- Disclaimer (not an offer, projections only, risk of total loss)
- Jurisdiction: Konstanz, German law
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Conditional sections only shown when instrument is "Wandeldarlehen"
- 200k investor ask + 200k L-Bank = 400k total funding visualization
- 3-step explanation: Investment → Conversion → Investor advantage
- Pre-Seed BW / L-Bank co-financing info box
- Cap Table before/after conversion example
- Use of Funds EUR amounts based on 400k total budget
- "1 Mio." version remains completely unaffected
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace emoji with Landmark icon
- Add JSON.parse fallback for use_of_funds
- Guard pieData labels and amounts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 15% tax-free acquisition grant (corrected from 25%)
- 25% exit grant on capital gains
- Up to 40% effective support (entry + exit combined)
- Program extended until 31.12.2026
- Disclaimer to verify current terms at bafa.de
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Theme toggle button below language toggle
- Uses existing theme-light CSS class from globals.css
- Moon/Sun icons with Nacht/Tag labels (DE) or Dark/Light (EN)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- All text-[10px] → text-xs (12px)
- All text-[9px] → text-[11px]
- All text-[8px] → text-[10px]
- Affected: BusinessModel, Product, Savings, Strategy slides
- Engineering: revert LoC to 481K (compliance SDK only, not all repos)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Ext. DSB: 6k→3k (halved, not eliminated)
- Compliance docs: 0→2k (reduced effort, not zero)
- Personnel: "~2/8/40 MA savings" → "50% more productive compliance time"
(realistic productivity gain, not full headcount elimination)
- ROIs now credible: KMU 3.7x, Mittelstand 6.4x, Konzern 15.6x
(was 11x/21x/62x — too aggressive for investors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 17 more umlaut fixes (Konformitätsbewertung, Löschkonzept,
Portabilität, Regelmäßige, etc.) across 6 files
- ComplAI → COMPLAI in all string contexts for consistency
- BrandName component used for JSX rendering (gradient AI)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Finanzen slide crashed on mount with "a.toFixed is not a function".
Traced to UnitEconomicsCards.tsx:59 calling ltvCacRatio.toFixed(1),
where ltvCacRatio arrives as a string.
Root cause: the cached path in /api/financial-model/compute returns raw
rows from pitch_fm_results. node-postgres returns NUMERIC / DECIMAL
columns as strings by default, so lastResult.ltv_cac_ratio (and every
other *_eur / *_pct / *_ratio field) flows through the app as a string.
Arithmetic-heavy code paths survived on accidental string-coerce (`-`,
`/`, `*`), but direct method calls like .toFixed() don't coerce, which
is why Unit Economics was the visible crash site.
Fix at the boundary: register a single types.setTypeParser(NUMERIC, …)
on the pg Pool so every query returns real numbers. All our NUMERIC
values fit well inside Number.MAX_SAFE_INTEGER, so parseFloat is safe.
One-line change, no component-level defensive coercions needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Regulatory landscape footer: text-xs text-white/50 (was text-[9px] text-white/20)
- New POST /api/admin/import-fp endpoint to import fp_* data from JSON dump
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>