How the model works
Transparency over performance. The model is intentionally explainable — no deep learning, no black boxes — so the reasoning behind every lean is auditable.
Flow
01 · Projection
For each player and market (PTS / REB / AST), the model produces a projection by blending three rolling windows plus a home/away adjustment:
+ 0.30 · ( split_avg − base )
The split adjustment uses the player's home or away average depending on tonight's matchup. Weights are deliberately simple and tuned for explainability rather than peak fit.
02 · Implied probability
Sportsbooks publish American odds with vig built in. We strip vig using two-sided proportional de-vigging:
p_raw_over = −odds / (−odds + 100) (when odds < 0)
p_implied_over = p_raw_over / (p_raw_over + p_raw_under)
03 · Model probability
We model the player's stat as a normal distribution centered at the projection, with σ derived from recent dispersion:
Φ is the standard normal CDF. σ is the population standard deviation of the player's last-N games on the same stat, with a floor of 1.0 to prevent degenerate cases.
04 · Edge
Reported in percentage points. The lean (Over / Under) is whichever side has positive edge.
05 · Confidence tiers
Tiers are assigned by edge magnitude AND data-quality sanity check.
- High — edge ≥ 5pp AND ≥ 8 recent games of data
- Medium — edge ≥ 2.5pp AND ≥ 5 recent games
- Low / No Play — anything below the medium threshold
Data sources
GametimePicks runs on a multi-source provider system. Each external service is accessed through a common adapter interface, so the pipeline can fail over from one source to the next without breaking.
- nba_api — official NBA.com Stats endpoints (Tier 1, no key)
- The Odds API — compliant sportsbook odds (Tier 1, free tier 500 req/mo)
- demo — bundled fallback (Tier 1, always works)
- balldontlie · espn · opticodds · sportsdata(Tier 2/3 — scaffolded, opt-in)
The system never scrapes sportsbook websites or reverse-engineers mobile apps. Every key lives in environment variables; nothing is hardcoded.
Demo, Live, Hybrid
Both NBA and odds providers are running on bundled fallback data. Useful for offline development and recruiter previews. Numbers are realistic but not from tonight's slate.
Real NBA data via nba_api and live sportsbook odds via The Odds API. Generated from tonight's actual schedule and current lines.
One source is live, the other fell back to demo. Usually means odds came through but a stats endpoint hiccupped, or vice versa. The data-source badge shows which is which.
Limitations
- No injury / minutes adjustment. The model treats minutes as constant. A late-scratch starter substantially changes projection inputs but isn't reflected until the next pipeline run.
- No back-to-back / rest adjustment. Travel and fatigue impact production. Not currently modeled.
- Lines move. The board reflects odds at pipeline time. By the time you read it, lines have likely shifted.
- No causal claims. A positive edge is correlation between recent stats and the line, not a guarantee.
Manual news overrides
Phase 7B-1 introduces a free, compliant news layer: a local JSON file at pipeline/manual_overrides/news_signals.json. The operator manually adds entries when verifiable news appears (official injury reports, beat reporters, team accounts). Each signal carries a source URL, timestamp, and modelAction directive (e.g. remove_from_board, flag_risk).
The pipeline reads this file on every run, filters expired signals, and attaches relevant ones to each lean. Signals appear on prop cards with the source label, update type, and a verification link.
What this does not do: we do not scrape Twitter/X, do not auto-ingest from any social platform, and do not invent player statuses. If no signal exists for a player, the UI says “No active manual signals” rather than asserting they are healthy. See docs/news_overrides.md in the repo for the operator workflow.