DEV Community

Cover image for I built an AI that predicts football matches before kickoff — and forces it to stay honest
Kristjan Vinderslev
Kristjan Vinderslev

Posted on

I built an AI that predicts football matches before kickoff — and forces it to stay honest

Most football "tipster" sites have the same trick: they show you the winners and quietly bury the losers. I wanted to build the opposite — an AI that commits to a prediction before a match starts, logs it, and then shows you whether it was right or wrong. No cherry-picking, no editing history after the fact.

That tool is now live as Odds Radar Pro. Here's how it works under the hood, and the design decisions that took the longest to get right.

The core idea: don't trust the market, measure against it

A bookmaker's odds are basically a probability with a profit margin baked in. The naive approach is to copy the market and shave the margin. I went the other way.

The AI builds its own probability for every match — home win, draw, away win, over/under 2.5 goals, both teams to score — from real signals: recent form, goals scored and conceded, head-to-head history, key players, injuries, lineups. Only after it has its own number does it look at the market. The gap between the two is the whole product: that's where a game is mispriced, and that's where value lives.

The important rule: the AI never blends the market into its own estimate. If it did, it would just slowly converge on the bookmaker and find nothing. It self-calibrates against its own historical hit rates instead, anchored to football's base rates.

The stack

Nothing exotic — I'm a solo, mostly non-technical builder, so "boring and readable" beats "clever":

  • Vite + React + TypeScript on the front end
  • Supabase for auth and storage
  • Vercel for hosting and cron jobs
  • API-Football as the single source of fixtures, odds, stats and injuries

Everything heavy runs in Vercel cron. A few times a day a job pulls the day's fixtures with odds, runs each match through the model once (cached so it never re-bills the same match), and stores the result.

The part I'm most proud of: keeping it honest

Two design decisions make the track record trustworthy:

  1. Picks are only logged before kickoff. There is zero retro-analysis of matches that already happened. If the model didn't commit before the whistle, it doesn't count.
  2. Every confident, correct call becomes a public page. When the AI's pre-match pick lands with high confidence, a cron job writes a server-rendered article: what the AI predicted, what the market gave, and the actual result. They're all collected on a public track-record page.

That second part doubles as organic SEO — real, server-rendered content that search engines and AI answer engines can read and cite. Each page ships with structured data (NewsArticle + SportsEvent), hreflang for Danish/English, and a dynamic sitemap.

What I'd tell another solo builder

  • Cache aggressively when you pay per API call. My biggest early bug was a function that silently failed on file-shaped responses and quietly re-billed. A connection indicator and retries everywhere fixed the trust problem.
  • Log before you predict, not after. It's tempting to "evaluate" past results. Don't. The discipline of committing first is the entire value of a track record.
  • Ship in your own language first if it's your edge. I kept the main app in Danish because it's a real niche advantage for a one-person project.

If you want to see it working, the live track record is here: oddsradarpro.com/en/resultater. It only shows matches where the AI made a confident call before kickoff — and got it right.

Gamble responsibly. This is an analysis tool, not a guarantee. 18+.

Top comments (2)

Collapse
 
vollos profile image
Pon

I went straight to the track record page first, since that's exactly where most tipster sites hide their losers. Good to see the misses sitting right there next to the hits. Logging picks before kickoff and never re-scoring them after is the discipline that makes the whole thing mean something, and not blending the market into your own estimate so it can't just converge on the bookmaker is an easy corner to cut that you didn't.

One thing worth a look while trust is on your mind, since you mentioned Supabase for auth and storage: the public track record is meant to be public, but the user side usually isn't, and Supabase makes it easy to leave a table readable by anyone through its auto-generated API. The common trap is row-level security that's off, or set to using (true), which looks locked but lets any anonymous request read the whole table. I build with AI a lot, and this is the exact thing it hands me without warning: it runs fine, and the gap only shows up if someone goes looking for it.

If it helps, I'm happy to look at your schema and RLS setup for free and tell you whether anything's exposed. No strings, I just keep hitting this pattern in AI-built Supabase apps and like checking them. Either way, solid build.

Collapse
 
xulingfeng profile image
xulingfeng

The "log before you predict, not after" rule is the kind of design constraint most people skip because it's harder — but it's also the only thing that makes a track record mean anything. Curious: how did you set the confidence threshold for what counts as a "confident enough to publish" call? Was it empirical (backtest) or did you tune it live? 🔥