161 lines
4.6 KiB
Python
161 lines
4.6 KiB
Python
"""Normalize NHL /v1/playoff-bracket payloads for the bracket template.
|
|
|
|
The NHL bracket uses stable series letters:
|
|
A,B,C,D = Round 1 East E,F,G,H = Round 1 West
|
|
I,J = Round 2 East K,L = Round 2 West
|
|
M = Conf Final East N = Conf Final West
|
|
O = Stanley Cup Final
|
|
"""
|
|
|
|
from app.playoff import ROUND_LABELS
|
|
|
|
EAST_R1 = ["A", "B", "C", "D"]
|
|
WEST_R1 = ["E", "F", "G", "H"]
|
|
EAST_R2 = ["I", "J"]
|
|
WEST_R2 = ["K", "L"]
|
|
EAST_CF = ["M"]
|
|
WEST_CF = ["N"]
|
|
CUP_FINAL = ["O"]
|
|
|
|
|
|
def build_bracket_view(year, bracket_payload, fetched_at=None):
|
|
"""Shape the raw bracket API payload for bracket.html.
|
|
|
|
Returns a dict of rounds grouped by conference, plus a flat `matchups` list
|
|
keyed by letter for the mobile accordion. Missing letters render as empty
|
|
placeholder slots so the grid stays visually complete before upsets decide.
|
|
"""
|
|
series_by_letter = {}
|
|
for s in (bracket_payload or {}).get("series", []):
|
|
letter = s.get("seriesLetter")
|
|
if letter:
|
|
series_by_letter[letter] = s
|
|
|
|
def slot(letter):
|
|
return _matchup(year, letter, series_by_letter.get(letter))
|
|
|
|
east_r1 = [slot(ltr) for ltr in EAST_R1]
|
|
west_r1 = [slot(ltr) for ltr in WEST_R1]
|
|
east_r2 = [slot(ltr) for ltr in EAST_R2]
|
|
west_r2 = [slot(ltr) for ltr in WEST_R2]
|
|
east_cf = [slot(ltr) for ltr in EAST_CF]
|
|
west_cf = [slot(ltr) for ltr in WEST_CF]
|
|
cup = [slot(ltr) for ltr in CUP_FINAL]
|
|
|
|
all_rounds = [
|
|
(1, east_r1 + west_r1),
|
|
(2, east_r2 + west_r2),
|
|
(3, east_cf + west_cf),
|
|
(4, cup),
|
|
]
|
|
current_round = None
|
|
for r, matchups in all_rounds:
|
|
if any(m["state"] == "active" for m in matchups):
|
|
current_round = r
|
|
break
|
|
|
|
return {
|
|
"year": year,
|
|
"fetched_at": fetched_at,
|
|
"bracket_logo": (bracket_payload or {}).get("bracketLogo"),
|
|
"current_round": current_round,
|
|
"east_r1": east_r1,
|
|
"west_r1": west_r1,
|
|
"east_r2": east_r2,
|
|
"west_r2": west_r2,
|
|
"east_cf": east_cf,
|
|
"west_cf": west_cf,
|
|
"cup": cup,
|
|
"rounds": [
|
|
{
|
|
"label": ROUND_LABELS[1],
|
|
"round_num": 1,
|
|
"east": east_r1,
|
|
"west": west_r1,
|
|
},
|
|
{
|
|
"label": ROUND_LABELS[2],
|
|
"round_num": 2,
|
|
"east": east_r2,
|
|
"west": west_r2,
|
|
},
|
|
{
|
|
"label": ROUND_LABELS[3],
|
|
"round_num": 3,
|
|
"east": east_cf,
|
|
"west": west_cf,
|
|
},
|
|
{"label": ROUND_LABELS[4], "round_num": 4, "cup": cup},
|
|
],
|
|
}
|
|
|
|
|
|
def _matchup(year, letter, series):
|
|
"""Render-ready dict for one bracket slot. Empty when the series is unknown."""
|
|
if not series:
|
|
return {
|
|
"letter": letter,
|
|
"series_id": f"{year}-{letter}",
|
|
"empty": True,
|
|
"top": None,
|
|
"bottom": None,
|
|
"top_wins": 0,
|
|
"bottom_wins": 0,
|
|
"round": None,
|
|
"winner_abbrev": None,
|
|
"state": "pending",
|
|
}
|
|
|
|
top = series.get("topSeedTeam") or {}
|
|
bot = series.get("bottomSeedTeam") or {}
|
|
top_wins = _to_int(series.get("topSeedWins"))
|
|
bot_wins = _to_int(series.get("bottomSeedWins"))
|
|
winning_id = series.get("winningTeamId")
|
|
|
|
winner_abbrev = None
|
|
if winning_id is not None:
|
|
if top.get("id") == winning_id:
|
|
winner_abbrev = top.get("abbrev")
|
|
elif bot.get("id") == winning_id:
|
|
winner_abbrev = bot.get("abbrev")
|
|
|
|
if winner_abbrev:
|
|
state = "complete"
|
|
elif top_wins > 0 or bot_wins > 0:
|
|
state = "active"
|
|
else:
|
|
state = "upcoming"
|
|
|
|
return {
|
|
"letter": letter,
|
|
"series_id": f"{year}-{letter}",
|
|
"empty": False,
|
|
"top": _team(top, series.get("topSeedRankAbbrev")),
|
|
"bottom": _team(bot, series.get("bottomSeedRankAbbrev")),
|
|
"top_wins": top_wins,
|
|
"bottom_wins": bot_wins,
|
|
"round": series.get("playoffRound"),
|
|
"winner_abbrev": winner_abbrev,
|
|
"state": state,
|
|
}
|
|
|
|
|
|
def _team(team, seed_abbrev=None):
|
|
if not team:
|
|
return None
|
|
return {
|
|
"id": team.get("id"),
|
|
"abbrev": team.get("abbrev"),
|
|
"name": (team.get("name") or {}).get("default"),
|
|
"common_name": (team.get("commonName") or {}).get("default"),
|
|
"logo": team.get("darkLogo") or team.get("logo"),
|
|
"seed": seed_abbrev,
|
|
}
|
|
|
|
|
|
def _to_int(v, default=0):
|
|
try:
|
|
return int(v)
|
|
except (TypeError, ValueError):
|
|
return default
|