A World Cup prediction pool for hospital colleagues
I wanted a bracket pool for hospital colleagues for the 2026 World Cup knockout stage. I built a site that scored every bracket from live results, ran it to the final and then took it down.
Updated27 Jul 2026
The problem
I wanted a prediction pool for hospital colleagues, covering the knockout stage of the 2026 World Cup from the Round of 32 to the final. It had to take entries quickly, score everyone on its own as results came in, and stay fair as games locked one by one.
What I built
I built a website where anyone could enter with a username and a self-chosen passcode (no email), fill out all 32 knockout games and watch the bracket score itself. Points for each correctly predicted team doubled by round: 1 in the Round of 32, 2 in the Round of 16, 4 in the quarter-finals, 8 in the semi-finals and 16 for the champion, plus 8 for third place. Scoring counted each team you sent through a round, so one wrong early pick did not wipe out a whole branch.
Each pick locked 15 minutes before its own kickoff, enforced on the server so a changed phone clock could not get around it. When a game finished, the real winner moved forward and any pick that had become impossible was cleared, so players could pick again. The leaderboards broke ties by total correct picks, then by correct picks in the earliest rounds, and genuine ties shared a rank. A public stats page showed the most-picked champion and the most divided games, without anyone's individual picks.
Equipment
- Claude Code Harnesses and agents
- Claude (Opus 4.6 to 5.5, Fable 5 and 5.1) AI models
- Codex CLI Harnesses and agents
- GPT (GPT-5.5 to GPT-6.1 in Codex) AI models
- Antigravity CLI (Agy) Harnesses and agents
- Next.js Code and infrastructure
- Supabase Code and infrastructure
- Vercel Code and infrastructure
- football-data.org Data sources
- GitHub and GitHub Pages Code and infrastructure
- Magnific Design and media
- Paper Design and media
How I built it
- I built the site in Next.js with a Supabase Postgres database, deployed on Vercel. The scoring and bracket logic are pure functions with unit tests.
- Codex (GPT-5.5) and Agy (Gemini) reviewed the code independently for security, results sync and data integrity, and I fixed what they found. The database refuses every request that does not come from the site's own server.
- I loaded the real fixtures and mapped them onto the official bracket tree, so the right winners met in each round.
- I pulled results from football-data.org on three paths: a GitHub Actions job every 20 minutes during game hours, a nightly Vercel job as the backstop, and a "Sync now" button for the admin.
- I gave it a clean look of its own, made the homepage mascots with Magnific (Nano Banana Pro) and designed the share card in Paper.
- After the final, I exported all the data with passcodes removed, wrote a final standings page and deleted the site and the database.
Results
The pool took 134 entries, and 99 of them (73.9%) were complete brackets. Spain won the tournament, beating Argentina in the final, and England took third place over France. France was the most-picked champion, in 57 brackets; only 22 picked Spain. The winning bracket scored 76 points, against a median of 39 and an average of 39.28. Every one of the 32 advancer slots was decided and scored.
The site and its database were deleted after the tournament, so there is no link to share.
What I learned
Free sports feeds have gaps, and they show up at the worst moments. After some penalty shootouts, football-data.org marked a game finished with no winner and a tied or empty shootout score, so I taught the sync to fall back on the full-time score; only a truly level result waits for the admin. Team names differ too: of my 32 teams, only "Cape Verde" appeared under another name in the feed.
Vercel's free plan runs scheduled jobs at most once a day. Live updates came from a free GitHub Actions workflow instead, with Vercel's nightly run as a safety net.
An export with credentials stripped and a final standings page let me delete the site and database without losing the result. The database setup scripts stay in the repository, so the pool can be rebuilt for a future tournament.
Lab notes
-
I archived the final data and standings, then deleted the site and its database.
-
On the day of the first knockout game I added a public stats page, tie-breaks where genuine ties share a rank, and cartoon mascots. Over the next two days I fixed the results sync for penalty shootouts that the feed marked finished without a winner, added a rules page and put each player's accuracy on the leaderboard.
-
I built the pool (bracket editor, scoring, username and passcode sign-in, three leaderboards and an admin page) and deployed it on Vercel. After independent reviews by Codex and Agy I hardened it, loaded the real Round of 32 fixtures and set results to sync every 20 minutes during game hours.