mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-11 01:26:37 +00:00
fix(sports): ask the endpoint the league actually publishes for standings
ESPNDataSource.fetch_standings tried /standings first regardless of league and fell back to /rankings only on a 404. College leagues answer /standings with a 200 that carries no poll, so the fallback never fired and the poll came back empty every time. Nothing failed; the rank badge simply never appeared, and anything keyed off rankings quietly did nothing. Endpoints are now ordered by whether the league publishes a poll, a 200 that lacks the key counts as a miss so a league answering both still ends up with whichever one carries the poll, and only a 404 is treated as routine -- it is how a league says it has none. A connection error, a timeout or an unparseable body is logged as an error again. This is the implementation the football, baseball and hockey boards already ship; core was the last copy still on the old one. Verified against live ESPN: mens-college-basketball returns a populated rankings key where it previously returned nothing, and nba still resolves from /standings alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9
This commit is contained in:
co-authored by
Claude Opus 5
parent
81e1bc596f
commit
09b2e3ae42
@@ -114,35 +114,52 @@ class ESPNDataSource(DataSource):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def fetch_standings(self, sport: str, league: str) -> Dict:
|
def fetch_standings(self, sport: str, league: str) -> Dict:
|
||||||
"""Fetch standings from ESPN API."""
|
"""Fetch standings, or the poll for leagues that have one.
|
||||||
# Try standings endpoint first (for professional leagues like NFL, NBA, etc.)
|
|
||||||
try:
|
|
||||||
url = f"{self.base_url}/{sport}/{league}/standings"
|
|
||||||
response = self.session.get(url, headers=self.get_headers(), timeout=15)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
data = response.json()
|
Order matters and used to be wrong. College leagues publish a poll at
|
||||||
self.logger.debug(f"Fetched standings for {sport}/{league}")
|
/rankings and a records table at /standings; professional leagues have
|
||||||
return data
|
only /standings. The old code tried /standings first and fell back to
|
||||||
except Exception as e:
|
/rankings only on a 404 -- but college /standings answers 200, so the
|
||||||
# If standings doesn't exist, try rankings (for college sports)
|
fallback never fired and college rankings came back empty forever.
|
||||||
if hasattr(e, 'response') and hasattr(e.response, 'status_code') and e.response.status_code == 404:
|
Nothing failed; the AP rank badge simply never appeared, and anything
|
||||||
try:
|
else keyed off rankings quietly did nothing.
|
||||||
url = f"{self.base_url}/{sport}/{league}/rankings"
|
|
||||||
response = self.session.get(url, headers=self.get_headers(), timeout=15)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
data = response.json()
|
A 200 that lacks the key is treated as a miss, so a league answering
|
||||||
self.logger.debug(f"Fetched rankings for {sport}/{league}")
|
both endpoints still ends up with whichever one actually carries a poll.
|
||||||
return data
|
"""
|
||||||
except Exception:
|
league_name = (league or "").lower()
|
||||||
# Both endpoints failed - standings/rankings may not be available for this sport/league
|
wants_poll = "college" in league_name or "ncaa" in league_name
|
||||||
self.logger.debug(f"Standings/rankings not available for {sport}/{league} from ESPN API")
|
endpoints = ["rankings", "standings"] if wants_poll else ["standings", "rankings"]
|
||||||
return {}
|
|
||||||
else:
|
for endpoint in endpoints:
|
||||||
# Non-404 error - log at debug level since standings are optional
|
try:
|
||||||
self.logger.debug(f"Error fetching standings from ESPN for {sport}/{league}: {e}")
|
url = f"{self.base_url}/{sport}/{league}/{endpoint}"
|
||||||
return {}
|
response = self.session.get(
|
||||||
|
url, headers=self.get_headers(), timeout=15
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if endpoint == "rankings" and not data.get("rankings"):
|
||||||
|
continue
|
||||||
|
self.logger.debug(f"Fetched {endpoint} for {sport}/{league}")
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
status = getattr(getattr(e, "response", None), "status_code", None)
|
||||||
|
# Only a 404 is routine -- it is how a league says "no poll
|
||||||
|
# here". Everything else is worth an error, and `status is
|
||||||
|
# None` covers the ones that matter most: ConnectionError,
|
||||||
|
# Timeout, a body that would not parse. Silencing those left a
|
||||||
|
# board that could not reach ESPN with one debug line, and the
|
||||||
|
# ranked filter running on an empty table.
|
||||||
|
if status != 404:
|
||||||
|
self.logger.error(
|
||||||
|
f"Error fetching {endpoint} from ESPN for "
|
||||||
|
f"{sport}/{league}: {e}"
|
||||||
|
)
|
||||||
|
self.logger.debug(
|
||||||
|
f"Standings/rankings not available for {sport}/{league} from ESPN API"
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
class MLBAPIDataSource(DataSource):
|
class MLBAPIDataSource(DataSource):
|
||||||
|
|||||||
Reference in New Issue
Block a user