feat: add Django web app, CloudKit sync, dashboard, and game_datetime_utc export

Adds the full Django application layer on top of sportstime_parser:
- core: Sport, Team, Stadium, Game models with aliases and league structure
- scraper: orchestration engine, adapter, job management, Celery tasks
- cloudkit: CloudKit sync client, sync state tracking, sync jobs
- dashboard: staff dashboard for monitoring scrapers, sync, review queue
- notifications: email reports for scrape/sync results
- Docker setup for deployment (Dockerfile, docker-compose, entrypoint)

Game exports now use game_datetime_utc (ISO 8601 UTC) instead of
venue-local date+time strings, matching the canonical format used
by the iOS app.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-19 14:04:27 -06:00
parent 4353d5943c
commit 63acf7accb
114 changed files with 13070 additions and 887 deletions

88
core/models/team.py Normal file
View File

@@ -0,0 +1,88 @@
from django.db import models
from simple_history.models import HistoricalRecords
class Team(models.Model):
"""
Team model with canonical identifiers.
"""
id = models.CharField(
max_length=50,
primary_key=True,
help_text='Canonical ID (e.g., team_nba_lal)'
)
sport = models.ForeignKey(
'core.Sport',
on_delete=models.CASCADE,
related_name='teams'
)
division = models.ForeignKey(
'core.Division',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='teams'
)
city = models.CharField(
max_length=100,
help_text='Team city (e.g., Los Angeles)'
)
name = models.CharField(
max_length=100,
help_text='Team name (e.g., Lakers)'
)
full_name = models.CharField(
max_length=200,
help_text='Full team name (e.g., Los Angeles Lakers)'
)
abbreviation = models.CharField(
max_length=10,
help_text='Team abbreviation (e.g., LAL)'
)
home_stadium = models.ForeignKey(
'core.Stadium',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='home_teams'
)
primary_color = models.CharField(
max_length=7,
blank=True,
help_text='Primary color hex (e.g., #552583)'
)
secondary_color = models.CharField(
max_length=7,
blank=True,
help_text='Secondary color hex (e.g., #FDB927)'
)
logo_url = models.URLField(
blank=True,
help_text='URL to team logo'
)
is_active = models.BooleanField(
default=True,
help_text='Whether team is currently active'
)
# Metadata
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# Audit trail
history = HistoricalRecords()
class Meta:
ordering = ['sport', 'city', 'name']
verbose_name = 'Team'
verbose_name_plural = 'Teams'
def __str__(self):
return self.full_name
@property
def conference(self):
"""Return team's conference via division."""
if self.division:
return self.division.conference
return None