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>
POST /api/admin/migrate creates all fp_* tables on production DB.
Admin-only, creates tables with IF NOT EXISTS for safe re-runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GitHub avatar (github.com/mighty840) saved as /team/sharang-parnerkar.jpg.
Team-data JSON for both draft versions (Wandeldarlehen and The Ask 1 Mio)
was updated out-of-band via the admin API:
- Bio lengthened (~640 chars DE/EN) to match Benjamin's depth — now
covers the ETO tenure (3→60 org scale), ETOPay, ViviSwap/MiCA,
enterprise AI on AWS/Azure/SysEleven, embedded Rust work, and the
ferrite-sdk open-source project.
- photo_url switched from empty to /team/sharang-parnerkar.jpg.
- Expertise tags unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Compute endpoint now returns cached results if available (single SELECT
instead of DELETE + 60 INSERTs)
- When recompute is needed, batch all 60 rows into a single INSERT
- Reduces DB calls from 61 to 2 (cached) or 3 (recompute)
- Fixes timeout/blank financial slides for investors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SDK Live Demo was janky: AnimatePresence mode="wait" unmounted the
current Image before mounting the next, so every advance forced a cold
fetch and left an empty black frame until the new image decoded. Only
the first three screenshots had priority; the rest fetched lazily, so
the first pass through the carousel repeatedly stalled.
Replaces the single swap-in/swap-out Image with a stack of 23 images
layered in an aspect-[1920/1080] container. Cross-fades are now pure
CSS opacity on always-mounted nodes, so there is no unmount and no gap.
Key details:
- priority on the first 3 (triggers <link rel="preload">); loading=eager
on the remaining 20 so the browser starts all fetches at mount rather
than deferring via IntersectionObserver.
- sizes="(max-width: 1024px) 100vw, 1024px" lets next/image serve the
actual displayed resolution instead of the 1920 hint — fewer bytes,
faster first paint.
- Load-gated reveal: a new `shown` state trails `current` until the
target image fires onLoadingComplete. If the user clicks ahead of
the network, the previous loaded screenshot stays visible — no more
black flashes before images arrive.
Second pass through the carousel is instant (images are in-cache).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an OVH-backed branch to /api/presenter/tts so the German presenter
narration is synthesized by OVH AI Endpoints' nvr-tts-de-de (NVIDIA Riva)
reached through the LiteLLM passthrough at /tts-ovh/audio/*, which
injects the OVH API token server-side.
- DE requests now hit ${LITELLM_URL}/tts-ovh/audio/v1/tts/text_to_audio
with the documented body shape (encoding=1, language_code=de-DE,
voice_name=German-DE-Male-1, sample_rate_hz=22050) and return the
audio/wav bytes upstream serves (confirmed RIFF-framed in a smoke test).
- EN continues to hit compliance-tts-service until OVH_TTS_URL_EN is set,
making the eventual EN switch a single env flip.
- OVH and voice/url/sample-rate parameters are env-overridable
(OVH_TTS_URL_DE, OVH_TTS_VOICE_DE, OVH_TTS_SAMPLE_RATE,
OVH_TTS_URL_EN, OVH_TTS_VOICE_EN) so retuning doesn't need a redeploy.
- Defensive: OVH failures surface as 502 (no silent fallback) so upstream
issues are visible during this test rollout.
- wrapPcmAsWav() helper is kept as a safety net in case OVH ever returns
bare PCM instead of a full WAV.
Adds X-TTS-Source response header (ovh | compliance) to make
provenance observable from DevTools.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Photo extracted from CV and placed in public/team/
- Team data updated via MCP (both versions):
- Bio: 18+ years industry/strategy, SVP at ETO GRUPPE,
60 employees, M&A, 11 patents, VDMA/CyberLAGO memberships
- Role: CEO & Gründer (was CEO & Co-Founder)
- Expertise tags: Strategie & M&A, DSGVO/AI Act/CRA,
IoT & Embedded, Web3 & Blockchain, 11 Patente
- photo_url set to /team/benjamin-boenisch.png
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SDK Live Demo slide renders screenshots via next/image from
/public/screenshots/*.png. Because /screenshots was not on the
PUBLIC_PATHS list, every request was 307-redirected to /auth, and the
next/image optimizer responded with
HTTP 400 "The requested resource isn't a valid image."
leaving the slide with empty dark frames (surfaced in the pitch preview).
next/image also bypasses middleware itself (see the matcher), but the
server-side fetch it performs for the source URL does hit middleware
and carries no investor cookie, so whitelisting the path is required
even for authenticated viewers.
These PNGs are public marketing assets — there's no reason to gate them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The SDK live-demo slide renders a fake browser URL bar to frame each
screenshot. It used admin.breakpilot.ai, but the actual demo instance
investors should be able to reach lives on admin-dev.breakpilot.ai.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>