Clippers Command Center is a Clippers dashboard with statistical context: a live game view, a between-games home, players, the schedule, every season since 2019-20, and news. Every stored insight carries the query that proves it, and every proof is re-run nightly.

The CCC home page: the next game, New Orleans at home on 8 November, with a countdown; a 1–7 record for 2026-27 with home and away splits and net, offensive and defensive ratings; and the season's eight games as margin bars, one win over Sacramento and seven losses
Fig. 1 Home between games: the next game with a countdown, this season's record and ratings, recent games as margins, and the top of player trends and the insight pool.Screenshot of clippers.lukeghanna.com, 7 November 2026, 1440×810 at 2x, nothing edited.

What it does

Six pages behind one top bar and a ⌘K palette that searches players, games and pages. Home is the between-games view. Live is the game view: scoreboard, quarter line scores, a dense box score, the score in the browser tab, and a sticky mini-scoreboard. Players has a page per player with season averages, rolling averages, splits and a game log. Schedule labels back-to-backs, rest days, home stands and road trips. History covers every season in the database game by game, with a results strip and streaks per season, play-in and playoff games labeled and kept out of the records, and drill-through to every box score. News collects articles alongside Reddit and Bluesky posts about the team.

Darius Garland's player page for 2026-27, eight games logged: 20.9 points, 1.6 rebounds, 6.6 assists and 58.2 true shooting, a rolling five-game points average, and home/away and win/loss splits
Fig. 2 A player page eight games into 2026-27: season averages, a rolling five-game average, and venue and result splits.Screenshot of clippers.lukeghanna.com/players/3153, 7 November 2026, 1440×810 at 2x, nothing edited.

The interface was rebuilt in September 2026 on a new design system: Geist, a Clippers palette and glass panels, real team logos, page transitions, and layouts that work down to phone width. It installs to a phone's home screen. Before that it was a desktop-only dashboard with a 1,024-pixel minimum.

Provable insights

The product rule is simple: if a claim cannot be verified from stored data, it does not display. Each insight row stores the parameterized SQL that supports it, the parameters it ran with, the rows that SQL actually returned when it was executed, and a stable identity key.

proof_sql     parameterized SQL that supports the headline
proof_params  the ids and dates it ran with
proof_result  the rows proof_sql returned; empty means the insight is rejected
proof_hash    sha256(category | team | player | game | season | metric): identity, not proof

A generator whose proof comes back empty is dropped before the upsert. The identity key is a unique index, so re-running the engine updates an existing insight instead of duplicating it, and any insight a run does not re-emit is deactivated. Every night a verify step re-runs every active proof and fails the job if the stored rows no longer come back. Batch categories run in dependency order: scoring and shooting streaks, season milestones, rare events (the top 1% of every NBA player-game that season), opponent context, league comparisons, and year over year. Claims are scoped to one stats season, the latest with at least ten Clippers games, regular season only, for players who finished the season with the Clippers and whose latest game was for the Clippers, so a player who has since left is not headlined. Rank proofs sum in double precision and round the values they return, so a re-run reproduces the stored rows exactly and the nightly verify cannot fail on floating-point noise. Importance is a base score per category plus rarity and recency boosts, capped at 100, and the client rotates the top of the pool.

Live insights are different. They are pure functions over the latest live data and the rolling ten-game context, they are never written to the table, and they carry no stored proof. Two detectors: a scoring run of eight or more unanswered points in the last two minutes of play-by-play, and a clutch flag for the last five minutes of the fourth quarter or overtime with a margin of eight or less.

An insight card: Darius Garland ranked 15th in the NBA in assists in 2025-26, 6.7 per game, 15th of 361 qualified players with a minimum of 41 games, with a Verified footer citing 45 games and 361 players ranked, above three more insights
Fig. 3 An insight as it renders: the headline, the number behind it, and a Verified footer naming what its proof returned. Until the Clippers have ten games in 2026-27, batch insights describe 2025-26.Screenshot of the home page's insight panel on clippers.lukeghanna.com, 7 November 2026, at 2x; this insight brought to the front by clicking it in the panel's list.

If it can't prove it from the database, it doesn't show it.

Live without flicker

A game-night runner polls the NBA feed on a cadence set by the state of the game: every three seconds in live play, two in the clutch, eight during stoppages, thirty early in halftime. It fetches the box score only when a new play appears, writes one live-state row per game, and publishes each change to a push hub. One poller makes about 3,000 requests to the NBA feed over a game. When the NBA CDN refuses it, the runner switches to ESPN's public scoreboard and game summary, translated into the shapes it already reads.

The hub is a Cloudflare Durable Object at live.lukeghanna.com with one room per game. Browsers hold a WebSocket to it and get a keyframe every thirty seconds with deltas in between, and a browser that reconnects replays the last 150 seconds. If the socket drops or goes quiet, the page polls /api/live instead, which Cloudflare's edge caches for two seconds, so the origin answers about once every two seconds per region however many people are watching. That endpoint reports one of three states: no active game, data delayed, or live. If the runner itself goes stale, the browser reads ESPN's scoreboard directly as a last resort, for the score, clock and period only. A chip on the page names the source it is on, and last-known numbers show under a "Data delayed" banner instead of passing as current.

