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

87 statements  

1"""LICENSE 

2Copyright 2017 Hermann Krumrey <hermann@krumreyh.com> 

3 

4This file is part of bundesliga-tippspiel. 

5 

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. 

10 

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. 

15 

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""" 

19 

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 

27 

28 

29class StatsGenerator(Leaderboard): 

30 """ 

31 Class that generates various statistics. 

32 Uses the Leaderboard class as base to calculate the statistics 

33 """ 

34 

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) 

54 

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) 

73 

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 ] 

81 

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)) 

92 

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)) 

107 

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)) 

117 

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)) 

127 

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)) 

141 

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)) 

160 

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 

169 

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) 

175 

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} 

185 

186 for bet in self.bets: 

187 per_user[bet.user][bet.points] += 1 

188 return per_user 

189 

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))