Case study · hey-metrics.com
Independent analytics for basketball lovers. Schedules, standings, advanced team and player stats, lineup data, shot charts, video play-by-play, assist network and more... Built for women's basketball first (WNBA + NCAAW), with NBA and NCAAM now inmplemented.
What it is
Metrics is a single web tool built for the people who care deeply about what happens on a basketball court. Podcasters, writers, content creators, video editors, scouts, and of course Twitter basketball threaders. It surfaces the data those users actually need, at the granularity they actually want.
Women's basketball first. The WNBA and NCAAW are underserved by existing analytics platforms. Metrics was built to fix that. Starting with the leagues where the gap is largest. NBA and NCAAM are both now available on Metrics as well.
Currently running in closed beta.
Features
Play-by-Play video tool: the signature feature
Filter every WNBA or NBA boxscore action into a video playlist. Season · Team · Player ·
Focus (player plays, plays drawn, plays caused) · Play type (assists, steals,
threes, turnovers, blocks, rebounds, fouls, free throws) · Advanced filters
(opposing team, game segment, score margin, period).
Result: a queued video reel with matchup, period, and play description under
each clip. 60 seconds to build a Jessica Shepard assist reel. 90 seconds to
export a Kelsey Plum pull-up highlight for a Twitter thread or a Youtube video.

League stats: Four Factors, six tabs, every filter
Selected league Teams ranked across the Four Factors: Offense (ORtg, eFG%, AST%,
TOV%, ORB%, FT rate) and Defense (DRtg, opp eFG%, opp AST%, opp TOV%,
DRB%, opp FT rate). Every cell carries a colored percentile rank.
Toggle Adjusted to switch values into deltas from league average.
Filter by date range, regular season vs playoffs, or limit to games against
Top 5 / Bottom 5. Standings panel adds Top 5 / Bot 5 records —
the kind of split that tells you whether a hot start is real or padded
by easy matchups. Six tabs: Basics · Overall · Net · Offense · Defense ·
Shooting · Opp Shooting.


Team pages — lineups, trends, roster stats
Every 5-player combination the team has fielded, with minutes together, GP, all the shooting splits, plus-minus — filterable by minimum offensive possessions. Season sparklines (pick any stat, see per-game evolution for every player overlapped with the league average). Minute-by-minute court presence heatmap partitioned by quarter, with color intensity as usage indicator. Roster payroll, contract years, cap percentage.


Player pages — radar, chemistry, shot charts, game log
Five seasons stacked vertically with career row. 6-axis radar (Scoring · Playmaking · Rebounding · Defense · Shooting · Discipline) against position-bucket peers. Lineups featuring this player — best and worst NetRtg chemistry by teammate. Shot charts (shots taken + shots created via assists). Assist network (outgoing and incoming). Full career game log, every row linkable to the game page. Season trends sparklines with rolling window (3 / 5 / 10 games).



Games — calendar, score progression, advanced box scores
Month calendar with per-day game counts. Sidebar on each day: season series record, last 5 form line, tip-off times. On played days: final score, score progression line chart, top performers. Dedicated game page: by-quarter table, score arc, top performers, Four Factors mirrored across both teams, full box score with percentile ranks. Six tabs: Basics · Advanced · Shooting · Offense · Playmaking · Defense.



Players table — every player, every filter
Every WNBA player who's logged a minute this season. Two hundred players. One sortable table with Four Factors per player, percentile ranks baked in under each value. Filter by date range, playoffs, vs Top 5 / Bot 5. Compare modal, scatter chart export, glossary.

NCAAW — same engine, college side
Power 5 + Big East coverage (79 teams, ~1000 players per season). Independent ESPN crawler. UI patterns mirror the WNBA side — anyone fluent on Metrics WNBA reads NCAAW the same way. Players index, single-player pages with Basics / Advanced / Shooting / Profile / Game Log / Season Trends.
Under the hood
What made this hard
Reverse-engineering
Most of the data is available for anyone, but still protected. A standard Python request returns may return a 403. I built a system to gather it and cache it indefinitely. Now runs silently on every request.
PBP possession counter at 99.5% accuracy
Counting possessions from raw play-by-play events sounds simple. And-ones, technical fouls, clock resets, quarter boundaries, team rebounds — all edge cases that break naive implementations. The remaining 0.5% gap is in the official data itself.
30+ advanced metrics, implemented from scratch
True Shooting %, Effective FG%, Usage Rate, Win Shares, PER, Dean Oliver's Four Factors, on/off court ratings, shot zone analytics across 6 zones, league-adjusted stats. Every formula from the source papers and cross-referenced.
A full SPA with no framework
No React. No Vue. No bundler. Custom router, lazy-loaded page modules. The 8-tab analytics grid — sortable columns, sticky team rows, Per Game / Per 100 / Totals / Adjusted toggles — hand-coded, every line. Bundle size near zero. Full control.
Admin panel & agentic maintenance layer
A full admin panel, built into the product
Three-column dashboard, auto-refreshed every 30 seconds.
Stats — signups by tier (free / beta / pro), beta funnel
conversion, 24h visit counts parsed from nginx access logs, activity feed.
Ops — beta invite queue (pending / approved / rejected),
feedback inbox from testers, one-click invite dispatch via Brevo.
Health — live VPS card (CPU, RAM, disk as color-coded bars
with sparklines over time), API service status, cache state (stacked bar:
today / yesterday / this week / older), warmup history for the last 7 runs,
6 alert rules with on/off toggle and test email trigger, quick actions
(trigger warmup, reload nginx, restart API — each requiring typed confirmation).
All side-effectful actions are logged in an admin_actions table.
6 automated alert rules, email-triggered
A cron runs every 5 minutes and evaluates six rules against the
metrics_history table (itself populated every 10 minutes via
psutil + journalctl): memory above 90% for 10+ minutes, disk above 85%,
CPU above 85% for 15+ minutes, 5xx errors above 20 per hour, workers below 1,
fewer than 10 cache files written today. Anti-flap cooldown built in.
Alert emails go out via Brevo the moment a rule fires.
No third-party monitoring service needed.
MCP servers & agentic layer for maintenance
Routine admin tasks — subscription management, email list operations,
deployment triggers, beta approvals — are handled through a set of MCP
servers connected to the product's external services: Lemon Squeezy
(payments), Brevo (transactional email + lists), GitLab (CI/CD pipeline).
This means an LLM can be instructed to approve a batch of beta applicants,
trigger a deployment, resync a subscription, or send a newsletter —
through natural language, without touching the codebase. The MCP layer
sits alongside the runtime app as a maintenance agent, used exclusively
for operator-side tasks. FastAPI talks directly to the external APIs at
runtime; MCP handles the human-in-the-loop operations.
In practice: what used to be 15 minutes of manual API calls across three
dashboards is now a single instruction. This is MCP in production — not as
a developer tool, but as an operational layer that reduces maintenance
overhead at scale.
Stack
"Built solo in Paris. No funding. No ads.""
Building something in sports data, analytics infrastructure, or AI systems? Most of the hard problems here are already solved.