Skip to content

Repository files navigation

AC — Verifiable AI for Allocators

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.


Quickstart — runs with zero API keys

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:3000

Click 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 -d

Stop 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.


Option A — Document Intelligence

"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.

Returns Explorer — the centerpiece

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.

Returns pivot with the provenance drawer

The rest of the product

Funds — full extracted profile: growth-of-$1000 vs a committed S&P 500 TR benchmark, a monthly heat-grid, and key terms where every figure carries a p.N provenance chip (AUM, fees, hurdle, lock-up). Review — the human-in-the-loop queue for everything that didn't auto-verify: trust meter, evidence quote, Accept / Correct / Reject with A/C/R keys.
Fund profile Review queue
Ask — natural-language questions → grounded answers with citation chips and a full ⓘ trace of the SQL the agent ran. Now with provenance-stamped agent memory (the allocator's mandate, watchlist, and entity-resolution preferences, each linked back to a verified row). Observability — cost/latency/quality as a product feature: spend by model, p95 by stage, an eval model-comparison table, and the ⓘ trace drawer with guardrail badges (SELECT-only · read-only role · statement_timeout).
Ask with citations Observability trace

Option B — Allocator Memo Builder

"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.

Universe → Dashboard

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).

Validation report on a dirty CSV Metrics dashboard with the live benchmark badge

Mandate → Memo + Audit

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.

Mandate screening with the gate-fund liquidity story Memo audit view, evidence drawer open on a flagged claim

Architecture — one spine, two products

┌──────────────┐   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.


Quality & verification

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 --strict and ruff clean.
  • 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/NaN cells 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)

Repository layout

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/

Deeper docs

A full MkDocs site stitches these together: cd backend && make docs-serve.


Honest status

  • 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 free FRED_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.

License

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages