refactor: recalibrate hype scoring to deflate gauge and add momentum signals
CI / Lint (push) Successful in 7s
CI / Test (push) Successful in 6s
CI / Build & Push (push) Successful in 15s

Unify overlapping late-P3 bonuses into a single score-state lookup, add
high-scoring and goal-spike signals, and tighten every component ceiling so
filling the hype bar is reserved for genuinely rare moments.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-19 11:26:07 -04:00
parent 108b77ed39
commit e0db8f0859
2 changed files with 220 additions and 179 deletions
+48 -41
View File
@@ -307,9 +307,9 @@ class TestEmptyNetBonus:
"timeRemaining": "1:30",
},
)
assert calculate_game_priority(with_en) - calculate_game_priority(base) == 200
assert calculate_game_priority(with_en) - calculate_game_priority(base) == 140
def test_en_mid_p3_adds_150(self, mocker):
def test_en_mid_p3_adds_100(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value={"league_sequence": 0, "league_l10_sequence": 0},
@@ -325,9 +325,9 @@ class TestEmptyNetBonus:
"timeRemaining": "5:00",
},
)
assert calculate_game_priority(with_en) - calculate_game_priority(base) == 150
assert calculate_game_priority(with_en) - calculate_game_priority(base) == 100
def test_en_ot_adds_250(self, mocker):
def test_en_ot_adds_180(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value={"league_sequence": 0, "league_l10_sequence": 0},
@@ -343,7 +343,7 @@ class TestEmptyNetBonus:
"timeRemaining": "10:00",
},
)
assert calculate_game_priority(with_en) - calculate_game_priority(base) == 250
assert calculate_game_priority(with_en) - calculate_game_priority(base) == 180
def test_en_stacks_with_pp(self, mocker):
mocker.patch(
@@ -362,12 +362,12 @@ class TestEmptyNetBonus:
},
)
delta = calculate_game_priority(with_both) - calculate_game_priority(base)
# PP late P3 = 150, EN late P3 = 200, total = 350
assert delta == 350
# PP late P3 = 90, EN late P3 = 140, total = 230
assert delta == 230
class TestMultiManAdvantage:
def test_5v3_ot_pp_bonus_is_320(self, mocker):
def test_5v3_ot_pp_bonus_is_180(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value={"league_sequence": 0, "league_l10_sequence": 0},
@@ -384,7 +384,8 @@ class TestMultiManAdvantage:
"situationCode": "1351",
},
)
assert calculate_game_priority(with_5v3) - calculate_game_priority(base) == 320
# OT PP 5-on-3: 120 * 1.5 = 180
assert calculate_game_priority(with_5v3) - calculate_game_priority(base) == 180
def test_standard_5v4_unchanged(self, mocker):
mocker.patch(
@@ -403,7 +404,8 @@ class TestMultiManAdvantage:
"situationCode": "1451",
},
)
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 200
# OT PP 5-on-4: 120 base, no advantage mult
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 120
class TestCalculateGamePriority:
@@ -544,18 +546,19 @@ class TestCalculateGamePriority:
one_goal = self._live_game(home_score=2, away_score=1)
assert calculate_game_priority(tied) > calculate_game_priority(one_goal)
def test_5_4_same_priority_as_1_0(self, mocker):
def test_5_4_beats_1_0_via_high_scoring_bonus(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value={"league_sequence": 0, "league_l10_sequence": 0},
)
high_scoring = self._live_game(home_score=5, away_score=4)
low_scoring = self._live_game(home_score=1, away_score=0)
assert calculate_game_priority(high_scoring) == calculate_game_priority(
# Same 1-goal diff, but 9 total goals earns the high-scoring bonus
assert calculate_game_priority(high_scoring) > calculate_game_priority(
low_scoring
)
def test_pp_in_ot_adds_200(self, mocker):
def test_pp_in_ot_adds_120(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value={"league_sequence": 0, "league_l10_sequence": 0},
@@ -571,9 +574,9 @@ class TestCalculateGamePriority:
"timeRemaining": "1:30",
},
)
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 200
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 120
def test_pp_late_p3_adds_150(self, mocker):
def test_pp_late_p3_adds_90(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value={"league_sequence": 0, "league_l10_sequence": 0},
@@ -589,9 +592,9 @@ class TestCalculateGamePriority:
"timeRemaining": "1:30",
},
)
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 150
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 90
def test_pp_mid_p3_adds_100(self, mocker):
def test_pp_mid_p3_adds_60(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value={"league_sequence": 0, "league_l10_sequence": 0},
@@ -607,9 +610,9 @@ class TestCalculateGamePriority:
"timeRemaining": "1:30",
},
)
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 100
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 60
def test_pp_early_p3_adds_50(self, mocker):
def test_pp_early_p3_adds_35(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value={"league_sequence": 0, "league_l10_sequence": 0},
@@ -625,9 +628,9 @@ class TestCalculateGamePriority:
"timeRemaining": "1:30",
},
)
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 50
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 35
def test_pp_p1_adds_30(self, mocker):
def test_pp_p1_adds_20(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value={"league_sequence": 0, "league_l10_sequence": 0},
@@ -643,7 +646,7 @@ class TestCalculateGamePriority:
"timeRemaining": "1:30",
},
)
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 30
assert calculate_game_priority(with_pp) - calculate_game_priority(base) == 20
def test_time_priority_increases_as_clock_runs(self, mocker):
mocker.patch(
@@ -676,25 +679,25 @@ class TestGetComebackBonus:
assert get_comeback_bonus(game) == 0
def test_two_goal_recovery_in_p3(self):
# Was 0-2, now 2-2: recovery=2, base=60, period_mult=1.0, tie=30
# Was 0-2, now 2-2: recovery=2, base=50, period_mult=1.0, tie=20
app.games._score_cache[("Maple Leafs", "Bruins")] = (0, 2)
app.games._comeback_tracker[("Maple Leafs", "Bruins")] = 2
game = make_game(home_score=2, away_score=2, period=3)
assert get_comeback_bonus(game) == 90 # 60*1.0 + 30
assert get_comeback_bonus(game) == 70 # 50*1.0 + 20
def test_three_goal_recovery_in_p3(self):
# Was 0-3, now 3-3: recovery=3, base=120, period_mult=1.0, tie=30
# Was 0-3, now 3-3: recovery=3, base=90, period_mult=1.0, tie=20
app.games._score_cache[("Maple Leafs", "Bruins")] = (2, 3)
app.games._comeback_tracker[("Maple Leafs", "Bruins")] = 3
game = make_game(home_score=3, away_score=3, period=3)
assert get_comeback_bonus(game) == 150 # 120*1.0 + 30
assert get_comeback_bonus(game) == 110 # 90*1.0 + 20
def test_partial_recovery_in_p3(self):
# Was 0-3, now 2-3: recovery=2, base=60, period_mult=1.0, no tie
# Was 0-3, now 2-3: recovery=2, base=50, period_mult=1.0, no tie
app.games._score_cache[("Maple Leafs", "Bruins")] = (1, 3)
app.games._comeback_tracker[("Maple Leafs", "Bruins")] = 3
game = make_game(home_score=2, away_score=3, period=3)
assert get_comeback_bonus(game) == 60 # 60*1.0
assert get_comeback_bonus(game) == 50 # 50*1.0
def test_bonus_persists_across_polls(self):
# Set up a 2-goal recovery, then call again — bonus stays
@@ -703,21 +706,21 @@ class TestGetComebackBonus:
game = make_game(home_score=2, away_score=2, period=3)
first = get_comeback_bonus(game)
second = get_comeback_bonus(game)
assert first == second == 90
assert first == second == 70
def test_period_multiplier_p1_lower(self):
# P1 recovery is less dramatic: base=60, period_mult=0.6, tie=30
# P1 recovery is less dramatic: base=50, period_mult=0.6, tie=20
app.games._score_cache[("Maple Leafs", "Bruins")] = (0, 2)
app.games._comeback_tracker[("Maple Leafs", "Bruins")] = 2
game = make_game(home_score=2, away_score=2, period=1)
assert get_comeback_bonus(game) == 66 # int(60*0.6 + 30)
assert get_comeback_bonus(game) == 50 # int(50*0.6 + 20)
def test_ot_multiplier_higher(self):
# OT: base=60, period_mult=1.2, tie=30
# OT: base=50, period_mult=1.2, tie=20
app.games._score_cache[("Maple Leafs", "Bruins")] = (2, 2)
app.games._comeback_tracker[("Maple Leafs", "Bruins")] = 2
game = make_game(home_score=2, away_score=2, period=4)
assert get_comeback_bonus(game) == 102 # int(60*1.2 + 30)
assert get_comeback_bonus(game) == 80 # int(50*1.2 + 20)
def test_no_bonus_in_intermission(self):
app.games._score_cache[("Maple Leafs", "Bruins")] = (0, 2)
@@ -739,7 +742,7 @@ class TestGetComebackBonus:
get_comeback_bonus(make_game(home_score=1, away_score=2, period=2))
result = get_comeback_bonus(make_game(home_score=2, away_score=2, period=3))
assert app.games._comeback_tracker[key] == 2
assert result == 90 # 60*1.0 + 30
assert result == 70 # 50*1.0 + 20
class TestCalculateGameImportance:
@@ -763,28 +766,31 @@ class TestCalculateGameImportance:
def test_playoff_game_gets_fallback_importance(self):
game = make_game(game_type=3)
assert calculate_game_importance(game) == 100
assert calculate_game_importance(game) == 60
def test_playoff_game7_cup_final_is_max(self):
game = make_game(
game_type=3,
series_status={"round": 4, "topSeedWins": 3, "bottomSeedWins": 3},
)
assert calculate_game_importance(game) == 200
# Game 7 Cup Final: series_factor 1.0 * round 1.5 * 100 = 150
assert calculate_game_importance(game) == 150
def test_playoff_elimination_round1(self):
game = make_game(
game_type=3,
series_status={"round": 1, "topSeedWins": 3, "bottomSeedWins": 2},
)
assert calculate_game_importance(game) == 170
# Elimination (3-x): 0.90 * 1.0 * 100 = 90
assert calculate_game_importance(game) == 90
def test_playoff_game1_round1_lowest(self):
game = make_game(
game_type=3,
series_status={"round": 1, "topSeedWins": 0, "bottomSeedWins": 0},
)
assert calculate_game_importance(game) == 80
# Series factor 0.45 * round 1.0 * 100 = 45
assert calculate_game_importance(game) == 45
def test_playoff_later_rounds_more_important(self):
series = {"topSeedWins": 2, "bottomSeedWins": 2}
@@ -810,7 +816,8 @@ class TestCalculateGameImportance:
return_value=self._standings(gp=82, wc=18, div="ATL", conf="E"),
)
game = make_game(game_state="FUT")
assert calculate_game_importance(game) == 150
# season_weight 1.0 * stakes 1.0 * rivalry 1.4 * 70 = 98
assert calculate_game_importance(game) == 98
def test_same_division_beats_same_conference(self, mocker):
home_st = self._standings(gp=70, wc=18, div="ATL", conf="E")
@@ -881,9 +888,9 @@ class TestCalculateGameImportance:
assert isinstance(result, int)
assert result >= 0
def test_result_never_exceeds_150(self, mocker):
def test_result_never_exceeds_100(self, mocker):
mocker.patch(
"app.games.get_team_standings",
return_value=self._standings(gp=82, wc=18, div="ATL", conf="E"),
)
assert calculate_game_importance(make_game(game_state="FUT")) <= 150
assert calculate_game_importance(make_game(game_state="FUT")) <= 100