Coverage for bundesliga_tippspiel/background/pointscalc.py: 8%

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 

20import time 

21from jerrycan.base import db, app 

22from jerrycan.db.User import User 

23from typing import List, Dict, Tuple 

24from bundesliga_tippspiel.db import Bet, Match, LeaderboardEntry, \ 

25 DisplayBotsSettings, MatchdayWinner 

26from bundesliga_tippspiel.utils.matchday import get_matchday_info 

27 

28 

29def update_leaderboard(): 

30 """ 

31 Updates the leaderboard entries 

32 :return: None 

33 """ 

34 start = time.time() 

35 app.logger.info("Updating leaderboard entries") 

36 update_bet_points() 

37 users = User.query.filter_by(confirmed=True).all() 

38 seasons = create_categorized_matches() 

39 season_points = calculate_matchday_points(users, seasons) 

40 

41 process_matchday_winners(season_points) 

42 

43 for (league, season), user_points in season_points.items(): 

44 

45 previous_positions = {} 

46 previous_no_bot_positions = {} 

47 user_totals = {user: 0 for user in users} 

48 for matchday, matchday_points in sorted( 

49 user_points.items(), key=lambda x: x[0] 

50 ): 

51 for user, points in matchday_points.items(): 

52 user_totals[user] += points 

53 process_league_table( 

54 league, 

55 season, 

56 matchday, 

57 user_totals, 

58 previous_positions, 

59 previous_no_bot_positions 

60 ) 

61 

62 app.logger.debug(f"Finished leaderboard update in " 

63 f"{time.time()-start:.2f}s") 

64 

65 

66def update_bet_points(): 

67 """ 

68 Updates the bet points 

69 :return: None 

70 """ 

71 bets = Bet.query.options(db.joinedload(Bet.match)).all() 

72 for bet in bets: 

73 bet.points = bet.evaluate() 

74 db.session.commit() 

75 

76 

77def create_categorized_matches() -> Dict[ 

78 Tuple[str, int], Dict[int, List[Match]] 

79]: 

80 """ 

81 Sorts matches into seasons and matchdays 

82 :return: The matches categorized like this: 

83 {(league, season): {matchday: [match, ...]}} 

84 """ 

85 seasons: Dict[Tuple[str, int], Dict[int, List[Match]]] = {} 

86 for match in Match.query.options( 

87 db.joinedload(Match.bets).subqueryload(Bet.user) 

88 ).all(): 

89 league_season = (match.league, match.season) 

90 matchday = match.matchday 

91 if league_season not in seasons: 

92 seasons[league_season] = {} 

93 if matchday not in seasons[league_season]: 

94 seasons[league_season][matchday] = [] 

95 seasons[league_season][matchday].append(match) 

96 return seasons 

97 

98 

99def calculate_matchday_points( 

100 users: List[User], 

101 seasons: Dict[Tuple[str, int], Dict[int, List[Match]]] 

102) -> Dict[Tuple[str, int], Dict[int, Dict[User, int]]]: 

103 """ 

104 Calculates every user's points per matchday for each season 

105 :param users: The users to include 

106 :param seasons: The seasons data 

107 :return: {(league, season): {matchday: {user: points}}} 

108 """ 

109 season_points = {} 

110 for league_season, season_data in seasons.items(): 

111 

112 user_points = { 

113 matchday: {user: 0 for user in users} 

114 for matchday in season_data.keys() 

115 } 

116 

117 for matchday, matches in sorted( 

118 season_data.items(), key=lambda x: x[0] 

119 ): 

120 for match in matches: 

121 for bet in match.bets: 

122 if bet.points is not None: 

123 user_points[matchday][bet.user] += bet.points 

124 

125 season_points[league_season] = user_points 

126 return season_points 

127 

128 

129def process_matchday_winners( 

130 per_matchday_data: Dict[Tuple[str, int], Dict[int, Dict[User, int]]] 

131): 

132 """ 

133 Processes the matchday winners 

134 :param per_matchday_data: The points of each user per matchday 

135 :return: None 

136 """ 

137 for (league, season), matchday_info in per_matchday_data.items(): 

138 current_matchday = get_matchday_info(league, season)[0] 

139 for matchday, user_points in matchday_info.items(): 

140 if matchday > current_matchday: 

141 continue 

142 

143 best_user_id, best_points = None, 0 

144 for user, points in user_points.items(): 

145 

146 if DisplayBotsSettings.bot_symbol() in user.username: 

147 continue 

148 if points > best_points: 

149 best_user_id, best_points = user.id, points 

150 elif points == best_points: 

151 best_user_id = None 

152 

153 matchday_winner = MatchdayWinner( 

154 league=league, 

155 season=season, 

156 matchday=matchday, 

157 user_id=best_user_id 

158 ) 

159 db.session.merge(matchday_winner) 

160 db.session.commit() 

161 

162 

163def process_league_table( 

164 league: str, 

165 season: int, 

166 matchday: int, 

167 user_points: Dict[User, int], 

168 previous_positions: Dict[User, int], 

169 previous_no_bot_positions: Dict[User, int] 

170): 

171 """ 

172 Processes the league table entries and updates their corresponding database 

173 entries. 

174 :param league: The league to process 

175 :param season: The season to process 

176 :param matchday: The matchday to process 

177 :param user_points: The points for every user to process: 

178 :param previous_positions: Dictionary that keeps track of the previous 

179 positions of the users 

180 :param previous_no_bot_positions: Dictionary that keeps track of the 

181 previous positions of the users 

182 disregarding bots 

183 :return: None 

184 """ 

185 bot_symbol = DisplayBotsSettings.bot_symbol() 

186 ranking = [(user, points) for user, points in user_points.items()] 

187 ranking.sort(key=lambda x: x[0].id) 

188 ranking.sort(key=lambda x: x[1], reverse=True) 

189 

190 no_bot_ranking = [x for x in ranking if bot_symbol not in x[0].username] 

191 no_bot_positions = {} 

192 for i, (user, _) in enumerate(no_bot_ranking): 

193 position = i + 1 

194 no_bot_positions[user] = position 

195 

196 for i, (user, points) in enumerate(ranking): 

197 position = i + 1 

198 no_bot_position = no_bot_positions.get(user, position) 

199 

200 previous_position = previous_positions.get(user, position) 

201 previous_positions[user] = position 

202 no_bot_previous_position = previous_no_bot_positions.get( 

203 user, no_bot_position 

204 ) 

205 previous_no_bot_positions[user] = no_bot_position 

206 

207 entry = LeaderboardEntry( 

208 league=league, 

209 season=season, 

210 matchday=matchday, 

211 user_id=user.id, 

212 points=points, 

213 position=position, 

214 no_bot_position=no_bot_position, 

215 previous_position=previous_position, 

216 no_bot_previous_position=no_bot_previous_position 

217 ) 

218 db.session.merge(entry) 

219 db.session.commit()