VEEMAN is a conventional three-tier web application. This page explains what runs where and how a request flows — useful if you're evaluating the system or just curious.
| Layer | Technology | Hosted on |
|---|---|---|
| Frontend | Next.js (App Router), React, TypeScript, Tailwind, Recharts | Vercel |
| Backend | FastAPI (Python), NumPy / pandas / SciPy for the maths | Render (Docker) |
| Auth & database | Supabase (Postgres + Auth) | Supabase |
The frontend renders the UI and charts; the backend owns the analytical engines (equity aggregation, metrics, Monte Carlo, correlations, risk sizing); Supabase handles identity and persistent storage.
Browser ──▶ Next.js (Vercel) ──▶ FastAPI (Render) ──▶ Session manager (in-memory)
│ │
▼ ▼
Auth verification Math engines
(Supabase JWT) (NumPy/pandas/SciPy)
│
▼
Storage (files / Postgres)
user_id.Your working set lives in an in-memory session keyed to your user_id:
Because the process does not live as long as the work, every session is continuously mirrored to a draft in storage, and rebuilt from it on demand. When a request arrives for a session id that is no longer in memory, the backend restores it from the draft under the same id — so the client's stored session id keeps working and the restart is invisible.
The draft is split in two records, because the cost is all in the P/L series:
A background loop compares the live session with what was last written and flushes only the difference, roughly every two seconds and once more on shutdown — so a graceful restart loses nothing and no keystroke ever waits on a database write. Drafts expire after 7 days; beyond the 5 most recent, dormant ones are pruned — drafts of sessions still live in memory are exempt.
This is why the app distinguishes the temporary session from things you explicitly save or freeze: a draft protects work in progress, it does not file it. See Core concepts.
Persistence goes through one storage abstraction with two backends:
user_data table keyed by (user_id, kind, name).The kind discriminates the data: portfolios, strategies, variants, history, drafts (the
session autosave above), the Research kinds (backtests, research_models, replay_journals,
tactics, research_studies, indicators), and the
trade-level CSVs used by per-strategy sizing. Either backend is strictly per-user.
Research is part of the same backend code, switched on by a flag, and usually runs on a dedicated server next to the main one, because it needs what the main backend doesn't have: the historical options dataset on local disk (minute-level chains in Parquet files) and the CPU to run backtests in parallel.
Browser ──▶ Next.js ──▶ Main backend /api/analysis/* sessions, library, AI
└──▶ Research server /api/research/* dataset, engine, studies
│
▼
Same Supabase tokens · same database
The frontend shows Research only in builds where it is enabled; without it, the /research routes
answer 404.
The backend accepts two auth regimes so the same code runs locally and in production:
Authorization: Bearer <jwt> header, verified by shared secret
(HS256) or by Supabase's public keys (JWKS). Your user_id is the Supabase UUID.user_id is a username.Routes are protected on both ends: the frontend guards /analysis/* and /research/*, and every backend route requires
a verified identity. More in Security.
Frontend deploys to Vercel on push; the backend is a Docker image on Render; Supabase provides auth and the database. Configuration is entirely via environment variables — no secrets in the code.