← Selected Work
BettingAIPro interface
Full Stack2025

BettingAIPro

A football prediction system. Dixon-Coles does the math. Groq and Gemini layer on top as an ensemble. PostgreSQL holds the audit ledger that logs the baseline, both AI deltas, and a tagged reason for any variance. When the model gets it wrong I know exactly why.

BettingAIPro system architectureFive stages flow left to right: external odds from API-Football feed a FastAPI worker, which calls the Poisson engine running Dixon-Coles, then an ensemble of Groq Qwen3-32B and Gemini Flash 2.0, finally a Next.js TypeScript frontend. The Poisson baseline and both AI deltas are persisted to a PostgreSQL audit ledger with a VarianceReason enum.EXTERNALLIVE ODDSAPI-FootballINGESTIONFASTAPI WORKERPython · async I/OMATH ENGINEPOISSON ENGINEDixon-Coles · xGAI ENSEMBLEGROQ + GEMINIQwen3-32B · Flash 2.0DELIVERYNEXT.JSTypeScript · App RouterPERSISTENCEPOSTGRESQL · AUDIT LEDGERPoisson baseline · Groq Δ · Gemini Δ · VarianceReason ENUM

0

Predictions

tracked · live system

0%

Win Rate

0W / 0L · 0 settled

2

AI Models

Groq Qwen3-32B · Gemini Flash

Feb 25

Active Since

tracking continues

Math Engine

Dixon-Coles with rho correction

Standard Poisson is terrible at low scores like 0-0. It inflates those probabilities in a way that doesn't reflect real football. I had to implement Dixon-Coles with the rho = -0.13 correction to actually get the draw probabilities right. The normalization drift that correction introduces gets absorbed by adjusting the largest output component rather than re-scaling everything, so the sum always lands at exactly 100.0.

Observability

I was guessing why the AI disagreed with the math

I realized I was guessing why the AI disagreed with the math. I added the audit ledger columns so I could see exactly where Groq or Gemini were overriding the baseline. For every prediction I store the three Poisson baseline probabilities, the three Groq-adjusted probabilities, the Gemini delta, and a tagged variance reason for any disagreement. When something is off I can trace which layer flipped which call. The VarianceReason ENUM has eight values like INJURY_DATA_MISSED and GROQ_ADJUSTMENT_WRONG so post-mortems get a specific cause instead of a shrug.

AI Layer

Python because the math lives there

I picked Python and FastAPI because the Dixon-Coles math lives natively there. Porting it to TypeScript would have meant reimplementing scipy-adjacent logic by hand. FastAPI's async I/O lets the same process handle odds ingestion and serve the Next.js frontend without spinning up separate workers.

Observability · Fixed

INJURY_DATA_MISSED was never being set

I had a VarianceReason enum defined and thought it was being set everywhere. It wasn't. When injury data was missing from the scouting layer, the field silently skipped assignment and got logged as null. I was running accuracy calculations on a variance flag that was never populated. Fixed by tracing the pipeline end-to-end and adding the assignment at the exact point where injury data is confirmed absent.

Infrastructure · Fixed

Vercel was killing my AI requests at 10 seconds

Vercel was timing out my AI requests at the 10-second function limit. I split the synchronous call into two endpoints. POST /api/analyse/start kicks the analysis off in a background task and returns a job_id immediately. The client then polls GET /api/job/{job_id} until the result comes back. The in-flight work lives in an in-memory dict with a 1-hour TTL, cleaned up opportunistically on every poll.

Security · Fixed

I caught myself leaving admin routes open

I found backdoors in my admin routes. I wrote a require_admin FastAPI dependency that reads an x-admin-token header and fails closed if the secret is not even configured. Every write-side route including /api/predict, /api/scout, and the job endpoints refuses to run without the right token now. There is no way to accidentally ship an unprotected admin endpoint.

I use AI for research and as a sounding board for syntax, but the logic here is mine. Whether it's the Dixon-Coles correction or the audit ledger schema, I spent the hours debugging these patterns myself. If you pick any line in this repo and ask me why it's there, I can give you the engineering reason and the specific bug it solved. I'm here to build systems, not just prompt them.

ESC