refactor: recalibrate hype scoring to deflate gauge and add momentum signals
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:
+48
-41
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user