| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| 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, |
| outcome TEXT, |
| 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); |
|
|
| |
| |
| |
| |
| 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, |
| 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); |
|
|
| |
| |
| |
| |
| 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, |
| event_type TEXT NOT NULL, |
| 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, |
| 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); |
|
|
| |
| |
| |
| |
| 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); |
|
|
| |
| |
| |
| |
| 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, |
| shot_frame INTEGER NOT NULL, |
| shooter_track_id INTEGER NOT NULL, |
| shooter_contested_distance REAL NOT NULL, |
| open_teammates INTEGER NOT NULL DEFAULT 0, |
| best_teammate_openness REAL, |
| decision_quality TEXT NOT NULL CHECK (decision_quality IN ('high_expected_value', 'acceptable', 'low_expected_value')), |
| teammate_positions JSONB, |
| 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); |
|
|
| |
| |
| |
| |
| 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, |
| player_track_ids INTEGER[] NOT NULL, |
| possessions_count INTEGER NOT NULL DEFAULT 0, |
| points_scored INTEGER NOT NULL DEFAULT 0, |
| points_allowed INTEGER NOT NULL DEFAULT 0, |
| offensive_rating REAL, |
| defensive_rating REAL, |
| net_rating REAL, |
| avg_spacing_score REAL, |
| turnovers INTEGER NOT NULL DEFAULT 0, |
| defensive_error_rate REAL, |
| 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); |
|
|
| |
| |
| |
| |
| 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, |
| time_window_end REAL NOT NULL, |
| minute INTEGER NOT NULL, |
| baseline_speed_mps REAL NOT NULL, |
| current_speed_mps REAL NOT NULL, |
| speed_drop_percentage REAL NOT NULL, |
| baseline_reaction_ms REAL, |
| current_reaction_ms REAL, |
| 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); |
|
|
| |
| |
| |
| |
| 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, |
| timestamp_end REAL NOT NULL, |
| frame_start INTEGER NOT NULL, |
| frame_end INTEGER NOT NULL, |
| players_involved INTEGER[] NOT NULL, |
| file_path TEXT NOT NULL, |
| description TEXT, |
| metadata JSONB, |
| 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); |
|
|
| |
| |
| |
|
|
| |
| 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; |
|
|
| |
| 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) |
| ); |
|
|
| |
| |
| |
|
|
| 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'; |
|
|