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>
145 lines
4.0 KiB
Python
145 lines
4.0 KiB
Python
"""Database-aware alias loaders for team and stadium resolution.
|
|
|
|
These loaders check the Django TeamAlias and StadiumAlias models
|
|
in addition to the hardcoded mappings, allowing aliases to be
|
|
managed via the admin interface.
|
|
"""
|
|
|
|
from datetime import date
|
|
from typing import Optional
|
|
|
|
|
|
class DatabaseTeamAliasLoader:
|
|
"""Load team aliases from the Django database.
|
|
|
|
Checks the core.TeamAlias model for alias mappings,
|
|
supporting date-aware lookups for historical names.
|
|
"""
|
|
|
|
def resolve(
|
|
self,
|
|
value: str,
|
|
sport_code: str,
|
|
check_date: Optional[date] = None,
|
|
) -> Optional[str]:
|
|
"""Resolve an alias value to a canonical team ID.
|
|
|
|
Args:
|
|
value: Alias value to look up (case-insensitive)
|
|
sport_code: Sport code to filter by
|
|
check_date: Date to check validity (None = current date)
|
|
|
|
Returns:
|
|
Canonical team ID if found, None otherwise
|
|
"""
|
|
from core.models import TeamAlias
|
|
from django.db.models import Q
|
|
|
|
if check_date is None:
|
|
check_date = date.today()
|
|
|
|
value_lower = value.lower().strip()
|
|
|
|
# Query aliases matching the value and sport
|
|
aliases = TeamAlias.objects.filter(
|
|
alias__iexact=value_lower,
|
|
team__sport__code=sport_code,
|
|
).select_related('team')
|
|
|
|
for alias in aliases:
|
|
if alias.is_valid_for_date(check_date):
|
|
return alias.team.id
|
|
|
|
return None
|
|
|
|
def get_aliases_for_team(
|
|
self,
|
|
team_id: str,
|
|
check_date: Optional[date] = None,
|
|
) -> list:
|
|
"""Get all aliases for a team.
|
|
|
|
Args:
|
|
team_id: Team ID
|
|
check_date: Date to filter by (None = all aliases)
|
|
|
|
Returns:
|
|
List of TeamAlias objects
|
|
"""
|
|
from core.models import TeamAlias
|
|
|
|
aliases = TeamAlias.objects.filter(team_id=team_id)
|
|
|
|
if check_date:
|
|
result = []
|
|
for alias in aliases:
|
|
if alias.is_valid_for_date(check_date):
|
|
result.append(alias)
|
|
return result
|
|
|
|
return list(aliases)
|
|
|
|
|
|
class DatabaseStadiumAliasLoader:
|
|
"""Load stadium aliases from the Django database.
|
|
|
|
Checks the core.StadiumAlias model for alias mappings,
|
|
supporting date-aware lookups for naming rights changes.
|
|
"""
|
|
|
|
def resolve(
|
|
self,
|
|
name: str,
|
|
sport_code: str,
|
|
check_date: Optional[date] = None,
|
|
) -> Optional[str]:
|
|
"""Resolve a stadium name to a canonical stadium ID.
|
|
|
|
Args:
|
|
name: Stadium name to look up (case-insensitive)
|
|
sport_code: Sport code to filter by
|
|
check_date: Date to check validity (None = current date)
|
|
|
|
Returns:
|
|
Canonical stadium ID if found, None otherwise
|
|
"""
|
|
from core.models import StadiumAlias
|
|
|
|
if check_date is None:
|
|
check_date = date.today()
|
|
|
|
name_lower = name.lower().strip()
|
|
|
|
# Query aliases matching the name and sport
|
|
aliases = StadiumAlias.objects.filter(
|
|
alias__iexact=name_lower,
|
|
stadium__sport__code=sport_code,
|
|
).select_related('stadium')
|
|
|
|
for alias in aliases:
|
|
if alias.is_valid_for_date(check_date):
|
|
return alias.stadium.id
|
|
|
|
return None
|
|
|
|
|
|
# Global instances
|
|
_db_team_loader: Optional[DatabaseTeamAliasLoader] = None
|
|
_db_stadium_loader: Optional[DatabaseStadiumAliasLoader] = None
|
|
|
|
|
|
def get_db_team_alias_loader() -> DatabaseTeamAliasLoader:
|
|
"""Get the database team alias loader."""
|
|
global _db_team_loader
|
|
if _db_team_loader is None:
|
|
_db_team_loader = DatabaseTeamAliasLoader()
|
|
return _db_team_loader
|
|
|
|
|
|
def get_db_stadium_alias_loader() -> DatabaseStadiumAliasLoader:
|
|
"""Get the database stadium alias loader."""
|
|
global _db_stadium_loader
|
|
if _db_stadium_loader is None:
|
|
_db_stadium_loader = DatabaseStadiumAliasLoader()
|
|
return _db_stadium_loader
|