Derek Blum / Tourney
Live production app

Tourney 🏆

A March Madness pool that's run for 15+ years on paper and spreadsheets - rebuilt as a real, live web application. You draft individual players and coaches, not whole teams, and your score updates automatically as real games happen.

1 AWS box
Flask + MySQL + nginx, containerized with Docker, one EC2 host
2 AI systems
Claude vs. a self-hosted model, run head-to-head on the same task
15+ years
the pool itself - paper, then spreadsheets, then this
Live ESPN data
polled continuously during games - not a nightly batch
5 CI gates
secrets · tests · lint · security-lint · dependency audit, before every deploy
Zero PII
in the AI worker's own database account - one table, no names or emails

01The idea

A normal bracket pool asks you to predict which teams win. This one asks you to draft individual players - 22 of them, across five groups, seeded 1-4 through 13-16, plus head coaches - and your score comes from what those specific people actually do in real games.

Two people can agree on every game's winner and still land on completely different scores, because they drafted different players off the same teams. It rewards knowing the players, not just the bracket - and it's why building a live scoring engine mattered more than building another prediction form.

02Architecture

One AWS EC2 box, three Docker containers: a reverse proxy handling HTTPS, the Flask app itself, and its MySQL database. Deliberately lean - no server fleet, no managed services to wire together - sized for a free pool with a modest number of players, not over-engineered for scale it doesn't need.

What it's built with

Python / Flask MySQL SQLAlchemy Docker Nginx HTMX Amazon EC2 AWS SES DuckDB (analytics warehouse) GitHub Actions

Deploy pipeline

A commit to main auto-rebuilds a staging environment the instant five automated gates go green - secrets scan, test suite, lint, security-lint (bandit), dependency audit. Promotion to production is a single manual click after that, fast-forward-only, never a force-push.

Separate from the live app entirely: a second, offline pipeline pulls a conformed copy of six completed seasons (2018-2025) into a DuckDB analytics warehouse, kept apart from production so analysis never touches live scoring.

03AI - two systems, compared honestly

Every day, the app writes a short AI-generated update on how the standings changed - generated two different ways, on two different models, so the comparison is real rather than a marketing claim.

Simple
The app looks up the standings itself and hands the model everything it needs in one message. The model just writes.
Agentic
The model gets no data upfront - it's given a short menu of questions it's allowed to ask (standings, today's results, biggest movers) and decides for itself what it needs before writing.
Claude
Hosted by Anthropic, called through a real agentic tool-calling loop - the prompt deliberately contains no data.
Self-hosted
An open-weight model running on a dedicated NVIDIA DGX Spark - physical GPU hardware, an agentic loop built from scratch, zero API cost.

Both the prompt sent and every tool call made are watchable live, in the running app - not a claim you have to take on faith.

04Built to be safe, not just built to work

A side project that handles real names, emails, and phone numbers doesn't get to skip security because it's small.