
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.
System Architecture
Live System Metrics
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
Engineering Decisions
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.
Production Incidents
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.
On Technical Provenance
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.