""" Data models and enums for Serien-Checker database """ from enum import Enum from typing import Optional, Union from dataclasses import dataclass from datetime import datetime def _safe_parse_datetime(value: Union[str, datetime, None]) -> Optional[datetime]: """ Safely parse a datetime value that might be a string, datetime object, or None Args: value: String ISO format, datetime object, or None Returns: datetime object or None """ if value is None: return None if isinstance(value, datetime): return value if isinstance(value, str): return datetime.fromisoformat(value) return None class SeasonType(Enum): """Types of seasons""" NORMAL = "normal" SPECIALS = "specials" EXTRAS = "extras" BEST_OF = "best_of" YEAR_BASED = "year_based" class DatePreference(Enum): """Preferred date types for episode releases""" DE_FIRST = "de_first" # Deutsche Erstausstrahlung (frühestes deutsches Datum) DE_TV = "de_tv" # Deutsche TV-Premiere DE_STREAMING = "de_streaming" # Deutsche Streaming-Premiere DE_HOME_MEDIA = "de_home_media" # Deutsche Home-Media-Premiere DE_SYNC = "de_sync" # Deutsche Synchronfassung ORIGINAL = "original" # Erstausstrahlung (Original) @dataclass class Series: """Represents a TV series""" id: Optional[int] title: str url: str date_preference: DatePreference last_updated: Optional[datetime] = None @classmethod def from_row(cls, row: tuple) -> 'Series': """Create Series from database row""" return cls( id=row[0], title=row[1], url=row[2], date_preference=DatePreference(row[3]), last_updated=datetime.fromisoformat(row[4]) if row[4] else None ) @dataclass class Season: """Represents a season of a series""" id: Optional[int] series_id: int name: str season_type: SeasonType sort_order: int @classmethod def from_row(cls, row: tuple) -> 'Season': """Create Season from database row""" return cls( id=row[0], series_id=row[1], name=row[2], season_type=SeasonType(row[3]), sort_order=row[4] ) @dataclass class Episode: """Represents an episode""" id: Optional[int] season_id: int episode_code: str # e.g., "01", "01a", "01b" title: str # Episode number (overall series number from fernsehserien.de) episode_number: Optional[int] = None # Episode ID from fernsehserien.de (e.g., "1828679") episode_id: Optional[str] = None # All available air dates date_de_tv: Optional[datetime] = None date_de_streaming: Optional[datetime] = None date_de_home_media: Optional[datetime] = None date_de_sync: Optional[datetime] = None date_original: Optional[datetime] = None # Comparison date (based on series preference) comparison_date: Optional[datetime] = None @classmethod def from_row(cls, row: tuple) -> 'Episode': """Create Episode from database row""" # Handle old (9/10/11 fields) and new (12 fields) schema if len(row) >= 12: # New schema with episode_number, episode_id, and date_de_home_media return cls( id=row[0], season_id=row[1], episode_number=row[2], episode_code=row[3], title=row[4], episode_id=row[5], date_de_tv=_safe_parse_datetime(row[6]), date_de_streaming=_safe_parse_datetime(row[7]), date_de_home_media=_safe_parse_datetime(row[8]), date_de_sync=_safe_parse_datetime(row[9]), date_original=_safe_parse_datetime(row[10]), comparison_date=_safe_parse_datetime(row[11]) ) elif len(row) >= 11: # Schema with episode_number and date_de_home_media but no episode_id return cls( id=row[0], season_id=row[1], episode_number=row[2], episode_code=row[3], title=row[4], episode_id=None, date_de_tv=_safe_parse_datetime(row[5]), date_de_streaming=_safe_parse_datetime(row[6]), date_de_home_media=_safe_parse_datetime(row[7]), date_de_sync=_safe_parse_datetime(row[8]), date_original=_safe_parse_datetime(row[9]), comparison_date=_safe_parse_datetime(row[10]) ) elif len(row) >= 10: # Schema with episode_number but without date_de_home_media and episode_id return cls( id=row[0], season_id=row[1], episode_number=row[2], episode_code=row[3], title=row[4], episode_id=None, date_de_tv=_safe_parse_datetime(row[5]), date_de_streaming=_safe_parse_datetime(row[6]), date_de_home_media=None, date_de_sync=_safe_parse_datetime(row[7]), date_original=_safe_parse_datetime(row[8]), comparison_date=_safe_parse_datetime(row[9]) ) else: # Old schema without episode_number, episode_id, and date_de_home_media return cls( id=row[0], season_id=row[1], episode_number=None, episode_code=row[2], title=row[3], episode_id=None, date_de_tv=_safe_parse_datetime(row[4]), date_de_streaming=_safe_parse_datetime(row[5]), date_de_home_media=None, date_de_sync=_safe_parse_datetime(row[6]), date_original=_safe_parse_datetime(row[7]), comparison_date=_safe_parse_datetime(row[8]) ) def calculate_comparison_date(self, preference: DatePreference) -> Optional[datetime]: """ Calculate the comparison date based on preference with fallback logic """ # Special case: DE_FIRST = earliest German date if preference == DatePreference.DE_FIRST: german_dates = [ d for d in [self.date_de_tv, self.date_de_streaming, self.date_de_home_media, self.date_de_sync] if d is not None ] if german_dates: return min(german_dates) # No fallback - return None if no German date available return None # Priority order based on preference # For German preferences: only use German dates, no fallback to original # For ORIGINAL preference: only use original date # Priority for German dates: TV → Streaming → Home-Media → Sync priority_map = { DatePreference.DE_TV: [self.date_de_tv, self.date_de_streaming, self.date_de_home_media, self.date_de_sync], DatePreference.DE_STREAMING: [self.date_de_streaming, self.date_de_tv, self.date_de_home_media, self.date_de_sync], DatePreference.DE_HOME_MEDIA: [self.date_de_home_media, self.date_de_streaming, self.date_de_tv, self.date_de_sync], DatePreference.DE_SYNC: [self.date_de_sync, self.date_de_home_media, self.date_de_streaming, self.date_de_tv], DatePreference.ORIGINAL: [self.date_original] } dates = priority_map.get(preference, []) for date in dates: if date is not None: return date return None @dataclass class Settings: """Application settings""" id: Optional[int] key: str value: str @classmethod def from_row(cls, row: tuple) -> 'Settings': """Create Settings from database row""" return cls( id=row[0], key=row[1], value=row[2] )