Data product · Live

World Cup Analytics

An analytics product for exploring 2026 World Cup data: matches, teams, players, xG, shot maps, and competition statistics.


01

Context

World Cup Analytics is a data-driven read on the 2026 World Cup: matches, teams, and players explained through data. It's live at worldcup.jvmello.dev, and the underlying data keeps itself current throughout the competition.

02

Problem

Raw match data (events, lineups, stats) doesn't tell a story on its own, and serving heavy aggregations at request time doesn't scale. As the competition progressed, a single API call ended up scanning hundreds of files and taking seconds to respond.

03

Goal

Build a product: dark background, strong typography, and firm presentation rules. Percentiles never as the headline, rates always per 90 minutes, undecided matchups with no internal IDs, with data served ready, never aggregated on the fly.

04

Architecture

A single FastAPI container serves both the SPA and the API. Data flows through two layers: bronze (raw JSON from the source, on disk) and gold (pre-computed payloads in PostgreSQL). The infrastructure includes Caddy with automatic TLS behind Cloudflare, Postgres on an internal network, and daily backups, all living in a separate repository.

05

Data

The source is TheStatsAPI: every match becomes a bundle of seven endpoints (lineups, player stats, event timeline, shot map, referee...). Ingestion is idempotent per endpoint, respecting a rate limit of ~12 requests/minute. Resuming an interrupted ingestion costs seconds, not hours.

06

Pipeline

A builder reads the bronze layer once and writes everything via staging + swap inside a single transaction: if it fails midway, the previous build keeps serving. The api_payloads table stores the exact JSON each endpoint returns. Over 8,500 pre-computed payloads, including player profiles sliced by edition, stage, and match.

07

API

With the payload ready on disk, the FastAPI handler becomes a one-row SELECT. Endpoints that used to take seconds now respond in 30–40 ms.

08

Frontend

A vanilla JavaScript SPA, no framework, with its own World Cup visual identity. Analytical copy, player highlight contexts, team diagnostics, head-to-head reads, comes from a rules engine over percentiles, not an LLM: explicit thresholds and numeric evidence on every card.

09

Technical decisions

The pattern that repeats the most: move cost to where it runs once. Aggregation at build time instead of request time, curation versioned in files, business rules in tested code. A cron job every 15 minutes keeps the data current on its own during the competition, with a file lock preventing overlapping runs.

10

Challenges

Real sports data lies if you don't question it: penalty shootout kicks were inflating a match's xG by several points; the shot map's axis was mirrored; a poorly named field briefly published an extra-time score as if it were a penalty shootout. Each case became a specific fix and, in the trickiest ones, a regression test.

11

Next steps

New competitions, more layers of historical context, and a public API are under consideration. Nothing final yet.

12

Links