Live: https://anandvaghasia.com/nba/
An interactive NBA analytics desk holding two tools behind one UI:
- Shot Chart Explorer — pick any scorer and see a hex-binned, league-relative shot chart: hex size is shot frequency, hex color is points-per-shot versus the league average from that exact spot that season (warm beats the league, cool trails it). Compare two players side by side. Each player gets a scouting blurb whose every number is drawn from the baked stats, plus rim / mid / three efficiency bars against league marks.
- Statistical Twins — every player-season is a point in a 13-dimensional, standardized box-score space; the nearest points are the twins, and they cross eras freely. Cross-era matches are flagged.
Project 03 in a series of interactive sports-analytics builds (sibling to the NFL 4th-Down Decision Model).
pipeline/build.py— pulls shots (shotchartdetail) and per-100 box stats (leaguedashplayerstats) LOCALLY via nba_api (stats.nba.com blocks cloud IPs). It bins shots into hexagons, colors each hex by the player's points-per-shot minus the league's from the same zone (the zone league averages ride along in the same API response), standardizes 13 features and builds the nearest-neighbor similarity matrix, assigns a rule-based archetype, and writes a deterministic scouting paragraph. Bakes:web/data/players.json— slim per-season meta, per-100 line, zone summary, top-8 twins, scoutingweb/data/shots/<id>-<season>.json— the hexbins for one player-season
web/— a static page (no framework, no build step). It draws the halfcourt and hexbins on<canvas>, does every lookup client-side, and lazy-loads a player's shot file only when picked. The scroll-swish hero is a scroll-scrubbed canvas animation (prefers-reduced-motion→ static frame).deploy.py— FTPS mirror ofweb/into the/nba/slug only (asserts the slug is the working directory before any upload; never touches other slugs).pipeline/verify.mjs— headless-Chrome CDP check of the live page.
- Shot value is league-relative and location-based; it rewards taking and making high-value shots and says nothing about defense.
- Similarity is box-score only — no defense, spacing, role, or shot creation context, and short seasons are noisier.
- The pool is the league's top scorers across eight seasons (2009-10 → 2024-25), so a "twin" is the closest match inside this set.
- No made-up numbers: scouting text is generated from the baked stats, so every figure it cites is exact.
cd nba
python3 -m venv .venv && .venv/bin/pip install nba_api pandas numpy
.venv/bin/python pipeline/build.py # local NBA pull -> bakes web/data
python3 deploy.py # FTPS -> anandvaghasia.com/nba/
node pipeline/verify.mjs # headless verify of the live pageBuilt end to end with Claude Code — pipeline, hexbin + similarity math, and the interface. Data: nba_api / stats.nba.com (no scraping of Sports-Reference).
— Anand G. Vaghasia · 2026 · Powered by NetRyse