File size: 12,819 Bytes
c6abe34 | 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 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | -- Advanced Analytics Schema Migration
-- Version: 001
-- Description: Adds 7 new tables for advanced basketball analytics
-- Date: 2026-02-11
-- IMPORTANT: This is ADDITIVE ONLY - no modifications to existing tables
-- ============================================
-- POSSESSIONS TABLE
-- ============================================
-- Segments game by offensive/defensive possessions
CREATE TABLE IF NOT EXISTS possessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
start_frame INTEGER NOT NULL,
end_frame INTEGER NOT NULL,
start_time REAL NOT NULL,
end_time REAL NOT NULL,
offense_team INTEGER NOT NULL CHECK (offense_team IN (1, 2)),
defense_team INTEGER NOT NULL CHECK (defense_team IN (1, 2)),
lineup_ids INTEGER[] NOT NULL, -- Array of player track IDs on offense
outcome TEXT, -- 'score', 'turnover', 'defensive_stop', etc.
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_possessions_video ON possessions(video_id);
CREATE INDEX idx_possessions_frames ON possessions(video_id, start_frame, end_frame);
-- ============================================
-- SPACING METRICS TABLE
-- ============================================
-- Per-possession spacing quality analysis
CREATE TABLE IF NOT EXISTS spacing_metrics (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
possession_id UUID NOT NULL REFERENCES possessions(id) ON DELETE CASCADE,
video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
frame INTEGER NOT NULL,
timestamp REAL NOT NULL,
spacing_quality TEXT NOT NULL CHECK (spacing_quality IN ('good', 'average', 'poor')),
avg_distance_m REAL NOT NULL,
paint_players INTEGER NOT NULL DEFAULT 0,
overlap_count INTEGER NOT NULL DEFAULT 0,
player_positions JSONB, -- Store actual positions for debugging
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_spacing_possession ON spacing_metrics(possession_id);
CREATE INDEX idx_spacing_video ON spacing_metrics(video_id);
CREATE INDEX idx_spacing_quality ON spacing_metrics(video_id, spacing_quality);
-- ============================================
-- DEFENSIVE REACTIONS TABLE
-- ============================================
-- Tracks defensive reaction times and closeout speeds
CREATE TABLE IF NOT EXISTS defensive_reactions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
event_id TEXT NOT NULL, -- Reference to event in analysis_results.events JSONB
event_type TEXT NOT NULL, -- 'shot', 'pass', 'drive'
event_frame INTEGER NOT NULL,
defender_track_id INTEGER NOT NULL,
offensive_player_track_id INTEGER NOT NULL,
reaction_start_frame INTEGER,
reaction_delay_ms REAL,
closeout_speed_mps REAL,
late_closeout BOOLEAN DEFAULT FALSE,
distance_at_event REAL, -- Defender distance when event occurred
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_defensive_reactions_video ON defensive_reactions(video_id);
CREATE INDEX idx_defensive_reactions_event ON defensive_reactions(video_id, event_id);
CREATE INDEX idx_defensive_reactions_late ON defensive_reactions(video_id, late_closeout);
-- ============================================
-- TRANSITION EFFORT TABLE
-- ============================================
-- Tracks player effort during transition (offense to defense, defense to offense)
CREATE TABLE IF NOT EXISTS transition_effort (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
possession_id UUID NOT NULL REFERENCES possessions(id) ON DELETE CASCADE,
video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
player_track_id INTEGER NOT NULL,
team_id INTEGER NOT NULL CHECK (team_id IN (1, 2)),
transition_type TEXT NOT NULL CHECK (transition_type IN ('offense_to_defense', 'defense_to_offense')),
effort_type TEXT NOT NULL CHECK (effort_type IN ('sprint', 'jog', 'walk')),
max_speed_mps REAL NOT NULL,
avg_speed_mps REAL NOT NULL,
effort_score REAL NOT NULL CHECK (effort_score >= 0 AND effort_score <= 100),
duration_seconds REAL NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_transition_possession ON transition_effort(possession_id);
CREATE INDEX idx_transition_video ON transition_effort(video_id);
CREATE INDEX idx_transition_player ON transition_effort(video_id, player_track_id);
CREATE INDEX idx_transition_effort_type ON transition_effort(video_id, effort_type);
-- ============================================
-- DECISION ANALYSIS TABLE
-- ============================================
-- Analyzes shot decision quality based on shooter vs. teammate openness
CREATE TABLE IF NOT EXISTS decision_analysis (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
event_id TEXT NOT NULL, -- Reference to shot event
shot_frame INTEGER NOT NULL,
shooter_track_id INTEGER NOT NULL,
shooter_contested_distance REAL NOT NULL, -- Distance to nearest defender
open_teammates INTEGER NOT NULL DEFAULT 0, -- Count of teammates with defender > 2.5m
best_teammate_openness REAL, -- Distance to defender for most open teammate
decision_quality TEXT NOT NULL CHECK (decision_quality IN ('high_expected_value', 'acceptable', 'low_expected_value')),
teammate_positions JSONB, -- Store teammate openness data
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_decision_video ON decision_analysis(video_id);
CREATE INDEX idx_decision_event ON decision_analysis(video_id, event_id);
CREATE INDEX idx_decision_quality ON decision_analysis(video_id, decision_quality);
-- ============================================
-- LINEUP METRICS TABLE
-- ============================================
-- Tracks performance of specific 5-player combinations
CREATE TABLE IF NOT EXISTS lineup_metrics (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
team_id INTEGER NOT NULL CHECK (team_id IN (1, 2)),
lineup_hash TEXT NOT NULL, -- Sorted player track IDs as string (e.g., "1_3_5_7_9")
player_track_ids INTEGER[] NOT NULL, -- Array of 5 player track IDs
possessions_count INTEGER NOT NULL DEFAULT 0,
points_scored INTEGER NOT NULL DEFAULT 0,
points_allowed INTEGER NOT NULL DEFAULT 0,
offensive_rating REAL, -- Points per 100 possessions
defensive_rating REAL, -- Points allowed per 100 possessions
net_rating REAL, -- Offensive - Defensive rating
avg_spacing_score REAL,
turnovers INTEGER NOT NULL DEFAULT 0,
defensive_error_rate REAL, -- Late closeouts / total defensive events
total_minutes REAL NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(video_id, team_id, lineup_hash)
);
CREATE INDEX idx_lineup_video ON lineup_metrics(video_id);
CREATE INDEX idx_lineup_team ON lineup_metrics(video_id, team_id);
CREATE INDEX idx_lineup_hash ON lineup_metrics(lineup_hash);
CREATE INDEX idx_lineup_net_rating ON lineup_metrics(video_id, net_rating);
-- ============================================
-- FATIGUE INDEX TABLE
-- ============================================
-- Tracks player fatigue indicators over time
CREATE TABLE IF NOT EXISTS fatigue_index (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
player_track_id INTEGER NOT NULL,
time_window_start REAL NOT NULL, -- Start time in seconds
time_window_end REAL NOT NULL, -- End time in seconds
minute INTEGER NOT NULL, -- Game minute (for easy querying)
baseline_speed_mps REAL NOT NULL, -- Early game average speed
current_speed_mps REAL NOT NULL, -- Current window average speed
speed_drop_percentage REAL NOT NULL,
baseline_reaction_ms REAL, -- Early game average reaction time
current_reaction_ms REAL, -- Current window average reaction time
reaction_delay_increase_percentage REAL,
fatigue_level TEXT NOT NULL CHECK (fatigue_level IN ('low', 'medium', 'high')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_fatigue_video ON fatigue_index(video_id);
CREATE INDEX idx_fatigue_player ON fatigue_index(video_id, player_track_id);
CREATE INDEX idx_fatigue_minute ON fatigue_index(video_id, minute);
CREATE INDEX idx_fatigue_level ON fatigue_index(video_id, fatigue_level);
-- ============================================
-- AUTO CLIPS TABLE
-- ============================================
-- Metadata for automatically generated coaching clips
CREATE TABLE IF NOT EXISTS auto_clips (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
clip_type TEXT NOT NULL CHECK (clip_type IN (
'poor_spacing',
'late_rotation',
'low_decision_quality',
'poor_transition',
'excellent_spacing',
'quick_rotation',
'high_effort_transition'
)),
timestamp_start REAL NOT NULL, -- Start time in seconds
timestamp_end REAL NOT NULL, -- End time in seconds
frame_start INTEGER NOT NULL,
frame_end INTEGER NOT NULL,
players_involved INTEGER[] NOT NULL, -- Track IDs of key players
file_path TEXT NOT NULL, -- Relative path to clip file
description TEXT, -- Auto-generated description
metadata JSONB, -- Additional context (spacing score, reaction delay, etc.)
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_clips_video ON auto_clips(video_id);
CREATE INDEX idx_clips_type ON auto_clips(video_id, clip_type);
CREATE INDEX idx_clips_timestamp ON auto_clips(video_id, timestamp_start);
-- ============================================
-- ROW LEVEL SECURITY POLICIES
-- ============================================
-- Enable RLS on all new tables
ALTER TABLE possessions ENABLE ROW LEVEL SECURITY;
ALTER TABLE spacing_metrics ENABLE ROW LEVEL SECURITY;
ALTER TABLE defensive_reactions ENABLE ROW LEVEL SECURITY;
ALTER TABLE transition_effort ENABLE ROW LEVEL SECURITY;
ALTER TABLE decision_analysis ENABLE ROW LEVEL SECURITY;
ALTER TABLE lineup_metrics ENABLE ROW LEVEL SECURITY;
ALTER TABLE fatigue_index ENABLE ROW LEVEL SECURITY;
ALTER TABLE auto_clips ENABLE ROW LEVEL SECURITY;
-- All tables: users can only access data for videos they own
CREATE POLICY "Users can view own possessions" ON possessions
FOR SELECT USING (
video_id IN (SELECT id FROM videos WHERE uploader_id::text = auth.uid()::text)
);
CREATE POLICY "Users can view own spacing metrics" ON spacing_metrics
FOR SELECT USING (
video_id IN (SELECT id FROM videos WHERE uploader_id::text = auth.uid()::text)
);
CREATE POLICY "Users can view own defensive reactions" ON defensive_reactions
FOR SELECT USING (
video_id IN (SELECT id FROM videos WHERE uploader_id::text = auth.uid()::text)
);
CREATE POLICY "Users can view own transition effort" ON transition_effort
FOR SELECT USING (
video_id IN (SELECT id FROM videos WHERE uploader_id::text = auth.uid()::text)
);
CREATE POLICY "Users can view own decision analysis" ON decision_analysis
FOR SELECT USING (
video_id IN (SELECT id FROM videos WHERE uploader_id::text = auth.uid()::text)
);
CREATE POLICY "Users can view own lineup metrics" ON lineup_metrics
FOR SELECT USING (
video_id IN (SELECT id FROM videos WHERE uploader_id::text = auth.uid()::text)
);
CREATE POLICY "Users can view own fatigue index" ON fatigue_index
FOR SELECT USING (
video_id IN (SELECT id FROM videos WHERE uploader_id::text = auth.uid()::text)
);
CREATE POLICY "Users can view own auto clips" ON auto_clips
FOR SELECT USING (
video_id IN (SELECT id FROM videos WHERE uploader_id::text = auth.uid()::text)
);
-- ============================================
-- COMMENTS FOR DOCUMENTATION
-- ============================================
COMMENT ON TABLE possessions IS 'Segments game footage into offensive/defensive possessions with lineup tracking';
COMMENT ON TABLE spacing_metrics IS 'Per-frame offensive spacing quality analysis';
COMMENT ON TABLE defensive_reactions IS 'Defensive reaction times and closeout speeds for all defensive events';
COMMENT ON TABLE transition_effort IS 'Player effort classification during transition phases';
COMMENT ON TABLE decision_analysis IS 'Shot decision quality based on shooter vs. teammate openness';
COMMENT ON TABLE lineup_metrics IS 'Performance metrics for specific 5-player combinations';
COMMENT ON TABLE fatigue_index IS 'Player fatigue indicators tracked over time windows';
COMMENT ON TABLE auto_clips IS 'Metadata for automatically generated coaching highlight clips';
|