<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kristjan Vinderslev</title>
    <description>The latest articles on DEV Community by Kristjan Vinderslev (@kristjan_vinderslev_95824).</description>
    <link>https://dev.to/kristjan_vinderslev_95824</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4008219%2Faca3c88c-6bcc-41c0-abd2-f5045f4d353a.jpg</url>
      <title>DEV Community: Kristjan Vinderslev</title>
      <link>https://dev.to/kristjan_vinderslev_95824</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kristjan_vinderslev_95824"/>
    <language>en</language>
    <item>
      <title>I built an AI that predicts football matches before kickoff — and forces it to stay honest</title>
      <dc:creator>Kristjan Vinderslev</dc:creator>
      <pubDate>Tue, 30 Jun 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/kristjan_vinderslev_95824/i-built-an-ai-that-predicts-football-matches-before-kickoff-and-forces-it-to-stay-honest-265l</link>
      <guid>https://dev.to/kristjan_vinderslev_95824/i-built-an-ai-that-predicts-football-matches-before-kickoff-and-forces-it-to-stay-honest-265l</guid>
      <description>&lt;p&gt;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 &lt;em&gt;before&lt;/em&gt; a match starts, logs it, and then shows you whether it was right or wrong. No cherry-picking, no editing history after the fact.&lt;/p&gt;

&lt;p&gt;That tool is now live as &lt;a href="https://oddsradarpro.com/en" rel="noopener noreferrer"&gt;Odds Radar Pro&lt;/a&gt;. Here's how it works under the hood, and the design decisions that took the longest to get right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea: don't trust the market, measure against it
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The AI builds its &lt;strong&gt;own&lt;/strong&gt; 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 &lt;em&gt;after&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Nothing exotic — I'm a solo, mostly non-technical builder, so "boring and readable" beats "clever":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vite + React + TypeScript&lt;/strong&gt; on the front end&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; for auth and storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; for hosting and cron jobs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API-Football&lt;/strong&gt; as the single source of fixtures, odds, stats and injuries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I'm most proud of: keeping it honest
&lt;/h2&gt;

&lt;p&gt;Two design decisions make the track record trustworthy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Picks are only logged before kickoff.&lt;/strong&gt; There is zero retro-analysis of matches that already happened. If the model didn't commit before the whistle, it doesn't count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every confident, correct call becomes a public page.&lt;/strong&gt; 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 &lt;a href="https://oddsradarpro.com/en/resultater" rel="noopener noreferrer"&gt;track-record page&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell another solo builder
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache aggressively when you pay per API call.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log before you predict, not after.&lt;/strong&gt; It's tempting to "evaluate" past results. Don't. The discipline of committing first is the entire value of a track record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship in your own language first if it's your edge.&lt;/strong&gt; I kept the main app in Danish because it's a real niche advantage for a one-person project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to see it working, the live track record is here: &lt;strong&gt;&lt;a href="https://oddsradarpro.com/en/resultater" rel="noopener noreferrer"&gt;oddsradarpro.com/en/resultater&lt;/a&gt;&lt;/strong&gt;. It only shows matches where the AI made a confident call before kickoff — and got it right.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Gamble responsibly. This is an analysis tool, not a guarantee. 18+.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>supabase</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
