feat: game importance factor in hype scoring
Some checks failed
CI / Lint (push) Failing after 6s
CI / Test (push) Has been skipped
CI / Build & Push (push) Has been skipped

Adds calculate_game_importance() that boosts Priority for high-stakes
regular-season matchups based on season progress (sharp ramp after game
55), playoff bubble proximity (wildcard rank ~17-19 = max relevance),
and divisional/conference rivalry (1.4x/1.2x multipliers). Max bonus
150 pts applied to both LIVE and PRE games; playoff and FINAL games
are unaffected. Extends standings schema with division, conference,
games_played, and wildcard_sequence fields fetched from the NHL API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 18:39:55 -04:00
parent 8945b99782
commit 47a8c34215
5 changed files with 311 additions and 9 deletions

View File

@@ -6,6 +6,7 @@ from app.standings import (
create_standings_table,
fetch_standings,
insert_standings,
migrate_standings_table,
refresh_standings,
truncate_standings_table,
)
@@ -16,11 +17,19 @@ SAMPLE_API_RESPONSE = {
"teamCommonName": {"default": "Bruins"},
"leagueSequence": 1,
"leagueL10Sequence": 2,
"divisionAbbrev": "ATL",
"conferenceAbbrev": "E",
"gamesPlayed": 60,
"wildcardSequence": 5,
},
{
"teamCommonName": {"default": "Maple Leafs"},
"leagueSequence": 5,
"leagueL10Sequence": 3,
"divisionAbbrev": "ATL",
"conferenceAbbrev": "E",
"gamesPlayed": 61,
"wildcardSequence": 8,
},
]
}
@@ -38,6 +47,10 @@ class TestFetchStandings:
"team_common_name": "Bruins",
"league_sequence": 1,
"league_l10_sequence": 2,
"division_abbrev": "ATL",
"conference_abbrev": "E",
"games_played": 60,
"wildcard_sequence": 5,
}
assert result[1]["team_common_name"] == "Maple Leafs"
@@ -98,6 +111,10 @@ class TestTruncateStandingsTable:
"team_common_name": "Bruins",
"league_sequence": 1,
"league_l10_sequence": 2,
"division_abbrev": "ATL",
"conference_abbrev": "E",
"games_played": 60,
"wildcard_sequence": 5,
}
],
)
@@ -119,11 +136,19 @@ class TestInsertStandings:
"team_common_name": "Bruins",
"league_sequence": 1,
"league_l10_sequence": 2,
"division_abbrev": "ATL",
"conference_abbrev": "E",
"games_played": 60,
"wildcard_sequence": 5,
},
{
"team_common_name": "Maple Leafs",
"league_sequence": 5,
"league_l10_sequence": 3,
"division_abbrev": "ATL",
"conference_abbrev": "E",
"games_played": 61,
"wildcard_sequence": 8,
},
]
insert_standings(conn, data)
@@ -142,6 +167,10 @@ class TestInsertStandings:
"team_common_name": "Bruins",
"league_sequence": 1,
"league_l10_sequence": 2,
"division_abbrev": "ATL",
"conference_abbrev": "E",
"games_played": 60,
"wildcard_sequence": 5,
}
],
)
@@ -162,6 +191,10 @@ class TestRefreshStandings:
"team_common_name": "Bruins",
"league_sequence": 1,
"league_l10_sequence": 2,
"division_abbrev": "ATL",
"conference_abbrev": "E",
"games_played": 60,
"wildcard_sequence": 5,
}
]
mocker.patch("app.standings.fetch_standings", return_value=standings)
@@ -183,6 +216,10 @@ class TestRefreshStandings:
"team_common_name": "Bruins",
"league_sequence": 1,
"league_l10_sequence": 2,
"division_abbrev": "ATL",
"conference_abbrev": "E",
"games_played": 60,
"wildcard_sequence": 5,
}
]
mocker.patch("app.standings.fetch_standings", return_value=first)
@@ -193,11 +230,19 @@ class TestRefreshStandings:
"team_common_name": "Oilers",
"league_sequence": 3,
"league_l10_sequence": 1,
"division_abbrev": "PAC",
"conference_abbrev": "W",
"games_played": 62,
"wildcard_sequence": 3,
},
{
"team_common_name": "Jets",
"league_sequence": 4,
"league_l10_sequence": 2,
"division_abbrev": "CEN",
"conference_abbrev": "W",
"games_played": 61,
"wildcard_sequence": 4,
},
]
mocker.patch("app.standings.fetch_standings", return_value=second)
@@ -218,6 +263,10 @@ class TestRefreshStandings:
"team_common_name": "Bruins",
"league_sequence": 1,
"league_l10_sequence": 2,
"division_abbrev": "ATL",
"conference_abbrev": "E",
"games_played": 60,
"wildcard_sequence": 5,
}
]
mocker.patch("app.standings.fetch_standings", return_value=seed)
@@ -231,3 +280,29 @@ class TestRefreshStandings:
count = conn.execute("SELECT COUNT(*) FROM standings").fetchone()[0]
conn.close()
assert count == 1
class TestMigrateStandingsTable:
def test_adds_missing_columns_to_existing_table(self, tmp_path):
conn = sqlite3.connect(str(tmp_path / "test.db"))
conn.execute(
"CREATE TABLE standings "
"(team_common_name TEXT, league_sequence INTEGER, league_l10_sequence INTEGER)"
)
conn.commit()
migrate_standings_table(conn)
cols = [row[1] for row in conn.execute("PRAGMA table_info(standings)").fetchall()]
assert "division_abbrev" in cols
assert "conference_abbrev" in cols
assert "games_played" in cols
assert "wildcard_sequence" in cols
conn.close()
def test_is_idempotent(self, tmp_path):
conn = sqlite3.connect(str(tmp_path / "test.db"))
create_standings_table(conn)
migrate_standings_table(conn)
migrate_standings_table(conn) # must not raise
conn.close()