FIFA World Cup Draft Pool
Fantasy draft order for 12 friends — determined by World Cup results, calibrated by Monte Carlo simulation.
The Problem
12 friends enter a pool. Each person is randomly assigned 4 World Cup teams — one from each tier. As the tournament plays out, your teams earn fantasy points. Most points = first pick in the NFL fantasy football draft. But without accounting for team strength, the person who gets Spain and France always wins. The scoring needed to be fair — every tier should matter.
What I Built
A full-stack web app that manages the entire pool lifecycle: draft randomization with group-conflict constraints, live match result entry, fantasy point calculation with tier-weighted multipliers, and a real-time leaderboard. Three independent pools share the same codebase with separate KV namespaces, and a central admin hub fans out match results to all three simultaneously.
The scoring system was calibrated using Monte Carlo simulation — 5,000 full tournament runs using real betting odds. The simulation revealed that without tier multipliers, Tier 1 teams dominated ~96% of every owner's score. The chosen multipliers (1x/2x/4x/8x) produce balanced contribution across tiers — each tier accounts for roughly 25-30% of the winning owner's score.
Architecture
┌──────────────────────────────────────────────────────────┐ │ Cloudflare Edge │ │ │ │ ┌─────────────────┐ ┌─────────────────────────────┐ │ │ │ Workers (V8) │────▶│ KV Namespace │ │ │ │ │ │ ┌─────────────────────────┐ │ │ │ │ - Router │ │ │ owners: [...12 owners] │ │ │ │ │ - Draft logic │ │ │ results: [...matches] │ │ │ │ │ - Scoring calc │ │ │ owner_names: [...names] │ │ │ │ │ - SSR HTML │ │ └─────────────────────────┘ │ │ │ └─────────────────┘ └─────────────────────────────┘ │ │ │ │ Pool 1: fifa.macksportreport.com │ │ Pool 2: ox.macksportreport.com │ │ Pool 3: bbff.macksportreport.com │ │ (Separate Workers + separate KV namespaces) │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Admin Hub (admin.macksportreport.com) │ │ │ │ Enter scores once → fans out to all 3 pool APIs │ │ │ └─────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────┘
Technical Deep Dive
Scoring System
Points = Round Weight × Tier Multiplier. Group wins earn 1 point (base), while the Final earns 5 points. Tier multipliers scale these: T1 teams get 1x, T2 gets 2x, T3 gets 4x, and T4 (Cinderellas) get 8x. A T4 team winning a group match earns 8 points — the same as a T1 team reaching the semifinals.
Draft Algorithm
The draft randomizer assigns 1 team per tier to each owner with a group conflict constraint: no owner can have two teams from the same FIFA group (they'd play each other, creating a conflict of interest). The algorithm shuffles teams per tier and assigns greedily with backtracking, retrying up to 200 times if a shuffle produces an unsolvable assignment.
Multi-Pool Support
Three pools run on identical source code deployed as separate Workers with separate KV namespaces. A fourth Worker (the Admin Hub) acts as a fan-out proxy — when a match result is submitted, it fires 3 parallel fetch() calls to all pool APIs simultaneously. Each pool's response is shown individually so the admin knows if any pool had an issue.
Monte Carlo Calibration
The simulation/ directory contains Python scripts that ran 5,000 full tournament simulations using real betting odds. The simulation tested various multiplier combinations and measured tier contribution variance. The 1x/2x/4x/8x scheme was chosen because it produces the most balanced expected contribution: each tier accounts for ~25-30% of the winning owner's total score.
Cloudflare Products Used
Workers — Application runtime. Full-stack SSR with routing, API handlers, draft logic, scoring calculations, and HTML rendering. Zero cold starts, global distribution. ~1,500 lines of TypeScript, no framework.
KV — Global key-value storage. Three keys per pool (owners, results, owner_names) serve the entire tournament lifecycle. Sub-10ms reads at the edge.
Custom Domains — Each pool has its own domain via Cloudflare DNS + Workers routes. The same codebase deploys to 3 domains with different KV bindings.
Wrangler CLI — Single-command deployment. wrangler deploy pushes to 300+ edge locations instantly.
What I Learned
- • KV is the right database for simple global state — three keys serve an entire multi-round tournament for 12 users at sub-10ms reads
- • Workers SSR replaces an entire Node.js server. No framework, no bundler, no cold starts, no Docker — total upload is 58KB (14KB gzipped)
- • Multi-tenancy via separate KV namespaces + identical Workers code is the pattern for white-label SaaS on Cloudflare
- • Monte Carlo simulation revealed that without tier multipliers, top-seeded teams dominated 96% of outcomes — math matters for fairness, not just intuition
Career Relevance
- • Demonstrates Workers as a complete full-stack platform — compute + storage + routing, zero origin server
- • Multi-tenant deployment pattern maps directly to how startups build SaaS products on Cloudflare
- • Monte Carlo calibration shows data-driven product decisions, not just shipping features