bulid 63
This commit is contained in:
199
tests/test_normalized_statistics_storage.py
Normal file
199
tests/test_normalized_statistics_storage.py
Normal file
@@ -0,0 +1,199 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from hockey_data.config import HockeySettingsStore
|
||||
from hockey_data.match_details_parser import parse_players_xml
|
||||
from hockey_data.models import (
|
||||
Game,
|
||||
GameTeamStatistic,
|
||||
PlayerSeasonStatistic,
|
||||
TeamSeasonStatistic,
|
||||
Tournament,
|
||||
TournamentStandingRow,
|
||||
TournamentStatisticRow,
|
||||
)
|
||||
from hockey_data.service import HockeyDataService
|
||||
from hockey_data.standings_parser import parse_standings_xml
|
||||
from hockey_data.tournament_statistics_parser import parse_tournament_statistics_xml
|
||||
from support import LocalTestDatabase
|
||||
|
||||
|
||||
PLAYERS_XML = b'''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PlayerStatsList tournamentId="1369">
|
||||
<PlayerStats id="101" pos="f" name="Ivanov Ivan" nameen="Ivanov Ivan"
|
||||
clubidt="14927" clubidg="1" teamname="Team A" teamnameen="Team A" jn="17"
|
||||
gp="63" g="11" a="14" pts="25" pim="24" pm="5" sog="111" sog_pct="9.9"
|
||||
hits="22" bls="50" fo="100" fow="55" fo_pct="55.0" toi_avg="14:54" />
|
||||
<PlayerStats id="202" pos="g" name="Petrov Pyotr" nameen="Petrov Pyotr"
|
||||
clubidt="14928" clubidg="2" teamname="Team B" teamnameen="Team B" jn="30"
|
||||
gp="40" w="25" l="10" sop="5" sog="1000" ga="80" sv="920" sv_pct="92.0" gaa="2.00" so="5" toi="2400:00" />
|
||||
</PlayerStatsList>'''
|
||||
|
||||
STANDINGS_XML = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Standings idtournament="1369">
|
||||
<League name="Общая" name_en="League">
|
||||
<Team id="14927" clubid="1" name="Команда А" nameen="Team A" rank="1" gp="10" w="7" otw="1" l="2" pts="17" pts_pct="85.0" gf="31" ga="18" pim="60" />
|
||||
<Team id="14928" clubid="2" name="Команда Б" nameen="Team B" rank="2" gp="10" w="6" sow="1" l="3" pts="15" pts_pct="75.0" gf="29" ga="22" pim="70" />
|
||||
</League>
|
||||
<Conferences><Conference item="west" name="Запад" name_en="West">
|
||||
<Team id="14927" clubid="1" name="Команда А" nameen="Team A" rank="1" gp="10" w="7" pts="17" gf="31" ga="18" />
|
||||
</Conference></Conferences>
|
||||
</Standings>'''
|
||||
|
||||
POWERPLAY_XML = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PowerPlay idtournament="1369">
|
||||
<Teams name="Большинство" nameen="Power play">
|
||||
<Team id="14927" name="Команда А" nameen="Team A" rank="1" gp="10" ppg="8" ppo="30" pp_pct="26.7" />
|
||||
<Team id="14928" name="Команда Б" nameen="Team B" rank="2" gp="10" ppg="6" ppo="31" pp_pct="19.4" />
|
||||
</Teams>
|
||||
</PowerPlay>'''
|
||||
|
||||
RANK_XML = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Rank idtournament="1369">
|
||||
<Category id="points" name="Бомбардиры" nameen="Points leaders">
|
||||
<Player id="101" rank="1" number="17" name="Иванов Иван" nameen="Ivan Ivanov" team="Команда А" teamen="Team A" gp="10" g="5" a="7" pts="12" />
|
||||
</Category>
|
||||
</Rank>'''
|
||||
|
||||
|
||||
class NormalizedStatisticsStorageTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.base_dir = Path(self.tmp.name)
|
||||
self.database = LocalTestDatabase(self.base_dir / "test.sqlite3")
|
||||
self.database.create_all()
|
||||
self.settings = HockeySettingsStore(self.base_dir / "settings")
|
||||
self.service = HockeyDataService(base_dir=self.base_dir, database=self.database, settings=self.settings)
|
||||
with self.database.session() as session:
|
||||
session.add(Tournament(external_id="1369"))
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.database.engine.dispose()
|
||||
self.tmp.cleanup()
|
||||
|
||||
def test_player_season_stats_are_normalized(self) -> None:
|
||||
profiles = parse_players_xml(PLAYERS_XML)
|
||||
now = datetime.utcnow()
|
||||
with self.database.session() as session:
|
||||
self.service._store_tournament_directory_profiles(
|
||||
session,
|
||||
tournament_external_id="1369",
|
||||
player_profiles=profiles,
|
||||
referee_profiles={},
|
||||
coach_profiles={},
|
||||
countries=None,
|
||||
now=now,
|
||||
)
|
||||
with self.database.session() as session:
|
||||
rows = list(session.scalars(select(PlayerSeasonStatistic).order_by(PlayerSeasonStatistic.player_external_id)))
|
||||
self.assertEqual(len(rows), 2)
|
||||
skater, goalie = rows
|
||||
self.assertEqual(skater.games, 63)
|
||||
self.assertEqual(skater.points, 25)
|
||||
self.assertEqual(skater.faceoff_pct, 55.0)
|
||||
self.assertEqual(goalie.role, "goalkeeper")
|
||||
self.assertEqual(goalie.wins, 25)
|
||||
self.assertEqual(goalie.saves, 920)
|
||||
self.assertEqual(goalie.save_pct, 92.0)
|
||||
|
||||
def test_game_team_stats_are_stored_for_total_and_periods(self) -> None:
|
||||
now = datetime.utcnow()
|
||||
raw_teams = {
|
||||
"A": {"shots": 31, "goals": 3, "fo": 50, "fow": 28, "fow_pct": "56.0", "hits": 14, "bls": 9, "pim": 6, "toa": "14:20"},
|
||||
"B": {"shots": 27, "goals": 2, "fo": 50, "fow": 22, "fow_pct": "44.0", "hits": 18, "bls": 12, "pim": 8, "toa": "11:05"},
|
||||
}
|
||||
raw_periods = {
|
||||
"A": {"1": {"shots": 10, "goals": 1}, "2": {"shots": 12, "goals": 2}},
|
||||
"B": {"1": {"shots": 8, "goals": 0}, "2": {"shots": 11, "goals": 1}},
|
||||
}
|
||||
with self.database.session() as session:
|
||||
game = Game(external_id="900001", tournament_external_id="1369", home_team_external_id="1", away_team_external_id="2")
|
||||
session.add(game)
|
||||
session.flush()
|
||||
created = self.service._store_game_team_statistics(session, game=game, raw_teams=raw_teams, raw_periods=raw_periods, now=now)
|
||||
self.assertEqual(created, 6)
|
||||
with self.database.session() as session:
|
||||
home_total = session.scalar(select(GameTeamStatistic).where(GameTeamStatistic.game_external_id == "900001", GameTeamStatistic.side == "home", GameTeamStatistic.segment_key == "total"))
|
||||
self.assertIsNotNone(home_total)
|
||||
self.assertEqual(home_total.shots, 31)
|
||||
self.assertEqual(home_total.faceoffs_won, 28)
|
||||
self.assertEqual(home_total.time_on_attack, "14:20")
|
||||
|
||||
def test_standings_powerplay_and_rank_are_normalized(self) -> None:
|
||||
now = datetime.utcnow()
|
||||
standings = parse_standings_xml(STANDINGS_XML, language="ru")
|
||||
powerplay = parse_tournament_statistics_xml(POWERPLAY_XML, resource_type="powerplay", language="ru")
|
||||
rank = parse_tournament_statistics_xml(RANK_XML, resource_type="rank", language="ru")
|
||||
with self.database.session() as session:
|
||||
self.service._store_normalized_standings(session, tournament_external_id="1369", parsed=standings, now=now)
|
||||
self.service._store_normalized_tournament_statistic_rows(session, tournament_external_id="1369", resource_type="powerplay", parsed=powerplay, now=now)
|
||||
self.service._store_normalized_tournament_statistic_rows(session, tournament_external_id="1369", resource_type="rank", parsed=rank, now=now)
|
||||
with self.database.session() as session:
|
||||
standing_count = session.scalar(select(func.count(TournamentStandingRow.id)))
|
||||
pp_count = session.scalar(select(func.count(TournamentStatisticRow.id)).where(TournamentStatisticRow.resource_type == "powerplay"))
|
||||
rank_count = session.scalar(select(func.count(TournamentStatisticRow.id)).where(TournamentStatisticRow.resource_type == "rank"))
|
||||
team_a = session.scalar(select(TeamSeasonStatistic).where(TeamSeasonStatistic.tournament_external_id == "1369", TeamSeasonStatistic.team_entry_external_id == "14927"))
|
||||
self.assertEqual(standing_count, 3)
|
||||
self.assertEqual(pp_count, 2)
|
||||
self.assertEqual(rank_count, 1)
|
||||
self.assertIsNotNone(team_a)
|
||||
self.assertEqual(team_a.points, 17)
|
||||
self.assertEqual(team_a.power_play_goals, 8)
|
||||
self.assertEqual(team_a.power_play_opportunities, 30)
|
||||
self.assertAlmostEqual(team_a.power_play_pct, 26.7)
|
||||
|
||||
status = self.service.normalized_storage_status(tournament_external_id="1369")
|
||||
self.assertEqual(status["tables"]["standings_rows"]["rows"], 3)
|
||||
self.assertEqual(status["resource_rows"]["powerplay"], 2)
|
||||
self.assertEqual(status["resource_rows"]["rank"], 1)
|
||||
|
||||
|
||||
def test_repeated_powerplay_team_sections_do_not_duplicate_team_season_rows(self) -> None:
|
||||
now = datetime.utcnow()
|
||||
standings = parse_standings_xml(STANDINGS_XML, language="ru")
|
||||
# Real Stat2TV powerplay resources can repeat the same team in several
|
||||
# sections. The actual PP section uses pp/ppgf/pp_pct rather than ppo/ppg.
|
||||
parsed = {
|
||||
"sections": [
|
||||
{
|
||||
"id": "goals-for",
|
||||
"names": {"ru": "Голы", "en": "Goals"},
|
||||
"periods": {},
|
||||
"rows": [
|
||||
{"id": "1", "name": "Команда А", "names": {"ru": "Команда А", "en": "Team A"}, "team_names": {}, "team_id": "", "player_id": "", "rank": "1", "number": "", "values": {"teid": "14927", "gp": "10", "gf": "31"}, "source_tag": "Team"},
|
||||
{"id": "2", "name": "Команда Б", "names": {"ru": "Команда Б", "en": "Team B"}, "team_names": {}, "team_id": "", "player_id": "", "rank": "2", "number": "", "values": {"teid": "14928", "gp": "10", "gf": "29"}, "source_tag": "Team"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "powerplay",
|
||||
"names": {"ru": "Большинство", "en": "Power play"},
|
||||
"periods": {},
|
||||
"rows": [
|
||||
{"id": "1", "name": "Команда А", "names": {"ru": "Команда А", "en": "Team A"}, "team_names": {}, "team_id": "", "player_id": "", "rank": "1", "number": "", "values": {"teid": "14927", "gp": "10", "pp": "30", "ppgf": "8", "pp_pct": "26.7"}, "source_tag": "Team"},
|
||||
{"id": "2", "name": "Команда Б", "names": {"ru": "Команда Б", "en": "Team B"}, "team_names": {}, "team_id": "", "player_id": "", "rank": "2", "number": "", "values": {"teid": "14928", "gp": "10", "pp": "31", "ppgf": "6", "pp_pct": "19.4"}, "source_tag": "Team"},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
with self.database.session() as session:
|
||||
self.service._store_normalized_standings(session, tournament_external_id="1369", parsed=standings, now=now)
|
||||
self.service._store_normalized_tournament_statistic_rows(session, tournament_external_id="1369", resource_type="powerplay", parsed=parsed, now=now)
|
||||
# Rebuild again in the same Session to cover pending/deleted ORM rows.
|
||||
self.service._rebuild_team_season_statistics(session, "1369", now=now)
|
||||
with self.database.session() as session:
|
||||
rows = list(session.scalars(select(TeamSeasonStatistic).where(TeamSeasonStatistic.tournament_external_id == "1369").order_by(TeamSeasonStatistic.team_key)))
|
||||
self.assertEqual(len(rows), 2)
|
||||
team_a = next(row for row in rows if row.team_entry_external_id == "14927")
|
||||
self.assertEqual(team_a.power_play_opportunities, 30)
|
||||
self.assertEqual(team_a.power_play_goals, 8)
|
||||
self.assertAlmostEqual(team_a.power_play_pct, 26.7)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user