from django.db import models from simple_history.models import HistoricalRecords class Game(models.Model): """ Game model representing a single game between two teams. """ STATUS_CHOICES = [ ('scheduled', 'Scheduled'), ('in_progress', 'In Progress'), ('final', 'Final'), ('postponed', 'Postponed'), ('cancelled', 'Cancelled'), ('suspended', 'Suspended'), ] id = models.CharField( max_length=100, primary_key=True, help_text='Canonical ID (e.g., game_nba_2025_20251022_bos_lal)' ) sport = models.ForeignKey( 'core.Sport', on_delete=models.CASCADE, related_name='games' ) season = models.PositiveSmallIntegerField( help_text='Season start year (e.g., 2025 for 2025-26 season)' ) home_team = models.ForeignKey( 'core.Team', on_delete=models.CASCADE, related_name='home_games' ) away_team = models.ForeignKey( 'core.Team', on_delete=models.CASCADE, related_name='away_games' ) stadium = models.ForeignKey( 'core.Stadium', on_delete=models.SET_NULL, null=True, blank=True, related_name='games' ) game_date = models.DateTimeField( help_text='Game date and time (UTC)' ) game_number = models.PositiveSmallIntegerField( null=True, blank=True, help_text='Game number for doubleheaders (1 or 2)' ) home_score = models.PositiveSmallIntegerField( null=True, blank=True ) away_score = models.PositiveSmallIntegerField( null=True, blank=True ) status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='scheduled' ) is_neutral_site = models.BooleanField( default=False, help_text='Whether game is at neutral site' ) is_playoff = models.BooleanField( default=False, help_text='Whether this is a playoff game' ) playoff_round = models.CharField( max_length=50, blank=True, help_text='Playoff round (e.g., Finals, Conference Finals)' ) # Raw scraped values (for debugging/review) raw_home_team = models.CharField( max_length=200, blank=True, help_text='Original scraped home team name' ) raw_away_team = models.CharField( max_length=200, blank=True, help_text='Original scraped away team name' ) raw_stadium = models.CharField( max_length=200, blank=True, help_text='Original scraped stadium name' ) source_url = models.URLField( blank=True, help_text='URL where game was scraped from' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) # Audit trail history = HistoricalRecords() class Meta: ordering = ['-game_date', 'sport'] verbose_name = 'Game' verbose_name_plural = 'Games' indexes = [ models.Index(fields=['sport', 'season']), models.Index(fields=['sport', 'game_date']), models.Index(fields=['home_team', 'season']), models.Index(fields=['away_team', 'season']), models.Index(fields=['status']), ] def __str__(self): return f"{self.away_team.abbreviation} @ {self.home_team.abbreviation} - {self.game_date.strftime('%Y-%m-%d')}" @property def is_final(self): return self.status == 'final' @property def winner(self): """Return winning team or None if not final.""" if not self.is_final or self.home_score is None or self.away_score is None: return None if self.home_score > self.away_score: return self.home_team elif self.away_score > self.home_score: return self.away_team return None # Tie @property def score_display(self): """Return score as 'away_score - home_score' or 'TBD'.""" if self.home_score is not None and self.away_score is not None: return f"{self.away_score} - {self.home_score}" return "TBD"