Quick Start

EPGOAT Documentation - User Guides

EPGOAT Quick Start Guide

Status: Active Last Updated: 2025-11-02 Related Docs: System Overview, Command Reference, Development Setup Code Location: backend/epgoat/, frontend/


Target Audience: New developers joining the EPGOAT project Time to Complete: 30-45 minutes Prerequisites: macOS/Linux, Git, Python 3.11+, Node.js 18+, GitHub account

Table of Contents

  1. Overview
  2. Development Environment Setup
  3. Running EPG Generation Locally
  4. Running the Admin Frontend
  5. Testing
  6. Common Workflows
  7. Troubleshooting
  8. Next Steps

Overview

What is EPGOAT?

EPGOAT is a SaaS platform that generates Electronic Program Guides (EPG) for IPTV services. It:

  1. Parses M3U playlists from IPTV providers
  2. Matches channels to sports events using a 6-stage regex matcher + LLM fallback
  3. Generates XMLTV files for EPG consumption (Plex, Jellyfin, etc.)
  4. Provides multi-tenant access via UUID-based keys
  5. Manages subscriptions via Stripe with Auth0 authentication

Architecture at a Glance:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      EPGOAT PLATFORM                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚  Marketing   β”‚    β”‚   Consumer   β”‚    β”‚    Admin     β”‚      β”‚
β”‚  β”‚   Website    β”‚    β”‚    Portal    β”‚    β”‚  Dashboard   β”‚      β”‚
β”‚  β”‚ epgoat.tv    β”‚    β”‚ app.epgoat.tvβ”‚    β”‚admin.epgoat.tvβ”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚         β”‚                    β”‚                    β”‚              β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚                              β”‚                                   β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                         β”‚
β”‚                    β”‚  Cloudflare       β”‚                         β”‚
β”‚                    β”‚  Workers API      β”‚                         β”‚
β”‚                    β”‚  (Serverless)     β”‚                         β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                         β”‚
β”‚                              β”‚                                   β”‚
β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚              β”‚               β”‚               β”‚                   β”‚
β”‚       β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”            β”‚
β”‚       β”‚ Cloudflare β”‚  β”‚ Cloudflare β”‚ β”‚   Stripe   β”‚            β”‚
β”‚       β”‚    D1      β”‚  β”‚     R2     β”‚ β”‚  (Billing) β”‚            β”‚
β”‚       β”‚ (Database) β”‚  β”‚  (Storage) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                            β”‚
β”‚                              β”‚                                   β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                         β”‚
β”‚                    β”‚  GitHub Actions   β”‚                         β”‚
β”‚                    β”‚  EPG Generation   β”‚                         β”‚
β”‚                    β”‚  (4x daily cron)  β”‚                         β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                         β”‚
β”‚                              β”‚                                   β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                         β”‚
β”‚                    β”‚ TheSportsDB API   β”‚                         β”‚
β”‚                    β”‚  (Event Data)     β”‚                         β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                         β”‚
β”‚                              β”‚                                   β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                         β”‚
β”‚                    β”‚  Claude API       β”‚                         β”‚
β”‚                    β”‚  (LLM Matching)   β”‚                         β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                         β”‚
β”‚                                                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Repository Structure:

epgoat-internal/
β”œβ”€β”€ engineering/
β”‚   β”œβ”€β”€ linker/               # Python EPG generation engine
β”‚   β”‚   β”œβ”€β”€ core/             # Parsers, patterns, models
β”‚   β”‚   β”œβ”€β”€ backend/epgoat/services/         # API clients, LLM matcher
β”‚   β”‚   β”œβ”€β”€ data/             # Enhanced matchers
β”‚   β”‚   β”œβ”€β”€ pipeline/         # Main EPG generator
β”‚   β”‚   └── tests/            # Unit tests
β”‚   β”œβ”€β”€ config/               # YAML configuration files
β”‚   └── scripts/              # Utility scripts
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ admin/                # Admin dashboard (React + MUI)
β”‚   β”œβ”€β”€ consumer/             # Consumer portal (React + MUI)
β”‚   └── marketing/            # Marketing website (React)
β”œβ”€β”€ Documentation/           # Documentation
β”œβ”€β”€ schema.sql                # Database schema (27 tables)
β”œβ”€β”€ API_SPECIFICATION.md      # OpenAPI spec
β”œβ”€β”€ TECH_STACK.md             # Technology decisions
└── README.md                 # Project overview

Development Environment Setup

Step 1: Clone the Repository

# Clone the repository
git clone https://github.com/epgoat/epgoat-internal.git
cd epgoat-internal

# Checkout main branch
git checkout main

Step 2: Python Environment Setup

# Create virtual environment
python3 -m venv venv

# Activate virtual environment
source venv/bin/activate  # macOS/Linux
# OR
venv\Scripts\activate     # Windows

# Upgrade pip
pip install --upgrade pip

# Install dependencies
pip install -r requirements.txt

# Verify installation
python --version  # Should be 3.11+
pytest --version  # Should be installed
ruff --version    # Should be installed

Requirements Overview:

pydantic==2.5.0              # Data validation
httpx==0.25.2                # Async HTTP client
lxml==5.0.0                  # XML parsing/generation
anthropic==0.18.0            # Claude API client
python-dotenv==1.0.0         # Environment variables
pyyaml==6.0.1                # YAML config loading
pytest==7.4.3                # Testing
ruff==0.1.9                  # Linting/formatting

Step 3: Environment Variables

# Copy example environment file
cp .env.example .env

# Edit .env with your credentials
nano .env  # or use your favorite editor

Required Environment Variables:

# .env
# TheSportsDB API (required for event data)
THESPORTSDB_API_KEY=your_api_key_here
# Get free key at: https://www.thesportsdb.com/api.php

# Anthropic Claude API (required for LLM matching)
ANTHROPIC_API_KEY=your_api_key_here
# Get key at: https://console.anthropic.com/

# Optional: Provider M3U URLs with credentials
TPS_M3U_URL=http://username:password@provider.com/playlist.m3u

Getting API Keys:

  1. TheSportsDB API Key:
  2. Visit https://www.thesportsdb.com/api.php
  3. Sign up for Patreon ($2/month) to get API key
  4. Or use free test key (limited to 100 requests/day)

  5. Anthropic Claude API Key:

  6. Visit https://console.anthropic.com/
  7. Create account
  8. Add $5 credit to start
  9. Copy API key from Settings

Step 4: Node.js Environment Setup (for Frontend)

# Navigate to admin frontend
cd frontend/admin

# Install dependencies
npm install

# Verify installation
npm --version  # Should be 18+
node --version # Should be 18+

Step 5: Verify Installation

# Run Python tests
cd backend/epgoat
pytest -v

# Expected output:
# ==================== test session starts ====================
# collected 45 items
#
# test_patterns.py::test_nba_pattern PASSED
# test_patterns.py::test_nfl_pattern PASSED
# ...
# ==================== 45 passed in 2.34s ====================

# Run linting
ruff check .

# Expected output:
# All checks passed!

# Run type checking
mypy .

# Expected output:
# Success: no issues found in 23 source files

If tests fail: - Ensure you're in the correct directory (backend/epgoat) - Verify Python version is 3.11+ - Check that all dependencies are installed: pip list - Review error messages for missing environment variables


Running EPG Generation Locally

Basic EPG Generation

Scenario: Generate an EPG for today for a single provider.

# Navigate to EPG pipeline directory
cd backend/epgoat/pipeline

# Run EPG generator
python epg_generator.py \
  --provider tps \
  --date today \
  --timezone "America/New_York"

# Expected output:
# [INFO] Loading configuration...
# [INFO] Fetching M3U playlist for provider: tps
# [INFO] Parsed 4,876 channels from M3U
# [INFO] Stage 1 (League prefix): 1,234 matches (25.3%)
# [INFO] Stage 2 (Team names): 892 matches (18.3%)
# [INFO] Stage 3 (Event time): 456 matches (9.4%)
# [INFO] Stage 4 (Abbreviations): 234 matches (4.8%)
# [INFO] Stage 5 (Fuzzy matching): 178 matches (3.6%)
# [INFO] Stage 6 (LLM fallback): 1,123 matches (23.0%)
# [INFO] Total matched: 4,117 / 4,876 (84.4%)
# [INFO] Unmatched: 759 channels (15.6%)
# [INFO] Generating XMLTV file...
# [INFO] Written to: output/tps_2025-10-30.xml
# [INFO] LLM cost: $0.42 (1,123 calls, 90% cached)

Output Files:

backend/epgoat/output/
β”œβ”€β”€ tps_2025-10-30.xml        # Generated XMLTV file
β”œβ”€β”€ unmatched_channels.json   # Channels that failed to match
└── match_report.json         # Detailed match statistics

Generate EPG for Specific Date

# Generate EPG for specific date (e.g., upcoming weekend)
python epg_generator.py \
  --provider tps \
  --date 2025-11-15 \
  --timezone "America/Chicago"

# Generate EPG for date range (future feature)
python epg_generator.py \
  --provider tps \
  --start-date 2025-11-15 \
  --end-date 2025-11-22 \
  --timezone "America/Los_Angeles"

Test with Sample M3U File

# Use a local test M3U file (no API calls)
python epg_generator.py \
  --m3u-file ../tests/fixtures/sample_playlist.m3u \
  --date today \
  --timezone "America/New_York" \
  --skip-api  # Don't call TheSportsDB API (use cached events)

# This is useful for:
# - Testing pattern changes without API costs
# - Working offline
# - Reproducible test runs

Debug Mode

# Enable verbose logging to see detailed matching process
python epg_generator.py \
  --provider tps \
  --date today \
  --debug

# Expected output (verbose):
# [DEBUG] Channel: "NBA 01: Lakers vs Celtics 7:00 PM ET"
# [DEBUG] Stage 1: Testing pattern: ^NBA\s+\d+
# [DEBUG] Stage 1: MATCH! Extracted: league=NBA
# [DEBUG] Searching events for: league=NBA, date=2025-10-30
# [DEBUG] Found 12 NBA events today
# [DEBUG] Testing team names: "Lakers" vs "Los Angeles Lakers" (score: 0.95)
# [DEBUG] Testing team names: "Celtics" vs "Boston Celtics" (score: 0.98)
# [DEBUG] MATCHED to event_id=12345 (confidence: 0.96)

Refresh Event Database

Before generating EPGs, refresh the event database from TheSportsDB:

# Navigate to utilities
cd backend/epgoat/utilities

# Refresh events for today
python refresh_event_db.py --date today

# Expected output:
# [INFO] Fetching events from TheSportsDB for 2025-10-30
# [INFO] Found 147 events across 8 sports
# [INFO] Basketball: 45 events (NBA, EuroLeague, NCAA)
# [INFO] American Football: 32 events (NFL, NCAA)
# [INFO] Soccer: 52 events (Premier League, La Liga, etc.)
# [INFO] Ice Hockey: 12 events (NHL)
# [INFO] Baseball: 0 events (off-season)
# [INFO] MMA: 3 events (UFC, Bellator)
# [INFO] Boxing: 2 events
# [INFO] Motor Racing: 1 event (F1)
# [INFO] Inserted 147 events into database
# [INFO] Updated 23 existing events

# Refresh events for specific date range
python refresh_event_db.py \
  --start-date 2025-11-15 \
  --end-date 2025-11-22

# Refresh leagues/competitions metadata
python refresh_leagues.py

Cost Note: TheSportsDB API has rate limits (100 requests/day on free tier). Use sparingly.


Running the Admin Frontend

Step 1: Start Development Server

