Coverage for bundesliga_tippspiel/utils/collections/StatsGenerator.py: 90%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""LICENSE
2Copyright 2017 Hermann Krumrey <hermann@krumreyh.com>
4This file is part of bundesliga-tippspiel.
6bundesliga-tippspiel is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 3 of the License, or
9(at your option) any later version.
11bundesliga-tippspiel is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
16You should have received a copy of the GNU General Public License
17along with bundesliga-tippspiel. If not, see <http://www.gnu.org/licenses/>.
18LICENSE"""
20from typing import Dict, List, Tuple
21from jerrycan.base import db
22from jerrycan.db.User import User
23from bundesliga_tippspiel.db import LeaderboardEntry, Team, Bet, Match, \
24 DisplayBotsSettings
25from bundesliga_tippspiel.utils.collections.Leaderboard import Leaderboard
26from bundesliga_tippspiel.utils.matchday import get_matchday_info
29class StatsGenerator(Leaderboard):
30 """
31 Class that generates various statistics.
32 Uses the Leaderboard class as base to calculate the statistics
33 """
35 def __init__(
36 self,
37 league: str,
38 season: int,
39 matchday: int,
40 include_bots: bool
41 ):
42 """
43 Initializes the StatsGenerator object.
44 Loads the required data from the database
45 :param league: The league for which to generate stats
46 :param season: The season for which to generate stats
47 :param matchday: The matchday for which to generate stats
48 :param include_bots: Whether or not to include bots
49 """
50 self.selected_matchday = matchday
51 self.max_matchday = get_matchday_info(league, season)[1]
52 self.midpoint = int(self.max_matchday / 2)
53 super().__init__(league, season, matchday, include_bots)
55 self.per_day_history: Dict[int, Dict[User, LeaderboardEntry]] = {}
56 for user, user_history in self.history: 56 ↛ 57line 56 didn't jump to line 57, because the loop on line 56 never started
57 for entry in user_history:
58 if entry.matchday not in self.per_day_history:
59 self.per_day_history[entry.matchday] = {}
60 self.per_day_history[entry.matchday][user] = entry
61 self.matches = [
62 x for x in Match.query.filter_by(
63 season=season, league=league
64 ).options(db.joinedload(Match.bets).subqueryload(Bet.user)).all()
65 if x.matchday <= self.selected_matchday
66 ]
67 self.bets = []
68 for match in self.matches:
69 for bet in [x for x in match.bets if x.points is not None]:
70 is_bot = DisplayBotsSettings.bot_symbol() in bet.user.username
71 if bet.points is not None and (include_bots or not is_bot): 71 ↛ 69line 71 didn't jump to line 69, because the condition on line 71 was never false
72 self.bets.append(bet)
74 self.teams = Team.get_teams_for_season(league, season)
75 self.users = [
76 x for x in
77 User.query.filter_by(confirmed=True).all()
78 if include_bots or
79 DisplayBotsSettings.bot_symbol() not in x.username
80 ]
82 def get_first_half_ranking(self) -> List[Tuple[User, int]]:
83 """
84 :return: A ranking for the first half of the season
85 """
86 matchday = min(self.selected_matchday, self.midpoint)
87 return list(sorted([
88 (user, position.points)
89 for user, position
90 in self.per_day_history.get(matchday, {}).items()
91 ], key=lambda x: x[1], reverse=True))
93 def get_second_half_ranking(self) -> List[Tuple[User, int]]:
94 """
95 :return: A ranking for the second half of the season
96 """
97 first_half = self.get_first_half_ranking()
98 if self.selected_matchday <= self.midpoint: 98 ↛ 99line 98 didn't jump to line 99, because the condition on line 98 was never true
99 return [(user, 0) for user, _ in first_half]
100 else:
101 first_half_mapping = {user: points for user, points in first_half}
102 return list(sorted([
103 (user, position.points - first_half_mapping[user])
104 for user, position
105 in self.per_day_history.get(self.selected_matchday, {}).items()
106 ], key=lambda x: x[1], reverse=True))
108 def get_correct_bets_ranking(self) -> List[Tuple[User, int]]:
109 """
110 :return: A ranking for the amount of correct bets for each user
111 """
112 return list(sorted([
113 (user, points[Bet.MAX_POINTS])
114 for user, points
115 in self.calculate_point_distribution_per_user().items()
116 ], key=lambda x: x[1], reverse=True))
118 def get_wrong_bets_ranking(self) -> List[Tuple[User, int]]:
119 """
120 :return: A ranking for the amount of incorrect bets for each user
121 """
122 return list(sorted([
123 (user, points[0])
124 for user, points
125 in self.calculate_point_distribution_per_user().items()
126 ], key=lambda x: x[1], reverse=True))
128 def get_points_average_ranking(self) -> List[Tuple[User, float]]:
129 """
130 :return: A ranking for the average points per bet for each user
131 """
132 per_user: Dict[User, List[int]] = {user: [] for user in self.users}
133 for bet in self.bets:
134 per_user[bet.user].append(bet.points)
135 ranking = []
136 for user, points in per_user.items():
137 total = len(points)
138 avg = 0.0 if total == 0 else sum(points) / total
139 ranking.append((user, avg))
140 return list(sorted(ranking, key=lambda x: x[1], reverse=True))
142 def get_participation_ranking(self) -> List[Tuple[User, int]]:
143 """
144 :return: A ranking for the participation rate for each user
145 """
146 matches = [x for x in self.matches if x.has_started]
147 total = len(matches)
148 participation = {user: 0 for user in self.users}
149 for match in matches:
150 if not match.has_started: 150 ↛ 151line 150 didn't jump to line 151, because the condition on line 150 was never true
151 continue
152 for bet in match.bets:
153 is_bot = DisplayBotsSettings.bot_symbol() in bet.user.username
154 if self.include_bots or not is_bot: 154 ↛ 152line 154 didn't jump to line 152, because the condition on line 154 was never false
155 participation[bet.user] += 1
156 return list(sorted([
157 (user, 0 if total == 0 else int(100 * (count / total)))
158 for user, count in participation.items()
159 ], key=lambda x: x[1], reverse=True))
161 def get_points_distribution(self) -> Dict[int, int]:
162 """
163 :return: A distribution of points for all possible point results
164 """
165 distribution = {x: 0 for x in Bet.POSSIBLE_POINTS}
166 for bet in self.bets:
167 distribution[bet.points] += 1
168 return distribution
170 def get_average_points_per_team(self) -> List[Tuple[Team, float]]:
171 """
172 :return: A ranking for the average points achieved by team
173 """
174 return self.calculate_average_points_per_team(self.bets)
176 def calculate_point_distribution_per_user(self) \
177 -> Dict[User, Dict[int, int]]:
178 """
179 Calculates the points distribution for each user
180 :return: {user: {points: count}}
181 """
182 per_user = {user: {
183 x: 0 for x in Bet.POSSIBLE_POINTS
184 } for user in self.users}
186 for bet in self.bets:
187 per_user[bet.user][bet.points] += 1
188 return per_user
190 def calculate_average_points_per_team(
191 self, bets: List[Bet]
192 ) -> List[Tuple[Team, float]]:
193 """
194 Calculates the average points per team
195 :param bets: The bets to use to calculate the values
196 :return: A ranking of the average points per team
197 """
198 team_map = {x.abbreviation: x for x in self.teams}
199 points: Dict[str, List[int]] = {x.abbreviation: [] for x in self.teams}
200 for bet in bets:
201 for team in [
202 bet.home_team_abbreviation,
203 bet.away_team_abbreviation
204 ]:
205 points[team].append(bet.points)
206 ranking = []
207 for team_name, results in points.items():
208 total = len(results)
209 avg = 0.0 if total == 0 else sum(results) / total
210 team = team_map[team_name]
211 ranking.append((team, avg))
212 return list(sorted(ranking, key=lambda x: x[1], reverse=True))