Two products, one discipline: every number an LLM puts in front of an allocator is one click from the evidence it came from — and was checked before you ever saw it.
Two products on one verification spine. Document Intelligence is the foundation, built to production quality, and Allocator Memo Builder is built on the same backbone — because both are really the same problem (defensible, auditable AI over messy financial data), and building the second on the first shows the architecture generalizes. They share one app, one verification spine, and one design language; you switch between them from the rail.
The thesis. In finance the valuable LLM property isn't fluency — it's defensibility. So the LLM is never trusted blindly:
- Option A (extraction): every extracted figure carries per-field provenance and is verified fail-closed — the source quote must contain the figure, YTD must reconcile to ∏(1+mᵢ)−1, a 3× self-consistency vote must agree — and anything that fails routes to a human review queue instead of being hidden.
- Option B (generation): the LLM never computes a number. Metrics are computed deterministically in Python (golden-tested to 1e-9 against
empyrical-reloaded), registered as facts with stable IDs, and the memo is generated as claims that must reference those facts. A fail-closed verifier then checks every number in the prose against its referenced fact — "no naked numbers." Whatever can't be verified ships visibly flagged.
The result, on both sides: an allocator can trust a number because they can see exactly where it came from and how it was checked.
Requires Docker. Brings up Postgres/TimescaleDB + the FastAPI API + the Procrastinate worker + the Next.js web app, then seeds realistic demo data for both products.
docker compose up --build -d # db + api + worker + web
docker compose exec api python -m tools.seed_demo # Option A: 11 funds / 286 returns, with provenance
docker compose exec api python -m tools.seed_universe_demo # Option B: a dirty + a cleaned fund universe (computes live metrics)
docker compose exec api python -m tools.seed_memo_demo # Option B: a mandate + a generated IC memo (verifier runs for real)
# open http://localhost:3000Click the AC monogram at the top of the rail to switch products:
- Document Intelligence → Returns · Documents · Funds · Review · Ask · Observability
- Memo Builder → Universe · Dashboard · Mandate · Memos · Observability
Everything renders on seeded data with no API keys — including a memo whose claims reference real computed facts (and one deliberately-wrong number the verifier catches in the demo). Benchmark data is real (S&P 500 Total Return, fetched live or from a committed snapshot).
Ports already in use? The host ports are parametrized:
API_PORT=8010 WEB_PORT=3001 WEB_ORIGIN=http://localhost:3001 \ API_BASE=http://localhost:8010 docker compose up --build -dStop everything with
docker compose down.
To run the live LLM pipelines (real extraction, real Ask answers, real memo generation) put keys in .env:
OPENAI_API_KEY=sk-... # A: ingestion (extract → verify) + Ask · B: header mapping, ranking, memo generation
FRED_API_KEY=... # B: real 3-month T-bill risk-free rate for Sharpe (free key; without it, rf=0, footnoted honestly)
GOOGLE_CLIENT_ID=... # optional: Google Drive folder sync (A documents folder; B universe folder)
GOOGLE_CLIENT_SECRET=...
Full go-live sequence: docs/runbooks/go-live.md.
"I connect Drive, point it at a folder of 20 messy PDFs from different fund managers, and immediately get a table of all extracted performance data — filterable, queryable, and every figure click-to-source."
A document lands → it's classified → extracted (transcribe-then-extract grids, parallel identity/grid/stats passes, 3× vote) → verified fail-closed → facts and returns land with provenance columns, and anything that fails verification becomes a review item. New files dropped in the watched Drive folder are picked up automatically.
The funds × months pivot with YTD. Click any cell → the provenance drawer: the value, a VERIFIED card (quote-found · value-matches-quote · YTD reconciles · self-consistency votes + model confidence), and the source quote with the figure highlighted.
"I upload a scrappy manager-universe CSV, select my constraints, the system pulls live benchmark data, and I get an IC-memo draft I can verify claim-by-claim."
Upload a messy fund-universe CSV → normalize & validate (fail-closed: clean files auto-proceed, ambiguous ones hold for review) → pull live benchmark data from a public market-data API (Yahoo Finance + FRED, with a committed snapshot fail-safe) → compute allocator-grade metrics (CAGR, vol, Sharpe, Sortino, max-drawdown, Calmar, correlation, beta, tracking error — golden-tested to 1e-9) → screen against a mandate and score deterministically → an LLM produces a ranked shortlist + a 1–2 page IC memo → and the audit view links every memo claim back to a computed metric or a source CSV cell.
Drop a CSV; the validation report shows every issue with severity, a fix-applied changelog, and per-cell provenance. Once validated, metrics compute against a live-fetched benchmark (note the source badge).
![]() |
![]() |
Set constraints → a pass/fail matrix with reasons (the demo gate fund fails a quarterly-liquidity mandate because its 90-day notice + 25% gate + lock-up works out to ~810 effective days-to-cash — shown in full) → a ranked shortlist → "Draft IC memo" streams the sections in, and the audit view lets you click any claim for its evidence. The deliberately-overstated CAGR claim below wears a loud mismatch badge because the verifier refused to let it pass — fail-closed honesty is the feature, not a bug to hide.
![]() |
![]() |
┌──────────────┐ typed OpenAPI client ┌──────────────┐ async ┌─────────────────────┐
│ Next.js web │ ───── (openapi-fetch) ───▶ │ FastAPI api │ ─────────▶ │ Postgres/TimescaleDB │
│ (React 19) │ ◀──── SSE live events ──── │ │ │ + pgvector │
└──────────────┘ └──────────────┘ └─────────────────────┘
│ workspace switcher ▲ ▲
├─ Document Intelligence Procrastinate queue extract→verify→resolve (A)
└─ Memo Builder (LISTEN/NOTIFY) ──▶ ┌──────────────┐ compute facts→generate→verify (B)
│ Python worker │ ──▶ OpenAI (gpt-5.4 family)
└──────────────┘ ──▶ Yahoo / FRED (benchmarks)
Backend — FastAPI + Python 3.12, OpenAI-only (gpt-5.4 / -mini / -nano + text-embedding-3-small), Procrastinate (Postgres-native queue, instant LISTEN/NOTIFY pickup), SQLAlchemy 2 async + Alembic, dependency-injector IoC, TimescaleDB hypertables for telemetry. Clean vertical slices (app/features/*) — A's ingestion/pipeline/review/ask, B's universe/benchmarks/metrics/mandates/memo — over a shared app/domain/* (verification, metrics, scoring, SQL guardrails). The Memo Builder reuses A's verification primitives, LLM seam, queue, and observability rather than reinventing them. NL→SQL runs under a least-privilege read-only role with sqlglot guardrails.
Frontend — Next.js 16 (App Router) + React 19 + TypeScript, Tailwind v4 themed to the design system's palette, types generated from the API's OpenAPI schema (no hand-synced types — the UI can't drift from the contract), TanStack Query, Recharts, a single SSE lib. One shell with a workspace switcher hosts both products.
Provider-portable by design. A thin LLMClient seam isolates the model provider; the verification layer is provider-independent. The system is OpenAI-only today (a deliberate choice — see docs/architecture/), and could swap providers without touching business logic.
Built like production, not a prototype:
- 972 backend tests at 100% branch coverage (CI-gated on
app/domain+app/features), plus 86 frontend unit tests and 18 Playwright end-to-end specs.mypy --strictandruffclean. - The quant is provably correct. Every metric in the Memo Builder is golden-tested to 1e-9 against
empyrical-reloaded— the single biggest credibility risk in front of allocator domain experts, neutralized. - Tests run against real Postgres (rolled-back transactions), not mocked DBs — mocks live only at process boundaries (LLM / market-data / Drive / clock).
- Adversarially reviewed. Layered review passes found and fixed real bugs across both products — a SQL-allowlist bypass, entity-resolution false-merges, two ingestion ship-blockers (
inf/NaNcells that would have crashed the pipeline or poisoned every metric), a sign-dropping verifier bug, and a crash path when the benchmark snapshot was missing.
Run it yourself:
# Backend (db up via `cd backend && make up`)
cd backend && make cov # 972 tests, 100% coverage gate
make type && make lint # mypy --strict, ruff
# Frontend
cd frontend && npm run typecheck && npm run lint && npm test && npm run build
npm run e2e # Playwright (needs the api running)backend/ FastAPI app — features/ (A: ingestion·pipeline·review·ask·search·observability·evals·funds·memory;
B: universe·benchmarks·metrics·mandates·memo), domain/ (verification·metrics·scoring·csv_normalize·
sqlguard·matching·memo_verify), adapters/, alembic/, tools/ (seeds + data generators), tests/ (100% gate)
frontend/ Next.js app — app/(screens)/* (both products), components/{shell,patterns,returns,documents,funds,
review,ask,observability,trace,universe,dashboard,memo}, lib/api (generated client + hooks)
docker-compose.yml db + api + worker + web (parametrized host ports)
sample-data/ synthetic documents (A) + a factor-modelled fund universe (B), each with ground-truth manifests
docs/ architecture/ + screenshots/ + runbooks/
- Option A:
docs/architecture/option-a-document-intelligence.md· ERD - Option B:
docs/architecture/option-b-memo-builder.md - Shared:
docs/architecture/code-architecture.md·00-shared-stack.md· agent memory design - Runbooks: go-live · Drive setup
A full MkDocs site stitches these together: cd backend && make docs-serve.
- Demoable now with zero keys via seeded data, including a fully-generated IC memo with a real verifier run. The live LLM pipelines (extraction, Ask, memo generation) require
OPENAI_API_KEY; the live risk-free rate needs a freeFRED_API_KEY; Drive sync needs Google OAuth. - The committed benchmark window is real S&P 500 TR data; the live FRED 3-month T-bill series is fetched when a key is present.
- Google Drive runs in OAuth Testing mode (7-day refresh tokens); the UI surfaces a reconnect when that lapses.
- Single-tenant by design (no multi-user auth/SSO).
- Document Intelligence is the foundation; Memo Builder demonstrates that the verification architecture generalizes from extraction to generation. Both are independently demoable.
Proprietary — all rights reserved. © 2026 Andres Campoverde. This is the author's proprietary and confidential work; no license or right is granted. No use, copying, modification, distribution, or deployment is permitted without the author's prior express written permission. See LICENSE for the full terms.








