{
 "_luma": {
  "source": "LUMA — court-share.com",
  "url": "https://court-share.com/luma/xpts-leaderboard",
  "cite": "LUMA (court-share.com), xPTS-RAPM",
  "terms": "Free to use with attribution. Link back to court-share.com/luma.",
  "contact": "@Starro____"
 },
 "describes": [
  "xpts-rapm-board.csv",
  "xpts-rapm-board-flat.json"
 ],
 "shape": "one row per (season, player); named columns",
 "rows": 827,
 "grain": "three-season trailing window anchored on `season`",
 "unit": "expected points per 100 possessions",
 "gate": "2000 weighted possessions",
 "spec": {
  "target_xpts": "stint expected points per 100, priced by a 707-bin engine (zone x distance x shot family x possession context), free throws at the season league rate",
  "target_conversion": "stint (actual - expected) points per 100",
  "target_pure": "stint actual points per 100",
  "possessions": "event-counted: FGA + 0.44*FTA + TOV - OREB",
  "estimator": "two-sided ridge (an offensive and a defensive coefficient per player), target centered on the possession-weighted window mean, no prior",
  "alpha": 10000,
  "decay": "1.0 / 0.7 / 0.49 over the anchor season and the two before it",
  "stint_floor": "stints of at least 15 seconds"
 },
 "seasons": {
  "2526": {
   "season": "2025-26",
   "window": [
    "2324",
    "2425",
    "2526"
   ],
   "players": 425,
   "alpha": 10000.0,
   "league_xpts_per_100": 112.128,
   "league_conversion_per_100": 0.194
  },
  "2425": {
   "season": "2024-25",
   "window": [
    "2223",
    "2324",
    "2425"
   ],
   "players": 402,
   "alpha": 10000.0,
   "league_xpts_per_100": 111.87,
   "league_conversion_per_100": 0.174
  }
 },
 "columns": [
  {
   "key": "season",
   "desc": "Season the window is anchored on. 2025-26 covers 2023-24 through 2025-26."
  },
  {
   "key": "nba_id",
   "desc": "NBA.com player id."
  },
  {
   "key": "player",
   "desc": "Player name."
  },
  {
   "key": "net_rapm",
   "desc": "Net RAPM: xpts_net + conv_net. The standard RAPM, recovered by the decomposition."
  },
  {
   "key": "pure_rapm",
   "desc": "Pure RAPM: an INDEPENDENT ridge solve on actual points, same stints, same weights, same penalty, no priors. Published so the identity can be checked against a number that was never derived from the other two."
  },
  {
   "key": "xpts_net",
   "desc": "Net xPTS-RAPM: xpts_off - xpts_def_raw. Shot-quality impact per 100 possessions."
  },
  {
   "key": "xpts_off",
   "desc": "Offensive xPTS-RAPM: how the player bends his own team's shot diet."
  },
  {
   "key": "xpts_def",
   "desc": "Defensive xPTS-RAPM, SIGN-FLIPPED so positive is good defence. This is the display convention used on court-share.com."
  },
  {
   "key": "xpts_def_raw",
   "desc": "Defensive xPTS-RAPM as solved, where NEGATIVE is good defence. xpts_net = xpts_off - xpts_def_raw holds on this column, not on xpts_def."
  },
  {
   "key": "conv_net",
   "desc": "Net Conversion RAPM: conv_off - conv_def_raw. Points above expected per 100 possessions."
  },
  {
   "key": "conv_off",
   "desc": "Offensive Conversion RAPM."
  },
  {
   "key": "conv_def",
   "desc": "Defensive Conversion RAPM, SIGN-FLIPPED so positive is good defence."
  },
  {
   "key": "conv_def_raw",
   "desc": "Defensive Conversion RAPM as solved, NEGATIVE is good."
  },
  {
   "key": "poss",
   "desc": "Weighted possessions across the three-season window, decayed 1.0 / 0.7 / 0.49. This is the sample behind every other column."
  },
  {
   "key": "min",
   "desc": "Weighted minutes played across the window, decayed like possessions."
  },
  {
   "key": "min_raw",
   "desc": "Minutes played across the window, UNDECAYED. Minutes per game is min_raw / gp; decayed minutes over an undecayed game count is not a rate."
  },
  {
   "key": "gp",
   "desc": "Games played across the window. Not decayed: a game is a game."
  }
 ],
 "identities": [
  "net_rapm == xpts_net + conv_net                (exact by construction, 827/827)",
  "net_rapm == pure_rapm within one rounding unit (827/827 at 0.001, max 0.0010)",
  "xpts_off + conv_off == pure offensive          (827/827 at 0.001, max 0.0010)",
  "xpts_def_raw + conv_def_raw == pure defensive  (827/827 at 0.001, max 0.0010)",
  "xpts_net == xpts_off - xpts_def_raw             (definitional; published fields are rounded to 3 dp independently, so this holds to 0.001)",
  "conv_net == conv_off - conv_def_raw             (definitional; same rounding note)",
  "league_xpts_per_100 + league_conversion_per_100 == the actual-points league mean (exact, both windows)"
 ],
 "verification": {
  "unrounded_identity_residual": "max |(xpts_net + conv_net) - pure_rapm| = 1.04e-10 (2025-26) and 7.23e-11 (2024-25), measured by re-solving all three ridges at full precision with _verify_xpts_resolve.py",
  "reproducibility": "re-solving from the stint files reproduces the published values to 0.00063, the difference between a 1,000-iteration and a 4,000-iteration LSQR run on a 3-dp grid",
  "possessions": "recounted from the stint files to within 0.05 weighted possessions on every player",
  "scripts": [
   "courtshare-backend/_xptsrapm_CANONICAL.py",
   "courtshare-backend/_pure_rapm_3yr.py",
   "courtshare-backend/_audit_xpts_board.py",
   "courtshare-backend/_verify_xpts_resolve.py",
   "courtshare-backend/_build_xpts_release.py"
  ]
 },
 "caveats": [
  "The main formulation has no defender distance. The 707-bin engine captures about 77% of the value found in defender distance. I lose roughly 23% of real shot-quality information, at the lowest. My xPTS is unfinished until I get real defender-distance data. Treat this as a working proof of concept.",
  "This is shots only, not full EPA. There is no passing value in the quantification. xPTS is expected points per shot attempt.",
  "The collinearity problem persists, even in xPTS-RAPM. Teammates who always share the floor are hard to separate. Some players carry credit that belongs to their lineups. 2,000 weighted possessions is a floor, not a cure.",
  "Descriptive, not predictive. Chance creation does not out-predict actual results in basketball. xPTS and its RAPMs are context for how value is created, not a forecast."
 ],
 "known_issues": [
  "Five seasons of stint data exist (2021-22 through 2025-26), so 2023-24 is the earliest anchor with a full three-season window. Only the two anchors published here have been audited row by row.",
  "Defence is stored twice: `*_def` is sign-flipped for display, `*_def_raw` is as solved. The subtraction identities hold on the raw columns."
 ]
}