Database Schema Reference (LLM)
Status: Auto-Generated Last Updated: 2025-11-12 13:15 Database: Supabase PostgreSQL
Quick Reference
Tables
- _migrations (3 columns) -
- access_keys (20 columns) -
- api_call_failures (28 columns) - System-wide API failure tracking with automatic GitHub issue creation
- api_leagues_canonical (21 columns) - Canonical leagues from TheSportsDB - source of truth for all API operations
- api_sports_canonical (13 columns) - Canonical sports from TheSportsDB - source of truth for all API operations
- audit_log (11 columns) -
- channel_families (12 columns) -
- channel_names (14 columns) - One record per unique channel per provider
- channel_patterns (20 columns) - Channel name patterns discovered during provider onboarding. Stores regex patterns that identify sports channels (e.g., "NBA \d+ :"). Used for filtering live M3U playlists during EPG generation.
- confidence_metrics (8 columns) -
- credential_access_log (7 columns) - Audit trail of all credential access. Logs when credentials are decrypted and used, including GitHub Actions run ID for traceability.
- daily_metrics (23 columns) -
- epg_data (47 columns) - Final output-ready EPG entries with full XMLTV fields
- epg_file_cache (21 columns) - Tracks every EPG file in R2 (pre-generated and on-demand)
- espn_league_mappings (16 columns) - Maps ESPN-specific league names to canonical TheSportsDB leagues with confidence scoring
- espn_sport_mappings (15 columns) - Maps ESPN-specific sport names to canonical TheSportsDB sports with confidence scoring
- event_identifiers (7 columns) -
- event_participants (8 columns) -
- events (42 columns) - Canonical sports events from TheSportsDB API
- family_league_mappings (9 columns) -
- invoices (26 columns) -
- key_access_logs (10 columns) -
- learned_aliases (10 columns) -
- learned_patterns (18 columns) -
- llm_parse_cache (16 columns) - LLM result deduplication - prevents duplicate API calls for similar channels
- match_cache (14 columns) -
- match_overrides (16 columns) -
- match_verifications (12 columns) -
- parsed_data (21 columns) - Every parse attempt, whether matched or not. JSONB stores flexible extracted fields.
- participant_aliases (10 columns) -
- participants (17 columns) -
- pattern_performance (14 columns) - Performance tracking for individual channel patterns (NBA, ESPN+, etc). Tracks match success rate, processing time, and API efficiency per pattern.
- provider_credentials (10 columns) - Stores encrypted M3U URL credentials for provider access. Credentials are encrypted using AES-256-GCM (Fernet) with key stored in GitHub Actions secrets.
- provider_health_status (10 columns) - Provider health monitoring. Detects anomalies like credential failures, channel count changes, or match rate degradation.
- provider_metrics (15 columns) - Time-series metrics for provider performance tracking. Records EPG generation stats, match success rates, and cost data per provider run.
- provider_patterns (16 columns) - Provider-specific channel matching patterns with LLM verification
- providers (22 columns) - IPTV service providers with M3U sources
- subscription_events (11 columns) -
- successful_matches (13 columns) -
- team_aliases (13 columns) - All team name variations for intelligent fuzzy matching
- team_discovery_cache (17 columns) - LLM-powered team name enrichment cache - converts abbreviations/nicknames to canonical names
- team_pairings (10 columns) -
- team_sync_audit_log (14 columns) -
- team_sync_auto_resolution_rules (13 columns) -
- team_sync_conflicts (28 columns) -
- team_sync_runs (18 columns) -
- teams (14 columns) - Canonical team records with API source IDs for intelligent matching
- tvg_id_mappings (14 columns) -
- unmatched_channels (30 columns) -
- user_subscriptions (23 columns) -
- users (20 columns) -
- vod_filter_patterns (9 columns) -
- webhook_events (11 columns) -
_migrations
| Column | Type | Nullable | Description |
|---|---|---|---|
| applied_at | DATETIME | No | |
| migration_name | TEXT | No | |
| migration_number 🔑 | INTEGER | Yes |
access_keys
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → user_subscriptions | KEY | Yes | |
| abuse_details | TEXT | Yes | |
| abuse_flags | INTEGER | Yes | |
| created_at | TEXT | No | |
| description | TEXT | Yes | |
| expires_at | TEXT | Yes | |
| id 🔑 | INTEGER | Yes | |
| key_type | TEXT | No | |
| key_uuid | TEXT | No | |
| last_abuse_flag_at | TEXT | Yes | |
| last_used_at | TEXT | Yes | |
| provider_ids | TEXT | No | |
| status | TEXT | No | |
| subscription_id | INTEGER | No | |
| suspended_at | TEXT | Yes | |
| suspended_reason | TEXT | Yes | |
| total_requests | INTEGER | Yes | |
| updated_at | TEXT | No | |
| user_id | INTEGER | No |
Indexes:
- idx_keys_uuid on key_uuid
- idx_keys_user on user_id
- idx_keys_subscription on subscription_id
- idx_keys_status on status
api_call_failures
Purpose: System-wide API failure tracking with automatic GitHub issue creation
| Column | Type | Nullable | Description |
|---|---|---|---|
| api_name | TEXT | No | API identifier: "thesportsdb", "espn", "supabase", "anthropic", etc. |
| created_at | TIMESTAMPTZ | Yes | |
| deployment_version | TEXT | Yes | |
| endpoint | TEXT | No | API endpoint path (e.g., "/search_teams.php", "/v2/scoreboard") |
| environment | TEXT | Yes | |
| error_code | TEXT | Yes | |
| error_message | TEXT | No | |
| error_type | TEXT | No | Exception class name (e.g., "HTTPError", "TimeoutError", "ConnectionError") |
| first_occurred_at | TIMESTAMPTZ | Yes | |
| fix_version | TEXT | Yes | Git tag/commit SHA where fix was deployed - enables verification |
| github_issue_created_at | TIMESTAMPTZ | Yes | |
| github_issue_number | INTEGER | Yes | GitHub issue number created for this failure (NULL until created) |
| github_issue_url | TEXT | Yes | |
| http_method | TEXT | Yes | |
| http_status | INTEGER | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| last_occurred_at | TIMESTAMPTZ | Yes | |
| occurrence_count | INTEGER | Yes | Number of times this exact error occurred (incremented on duplicates) |
| request_body | TEXT | Yes | |
| request_headers | JSONB | Yes | |
| request_params | JSONB | Yes | Full request parameters as JSON - sanitize sensitive data before storing |
| resolution_notes | TEXT | Yes | |
| resolution_status | IN | Yes | open |
| resolved_at | TIMESTAMPTZ | Yes | |
| resolved_by | TEXT | Yes | |
| server_hostname | TEXT | Yes | |
| stack_trace | TEXT | Yes | |
| updated_at | TIMESTAMPTZ | Yes |
Indexes:
- idx_api_failures_api_name on api_name
- idx_api_failures_endpoint on endpoint
- idx_api_failures_error_type on error_type
- idx_api_failures_status on resolution_status
- idx_api_failures_github_issue on github_issue_number
- idx_api_failures_open on resolution_status
- idx_api_failures_created_at on created_at DESC
- idx_api_failures_last_occurred on last_occurred_at DESC
- idx_api_failures_occurrence_count on occurrence_count DESC
- idx_api_failures_dedup on api_name, endpoint, error_type, resolution_status
api_leagues_canonical
Purpose: Canonical leagues from TheSportsDB - source of truth for all API operations
| Column | Type | Nullable | Description |
|---|---|---|---|
| canonical_name | TEXT | No | Canonical league name (e.g., "NFL", "NBA", "English Premier League") |
| country | TEXT | Yes | Primary country (e.g., "United States", "England") - leagues can span multiple countries |
| country_code | TEXT | Yes | |
| created_at | TIMESTAMPTZ | Yes | |
| description | TEXT | Yes | |
| discovered_at | TIMESTAMPTZ | Yes | |
| discovery_source | TEXT | Yes | |
| end_year | INTEGER | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| is_active | BOOLEAN | Yes | FALSE if league has ended/dissolved. Check end_year for termination date. |
| is_verified | BOOLEAN | Yes | TRUE if human verified, FALSE if auto-discovered. Review low-confidence entries. |
| logo_url | TEXT | Yes | |
| region | TEXT | Yes | |
| sport_id → api_sports_canonical | BIGINT | No | Foreign key to api_sports_canonical - each league belongs to exactly one sport |
| start_year | INTEGER | Yes | |
| thesportsdb_league_id | TEXT | Yes | TheSportsDB league ID (e.g., "4391" for NFL) - required for API queries |
| thesportsdb_name | TEXT | Yes | |
| updated_at | TIMESTAMPTZ | Yes | |
| verified_at | TIMESTAMPTZ | Yes | |
| verified_by | TEXT | Yes | |
| website_url | TEXT | Yes |
Indexes:
- idx_api_leagues_canonical_name on canonical_name
- idx_api_leagues_sport_id on sport_id
- idx_api_leagues_thesportsdb_id on thesportsdb_league_id
- idx_api_leagues_country on country
- idx_api_leagues_active on is_active
- idx_api_leagues_verified on is_verified
- idx_api_leagues_sport_country on sport_id, country
- idx_api_leagues_created_at on created_at DESC
api_sports_canonical
Purpose: Canonical sports from TheSportsDB - source of truth for all API operations
| Column | Type | Nullable | Description |
|---|---|---|---|
| canonical_name | TEXT | No | Canonical sport name (e.g., "American Football", "Basketball", "Ice Hockey") |
| created_at | TIMESTAMPTZ | Yes | |
| description | TEXT | Yes | |
| discovered_at | TIMESTAMPTZ | Yes | |
| discovery_source | TEXT | Yes | How this sport was discovered: "thesportsdb", "bootstrap", "sync", "manual" |
| icon_url | TEXT | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| is_verified | BOOLEAN | Yes | TRUE if human verified, FALSE if auto-discovered. Review low-confidence entries. |
| thesportsdb_name | TEXT | Yes | |
| thesportsdb_sport_id | TEXT | Yes | TheSportsDB sport ID if available (some APIs return string IDs) |
| updated_at | TIMESTAMPTZ | Yes | |
| verified_at | TIMESTAMPTZ | Yes | |
| verified_by | TEXT | Yes |
Indexes:
- idx_api_sports_canonical_name on canonical_name
- idx_api_sports_thesportsdb_id on thesportsdb_sport_id
- idx_api_sports_verified on is_verified
- idx_api_sports_discovery_source on discovery_source
- idx_api_sports_created_at on created_at DESC
audit_log
| Column | Type | Nullable | Description |
|---|---|---|---|
| FOREIGN → users | KEY | Yes | |
| action | TEXT | No | |
| created_at | TEXT | No | |
| entity_id | INTEGER | Yes | |
| entity_type | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| metadata | TEXT | Yes | |
| new_value | TEXT | Yes | |
| old_value | TEXT | Yes | |
| provider_id | INTEGER | Yes | |
| user_id | INTEGER | Yes |
Indexes:
- idx_audit_action on action
- idx_audit_entity on entity_type, entity_id
- idx_audit_created on created_at DESC
- idx_audit_provider on provider_id
channel_families
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → providers | KEY | Yes | |
| active | BOOLEAN | Yes | |
| created_at | TEXT | No | |
| description | TEXT | Yes | |
| family_name | TEXT | No | |
| family_pattern | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| match_behavior | TEXT | No | |
| priority | INTEGER | Yes | |
| provider_id | INTEGER | No | |
| updated_at | TEXT | No |
Indexes:
- idx_families_provider on provider_id
- idx_families_active on active
- idx_families_priority on priority DESC
channel_names
Purpose: One record per unique channel per provider
| Column | Type | Nullable | Description |
|---|---|---|---|
| channel_name | TEXT | No | |
| channel_name_normalized | TEXT | No | Lowercase, special chars removed, for deduplication |
| created_at | TIMESTAMPTZ | Yes | |
| first_seen | TIMESTAMPTZ | Yes | |
| group_title | TEXT | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| last_seen | TIMESTAMPTZ | Yes | |
| provider_id → providers | BIGINT | No | |
| record_status | TEXT | Yes | Soft delete: active, archived, deleted |
| stream_url | TEXT | Yes | |
| tvg_id | TEXT | Yes | |
| tvg_logo | TEXT | Yes | |
| tvg_name | TEXT | Yes | |
| updated_at | TIMESTAMPTZ | Yes |
Indexes:
- idx_channel_names_provider on provider_id
- idx_channel_names_status on record_status
- idx_channel_names_tvg_id on tvg_id
channel_patterns
Purpose: Channel name patterns discovered during provider onboarding. Stores regex patterns that identify sports channels (e.g., "NBA \d+ :"). Used for filtering live M3U playlists during EPG generation.
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| confidence | REAL | No | |
| created_at | TEXT | No | |
| discovered_at | TIMESTAMPTZ | No | |
| example_channel | TEXT | Yes | |
| example_channels | JSONB | Yes | |
| frequency | INTEGER | No | Number of channels matching this pattern. Helps identify most common/important patterns. |
| id 🔑 | SERIAL | Yes | |
| is_active | BOOLEAN | Yes | |
| is_league | BOOLEAN | Yes | |
| last_seen | TEXT | No | |
| last_verified_at | TIMESTAMPTZ | Yes | |
| league | TEXT | Yes | |
| occurrence_count | INTEGER | Yes | |
| pattern | TEXT | No | |
| pattern_prefix | TEXT | No | Pattern prefix (e.g., "NBA", "ESPN+", "UFC"). Stable identifier that does not change daily. |
| pattern_regex | TEXT | No | Compiled regex pattern (e.g., r |
| pattern_type | IN | Yes | Pattern type: numbered (sports channels like "NBA 01") or category (country codes like "FR:"). Numbered patterns are more useful for EPG matching. |
| provider_id → providers | INTEGER | No | |
| sport | TEXT | Yes |
Indexes:
- idx_channel_patterns_league on league
- idx_channel_patterns_confidence on confidence DESC
- idx_channel_patterns_occurrence on occurrence_count DESC
- idx_channel_patterns_provider_id on provider_id
- idx_channel_patterns_active on is_active
- idx_channel_patterns_pattern_type on pattern_type
confidence_metrics
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| average_confidence | REAL | Yes | |
| created_at | TEXT | No | |
| date | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| match_rate | REAL | Yes | |
| successful_matches | INTEGER | Yes | |
| total_attempts | INTEGER | Yes |
Indexes:
- idx_confidence_metrics_date on date DESC
credential_access_log
Purpose: Audit trail of all credential access. Logs when credentials are decrypted and used, including GitHub Actions run ID for traceability.
| Column | Type | Nullable | Description |
|---|---|---|---|
| access_context | TEXT | No | Context of credential access: epg_generation (scheduled run), onboarding_validation (testing provider), manual_refresh (on-demand) |
| accessed_at | TIMESTAMPTZ | No | |
| error_message | TEXT | Yes | |
| github_run_id | TEXT | Yes | GitHub Actions run ID from ${{ github.run_id }} for workflow traceability |
| id 🔑 | SERIAL | Yes | |
| provider_id → providers | INTEGER | No | |
| success | BOOLEAN | No |
Indexes:
- idx_credential_access_log_provider_id on provider_id
- idx_credential_access_log_accessed_at on accessed_at DESC
- idx_credential_access_log_github_run_id on github_run_id
daily_metrics
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| active_subscriptions | INTEGER | Yes | |
| arr_cents | INTEGER | Yes | |
| created_at | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| llm_cost_cents | INTEGER | Yes | |
| llm_matched_channels | INTEGER | Yes | |
| llm_tokens_cached | INTEGER | Yes | |
| llm_tokens_input | INTEGER | Yes | |
| llm_tokens_output | INTEGER | Yes | |
| match_rate | REAL | Yes | |
| metric_date | TEXT | No | |
| mrr_cents | INTEGER | Yes | |
| new_signups | INTEGER | Yes | |
| regex_matched_channels | INTEGER | Yes | |
| revenue_cents | INTEGER | Yes | |
| total_bytes_transferred | INTEGER | Yes | |
| total_channels_processed | INTEGER | Yes | |
| total_epg_requests | INTEGER | Yes | |
| total_users | INTEGER | Yes | |
| trial_subscriptions | INTEGER | Yes | |
| unmatched_channels | INTEGER | Yes | |
| updated_at | TEXT | No |
Indexes:
- idx_metrics_date on metric_date DESC
epg_data
Purpose: Final output-ready EPG entries with full XMLTV fields
| Column | Type | Nullable | Description |
|---|---|---|---|
| "away_logo": | UNKNOWN | Yes | |
| "away_team": | UNKNOWN | Yes | |
| "broadcaster": | UNKNOWN | Yes | |
| "city": | UNKNOWN | Yes | |
| "event_status": | UNKNOWN | Yes | |
| "home_logo": | UNKNOWN | Yes | |
| "home_team": | UNKNOWN | Yes | |
| "league": | UNKNOWN | Yes | |
| "league_logo": | UNKNOWN | Yes | |
| "round": | UNKNOWN | Yes | |
| "season": | UNKNOWN | Yes | |
| "show_debug_info": | FALSE | Yes | |
| "show_emoji": | TRUE | Yes | |
| "show_logos": | TRUE | Yes | |
| "show_venue": | TRUE | Yes | |
| "sport": | UNKNOWN | Yes | |
| "timezone": | UNKNOWN | Yes | |
| "venue": | UNKNOWN | Yes | |
| audio_quality | TEXT | Yes | |
| category | TEXT | Yes | |
| channel_name_id → channel_names | BIGINT | No | |
| confidence | REAL | Yes | |
| content_rating | TEXT | Yes | |
| created_at | TIMESTAMPTZ | Yes | |
| description | TEXT | Yes | |
| end_time | TIMESTAMPTZ | No | |
| episode_num | TEXT | Yes | |
| icon_url | TEXT | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| is_live | BOOLEAN | Yes | |
| is_new | BOOLEAN | Yes | |
| is_premiere | BOOLEAN | Yes | |
| is_repeat | BOOLEAN | Yes | |
| language | TEXT | Yes | |
| match_status | TEXT | Yes | |
| original_air_date | DATE | Yes | |
| parsed_data_id → parsed_data | BIGINT | Yes | |
| record_status | TEXT | Yes | |
| sports_metadata | JSONB | Yes | JSONB: venue, teams, logos, league, season, round, broadcaster, event_status |
| star_rating | REAL | Yes | |
| start_time | TIMESTAMPTZ | No | |
| sub_title | TEXT | Yes | |
| tier_features | JSONB | Yes | JSONB: Controls what gets rendered for each tier (emojis, logos, debug info, timezone) |
| title | TEXT | No | |
| updated_at | TIMESTAMPTZ | Yes | |
| url | TEXT | Yes | |
| video_quality | TEXT | Yes |
Indexes:
- idx_epg_data_channel on channel_name_id
- idx_epg_data_time on start_time, end_time
- idx_epg_data_parsed on parsed_data_id
- idx_epg_data_status on match_status
- idx_epg_data_live on is_live
epg_file_cache
Purpose: Tracks every EPG file in R2 (pre-generated and on-demand)
| Column | Type | Nullable | Description |
|---|---|---|---|
| access_count | INTEGER | Yes | |
| created_at | TIMESTAMPTZ | Yes | |
| custom_config_hash | TEXT | Yes | |
| file_size_bytes | BIGINT | Yes | |
| generation_completed_at | TIMESTAMPTZ | Yes | |
| generation_duration_ms | INTEGER | Yes | |
| generation_error | TEXT | Yes | |
| generation_started_at | TIMESTAMPTZ | Yes | |
| generation_status | TEXT | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| invalidated_at | TIMESTAMPTZ | Yes | Triggers regeneration when EPGData changes |
| last_accessed_at | TIMESTAMPTZ | Yes | |
| last_regenerated_at | TIMESTAMPTZ | Yes | |
| promoted_at | TIMESTAMPTZ | Yes | |
| promotion_status | TEXT | Yes | on_demand → pre_generated when accessed 3+ times in 24h |
| provider_id → providers | BIGINT | No | |
| r2_etag | TEXT | Yes | |
| r2_key | TEXT | No | |
| tier | TEXT | No | |
| timezone | TEXT | No | |
| updated_at | TIMESTAMPTZ | Yes |
Indexes:
- idx_epg_cache_unique_standard on provider_id, tier, timezone (UNIQUE)
- idx_epg_cache_unique_custom on provider_id, tier, timezone, custom_config_hash (UNIQUE)
- idx_epg_cache_provider on provider_id
- idx_epg_cache_status on generation_status
- idx_epg_cache_promotion on promotion_status
- idx_epg_cache_access on access_count DESC, last_accessed_at DESC
- idx_epg_cache_invalidated on invalidated_at
espn_league_mappings
Purpose: Maps ESPN-specific league names to canonical TheSportsDB leagues with confidence scoring
| Column | Type | Nullable | Description |
|---|---|---|---|
| (is_verified | UNKNOWN | No | |
| canonical_league_id → api_leagues_canonical | BIGINT | No | FK to api_leagues_canonical - the TheSportsDB canonical league this maps to |
| confidence | REAL | Yes | Mapping confidence: 0.0 = guess, 0.5 = fuzzy match, 1.0 = human verified |
| created_at | TIMESTAMPTZ | Yes | |
| espn_league_id | TEXT | Yes | ESPN internal league ID - used for API queries to ESPN |
| espn_name | TEXT | No | ESPN league name (e.g., "National Football League") - may differ from canonical |
| espn_slug | TEXT | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| is_verified | BOOLEAN | Yes | |
| last_used_at | TIMESTAMPTZ | Yes | |
| mapping_method | TEXT | Yes | |
| updated_at | TIMESTAMPTZ | Yes | |
| use_count | INTEGER | Yes | |
| verification_notes | TEXT | Yes | |
| verified_at | TIMESTAMPTZ | Yes | |
| verified_by | TEXT | Yes |
Indexes:
- idx_espn_league_name on espn_name
- idx_espn_league_canonical on canonical_league_id
- idx_espn_league_espn_id on espn_league_id
- idx_espn_league_confidence on confidence
- idx_espn_league_verified on is_verified
- idx_espn_league_low_confidence on confidence
- idx_espn_league_last_used on last_used_at DESC
espn_sport_mappings
Purpose: Maps ESPN-specific sport names to canonical TheSportsDB sports with confidence scoring
| Column | Type | Nullable | Description |
|---|---|---|---|
| (is_verified | UNKNOWN | No | |
| canonical_sport_id → api_sports_canonical | BIGINT | No | FK to api_sports_canonical - the TheSportsDB canonical sport this maps to |
| confidence | REAL | Yes | Mapping confidence: 0.0 = guess, 0.5 = fuzzy match, 1.0 = human verified |
| created_at | TIMESTAMPTZ | Yes | |
| espn_name | TEXT | No | ESPN sport name (e.g., "football", "basketball") - may differ from canonical |
| espn_slug | TEXT | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| is_verified | BOOLEAN | Yes | |
| last_used_at | TIMESTAMPTZ | Yes | |
| mapping_method | TEXT | Yes | How mapping was created: "manual", "fuzzy_match", "exact_match", "api_verified" |
| updated_at | TIMESTAMPTZ | Yes | |
| use_count | INTEGER | Yes | Number of times this mapping was used - tracks importance |
| verification_notes | TEXT | Yes | |
| verified_at | TIMESTAMPTZ | Yes | |
| verified_by | TEXT | Yes |
Indexes:
- idx_espn_sport_name on espn_name
- idx_espn_sport_canonical on canonical_sport_id
- idx_espn_sport_confidence on confidence
- idx_espn_sport_verified on is_verified
- idx_espn_sport_low_confidence on confidence
- idx_espn_sport_last_used on last_used_at DESC
event_identifiers
| Column | Type | Nullable | Description |
|---|---|---|---|
| FOREIGN → events | KEY | Yes | |
| created_at | TEXT | No | |
| event_id | INTEGER | No | |
| id 🔑 | INTEGER | Yes | |
| identifier_type | TEXT | No | |
| identifier_value | TEXT | No | |
| source | TEXT | No |
Indexes:
- idx_identifiers_event on event_id
- idx_identifiers_type_value on identifier_type, identifier_value
event_participants
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → participants | KEY | Yes | |
| created_at | TEXT | No | |
| event_id | INTEGER | No | |
| id 🔑 | INTEGER | Yes | |
| participant_id | INTEGER | No | |
| role | TEXT | No | |
| score | INTEGER | Yes |
Indexes:
- idx_event_participants_event on event_id
- idx_event_participants_participant on participant_id
events
Purpose: Canonical sports events from TheSportsDB API
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| alternate_ids | TEXT | Yes | |
| away_logo_url | TEXT | Yes | |
| away_team | TEXT | Yes | |
| away_team_id → teams | BIGINT | Yes | FK to canonical team record |
| away_team_normalized | TEXT | Yes | |
| away_team_original_espn | TEXT | Yes | |
| away_team_original_thesportsdb | TEXT | Yes | |
| broadcaster | TEXT | Yes | |
| city | TEXT | Yes | |
| country | TEXT | Yes | |
| created_at | TIMESTAMPTZ | No | |
| data_sources | TEXT | Yes | Array of source APIs, e.g., [ |
| description | TEXT | Yes | |
| details_fetched | BOOLEAN | Yes | TRUE if full details fetched from APIs |
| details_fetched_at | TIMESTAMPTZ | Yes | |
| espn_id | TEXT | Yes | Event ID from ESPN API |
| espn_metadata | JSONB | Yes | Complete ESPN API response (JSONB) |
| event_date | DATE | No | |
| event_datetime | TIMESTAMPTZ | No | |
| event_name | TEXT | No | |
| event_name_normalized | TEXT | No | |
| event_time | TIME | Yes | |
| home_logo_url | TEXT | Yes | |
| home_team | TEXT | Yes | |
| home_team_id → teams | BIGINT | Yes | FK to canonical team record |
| home_team_normalized | TEXT | Yes | |
| home_team_original_espn | TEXT | Yes | Original team name from ESPN (audit trail) |
| home_team_original_thesportsdb | TEXT | Yes | Original team name from TheSportsDB (audit trail) |
| id 🔑 | BIGSERIAL | Yes | |
| league | TEXT | No | |
| league_normalized | TEXT | No | |
| round | TEXT | Yes | |
| season | TEXT | Yes | |
| sport | TEXT | No | |
| status | TEXT | Yes | |
| thesportsdb_id | TEXT | Yes | |
| thesportsdb_metadata | JSONB | Yes | Complete TheSportsDB API response (JSONB) |
| thumbnail_url | TEXT | Yes | |
| timezone | TEXT | Yes | |
| updated_at | TIMESTAMPTZ | No | |
| venue | TEXT | Yes |
Indexes:
- idx_events_datetime on event_datetime
- idx_events_league on league
- idx_events_sport on sport
- idx_events_date on event_date
- idx_events_normalized on event_name_normalized
- idx_events_datetime on event_datetime
- idx_events_league on league
- idx_events_sport on sport
- idx_events_date on event_date
- idx_events_normalized on event_name_normalized
- idx_events_espn on espn_id
- idx_events_teams on home_team_normalized, away_team_normalized
- idx_events_unfetched on details_fetched
- idx_events_home_team_id on home_team_id
- idx_events_away_team_id on away_team_id
- idx_events_teams on home_team_id, away_team_id
- idx_events_espn_id on espn_id
family_league_mappings
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → channel_families | KEY | Yes | |
| confidence_boost | REAL | Yes | |
| created_at | TEXT | No | |
| family_id | INTEGER | No | |
| id 🔑 | INTEGER | Yes | |
| league | TEXT | No | |
| require_exact_league | BOOLEAN | Yes | |
| sport | TEXT | No |
Indexes:
- idx_family_mappings_family on family_id
- idx_family_mappings_league on league
invoices
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → user_subscriptions | KEY | Yes | |
| amount_due_cents | INTEGER | No | |
| amount_paid_cents | INTEGER | Yes | |
| created_at | TEXT | No | |
| created_at_stripe | TEXT | No | |
| currency | TEXT | No | |
| description | TEXT | Yes | |
| due_date | TEXT | Yes | |
| hosted_invoice_url | TEXT | Yes | |
| id 🔑 | INTEGER | Yes | |
| invoice_number | TEXT | Yes | |
| invoice_pdf_url | TEXT | Yes | |
| metadata | TEXT | Yes | |
| paid_at | TEXT | Yes | |
| period_end | TEXT | No | |
| period_start | TEXT | No | |
| status | TEXT | No | |
| stripe_customer_id | TEXT | No | |
| stripe_invoice_id | TEXT | No | |
| subscription_id | INTEGER | Yes | |
| subtotal_cents | INTEGER | No | |
| tax_cents | INTEGER | Yes | |
| total_cents | INTEGER | No | |
| updated_at | TEXT | No | |
| user_id | INTEGER | No |
Indexes:
- idx_invoices_user on user_id
- idx_invoices_stripe on stripe_invoice_id
- idx_invoices_status on status
- idx_invoices_paid_at on paid_at DESC
key_access_logs
| Column | Type | Nullable | Description |
|---|---|---|---|
| FOREIGN → access_keys | KEY | Yes | |
| accessed_at | TEXT | No | |
| bytes_transferred | INTEGER | Yes | |
| id 🔑 | INTEGER | Yes | |
| ip_address | TEXT | No | |
| key_id | INTEGER | No | |
| provider_slug | TEXT | No | |
| response_time_ms | INTEGER | Yes | |
| status_code | INTEGER | No | |
| user_agent | TEXT | Yes |
Indexes:
- idx_access_logs_key on key_id
- idx_access_logs_accessed on accessed_at DESC
- idx_access_logs_ip on ip_address, key_id
learned_aliases
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| alias | TEXT | No | |
| alias_normalized | TEXT | No | |
| confidence | REAL | No | |
| created_at | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| last_seen | TEXT | No | |
| occurrence_count | INTEGER | Yes | |
| team_name | TEXT | No | |
| team_name_normalized | TEXT | No |
Indexes:
- idx_learned_aliases_team on team_name_normalized
- idx_learned_aliases_confidence on confidence DESC
- idx_learned_aliases_occurrence on occurrence_count DESC
learned_patterns
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → events | KEY | Yes | |
| approved_at | TEXT | Yes | |
| approved_by | TEXT | Yes | |
| confidence | REAL | No | |
| created_at | TEXT | No | |
| discovery_method | TEXT | No | |
| false_positive_count | INTEGER | Yes | |
| id 🔑 | INTEGER | Yes | |
| league | TEXT | No | |
| learned_from_channel | TEXT | Yes | |
| learned_from_event_id | INTEGER | Yes | |
| match_count | INTEGER | Yes | |
| pattern_description | TEXT | No | |
| pattern_regex | TEXT | No | |
| sport | TEXT | No | |
| status | TEXT | Yes | |
| updated_at | TEXT | No |
Indexes:
- idx_patterns_league on league
- idx_patterns_status on status
- idx_patterns_confidence on confidence DESC
llm_parse_cache
Purpose: LLM result deduplication - prevents duplicate API calls for similar channels
| Column | Type | Nullable | Description |
|---|---|---|---|
| channel_name_normalized | TEXT | No | |
| created_at | TIMESTAMPTZ | Yes | |
| extracted_league | TEXT | Yes | |
| extracted_sport | TEXT | Yes | |
| extracted_teams | TEXT | Yes | |
| extracted_time | TEXT | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| last_reused_at | TIMESTAMPTZ | Yes | |
| llm_cost_cents | REAL | Yes | |
| llm_model | TEXT | Yes | |
| llm_prompt | TEXT | Yes | |
| llm_provider | TEXT | Yes | |
| llm_response | JSONB | Yes | |
| llm_tokens_used | INTEGER | Yes | |
| raw_channel_name | TEXT | No | |
| reuse_count | INTEGER | Yes | Tracks cost savings: each reuse = one saved LLM call |
Indexes:
- idx_llm_cache_similarity on extracted_teams, extracted_time, extracted_league
- idx_llm_cache_reuse on reuse_count DESC
- idx_llm_cache_sport on extracted_sport
match_cache
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → events | KEY | Yes | |
| channel_name | TEXT | No | |
| channel_name_normalized | TEXT | No | |
| confidence | REAL | No | |
| created_at | TEXT | No | |
| event_datetime | TEXT | No | |
| event_id | INTEGER | No | |
| expires_at | TEXT | No | |
| hit_count | INTEGER | Yes | |
| id 🔑 | INTEGER | Yes | |
| last_hit_at | TEXT | Yes | |
| match_method | TEXT | No | |
| provider_id | INTEGER | No |
Indexes:
- idx_cache_lookup on channel_name_normalized, provider_id
- idx_cache_expires on expires_at
- idx_cache_event on event_id
match_overrides
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → events | KEY | Yes | |
| active | BOOLEAN | Yes | |
| channel_id | TEXT | Yes | |
| channel_name | TEXT | No | |
| channel_name_normalized | TEXT | No | |
| confidence | INTEGER | Yes | |
| created_at | TEXT | No | |
| event_id | INTEGER | No | |
| event_title | TEXT | Yes | |
| family | TEXT | Yes | |
| id 🔑 | INTEGER | Yes | |
| notes | TEXT | Yes | |
| target_date | TEXT | No | |
| updated_at | TEXT | No | |
| verified_by | TEXT | Yes |
Indexes:
- idx_match_overrides_channel_date on channel_name_normalized, target_date
- idx_match_overrides_active on active
- idx_match_overrides_family on family
- idx_match_overrides_target_date on target_date
match_verifications
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → events | KEY | Yes | |
| api_source | TEXT | Yes | |
| channel_name | TEXT | No | |
| channel_name_normalized | TEXT | No | |
| confidence | INTEGER | Yes | |
| created_at | TEXT | No | |
| event_id | INTEGER | No | |
| id 🔑 | INTEGER | Yes | |
| notes | TEXT | Yes | |
| target_date | TEXT | No | |
| verified_by | TEXT | Yes |
Indexes:
- idx_match_verifications_channel_date on channel_name_normalized, target_date
- idx_match_verifications_event on event_id
- idx_match_verifications_target_date on target_date
parsed_data
Purpose: Every parse attempt, whether matched or not. JSONB stores flexible extracted fields.
| Column | Type | Nullable | Description |
|---|---|---|---|
| "confidence": | UNKNOWN | Yes | |
| "date": | UNKNOWN | Yes | |
| "event_type": | UNKNOWN | Yes | |
| "league": | UNKNOWN | Yes | |
| "overall": | UNKNOWN | Yes | |
| "sport": | UNKNOWN | Yes | |
| "teams": | UNKNOWN | Yes | |
| "time": | UNKNOWN | Yes | |
| "timezone": | UNKNOWN | Yes | |
| "venue": | UNKNOWN | Yes | |
| channel_name_id → channel_names | BIGINT | No | |
| created_at | TIMESTAMPTZ | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| match_confidence | REAL | Yes | |
| match_method | TEXT | Yes | |
| match_reason | TEXT | Yes | |
| matched_event_id → events | BIGINT | Yes | |
| parse_date | DATE | No | |
| parsed_data | JSONB | No | JSONB structure: time, timezone, date, teams[], league, sport, event_type, confidence{} |
| raw_title | TEXT | No | |
| record_status | TEXT | Yes |
Indexes:
- idx_parsed_data_channel on channel_name_id
- idx_parsed_data_date on parse_date
- idx_parsed_data_event on matched_event_id
- idx_parsed_data_method on match_method
participant_aliases
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → participants | KEY | Yes | |
| alias | TEXT | No | |
| alias_normalized | TEXT | No | |
| alias_type | TEXT | No | |
| confidence | REAL | Yes | |
| created_at | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| participant_id | INTEGER | No | |
| source | TEXT | No |
Indexes:
- idx_aliases_participant on participant_id
- idx_aliases_normalized on alias_normalized
- idx_aliases_unique on participant_id, alias_normalized (UNIQUE)
participants
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| abbreviation | TEXT | Yes | |
| badge_url | TEXT | Yes | |
| country | TEXT | Yes | |
| created_at | TEXT | No | |
| description | TEXT | Yes | |
| formed_year | INTEGER | Yes | |
| id 🔑 | INTEGER | Yes | |
| league | TEXT | Yes | |
| logo_url | TEXT | Yes | |
| name | TEXT | No | |
| name_normalized | TEXT | No | |
| participant_type | TEXT | No | |
| short_name | TEXT | Yes | |
| sport | TEXT | No | |
| thesportsdb_id | TEXT | Yes | |
| updated_at | TEXT | No |
Indexes:
- idx_participants_name on name_normalized
- idx_participants_sport on sport
- idx_participants_league on league
pattern_performance
Purpose: Performance tracking for individual channel patterns (NBA, ESPN+, etc). Tracks match success rate, processing time, and API efficiency per pattern.
| Column | Type | Nullable | Description |
|---|---|---|---|
| avg_api_calls_per_match | DECIMAL(5,2) | Yes | Average number of API calls (TheSportsDB, etc.) needed to resolve one channel match |
| avg_match_time_ms | INTEGER | Yes | |
| failed_matches | INTEGER | No | |
| id 🔑 | SERIAL | Yes | |
| league | TEXT | Yes | |
| match_success_rate | DECIMAL(5,2) | Yes | |
| measured_at | TIMESTAMPTZ | No | |
| pattern_prefix | TEXT | No | |
| pattern_type | IN | Yes | |
| provider_id → providers | INTEGER | No | |
| requires_llm_resolution | BOOLEAN | Yes | True if this pattern frequently needs LLM team name resolution (expensive) |
| sport | TEXT | Yes | |
| successful_matches | INTEGER | No | |
| total_channels | INTEGER | No |
Indexes:
- idx_pattern_performance_provider_id on provider_id
- idx_pattern_performance_pattern_prefix on pattern_prefix
- idx_pattern_performance_measured_at on measured_at DESC
provider_credentials
Purpose: Stores encrypted M3U URL credentials for provider access. Credentials are encrypted using AES-256-GCM (Fernet) with key stored in GitHub Actions secrets.
| Column | Type | Nullable | Description |
|---|---|---|---|
| base_url | TEXT | No | Base URL without credentials (e.g., https://provider.com/get.php) |
| created_at | TIMESTAMPTZ | No | |
| credential_type | IN | Yes | |
| encrypted_credentials | TEXT | No | AES-256-GCM encrypted JSON containing username/password/token |
| encryption_version | INTEGER | No | Encryption key version for supporting key rotation |
| id 🔑 | SERIAL | Yes | |
| last_used_at | TIMESTAMPTZ | Yes | |
| provider_id → providers | INTEGER | No | |
| static_params | JSONB | Yes | Non-credential URL parameters (e.g., {"type": "m3u", "output": "ts"}) |
| updated_at | TIMESTAMPTZ | No |
Indexes:
- idx_provider_credentials_provider_id on provider_id
provider_health_status
Purpose: Provider health monitoring. Detects anomalies like credential failures, channel count changes, or match rate degradation.
| Column | Type | Nullable | Description |
|---|---|---|---|
| alert_message | TEXT | Yes | |
| alert_triggered | BOOLEAN | Yes | |
| channel_count_drift | INTEGER | Yes | Change in total channel count since last check. Large changes may indicate credential expiration or provider changes. |
| checked_at | TIMESTAMPTZ | No | |
| credential_status | IN | Yes | |
| id 🔑 | SERIAL | Yes | |
| match_rate_drift | DECIMAL(5,2) | Yes | |
| pattern_changes_detected | INTEGER | Yes | Number of patterns that changed format (e.g., "NBA 01 :" → "NBA 01 ::"). May indicate provider naming scheme update. |
| provider_id → providers | INTEGER | No | |
| status | IN | Yes |
Indexes:
- idx_provider_health_status_provider_id on provider_id
- idx_provider_health_status_checked_at on checked_at DESC
- idx_provider_health_status_status on status
provider_metrics
Purpose: Time-series metrics for provider performance tracking. Records EPG generation stats, match success rates, and cost data per provider run.
| Column | Type | Nullable | Description |
|---|---|---|---|
| channels_processed | INTEGER | Yes | |
| epg_generation_time_seconds | INTEGER | Yes | |
| estimated_cost_usd | DECIMAL(10,4) | Yes | |
| failed_matches | INTEGER | Yes | |
| id 🔑 | SERIAL | Yes | |
| llm_team_resolutions | INTEGER | Yes | |
| match_success_rate | DECIMAL(5,2) | Yes | Percentage of processed channels that successfully matched to events (0-100) |
| measured_at | TIMESTAMPTZ | No | |
| parseable_channels | INTEGER | No | |
| provider_id → providers | INTEGER | No | |
| sports_channels | INTEGER | No | |
| successful_matches | INTEGER | Yes | |
| thesportsdb_api_calls | INTEGER | Yes | |
| total_channels | INTEGER | No | |
| tvg_id_coverage_percent | DECIMAL(5,2) | Yes | Percentage of channels that have tvg-id attribute (0-100) |
Indexes:
- idx_provider_metrics_provider_id on provider_id
- idx_provider_metrics_measured_at on measured_at DESC
provider_patterns
| Column | Type | Nullable | Description |
|---|---|---|---|
| FOREIGN → providers | KEY | Yes | |
| created_at | TEXT | Yes | |
| id 🔑 | INTEGER | Yes | |
| is_active | INTEGER | Yes | |
| last_matched_at | TEXT | Yes | |
| llm_confidence | FLOAT | Yes | LLM confidence score (0.0-1.0) |
| llm_verified | BOOLEAN | Yes | Whether LLM verified this pattern |
| llm_verification_reason | TEXT | Yes | LLM reasoning for verification result |
| match_count | INTEGER | Yes | |
| normalized_sport | TEXT | Yes | |
| pattern | TEXT | No | |
| pattern_type | TEXT | No | |
| priority | INTEGER | Yes | |
| provider_id | INTEGER | No | |
| sport_family | TEXT | Yes | |
| updated_at | TEXT | Yes |
Indexes:
- idx_provider_patterns_provider on provider_id
- idx_provider_patterns_active on is_active, provider_id
- idx_provider_patterns_priority on priority DESC
- idx_provider_patterns_sport on sport_family
- idx_provider_patterns_llm_verified on llm_verified (WHERE llm_verified IS NOT NULL)
providers
Purpose: IPTV service providers with M3U sources
| Column | Type | Nullable | Description |
|---|---|---|---|
| active | BOOLEAN | Yes | |
| channel_prefix | TEXT | Yes | |
| config_json | TEXT | Yes | |
| created_at | TIMESTAMPTZ | No | |
| default_timezone | TEXT | Yes | |
| description | TEXT | Yes | |
| display_name | TEXT | No | |
| enable_llm_fallback | BOOLEAN | Yes | |
| id 🔑 | BIGSERIAL | Yes | |
| identification_method | TEXT | No | DEPRECATED: No longer used for business logic. Kept for backward compatibility. |
| Originally intended to route between tvg_id and name_prefix matching strategies. | |||
| Architecture simplified to use pattern-based filtering exclusively, making this | |||
| field obsolete. Always defaults to | |||
| is_active | INTEGER | Yes | |
| last_error | TEXT | Yes | |
| last_processed_at | TIMESTAMPTZ | Yes | |
| m3u_password | TEXT | Yes | |
| m3u_requires_auth | BOOLEAN | Yes | |
| m3u_url | TEXT | No | |
| m3u_username | TEXT | Yes | |
| name | TEXT | No | |
| slug | TEXT | No | |
| support_email | TEXT | Yes | |
| updated_at | TIMESTAMPTZ | No | |
| website_url | TEXT | Yes |
Indexes:
- idx_providers_active on active
- idx_providers_slug on slug
- idx_providers_slug on slug
- idx_providers_active on is_active
- idx_providers_active on active
- idx_providers_slug on slug
subscription_events
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → users | KEY | Yes | |
| admin_user_id | INTEGER | Yes | |
| created_at | TEXT | No | |
| event_type | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| metadata | TEXT | Yes | |
| new_status | TEXT | Yes | |
| previous_status | TEXT | Yes | |
| subscription_id | INTEGER | No | |
| triggered_by | TEXT | Yes |
Indexes:
- idx_sub_events_subscription on subscription_id
- idx_sub_events_type on event_type
- idx_sub_events_created on created_at DESC
successful_matches
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| api_source | TEXT | Yes | |
| channel_name | TEXT | No | |
| channel_name_normalized | TEXT | No | |
| confidence | REAL | Yes | |
| created_at | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| league | TEXT | Yes | |
| match_method | TEXT | Yes | |
| matched_team1 | TEXT | Yes | |
| matched_team2 | TEXT | Yes | |
| parsed_team1 | TEXT | Yes | |
| parsed_team2 | TEXT | Yes |
Indexes:
- idx_successful_matches_league on league
- idx_successful_matches_confidence on confidence DESC
- idx_successful_matches_created on created_at DESC
- idx_successful_matches_matched_teams on matched_team1, matched_team2
team_aliases
Purpose: All team name variations for intelligent fuzzy matching
| Column | Type | Nullable | Description |
|---|---|---|---|
| alias | TEXT | No | Original alias (e.g., "Lakers") |
| alias_normalized | TEXT | No | Normalized for matching (e.g., "lakers") |
| channel_context | TEXT | Yes | Original channel name that led to this alias discovery |
| created_at | TIMESTAMPTZ | No | |
| id 🔑 | BIGSERIAL | Yes | |
| last_matched_at | TIMESTAMPTZ | Yes | |
| llm_confidence | FLOAT | Yes | LLM confidence score (0.0-1.0), NULL if not from LLM |
| llm_cost_cents | FLOAT | Yes | Cost in cents for LLM enrichment, NULL if not from LLM |
| llm_tokens_used | INTEGER | Yes | Tokens used by LLM call, NULL if not from LLM |
| match_count | INTEGER | Yes | Times this alias led to successful match |
| priority | INTEGER | Yes | 0-10 scale, higher = more trusted |
| source | TEXT | No | |
| team_id → teams | BIGINT | No |
Indexes:
- idx_team_aliases_normalized on alias_normalized
- idx_team_aliases_team_id on team_id
- idx_team_aliases_source on source
- idx_team_aliases_priority on priority DESC
team_discovery_cache
Purpose: LLM-powered team name enrichment cache - converts abbreviations/nicknames to canonical names
| Column | Type | Nullable | Description |
|---|---|---|---|
| canonical_team_name | TEXT | No | Canonical team name returned by LLM (e.g., "Galatasaray S.K.", "Los Angeles Lakers", "Boston Celtics") |
| channel_context | TEXT | No | Full channel name for context (e.g., "Turkish Basketball: GSK vs FBU 8:00 PM") |
| confidence | FLOAT | Yes | LLM confidence score (0-1) - reject entries with confidence < 0.5 |
| created_at | TIMESTAMPTZ | No | |
| id 🔑 | BIGSERIAL | Yes | |
| last_reused_at | TIMESTAMPTZ | Yes | |
| league | TEXT | Yes | |
| llm_cost_cents | FLOAT | Yes | |
| llm_model | TEXT | Yes | |
| llm_prompt | TEXT | Yes | |
| llm_provider | TEXT | Yes | |
| llm_response | JSONB | Yes | Full JSON response from Haiku including canonical name, league, sport, confidence |
| llm_tokens_used | INTEGER | Yes | |
| raw_team_name | TEXT | No | Raw team name from channel (e.g., "GSK", "Lakers", "Celtiks") |
| reuse_count | INTEGER | Yes | Number of times this cache entry was reused - tracks cost savings |
| sport | TEXT | Yes | |
| team_id → teams | BIGINT | Yes | Foreign key to teams table - set after team is created via API lookup |
Indexes:
- idx_team_discovery_raw_name on raw_team_name
- idx_team_discovery_canonical on canonical_team_name
- idx_team_discovery_team_id on team_id
- idx_team_discovery_league_sport on league, sport
- idx_team_discovery_created_at on created_at DESC
team_pairings
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| created_at | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| last_seen | TEXT | No | |
| league | TEXT | No | |
| occurrence_count | INTEGER | Yes | |
| team1 | TEXT | No | |
| team1_normalized | TEXT | No | |
| team2 | TEXT | No | |
| team2_normalized | TEXT | No |
Indexes:
- idx_team_pairings_teams on team1_normalized, team2_normalized
- idx_team_pairings_league on league
- idx_team_pairings_occurrence on occurrence_count DESC
team_sync_audit_log
| Column | Type | Nullable | Description |
|---|---|---|---|
| 'alias_added', | UNKNOWN | Yes | |
| 'conflict_detected', | UNKNOWN | Yes | |
| 'team_created', | UNKNOWN | Yes | |
| action_type | TEXT | No | |
| changes | TEXT | Yes | |
| conflict_id → team_sync_conflicts | INTEGER | Yes | |
| entity_id | INTEGER | No | |
| entity_type | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| notes | TEXT | Yes | |
| performed_at | DATETIME | No | |
| performed_by | TEXT | Yes | |
| sync_run_id → team_sync_runs | TEXT | Yes | |
| team_id → teams | INTEGER | Yes |
Indexes:
- idx_team_sync_audit_log_sync_run on sync_run_id
- idx_team_sync_audit_log_team on team_id
- idx_team_sync_audit_log_performed on performed_at DESC
- idx_team_sync_audit_log_action on action_type
- idx_team_sync_audit_log_sync_run on sync_run_id
- idx_team_sync_audit_log_team on team_id
- idx_team_sync_audit_log_performed on performed_at DESC
- idx_team_sync_audit_log_action on action_type
team_sync_auto_resolution_rules
| Column | Type | Nullable | Description |
|---|---|---|---|
| 'equals', | UNKNOWN | Yes | |
| 'merge', | UNKNOWN | Yes | |
| action | TEXT | No | |
| condition_field | TEXT | No | |
| condition_operator | TEXT | No | |
| condition_value | TEXT | No | |
| conflict_type | TEXT | No | |
| created_at | DATETIME | No | |
| created_by | TEXT | Yes | |
| description | TEXT | No | |
| enabled | BOOLEAN | No | |
| id 🔑 | INTEGER | Yes | |
| priority | INTEGER | No |
team_sync_conflicts
| Column | Type | Nullable | Description |
|---|---|---|---|
| 'alias_conflict' | UNKNOWN | Yes | |
| 'api_id_mismatch', | UNKNOWN | Yes | |
| 'auto_resolved', | UNKNOWN | Yes | |
| 'canonical_name_conflict', | UNKNOWN | Yes | |
| 'create_new', | UNKNOWN | Yes | |
| 'duplicate_normalized_name', | UNKNOWN | Yes | |
| 'ignore', | UNKNOWN | Yes | |
| 'ignored' | UNKNOWN | Yes | |
| 'manual_edit' | UNKNOWN | Yes | |
| 'merge', | UNKNOWN | Yes | |
| 'pending', | UNKNOWN | Yes | |
| 'resolved', | UNKNOWN | Yes | |
| 'update_existing', | UNKNOWN | Yes | |
| api_source | TEXT | No | |
| conflict_field | TEXT | No | |
| conflict_type | TEXT | No | |
| conflict_value | TEXT | No | |
| detected_at | DATETIME | No | |
| existing_team_id → teams | INTEGER | Yes | |
| id 🔑 | INTEGER | Yes | |
| new_team_data | TEXT | No | |
| resolution_action | TEXT | Yes | |
| resolution_notes | TEXT | Yes | |
| resolved_at | DATETIME | Yes | |
| resolved_by | TEXT | Yes | |
| severity | TEXT | No | |
| status | TEXT | No | |
| sync_run_id | TEXT | Yes |
Indexes:
- idx_team_sync_conflicts_status on status
- idx_team_sync_conflicts_type on conflict_type
- idx_team_sync_conflicts_team on existing_team_id
- idx_team_sync_conflicts_sync_run on sync_run_id
- idx_team_sync_conflicts_detected on detected_at DESC
- idx_team_sync_conflicts_status on status
- idx_team_sync_conflicts_type on conflict_type
- idx_team_sync_conflicts_team on existing_team_id
- idx_team_sync_conflicts_sync_run on sync_run_id
- idx_team_sync_conflicts_detected on detected_at DESC
team_sync_runs
| Column | Type | Nullable | Description |
|---|---|---|---|
| 'running', | UNKNOWN | Yes | |
| api_sources | TEXT | No | |
| auto_resolve_enabled | BOOLEAN | No | |
| completed_at | DATETIME | Yes | |
| conflicts_detected | INTEGER | No | |
| dry_run | BOOLEAN | No | |
| error_log | TEXT | Yes | |
| errors | INTEGER | No | |
| id 🔑 | TEXT | Yes | |
| league_filter | TEXT | Yes | |
| sport_filter | TEXT | Yes | |
| started_at | DATETIME | No | |
| status | TEXT | No | |
| summary | TEXT | Yes | |
| teams_created | INTEGER | No | |
| teams_discovered | INTEGER | No | |
| teams_updated | INTEGER | No | |
| triggered_by | TEXT | Yes |
Indexes:
- idx_team_sync_runs_started on started_at DESC
- idx_team_sync_runs_status on status
- idx_team_sync_runs_started on started_at DESC
- idx_team_sync_runs_status on status
teams
Purpose: Canonical team records with API source IDs for intelligent matching
| Column | Type | Nullable | Description |
|---|---|---|---|
| abbreviation | TEXT | Yes | |
| canonical_name | TEXT | No | Primary team name (e.g., "Los Angeles Lakers") |
| canonical_name_normalized | TEXT | No | |
| city | TEXT | Yes | |
| country | TEXT | Yes | |
| created_at | TIMESTAMPTZ | No | |
| discovery_source | TEXT | Yes | |
| espn_team_id | TEXT | Yes | Team ID from ESPN API |
| id 🔑 | BIGSERIAL | Yes | |
| is_placeholder | BOOLEAN | Yes | TRUE if auto-created, needs verification |
| league | TEXT | No | |
| sport | TEXT | No | |
| thesportsdb_team_id | TEXT | Yes | Team ID from TheSportsDB API |
| updated_at | TIMESTAMPTZ | No |
Indexes:
- idx_teams_league on league
- idx_teams_sport on sport
- idx_teams_thesportsdb_id on thesportsdb_team_id
- idx_teams_espn_id on espn_team_id
- idx_teams_is_placeholder on is_placeholder
tvg_id_mappings
| Column | Type | Nullable | Description |
|---|---|---|---|
| FOREIGN → providers | KEY | Yes | |
| channel_name | TEXT | Yes | |
| created_at | TEXT | Yes | |
| id 🔑 | INTEGER | Yes | |
| is_active | INTEGER | Yes | |
| last_matched_at | TEXT | Yes | |
| league | TEXT | Yes | |
| match_count | INTEGER | Yes | |
| normalized_sport | TEXT | Yes | |
| provider_id | INTEGER | No | |
| sport_family | TEXT | Yes | |
| tvg_id | TEXT | No | |
| tvg_id_prefix | TEXT | Yes | |
| updated_at | TEXT | Yes |
Indexes:
- idx_tvg_id_mappings_provider on provider_id
- idx_tvg_id_mappings_tvg_id on tvg_id
- idx_tvg_id_mappings_prefix on tvg_id_prefix
- idx_tvg_id_mappings_active on is_active, provider_id
- idx_tvg_id_mappings_provider on provider_id
- idx_tvg_id_mappings_tvg_id on tvg_id
- idx_tvg_id_mappings_prefix on tvg_id_prefix
- idx_tvg_id_mappings_active on is_active, provider_id
- idx_tvg_id_mappings_provider on provider_id
- idx_tvg_id_mappings_tvg_id on tvg_id
- idx_tvg_id_mappings_prefix on tvg_id_prefix
- idx_tvg_id_mappings_active on is_active, provider_id
unmatched_channels
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → events | KEY | Yes | |
| channel_name | TEXT | No | |
| channel_name_normalized | TEXT | No | |
| failure_reason | TEXT | Yes | |
| family | TEXT | Yes | |
| first_seen | TEXT | No | |
| group_title | TEXT | Yes | |
| id 🔑 | INTEGER | Yes | |
| last_seen | TEXT | No | |
| llm_attempted | BOOLEAN | Yes | |
| llm_response | TEXT | Yes | |
| manual_match_confidence | REAL | Yes | |
| manual_match_event_id | INTEGER | Yes | |
| manual_matched_at | TEXT | Yes | |
| manual_matched_by | TEXT | Yes | |
| metadata | TEXT | Yes | |
| occurrence_count | INTEGER | Yes | |
| payload | TEXT | Yes | |
| provider | TEXT | Yes | |
| provider_id | INTEGER | No | |
| regex_stage_reached | INTEGER | Yes | |
| resolution_notes | TEXT | Yes | |
| resolved | BOOLEAN | Yes | |
| target_date | TEXT | Yes | |
| team1 | TEXT | Yes | |
| team2 | TEXT | Yes | |
| timestamp | TEXT | Yes | |
| tvg_id | TEXT | Yes | |
| tvg_name | TEXT | Yes |
Indexes:
- idx_unmatched_provider on provider_id
- idx_unmatched_resolved on resolved
- idx_unmatched_normalized on channel_name_normalized
- idx_unmatched_occurrence on occurrence_count DESC
- idx_unmatched_family on family
- idx_unmatched_target_date on target_date
- idx_unmatched_team1 on team1
- idx_unmatched_provider_name on provider
- idx_unmatched_timestamp on timestamp DESC
user_subscriptions
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → users | KEY | Yes | |
| amount_cents | INTEGER | No | |
| cancel_at_period_end | BOOLEAN | Yes | |
| canceled_at | TEXT | Yes | |
| created_at | TEXT | No | |
| currency | TEXT | No | |
| current_period_end | TEXT | No | |
| current_period_start | TEXT | No | |
| ended_at | TEXT | Yes | |
| grace_period_emails_sent | INTEGER | Yes | |
| grace_period_end | TEXT | Yes | |
| id 🔑 | INTEGER | Yes | |
| plan_type | TEXT | No | |
| provider_packs | TEXT | Yes | |
| status | TEXT | No | |
| stripe_customer_id | TEXT | No | |
| stripe_price_id | TEXT | No | |
| stripe_subscription_id | TEXT | No | |
| trial_end | TEXT | Yes | |
| trial_start | TEXT | Yes | |
| updated_at | TEXT | No | |
| user_id | INTEGER | No |
Indexes:
- idx_subscriptions_user on user_id
- idx_subscriptions_stripe on stripe_subscription_id
- idx_subscriptions_status on status
- idx_subscriptions_period_end on current_period_end
users
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| FOREIGN → users | KEY | Yes | |
| auth0_id | TEXT | No | |
| created_at | TEXT | No | |
| TEXT | No | ||
| email_notifications | BOOLEAN | Yes | |
| email_verified | BOOLEAN | Yes | |
| id 🔑 | INTEGER | Yes | |
| last_login_at | TEXT | Yes | |
| name | TEXT | Yes | |
| picture_url | TEXT | Yes | |
| preferred_language | TEXT | Yes | |
| preferred_timezone | TEXT | Yes | |
| referrer_user_id | INTEGER | Yes | |
| role | TEXT | No | |
| signup_source | TEXT | Yes | |
| status | TEXT | No | |
| suspended_at | TEXT | Yes | |
| suspended_reason | TEXT | Yes | |
| updated_at | TEXT | No |
Indexes:
- idx_users_auth0 on auth0_id
- idx_users_email on email
- idx_users_role on role
- idx_users_status on status
vod_filter_patterns
| Column | Type | Nullable | Description |
|---|---|---|---|
| FOREIGN → providers | KEY | Yes | |
| created_at | TEXT | Yes | |
| description | TEXT | Yes | |
| id 🔑 | INTEGER | Yes | |
| is_active | INTEGER | Yes | |
| match_count | INTEGER | Yes | |
| pattern | TEXT | No | |
| pattern_type | TEXT | Yes | |
| provider_id | INTEGER | Yes |
Indexes:
- idx_vod_patterns_provider on provider_id
- idx_vod_patterns_active on is_active
- idx_vod_patterns_provider on provider_id
- idx_vod_patterns_active on is_active
- idx_vod_patterns_provider on provider_id
- idx_vod_patterns_active on is_active
webhook_events
| Column | Type | Nullable | Description |
|---|---|---|---|
| CHECK | UNKNOWN | Yes | |
| created_at_stripe | TEXT | No | |
| event_type | TEXT | No | |
| id 🔑 | INTEGER | Yes | |
| payload | TEXT | No | |
| processed | BOOLEAN | Yes | |
| processed_at | TEXT | Yes | |
| processing_error | TEXT | Yes | |
| received_at | TEXT | No | |
| retry_count | INTEGER | Yes | |
| stripe_event_id | TEXT | No |
Indexes:
- idx_webhooks_stripe_id on stripe_event_id
- idx_webhooks_type on event_type
- idx_webhooks_processed on processed
- idx_webhooks_received on received_at DESC