Drawing 02.1, league platform
Advantage Sports
The software that runs the Advantage Flag League, about 1,500 families a season: registration, the payment ledger, team formation, schedules, standings, and messages to families. Built from scratch for one league.
| No. | Part | What it does here |
|---|---|---|
| 1 | Browser | Families register and owe a balance; admins upload check deposits, confirm matches, and run the season. |
| 2 | Cloud Storage | The deposit PDFs the browser uploads by signed URL, and the page images rendered from them. |
| 3 | Cloud Run renderer | A container that renders each deposit page to an image with pdfjs-dist and @napi-rs/canvas. |
| 4 | Next.js on Vercel | Pages, server actions, eight crons, and a job worker that runs each step inside a 60-second call and then calls itself for the next. |
| 5 | Neon Postgres | League data and the payment ledger through Drizzle, plus the job table and its leases. |
| 6 | Claude | Reads each check on a page: payer, amount, and check number, for matching. |
| 7 | SendGrid, Twilio | Sign-in codes, notices, and blasts by email, and texts behind a kill switch. |
| 8 | Schedule solver | A one-shot Fargate Spot task that builds the season's schedule and writes it to Postgres. |
Why it exists
The league ran on Sports Connect, which goes end-of-life in fall 2026, and nothing off the shelf fit how a volunteer-run flag football league actually works. So I built what should have existed. It started as League-OS; the Advantage Sports name is for the day other leagues run on it too.
The interesting part
Most of it is careful bookkeeping. Two pieces are not. Families still pay by paper check, so an admin uploads a scanned deposit and the app posts the payments. The browser writes the PDF straight to cloud storage, a container on Google Cloud Run renders each page to an image, and Claude reads every check on it: payer, amount, check number. Each check is matched to a registration and checked for duplicates. Strong matches confirm themselves, an admin reviews the rest, and a separate job posts them to the ledger. There are no cards: checks, Zelle transfers imported from the bank's file, cash, and money orders all land in the same ledger.
Those steps run as jobs in Postgres. A worker claims a job with a three-minute lease, runs a step inside a 60-second serverless route, and calls itself for the next one; a cron every five minutes picks up anything that stalled.
The other piece is team formation. Sorting hundreds of kids into fair teams used to be a weekend of spreadsheets. The solver seeds teams greedily, polishes them with simulated annealing, then repairs coverage. Friend requests and keeping schoolmates together come first, then balance by grade and quarterbacks, and every kid is checked for at least one familiar face on the roster. Claude narrates what changed between two runs, nothing goes live until an admin accepts one, and coaches see their teams before families do.
What it does
- Registration with a running balance for each family
- One ledger for checks, Zelle, cash, and money orders
- Check deposits read by Claude, matched, and confirmed by an admin
- A team-formation solver, with runs compared side by side
- Season schedules built by a solver task on AWS
- Email and text blasts, with lanes families unsubscribe from separately
Plates