Around the score: a game-flow chart with a model win probability, always labeled a model estimate; a rotation clipboard with stints, foul trouble and minutes against the player's usual; tonight's five-man units; and spoiler sync, which holds everything on the page to the fan's TV or stream delay. The model's σ, how far the final margin strays from its expectation, is fitted by Brier score separately for games with a closing spread and games with only home court. As of 7 November the home-court fit (573 games) is stored, and spread games use a default σ until 50 of them exist; there are six. Preseason games go live too, but are never finalized or counted.

Game nights so far

The runner tracked the season opener on 21 October, a 121–96 win over Sacramento: 746 live-state updates, 586 plays, and the game finalized when it ended (Actions run 39706347013). It did the same for the Lakers on 23 October and Oklahoma City on 25 October. Around 27 October cdn.nba.com started answering the runner with 403s, every tick failed, and the 28 and 30 October and 2 and 4 November games got no live state; the browser's ESPN fallback showed a bare score. GitHub also dropped scheduled runs: the 1 November game was never launched, and the 28 October run started four hours after tip.

The fixes merged between 3 and 5 November. The runner falls back to ESPN after the first 403, and the scoreboard request now raises the error that triggers the fallback, which it did not on 4 November. A launch picks up any game tipping within five hours and sleeps until tip, and a run that hits its time limit mid-game starts its own successor. A running runner checks main every two minutes and hands the game to a fresh run when the code it runs has changed, because on 4 November a fix merged 28 minutes after tip never reached the run already under way. None of this has run during a real game yet; the next is New Orleans on 8 November.

Pipeline

Separate scripts, one job each: sync the schedule and attach NBA game ids, sync the odds, run the live game night, sync every NBA game's box scores league-wide (the last three days nightly, or a whole season on demand), finalize games, compute stats incrementally (possessions, pace, offensive and defensive rating, shooting efficiency, five- and ten-game rolling windows), generate insights, verify insights, and backfill. Every write is an idempotent upsert, so any script can be re-run. GitHub Actions drives them: the schedule daily, odds every six hours, news every fifteen minutes, the game-night runner, and a post-game job that syncs, finalizes, computes, ingests play-by-play, builds the record book, generates and verifies, in that order. The runner dispatches the post-game job the moment a game goes final; its nightly cron stays as a backup. No queue, no workflow engine. Cron authentication fails closed with a constant-time compare, failures return real error codes instead of a quiet 200, and CI runs typecheck, lint and 652 Vitest cases (plus 34 marked todo) across 63 files against a Postgres service, and the hub's 16 tests separately (run 42135304934). The whole thing runs on free tiers: Cloudflare Workers and Durable Objects, Neon Free and GitHub Actions.

NBA CDN
live scoreboard, box score, play-by-play; league-wide box scores and game ids
ESPN
scoreboard and game summary when the CDN refuses the runner
balldontlie
schedule, teams, players
the-odds-api
spreads, moneylines, totals; display only
game-night runner
polls by game phase, 2–30 s; box score only on a new play; one live-state row per game; publishes to the hub
sync-schedule · sync-odds · sync-media
daily, with NBA ids · every 6 h · news every 15 min
sync-league-games
every NBA game, last three days; dispatched at the final, nightly as backup
finalize-games
box scores locked once the game is final
compute-stats · ingest-pbp · build-record-book
possessions, pace, ratings, rolling 5 and 10; play-by-play; records; incremental
generate-insights · verify-insights
proof_sql, proof_params, proof_result, identity key; empty proof rejected; every active proof re-run
Page-shaped API
/api/live through the CDN; /api/home, /api/schedule, /api/history, /api/players; staleness in every envelope
Pages
live over the hub WebSocket, then /api/live, then ESPN direct; home, schedule, history, players and news on SWR
Fig. 4 The two pipelines. The game-night runner keeps one live-state row per game current and pushes each change through the hub; everything derived is recomputed after each final, and nightly as a backup, incrementally.Drawn from scripts/, app/api, workers/live-hub, .github/workflows and Docs/DB_SCHEMA.sql in the repository (main, e3e860a).

History

The database holds box scores for every NBA game league-wide, not just the Clippers'. Six seasons are complete, 2020-21 through 2025-26, regular season and postseason; 2019-20 is partial and still loading.

Game detail for 18 June 2021, Clippers 131, Jazz 119, playoffs: quarter line score and the Clippers box score led by Terance Mann's 39 points
Fig. 5 Game 6 against Utah, 18 June 2021: the win that sent the Clippers to their first conference finals, Terance Mann with 39.Screenshot of clippers.lukeghanna.com/history/8312.
The 2025-26 history page: 42-40 record, home and away splits, average margin, and a season results strip of 83 games with win and loss bars
Fig. 6 A season at a glance: record, splits, and every game's margin as one strip, with the longest streaks underneath.Screenshot of clippers.lukeghanna.com/history, 2025-26.

What it is not

No accounts and no personalization. Odds are for display and insight context, not betting. Live mode has run through real games, but the fixes for the NBA CDN's refusals have not yet. And the engine can only prove what is in the database, so a batch claim is scoped to one stats season and the one before it, regular season only.

At a glance
Live poll
2–30 s by game phase
Insight rule
proof, re-verified nightly
History
6 full seasons, league-wide
Seasons
2019-20 → 2026-27
Running cost
$0 on free tiers
Tests
652, plus 16 for the hub