Spaces:
Paused
Paused
Download sql/nfl_props_schema.sql from AIBRUH/big-proppa: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/spaces/AIBRUH/big-proppa/resolve/main/sql/nfl_props_schema.sql
- Command line
-
hf download hf://spaces/AIBRUH/big-proppa/sql/nfl_props_schema.sql
-
curl -L -o nfl_props_schema.sql https://huggingface.co/spaces/AIBRUH/big-proppa/resolve/main/sql/nfl_props_schema.sql
10.9 kB
| -- ============================================================================= | |
| -- frontal-lobe2 : NFL player props -- RotoWire (all 22 markets, 8 books) | |
| -- ============================================================================= | |
| -- Source: https://www.rotowire.com/betting/nfl/player-props.php | |
| -- Confirmed 2026-09-25: | |
| -- - robots.txt allows /betting/ (only /users/login.php and /account/ disallowed) | |
| -- - the real prop lines are RotoWire's OWN first-party betting tables, embedded | |
| -- directly as JSON inside the page's static HTML (no JS execution needed to | |
| -- read them -- a plain HTTP GET returns the data already in the response body) | |
| -- - `isPaywalled = true` in the page's JS only gates RotoWire's own CSV *export* | |
| -- button ("must be a paid subscriber" to click Export). It does NOT hide or | |
| -- restrict the underlying data, which is visible to any visitor and is what | |
| -- this schema loads. | |
| -- - The page ALSO embeds a separate third-party "Props.Cash" demo widget with | |
| -- its own weather-context feed (an unrelated Val Town endpoint). That widget | |
| -- is NOT used as a source here -- only RotoWire's first-party tables are. | |
| -- | |
| -- 22 markets confirmed populated (field counts from the raw page, 2026-09-25): | |
| -- TD scorer: firsttd, anytd, lasttd, twotd, threetd | |
| -- Passing: comp, passatt, passyds, passtd, intsthrown | |
| -- Rushing: rushyds, longrush | |
| -- Receiving: recs, recyds, rushrec (combined rush+rec yards) | |
| -- Defense: tackle, solo, assist, sack | |
| -- Kicking: kickpts, fgm, xpm | |
| -- | |
| -- 8 sportsbooks tracked (not every book posts every market -- columns are | |
| -- nullable and a book's absence on a given market/game is normal, not a bug): | |
| -- betr, betrivers, caesars, draftkings, fanduel, fanatics, mgm, hardrock, | |
| -- thescore, circasports | |
| -- (note: some prop blocks show "fanduel", others show "fanatics" in the same | |
| -- book-name slot -- RotoWire appears to have transitioned sportsbook partners | |
| -- mid-market-rollout; both are stored as distinct book codes, not merged.) | |
| -- | |
| -- GRAIN: one row per (player, game, book, market, pull). Lines move during the | |
| -- week, so this is append-only like odds_snapshots in the CFB schema -- never | |
| -- update or delete a row, always insert a new snapshot. Closing-line value (CLV) | |
| -- analysis depends on keeping every snapshot, not just the latest. | |
| -- | |
| -- Engine: DuckDB. | |
| -- ============================================================================= | |
| CREATE SCHEMA IF NOT EXISTS gold; | |
| CREATE TABLE IF NOT EXISTS gold.nfl_prop_market_catalog ( | |
| market_slug VARCHAR PRIMARY KEY, -- e.g. 'passyds' | |
| market_name VARCHAR NOT NULL, -- e.g. 'Pass Yards' | |
| category VARCHAR NOT NULL, -- td_scorer | passing | rushing | receiving | defense | kicking | |
| line_type VARCHAR NOT NULL -- 'over_under' (has a numeric line) | 'moneyline_only' (TD scorer props) | |
| ); | |
| INSERT OR REPLACE INTO gold.nfl_prop_market_catalog VALUES | |
| ('firsttd', 'First TD', 'td_scorer', 'moneyline_only'), | |
| ('anytd', 'Anytime TD', 'td_scorer', 'moneyline_only'), | |
| ('lasttd', 'Last TD', 'td_scorer', 'moneyline_only'), | |
| ('twotd', 'Score 2+ TDs', 'td_scorer', 'moneyline_only'), | |
| ('threetd', 'Score 3+ TDs', 'td_scorer', 'moneyline_only'), | |
| ('comp', 'Completions', 'passing', 'over_under'), | |
| ('passatt', 'Pass Attempts', 'passing', 'over_under'), | |
| ('passyds', 'Pass Yards', 'passing', 'over_under'), | |
| ('passtd', 'Pass TDs', 'passing', 'over_under'), | |
| ('intsthrown', 'Ints Thrown', 'passing', 'over_under'), | |
| ('rushyds', 'Rush Yards', 'rushing', 'over_under'), | |
| ('longrush', 'Longest Rush', 'rushing', 'over_under'), | |
| ('recs', 'Receptions', 'receiving', 'over_under'), | |
| ('recyds', 'Rec Yards', 'receiving', 'over_under'), | |
| ('rushrec', 'Rush + Rec Yards', 'receiving', 'over_under'), | |
| ('tackle', 'Total Tackles', 'defense', 'over_under'), | |
| ('solo', 'Solo Tackles', 'defense', 'over_under'), | |
| ('assist', 'Assisted Tackles', 'defense', 'over_under'), | |
| ('sack', 'Sacks', 'defense', 'over_under'), | |
| ('kickpts', 'Kicking Points', 'kicking', 'over_under'), | |
| ('fgm', 'Field Goals Made', 'kicking', 'over_under'), | |
| ('xpm', 'Extra Points Made', 'kicking', 'over_under'); | |
| -- Scoped to the books the user actually wants tracked (draftkings, fanduel, | |
| -- caesars). RotoWire's source page also carries betr, betrivers, fanatics, mgm, | |
| -- hardrock, thescore, circasports -- omitted here by request, not by limitation. | |
| -- bet365 was requested too but is NOT present on this source at all (confirmed | |
| -- 2026-09-25); it would need a different data source if required later. | |
| -- | |
| -- KNOWN TEAM-CODE VARIANCE: RotoWire uses 'LAR' for the LA Rams; our schedule | |
| -- table (nflverse-sourced) uses 'LA'. Confirmed this causes is_pregame to be | |
| -- NULL for that one team (no match found, not a wrong guess) rather than | |
| -- silently mapping it incorrectly. Fix properly (a small team-code alias | |
| -- table) if this team's line movement is needed; not blocking otherwise. | |
| CREATE TABLE IF NOT EXISTS gold.nfl_prop_book_catalog ( | |
| book_slug VARCHAR PRIMARY KEY, -- e.g. 'draftkings' | |
| book_name VARCHAR NOT NULL -- e.g. 'DraftKings' | |
| ); | |
| INSERT OR REPLACE INTO gold.nfl_prop_book_catalog VALUES | |
| ('draftkings', 'DraftKings'), | |
| ('fanduel', 'FanDuel'), | |
| ('caesars', 'Caesars'); | |
| -- One row per player/game/book/market/pull. Append-only. | |
| CREATE TABLE IF NOT EXISTS gold.nfl_prop_line_rotowire ( | |
| pull_id VARCHAR NOT NULL, -- one id per script run (this snapshot) | |
| fetched_at_utc TIMESTAMPTZ NOT NULL, -- TIMESTAMPTZ, not TIMESTAMP: a naive TIMESTAMP | |
| -- silently stored LOCAL time under a column | |
| -- literally named _utc (confirmed real bug, | |
| -- fixed here -- old rows before this fix are | |
| -- off by the host's UTC offset) | |
| game_id VARCHAR, -- rotowire gameID | |
| player_id VARCHAR, -- rotowire playerID | |
| player_name VARCHAR, | |
| team VARCHAR, | |
| opponent VARCHAR, -- as shown, e.g. '@MIA' or 'SEA' (home/away encoded) | |
| market_slug VARCHAR NOT NULL, -- FK -> nfl_prop_market_catalog | |
| book_slug VARCHAR NOT NULL, -- FK -> nfl_prop_book_catalog | |
| line DOUBLE, -- numeric line (NULL for moneyline-only TD props) | |
| over_price_american INTEGER, -- NULL for TD scorer props (no over/under side) | |
| under_price_american INTEGER, | |
| moneyline_american INTEGER, -- for TD scorer props: the single moneyline (e.g. firsttd odds) | |
| is_pregame BOOLEAN, -- true if fetched before that game's kickoff; | |
| -- NULL if kickoff time unknown at load time. | |
| -- Required so movement analysis never mixes | |
| -- pre-game and live in-game lines (confirmed | |
| -- real contamination: a fake "recyds 60.5 -> | |
| -- 174.5" move was actually two in-game snapshots | |
| -- of a game already underway) | |
| source_name VARCHAR NOT NULL DEFAULT 'rotowire', | |
| PRIMARY KEY (pull_id, player_id, game_id, market_slug, book_slug) | |
| ); | |
| CREATE INDEX IF NOT EXISTS ix_propline_rw_lookup | |
| ON gold.nfl_prop_line_rotowire (player_id, market_slug, fetched_at_utc); | |
| CREATE INDEX IF NOT EXISTS ix_propline_rw_game | |
| ON gold.nfl_prop_line_rotowire (game_id, market_slug); | |
| -- Latest snapshot per player/market/book -- what you'd actually compare against | |
| -- your own model right now, without re-deriving "most recent" every query. | |
| CREATE OR REPLACE VIEW gold.v_nfl_prop_line_latest AS | |
| SELECT p.* | |
| FROM gold.nfl_prop_line_rotowire p | |
| WHERE p.fetched_at_utc = ( | |
| SELECT max(p2.fetched_at_utc) | |
| FROM gold.nfl_prop_line_rotowire p2 | |
| WHERE p2.player_id = p.player_id | |
| AND p2.market_slug = p.market_slug | |
| AND p2.book_slug = p.book_slug | |
| AND p2.game_id = p.game_id | |
| ); | |
| -- Best (highest) over price and best (highest) under price across books, per | |
| -- player/market/game -- the actual best-price shopping view for placing a bet. | |
| CREATE OR REPLACE VIEW gold.v_nfl_prop_best_price AS | |
| SELECT game_id, player_id, ANY_VALUE(player_name) AS player_name, market_slug, | |
| max(line) AS line, -- lines mostly agree across books; max as a representative value | |
| max(over_price_american) AS best_over_price, | |
| arg_max(book_slug, over_price_american) AS best_over_book, | |
| max(under_price_american) AS best_under_price, | |
| arg_max(book_slug, under_price_american) AS best_under_book, | |
| max(moneyline_american) AS best_moneyline_price, | |
| arg_max(book_slug, moneyline_american) AS best_moneyline_book, | |
| count(DISTINCT book_slug) AS num_books | |
| FROM gold.v_nfl_prop_line_latest | |
| GROUP BY game_id, player_id, market_slug; | |
| -- Line movement: first-seen vs latest, per player/market/book. This is the CLV | |
| -- input -- compare where your model fired to the closing number, not just today's. | |
| -- | |
| -- PRE-GAME ONLY (is_pregame = true), on purpose. Confirmed real contamination | |
| -- otherwise: comparing two in-game snapshots of a game already underway | |
| -- produced a fake "Drake London recyds 60.5 -> 174.5" move -- that's live | |
| -- re-pricing during the game, not pre-game market movement. Never compare | |
| -- across that boundary. | |
| CREATE OR REPLACE VIEW gold.v_nfl_prop_line_movement AS | |
| SELECT player_id, ANY_VALUE(player_name) AS player_name, market_slug, book_slug, | |
| min(fetched_at_utc) AS first_seen_at, | |
| max(fetched_at_utc) AS last_seen_at, | |
| arg_min(line, fetched_at_utc) AS opening_line, | |
| arg_max(line, fetched_at_utc) AS current_line, | |
| arg_max(line, fetched_at_utc) - arg_min(line, fetched_at_utc) AS line_delta, | |
| count(DISTINCT fetched_at_utc) AS num_snapshots | |
| FROM gold.nfl_prop_line_rotowire | |
| WHERE line IS NOT NULL AND is_pregame = true | |
| GROUP BY player_id, market_slug, book_slug | |
| HAVING count(DISTINCT fetched_at_utc) > 1; | |