# Navigate to admin frontend
cd frontend/admin

# Start Vite dev server
npm run dev

# Expected output:
#
#   VITE v5.0.8  ready in 342 ms
#
#   ➜  Local:   http://localhost:5173/
#   ➜  Network: use --host to expose
#   ➜  press h + enter to show help

Step 2: Access Admin Dashboard

Open your browser to: http://localhost:5173/

Default Login (Development): - Email: admin@epgoat.tv - Password: admin123 (development only!)

Pages Available:

  1. Dashboard (/)
  2. Match rate metrics
  3. Daily statistics
  4. Recent unmatched channels

  5. Providers (/providers)

  6. View all IPTV providers
  7. Edit M3U URLs
  8. Enable/disable LLM fallback

  9. Events (/events)

  10. Search TheSportsDB events
  11. View event details
  12. Manual channel matching

  13. Unmatched Channels (/unmatched)

  14. View channels that failed to match
  15. Manually link to events
  16. Submit as learned patterns

  17. Users (/users)

  18. View all users
  19. Manage subscriptions
  20. Suspend/reactivate accounts

  21. Analytics (/analytics)

  22. Match rate trends
  23. LLM cost tracking
  24. User growth metrics

Step 3: Configure Auth0 (Optional)

For full authentication flow, set up Auth0:

# Edit .env in frontend/admin/
nano .env

# Add Auth0 credentials:
VITE_AUTH0_DOMAIN=epgoat.us.auth0.com
VITE_AUTH0_CLIENT_ID=your_client_id_here
VITE_AUTH0_AUDIENCE=https://api.epgoat.tv

To get Auth0 credentials:

  1. Sign up at https://auth0.com/ (free tier)
  2. Create a new Single Page Application
  3. Set Allowed Callback URLs: http://localhost:5173
  4. Set Allowed Logout URLs: http://localhost:5173
  5. Copy Domain and Client ID to .env

Without Auth0: The app will use mock authentication (always logged in as admin).

Step 4: Hot Module Replacement (HMR)

Edit any React component and see changes instantly:

# Edit a component
nano src/pages/Dashboard.tsx

# Save file (Cmd+S / Ctrl+S)
# Browser auto-refreshes with changes (no manual reload needed)

Testing

Python Unit Tests

# Navigate to linker directory
cd backend/epgoat

# Run all tests
pytest

# Run specific test file
pytest tests/test_patterns.py

# Run specific test function
pytest tests/test_patterns.py::test_nba_pattern_matching

# Run with coverage report
pytest --cov=core --cov=services --cov-report=html

# View coverage report
open htmlcov/index.html  # macOS
xdg-open htmlcov/index.html  # Linux

Test Categories:

  1. Pattern Tests (test_patterns.py)
  2. Test regex patterns for all leagues
  3. Verify stage-by-stage matching
  4. Edge cases (special characters, unicode)

  5. Parser Tests (test_parsers.py)

  6. M3U parsing
  7. Time extraction
  8. Channel normalization

  9. Integration Tests (test_integration.py)

  10. End-to-end EPG generation
  11. API mocking (TheSportsDB, Claude)
  12. XMLTV validation

Writing New Tests:

# tests/test_patterns.py

import pytest
from core.patterns import ChannelMatcher

def test_custom_league_pattern():
    """Test matching for a new league pattern."""
    matcher = ChannelMatcher()

    channel_name = "UFC 301: Main Event 10:00 PM ET"
    result = matcher.match(channel_name)

    assert result is not None
    assert result.league == "UFC"
    assert result.confidence > 0.9
    assert result.stage == 1  # Matched in stage 1 (league prefix)

Frontend Tests

# Navigate to admin frontend
cd frontend/admin

# Run Vitest tests
npm run test

# Run with UI
npm run test:ui

# Run with coverage
npm run test:coverage

Frontend Test Example:

// src/components/SubscriptionCard.test.tsx

