async def adminquest(self, ctx, action: str, coach_name: str, quest: str): """Manually marks the quest as achieved or not. Does not award the prize USAGE: !adminquest <action> <coach> <quest> <action>: on to mark as complete, off the clear flag <coach>: coach discord name or its part, must be unique <quest>: *collect3legends* or *buildyourownlegend* """ self.is_admin_channel(ctx.channel) if action not in ["on", "off"]: raise ValueError("Invalid action") if quest not in ["collect3legends", "buildyourownlegend"]: raise ValueError("Invalid quest") coach = await coach_unique(coach_name, ctx) if coach is None: return if action == "on": value = True if action == "off": value = False CoachService.set_achievement(coach, ["quests", quest], value) db.session.commit() await ctx.send( f"Quest updated for {coach.short_name()}: {quest} - {action}") return
def activate_coach(coach_id, **kwargs): """Activates coach""" coach = kwargs['coach'] if coach is None: abort(404) if not owning_coach(coach): raise InvalidUsage("Unauthorized access!!!!", status_code=403) try: card_ids = request.get_json()['card_ids'] CoachService.activate_coach(coach=coach, card_ids=card_ids) except (ValueError, TypeError, TransactionError) as exc: raise InvalidUsage(str(exc), status_code=403) result = coach_schema.dump(coach) return jsonify(result.data)
async def newcoach(self, ctx): """Creates new coach account""" coach = CoachService.discord_user_to_coach(ctx.author) if coach: await ctx.send(f"**{ctx.author.mention}** account exists already") elif Coach.get_by_discord_id(ctx.author.id, deleted=True): await ctx.send( f"**{ctx.author.mention}** account is inactive, use the web page to activate it" ) else: coach = CoachService.new_coach(ctx.author, ctx.author.id) msg = f"**{ctx.author.mention}** account created\n" \ + f"**Bank:** {coach.account.amount} coins\n" \ + f"**Rules**: <{RULES_LINK}>" await ctx.send(msg)
async def reaction(self, ctx, *card_name): """User reaction card USAGE: !reaction <card_name> <card_name>: name of the reaction card """ coach = CoachService.discord_user_to_coach(ctx.author) room = ctx.channel.name if coach is None: await ctx.send( f"Coach {ctx.author.mention} does not exist. Use !newcoach to create coach first." ) return get_coach_deck_for_room_or_raise(room, coach) card_name = " ".join(card_name).strip() card = CardService.get_card_from_coach(coach, card_name) if card and card.template.card_type == "Reaction": msg = [ f"**{coach.short_name()}** plays **{card.get('name')}**: {card.get('description')}" ] notification = f"Card **{card.get('name')}** removed from your collection" db.session.delete(card) db.session.expire(coach, ['cards']) reason = f"Reaction card {card.template.name} used in {room} tournament" tran = Transaction(description=reason, price=0) coach.make_transaction(tran) await self.send_message(ctx.channel, msg) await self.bank_notification(ctx, notification, coach) else: raise ValueError(f"Reaction card {card_name} was not found!!!") return
async def CoMWithFriends(self, ctx): """**Randomise**. Open an Inducement Skill Pack and randomly apply all Training cards across your opponents' teams.""" me = CoachService.discord_user_to_coach(ctx.author) data = getattr(special_play, inspect.currentframe().f_code.co_name)(ctx.channel.name, me) await self.send_embed(data, ctx)
async def SoM3000(self, ctx): """Each team in the tournament receives a random stadium enhancement. Other stadium enhancement cards are ignored and may not be replaced.""" me = CoachService.discord_user_to_coach(ctx.author) data = getattr(special_play, inspect.currentframe().f_code.co_name)(ctx.channel.name, me) await self.send_embed(data, ctx)
async def CoMLegendBuilder(self, ctx): """Roll a dice to select a random rookie player from your roster. Open a Temporary Legend Pack for your chosen mixed team and apply all possible skills and injuries from the Legend to the chosen rookie player.""" me = CoachService.discord_user_to_coach(ctx.author) data = getattr(special_play, inspect.currentframe().f_code.co_name)(ctx.channel.name, me) await self.send_embed(data, ctx)
async def CoM2000(self, ctx): """**Randomise**. In the Deck Inducement phase, open an additional Inducement Skill Pack. Each Training Card in the pack must be applied to a random player on your team. This card is removed from your collection after use.""" me = CoachService.discord_user_to_coach(ctx.author) data = getattr(special_play, inspect.currentframe().f_code.co_name)(ctx.channel.name, me) await self.send_embed(data, ctx)
async def CoM9000(self, ctx): """**Randomise**. Choose one Special Play card that is yet to be resolved. The chosen card must now be played as if it had the **Randomise** key word. In the Deck Inducement phase, open three additional Inducement Coaching Packs. Each Training Card in the pack must be applied to a random player on your team. This card is removed from your collection after use.""" me = CoachService.discord_user_to_coach(ctx.author) data = getattr(special_play, inspect.currentframe().f_code.co_name)(ctx.channel.name, me) await self.send_embed(data, ctx)
async def CelebrityMasterChef(self, ctx): """All other teams in the tournament roll a D6. On a result of 1-3, they permanently lose a team re-roll for the entire tournament! Roll a D6. On a result of 4-6, you gain one team re-roll for the entire tournament! If multiple Celebrity Master Chefs are played, each team can only ever lose a maximum of one team re-roll across all instances of the card.""" me = CoachService.discord_user_to_coach(ctx.author) data = getattr(special_play, inspect.currentframe().f_code.co_name)(ctx.channel.name, me) await self.send_embed(data, ctx)
async def CommentatorsCurse(self, ctx): """The most powerful force in the known universe… joins your team! Add a Human Ogre or Ogre Ogre called Biff Bobbord to your team and grant him +ST, +AG, Break Tackle, Piling On, Juggernaut and Block. All teams at the tournament (including your own) then roll a D6. On a result of 1-2, they must roll a Level 1 Curse for their team. If all teams succeed their 3+ roll, you must instead roll a Level 2 curse for your own team.""" me = CoachService.discord_user_to_coach(ctx.author) data = getattr(special_play, inspect.currentframe().f_code.co_name)(ctx.channel.name, me) await self.send_embed(data, ctx)
def new_coach(): """creates new coach""" try: user = current_user() name = user['username'] + "#" + user['discriminator'] coach = CoachService.new_coach(name, user['id']) except TransactionError as exc: raise InvalidUsage(str(exc), status_code=403) result = coach_schema.dump(coach) return jsonify(result.data)
async def focus(self, ctx): """Reviews your deck and tells what the deck focus is""" me = CoachService.discord_user_to_coach(ctx.author) tourn = TournamentService.get_tournament_using_room(ctx.channel.name) deck = [ts.deck for ts in tourn.tournament_signups if ts.coach == me] if not deck: raise Exception( f"#{me.short_name()} is not signed into the tournament. Nice try!" ) focus = DeckService.focus(deck[0]) await ctx.send(f"Deck focus is {', '.join(focus)}")
async def list(self, ctx, all=None): """List details about coach's collection""" with db.session.no_autoflush: coach = CoachService.discord_user_to_coach(ctx.author) show_starter = True if all == "all" else False if coach is None: await ctx.send( f"Coach {ctx.author.mention} does not exist. Use !newcoach to create coach first." ) return all_cards = coach.active_cards() sp_msg = " (with Starter Pack)" if not show_starter: all_cards = [card for card in all_cards if not card.is_starter] sp_msg = "" msg = [ f"**Bank:** {coach.account.amount} coins\n", f"**Tournaments:**", *[ f'{t.tournament_id}. {t.name}, status: {t.status}, expected start: {t.expected_start_date}' for t in coach.tournaments ], f"\n**Collection**{sp_msg}:", "-" * 65 + "", PackHelper.format_pack(all_cards, show_hidden=True), "-" * 65 + "\n" ] if coach.duster: msg.append( f"**Dusting** - {coach.duster.type} - {coach.duster.status}" ) admin_in = Tournament.query.filter( Tournament.admin == coach.short_name(), Tournament.status.in_(("OPEN", "RUNNING"))).all() if admin_in: msg.append(f"**Tournament Admin:**") msg.extend([ f'{t.tournament_id}. {t.name}, status: {t.status}, channel: {t.discord_channel}' for t in admin_in ]) free_packs = coach.get_freepacks() if free_packs: msg.append(f"**Free Packs:**") msg.append((', ').join(free_packs)) await self.send_message(ctx.author, msg) await ctx.send("Info sent to PM")
async def resign(self, ctx, tournament_id: int): """Resigns coach from tournament USAGE: !resign <tournament_id> <tournament_id>: id of tournament from !complist """ coach = CoachService.discord_user_to_coach(ctx.author) if coach is None: await ctx.send( f"Coach {ctx.author.mention} does not exist. Use !newcoach to create coach first." ) return await resign(tournament_id, coach, ctx) return
async def done(self, ctx): """Confirms Special Play, Inducement and Blood Bowl phase""" coach = CoachService.discord_user_to_coach(ctx.author) room = ctx.channel.name if coach is None: await ctx.send( f"Coach {ctx.author.mention} does not exist. Use !newcoach to create coach first." ) return if TournamentService.get_phase(room) in [ Tourn.DB_PHASE, Tourn.LOCKED_PHASE ]: msg = "This command has no effect in this phase. Go and commit your deck!" elif TournamentService.confirm_phase(coach, room): msg = f"Phase confirmed for {coach.short_name()}" else: msg = "No deck found!" await ctx.send(msg)
def get_deck(deck_id): if not current_user(): raise InvalidUsage('You are not authenticated', status_code=401) deck = Deck.query.get(deck_id) if deck is None: abort(404) coach = Coach.query.options(raiseload(Coach.cards),raiseload(Coach.packs)).filter_by(disc_id=current_user()['id']).one_or_none() if not deck.commited and not (coach.id==deck.tournament_signup.coach.id or coach.short_name()=="TomasT"): raise InvalidUsage("Deck not commited, only owner can display it!", status_code=403) # is committed if deck.tournament_signup.tournament.phase=="deck_building" and not (coach.id==deck.tournament_signup.coach.id or coach.short_name()==deck.tournament_signup.tournament.admin or coach.short_name()=="TomasT"): raise InvalidUsage("Only owner and admin can see display commited deck in the Deck Building phase!", status_code=403) starter_cards = CoachService.get_starter_cards(deck.tournament_signup.coach) result = deck_schema.dump(deck) result2 = cards_schema.dump(starter_cards) return jsonify({'deck':result.data, 'starter_cards':result2.data})
async def genpacktemp(self, ctx, pack_type:str, subtype:str=None): """Generates special play or inducement packs. These packs are for one time use and are not assigned to coaches permanent collection""" if not check_gentemp_command(pack_type, subtype): raise discord.ext.commands.CommandError("Invalid usage!") coach = CoachService.discord_user_to_coach(ctx.author) if coach is None: await ctx.send(f"Coach {ctx.author.mention} does not exist. Use !newcoach to create coach first.") return if pack_type in ["player", "positional", "legendary"]: pack = PackService.generate(pack_type, team=subtype) elif pack_type in ["training", "special", "skill", "coaching", "brawl","hc"]: pack = PackService.generate(pack_type) elif pack_type == "booster": pack_type = "booster_budget" if not subtype else f"booster_{subtype}" pack = PackService.generate(pack_type) title = f"**Temporary {PackService.description(pack)}**" description = "**Note**: This is one time pack only!!!" pieces = PackHelper.format_pack_to_pieces(pack.cards) efs = [] efs.append({ 'name': "Cards:", 'value': pieces['cards'], 'inline': False, }) for desc in pieces['descriptions']: efs.append({ 'name': desc['name'], 'value': desc['description'], 'inline': False, }) embed = { 'embed_title': title, 'embed_desc': description, 'thumbnail_url': 'https://cdn2.rebbl.net/images/cards/dice_small.png', 'embed_fields': efs, } await self.send_embed(embed, ctx)
async def ransom(self, ctx): """Pay ransom for the kidnapped player""" coach = CoachService.discord_user_to_coach(ctx.author) if coach is None: await ctx.send( f"Coach {ctx.author.mention} does not exist. Use !newcoach to create coach first." ) return reason = 'Ransom' amount = -5 tran = Transaction(description=reason, price=-1 * amount) coach.make_transaction(tran) msg = [ f"Bank for {coach.name} updated to **{coach.account.amount}** coins:\n", f"Note: {reason}\n", f"Change: {amount} coins" ] await self.send_message(ctx.channel, msg) await self.bank_notification( ctx, f"Your bank has been updated by **{amount}** coins - {reason}", coach) return
async def genpack(self, ctx, pack_type:str, subtype:str=None): """Generates packs""" if not check_gen_command(pack_type, subtype): raise discord.ext.commands.CommandError("Invalid usage!") coach = CoachService.discord_user_to_coach(ctx.author) if coach is None: await ctx.send(f"Coach {ctx.author.mention} does not exist. Use !newcoach to create coach first.") return pp_count = db.session.query(Pck.id).filter_by(coach_id=coach.id, pack_type="player").count() if pack_type == "player": first = True if pp_count == 0 else False pack = PackService.generate(pack_type, team=subtype, first=first, coach=coach) elif pack_type in ["training","special", "hc"]: pack = PackService.generate(pack_type, coach=coach) elif pack_type == "booster": pack_type = "booster_budget" if not subtype else f"booster_{subtype}" pack = PackService.generate(pack_type, coach=coach) duster = coach.duster duster_on = False duster_txt = "" if pp_count > 0 and duster and duster.status == "COMMITTED": if (pack_type == "player" and duster.type == "Tryouts" or pack_type == "training" and duster.type == "Drills" or pack_type == "special" and duster.type == "Drills"): duster_on = True duster_txt = f" ({duster.type})" db.session.delete(duster) free_packs = coach.get_freepacks() if pack_type in ["player"] and not duster_on: if pack_type in free_packs: pack.price = 0 coach.remove_from_freepacks(pack_type) else: raise TransactionError( "You need to commit Tryouts or earn the pack through" + " Achievements to be able to generate this pack!" ) if pack_type in ["training", "special"] and not duster_on: raise TransactionError( "You need to commit Drills to be able to generate this pack!" ) if pack_type in ["booster_budget", "booster_premium"]: if pack_type in free_packs: pack.price = 0 coach.remove_from_freepacks(pack_type) tran = Transaction(pack=pack, price=pack.price, description=PackService.description(pack)) coach.make_transaction(tran) title = f"**{PackService.description(pack)}** for **{ctx.message.author.name}** - **{pack.price}** coins{duster_txt}" description = f"**Bank:** {coach.account.amount} coins" pieces = PackHelper.format_pack_to_pieces(pack.cards) await self.auto_cards(pack, ctx) CoachService.check_collect_three_legends_quest(coach) efs = [] efs.append({ 'name': "Cards:", 'value': pieces['cards'], 'inline': False, }) for desc in pieces['descriptions']: efs.append({ 'name': desc['name'], 'value': desc['description'], 'inline': False, }) embed = { 'embed_title': title, 'embed_desc': description, 'thumbnail_url': 'https://cdn2.rebbl.net/images/cards/dice_small.png', 'embed_fields': efs, } await self.send_embed(embed, ctx)
async def comp(self, ctx, *args): """Manages in-game competitions for tournaments. Needs to be run in the tournament discord channel USAGE: !comp list list all competitions for tournament !comp create ladder creates ladder comp for tournament if it does not exists !comp create 1on1 <competition_name> creates 1 on 1 comp with <competition_name> for tournament if it does not exists !comp create knockout <team_number> <name> creates knockout comp with <name> for <team_number> teams !comp ticket <competition_name> sends ticket to the <competition_name> to the coach issuing the command in the tournament room !comp start <competition_name> starts comp with <competition_name> for tournament """ room = ctx.channel.name args_len = len(args) if args_len == 0 \ or (args_len > 0 and args[0] not in ["list","create", "ticket", "start"]) \ or (args[0] == "create" and \ (args_len < 2 or args[1] not in ["ladder", "1on1","knockout"] or (args[1] == "1on1" and args_len == 2) \ or (args[1] == "knockout" and args_len < 4 and not represents_int(args[2])))) \ or (args[0] in ["ticket", "start"] and args_len < 2): raise ValueError("Incorrect arguments") tourn = TournamentService.get_tournament_using_room(room) if tourn.status != "RUNNING": await ctx.send("Tournament is not running!") return if args[0] == "create": if args[1] == "ladder": comp_name = tourn.ladder_room_name() comp = CompetitionService.create_imperium_ladder(comp_name) if args[1] == "1on1": comp_name = " ".join(args[2:]) comp_name = comp_name[:25] if len( comp_name) > 25 else comp_name comp = CompetitionService.create_imperium_rr(comp_name) if args[1] == "knockout": comp_name = " ".join(args[3:]) comp_name = comp_name[:25] if len( comp_name) > 25 else comp_name comp = CompetitionService.create_imperium_knockout( comp_name, int(args[2])) tourn.competitions.append(comp) db.session.commit() await ctx.send( f"Competition **{comp.name}** created in **{comp.league_name}**" ) return if args[0] == "list": msg = [] msg.append('{:25s} | {:25} | {:25}'.format("League", "Name", "Type")) msg.append(78 * "-") for comp in tourn.competitions: msg.append('{:25s} | {:25} | {:25}'.format( comp.league_name, comp.name, comp.type_str())) await self.send_message(ctx.channel, msg, block=True) # await ctx.send("\n".join(msg)) return if args[0] == "ticket": comp_name = " ".join(args[1:]) # get coach coach = CoachService.discord_user_to_coach(ctx.author) result = CompetitionService.ticket_competition( comp_name, coach, tourn) team_name = result['ResponseCreateCompetitionTicket'][ 'TicketInfos']['RowTeam']['Name'] await ctx.send( f"Ticket sent to **{team_name}** for competition **{comp_name}**" ) return if args[0] == "start": comp_name = " ".join(args[1:]) comp = Competition.query.filter_by(name=comp_name).one_or_none() if not comp: raise CompetitionError( f"Competition **{comp_name}** does not exist") result = CompetitionService.start_competition(comp) await ctx.send(f"Started competition **{comp_name}**") return
def main(argv): """main()""" try: opts, args = getopt.getopt(argv, "rs:") except getopt.GetoptError: print('recalculate_old_stats.py -r -s <season>') sys.exit(2) refresh = False for opt, arg in opts: if opt == '-r': refresh = True print("Recalculating statistics from the scratch") if opt == '-s': season = arg print(f"Using data for season {season}") logger = logging.getLogger('collector') logger.propagate = False logger.setLevel(logging.INFO) handler = RotatingFileHandler(os.path.join(ROOT, 'logs/collector.log'), maxBytes=10000000, backupCount=5, encoding='utf-8', mode='a') handler.setFormatter( logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) logger.addHandler(handler) st = StatsHandler(season) stats = st.get_stats(refresh) matches_folder = st.matches_folder() st.set_folders() # stats rebuilding matchfiles = [ f for f in os.listdir(matches_folder) if os.path.isfile(os.path.join(matches_folder, f)) ] if not 'matchfiles' in stats: stats['matchfiles'] = [] for file_name in matchfiles: file = open(os.path.join(matches_folder, file_name), "r") data = json.loads(file.read()) file.close() match = bb2.Match(data) st.create_folder(st.competition_folder(match.competition_id())) st.write_file( os.path.join(st.competition_folder(match.competition_id()), match.uuid()), data) if data['uuid'] in stats['matchfiles']: continue stats['matchfiles'].append(data['uuid']) logger.info("Processing stat calculation of match %s ", data['uuid']) # ignore concedes if bb2.is_concede(data): logger.info("Match %s is concede", data['uuid']) continue # initialize coaches coach1 = data['match']['coaches'][0] if not coach1['coachname'] in stats['coaches']: stats['coaches'][coach1['coachname']] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0, 'max': {} } stats['coaches'][coach1['coachname']]['name'] = coach1['coachname'] stats['coaches'][coach1['coachname']]['teams'] = {} coach2 = data['match']['coaches'][1] if not coach2['coachname'] in stats['coaches']: stats['coaches'][coach2['coachname']] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0, 'max': {} } stats['coaches'][coach2['coachname']]['name'] = coach2['coachname'] stats['coaches'][coach2['coachname']]['teams'] = {} # initialize teams team1 = data['match']['teams'][0] idraces1 = str(team1['idraces']) if not idraces1 in stats['teams']: stats['teams'][idraces1] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0 } stats['teams'][idraces1]['idraces'] = idraces1 team2 = data['match']['teams'][1] idraces2 = str(team2['idraces']) if not idraces2 in stats['teams']: stats['teams'][idraces2] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0 } stats['teams'][idraces2]['idraces'] = idraces2 #alias coaches and teams coach1_stats = stats['coaches'][coach1['coachname']] coach2_stats = stats['coaches'][coach2['coachname']] team1_stats = stats['teams'][idraces1] team2_stats = stats['teams'][idraces2] # initialize the team under coach if idraces1 not in coach1_stats['teams']: coach1_stats['teams'][idraces1] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0 } if idraces2 not in coach2_stats['teams']: coach2_stats['teams'][idraces2] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0 } # coach team alias coach1_team_stats = coach1_stats['teams'][idraces1] coach2_team_stats = coach2_stats['teams'][idraces2] coach1_stats['matches'] += 1 team1_stats['matches'] += 1 coach1_team_stats['matches'] += 1 coach2_stats['matches'] += 1 team2_stats['matches'] += 1 coach2_team_stats['matches'] += 1 for stat in [ "inflictedtouchdowns", "inflictedtackles", "inflictedcasualties", 'inflictedinjuries', 'inflictedko', 'inflicteddead', 'inflictedmetersrunning', 'inflictedpasses', 'inflictedcatches', 'inflictedinterceptions', 'sustainedexpulsions', 'sustainedcasualties', 'sustainedko', 'sustainedinjuries', 'sustaineddead', 'inflictedmeterspassing', ]: if not stat in coach1_stats: coach1_stats[stat] = 0 coach1_stats[stat] += team1[stat] if not stat in coach2_stats: coach2_stats[stat] = 0 coach2_stats[stat] += team2[stat] if not stat in team1_stats: team1_stats[stat] = 0 team1_stats[stat] += team1[stat] if not stat in team2_stats: team2_stats[stat] = 0 team2_stats[stat] += team2[stat] if not stat in coach1_team_stats: coach1_team_stats[stat] = 0 coach1_team_stats[stat] += team1[stat] if not stat in coach2_team_stats: coach2_team_stats[stat] = 0 coach2_team_stats[stat] += team2[stat] #sustd workaround if not 'sustainedtouchdowns' in coach1_stats: coach1_stats['sustainedtouchdowns'] = 0 coach1_stats['sustainedtouchdowns'] += team2['inflictedtouchdowns'] if not 'sustainedtouchdowns' in coach2_stats: coach2_stats['sustainedtouchdowns'] = 0 coach2_stats['sustainedtouchdowns'] += team1['inflictedtouchdowns'] if not 'sustainedtouchdowns' in team1_stats: team1_stats['sustainedtouchdowns'] = 0 team1_stats['sustainedtouchdowns'] += team2['inflictedtouchdowns'] if not 'sustainedtouchdowns' in team2_stats: team2_stats['sustainedtouchdowns'] = 0 team2_stats['sustainedtouchdowns'] += team1['inflictedtouchdowns'] if not 'sustainedtouchdowns' in coach1_team_stats: coach1_team_stats['sustainedtouchdowns'] = 0 coach1_team_stats['sustainedtouchdowns'] += team2[ 'inflictedtouchdowns'] if not 'sustainedtouchdowns' in coach2_team_stats: coach2_team_stats['sustainedtouchdowns'] = 0 coach2_team_stats['sustainedtouchdowns'] += team1[ 'inflictedtouchdowns'] # inflictedpushouts fix if not 'inflictedpushouts' in coach1_stats: coach1_stats['inflictedpushouts'] = 0 coach1_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team1['roster'] ]) if not 'inflictedpushouts' in coach2_stats: coach2_stats['inflictedpushouts'] = 0 coach2_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team2['roster'] ]) if not 'inflictedpushouts' in team1_stats: team1_stats['inflictedpushouts'] = 0 team1_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team1['roster'] ]) if not 'inflictedpushouts' in team2_stats: team2_stats['inflictedpushouts'] = 0 team2_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team2['roster'] ]) if not 'inflictedpushouts' in coach1_team_stats: coach1_team_stats['inflictedpushouts'] = 0 coach1_team_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team1['roster'] ]) if not 'inflictedpushouts' in coach2_team_stats: coach2_team_stats['inflictedpushouts'] = 0 coach2_team_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team2['roster'] ]) # max tracking for stat in [ "inflictedtouchdowns", "inflictedtackles", "inflictedcasualties", 'inflictedinjuries', 'inflictedinterceptions' ]: if not stat in coach1_stats['max']: coach1_stats['max'][stat] = 0 if team1[stat] > coach1_stats['max'][stat]: coach1_stats['max'][stat] = team1[stat] if not stat in coach2_stats['max']: coach2_stats['max'][stat] = 0 if team2[stat] > coach2_stats['max'][stat]: coach2_stats['max'][stat] = team2[stat] if not 'max_cas_win' in coach1_stats['max']: coach1_stats['max']['max_cas_win'] = 0 if not 'max_cas_win' in coach2_stats['max']: coach2_stats['max']['max_cas_win'] = 0 if not 'max_tvdiff_win' in coach1_stats['max']: coach1_stats['max']['max_tvdiff_win'] = 0 if not 'max_tvdiff_win' in coach2_stats['max']: coach2_stats['max']['max_tvdiff_win'] = 0 # wins/drawslosses if team1['inflictedtouchdowns'] > team2['inflictedtouchdowns']: coach1_stats['wins'] += 1 coach1_stats['points'] += 3 coach2_stats['losses'] += 1 team1_stats['wins'] += 1 team1_stats['points'] += 3 team2_stats['losses'] += 1 coach1_team_stats['wins'] += 1 coach1_team_stats['points'] += 3 coach2_team_stats['losses'] += 1 # cas achievement check if team1['sustainedcasualties'] > coach1_stats['max'][ 'max_cas_win']: coach1_stats['max']['max_cas_win'] = team1[ 'sustainedcasualties'] # down TV achievement check tv_diff = team2['value'] - team1['value'] if tv_diff > coach1_stats['max']['max_tvdiff_win']: coach1_stats['max']['max_tvdiff_win'] = tv_diff elif team1['inflictedtouchdowns'] < team2['inflictedtouchdowns']: coach2_stats['wins'] += 1 coach2_stats['points'] += 3 coach1_stats['losses'] += 1 team2_stats['wins'] += 1 team2_stats['points'] += 3 team1_stats['losses'] += 1 coach2_team_stats['wins'] += 1 coach2_team_stats['points'] += 3 coach1_team_stats['losses'] += 1 # cas achievement check if team2['sustainedcasualties'] > coach2_stats['max'][ 'max_cas_win']: coach2_stats['max']['max_cas_win'] = team2[ 'sustainedcasualties'] # down TV achievement check tv_diff = team1['value'] - team2['value'] if tv_diff > coach2_stats['max']['max_tvdiff_win']: coach2_stats['max']['max_tvdiff_win'] = tv_diff else: coach1_stats['draws'] += 1 coach1_stats['points'] += 1 coach2_stats['draws'] += 1 coach2_stats['points'] += 1 team1_stats['draws'] += 1 team1_stats['points'] += 1 team2_stats['draws'] += 1 team2_stats['points'] += 1 coach1_team_stats['draws'] += 1 coach1_team_stats['points'] += 1 coach2_team_stats['draws'] += 1 coach2_team_stats['points'] += 1 tcoach = CoachService.link_bb2_coach(coach1['coachname'], team1['teamname']) if tcoach: msg = f"{coach1['coachname']} account linked to {tcoach.short_name()}" Notificator("achievement").notify(msg) logger.info(msg) tcoach = CoachService.link_bb2_coach(coach2['coachname'], team2['teamname']) if tcoach: msg = f"{coach2['coachname']} account linked to {tcoach.short_name()}" Notificator("achievement").notify(msg) logger.info(msg) db.session.commit() logger.info("Stats calculation of match %s completed", data['uuid']) try: all_coaches = Coach.query.all() stats['coaches_extra'] = leaderboard_coach_schema.dump( all_coaches).data stats['coaches'].pop('', None) st.save_stats(stats) except Exception as exp: logger.error(exp) raise exp logger.info("Stats recalculated")
async def admincard(self, ctx, action: str, coach_name: str = None, *cards): """Adds or removes cards from coach, or updates card database USAGE 1: Add or remove cards from coach !admincard <action> <coach> <card>;...;<card> <action>: add or remove <coach>: coach discord name or its part, must be unique <card>: Exact card name as is in the All Cards list, if mutliple cards are specified separate them by **;** USAGE 2: Updates card database from the master sheet !admincard update """ if action not in ["add", "remove", "update"]: raise ValueError("Incorrect action") if not cards and not coach_name and action in ["add", "remove"]: raise ValueError("Missing arguments") if action == "update": await ctx.send(f"Updating...") CardService.update() await ctx.send(f"Cards updated!!!") return else: coach = await coach_unique(coach_name, ctx) if coach is None: return card_names = [card.strip() for card in " ".join(cards).split(";")] if action == "add": pack = PackService.admin_pack(0, card_names, coach) # situation when some of the cards could not be found if len(card_names) != len(pack.cards): msg = [] msg.append(f"Not all cards were found, check the names!!!\n") for card in card_names: if card in [ card.get('name').lower() for card in pack.cards ]: found = True else: found = False found_msg = "**not found**" if not found else "found" msg.append(f"{card}: {found_msg}") await self.send_message(ctx.channel, msg) return reason = f"{action.capitalize()} {';'.join([str(card.get('name')) for card in pack.cards])} - by " + str( ctx.author.name) tran = Transaction(pack=pack, description=reason, price=0) coach.make_transaction(tran) msg = [] msg.append( f"**{PackService.description(pack)}** for @{coach.name} - **{pack.price}** coins:\n" ) # message sent to admin so display the hidden cards msg.append( f"{PackHelper.format_pack(pack.cards, show_hidden=True)}") msg.append(f"**Bank:** {coach.account.amount} coins") await self.send_message(ctx.channel, msg) # message sent to discord so hide the names await self.bank_notification( ctx, f"Card(s) **{', '.join([card.get('name', show_hidden=False) for card in pack.cards])}** added to your collection by {str(ctx.author.name)}", coach) await self.auto_cards(pack, ctx) CoachService.check_collect_three_legends_quest(coach) return if action == "remove": removed_cards = [] unknown_cards = [] for name in card_names: card = CardService.get_card_from_coach(coach, name) if card: removed_cards.append(card) db.session.delete(card) db.session.expire(coach, ['cards']) else: unknown_cards.append(name) reason = f"{action.capitalize()} {';'.join([card.get('name') for card in removed_cards])} - by " + str( ctx.author.name) tran = Transaction(description=reason, price=0) coach.make_transaction(tran) if removed_cards: msg = [] msg.append(f"Cards removed from @{coach.name} collection:\n") msg.append( f"{PackHelper.format_pack(removed_cards, show_hidden=True)}" ) await self.send_message(ctx.channel, msg) await self.bank_notification( ctx, f"Card(s) **{', '.join([card.get('name') for card in removed_cards])}** removed from your collection by {str(ctx.author.name)}", coach) if unknown_cards: msg = ["**Warning** - these cards have been skipped:"] for name in unknown_cards: msg.append(f"{name}: **not found**") await self.send_message(ctx.channel, msg) return
def populate_db(self): with self.app.app_context(): c = CoachService.new_coach("TomasT", "1") db.session.add(c) db.session.commit()
def main(argv): """main()""" try: opts, args = getopt.getopt(argv, "r") except getopt.GetoptError: print('update_stats.py -r') sys.exit(2) refresh = False for opt, arg in opts: if opt == '-r': refresh = True print("Recalculating statistics from the scratch") logger = logging.getLogger('collector') logger.propagate = False logger.setLevel(logging.INFO) handler = RotatingFileHandler(os.path.join(ROOT, 'logs/collector.log'), maxBytes=10000000, backupCount=5, encoding='utf-8', mode='a') handler.setFormatter( logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s')) logger.addHandler(handler) st = StatsHandler() matches_folder = st.matches_folder() start_date = DT.date.today() - DT.timedelta(days=1) agent = bb2.Agent(app.config['BB2_API_KEY']) stats = st.get_stats(refresh) logger.info("Getting matches since %s", start_date) try: data = agent.matches(start=start_date, league=','.join(app.config['LEAGUES'])) except Exception as exc: logger.error(exc) raise exc logger.info("Matches colleted") st.set_folders() for match in data['matches']: filename = os.path.join(matches_folder, f"{match['uuid']}.json") if os.path.isfile(filename): logger.info("File %s exists", filename) continue logger.info("Collecting match %s", match['uuid']) try: detailed = agent.match(id=match['uuid']) file = open(filename, "a") file.write(json.dumps(detailed)) file.close() except Exception as exc: logger.error(exc) raise exc logger.info("Match %s saved", match['uuid']) # stats rebuilding matchfiles = [ f for f in os.listdir(matches_folder) if os.path.isfile(os.path.join(matches_folder, f)) ] if not 'matchfiles' in stats: stats['matchfiles'] = [] for file_name in matchfiles: file = open(os.path.join(matches_folder, file_name), "r") data = json.loads(file.read()) file.close() match = bb2.Match(data) st.create_folder(st.competition_folder(match.competition_id())) st.write_file( os.path.join(st.competition_folder(match.competition_id()), match.uuid()), data) if data['uuid'] in stats['matchfiles']: continue stats['matchfiles'].append(data['uuid']) logger.info("Processing stat calculation of match %s ", data['uuid']) # ignore concedes if bb2.match.is_concede(data): logger.info("Match %s is concede", data['uuid']) continue # initialize coaches coach1 = data['match']['coaches'][0] if not coach1['coachname'] in stats['coaches']: stats['coaches'][coach1['coachname']] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0, 'max': {} } stats['coaches'][coach1['coachname']]['name'] = coach1['coachname'] stats['coaches'][coach1['coachname']]['teams'] = {} coach2 = data['match']['coaches'][1] if not coach2['coachname'] in stats['coaches']: stats['coaches'][coach2['coachname']] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0, 'max': {} } stats['coaches'][coach2['coachname']]['name'] = coach2['coachname'] stats['coaches'][coach2['coachname']]['teams'] = {} # initialize teams team1 = data['match']['teams'][0] idraces1 = str(team1['idraces']) if not idraces1 in stats['teams']: stats['teams'][idraces1] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0 } stats['teams'][idraces1]['idraces'] = idraces1 team2 = data['match']['teams'][1] idraces2 = str(team2['idraces']) if not idraces2 in stats['teams']: stats['teams'][idraces2] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0 } stats['teams'][idraces2]['idraces'] = idraces2 #alias coaches and teams coach1_stats = stats['coaches'][coach1['coachname']] coach2_stats = stats['coaches'][coach2['coachname']] team1_stats = stats['teams'][idraces1] team2_stats = stats['teams'][idraces2] # initialize the team under coach if idraces1 not in coach1_stats['teams']: coach1_stats['teams'][idraces1] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0 } if idraces2 not in coach2_stats['teams']: coach2_stats['teams'][idraces2] = { 'wins': 0, 'losses': 0, 'draws': 0, 'matches': 0, 'points': 0 } # coach team alias coach1_team_stats = coach1_stats['teams'][idraces1] coach2_team_stats = coach2_stats['teams'][idraces2] coach1_stats['matches'] += 1 team1_stats['matches'] += 1 coach1_team_stats['matches'] += 1 coach2_stats['matches'] += 1 team2_stats['matches'] += 1 coach2_team_stats['matches'] += 1 for stat in [ "inflictedtouchdowns", "inflictedtackles", "inflictedcasualties", 'inflictedinjuries', 'inflictedko', 'inflicteddead', 'inflictedmetersrunning', 'inflictedpasses', 'inflictedcatches', 'inflictedinterceptions', 'sustainedexpulsions', 'sustainedcasualties', 'sustainedko', 'sustainedinjuries', 'sustaineddead', 'inflictedmeterspassing', ]: if not stat in coach1_stats: coach1_stats[stat] = 0 coach1_stats[stat] += team1[stat] if not stat in coach2_stats: coach2_stats[stat] = 0 coach2_stats[stat] += team2[stat] if not stat in team1_stats: team1_stats[stat] = 0 team1_stats[stat] += team1[stat] if not stat in team2_stats: team2_stats[stat] = 0 team2_stats[stat] += team2[stat] if not stat in coach1_team_stats: coach1_team_stats[stat] = 0 coach1_team_stats[stat] += team1[stat] if not stat in coach2_team_stats: coach2_team_stats[stat] = 0 coach2_team_stats[stat] += team2[stat] #sustd workaround if not 'sustainedtouchdowns' in coach1_stats: coach1_stats['sustainedtouchdowns'] = 0 coach1_stats['sustainedtouchdowns'] += team2['inflictedtouchdowns'] if not 'sustainedtouchdowns' in coach2_stats: coach2_stats['sustainedtouchdowns'] = 0 coach2_stats['sustainedtouchdowns'] += team1['inflictedtouchdowns'] if not 'sustainedtouchdowns' in team1_stats: team1_stats['sustainedtouchdowns'] = 0 team1_stats['sustainedtouchdowns'] += team2['inflictedtouchdowns'] if not 'sustainedtouchdowns' in team2_stats: team2_stats['sustainedtouchdowns'] = 0 team2_stats['sustainedtouchdowns'] += team1['inflictedtouchdowns'] if not 'sustainedtouchdowns' in coach1_team_stats: coach1_team_stats['sustainedtouchdowns'] = 0 coach1_team_stats['sustainedtouchdowns'] += team2[ 'inflictedtouchdowns'] if not 'sustainedtouchdowns' in coach2_team_stats: coach2_team_stats['sustainedtouchdowns'] = 0 coach2_team_stats['sustainedtouchdowns'] += team1[ 'inflictedtouchdowns'] # inflictedpushouts fix if not 'inflictedpushouts' in coach1_stats: coach1_stats['inflictedpushouts'] = 0 coach1_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team1['roster'] ]) if not 'inflictedpushouts' in coach2_stats: coach2_stats['inflictedpushouts'] = 0 coach2_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team2['roster'] ]) if not 'inflictedpushouts' in team1_stats: team1_stats['inflictedpushouts'] = 0 team1_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team1['roster'] ]) if not 'inflictedpushouts' in team2_stats: team2_stats['inflictedpushouts'] = 0 team2_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team2['roster'] ]) if not 'inflictedpushouts' in coach1_team_stats: coach1_team_stats['inflictedpushouts'] = 0 coach1_team_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team1['roster'] ]) if not 'inflictedpushouts' in coach2_team_stats: coach2_team_stats['inflictedpushouts'] = 0 coach2_team_stats['inflictedpushouts'] += sum([ player['stats']['inflictedpushouts'] for player in team2['roster'] ]) # max tracking for stat in [ "inflictedtouchdowns", "inflictedtackles", "inflictedcasualties", 'inflictedinjuries', 'inflictedinterceptions' ]: if not stat in coach1_stats['max']: coach1_stats['max'][stat] = 0 if team1[stat] > coach1_stats['max'][stat]: coach1_stats['max'][stat] = team1[stat] if not stat in coach2_stats['max']: coach2_stats['max'][stat] = 0 if team2[stat] > coach2_stats['max'][stat]: coach2_stats['max'][stat] = team2[stat] if not 'max_cas_win' in coach1_stats['max']: coach1_stats['max']['max_cas_win'] = 0 if not 'max_cas_win' in coach2_stats['max']: coach2_stats['max']['max_cas_win'] = 0 if not 'max_tvdiff_win' in coach1_stats['max']: coach1_stats['max']['max_tvdiff_win'] = 0 if not 'max_tvdiff_win' in coach2_stats['max']: coach2_stats['max']['max_tvdiff_win'] = 0 # wins/drawslosses if team1['inflictedtouchdowns'] > team2['inflictedtouchdowns']: coach1_stats['wins'] += 1 coach1_stats['points'] += 3 coach2_stats['losses'] += 1 team1_stats['wins'] += 1 team1_stats['points'] += 3 team2_stats['losses'] += 1 coach1_team_stats['wins'] += 1 coach1_team_stats['points'] += 3 coach2_team_stats['losses'] += 1 # cas achievement check if team1['sustainedcasualties'] > coach1_stats['max'][ 'max_cas_win']: coach1_stats['max']['max_cas_win'] = team1[ 'sustainedcasualties'] # down TV achievement check tv_diff = team2['value'] - team1['value'] if tv_diff > coach1_stats['max']['max_tvdiff_win']: coach1_stats['max']['max_tvdiff_win'] = tv_diff elif team1['inflictedtouchdowns'] < team2['inflictedtouchdowns']: coach2_stats['wins'] += 1 coach2_stats['points'] += 3 coach1_stats['losses'] += 1 team2_stats['wins'] += 1 team2_stats['points'] += 3 team1_stats['losses'] += 1 coach2_team_stats['wins'] += 1 coach2_team_stats['points'] += 3 coach1_team_stats['losses'] += 1 # cas achievement check if team2['sustainedcasualties'] > coach2_stats['max'][ 'max_cas_win']: coach2_stats['max']['max_cas_win'] = team2[ 'sustainedcasualties'] # down TV achievement check tv_diff = team1['value'] - team2['value'] if tv_diff > coach2_stats['max']['max_tvdiff_win']: coach2_stats['max']['max_tvdiff_win'] = tv_diff else: coach1_stats['draws'] += 1 coach1_stats['points'] += 1 coach2_stats['draws'] += 1 coach2_stats['points'] += 1 team1_stats['draws'] += 1 team1_stats['points'] += 1 team2_stats['draws'] += 1 team2_stats['points'] += 1 coach1_team_stats['draws'] += 1 coach1_team_stats['points'] += 1 coach2_team_stats['draws'] += 1 coach2_team_stats['points'] += 1 tcoach = CoachService.link_bb2_coach(coach1['coachname'], team1['teamname']) if tcoach: msg = f"{coach1['coachname']} account linked to {tcoach.short_name()}" Notificator("achievement").notify(msg) logger.info(msg) tcoach = CoachService.link_bb2_coach(coach2['coachname'], team2['teamname']) if tcoach: msg = f"{coach2['coachname']} account linked to {tcoach.short_name()}" Notificator("achievement").notify(msg) logger.info(msg) db.session.commit() logger.info("Stats calculation of match %s completed", data['uuid']) try: all_coaches = Coach.query.all() stats['coaches_extra'] = leaderboard_coach_schema.dump( all_coaches).data stats['coaches'].pop('', None) st.save_stats(stats) except Exception as exp: logger.error(exp) raise exp logger.info("Stats recalculated") logger.info("Achievement processing") # update achievements for coach in Coach.query.all(): if not coach.bb2_name: continue coach_stats = stats['coaches'].get(coach.bb2_name, None) if not coach_stats: continue coach.achievements['match']['winwithall']['best'] = 0 # team achievements for team_id, data in coach_stats['teams'].items(): team_id = str(team_id) # win for all achievement if data['wins'] > 0: coach.achievements['match']['winwithall']['best'] += 1 for key, ach in coach.achievements['team'][team_id][ 'played'].items(): ach['best'] = data['matches'] for key, ach in coach.achievements['team'][team_id]['wins'].items( ): ach['best'] = data['wins'] for key, ach in coach.achievements['team'][team_id][ 'touchdowns'].items(): ach['best'] = data['inflictedtouchdowns'] for key, ach in coach.achievements['team'][team_id][ 'casualties'].items(): ach['best'] = data['inflictedcasualties'] for key, ach in coach.achievements['team'][team_id]['kills'].items( ): ach['best'] = data['inflicteddead'] for key, ach in coach.achievements['team'][team_id][ 'passes'].items(): ach['best'] = data['inflictedpasses'] # match achievements coach.achievements['match']['passingtotal1']['best'] = coach_stats[ 'inflictedmeterspassing'] coach.achievements['match']['passingtotal2']['best'] = coach_stats[ 'inflictedmeterspassing'] coach.achievements['match']['runningtotal1']['best'] = coach_stats[ 'inflictedmetersrunning'] coach.achievements['match']['runningtotal2']['best'] = coach_stats[ 'inflictedmetersrunning'] coach.achievements['match']['surfstotal1']['best'] = coach_stats[ 'inflictedpushouts'] coach.achievements['match']['surfstotal2']['best'] = coach_stats[ 'inflictedpushouts'] coach.achievements['match']['blocks1game1']['best'] = coach_stats[ 'max']['inflictedtackles'] coach.achievements['match']['blocks1game2']['best'] = coach_stats[ 'max']['inflictedtackles'] coach.achievements['match']['breaks1game1']['best'] = coach_stats[ 'max']['inflictedinjuries'] coach.achievements['match']['breaks1game2']['best'] = coach_stats[ 'max']['inflictedinjuries'] coach.achievements['match']['cas1game1']['best'] = coach_stats['max'][ 'inflictedcasualties'] coach.achievements['match']['cas1game2']['best'] = coach_stats['max'][ 'inflictedcasualties'] coach.achievements['match']['score1game1']['best'] = coach_stats[ 'max']['inflictedtouchdowns'] coach.achievements['match']['score1game2']['best'] = coach_stats[ 'max']['inflictedtouchdowns'] coach.achievements['match']['int1game1']['best'] = coach_stats['max'][ 'inflictedinterceptions'] coach.achievements['match']['sufferandwin1']['best'] = coach_stats[ 'max']['max_cas_win'] coach.achievements['match']['sufferandwin2']['best'] = coach_stats[ 'max']['max_cas_win'] coach.achievements['match']['win500down']['best'] = coach_stats['max'][ 'max_tvdiff_win'] flag_modified(coach, "achievements") db.session.commit() # update achievements coach_mention = f'<@{coach.disc_id}>' for key, achievement in coach.achievements['match'].items(): if achievement['target'] <= achievement[ 'best'] and not achievement['completed']: achievement_bank_text = f"{achievement['award_text']} awarded - {achievement['desc']}" Notificator("achievement").notify( f"{coach.short_name()}: {achievement['desc']} - completed") call, arg = achievement['award'].split(",") res, error = getattr(coach, call)(arg, achievement['desc']) if res: logger.info("%s: %s awarded", coach_mention, {achievement['desc']}) Notificator("bank").notify( f"{coach_mention}: {achievement_bank_text}") coach.achievements['match'][key]['completed'] = True flag_modified(coach, "achievements") else: logger.error(error) Notificator("bank").notify( f"{coach_mention}: {achievement['award_text']} " + f"could not be awarded - {error}") for key1, stat in coach.achievements['team'].items(): for key2, item in stat.items(): for key3, achievement in item.items(): if (achievement['target'] <= achievement['best'] and not achievement['completed']): achievement_bank_text = f"{achievement['award_text']} awarded - {achievement['desc']}" Notificator("achievement").notify( f"{coach.short_name()}: {achievement['desc']} - completed" ) call, arg = achievement['award'].split(",") res, error = getattr(coach, call)(arg, achievement['desc']) if res: logger.info("%s: %s awarded", coach_mention, {achievement['desc']}) coach.achievements['team'][key1][key2][key3][ 'completed'] = True flag_modified(coach, "achievements") Notificator("bank").notify( f"{coach_mention}: {achievement_bank_text}") else: logger.error(error) Notificator("bank").notify( f"{coach_mention}: {achievement['award_text']} could " + f"not be awarded - {error}") db.session.commit() logger.info("Achievement processed")