Live: clippers.lukeghanna.com
A desktop analytics dashboard for avid LA Clippers fans. Three modes that blend into one continuous experience:
- Live Game Mode — dense box score, rotating "provable" insights, league-context side panels, Vegas odds, polling every ~12s during Clippers games.
- Between-Games Mode — team snapshot, upcoming schedule, player trend summaries, macro insights.
- Historical Explorer — every season since 2010-11, game by game, with drill-through to box scores and trend charts.
Provable insights, not vibe stats. Every headline on the dashboard is generated by a deterministic engine that must verify the claim against stored data before displaying it. If it can't prove "first time in franchise history a Clipper has scored 50+ in a quarter" from the DB, it doesn't show it. Each insight stores the query that produced it, and that stored query is the one that ran — npm run verify-insights re-executes every active proof and fails if a claim is no longer backed by the data. League-wide box scores (every NBA game, not just the Clippers') power ranks, percentiles and scouting reports.
Real-time without flicker. The live page uses SWR over a Next.js API that merges a short-lived cache with snapshots written by a background poller. Stale reads surface a banner instead of silently serving old numbers.
One operator, full pipeline. Schedule sync, odds sync, live polling, finalization, league ingest, stat computation, and insight generation all run as separate scripts, all scheduled by GitHub Actions (live polling is a game-night runner that polls every 12s during Clippers games) — no hosted workflow engine, no queue, no magic.
- Next.js 16 (App Router, Turbopack) + React 19 + TypeScript
- Neon Postgres via
postgres - Tailwind v4 with a small in-house component set (Geist type, Clippers palette), Recharts,
cmdk - SWR for client data,
tsxfor ops scripts - Vitest for unit tests
- Served at clippers.lukeghanna.com (Vercel project
clippers-command-center; the*.vercel.appandccc.hosts redirect there); every data job runs on GitHub Actions
balldontlie.io— NBA schedule, games, box scores (historical + finalization)stats.nba.com/ NBA CDN — live in-game polling and schedule backfillthe-odds-api.com— Vegas odds (display + insight annotation; no betting)
app/ — Next.js App Router: /live, /home, /schedule, /history, /players
components/ — UI: ui/ primitives, shell/ (top bar, ⌘K), game/, live/, home/, players/, schedule/, history/
hooks/ — useLiveData, useInsightRotation
src/lib/ — DB client, insight engine, domain utils + tests
scripts/ — pipeline scripts (sync, ingest, compute-stats, insights); scripts/lib/insights = batch insight engine
scripts/dev/ — seed-fixture-league.ts: synthetic league for local/CI pipeline tests
Docs/ — ARCHITECTURE.md, API_SPEC.md, DB_SCHEMA.sql, DATA_DICTIONARY.md, migrations/
cp .env.example .env.local # fill DATABASE_URL, BALLDONTLIE_API_KEY, ODDS_API_KEY, CRON_SECRET
npm install
npm run db:schema # applies Docs/DB_SCHEMA.sql to Neon
npm run sync-schedule # seed a season
npm run compute-stats
npm run dev # http://localhost:3000Tests:
npm test # vitest (unit)
npm run lint
# End-to-end pipeline test against a throwaway LOCAL Postgres (wiped):
FIXTURE_DATABASE_URL=postgres://postgres@127.0.0.1:5432/fixture npm testData-pipeline scripts:
npm run sync-schedule # schedule from BDL
npm run sync-odds # Vegas odds
npm run poll-live # game-night live poller (GitHub Actions: game-night.yml)
npm run backfill-schedule-nba # attach official NBA game ids to Clippers games
npm run sync-league-games # every NBA game from the last 3 days (nightly)
npm run sync-league-games -- --season=2025-26 # load a whole past season, league-wide
# Seasons before 2019-20 aren't in the cdn.nba.com archive and load from stats.nba.com,
# which blocks cloud IPs — run them from a home network, e.g.:
DB_POOL_MAX=10 npm run sync-league-games -- --season=2012-13
npm run finalize-games # lock Clippers box scores after each game
npm run compute-stats # advanced stats + rolling windows (incremental; --all to rebuild)
npm run generate-insights -- --verbose # run the insight engine, print every headline
npm run verify-insights # re-run every active insight's proof query- 18 routes deployed to production
- 266 commits, 16 phases shipped
- Tests cover the domain layer: insight engine, odds math, history, home, live, players, schedule
- Responsive down to phone width; installable to the home screen (web app manifest)
/dev/liverenders the Live view from a fixture payload in development (404 in production)
Product definition, architecture, and the live API contract are in docs/.