import { render, screen } from '@testing-library/react';
import { SubscriptionCard } from './SubscriptionCard';

test('displays active subscription correctly', () => {
  const subscription = {
    status: 'active',
    plan_type: 'consumer_annual',
    current_period_end: '2026-10-30',
  };

  render(<SubscriptionCard subscription={subscription} />);

  expect(screen.getByText('Active')).toBeInTheDocument();
  expect(screen.getByText('Consumer Annual')).toBeInTheDocument();
});

Linting & Formatting

# Python: Ruff (replaces Flake8 + Black)
cd backend/epgoat

# Check for issues
ruff check .

# Auto-fix issues
ruff check --fix .

# Format code
ruff format .

# TypeScript/React: ESLint + Prettier
cd frontend/admin

# Lint
npm run lint

# Format
npm run format

# Type check
npm run type-check

Common Workflows

Workflow 1: Adding Support for a New League

Scenario: Add support for "LIV Golf" league.

Steps:

  1. Add emoji configuration:
# Edit config file
nano backend/config/sport_emojis.yml

# Add entry:
Golf: β›³
LIV Golf: β›³
  1. Add XMLTV category:
# Edit config file
nano backend/config/sport_categories.yml

# Add entry:
Golf:
  - Sports
  - Golf
LIV Golf:
  - Sports
  - Golf
  - Professional
  1. Add channel pattern:
# Edit patterns file
nano backend/epgoat/domain/patterns.py

# Add to ALLOWED_CHANNEL_PATTERNS:
(r'^LIV\s+Golf\s+\d+', 'LIV Golf'),     # "LIV Golf 1", "LIV Golf 2"
(r'^LIV\s+\d+', 'LIV Golf'),            # "LIV 1", "LIV 2"
  1. Add test:
# Add to test file
nano backend/epgoat/tests/test_patterns.py

def test_liv_golf_pattern():
    """Test LIV Golf channel matching."""
    matcher = ChannelMatcher()

    test_cases = [
        "LIV Golf 1",
        "LIV Golf 2: Singapore Day 1",
        "LIV 3: Final Round",
    ]

    for channel in test_cases:
        result = matcher.match(channel)
        assert result is not None
        assert result.league == "LIV Golf"
        assert result.confidence > 0.8
  1. Run tests:
pytest tests/test_patterns.py::test_liv_golf_pattern -v
  1. Commit changes:
git add -A
git commit -m "feat(patterns): add LIV Golf league support"
git push origin feature/add-liv-golf

Workflow 2: Debugging a Failed Match

Scenario: A channel "NBA Championship: Lakers vs Celtics" is not matching.

Steps:

  1. Run EPG generator in debug mode:
cd backend/epgoat/pipeline

python epg_generator.py \
  --provider tps \
  --date today \
  --debug \
  --channel-filter "NBA Championship"

# Look for debug output showing why it failed
  1. Check unmatched channels:
# View unmatched channels JSON
cat output/unmatched_channels.json | jq '.[] | select(.channel_name | contains("NBA Championship"))'

# Example output:
# {
#   "channel_name": "NBA Championship: Lakers vs Celtics",
#   "regex_stage_reached": 0,
#   "llm_attempted": false,
#   "failure_reason": "No regex pattern matched"
# }
  1. Test pattern manually:
# Create test script
nano test_pattern.py

from core.patterns import ChannelMatcher

matcher = ChannelMatcher()
channel = "NBA Championship: Lakers vs Celtics"
result = matcher.match(channel)

print(f"Match result: {result}")
print(f"Stage: {result.stage if result else 'None'}")
print(f"Confidence: {result.confidence if result else 0.0}")

# Run test
python test_pattern.py
  1. Add missing pattern:
# If pattern is missing, add it:
nano backend/epgoat/domain/patterns.py

# Add to ALLOWED_CHANNEL_PATTERNS (in appropriate order):
(r'^NBA\s+Championship', 'NBA'),  # Handle "NBA Championship" variant
  1. Verify fix:
