All projects

WC26 Dashboard: World Cup Pool Forecasting

mldashboardbackend-api
WC26 Dashboard: World Cup Pool Forecasting hero

Tracked five quiniela players’ live win odds across all 104 matches of WC2026, updating on every real result. Final score: Spain won the cup, and its owner took the pool with 46 points.

10,000 runs of the full 104-match tournament in under 3 s; odds cached in Cosmos by completed-match count so they served instantly between real matches and refreshed the moment one landed.

Situation

Five of us ran a World Cup quiniela: each picked five teams from a hat out of the best 25 we thought could win, and whoever’s team won the world cup, won the pool. Everyone wanted to know their live odds, and there was no public, inspectable model that updated as the real tournament unfolded.

Task

Build and ship a live dashboard that, as real results come in, gives each player their probability of finishing 1st-5th in the pool, and lets anyone explore what-if scenarios.

Action

  • Built a pure, seedable simulation engine: a standard Elo win-probability drives a Monte Carlo over every remaining match, run 10,000× to tally each player’s finish distribution. It imports no cloud or IO, and a contract test fails the build if it ever does.
  • Wrapped the engine in a Python Azure Functions handler with a Cosmos DB cache keyed by the completed-match count: baseline results are served instantly from the cache; a scenario override always recomputes fresh and is never cached.
  • A timer-triggered job pulled live results from a sports API and updated team Elo ratings (K=32) after each match. The front end is a static-exported Next.js app on Azure Static Web Apps, bilingual es/en, with a scenario builder that let anyone fix a hypothetical result and watch the pool odds shift.

How it works

A pure simulation engine (Elo logistic + Monte Carlo, importing no cloud) behind a Python Azure Functions API, with a Cosmos cache keyed by tournament progress and a timer job that live-updates Elo from a sports feed. The front end is a static-exported Next.js app on Azure SWA.

Result

Live in three days from conception and served a real World Cup pool through all 104 matches: a Next.js + Azure Functions app with 138 tests across 10 files, green on every CI push. The pure engine simulates the full tournament 10,000× in under 3 seconds (a bound enforced on every CI run), so baseline odds were cached in Cosmos and recomputed only when a real match finished. After the final (Spain 1-0 Argentina), the last API responses were frozen into the site as static JSON and the backend was deleted; the dashboard survives as a static archive that costs nothing to keep online.

A flowchart: scenario overrides bypass the cache; otherwise a baseline request is served from Cosmos when the completed-match count is unchanged, or recomputed when a real match has finished.
The most interesting part: the cache is keyed by the number of completed matches, so baseline odds stay cached exactly as long as the real tournament stands still; a what-if never touches the cache.
Two zones: a pure simulation engine that imports no cloud, and the serverless IO shell around it, separated by a purity contract test.
The compute/IO boundary, enforced by a test: the engine imports no azure/cosmos/httpx, so the model stays unit-testable offline and an accidental cloud dependency becomes a red build, not a code review.

Learning

The value here was never model sophistication: a 400-point Elo logistic with a flat draw rate is the whole match model. It was engineering the boundary: a pure, seedable, dependency-free simulation engine wrapped in a serverless handler that caches by real-world state and recomputes only for hypotheticals. The artifact I'm proudest of is the contract test that fails the build if the word "azure" ever appears in the engine. It keeps the model something you can test and reason about offline, no cloud attached.

Tech Stack

Next.jsTypeScriptPythonAzure FunctionsAzure Static Web AppsAzure Cosmos DBPydanticRechartsMonte Carlo SimulationElo Rating System

Services

Data EngineeringBackend APIDashboard & Reporting

Status

Archived