fix: don't label a FINAL playoff card as CLINCHER — those stakes belong to the next game
CI / Lint (push) Successful in 11s
CI / Test (push) Successful in 14s
CI / Build & Push (push) Successful in 52s

seriesStatus updates with the just-played game's win, so once a card goes
FINAL the is_clincher / is_game7 / is_pivotal predicates point at the
upcoming game. Gate the stake badge, stake blurb, and elimination_count
tally on a non-FINAL gameState so a completed Game 3 that left the series
3-0 reads "Flyers lead 3-0" instead of "Flyers can close it out — Game 3."

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 22:35:32 -04:00
parent c7ba334bb9
commit 4e5fab654d
2 changed files with 71 additions and 14 deletions
+27 -14
View File
@@ -112,15 +112,20 @@ def series_blurb(game):
g = _game_number(game, state)
leader_name = _leader_name(game, state)
trailer_name = _trailer_name(game, state)
is_final = game.get("gameState") in ("FINAL", "OFF")
if state["is_game7"]:
return "Win-or-go-home \u2014 Game 7."
if state["is_clincher"] and leader_name:
return f"{leader_name} can close it out \u2014 Game {g}."
if state["is_pivotal"]:
return f"Series tied 2\u20112 \u2014 pivotal Game {g}."
if state["is_opener"]:
return "Series opener"
# Stake / opener blurbs describe what's *about* to happen. For a FINAL card
# the seriesStatus already includes this game, so the stake really points at
# the next matchup \u2014 fall through to a generic series-score blurb instead.
if not is_final:
if state["is_game7"]:
return "Win-or-go-home \u2014 Game 7."
if state["is_clincher"] and leader_name:
return f"{leader_name} can close it out \u2014 Game {g}."
if state["is_pivotal"]:
return f"Series tied 2\u20112 \u2014 pivotal Game {g}."
if state["is_opener"]:
return "Series opener"
if leader_name and trailer_name:
return f"{leader_name} lead {state['hi']}\u2011{state['lo']}"
if state["hi"] == state["lo"]:
@@ -138,12 +143,16 @@ def series_badges(game):
)
badges.append(round_abbrev)
if state["is_game7"]:
badges.append("GAME 7")
elif state["is_clincher"]:
badges.append("CLINCHER")
elif state["is_pivotal"]:
badges.append("PIVOTAL")
# Stake badges describe the *upcoming* game. Once a game is FINAL the
# seriesStatus reflects post-game wins, so the predicate now points at the
# next card in the series — don't stamp it onto the one that's already done.
if game.get("gameState") not in ("FINAL", "OFF"):
if state["is_game7"]:
badges.append("GAME 7")
elif state["is_clincher"]:
badges.append("CLINCHER")
elif state["is_pivotal"]:
badges.append("PIVOTAL")
return badges
@@ -217,6 +226,10 @@ def today_meta(raw_games, now=None, day_n=None):
series_letters.add(letter)
state = series_state(ss)
max_round = max(max_round, state["round"])
# Only pending/live games can still become the clincher or Game 7
# today. Once a card is FINAL its seriesStatus points at the next game.
if g.get("gameState") in ("FINAL", "OFF"):
continue
if state["is_game7"]:
g7 += 1
elif state["is_clincher"]:
+44
View File
@@ -105,6 +105,22 @@ class TestSeriesBlurb:
assert "1" in blurb
assert "Game 3" in blurb
def test_final_clincher_falls_through_to_leader_blurb(self):
# Post-game seriesStatus (3-0) would trigger the clincher branch, but
# the FINAL card is already decided — that stake belongs to Game 4.
game = make_playoff_game(
top_wins=3,
bottom_wins=0,
top_abbrev="PHI",
bottom_abbrev="PIT",
top_is_home=True,
game_state="OFF",
)
blurb = series_blurb(game)
assert "close it out" not in blurb
assert "lead" in blurb
assert "30" in blurb
class TestSeriesBadges:
def test_round_1_always_first(self):
@@ -135,6 +151,12 @@ class TestSeriesBadges:
badges = series_badges(make_playoff_game(top_wins=0, bottom_wins=0))
assert badges == ["R1"]
def test_no_stake_badge_on_final(self):
# Post-game seriesStatus shows is_clincher true, but CLINCHER refers to
# the upcoming Game 4, not the completed card.
game = make_playoff_game(top_wins=3, bottom_wins=0, game_state="OFF")
assert series_badges(game) == ["R1"]
class TestSeriesSummary:
def test_opener_summary(self):
@@ -285,3 +307,25 @@ class TestTodayMeta:
games = [make_playoff_game(round_num=4, series_letter="P")]
meta = today_meta(games)
assert meta["round_label"] == "Stanley Cup Final"
def test_does_not_count_final_games_as_elimination(self):
games = [
make_playoff_game(
top_wins=3, bottom_wins=0, series_letter="A", game_state="OFF"
),
make_playoff_game(
top_wins=3, bottom_wins=1, series_letter="B", game_state="LIVE"
),
]
meta = today_meta(games)
# Only the LIVE card counts; the FINAL card describes a completed game.
assert meta["elimination_count"] == 1
def test_does_not_count_final_game7(self):
games = [
make_playoff_game(
top_wins=3, bottom_wins=3, series_letter="A", game_state="OFF"
)
]
meta = today_meta(games)
assert meta["game7_count"] == 0