wip
This commit is contained in:
@@ -11,6 +11,15 @@ from .aliases import (
|
||||
FuzzyMatch,
|
||||
ManualReviewItem,
|
||||
)
|
||||
from .sport import (
|
||||
Sport,
|
||||
LeagueStructure,
|
||||
LeagueStructureType,
|
||||
save_sports,
|
||||
load_sports,
|
||||
save_league_structures,
|
||||
load_league_structures,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Game
|
||||
@@ -32,4 +41,12 @@ __all__ = [
|
||||
"StadiumAlias",
|
||||
"FuzzyMatch",
|
||||
"ManualReviewItem",
|
||||
# Sport and League Structure
|
||||
"Sport",
|
||||
"LeagueStructure",
|
||||
"LeagueStructureType",
|
||||
"save_sports",
|
||||
"load_sports",
|
||||
"save_league_structures",
|
||||
"load_league_structures",
|
||||
]
|
||||
|
||||
157
Scripts/sportstime_parser/models/sport.py
Normal file
157
Scripts/sportstime_parser/models/sport.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""Sport and LeagueStructure data models for sportstime-parser."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
import json
|
||||
|
||||
|
||||
class LeagueStructureType(str, Enum):
|
||||
"""Type of league structure element."""
|
||||
CONFERENCE = "conference"
|
||||
DIVISION = "division"
|
||||
LEAGUE = "league"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Sport:
|
||||
"""Represents a sport with all CloudKit fields.
|
||||
|
||||
Attributes:
|
||||
id: Canonical sport ID (e.g., 'MLB', 'NBA')
|
||||
abbreviation: Sport abbreviation (e.g., 'MLB', 'NBA')
|
||||
display_name: Full display name (e.g., 'Major League Baseball')
|
||||
icon_name: SF Symbol name for the sport icon
|
||||
color_hex: Primary color as hex string (e.g., '#FF0000')
|
||||
season_start_month: Month number when season typically starts (1-12)
|
||||
season_end_month: Month number when season typically ends (1-12)
|
||||
is_active: Whether the sport is currently active/supported
|
||||
"""
|
||||
|
||||
id: str
|
||||
abbreviation: str
|
||||
display_name: str
|
||||
icon_name: str
|
||||
color_hex: str
|
||||
season_start_month: int
|
||||
season_end_month: int
|
||||
is_active: bool = True
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"abbreviation": self.abbreviation,
|
||||
"display_name": self.display_name,
|
||||
"icon_name": self.icon_name,
|
||||
"color_hex": self.color_hex,
|
||||
"season_start_month": self.season_start_month,
|
||||
"season_end_month": self.season_end_month,
|
||||
"is_active": self.is_active,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "Sport":
|
||||
"""Create a Sport from a dictionary."""
|
||||
return cls(
|
||||
id=data["id"],
|
||||
abbreviation=data["abbreviation"],
|
||||
display_name=data["display_name"],
|
||||
icon_name=data["icon_name"],
|
||||
color_hex=data["color_hex"],
|
||||
season_start_month=data["season_start_month"],
|
||||
season_end_month=data["season_end_month"],
|
||||
is_active=data.get("is_active", True),
|
||||
)
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Serialize to JSON string."""
|
||||
return json.dumps(self.to_dict(), indent=2)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> "Sport":
|
||||
"""Deserialize from JSON string."""
|
||||
return cls.from_dict(json.loads(json_str))
|
||||
|
||||
|
||||
@dataclass
|
||||
class LeagueStructure:
|
||||
"""Represents a league structure element (conference, division, etc.).
|
||||
|
||||
Attributes:
|
||||
id: Unique ID (e.g., 'nba_eastern', 'mlb_al_east')
|
||||
sport: Sport code (e.g., 'NBA', 'MLB')
|
||||
structure_type: Type of structure (conference, division, league)
|
||||
name: Full name (e.g., 'Eastern Conference', 'AL East')
|
||||
abbreviation: Optional abbreviation (e.g., 'East', 'ALE')
|
||||
parent_id: Parent structure ID (e.g., division's parent is conference)
|
||||
display_order: Order for display (0-indexed)
|
||||
"""
|
||||
|
||||
id: str
|
||||
sport: str
|
||||
structure_type: LeagueStructureType
|
||||
name: str
|
||||
abbreviation: Optional[str] = None
|
||||
parent_id: Optional[str] = None
|
||||
display_order: int = 0
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"sport": self.sport,
|
||||
"structure_type": self.structure_type.value,
|
||||
"name": self.name,
|
||||
"abbreviation": self.abbreviation,
|
||||
"parent_id": self.parent_id,
|
||||
"display_order": self.display_order,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "LeagueStructure":
|
||||
"""Create a LeagueStructure from a dictionary."""
|
||||
return cls(
|
||||
id=data["id"],
|
||||
sport=data["sport"],
|
||||
structure_type=LeagueStructureType(data["structure_type"]),
|
||||
name=data["name"],
|
||||
abbreviation=data.get("abbreviation"),
|
||||
parent_id=data.get("parent_id"),
|
||||
display_order=data.get("display_order", 0),
|
||||
)
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Serialize to JSON string."""
|
||||
return json.dumps(self.to_dict(), indent=2)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> "LeagueStructure":
|
||||
"""Deserialize from JSON string."""
|
||||
return cls.from_dict(json.loads(json_str))
|
||||
|
||||
|
||||
def save_sports(sports: list[Sport], filepath: str) -> None:
|
||||
"""Save a list of sports to a JSON file."""
|
||||
with open(filepath, "w", encoding="utf-8") as f:
|
||||
json.dump([s.to_dict() for s in sports], f, indent=2)
|
||||
|
||||
|
||||
def load_sports(filepath: str) -> list[Sport]:
|
||||
"""Load a list of sports from a JSON file."""
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return [Sport.from_dict(d) for d in data]
|
||||
|
||||
|
||||
def save_league_structures(structures: list[LeagueStructure], filepath: str) -> None:
|
||||
"""Save a list of league structures to a JSON file."""
|
||||
with open(filepath, "w", encoding="utf-8") as f:
|
||||
json.dump([s.to_dict() for s in structures], f, indent=2)
|
||||
|
||||
|
||||
def load_league_structures(filepath: str) -> list[LeagueStructure]:
|
||||
"""Load a list of league structures from a JSON file."""
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return [LeagueStructure.from_dict(d) for d in data]
|
||||
@@ -33,12 +33,20 @@ from ..utils.logging import get_logger
|
||||
|
||||
|
||||
class RecordType(str, Enum):
|
||||
"""CloudKit record types for SportsTime."""
|
||||
"""CloudKit record types for SportsTime.
|
||||
|
||||
Must match CKRecordType constants in CKModels.swift.
|
||||
"""
|
||||
GAME = "Game"
|
||||
TEAM = "Team"
|
||||
STADIUM = "Stadium"
|
||||
TEAM_ALIAS = "TeamAlias"
|
||||
STADIUM_ALIAS = "StadiumAlias"
|
||||
SPORT = "Sport"
|
||||
LEAGUE_STRUCTURE = "LeagueStructure"
|
||||
TRIP_POLL = "TripPoll"
|
||||
POLL_VOTE = "PollVote"
|
||||
ITINERARY_ITEM = "ItineraryItem"
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -2,19 +2,44 @@
|
||||
|
||||
This module compares local records with CloudKit records to determine
|
||||
what needs to be created, updated, or deleted.
|
||||
|
||||
Field names must match CKModels.swift exactly:
|
||||
- Stadium: stadiumId, canonicalId, name, city, state, location (CLLocation),
|
||||
capacity, yearOpened, imageURL, sport
|
||||
- Team: teamId, canonicalId, name, abbreviation, sport, city, stadiumCanonicalId,
|
||||
logoURL, primaryColor, secondaryColor
|
||||
- Game: gameId, canonicalId, homeTeamCanonicalId, awayTeamCanonicalId,
|
||||
stadiumCanonicalId, dateTime, sport, season, isPlayoff, broadcastInfo
|
||||
- TeamAlias: aliasId, teamCanonicalId, aliasType, aliasValue, validFrom, validUntil
|
||||
- StadiumAlias: aliasName, stadiumCanonicalId, validFrom, validUntil
|
||||
- Sport: sportId, abbreviation, displayName, iconName, colorHex,
|
||||
seasonStartMonth, seasonEndMonth, isActive
|
||||
- LeagueStructure: structureId, sport, type, name, abbreviation, parentId, displayOrder
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date
|
||||
from enum import Enum
|
||||
from typing import Any, Optional
|
||||
|
||||
from ..models.game import Game
|
||||
from ..models.team import Team
|
||||
from ..models.stadium import Stadium
|
||||
from ..models.aliases import TeamAlias, StadiumAlias, AliasType
|
||||
from ..models.sport import Sport, LeagueStructure
|
||||
from .cloudkit import CloudKitRecord, RecordType
|
||||
|
||||
|
||||
def _date_to_datetime(d: Optional[date]) -> Optional[datetime]:
|
||||
"""Convert a date to a datetime at midnight UTC.
|
||||
|
||||
CloudKit TIMESTAMP fields require datetime, not date.
|
||||
"""
|
||||
if d is None:
|
||||
return None
|
||||
return datetime(d.year, d.month, d.day, 0, 0, 0)
|
||||
|
||||
|
||||
class DiffAction(str, Enum):
|
||||
"""Action to take for a record."""
|
||||
CREATE = "create"
|
||||
@@ -98,24 +123,45 @@ class DiffResult:
|
||||
|
||||
|
||||
class RecordDiffer:
|
||||
"""Compares local records with CloudKit records."""
|
||||
"""Compares local records with CloudKit records.
|
||||
|
||||
# Fields to compare for each record type
|
||||
Field names must match CKModels.swift field keys exactly (camelCase).
|
||||
"""
|
||||
|
||||
# Fields to compare for each record type (matching CKModels.swift keys)
|
||||
GAME_FIELDS = [
|
||||
"sport", "season", "home_team_id", "away_team_id", "stadium_id",
|
||||
"game_date", "game_number", "home_score", "away_score", "status",
|
||||
"gameId", "canonicalId", "sport", "season", "dateTime",
|
||||
"homeTeamCanonicalId", "awayTeamCanonicalId", "stadiumCanonicalId",
|
||||
"isPlayoff", "broadcastInfo",
|
||||
]
|
||||
|
||||
TEAM_FIELDS = [
|
||||
"sport", "city", "name", "full_name", "abbreviation",
|
||||
"conference", "division", "primary_color", "secondary_color",
|
||||
"logo_url", "stadium_id",
|
||||
"teamId", "canonicalId", "sport", "city", "name", "abbreviation",
|
||||
"stadiumCanonicalId", "logoURL", "primaryColor", "secondaryColor",
|
||||
]
|
||||
|
||||
STADIUM_FIELDS = [
|
||||
"sport", "name", "city", "state", "country",
|
||||
"latitude", "longitude", "capacity", "surface",
|
||||
"roof_type", "opened_year", "image_url", "timezone",
|
||||
"stadiumId", "canonicalId", "sport", "name", "city", "state",
|
||||
"location", "capacity", "yearOpened", "imageURL",
|
||||
]
|
||||
|
||||
TEAM_ALIAS_FIELDS = [
|
||||
"aliasId", "teamCanonicalId", "aliasType", "aliasValue",
|
||||
"validFrom", "validUntil",
|
||||
]
|
||||
|
||||
STADIUM_ALIAS_FIELDS = [
|
||||
"aliasName", "stadiumCanonicalId", "validFrom", "validUntil",
|
||||
]
|
||||
|
||||
SPORT_FIELDS = [
|
||||
"sportId", "abbreviation", "displayName", "iconName",
|
||||
"colorHex", "seasonStartMonth", "seasonEndMonth", "isActive",
|
||||
]
|
||||
|
||||
LEAGUE_STRUCTURE_FIELDS = [
|
||||
"structureId", "sport", "type", "name", "abbreviation",
|
||||
"parentId", "displayOrder",
|
||||
]
|
||||
|
||||
def diff_games(
|
||||
@@ -184,6 +230,94 @@ class RecordDiffer:
|
||||
self.STADIUM_FIELDS,
|
||||
)
|
||||
|
||||
def diff_team_aliases(
|
||||
self,
|
||||
local_aliases: list[TeamAlias],
|
||||
remote_records: list[dict],
|
||||
) -> DiffResult:
|
||||
"""Diff local team aliases against remote CloudKit records.
|
||||
|
||||
Args:
|
||||
local_aliases: List of local TeamAlias objects
|
||||
remote_records: List of remote record dictionaries
|
||||
|
||||
Returns:
|
||||
DiffResult with creates, updates, deletes
|
||||
"""
|
||||
local_records = [self._team_alias_to_record(a) for a in local_aliases]
|
||||
return self._diff_records(
|
||||
local_records,
|
||||
remote_records,
|
||||
RecordType.TEAM_ALIAS,
|
||||
self.TEAM_ALIAS_FIELDS,
|
||||
)
|
||||
|
||||
def diff_stadium_aliases(
|
||||
self,
|
||||
local_aliases: list[StadiumAlias],
|
||||
remote_records: list[dict],
|
||||
) -> DiffResult:
|
||||
"""Diff local stadium aliases against remote CloudKit records.
|
||||
|
||||
Args:
|
||||
local_aliases: List of local StadiumAlias objects
|
||||
remote_records: List of remote record dictionaries
|
||||
|
||||
Returns:
|
||||
DiffResult with creates, updates, deletes
|
||||
"""
|
||||
local_records = [self._stadium_alias_to_record(a) for a in local_aliases]
|
||||
return self._diff_records(
|
||||
local_records,
|
||||
remote_records,
|
||||
RecordType.STADIUM_ALIAS,
|
||||
self.STADIUM_ALIAS_FIELDS,
|
||||
)
|
||||
|
||||
def diff_sports(
|
||||
self,
|
||||
local_sports: list[Sport],
|
||||
remote_records: list[dict],
|
||||
) -> DiffResult:
|
||||
"""Diff local sports against remote CloudKit records.
|
||||
|
||||
Args:
|
||||
local_sports: List of local Sport objects
|
||||
remote_records: List of remote record dictionaries
|
||||
|
||||
Returns:
|
||||
DiffResult with creates, updates, deletes
|
||||
"""
|
||||
local_records = [self._sport_to_record(s) for s in local_sports]
|
||||
return self._diff_records(
|
||||
local_records,
|
||||
remote_records,
|
||||
RecordType.SPORT,
|
||||
self.SPORT_FIELDS,
|
||||
)
|
||||
|
||||
def diff_league_structures(
|
||||
self,
|
||||
local_structures: list[LeagueStructure],
|
||||
remote_records: list[dict],
|
||||
) -> DiffResult:
|
||||
"""Diff local league structures against remote CloudKit records.
|
||||
|
||||
Args:
|
||||
local_structures: List of local LeagueStructure objects
|
||||
remote_records: List of remote record dictionaries
|
||||
|
||||
Returns:
|
||||
DiffResult with creates, updates, deletes
|
||||
"""
|
||||
local_records = [self._league_structure_to_record(s) for s in local_structures]
|
||||
return self._diff_records(
|
||||
local_records,
|
||||
remote_records,
|
||||
RecordType.LEAGUE_STRUCTURE,
|
||||
self.LEAGUE_STRUCTURE_FIELDS,
|
||||
)
|
||||
|
||||
def _diff_records(
|
||||
self,
|
||||
local_records: list[CloudKitRecord],
|
||||
@@ -337,63 +471,206 @@ class RecordDiffer:
|
||||
return value
|
||||
|
||||
def _game_to_record(self, game: Game) -> CloudKitRecord:
|
||||
"""Convert a Game to a CloudKitRecord."""
|
||||
"""Convert a Game to a CloudKitRecord.
|
||||
|
||||
Field names match CKGame keys in CKModels.swift:
|
||||
- gameId, canonicalId: Unique identifiers
|
||||
- homeTeamCanonicalId, awayTeamCanonicalId, stadiumCanonicalId: References as strings
|
||||
- dateTime: Game time as datetime (will be converted to TIMESTAMP)
|
||||
- sport: Sport code uppercase (e.g., "MLB")
|
||||
- season: Season string (e.g., "2025-26" or "2026")
|
||||
- isPlayoff: Boolean as int (1 or 0)
|
||||
- broadcastInfo: Optional broadcast network string
|
||||
"""
|
||||
# Format season as string
|
||||
sport_lower = game.sport.lower()
|
||||
if sport_lower in ("nba", "nhl"):
|
||||
season_str = f"{game.season}-{str(game.season + 1)[-2:]}"
|
||||
else:
|
||||
season_str = str(game.season)
|
||||
|
||||
return CloudKitRecord(
|
||||
record_name=game.id,
|
||||
record_type=RecordType.GAME,
|
||||
fields={
|
||||
"sport": game.sport,
|
||||
"season": game.season,
|
||||
"home_team_id": game.home_team_id,
|
||||
"away_team_id": game.away_team_id,
|
||||
"stadium_id": game.stadium_id,
|
||||
"game_date": game.game_date,
|
||||
"game_number": game.game_number,
|
||||
"home_score": game.home_score,
|
||||
"away_score": game.away_score,
|
||||
"status": game.status,
|
||||
"gameId": game.id,
|
||||
"canonicalId": game.id,
|
||||
"sport": game.sport.upper(),
|
||||
"season": season_str,
|
||||
"dateTime": game.game_date,
|
||||
"homeTeamCanonicalId": game.home_team_id,
|
||||
"awayTeamCanonicalId": game.away_team_id,
|
||||
"stadiumCanonicalId": game.stadium_id,
|
||||
"isPlayoff": False, # Default, can be overridden
|
||||
"broadcastInfo": None, # Default, can be overridden
|
||||
},
|
||||
)
|
||||
|
||||
def _team_to_record(self, team: Team) -> CloudKitRecord:
|
||||
"""Convert a Team to a CloudKitRecord."""
|
||||
"""Convert a Team to a CloudKitRecord.
|
||||
|
||||
Field names match CKTeam keys in CKModels.swift:
|
||||
- teamId, canonicalId: Unique identifiers
|
||||
- name, abbreviation, city: Team info
|
||||
- sport: Sport code uppercase (e.g., "NBA")
|
||||
- stadiumCanonicalId: Home stadium canonical ID string
|
||||
- logoURL: URL string for team logo
|
||||
- primaryColor, secondaryColor: Hex color strings
|
||||
"""
|
||||
return CloudKitRecord(
|
||||
record_name=team.id,
|
||||
record_type=RecordType.TEAM,
|
||||
fields={
|
||||
"sport": team.sport,
|
||||
"teamId": team.id,
|
||||
"canonicalId": team.id,
|
||||
"sport": team.sport.upper(),
|
||||
"city": team.city,
|
||||
"name": team.name,
|
||||
"full_name": team.full_name,
|
||||
"abbreviation": team.abbreviation,
|
||||
"conference": team.conference,
|
||||
"division": team.division,
|
||||
"primary_color": team.primary_color,
|
||||
"secondary_color": team.secondary_color,
|
||||
"logo_url": team.logo_url,
|
||||
"stadium_id": team.stadium_id,
|
||||
"stadiumCanonicalId": team.stadium_id,
|
||||
"logoURL": team.logo_url,
|
||||
"primaryColor": team.primary_color,
|
||||
"secondaryColor": team.secondary_color,
|
||||
},
|
||||
)
|
||||
|
||||
def _stadium_to_record(self, stadium: Stadium) -> CloudKitRecord:
|
||||
"""Convert a Stadium to a CloudKitRecord."""
|
||||
"""Convert a Stadium to a CloudKitRecord.
|
||||
|
||||
Field names match CKStadium keys in CKModels.swift:
|
||||
- stadiumId, canonicalId: Unique identifiers
|
||||
- name, city, state: Location info
|
||||
- location: CloudKit LOCATION type with latitude/longitude
|
||||
- capacity: Seating capacity as int
|
||||
- yearOpened: Year opened as int
|
||||
- imageURL: URL string for stadium image
|
||||
- sport: Sport code uppercase (e.g., "MLB")
|
||||
"""
|
||||
return CloudKitRecord(
|
||||
record_name=stadium.id,
|
||||
record_type=RecordType.STADIUM,
|
||||
fields={
|
||||
"sport": stadium.sport,
|
||||
"stadiumId": stadium.id,
|
||||
"canonicalId": stadium.id,
|
||||
"sport": stadium.sport.upper(),
|
||||
"name": stadium.name,
|
||||
"city": stadium.city,
|
||||
"state": stadium.state,
|
||||
"country": stadium.country,
|
||||
"latitude": stadium.latitude,
|
||||
"longitude": stadium.longitude,
|
||||
# CloudKit LOCATION type expects dict with latitude/longitude
|
||||
"location": {
|
||||
"latitude": stadium.latitude,
|
||||
"longitude": stadium.longitude,
|
||||
},
|
||||
"capacity": stadium.capacity,
|
||||
"surface": stadium.surface,
|
||||
"roof_type": stadium.roof_type,
|
||||
"opened_year": stadium.opened_year,
|
||||
"image_url": stadium.image_url,
|
||||
"timezone": stadium.timezone,
|
||||
"yearOpened": stadium.opened_year,
|
||||
"imageURL": stadium.image_url,
|
||||
},
|
||||
)
|
||||
|
||||
def _team_alias_to_record(self, alias: TeamAlias) -> CloudKitRecord:
|
||||
"""Convert a TeamAlias to a CloudKitRecord.
|
||||
|
||||
Field names match CKTeamAlias keys in CKModels.swift:
|
||||
- aliasId: Unique identifier
|
||||
- teamCanonicalId: The canonical team this alias resolves to
|
||||
- aliasType: Type of alias ("abbreviation", "name", "city")
|
||||
- aliasValue: The alias value to match
|
||||
- validFrom, validUntil: Optional date bounds
|
||||
- schemaVersion, lastModified: Versioning fields
|
||||
"""
|
||||
return CloudKitRecord(
|
||||
record_name=alias.id,
|
||||
record_type=RecordType.TEAM_ALIAS,
|
||||
fields={
|
||||
"aliasId": alias.id,
|
||||
"teamCanonicalId": alias.team_canonical_id,
|
||||
"aliasType": alias.alias_type.value,
|
||||
"aliasValue": alias.alias_value,
|
||||
"validFrom": _date_to_datetime(alias.valid_from),
|
||||
"validUntil": _date_to_datetime(alias.valid_until),
|
||||
"schemaVersion": 1,
|
||||
"lastModified": datetime.utcnow(),
|
||||
},
|
||||
)
|
||||
|
||||
def _stadium_alias_to_record(self, alias: StadiumAlias) -> CloudKitRecord:
|
||||
"""Convert a StadiumAlias to a CloudKitRecord.
|
||||
|
||||
Field names match CKStadiumAlias keys in CKModels.swift:
|
||||
- aliasName: The alias name (used as record name/primary key)
|
||||
- stadiumCanonicalId: The canonical stadium this alias resolves to
|
||||
- validFrom, validUntil: Optional date bounds
|
||||
- schemaVersion, lastModified: Versioning fields
|
||||
"""
|
||||
return CloudKitRecord(
|
||||
record_name=alias.alias_name.lower(),
|
||||
record_type=RecordType.STADIUM_ALIAS,
|
||||
fields={
|
||||
"aliasName": alias.alias_name.lower(),
|
||||
"stadiumCanonicalId": alias.stadium_canonical_id,
|
||||
"validFrom": _date_to_datetime(alias.valid_from),
|
||||
"validUntil": _date_to_datetime(alias.valid_until),
|
||||
"schemaVersion": 1,
|
||||
"lastModified": datetime.utcnow(),
|
||||
},
|
||||
)
|
||||
|
||||
def _sport_to_record(self, sport: Sport) -> CloudKitRecord:
|
||||
"""Convert a Sport to a CloudKitRecord.
|
||||
|
||||
Field names match CKSport keys in CKModels.swift:
|
||||
- sportId: Unique identifier (e.g., 'MLB', 'NBA')
|
||||
- abbreviation: Sport abbreviation
|
||||
- displayName: Full display name
|
||||
- iconName: SF Symbol name
|
||||
- colorHex: Primary color as hex string
|
||||
- seasonStartMonth, seasonEndMonth: Season boundary months (1-12)
|
||||
- isActive: Whether sport is currently supported
|
||||
- schemaVersion, lastModified: Versioning fields
|
||||
"""
|
||||
return CloudKitRecord(
|
||||
record_name=sport.id,
|
||||
record_type=RecordType.SPORT,
|
||||
fields={
|
||||
"sportId": sport.id,
|
||||
"abbreviation": sport.abbreviation,
|
||||
"displayName": sport.display_name,
|
||||
"iconName": sport.icon_name,
|
||||
"colorHex": sport.color_hex,
|
||||
"seasonStartMonth": sport.season_start_month,
|
||||
"seasonEndMonth": sport.season_end_month,
|
||||
"isActive": sport.is_active,
|
||||
"schemaVersion": 1,
|
||||
"lastModified": datetime.utcnow(),
|
||||
},
|
||||
)
|
||||
|
||||
def _league_structure_to_record(self, structure: LeagueStructure) -> CloudKitRecord:
|
||||
"""Convert a LeagueStructure to a CloudKitRecord.
|
||||
|
||||
Field names match CKLeagueStructure keys in CKModels.swift:
|
||||
- structureId: Unique identifier (e.g., 'nba_eastern', 'mlb_al_east')
|
||||
- sport: Sport code (e.g., 'NBA', 'MLB')
|
||||
- type: Structure type ('conference', 'division', 'league')
|
||||
- name: Full name
|
||||
- abbreviation: Optional abbreviation
|
||||
- parentId: Parent structure ID (e.g., division's parent is conference)
|
||||
- displayOrder: Order for display (0-indexed)
|
||||
- schemaVersion, lastModified: Versioning fields
|
||||
"""
|
||||
return CloudKitRecord(
|
||||
record_name=structure.id,
|
||||
record_type=RecordType.LEAGUE_STRUCTURE,
|
||||
fields={
|
||||
"structureId": structure.id,
|
||||
"sport": structure.sport.upper(),
|
||||
"type": structure.structure_type.value,
|
||||
"name": structure.name,
|
||||
"abbreviation": structure.abbreviation,
|
||||
"parentId": structure.parent_id,
|
||||
"displayOrder": structure.display_order,
|
||||
"schemaVersion": 1,
|
||||
"lastModified": datetime.utcnow(),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -423,3 +700,39 @@ def stadium_to_cloudkit_record(stadium: Stadium) -> CloudKitRecord:
|
||||
"""
|
||||
differ = RecordDiffer()
|
||||
return differ._stadium_to_record(stadium)
|
||||
|
||||
|
||||
def team_alias_to_cloudkit_record(alias: TeamAlias) -> CloudKitRecord:
|
||||
"""Convert a TeamAlias to a CloudKitRecord.
|
||||
|
||||
Convenience function for external use.
|
||||
"""
|
||||
differ = RecordDiffer()
|
||||
return differ._team_alias_to_record(alias)
|
||||
|
||||
|
||||
def stadium_alias_to_cloudkit_record(alias: StadiumAlias) -> CloudKitRecord:
|
||||
"""Convert a StadiumAlias to a CloudKitRecord.
|
||||
|
||||
Convenience function for external use.
|
||||
"""
|
||||
differ = RecordDiffer()
|
||||
return differ._stadium_alias_to_record(alias)
|
||||
|
||||
|
||||
def sport_to_cloudkit_record(sport: Sport) -> CloudKitRecord:
|
||||
"""Convert a Sport to a CloudKitRecord.
|
||||
|
||||
Convenience function for external use.
|
||||
"""
|
||||
differ = RecordDiffer()
|
||||
return differ._sport_to_record(sport)
|
||||
|
||||
|
||||
def league_structure_to_cloudkit_record(structure: LeagueStructure) -> CloudKitRecord:
|
||||
"""Convert a LeagueStructure to a CloudKitRecord.
|
||||
|
||||
Convenience function for external use.
|
||||
"""
|
||||
differ = RecordDiffer()
|
||||
return differ._league_structure_to_record(structure)
|
||||
|
||||
Reference in New Issue
Block a user