# Re-run generator
python epg_generator.py --provider tps --date today --channel-filter "NBA Championship"

# Should now match successfully

Workflow 3: Manual Channel-to-Event Linking

Scenario: Admin user manually links an unmatched channel to an event.

Steps:

  1. Open Admin Dashboard:
http://localhost:5173/unmatched
  1. Find unmatched channel:
  2. Filter by provider: "TPS"
  3. Search: "Lakers"
  4. Click on channel row

  5. Search for event:

  6. Event search modal opens
  7. Search: "Lakers Celtics"
  8. Select correct event from results

  9. Set confidence:

  10. Confidence slider: 0.95 (very confident match)
  11. Click "Link Channel to Event"

  12. Verify database update:

-- Check unmatched_channels table
SELECT * FROM unmatched_channels
WHERE channel_name LIKE '%Lakers%'
AND manual_match_event_id IS NOT NULL;
  1. Pattern learning (automatic):
  2. System analyzes the manual match
  3. Suggests new regex pattern
  4. Admin approves pattern
  5. Pattern added to learned_patterns table

Workflow 4: Deploying EPG Generation to Production

Scenario: Deploy EPG generation workflow to GitHub Actions.

Steps:

  1. Verify workflow file:
cat .github/workflows/epg-generation.yml
  1. Add secrets to GitHub:
  2. Go to repository Settings β†’ Secrets and variables β†’ Actions
  3. Add secrets:

    • THESPORTSDB_API_KEY
    • ANTHROPIC_API_KEY
    • CLOUDFLARE_API_TOKEN
    • CLOUDFLARE_ACCOUNT_ID
  4. Test workflow locally with act (optional):

# Install act: https://github.com/nektos/act
brew install act  # macOS

# Run workflow locally
act schedule --secret-file .env
  1. Trigger manual workflow:
  2. Go to GitHub repository β†’ Actions tab
  3. Select "EPG Generation" workflow
  4. Click "Run workflow"
  5. Select branch: main
  6. Click "Run workflow"

  7. Monitor execution:

  8. Watch workflow logs in real-time
  9. Check for successful R2 upload
  10. Verify XMLTV files are accessible

  11. Set up scheduled runs:

  12. Already configured in workflow file (4x daily cron)
  13. Verify cron schedule is correct for your timezone

Troubleshooting

Issue 1: "Module not found" errors in Python

Symptom:

ModuleNotFoundError: No module named 'pydantic'

Solution:

# Ensure virtual environment is activated
source venv/bin/activate

# Reinstall requirements
pip install -r requirements.txt

# Verify installation
pip list | grep pydantic

Issue 2: API Key errors

Symptom:

Error: Invalid API key for TheSportsDB

Solution:

# Check .env file exists and has correct keys
cat .env | grep THESPORTSDB_API_KEY

# Ensure .env is being loaded
python -c "from dotenv import load_dotenv; import os; load_dotenv(); print(os.getenv('THESPORTSDB_API_KEY'))"

# If None, check .env location (should be in project root)

Issue 3: Tests fail with "fixture not found"

Symptom:

pytest tests/test_patterns.py
ERROR: fixture 'sample_m3u' not found

Solution:

# Ensure you're running tests from correct directory
cd backend/epgoat
pytest tests/test_patterns.py

# Check conftest.py exists
ls tests/conftest.py

Issue 4: Frontend won't start

Symptom:

npm run dev
Error: Cannot find module '@vitejs/plugin-react'

Solution:

# Clear node_modules and reinstall
cd frontend/admin
rm -rf node_modules package-lock.json
npm install

# Try again
npm run dev

Issue 5: LLM matching not working

Symptom:

[ERROR] Claude API error: 401 Unauthorized

Solution:

# Verify API key is set correctly
echo $ANTHROPIC_API_KEY

# Test API key manually
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"claude-3-5-haiku-20241022","max_tokens":10,"messages":[{"role":"user","content":"Hi"}]}'

