Initial Home Assistant commit
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
""" 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
|
||||
Reference in New Issue
Block a user