Work / case study 02
Banger Board
A fantasy hockey draft platform for a league whose scoring makes the public rankings wrong.
- 14teams in the league
- 497players in the dataset
- 16starters per roster the model fills
- 45second draft clock in the mock

The problem
A 14-team ESPN head-to-head points league with scoring that punishes anyone who drafts off a magazine list: defensemen get a bonus on every point, faceoffs count, hits and blocks count, penalty minutes cost, and goalies are paid for workload as much as for wins. Public rankings assume standard scoring, so they are wrong in predictable ways. Banger Board replaces them with the league’s own numbers.
What I built
- Projections and value over replacement. Every player gets a projection from per-game rates over projected games played, blended with published projections where they exist. Replacement level per slot comes from filling all 14 lineups league-wide, best players first, and reading off the best player left.
- A draft room that thinks about waiting. For each open slot it estimates the best player likely to still be there at your next pick, using a logistic “gone” probability over the picks until your turn, and ranks by value now minus expected value later. Players who fill no open starting slot only show up when they clearly beat the need-fill.
- A mock-draft simulator with CPU drafters in two styles, a 45-second clock, auto-pick, a grade against the other 13 lineups and a pick-by-pick review.
- Draft-night sync and live market data. Two devices converge through an 8-character share code and a revision-guarded document in Redis; a losing writer adopts the winning copy and picks are merged rather than clobbered. Live ESPN average draft position feeds the wait model only, so value never reads the market.
How it is built
Next.js 15, TypeScript and React 19 on Vercel. All draft state lives in the browser with export and import, which is also why the same source builds as one self-contained HTML file through Vite, with next/link and next/navigation aliased to hash-router shims. The two builds cannot drift because they compile the same files.
A Python pipeline over NHL Stats API extracts and several ranking lists produces the 497-player dataset, with hand-maintained tables for offseason moves, position eligibility and injured players. The pure modules (scoring, the draft model, the sync merge rules, accent-insensitive name matching) are unit-tested.
What I would do next
Cloud sync for the whole platform instead of one draft, a nightly projection refresh from live season stats, a waivers page ranked by value over your worst starter, and a week planner that counts games per team against your lineup slots.