# If 401, regenerate API key at console.anthropic.com

Issue 6: Regex pattern not matching

Symptom: Channel "NFL 01: Cowboys vs Giants" not matching.

Debug Steps:

# Test regex pattern in Python REPL
import re

pattern = r'^NFL\s+\d+'
channel = "NFL 01: Cowboys vs Giants"

match = re.match(pattern, channel)
print(match)  # Should print match object

# If None, adjust pattern:
pattern = r'^NFL\s+\d+:'  # Added colon
match = re.match(pattern, channel)
print(match)  # Should now match

Fix: Update pattern in backend/epgoat/domain/patterns.py and add test case.

Issue 7: Database connection errors (D1)

Symptom:

Error: Supabase database not found

Solution:

# This error occurs in production, not local development
# For local development, use SQLite file

# Create local SQLite database
sqlite3 dist/epgoat.db < schema.sql

# Update connection string in config
export DATABASE_URL=sqlite:///dist/epgoat.db

# For production D1:
# - Ensure Cloudflare account is set up
# - Create Supabase database via wrangler CLI
# - Deploy schema.sql to D1

Next Steps

For New Developers

After completing this quick start, explore these areas:

  1. Read Core Documentation:
  2. TECH_STACK.md - Understand technology decisions
  3. API_SPECIFICATION.md - Learn API endpoints
  4. schema.sql - Understand database structure

  5. Explore Codebase:

  6. Read backend/epgoat/domain/patterns.py - Pattern matching logic
  7. Read backend/epgoat/services/llm_matcher.py - LLM integration
  8. Read frontend/admin/src/pages/Dashboard.tsx - Admin UI

  9. Pick a Starter Task:

  10. Add support for a new league (low complexity)
  11. Fix a failing test (medium complexity)
  12. Implement a new API endpoint (high complexity)

  13. Join Team Communication:

  14. Slack channel: #epgoat-dev
  15. Weekly standup: Mondays 10am ET
  16. Code review: All PRs require 1 approval

Contribution Guidelines

Before submitting a PR:

  1. βœ… All tests pass: make test
  2. βœ… Code is formatted: make format
  3. βœ… No linting errors: make lint
  4. βœ… Type checks pass: make type-check
  5. βœ… Branch is up to date with main
  6. βœ… Commit messages follow convention: feat:, fix:, docs:, etc.

PR Template:

## Description
Brief description of changes.

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests pass
- [ ] Manual testing completed

## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Comments added for complex logic
- [ ] Documentation updated

Learning Resources

EPGOAT-Specific: - Project Overview - Architecture Docs - API Documentation

Python: - Pydantic Docs - pytest Tutorial - Ruff Linter

React + TypeScript: - React Docs - TypeScript Handbook - MUI Components

Cloudflare: - Workers Docs - Supabase Database - R2 Storage

Auth0: - React SDK Guide - JWT Tokens

Stripe: - Subscription Guide - Webhooks


Getting Help

Stuck? Ask for help:

  1. Check Documentation First:
  2. Search this guide
  3. Read relevant spec documents
  4. Check code comments

  5. Slack Channels:

  6. #epgoat-dev - General development questions
  7. #epgoat-support - User-facing issues
  8. #epgoat-infra - Infrastructure/deployment

  9. GitHub Issues:

  10. Search existing issues first
  11. Create new issue with template
  12. Tag with appropriate labels

  13. Code Review:

  14. Open draft PR early for feedback
  15. Tag specific reviewers for expertise
  16. Respond to comments promptly

Office Hours: - Tuesdays 2-3pm ET - Frontend questions (Sarah) - Wednesdays 11am-12pm ET - Backend questions (Mike) - Thursdays 3-4pm ET - Infrastructure questions (Alex)


Welcome to the EPGOAT team! πŸŽ‰

Last Updated: 2025-10-30 Maintainer: Engineering Team Questions? Slack: #epgoat-dev