Spaces:
Paused
Paused
File size: 9,607 Bytes
367f592 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | -- frontal-lobe2 canonical schema.
-- Derived from docs/CHART_AUDIT.md (TeamRankings structural audit, 2026-09-24).
-- Design-only: recreates chart shape (columns + splits/dropdowns), not TeamRankings' data.
-- Target engine: DuckDB (schemas as namespaces; PRAGMA lines are SQLite-compatible fallback).
-- Leagues in scope: nfl, ncf (college football), nba, mlb. NHL deferred (docs/NHL_GAP.md).
CREATE SCHEMA IF NOT EXISTS bronze;
CREATE SCHEMA IF NOT EXISTS silver;
CREATE SCHEMA IF NOT EXISTS gold;
-- ============================================================================
-- BRONZE: ingestion tracking (shared across all reference pulls and live pulls)
-- ============================================================================
CREATE TABLE IF NOT EXISTS bronze.ingestion_runs (
run_id VARCHAR PRIMARY KEY,
source_name VARCHAR NOT NULL, -- e.g. 'teamrankings_reference', 'espn_api', 'cfbd'
source_kind VARCHAR NOT NULL, -- 'html_snapshot' | 'api'
league VARCHAR, -- nfl | ncf | nba | mlb
started_at TIMESTAMP NOT NULL,
completed_at TIMESTAMP,
row_count INTEGER,
status VARCHAR NOT NULL,
error_message VARCHAR
);
-- ============================================================================
-- SILVER: chart catalog -- the master list of every chart/stat available per league.
-- Populated from the *_stats.html / *_player-stats.html index pages (~600+ slugs total).
-- This IS the spreadsheet deliverable's source of truth; export this table to build it.
-- ============================================================================
CREATE TABLE IF NOT EXISTS silver.stat_catalog (
league VARCHAR NOT NULL, -- nfl | ncf | nba | mlb
stat_slug VARCHAR NOT NULL, -- e.g. 'points-per-game'
display_name VARCHAR NOT NULL, -- e.g. 'Points per Game'
stat_scope VARCHAR NOT NULL, -- 'team' | 'player'
category VARCHAR, -- offense | defense | special_teams | batting | pitching | etc.
source_page VARCHAR, -- which catalog page this was found on
PRIMARY KEY (league, stat_scope, stat_slug)
);
CREATE TABLE IF NOT EXISTS silver.ranking_catalog (
league VARCHAR NOT NULL,
rating_name VARCHAR NOT NULL, -- e.g. 'Predictive by other', 'Home Advantage'
best_label VARCHAR, -- label used for #1 in that rating system
worst_label VARCHAR,
PRIMARY KEY (league, rating_name)
);
-- ============================================================================
-- SILVER: the universal per-stat chart template.
-- Confirmed identical shape across every sampled stat page (NFL/NBA points-per-game,
-- MLB runs-per-game): Rank | Team | current season | Last 3 | Last 1 | Home | Away | prior season.
-- One normalized fact table covers all ~600 stat_catalog entries -- do not create one
-- physical table per stat.
-- ============================================================================
CREATE TABLE IF NOT EXISTS silver.team_stat_value (
league VARCHAR NOT NULL,
stat_slug VARCHAR NOT NULL, -- FK -> stat_catalog.stat_slug (stat_scope='team')
team VARCHAR NOT NULL,
season INTEGER NOT NULL, -- the 'date' dropdown selection
split VARCHAR NOT NULL, -- 'overall' | 'last_3' | 'last_1' | 'home' | 'away'
rank INTEGER,
value DOUBLE,
source_name VARCHAR NOT NULL, -- live source once wired, e.g. 'espn', 'nflverse'
captured_at TIMESTAMP NOT NULL,
PRIMARY KEY (league, stat_slug, team, season, split, captured_at)
);
CREATE TABLE IF NOT EXISTS silver.player_stat_value (
league VARCHAR NOT NULL,
stat_slug VARCHAR NOT NULL, -- FK -> stat_catalog.stat_slug (stat_scope='player')
player_name VARCHAR NOT NULL,
team VARCHAR,
position VARCHAR,
season INTEGER NOT NULL,
split VARCHAR NOT NULL,
rank INTEGER,
value DOUBLE,
source_name VARCHAR NOT NULL,
captured_at TIMESTAMP NOT NULL,
PRIMARY KEY (league, stat_slug, player_name, team, season, split, captured_at)
);
-- ============================================================================
-- SILVER: standings. Confirmed division/conference-grouped per league; NCF has
-- 11 conference tables (vs. 2 for pro leagues) and a MAC East/West sub-split.
-- group_name carries the division/conference label so all leagues share one table.
-- ============================================================================
CREATE TABLE IF NOT EXISTS silver.standings (
league VARCHAR NOT NULL,
season INTEGER NOT NULL,
group_name VARCHAR NOT NULL, -- division/conference label, e.g. 'AFC East', 'MAC East'
rank_in_group INTEGER,
team VARCHAR NOT NULL,
overall_wins INTEGER,
overall_losses INTEGER,
overall_ties INTEGER, -- NCF/NFL can have ties; NULL where not applicable
win_pct DOUBLE,
games_back DOUBLE,
group_wins INTEGER, -- division/conference record
group_losses INTEGER,
home_wins INTEGER,
home_losses INTEGER,
road_wins INTEGER,
road_losses INTEGER,
streak VARCHAR,
source_name VARCHAR NOT NULL,
captured_at TIMESTAMP NOT NULL,
PRIMARY KEY (league, season, group_name, team, captured_at)
);
-- ============================================================================
-- SILVER: schedules. Confirmed: date-pivoted headers with Time/Location per league,
-- plus week and group(division/conference/poll) filters as splits.
-- ============================================================================
CREATE TABLE IF NOT EXISTS silver.schedule (
league VARCHAR NOT NULL,
season INTEGER NOT NULL,
week VARCHAR, -- NFL/NCF use week labels incl. 'Bowl Week'; NBA/MLB use game date grouping
game_date DATE,
home_team VARCHAR,
away_team VARCHAR,
game_time_utc TIME,
location VARCHAR,
group_filter VARCHAR, -- the 'group' dropdown value this row was captured under (division/conference/poll), if applicable
home_score INTEGER,
away_score INTEGER,
status VARCHAR,
source_name VARCHAR NOT NULL,
captured_at TIMESTAMP NOT NULL,
PRIMARY KEY (league, season, game_date, home_team, away_team, captured_at)
);
-- ============================================================================
-- SILVER: projections. Confirmed 2-tier (NFL/NBA/MLB) or 3-tier (NCF, adds ties)
-- structure: Current record / Projected record / Playoff-odds percentages.
-- ============================================================================
CREATE TABLE IF NOT EXISTS silver.season_projection (
league VARCHAR NOT NULL,
season INTEGER NOT NULL,
team VARCHAR NOT NULL,
current_wins INTEGER,
current_losses INTEGER,
current_ties INTEGER, -- NCF only; NULL elsewhere
projected_wins DOUBLE,
projected_losses DOUBLE,
projected_ties DOUBLE, -- NCF only
playoff_pct DOUBLE,
win_division_pct DOUBLE,
top_seed_pct DOUBLE,
win_championship_pct DOUBLE, -- Win SB / Champs / WS Champs / Bowl Eligible% depending on league
undefeated_pct DOUBLE, -- NCF only; NULL elsewhere
source_name VARCHAR NOT NULL,
captured_at TIMESTAMP NOT NULL,
PRIMARY KEY (league, season, team, captured_at)
);
-- ============================================================================
-- SILVER: odds board. SCHEMA IS PROVISIONAL -- the real column structure was NOT
-- captured (client-side AJAX render, see CHART_AUDIT.md "Confirmed gaps"). This
-- shape is inferred from The Odds API's standard market fields, not from a
-- confirmed TeamRankings sample. Revise once a headless-browser capture or the
-- AJAX endpoint response is inspected directly.
-- ============================================================================
CREATE TABLE IF NOT EXISTS silver.odds_snapshot ( -- PROVISIONAL
league VARCHAR NOT NULL,
game_id VARCHAR NOT NULL,
book VARCHAR NOT NULL,
market VARCHAR NOT NULL, -- spread | total | moneyline
home_line DOUBLE,
away_line DOUBLE,
home_price DOUBLE,
away_price DOUBLE,
is_closing_line BOOLEAN,
fetched_at_utc TIMESTAMP NOT NULL,
source_name VARCHAR NOT NULL,
PRIMARY KEY (league, game_id, book, market, fetched_at_utc)
);
CREATE INDEX IF NOT EXISTS idx_team_stat_lookup
ON silver.team_stat_value (league, stat_slug, season, split);
CREATE INDEX IF NOT EXISTS idx_player_stat_lookup
ON silver.player_stat_value (league, stat_slug, season, split);
CREATE INDEX IF NOT EXISTS idx_standings_lookup
ON silver.standings (league, season, group_name);
CREATE INDEX IF NOT EXISTS idx_schedule_lookup
ON silver.schedule (league, season, week);
|