Files
HomeAssistance/custom_components/teamtracker/parse_cflscoreboard.py
T
2026-06-05 22:34:31 -04:00

339 lines
12 KiB
Python

""" Parse CFL Scoreboard JSON response """
from __future__ import annotations
from datetime import datetime, timedelta
import logging
import re
from typing import TYPE_CHECKING
import arrow
from .const import DEFAULT_LOGO
from .models import TeamTrackerValues
from .parser_base import BaseSportParser
from .utils import get_value
if TYPE_CHECKING:
from .coordinator import TeamTrackerCoordinator
_LOGGER = logging.getLogger(__name__)
DEFAULT_COLORS = ["#D3D3D3", "#A9A9A9"]
class CflScoreboardParser(BaseSportParser):
"""Class to parse responses in ESPN JSON format."""
def __init__(self, coordinator: TeamTrackerCoordinator) -> None:
# Define the attributes that must be available on all providers
super().__init__(coordinator)
self._lang = ""
self._search_key = ""
self._stop_flag = False
self._found_competitor = False
self._event_state = "NOT_FOUND"
self._prev_values: TeamTrackerValues
self._team_side = ""
self._opponent_side = ""
#
# initialize_values()
# Set sensor attributes that do not rely on the API
#
def initialize_sensor_values(self, provider_response) -> bool:
rc = super().initialize_sensor_values(provider_response)
self._values.sport = "football"
return rc
def setup(self,
sensor_name: str,
sport_path: str,
league_path: str,
league_id: str,
team_id: str,
) -> bool:
self._sensor_name = sensor_name
self._sport_path = sport_path
self._league_path = league_path
self._league_id = league_id
self._default_logo = DEFAULT_LOGO
self._team_id = team_id.upper()
return True
def parse_response(
self,
provider_response,
lang: str
) -> TeamTrackerValues:
"""Loop throught the json data returned by the API to find the right event and set values"""
rc = self.initialize_sensor_values(provider_response)
if rc is False:
return self._values
data = provider_response["data"]
self._lang = lang
self._search_key = self._team_id
weekly_schedule = self._get_current_schedule(data)
week_name = get_value(weekly_schedule, "name", default="")
first_date_str = get_value(weekly_schedule, "startDate", default="")
last_date_str = get_value(weekly_schedule, "endDate", default="")
tournaments = get_value(weekly_schedule, "tournaments", default=[])
tournament = self._get_tournament(tournaments, self._team_id)
if tournament:
rc = self._set_values(weekly_schedule, tournament)
if rc is False:
_LOGGER.debug(
"%s: Error parsing response for '%s' for CFL '%s'",
self._sensor_name,
self._search_key,
week_name
)
else:
first_date = datetime.fromisoformat(str(first_date_str)).replace(tzinfo=None)
last_date = datetime.fromisoformat(str(last_date_str)).replace(tzinfo=None)
self._values.api_message = (
"No competition scheduled for '"
+ str(self._values.team_abbr)
+ "' in CFL '"
+ week_name
+ "' between "
+ first_date.strftime("%Y-%m-%dT%H:%MZ")
+ " and "
+ last_date.strftime("%Y-%m-%dT%H:%MZ")
)
_LOGGER.debug(
"%s: No competitor information '%s' returned by API for %s",
self._sensor_name,
self._search_key,
week_name
)
rc = self.finalize_sensor_values(provider_response)
return self._values
#
# _get_current_schedule()
#
def _get_current_schedule(self, rounds) -> dict:
"""Return the tournaments for the current active or recently completed round."""
if not rounds:
return {}
# Get the current time in ISO format to match the API's timezone-aware strings
# The API uses '+00:00', which aligns with UTC
now = datetime.utcnow()
# Calculate the cutoff timestamp (24 hours ago) in ISO format
cutoff_time = now - timedelta(hours=24)
cutoff_iso = cutoff_time.isoformat() + "+00:00"
r = {}
for r in rounds:
status = r.get("status", "").lower()
end_date = r.get("endDate", "")
# 1. Condition: The round is explicitly not complete (e.g., 'playing' or 'scheduled')
if status != "complete":
return r
# 2. Condition: The round is complete, but it ended within the last 24 hours
if end_date and end_date >= cutoff_iso:
return r
# 3. Fallback Condition: If all rounds are complete and past the 24h window,
# return the very last round item in the list so the sensor doesn't go blank.
return r[-1]
#
# _get_tournament()
#
def _get_tournament(self,
tournaments,
search_key,
) -> dict:
"""Check if there is a match on wildcard, team_abbreviation, event_name, or athlete_name"""
for t in tournaments:
for side in ("home", "away"):
self._team_side = side
self._opponent_side = "away" if side == "home" else "home"
if search_key == "*":
_LOGGER.debug(
"%s: Found competitor using wildcard '%s'; parsing data.",
self._sensor_name,
search_key,
)
return t
team_id = str(get_value(
t, f"{side}Squad", "id", default=""
))
if search_key == team_id:
_LOGGER.debug(
"%s: Found competition for '%s' in team id; parsing data.",
self._sensor_name,
search_key,
)
return t
team_abbr = get_value(
t, f"{side}Squad", "shortName", default=""
)
if search_key == team_abbr:
_LOGGER.debug(
"%s: Found competition for '%s' in team shortName; parsing data.",
self._sensor_name,
search_key,
)
return t
team_name = str(get_value(
t, f"{side}Squad", "name", default=""
)).upper()
try:
if team_name and re.fullmatch(search_key, team_name):
_LOGGER.debug(
"%s: Found competition for regex '%s' in team name; parsing data.",
self._sensor_name,
search_key,
)
return t
except re.error as e:
_LOGGER.warning(
"%s: Invalid regular expression '%s' in search key (exception %s)",
self._sensor_name,
search_key,
e,
)
return {}
return {}
#
# Set Values
#
def _set_values(
self,
schedule,
tournament
) -> bool:
status = get_value(tournament, "status", default="")
if status.lower() == "complete":
self._values.state = "POST"
elif status.lower() == "scheduled":
self._values.state = "PRE"
else:
self._values.state = "IN"
self._values.season = get_value(schedule, "type", default="")
# Event Details
self._values.team_abbr = get_value(tournament, f"{self._team_side}Squad", "shortName", default="")
self._values.opponent_abbr = get_value(tournament, f"{self._opponent_side}Squad", "shortName", default="")
away = get_value(tournament, "awaySquad", "shortName", default="{shortName}")
home = get_value(tournament, "homeSquad", "shortName", default="{shortName}")
self._values.event_name = f"{away}@{home}"
self._values.event_id = get_value(tournament, "cflId", default=None)
self._values.event_id = None if (self._values.event_id is None) else str(self._values.event_id)
self._values.date = get_value(tournament, "date")
self._values.kickoff_in = arrow.get(self._values.date).humanize(locale=self._lang)
self._values.series_summary = None
self._values.venue = None
self._values.location = None
self._values.tv_network = None
odds = get_value(tournament, "markets", "away", "value", default="")
self._values.odds = f"{self._values.team_abbr} {odds}"
self._values.overunder = None
# Team Data
self._values.team_name = get_value(tournament, f"{self._team_side}Squad", "name", default="")
self._values.team_long_name = self._values.team_name
self._values.team_id = str(get_value(tournament, f"{self._team_side}Squad", "id", default=""))
self._values.team_record = None
self._values.team_rank = None
self._values.team_conference_id = None
self._values.team_homeaway = self._team_side
self._values.team_logo = None
self._values.team_url = None
self._values.team_colors = DEFAULT_COLORS
self._values.team_score = get_value(tournament, f"{self._team_side}Squad", "score")
self._values.team_win_probability = None
winner = str(get_value(tournament, "winner", default=""))
self._values.team_winner = (winner == self._values.team_id)
self._values.team_timeouts = get_value(tournament, "timeouts", f"{self._team_side}")
# Opponent Data
self._values.opponent_name = get_value(tournament, f"{self._opponent_side}Squad", "name", default="")
self._values.opponent_long_name = self._values.opponent_name
self._values.opponent_id = str(get_value(tournament, f"{self._opponent_side}Squad", "id", default=""))
self._values.opponent_record = None
self._values.opponent_rank = None
self._values.opponent_conference_id = None
self._values.opponent_homeaway = self._opponent_side
self._values.opponent_logo = None
self._values.opponent_url = None
self._values.opponent_colors = DEFAULT_COLORS
self._values.opponent_score = get_value(tournament, f"{self._opponent_side}Squad", "score")
self._values.team_win_probability = None
winner = str(get_value(tournament, "winner", default=""))
self._values.opponent_winner = (winner == self._values.opponent_id)
self._values.opponent_timeouts = get_value(tournament, "timeouts", f"{self._opponent_side}")
# In Game Attributes
self._values.quarter = get_value(tournament, "activePeriod")
self._values.clock = get_value(tournament, "clock")
possession = str(get_value(tournament, "possession", "")).lower()
if possession == self._team_side:
self._values.possession = self._values.team_id
elif possession == self._opponent_side:
self._values.possession = self._values.opponent_id
else:
self._values.possession = None
self._values.last_play = None
self._values.down_distance_text = None
# Baseball Specific
self._values.outs = None
self._values.balls = None
self._values.strikes = None
self._values.on_first = None
self._values.on_second = None
self._values.on_third = None
# Soccer/Hockey
self._values.team_shots_on_target = None
self._values.team_total_shots = None
self._values.opponent_shots_on_target = None
self._values.opponent_total_shots = None
# Volleyball
self._values.team_sets_won = None
self._values.opponent_sets_won = None
# System/API Metadata
if self._values.state == "IN":
self._values.private_fast_refresh = True
return True