Reference
Methods explained
The story of a stat: how a box score turns into a daily label like “Sleeper” and a next-10-games projection — and exactly which numbers do the work at each step.
Every night, ~450 stories land in the database
Every player who steps on the floor produces a box score line the moment the final buzzer sounds — points, rebounds, assists, the usual. On its own, that line doesn't say much. Twenty-four points off a hot shooting night and 24 points off a heavy, ground-out workload look identical in the box score, but they mean very different things for what happens next. This page follows one number through the whole pipeline that tries to tell those two nights apart: from the raw box score, through two extra layers most stat sites never show you (Opportunity and Pressure), into a daily classification, and finally into a projection with an honest range instead of a single confident-looking number.
Chapter 1 — What actually happened
The Performance family of metrics is the box score cleaned up: raw counts normalized so you can compare a starter's 30 minutes to a bench player's 15, or a slow-paced defensive team to a track meet. It answers “what happened,” not “why” — that distinction matters later, because it's exactly the piece this family can't provide on its own.
Every one of these lives in public.player_performance_game, and every one of them is outcome-only. That becomes important below: this is the one family of metrics that plays no part in the 9-Cat system.
Chapter 2 — Why it happened: opportunity
Two players can score the same 24 points from completely different places: one got there on volume — touches, shots, trips to the line — the other on efficiency alone. Opportunity metrics measure the raw input — how many touches, shots, and rebound chances a player actually got — independent of whether they converted any of it. This is the layer that starts to answer “why,” and it's the first of the two families that feed directly into the 9-Cat system.
Each of these also has an _abs (volume) and _norm (share of the team, adjusted for minutes played) version:
The normalized versions are what make it possible to fairly compare a 15-minute bench player's opportunity against a starter's — and they're the reason the system can say “this bench player is getting more than their share” before it ever shows up as a big per-game number.
Chapter 3 — Who else is eating: pressure
Opportunity isn't just about one player — it's a shared resource on a 5-man floor. Pressure quantifies how much teammates in a similar role are “crowding” that resource, which is what lets the system reason ahead of time about who actually benefits when a high-usage teammate goes down, instead of guessing.
This is the second family that feeds directly into the 9-Cat system — specifically, it's what powers the daily Injury beneficiary and Role decline labels in Chapter 5.
Chapter 4 — Not overreacting to a hot week
A player who goes 3-for-4 from three on a Tuesday isn't suddenly a 75% shooter, and a system that reacted as if they were would be worse than useless. Every conversion rate is shrunk back toward that player's baseline, by an amount that depends on how much recent volume actually backs the new number up:
k isn't a guess — it's measured directly from 9 seasons of historical data as “how much volume until this category stops being mostly noise.” Points stabilize fast, needing only about 10 games' worth of volume; turnovers and steals are noisier and need roughly 40. The baseline itself comes from a fallback chain built for exactly this: a career-weighted average of the last 3 seasons first, a player's own first 20 games this season if they have no NBA history yet, and a historical rookie-cohort average (same draft slot, same position) as the last resort. A trade resets the clock — a player's “current typical” becomes their new-team stint, not a blend with their old team.
Chapter 5 — Turning signals into a label
Every day, each of the 9 categories gets a handful of these stabilized signals compared against a threshold — and that threshold stretches wider for noisier categories, so a flag means roughly the same level of confidence whether it's attached to rebounds or free-throw percentage.
If enough of these clear the bar in the right combination, a category earns one of 7 flags:
Real volume growth backed by more minutes, conversion in line.
Low competition for touches, or unclaimed opportunity — hasn't broken out yet.
Elevated volume tied to a currently-out teammate — may regress on their return.
Conversion running cold with opportunity intact — due for positive regression.
Conversion running hot without the volume to back it up — expect it to cool.
Crowding up or opportunity down despite stable minutes — a real usage cut.
No notable deviation from baseline.
Every flag travels with a strength (Strong / Moderate / Weak — how decisive and reliable the signal is) and a confidence score (how much sample backs it). Injury beneficiary is never the headline — it always shows as a caveat, since it depends on someone else staying hurt. In the Scanner, a player's flagged categories are rolled into one Overall label by weighted vote, heaviest group wins.
Chapter 6 — What comes next: the projection
Seven of the nine categories get a precise next-10-games number with a range around it. Steals and made threes get a range only, tagged Range only in the player view: a held-out validation test (never-seen-the-future data) showed neither stat supports a reliable single number — steals is just a noisy category by nature, and made threes multiplies three independently-uncertain factors (minutes, attempt rate, shooting%), which compounds noise faster than it can be averaged away. Rather than hide them, the range itself is separately checked against real outcomes and widened until it actually captures the true value about as often as it claims to.
Projected minutes come from their own small model: recent form, a discount for back-to-backs, a bump if a same-position teammate is currently out (capped, and fading the further into the 10 games it projects, since absences rarely last the whole window), and a ceiling based on rotation size.
Scoring, rebuilt — August 2026
Points used to be one blended number: chances to score, times points per chance. That hid two different skills inside one figure. A player who shoots well and a player who lives at the free-throw line are doing different things, and a single conversion rate cannot tell them apart — nor can it be smoothed correctly, because the two need very different amounts of evidence before they are worth believing. Shooting percentage takes roughly six times as many attempts to settle as free-throw percentage does.
The shot-attempt model is the part that matters most: it is worth around three quarters of everything the points projection has gained. It is also the most heavily tested thing here. Several stronger-looking versions were discarded — one where five random ranking keys scored exactly as well as the real one, and another whose arrival and departure terms came out with textbook opposite signs before turning out to be measuring minutes rather than shots. What survived was checked by rebuilding it from rebounding, a skill nobody competes with a shooter for; that version cannot beat the baseline at all, which is what tells you the real one is measuring competition for shots and not simply roster upheaval.
One thing the model does well and one it does not. When a high-volume shooter arrives, it moves every teammate down correctly — on one such roster it moved all eleven the right way and cut the error by 28%. When a shooter leaves, someone takes those shots and nothing tested so far identifies who: ten different approaches have failed at it. So a projection for a player whose team just lost a scorer deserves less confidence than one for a player whose team just gained one, and that asymmetry is real rather than a hedge.
A player with no games logged yet this season — an unplayed season, a not-yet-debuted rookie, a fresh trade — is tagged Preseason and projected from last season's own numbers instead (falling back to a career average only when there is no prior season at all), with a visibly wider range to reflect the extra uncertainty.
How it all connects
Nothing above is recomputed from scratch at each step — the 9-Cat system reuses Opportunity and Pressure as its raw inputs. Each category is wired to exactly one opportunity column (its volume signal) and one pressure column (its crowding signal); its “conversion” is the genuine box-score outcome divided by that same opportunity count — a stricter, chance-adjusted version of “shooting/passing/rebounding well” than a raw percentage.
| Cat | Opportunity input | Pressure input | Conversion (skill rate) |
|---|---|---|---|
| PTS | sor_p36 — Scoring Attempts | pressure_v2_score | scon = points / scoring_attempts |
| REB | reb_ch_p36 — Rebound Chances | pressure_v2_rebch | rcon = rebounds / rebound_chances_total |
| AST | pass_p36 — Passes | pressure_v2_aor | acon = assists / passes |
| STL | deflections_p36 | pressure_v2_stl_opp | stl_conv = steals / deflections |
| BLK | contested_p36 | pressure_v2_blk_opp | blk_conv = blocks / contested_shots |
| TOV | touches_p36 | pressure_v2_touch | tov_per_touch = turnovers / touches |
| 3PM | three_opp_p36 — 3PT Opportunity | pressure_v2_three | fg3_pct = 3PM / 3PA |
| FG% | uncontested_fga_share — shot-mix quality | pressure_v2_score | fg_pct = FGM / FGA |
| FT% | fta_pg — FT attempts/game | pressure_v2_score | ft_pct = FTM / FTA |
What does NOT feed the 9-Cat system: Chapter 1's Performance metrics. They live in a separate table (public.player_performance_game) the flags/ projection pipeline never reads — a parallel, outcome-only lens on the same games, good for browsing box scores, not part of how a flag or projection is calculated.
Backing tables: public.player_opportunity_game, public.player_opportunity_pressure_game, public.boxscore_traditional_v3 roll up into public.player_window_agg, which feeds public.player_cat_flags and public.player_cat_projection. All recompute daily.
