Skip to content

How it's built

A Dixon-Coles model produces the probabilities, an agent with a handful of tools explains how news changes them, and everything sits in one open repository. Here is how the pieces fit.

The system

BundesPredict architectureThe browser talks to a Next.js app on Vercel. Next.js proxies API calls to a Django service on Railway. Django imports a pure Python model library and stores everything in Postgres on Neon. Stripe sends signed webhooks to Django, the agent calls the Anthropic API, and scheduled GitHub Actions run Django management commands against the database.Browserone origin, session cookieNext.js on Vercelproxy.ts rewrites /api/*and adds the proxy secretDjango + DRF on Railwayfootball predictions accounts chatbilling track_record notificationsowns plan, quota and migrationsimportspackages/modelDixon-Coles engine, agent loop,backtest, data parsersnever imports DjangoPostgres on NeonDjango migrations own every tableStripesigned webhooks into DjangoAnthropic APIcalled by the agent for language

Where the boundaries are

The model owns probabilities, the agent owns language

No endpoint, tool or serializer accepts a probability from the language model. The agent can only ask for expected-goals changes, and the engine clamps each one to ±0.60 before recomputing every market from the score grid.

The library never imports Django

packages/model is pure Python: no I/O, no database, no HTTP. The agent reaches data only through small protocols it defines, and the Django app supplies the implementations.

Training never happens in a request

A scheduled job refits the model and stores a new run. Serving reads the newest run, so every stored prediction points at the exact parameters that produced it.

The web app knows who you are, and nothing else

Plan, quota and entitlements are decided in Django, next to the agent that spends the tokens. The browser only ever talks to the Next.js origin, which proxies the API.

Read the source