def _process_request(self, request, event_key): matches = [] needs_time = [] for match in JSONMatchesParser.parse(request.body, self.event.year): match = Match( id=Match.renderKeyName( self.event.key.id(), match.get("comp_level", None), match.get("set_number", 0), match.get("match_number", 0)), event=self.event.key, year=self.event.year, set_number=match.get("set_number", 0), match_number=match.get("match_number", 0), comp_level=match.get("comp_level", None), team_key_names=match.get("team_key_names", None), alliances_json=match.get("alliances_json", None), score_breakdown_json=match.get("score_breakdown_json", None), time_string=match.get("time_string", None), time=match.get("time", None), ) if (not match.time or match.time == "") and match.time_string: # We can calculate the real time from the time string needs_time.append(match) matches.append(match) if needs_time: try: logging.debug("Calculating time!") MatchHelper.add_match_times(self.event, needs_time) except Exception, e: logging.error("Failed to calculate match times")
def send_upcoming_matches(cls, live_events): from helpers.match_helper import MatchHelper # PJL: Hacky :P # Causes circular import, otherwise # https://github.com/the-blue-alliance/the-blue-alliance/pull/1098#discussion_r25128966 now = datetime.datetime.utcnow() for event in live_events: matches = event.matches if not matches: continue last_matches = MatchHelper.recentMatches(matches, num=1) next_matches = MatchHelper.upcomingMatches(matches, num=2) # First, compare the difference between scheduled times of next/last match # Send an upcoming notification if it's <10 minutes, to account for events ahead of schedule if last_matches != []: last_match = last_matches[0] for i, next_match in enumerate(next_matches): if not next_match.push_sent and last_match.time and next_match.time: diff = next_match.time - last_match.time if diff < datetime.timedelta(minutes=10 * (i + 1)): cls.send_upcoming_match_notification(next_match, event) for match in next_matches: if match and not match.push_sent: # Only continue sending for the next match if a push hasn't already been sent for it if match.time is None or match.time + datetime.timedelta(minutes=-7) <= now: # Only send notifications for matches no more than 7 minutes (average-ish match cycle time) before it's scheduled to start # Unless, the match has no time info. Then #yolo and send it cls.send_upcoming_match_notification(match, event)
def post(self): self._require_admin() event_key = self.request.get('event_key') matches_csv = self.request.get('matches_csv') matches = OffseasonMatchesParser.parse(matches_csv) event = Event.get_by_id(event_key) matches = [Match( id=Match.renderKeyName( event.key.id(), match.get("comp_level", None), match.get("set_number", 0), match.get("match_number", 0)), event=event.key, game=Match.FRC_GAMES_BY_YEAR.get(event.year, "frc_unknown"), set_number=match.get("set_number", 0), match_number=match.get("match_number", 0), comp_level=match.get("comp_level", None), team_key_names=match.get("team_key_names", None), alliances_json=match.get("alliances_json", None) ) for match in matches] new_matches = MatchManipulator.createOrUpdate(matches) try: last_matches = MatchHelper.recentMatches(new_matches, 1) upcoming_matches = MatchHelper.upcomingMatches(new_matches, 8) except: logging.warning("Computing last/upcoming matches for Firebase failed!") try: FirebasePusher.updateEvent(event, last_matches, upcoming_matches) except: logging.warning("Enqueuing Firebase push failed!") self.redirect('/admin/event/{}'.format(event_key))
def _render(self, event_key): event_future = EventQuery(event_key).fetch_async(return_updated=True) matches_future = EventMatchesQuery(event_key).fetch_async(return_updated=True) event, event_updated = event_future.get_result() matches, matches_updated = matches_future.get_result() self._last_modified = max(event_updated, matches_updated) cleaned_matches = MatchHelper.deleteInvalidMatches(matches, event) matches = MatchHelper.organizeMatches(cleaned_matches) bracket_table, playoff_advancement, _, _ = PlayoffAdvancementHelper.generatePlayoffAdvancement(event, matches) output = [] for level in Match.ELIM_LEVELS: level_ranks = [] if playoff_advancement and playoff_advancement.get(level): if event.playoff_type == PlayoffType.AVG_SCORE_8_TEAM: level_ranks = PlayoffAdvancementHelper.transform2015AdvancementLevelForApi(event, playoff_advancement, level) else: level_ranks = PlayoffAdvancementHelper.transformRoundRobinAdvancementLevelForApi(event, playoff_advancement, level) elif bracket_table and bracket_table.get(level): level_ranks = PlayoffAdvancementHelper.transformBracketLevelForApi(event, bracket_table, level) output.extend(level_ranks) return json.dumps(output, ensure_ascii=True, indent=2, sort_keys=True)
def test_parse_qual(self): self.event = Event( id="2016nyny", name="NYC Regional", event_type_enum=EventType.REGIONAL, short_name="NYC", event_short="nyny", year=2016, end_date=datetime(2016, 03, 27), official=True, start_date=datetime(2016, 03, 24), timezone_id="America/New_York" ) self.event.put() with open('test_data/fms_api/2016_nyny_hybrid_schedule_qual.json', 'r') as f: matches, _ = FMSAPIHybridScheduleParser(2016, 'nyny').parse(json.loads(f.read())) self.assertTrue(isinstance(matches, list)) self.assertEqual(len(matches), 88) # Assert we get enough of each match type clean_matches = MatchHelper.organizeMatches(matches) self.assertEqual(len(clean_matches["qm"]), 88) # Changed format in 2018 with open('test_data/fms_api/2016_nyny_hybrid_schedule_qual_2018update.json', 'r') as f: matches, _ = FMSAPIHybridScheduleParser(2016, 'nyny').parse(json.loads(f.read())) self.assertTrue(isinstance(matches, list)) self.assertEqual(len(matches), 88) # Assert we get enough of each match type clean_matches = MatchHelper.organizeMatches(matches) self.assertEqual(len(clean_matches["qm"]), 88)
def _process_request(self, request, event_key): event = Event.get_by_id(event_key) year = int(event_key[:4]) matches = [] for match in JSONMatchesParser.parse(request.body, year): match = Match( id=Match.renderKeyName( event.key.id(), match.get("comp_level", None), match.get("set_number", 0), match.get("match_number", 0)), event=event.key, year=event.year, set_number=match.get("set_number", 0), match_number=match.get("match_number", 0), comp_level=match.get("comp_level", None), team_key_names=match.get("team_key_names", None), alliances_json=match.get("alliances_json", None), score_breakdown_json=match.get("score_breakdown_json", None), time_string=match.get("time_string", None), time=match.get("time", None), ) if (not match.time or match.time == "") and match.time_string: # We can calculate the real time from the time string logging.debug("Calculating time!") MatchHelper.add_match_times(event, [match]) matches.append(match) MatchManipulator.createOrUpdate(matches) self.response.out.write(json.dumps({'Success': "Matches successfully updated"}))
def get(self): self._require_registration() current_events = filter(lambda e: e.now, EventHelper.getEventsWithinADay()) popular_teams_events = TeamHelper.getPopularTeamsEvents(current_events) popular_team_keys = set() for team, _ in popular_teams_events: popular_team_keys.add(team.key.id()) for event in current_events: event.prep_details() event.prep_matches() finished_matches = [] current_matches = [] upcoming_matches = [] ranks = {} alliances = {} for event in current_events: if not event.details: continue finished_matches += MatchHelper.recentMatches(event.matches, num=1) for i, match in enumerate(MatchHelper.upcomingMatches(event.matches, num=3)): if not match.time: continue if not event.details.predictions or match.key.id() not in event.details.predictions['match_predictions']['qual' if match.comp_level == 'qm' else 'playoff']: match.prediction = defaultdict(lambda: defaultdict(float)) match.bluezone_score = 0 else: match.prediction = event.details.predictions['match_predictions']['qual' if match.comp_level == 'qm' else 'playoff'][match.key.id()] match.bluezone_score = self.get_qual_bluezone_score(match.prediction) if match.comp_level == 'qm' else self.get_elim_bluezone_score(match.prediction) if i == 0: current_matches.append(match) else: upcoming_matches.append(match) if event.details.rankings2: for rank in event.details.rankings2: ranks[rank['team_key']] = rank['rank'] if event.alliance_selections: for i, alliance in enumerate(event.alliance_selections): for pick in alliance['picks']: alliances[pick] = i + 1 finished_matches = sorted(finished_matches, key=lambda m: m.actual_time if m.actual_time else m.time) current_matches = sorted(current_matches, key=lambda m: m.predicted_time if m.predicted_time else m.time) upcoming_matches = sorted(upcoming_matches, key=lambda m: m.predicted_time if m.predicted_time else m.time) self.template_values.update({ 'finished_matches': finished_matches, 'current_matches': current_matches, 'upcoming_matches': upcoming_matches, 'ranks': ranks, 'alliances': alliances, 'popular_team_keys': popular_team_keys, }) self.response.out.write(jinja2_engine.render('match_suggestion.html', self.template_values))
def test_event_winner(self): MatchHelper.add_surrogates(self.event) for match in self.event.matches: if match.comp_level != 'qm' or match.match_number != 18: for alliance_color in ['red', 'blue']: self.assertEqual(match.alliances[alliance_color]['surrogates'], []) else: self.assertEqual(match.alliances['red']['surrogates'], ['frc5496']) self.assertEqual(match.alliances['blue']['surrogates'], ['frc1323'])
def _render(self, event_key): event = Event.get_by_id(event_key) if not event: self.abort(404) event.prepAwardsMatchesTeams() awards = AwardHelper.organizeAwards(event.awards) cleaned_matches = MatchHelper.deleteInvalidMatches(event.matches) matches = MatchHelper.organizeMatches(cleaned_matches) teams = TeamHelper.sortTeams(event.teams) num_teams = len(teams) middle_value = num_teams / 2 if num_teams % 2 != 0: middle_value += 1 teams_a, teams_b = teams[:middle_value], teams[middle_value:] oprs = [i for i in event.matchstats['oprs'].items()] if (event.matchstats is not None and 'oprs' in event.matchstats) else [] oprs = sorted(oprs, key=lambda t: t[1], reverse=True) # sort by OPR oprs = oprs[:15] # get the top 15 OPRs if event.within_a_day: matches_recent = MatchHelper.recentMatches(cleaned_matches) matches_upcoming = MatchHelper.upcomingMatches(cleaned_matches) else: matches_recent = None matches_upcoming = None bracket_table = MatchHelper.generateBracket(matches, event.alliance_selections) district_points_sorted = None if event.district_points: district_points_sorted = sorted(event.district_points['points'].items(), key=lambda (team, points): -points['total']) self.template_values.update({ "event": event, "matches": matches, "matches_recent": matches_recent, "matches_upcoming": matches_upcoming, "awards": awards, "teams_a": teams_a, "teams_b": teams_b, "num_teams": num_teams, "oprs": oprs, "bracket_table": bracket_table, "district_points_sorted": district_points_sorted, }) if event.within_a_day: self._cache_expiration = self.SHORT_CACHE_EXPIRATION path = os.path.join(os.path.dirname(__file__), '../templates/event_details.html') return template.render(path, self.template_values)
def getMatches(self, event): matches_url = self.YEAR_MATCH_RESULTS_URL_PATTERN.get( event.year, self.DEFAULT_MATCH_RESULTS_URL_PATTERN) % ( event.year, self.EVENT_SHORT_EXCEPTIONS.get(event.event_short, event.event_short)) match_dicts, _ = self.parse(matches_url, self.YEAR_MATCH_PARSER.get(event.year, self.DEFAULT_MATCH_PARSER)) if not match_dicts: # Matches have not been played, but qual match schedule may be out # If this is run when there are already matches in the DB, it will overwrite scores! # Check to make sure event has no existing matches if len(Match.query(Match.event == event.key).fetch(1, keys_only=True)) == 0: logging.warning("No matches found for {}. Trying to parse qual match schedule.".format(event.key.id())) qual_match_sched_url = self.MATCH_SCHEDULE_QUAL_URL_PATTERN % ( event.year, self.EVENT_SHORT_EXCEPTIONS.get(event.event_short, event.event_short)) match_dicts, _ = self.parse(qual_match_sched_url, self.MATCH_SCHEDULE_PARSER) for match_dict in match_dicts: alliances = json.loads(match_dict['alliances_json']) if (alliances['red']['score'] == -1 or alliances['blue']['score'] == -1 or match_dict['comp_level'] in Match.ELIM_LEVELS): break else: # Only qual matches have been played and they have all been played # If this is run when there are already elim matches in the DB, it will overwrite scores! # Check to make sure event has no existing elim matches if len(Match.query(Match.event == event.key, Match.comp_level.IN(Match.ELIM_LEVELS)).fetch(1, keys_only=True)) == 0: logging.warning("No elim matches found for {}. Trying to parse elim match schedule.".format(event.key.id())) elim_match_sched_url = self.MATCH_SCHEDULE_ELIMS_URL_PATTERN % ( event.year, self.EVENT_SHORT_EXCEPTIONS.get(event.event_short, event.event_short)) elim_match_dicts, _ = self.parse(elim_match_sched_url, self.MATCH_SCHEDULE_PARSER) match_dicts += elim_match_dicts matches = [Match( id=Match.renderKeyName( event.key.id(), match_dict.get("comp_level", None), match_dict.get("set_number", 0), match_dict.get("match_number", 0)), event=event.key, game=Match.FRC_GAMES_BY_YEAR.get(event.year, "frc_unknown"), set_number=match_dict.get("set_number", 0), match_number=match_dict.get("match_number", 0), comp_level=match_dict.get("comp_level", None), team_key_names=match_dict.get("team_key_names", None), time_string=match_dict.get("time_string", None), alliances_json=match_dict.get("alliances_json", None) ) for match_dict in match_dicts] MatchHelper.add_match_times(event, matches) return matches
def test_match_times(self): with open('test_data/usfirst_html/usfirst_event_matches_2013cama.html', 'r') as f: # using matches from a random event as data match_dicts, _ = UsfirstMatchesParser.parse(f.read()) matches = self.matchDictToMatches(match_dicts) MatchHelper.add_match_times(self.event, matches) self.assertEqual(len(matches), 92) PST_OFFSET = -5 self.assertEqual(matches[0].time, datetime.datetime(2014, 2, 28, 9, 0) - datetime.timedelta(hours=PST_OFFSET)) self.assertEqual(matches[75].time, datetime.datetime(2014, 3, 1, 11, 50) - datetime.timedelta(hours=PST_OFFSET))
def _render(self, event_key): event = Event.get_by_id(event_key) if not event: return self.redirect("/error/404") event.prepAwards() event.prepMatches() event.prepTeams() awards = AwardHelper.organizeAwards(event.awards) matches = MatchHelper.organizeMatches(event.matches) teams = TeamHelper.sortTeams(event.teams) num_teams = len(teams) middle_value = num_teams/2 if num_teams%2 != 0: middle_value += 1 teams_a, teams_b = teams[:middle_value], teams[middle_value:] oprs = sorted(zip(event.oprs,event.opr_teams), reverse=True) # sort by OPR oprs = oprs[:14] # get the top 15 OPRs if event.now: matches_recent = MatchHelper.recentMatches(event.matches) else: matches_recent = None bracket_table = {} qf_matches = matches['qf'] sf_matches = matches['sf'] f_matches = matches['f'] if qf_matches: bracket_table['qf'] = MatchHelper.generateBracket(qf_matches) if sf_matches: bracket_table['sf'] = MatchHelper.generateBracket(sf_matches) if f_matches: bracket_table['f'] = MatchHelper.generateBracket(f_matches) template_values = { "event": event, "matches": matches, "matches_recent": matches_recent, "awards": awards, "teams_a": teams_a, "teams_b": teams_b, "num_teams": num_teams, "oprs": oprs, "bracket_table": bracket_table, } path = os.path.join(os.path.dirname(__file__), '../templates/event_details.html') return template.render(path, template_values)
def test_2017scmb_sequence(self): event = Event( id='2017scmb', event_short='scmb', year=2017, event_type_enum=0, timezone_id='America/New_York' ) event.put() event_code = 'scmb' file_prefix = 'frc-api-response/v2.0/2017/schedule/{}/playoff/hybrid/'.format(event_code) context = ndb.get_context() result = context.urlfetch('https://www.googleapis.com/storage/v1/b/bucket/o?bucket=tbatv-prod-hrd.appspot.com&prefix={}'.format(file_prefix)).get_result() for item in json.loads(result.content)['items']: filename = item['name'] time_str = filename.replace(file_prefix, '').replace('.json', '').strip() file_time = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S.%f") query_time = file_time + datetime.timedelta(seconds=30) MatchManipulator.createOrUpdate(DatafeedFMSAPI('v2.0', sim_time=query_time).getMatches('2017{}'.format(event_code)), run_post_update_hook=False) MatchHelper.deleteInvalidMatches(event.matches, event) qf_matches = Match.query(Match.event == ndb.Key(Event, '2017scmb'), Match.comp_level == 'qf').fetch() self.assertEqual(len(qf_matches), 11) sf_matches = Match.query(Match.event == ndb.Key(Event, '2017scmb'), Match.comp_level == 'sf').fetch() self.assertEqual(len(sf_matches), 4) f_matches = Match.query(Match.event == ndb.Key(Event, '2017scmb'), Match.comp_level == 'f').fetch() self.assertEqual(len(f_matches), 3) self.assertEqual(Match.get_by_id('2017scmb_qf4m1').alliances['red']['score'], 305) self.assertEqual(Match.get_by_id('2017scmb_qf4m1').alliances['blue']['score'], 305) self.assertEqual(Match.get_by_id('2017scmb_qf4m1').score_breakdown['red']['totalPoints'], 305) self.assertEqual(Match.get_by_id('2017scmb_qf4m1').score_breakdown['blue']['totalPoints'], 305) self.assertEqual(Match.get_by_id('2017scmb_qf4m2').alliances['red']['score'], 213) self.assertEqual(Match.get_by_id('2017scmb_qf4m2').alliances['blue']['score'], 305) self.assertEqual(Match.get_by_id('2017scmb_qf4m2').score_breakdown['red']['totalPoints'], 213) self.assertEqual(Match.get_by_id('2017scmb_qf4m2').score_breakdown['blue']['totalPoints'], 305) self.assertEqual(Match.get_by_id('2017scmb_qf4m3').alliances['red']['score'], 312) self.assertEqual(Match.get_by_id('2017scmb_qf4m3').alliances['blue']['score'], 255) self.assertEqual(Match.get_by_id('2017scmb_qf4m3').score_breakdown['red']['totalPoints'], 312) self.assertEqual(Match.get_by_id('2017scmb_qf4m3').score_breakdown['blue']['totalPoints'], 255) self.assertEqual(Match.get_by_id('2017scmb_qf4m4').alliances['red']['score'], 310) self.assertEqual(Match.get_by_id('2017scmb_qf4m4').alliances['blue']['score'], 306) self.assertEqual(Match.get_by_id('2017scmb_qf4m4').score_breakdown['red']['totalPoints'], 310) self.assertEqual(Match.get_by_id('2017scmb_qf4m4').score_breakdown['blue']['totalPoints'], 306)
def test_match_times_dst(self): with open('test_data/usfirst_html/usfirst_event_matches_2012ct.html', 'r') as f: # using matches from a random event as data match_dicts, _ = UsfirstMatchesParser.parse(f.read()) matches = self.matchDictToMatches(match_dicts) MatchHelper.add_match_times(self.event_dst, matches) self.assertEqual(len(matches), 125) PST_OFFSET = -8 PDT_OFFSET = -7 self.assertEqual(matches[0].time, datetime.datetime(2014, 3, 8, 9, 0) - datetime.timedelta(hours=PST_OFFSET)) self.assertEqual(matches[-1].time, datetime.datetime(2014, 3, 9, 16, 5) - datetime.timedelta(hours=PDT_OFFSET))
def get(self, event_key): event = Event.get_by_id(event_key) if not event: self.abort(404) matches = event.matches if not matches: return timezone = pytz.timezone(event.timezone_id) played_matches = MatchHelper.recentMatches(matches, num=0) unplayed_matches = MatchHelper.upcomingMatches(matches, num=10) MatchTimePredictionHelper.predict_future_matches(played_matches, unplayed_matches, timezone, event.within_a_day)
def generateTeamAtEventStatusAsync(cls, team_key, event): """ Generate Team@Event status items :return: a tuple future <long summary string, qual record, qual ranking, playoff status> """ team_number = team_key[3:] event.prep_details() # We need all the event's playoff matches here to properly account for backup teams matches = yield EventMatchesQuery(event.key.id()).fetch_async() qual_match_count = 0 playoff_match_count = 0 playoff_matches = [] for match in matches: if match.comp_level in Match.ELIM_LEVELS: playoff_match_count += 1 playoff_matches.append(match) else: qual_match_count += 1 matches = MatchHelper.organizeMatches(playoff_matches) team_status = cls.generate_team_at_event_status(team_key, event, matches) rank_status = team_status.get('rank', None) alliance_status = team_status.get('alliance', None) playoff_status = team_status.get('playoff', None) # Playoff Status status, short_playoff_status = cls._get_playoff_status_string(team_key, alliance_status, playoff_status) # Still in quals or team did not make it to elims if not rank_status or rank_status.get('played', 0) == 0: # No matches played yet status = "Team {} has not played any matches yet.".format(team_number) if not status else status record = '?' rank_str = '?' else: # Compute rank & num_teams # Gets record from ranking data to account for surrogate matches rank = rank_status.get('rank', '?') ranking_points = rank_status.get('first_sort', '?') record = rank_status.get('record', '?') num_teams = rank_status.get('total', '?') rank_str = "Rank {} with {} RP".format(rank, ranking_points) alliance_name = alliance_status.get('name', '?') if alliance_status else '?' # Compute final long status for nightbot, if one isn't already there matches_per_team = qual_match_count // rank_status.get('total', 1) if rank_status.get('played', 0) - matches_per_team > 0 and not status: if rank is not None: status = "Team {} is currently rank {}/{} with a record of {} and {} ranking points.".format(team_number, rank, num_teams, record, ranking_points) else: status = "Team {} currently has a record of {}.".format(team_number, record) elif not status: if alliance_status is None and playoff_match_count == 0: status = "Team {} ended qualification matches at rank {}/{} with a record of {}.".format(team_number, rank, num_teams, record) elif alliance_status is None and playoff_match_count > 0: status = "Team {} ended qualification matches at rank {}/{} with a record of {} and was not picked for playoff matches.".format(team_number, rank, num_teams, record) else: status = "Team {} will be competing in the playoff matches on {}.".format(team_number, alliance_name) raise ndb.Return(status, record, rank_str, short_playoff_status)
def _render(self, team_number): self.response.headers['content-type'] = 'text/plain; charset="utf-8"' user = self.request.get('user') if user: user_str = '@{}, '.format(user) else: user_str = '' team_event_or_error = validate_team(user_str, team_number) if type(team_event_or_error) == str: return team_event_or_error _, event = team_event_or_error event_code_upper = event.event_short.upper() matches_future = TeamEventMatchesQuery('frc{}'.format(team_number), event.key.id()).fetch_async() matches = MatchHelper.play_order_sort_matches(matches_future.get_result()) # No match schedule yet if not matches: return "{}[{}] Team {} has no scheduled matches yet.".format(user_str, event_code_upper, team_number) next_match = None for match in matches: if not match.has_been_played: next_match = match break if next_match is None: return "{}[{}] Team {} has no more scheduled matches.".format(user_str, event_code_upper, team_number) return "{}[{}] Team {} will be playing in match {}.".format(user_str, event_code_upper, team_number, match.short_name)
def test_parse_2017micmp(self): # 2017micmp is a 4 team bracket that starts playoff match numbering at 1 self.event = Event( id="2017micmp", name="Michigan District Champs", event_type_enum=EventType.DISTRICT_CMP, short_name="Michigan", event_short="micmp", year=2017, end_date=datetime(2017, 03, 27), official=True, start_date=datetime(2017, 03, 24), timezone_id="America/New_York", playoff_type=PlayoffType.BRACKET_4_TEAM ) self.event.put() with open('test_data/fms_api/2017micmp_playoff_schedule.json', 'r') as f: matches, _ = FMSAPIHybridScheduleParser(2017, 'micmp').parse(json.loads(f.read())) self.assertTrue(isinstance(matches, list)) self.assertEquals(len(matches), 6) # Assert we get enough of each match type clean_matches = MatchHelper.organizeMatches(matches) self.assertEqual(len(clean_matches["ef"]), 0) self.assertEqual(len(clean_matches["qf"]), 0) self.assertEqual(len(clean_matches["sf"]), 4) self.assertEqual(len(clean_matches["f"]), 2)
def test_parse_foc_b05(self): self.event = Event( id="2017nhfoc", name="FIRST Festival of Champions", event_type_enum=EventType.CMP_FINALS, short_name="FIRST Festival of Champions", event_short="nhfoc", first_code="foc", year=2017, end_date=datetime(2017, 07, 29), official=True, start_date=datetime(2017, 07, 29), timezone_id="America/New_York", playoff_type=PlayoffType.BO5_FINALS ) self.event.put() with open('test_data/fms_api/2017foc_staging_hybrid_schedule_playoff.json', 'r') as f: matches, _ = FMSAPIHybridScheduleParser(2017, 'nhfoc').parse(json.loads(f.read())) self.assertTrue(isinstance(matches, list)) self.assertEquals(len(matches), 5) # Assert we get enough of each match type clean_matches = MatchHelper.organizeMatches(matches) self.assertEqual(len(clean_matches["ef"]), 0) self.assertEqual(len(clean_matches["qf"]), 0) self.assertEqual(len(clean_matches["sf"]), 0) self.assertEqual(len(clean_matches["f"]), 5) for i, match in enumerate(clean_matches['f']): self.assertEqual(match.set_number, 1) self.assertEqual(match.match_number, i+1)
def test_parse_playoff_with_octofinals(self): self.event = Event( id="2016micmp", name="Michigan District Champs", event_type_enum=EventType.DISTRICT_CMP, short_name="Michigan", event_short="micmp", year=2016, end_date=datetime(2016, 03, 27), official=True, start_date=datetime(2016, 03, 24), timezone_id="America/New_York" ) self.event.put() with open('test_data/fms_api/2016_micmp_staging_hybrid_schedule_playoff.json', 'r') as f: matches = FMSAPIHybridScheduleParser(2016, 'micmp').parse(json.loads(f.read())) self.assertTrue(isinstance(matches, list)) self.assertEquals(len(matches), 36) # Assert we get enough of each match type clean_matches = MatchHelper.organizeMatches(matches) self.assertEqual(len(clean_matches["ef"]), 20) self.assertEqual(len(clean_matches["qf"]), 10) self.assertEqual(len(clean_matches["sf"]), 4) self.assertEqual(len(clean_matches["f"]), 2)
def test_parse_2champs_einstein(self): self.event = Event( id="2017cmptx", name="Einstein (Houston)", event_type_enum=EventType.CMP_FINALS, short_name="Einstein", event_short="cmptx", year=2017, end_date=datetime(2017, 03, 27), official=True, start_date=datetime(2017, 03, 24), timezone_id="America/New_York", playoff_type=PlayoffType.ROUND_ROBIN_6_TEAM ) self.event.put() with open('test_data/fms_api/2017cmptx_staging_playoff_schedule.json', 'r') as f: matches, _ = FMSAPIHybridScheduleParser(2017, 'cmptx').parse(json.loads(f.read())) self.assertTrue(isinstance(matches, list)) self.assertEquals(len(matches), 18) # Assert we get enough of each match type clean_matches = MatchHelper.organizeMatches(matches) self.assertEqual(len(clean_matches["ef"]), 0) self.assertEqual(len(clean_matches["qf"]), 0) self.assertEqual(len(clean_matches["sf"]), 15) self.assertEqual(len(clean_matches["f"]), 3)
def previous_match(self): from helpers.match_helper import MatchHelper recent_matches = MatchHelper.recentMatches(self.matches, 1)[0] if recent_matches: return recent_matches[0] else: return None
def get(self, event_key): event = Event.get_by_id(event_key) matchstats_dict = MatchstatsHelper.calculate_matchstats(event.matches, event.year) if event.year == 2016: organized_matches = MatchHelper.organizeMatches(event.matches) match_predictions, match_prediction_stats = PredictionHelper.get_match_predictions(organized_matches['qm']) ranking_predictions, ranking_prediction_stats = PredictionHelper.get_ranking_predictions(organized_matches['qm'], match_predictions) matchstats_dict['match_predictions'] = match_predictions matchstats_dict['match_prediction_stats'] = match_prediction_stats matchstats_dict['ranking_predictions'] = ranking_predictions matchstats_dict['ranking_prediction_stats'] = ranking_prediction_stats if any([v != {} for v in matchstats_dict.values()]): event.matchstats_json = json.dumps(matchstats_dict) EventManipulator.createOrUpdate(event) else: logging.warn("Matchstat calculation for {} failed!".format(event_key)) template_values = { 'matchstats_dict': matchstats_dict, } path = os.path.join(os.path.dirname(__file__), '../templates/math/event_matchstats_do.html') self.response.out.write(template.render(path, template_values))
def test_cleanup(self): matches = setupMatches('test_data/cleanup_matches.csv') cleaned_matches = MatchHelper.deleteInvalidMatches(matches) indices = [9, 12, 26] for index in sorted(indices, reverse=True): #need to delete in reverse order so indices don't get messed up del matches[index] self.assertEqual(matches, cleaned_matches)
def test_parse_2015_playoff(self): self.event = Event( id="2015nyny", name="NYC Regional", event_type_enum=EventType.REGIONAL, short_name="NYC", event_short="nyny", year=2015, end_date=datetime(2015, 03, 27), official=True, start_date=datetime(2015, 03, 24), timezone_id="America/New_York", playoff_type=PlayoffType.AVG_SCORE_8_TEAM ) self.event.put() with open('test_data/fms_api/2015nyny_hybrid_schedule_playoff.json', 'r') as f: matches, _ = FMSAPIHybridScheduleParser(2015, 'nyny').parse(json.loads(f.read())) self.assertTrue(isinstance(matches, list)) self.assertEqual(len(matches), 17) # Assert we get enough of each match type clean_matches = MatchHelper.organizeMatches(matches) self.assertEqual(len(clean_matches["ef"]), 0) self.assertEqual(len(clean_matches["qf"]), 8) self.assertEqual(len(clean_matches["sf"]), 6) self.assertEqual(len(clean_matches["f"]), 3)
def next_match(self): from helpers.match_helper import MatchHelper upcoming_matches = MatchHelper.upcomingMatches(self.matches, 1) if upcoming_matches: return upcoming_matches[0] else: return None
def __init__(self, event): from helpers.match_helper import MatchHelper # recursive import issues self.event = event self._event_feed = event.key_name self._district_feed = event.event_district_abbrev upcoming = MatchHelper.upcomingMatches(event.matches, 1) self.next_match = upcoming[0] if upcoming[0] else None
def get_event_winners(cls, event, matches): """ First alliance to win two finals matches is the winner """ matches_by_type = MatchHelper.organizeMatches(matches) if 'f' not in matches_by_type or not matches_by_type['f']: return set() finals_matches = matches_by_type['f'] red_wins = 0 blue_wins = 0 for match in finals_matches: if match.has_been_played: if match.winning_alliance == 'red': red_wins += 1 elif match.winning_alliance == 'blue': blue_wins += 1 winning_teams = set() if red_wins >= 2: winning_teams = set(finals_matches[0].alliances['red']['teams']) elif blue_wins >= 2: winning_teams = set(finals_matches[0].alliances['blue']['teams']) # Return the entire alliance alliance_selections = event.alliance_selections if alliance_selections: for alliance in alliance_selections: if len(winning_teams.intersection(set(alliance['picks']))) >= 2: complete_alliance = set(alliance['picks']) if alliance else set() if alliance and alliance.get('backup'): complete_alliance.add(alliance['backup']['in']) return complete_alliance # Fall back to the match winners return winning_teams
def _render(self, event_key): event = Event.get_by_id(event_key) if not event: self.abort(404) return medias_future = media_query.EventTeamsPreferredMediasQuery(event_key).fetch_async() next_match = MatchHelper.upcomingMatches(event.matches, num=1) next_match = next_match[0] if next_match else None team_and_medias = [] if next_match: # Organize medias by team teams = ndb.get_multi([ndb.Key(Team, team_key) for team_key in next_match.alliances['red']['teams'] + next_match.alliances['blue']['teams']]) image_medias = MediaHelper.get_images([media for media in medias_future.get_result()]) team_medias = defaultdict(list) for image_media in image_medias: for reference in image_media.references: team_medias[reference].append(image_media) stations = ['Red 1', 'Red 2', 'Red 3', 'Blue 1', 'Blue 2', 'Blue 3'] for i, team in enumerate(teams): team_and_medias.append((team, stations[i], team_medias.get(team.key, []))) self.template_values.update({ 'event': event, 'next_match': next_match, 'teams_and_media': team_and_medias, }) return jinja2_engine.render('nextmatch.html', self.template_values)
def _render(self, team_number): team = Team.get_by_id("frc" + team_number) if not team: return self.redirect("/error/404") event_team_keys_future = EventTeam.query(EventTeam.team == team.key).fetch_async(1000, keys_only=True) award_keys_future = Award.query(Award.team == team.key).fetch_async(1000, keys_only=True) event_teams_futures = ndb.get_multi_async(event_team_keys_future.get_result()) awards_futures = ndb.get_multi_async(award_keys_future.get_result()) event_keys = [event_team_future.get_result().event for event_team_future in event_teams_futures] events_futures = ndb.get_multi_async(event_keys) awards_by_event = {} for award_future in awards_futures: award = award_future.get_result() if award.event.id() not in awards_by_event: awards_by_event[award.event.id()] = [award] else: awards_by_event[award.event.id()].append(award) event_awards = [] current_event = None matches_upcoming = None short_cache = False for event_future in events_futures: event = event_future.get_result() if event.now: current_event = event team_matches_future = Match.query(Match.event == event.key, Match.team_key_names == team.key_name)\ .fetch_async(500, keys_only=True) matches = ndb.get_multi(team_matches_future.get_result()) matches_upcoming = MatchHelper.upcomingMatches(matches) if event.within_a_day: short_cache = True if event.key_name in awards_by_event: sorted_awards = AwardHelper.organizeAwards(awards_by_event[event.key_name])['list'] else: sorted_awards = [] event_awards.append((event, sorted_awards)) event_awards = sorted(event_awards, key=lambda (e, _): e.start_date if e.start_date else datetime.datetime(e.year, 12, 31)) years = sorted(set([et.get_result().year for et in event_teams_futures if et.get_result().year != None])) template_values = {'team': team, 'event_awards': event_awards, 'years': years, 'current_event': current_event, 'matches_upcoming': matches_upcoming} if short_cache: self._cache_expiration = self.SHORT_CACHE_EXPIRATION path = os.path.join(os.path.dirname(__file__), '../templates/team_history.html') return template.render(path, template_values)
def render_team_details(cls, handler, team, year, is_canonical): media_key_futures = Media.query(Media.references == team.key, Media.year == year).fetch_async( 500, keys_only=True) events_sorted, matches_by_event_key, awards_by_event_key, valid_years = TeamDetailsDataFetcher.fetch( team, year, return_valid_years=True) if not events_sorted: return None media_futures = ndb.get_multi_async(media_key_futures.get_result()) participation = [] year_wlt_list = [] year_match_avg_list = [] current_event = None matches_upcoming = None short_cache = False for event in events_sorted: event_matches = matches_by_event_key.get(event.key, []) event_awards = AwardHelper.organizeAwards( awards_by_event_key.get(event.key, [])) matches_organized = MatchHelper.organizeMatches(event_matches) if event.now: current_event = event matches_upcoming = MatchHelper.upcomingMatches(event_matches) if event.within_a_day: short_cache = True if year == 2015: display_wlt = None match_avg = EventHelper.calculateTeamAvgScoreFromMatches( team.key_name, event_matches) year_match_avg_list.append(match_avg) qual_avg, elim_avg, _, _ = match_avg else: qual_avg = None elim_avg = None wlt = EventHelper.calculateTeamWLTFromMatches( team.key_name, event_matches) year_wlt_list.append(wlt) if wlt["win"] + wlt["loss"] + wlt["tie"] == 0: display_wlt = None else: display_wlt = wlt team_rank = None if event.rankings: for element in event.rankings: if str(element[1]) == str(team.team_number): team_rank = element[0] break participation.append({ 'event': event, 'matches': matches_organized, 'wlt': display_wlt, 'qual_avg': qual_avg, 'elim_avg': elim_avg, 'rank': team_rank, 'awards': event_awards }) if year == 2015: year_wlt = None year_qual_scores = [] year_elim_scores = [] for _, _, event_qual_scores, event_elim_scores in year_match_avg_list: year_qual_scores += event_qual_scores year_elim_scores += event_elim_scores year_qual_avg = float(sum(year_qual_scores)) / len( year_qual_scores) if year_qual_scores != [] else None year_elim_avg = float(sum(year_elim_scores)) / len( year_elim_scores) if year_elim_scores != [] else None else: year_qual_avg = None year_elim_avg = None year_wlt = {"win": 0, "loss": 0, "tie": 0} for wlt in year_wlt_list: year_wlt["win"] += wlt["win"] year_wlt["loss"] += wlt["loss"] year_wlt["tie"] += wlt["tie"] if year_wlt["win"] + year_wlt["loss"] + year_wlt["tie"] == 0: year_wlt = None medias_by_slugname = MediaHelper.group_by_slugname( [media_future.get_result() for media_future in media_futures]) handler.template_values.update({ "is_canonical": is_canonical, "team": team, "participation": participation, "year": year, "years": valid_years, "year_wlt": year_wlt, "year_qual_avg": year_qual_avg, "year_elim_avg": year_elim_avg, "current_event": current_event, "matches_upcoming": matches_upcoming, "medias_by_slugname": medias_by_slugname }) if short_cache: handler._cache_expiration = handler.SHORT_CACHE_EXPIRATION path = os.path.join(os.path.dirname(__file__), '../templates/team_details.html') return template.render(path, handler.template_values)
def render_team_history(cls, handler, team, is_canonical): hof_award_future = award_query.TeamEventTypeAwardsQuery( team.key.id(), EventType.CMP_FINALS, AwardType.CHAIRMANS).fetch_async() hof_video_future = media_query.TeamTagMediasQuery( team.key.id(), MediaTag.CHAIRMANS_VIDEO).fetch_async() hof_presentation_future = media_query.TeamTagMediasQuery( team.key.id(), MediaTag.CHAIRMANS_PRESENTATION).fetch_async() hof_essay_future = media_query.TeamTagMediasQuery( team.key.id(), MediaTag.CHAIRMANS_ESSAY).fetch_async() award_futures = award_query.TeamAwardsQuery( team.key.id()).fetch_async() event_futures = event_query.TeamEventsQuery( team.key.id()).fetch_async() participation_future = team_query.TeamParticipationQuery( team.key.id()).fetch_async() social_media_future = media_query.TeamSocialMediaQuery( team.key.id()).fetch_async() hof_awards = hof_award_future.get_result() hof_video = hof_video_future.get_result() hof_presentation = hof_presentation_future.get_result() hof_essay = hof_essay_future.get_result() hall_of_fame = { "is_hof": len(hof_awards) > 0, "years": [award.year for award in hof_awards], "media": { "video": hof_video[0].youtube_url_link if len(hof_video) > 0 else None, "presentation": hof_presentation[0].youtube_url_link if len(hof_presentation) > 0 else None, "essay": hof_essay[0].external_link if len(hof_essay) > 0 else None, }, } awards_by_event = {} for award in award_futures.get_result(): if award.event.id() not in awards_by_event: awards_by_event[award.event.id()] = [award] else: awards_by_event[award.event.id()].append(award) event_awards = [] current_event = None matches_upcoming = None short_cache = False years = set() for event in event_futures.get_result(): years.add(event.year) if event.now: current_event = event matches = match_query.TeamEventMatchesQuery( team.key.id(), event.key.id()).fetch() matches_upcoming = MatchHelper.upcomingMatches(matches) if event.within_a_day: short_cache = True if event.key_name in awards_by_event: sorted_awards = AwardHelper.organizeAwards( awards_by_event[event.key_name]) else: sorted_awards = [] event_awards.append((event, sorted_awards)) event_awards = sorted( event_awards, key=lambda (e, _): e.start_date if e.start_date else datetime.datetime(e.year, 12, 31)) last_competed = None participation_years = participation_future.get_result() if len(participation_years) > 0: last_competed = max(participation_years) current_year = datetime.date.today().year social_medias = sorted(social_media_future.get_result(), key=MediaHelper.social_media_sorter) handler.template_values.update({ "is_canonical": is_canonical, "team": team, "event_awards": event_awards, "years": sorted(years), "social_medias": social_medias, "current_event": current_event, "matches_upcoming": matches_upcoming, "last_competed": last_competed, "current_year": current_year, "max_year": tba_config.MAX_YEAR, "hof": hall_of_fame, }) if short_cache: handler._cache_expiration = handler.SHORT_CACHE_EXPIRATION return jinja2_engine.render("team_history.html", handler.template_values)
def get_ranking_predictions(cls, matches, match_predictions, n=1000): matches = MatchHelper.organizeMatches(matches)['qm'] if not matches or not match_predictions: return None, None match_predictions = match_predictions.get('qual') if not match_predictions: return None, None # Calc surrogates match_counts = defaultdict(int) for match in matches: for alliance_color in ['red', 'blue']: for team in match.alliances[alliance_color]['teams']: match_counts[team] += 1 num_matches = min(match_counts.values()) surrogate_teams = set() for k, v in match_counts.items(): if v > num_matches: surrogate_teams.add(k) # Calculate ranking points and tiebreakers all_rankings = defaultdict(lambda: [0] * n) all_ranking_points = defaultdict(lambda: [0] * n) last_played_match = None for i in xrange(n): team_ranking_points = defaultdict(int) team_rank_tiebreaker = defaultdict(int) num_played = defaultdict(int) for match in matches: for alliance_color in ['red', 'blue']: for team in match.alliances[alliance_color]['teams']: num_played[team] += 1 sampled_rp1 = { 'red': False, 'blue': False, } sampled_rp2 = { 'red': False, 'blue': False, } sampled_tiebreaker = { 'red': 0, 'blue': 0, } # Get actual results or sampled results, depending if match has been played if match.has_been_played: if not match.score_breakdown: # Can't do rankings without score breakdown return None, None last_played_match = match.key.id() sampled_winner = match.winning_alliance for alliance_color in ['red', 'blue']: if match.year == 2016: sampled_rp1[ alliance_color] = match.score_breakdown[ alliance_color]['teleopDefensesBreached'] sampled_rp2[ alliance_color] = match.score_breakdown[ alliance_color]['teleopTowerCaptured'] sampled_tiebreaker[ alliance_color] = match.score_breakdown[ alliance_color]['autoPoints'] elif match.year == 2017: sampled_rp1[ alliance_color] = match.score_breakdown[ alliance_color]['kPaRankingPointAchieved'] sampled_rp2[alliance_color] = match.score_breakdown[ alliance_color]['rotorRankingPointAchieved'] sampled_tiebreaker[ alliance_color] = match.score_breakdown[ alliance_color]['totalPoints'] elif match.year == 2018: sampled_rp1[ alliance_color] = match.score_breakdown[ alliance_color]['autoQuestRankingPoint'] sampled_rp2[ alliance_color] = match.score_breakdown[ alliance_color]['faceTheBossRankingPoint'] sampled_tiebreaker[ alliance_color] = match.score_breakdown[ alliance_color]['totalPoints'] elif match.year == 2019: sampled_rp1[alliance_color] = match.score_breakdown[ alliance_color]['completeRocketRankingPoint'] sampled_rp2[ alliance_color] = match.score_breakdown[ alliance_color]['habDockingRankingPoint'] sampled_tiebreaker[ alliance_color] = match.score_breakdown[ alliance_color]['totalPoints'] else: prediction = match_predictions[match.key.id()] if np.random.uniform(high=1) < prediction['prob']: sampled_winner = prediction['winning_alliance'] else: if prediction['winning_alliance'] == 'red': sampled_winner = 'blue' elif prediction['winning_alliance'] == 'blue': sampled_winner = 'red' for alliance_color in ['red', 'blue']: if match.year == 2016: sampled_rp1[alliance_color] = np.random.uniform( high=1 ) < prediction[alliance_color]['prob_breach'] sampled_rp2[alliance_color] = np.random.uniform( high=1 ) < prediction[alliance_color]['prob_capture'] sampled_tiebreaker[alliance_color] = prediction[ alliance_color]['auto_points'] elif match.year == 2017: sampled_rp1[alliance_color] = np.random.uniform( high=1 ) < prediction[alliance_color]['prob_pressure'] sampled_rp2[alliance_color] = np.random.uniform( high=1 ) < prediction[alliance_color]['prob_gears'] sampled_tiebreaker[alliance_color] = prediction[ alliance_color]['score'] elif match.year == 2018: sampled_rp1[alliance_color] = np.random.uniform( high=1 ) < prediction[alliance_color]['prob_auto_quest'] sampled_rp2[alliance_color] = np.random.uniform( high=1 ) < prediction[alliance_color]['prob_face_boss'] sampled_tiebreaker[alliance_color] = prediction[ alliance_color]['score'] elif match.year == 2019: sampled_rp1[alliance_color] = np.random.uniform( high=1) < prediction[alliance_color][ 'prob_complete_rocket'] sampled_rp2[alliance_color] = np.random.uniform( high=1 ) < prediction[alliance_color]['prob_hab_docking'] sampled_tiebreaker[alliance_color] = prediction[ alliance_color]['score'] # Using match results, update RP and tiebreaker for alliance_color in ['red', 'blue']: for team in match.alliances[alliance_color]['teams']: if team in surrogate_teams and num_played[team] == 3: continue if sampled_rp1[alliance_color]: team_ranking_points[team] += 1 if sampled_rp2[alliance_color]: team_ranking_points[team] += 1 team_rank_tiebreaker[team] += sampled_tiebreaker[ alliance_color] if sampled_winner == '': for alliance_color in ['red', 'blue']: for team in match.alliances[alliance_color]['teams']: if team in surrogate_teams and num_played[ team] == 3: continue team_ranking_points[team] += 1 else: for team in match.alliances[sampled_winner]['teams']: if team in surrogate_teams and num_played[team] == 3: continue team_ranking_points[team] += 2 sampled_loser = 'red' if sampled_winner == 'blue' else 'blue' for team in match.alliances[sampled_loser]['teams']: team_ranking_points[team] += 0 # Compute ranks for this sample sample_rankings = sorted(team_ranking_points.items(), key=lambda x: -team_rank_tiebreaker[x[0]] ) # Sort by tiebreaker. sample_rankings = sorted( sample_rankings, key=lambda x: -x[1]) # Sort by RP. Sort is stable. for rank, (team, ranking_points) in enumerate(sample_rankings): all_rankings[team][i] = rank + 1 all_ranking_points[team][i] = ranking_points rankings = {} for team, team_rankings in all_rankings.items(): avg_rank = np.mean(team_rankings) min_rank = min(team_rankings) median_rank = np.median(team_rankings) max_rank = max(team_rankings) avg_rp = np.mean(all_ranking_points[team]) min_rp = min(all_ranking_points[team]) max_rp = max(all_ranking_points[team]) rankings[team] = (avg_rank, min_rank, median_rank, max_rank, avg_rp, min_rp, max_rp) ranking_predictions = sorted(rankings.items(), key=lambda x: x[1][0]) # Sort by avg_rank ranking_stats = {'last_played_match': last_played_match} return ranking_predictions, ranking_stats
def parse(self, matches_json, year): """ Parse JSON that contains a list of matches for a given year where each match is a dict of: comp_level: String in the set {"qm", "ef", "qf", "sf", "f"} set_number: Integer identifying the elim set number. Ignored for qual matches. ex: the 4 in qf4m2 match_number: Integer identifying the match number within a set. ex: the 2 in qf4m2 alliances: Dict of {'red': {'teams': ['frcXXX'...], 'score': S}, 'blue': {...}}. Where scores (S) are integers. Null scores indicate that a match has not yet been played. score_breakdown: Dict of {'red': {K1: V1, K2: V2, ...}, 'blue': {...}}. Where Kn are keys and Vn are values for those keys. time_string: String in the format "(H)H:MM AM/PM" for when the match will be played in the event's local timezone. ex: "9:15 AM" time: UTC time of the match as a string in ISO 8601 format (YYYY-MM-DDTHH:MM:SS). """ try: matches = json.loads(matches_json) except: raise ParserInputException("Invalid JSON. Please check input.") parsed_matches = [] for match in matches: if type(match) is not dict: raise ParserInputException("Matches must be dicts.") comp_level = match.get('comp_level', None) set_number = match.get('set_number', None) match_number = match.get('match_number', None) alliances = match.get('alliances', None) score_breakdown = match.get('score_breakdown', None) time_string = match.get('time_string', None) time_utc = match.get('time_utc', None) if comp_level is None: raise ParserInputException("Match must have a 'comp_level'") if comp_level not in Match.COMP_LEVELS: raise ParserInputException( "'comp_level' must be one of: {}".format( Match.COMP_LEVELS)) if comp_level == 'qm': set_number = 1 elif set_number is None or type(set_number) is not int: raise ParserInputException( "Match must have an integer 'set_number'") if match_number is None or type(match_number) is not int: raise ParserInputException( "Match must have an integer 'match_number'") if type(alliances) is not dict: raise ParserInputException("'alliances' must be a dict") else: for color, details in alliances.items(): if color not in {'red', 'blue'}: raise ParserInputException( "Alliance color '{}' not recognized".format(color)) if 'teams' not in details: raise ParserInputException( "alliances[color] must have key 'teams'") if 'score' not in details: raise ParserInputException( "alliances[color] must have key 'score'") for team_key in details['teams']: if not re.match(r'frc\d+', str(team_key)): raise ParserInputException( "Bad team: '{}'. Must follow format 'frcXXX'.". format(team_key)) if details['score'] is not None and type( details['score']) is not int: raise ParserInputException( "alliances[color]['score'] must be an integer or null" ) if score_breakdown is not None: if type(score_breakdown) is not dict: raise ParserInputException( "'score_breakdown' must be a dict") else: for color, breakdown in score_breakdown.items(): if color not in {'red', 'blue'}: raise ParserInputException( "Alliance color '{}' not recognized".format( color)) for k in breakdown.keys(): is_valid = MatchHelper.is_valid_score_breakdown_key( k, year) if is_valid != True: raise ParserInputException( "Valid score breakdowns for {} are: {}". format(year, is_valid)) datetime_utc = None if time_utc is not None: try: import iso8601 datetime_utc = iso8601.parse_date(time_utc) # remove timezone info because DatetimeProperty can't handle timezones datetime_utc = datetime_utc.replace(tzinfo=None) except Exception: raise ParserInputException( "Could not parse 'time_utc'. Check that it is in ISO 8601 format." ) # validation passed. build new dicts to sanitize parsed_alliances = { 'red': { 'teams': alliances['red']['teams'], 'score': alliances['red']['score'], }, 'blue': { 'teams': alliances['blue']['teams'], 'score': alliances['blue']['score'], }, } parsed_match = { 'comp_level': comp_level, 'set_number': set_number, 'match_number': match_number, 'alliances_json': json.dumps(parsed_alliances), 'score_breakdown_json': json.dumps(score_breakdown) if score_breakdown is not None else None, 'time_string': time_string, 'time': datetime_utc, 'team_key_names': parsed_alliances['red']['teams'] + parsed_alliances['blue']['teams'], } parsed_matches.append(parsed_match) return parsed_matches
def _render(self, event_key): event = Event.get_by_id(event_key) if not event or event.year < 2016 or not event.details.predictions: self.abort(404) event.get_matches_async() match_predictions = event.details.predictions.get('match_predictions', None) match_prediction_stats = event.details.predictions.get('match_prediction_stats', None) ranking_predictions = event.details.predictions.get('ranking_predictions', None) ranking_prediction_stats = event.details.predictions.get('ranking_prediction_stats', None) cleaned_matches = MatchHelper.deleteInvalidMatches(event.matches, event) matches = MatchHelper.organizeMatches(cleaned_matches) # If no matches but there are match predictions, create fake matches # For cases where FIRST doesn't allow posting of match schedule fake_matches = False if match_predictions and (not matches['qm'] and match_predictions['qual']): fake_matches = True for i in xrange(len(match_predictions['qual'].keys())): match_number = i + 1 alliances = { 'red': { 'score': -1, 'teams': ['frc?', 'frc?', 'frc?'] }, 'blue': { 'score': -1, 'teams': ['frc?', 'frc?', 'frc?'] } } matches['qm'].append(Match( id=Match.renderKeyName( event_key, 'qm', 1, match_number), event=event.key, year=event.year, set_number=1, match_number=match_number, comp_level='qm', alliances_json=json.dumps(alliances), )) # Add actual scores to predictions distribution_info = {} for comp_level in Match.COMP_LEVELS: level = 'qual' if comp_level == 'qm' else 'playoff' for match in matches[comp_level]: distribution_info[match.key.id()] = { 'level': level, 'red_actual_score': match.alliances['red']['score'], 'blue_actual_score': match.alliances['blue']['score'], 'red_mean': match_predictions[level][match.key.id()]['red']['score'], 'blue_mean': match_predictions[level][match.key.id()]['blue']['score'], 'red_var': match_predictions[level][match.key.id()]['red']['score_var'], 'blue_var': match_predictions[level][match.key.id()]['blue']['score_var'], } last_played_match_num = None if ranking_prediction_stats: last_played_match_key = ranking_prediction_stats.get('last_played_match', None) if last_played_match_key: last_played_match_num = last_played_match_key.split('_qm')[1] self.template_values.update({ "event": event, "matches": matches, "fake_matches": fake_matches, "match_predictions": match_predictions, "distribution_info_json": json.dumps(distribution_info), "match_prediction_stats": match_prediction_stats, "ranking_predictions": ranking_predictions, "ranking_prediction_stats": ranking_prediction_stats, "last_played_match_num": last_played_match_num, }) if event.within_a_day: self._cache_expiration = self.SHORT_CACHE_EXPIRATION return jinja2_engine.render('event_insights.html', self.template_values)
def __init__(self, has_event_details=True, batch_advance=False): self._step = 0 self._substep = 0 # whether to update rankings and alliance selections self._has_event_details = has_event_details # whether to update next playoff level all at once, or as winners are determined self._batch_advance = batch_advance # Load and save complete data load_fixture('test_data/fixtures/2016nytr_event_team_status.json', kind={ 'EventDetails': EventDetails, 'Event': Event, 'Match': Match }, post_processor=self._event_key_adder) event = Event.get_by_id('2016nytr') # Add 3rd matches that never got played unplayed_matches = [ Match( id='2016nytr_qf1m3', year=2016, event=event.key, comp_level='qf', set_number=1, match_number=3, alliances_json=json.dumps({ 'red': { 'teams': ['frc3990', 'frc359', 'frc4508'], 'score': -1, }, 'blue': { 'teams': ['frc3044', 'frc4930', 'frc4481'], 'score': -1, } }), time=datetime.datetime(2016, 3, 19, 18, 34), ), Match( id='2016nytr_qf3m3', year=2016, event=event.key, comp_level='qf', set_number=3, match_number=3, alliances_json=json.dumps({ 'red': { 'teams': ['frc20', 'frc5254', 'frc229'], 'score': -1, }, 'blue': { 'teams': ['frc3003', 'frc358', 'frc527'], 'score': -1, } }), time=datetime.datetime(2016, 3, 19, 18, 48), ), Match( id='2016nytr_sf1m3', year=2016, event=event.key, comp_level='sf', set_number=1, match_number=3, alliances_json=json.dumps({ 'red': { 'teams': ['frc3990', 'frc359', 'frc4508'], 'score': -1, }, 'blue': { 'teams': ['frc5240', 'frc3419', 'frc663'], 'score': -1, } }), time=datetime.datetime(2016, 3, 19, 19, 42), ) ] self._event_details = event.details self._alliance_selections_without_backup = copy.deepcopy( event.details.alliance_selections) self._alliance_selections_without_backup[1]['backup'] = None self._played_matches = MatchHelper.organizeMatches(event.matches) self._all_matches = MatchHelper.organizeMatches(event.matches + unplayed_matches) # Delete data event.details.key.delete() ndb.delete_multi([match.key for match in event.matches]) ndb.get_context().clear_cache() # Used to keep track of non-batch advancement self._advancement_alliances = defaultdict(dict)
def get(self, event_key): # Fetch for later event_future = Event.get_by_id_async(event_key) matches_future = match_query.EventMatchesQuery(event_key).fetch_async() # Rebuild event teams taskqueue.add(url='/tasks/math/do/eventteam_update/' + event_key, method='GET') # Create Winner/Finalist awards for offseason events awards = [] event = event_future.get_result() if event.event_type_enum in {EventType.OFFSEASON, EventType.FOC}: matches = MatchHelper.organizeMatches(matches_future.get_result()) bracket = PlayoffAdvancementHelper.generateBracket( matches, event, event.alliance_selections) if 'f' in bracket: winning_alliance = '{}_alliance'.format( bracket['f']['f1']['winning_alliance']) if winning_alliance == 'red_alliance': losing_alliance = 'blue_alliance' else: losing_alliance = 'red_alliance' awards.append( Award( id=Award.render_key_name(event.key_name, AwardType.WINNER), name_str="Winner", award_type_enum=AwardType.WINNER, year=event.year, event=event.key, event_type_enum=event.event_type_enum, team_list=[ ndb.Key(Team, 'frc{}'.format(team)) for team in bracket['f']['f1'][winning_alliance] if team.isdigit() ], recipient_json_list=[ json.dumps({ 'team_number': team, 'awardee': None }) for team in bracket['f']['f1'][winning_alliance] ], )) awards.append( Award( id=Award.render_key_name(event.key_name, AwardType.FINALIST), name_str="Finalist", award_type_enum=AwardType.FINALIST, year=event.year, event=event.key, event_type_enum=event.event_type_enum, team_list=[ ndb.Key(Team, 'frc{}'.format(team)) for team in bracket['f']['f1'][losing_alliance] if team.isdigit() ], recipient_json_list=[ json.dumps({ 'team_number': team, 'awardee': None }) for team in bracket['f']['f1'][losing_alliance] ], )) AwardManipulator.createOrUpdate(awards) self.response.out.write( "Finished post-event tasks for {}. Created awards: {}".format( event_key, awards))
def calc_rank_based_match_points(cls, event, district_points, match_futures, POINTS_MULTIPLIER): """ Calculates match district points based on team ranking This algorithm was introduced for the 2015 season and also used for 2016 See: http://www.firstinspires.org/node/7616 and also http://www.firstinspires.org/robotics/frc/blog/Admin-Manual-Section-7-and-the-FIRST-STRONGHOLD-Logo """ from helpers.match_helper import MatchHelper # circular import issue # qual match points are calculated by rank if event.rankings and len(event.rankings) > 1: rankings = event.rankings[1:] # skip title row num_teams = len(rankings) alpha = 1.07 for row in rankings: rank = int(row[0]) team = 'frc{}'.format(row[1]) qual_points = int(np.ceil(cls.inverf(float(num_teams - 2 * rank + 2) / (alpha * num_teams)) * ( 10.0 / cls.inverf(1.0 / alpha)) + 12)) district_points['points'][team]['qual_points'] = qual_points * POINTS_MULTIPLIER else: logging.warning("Event {} has no rankings for qual_points calculations!".format(event.key.id())) matches = MatchHelper.organizeMatches([mf.get_result() for mf in match_futures]) # qual match calculations. only used for tiebreaking for match in matches['qm']: for color in ['red', 'blue']: for team in match.alliances[color]['teams']: score = match.alliances[color]['score'] district_points['tiebreakers'][team]['highest_qual_scores'] = heapq.nlargest(3, district_points[ 'tiebreakers'][team]['highest_qual_scores'] + [score]) # elim match point calculations # count number of matches played per team per comp level num_played = defaultdict(lambda: defaultdict(int)) for level in ['qf', 'sf']: for match in matches[level]: if not match.has_been_played: continue for color in ['red', 'blue']: for team in match.alliances[color]['teams']: num_played[level][team] += 1 # qf and sf points advancement = MatchHelper.generatePlayoffAdvancement2015(matches) for last_level, level in [('qf', 'sf'), ('sf', 'f')]: for (teams, _, _) in advancement[last_level]: teams = ['frc{}'.format(t) for t in teams] done = False for match in matches[level]: for color in ['red', 'blue']: if set(teams).intersection(set(match.alliances[color]['teams'])) != set(): for team in teams: points = DistrictPointValues.QF_WIN.get(event.year, DistrictPointValues.QF_WIN_DEFAULT) if last_level == 'qf' else DistrictPointValues.SF_WIN.get(event.year, DistrictPointValues.SF_WIN_DEFAULT) district_points['points'][team]['elim_points'] += int( np.ceil(points * num_played[last_level][team])) * POINTS_MULTIPLIER done = True break if done: break if done: break # final points num_wins = {'red': 0, 'blue': 0} team_matches_played = {'red': [], 'blue': []} for match in matches['f']: if not match.has_been_played or match.winning_alliance == '': continue num_wins[match.winning_alliance] += 1 for team in match.alliances[match.winning_alliance]['teams']: team_matches_played[match.winning_alliance].append(team) if num_wins[match.winning_alliance] >= 2: points = DistrictPointValues.F_WIN.get(event.year, DistrictPointValues.F_WIN_DEFAULT) for team in team_matches_played[match.winning_alliance]: district_points['points'][team]['elim_points'] += points * POINTS_MULTIPLIER
def step(self): event = Event.get_by_id('2016nytr') if self._step == 0: # Qual match schedule added for match in copy.deepcopy(self._all_matches['qm']): for alliance in ['red', 'blue']: match.alliances[alliance]['score'] = -1 match.alliances_json = json.dumps(match.alliances) match.score_breakdown_json = None match.actual_time = None MatchManipulator.createOrUpdate(match) self._step += 1 elif self._step == 1: # After each qual match MatchManipulator.createOrUpdate( self._played_matches['qm'][self._substep]) if self._substep < len(self._played_matches['qm']) - 1: self._substep += 1 else: self._step += 1 self._substep = 0 EventDetailsManipulator.createOrUpdate(EventDetails(id='2016nytr')) elif self._step == 2: # After alliance selections EventDetailsManipulator.createOrUpdate( EventDetails(id='2016nytr', alliance_selections=self. _alliance_selections_without_backup)) self._step += 1 elif self._step == 3: # QF schedule added for match in copy.deepcopy(self._all_matches['qf']): for alliance in ['red', 'blue']: match.alliances[alliance]['score'] = -1 match.alliances_json = json.dumps(match.alliances) match.score_breakdown_json = None match.actual_time = None MatchManipulator.createOrUpdate(match) self._step += 1 elif self._step == 4: # After each QF match new_match = MatchHelper.play_order_sort_matches( self._played_matches['qf'])[self._substep] MatchManipulator.createOrUpdate(new_match) if not self._batch_advance: win_counts = { 'red': 0, 'blue': 0, } for i in xrange(new_match.match_number): win_counts[Match.get_by_id( Match.renderKeyName(new_match.event.id(), new_match.comp_level, new_match.set_number, i + 1)).winning_alliance] += 1 for alliance, wins in win_counts.items(): if wins == 2: s = new_match.set_number if s in {1, 2}: self._advancement_alliances[ 'sf1']['red' if s == 1 else 'blue'] = new_match.alliances[ alliance]['teams'] elif s in {3, 4}: self._advancement_alliances[ 'sf2']['red' if s == 3 else 'blue'] = new_match.alliances[ alliance]['teams'] else: raise Exception("Invalid set number: {}".format(s)) for match_set, alliances in self._advancement_alliances.items( ): if match_set.startswith('sf'): for i in xrange(3): for match in copy.deepcopy( self._all_matches['sf']): key = '2016nytr_{}m{}'.format( match_set, i + 1) if match.key.id() == key: for color in ['red', 'blue']: match.alliances[color][ 'score'] = -1 match.alliances[color][ 'teams'] = alliances.get( color, []) match.alliances_json = json.dumps( match.alliances) match.score_breakdown_json = None match.actual_time = None MatchManipulator.createOrUpdate( match) if self._substep < len(self._played_matches['qf']) - 1: self._substep += 1 else: self._step += 1 if self._batch_advance else 2 self._substep = 0 elif self._step == 5: # SF schedule added if self._batch_advance: for match in copy.deepcopy(self._all_matches['sf']): for alliance in ['red', 'blue']: match.alliances[alliance]['score'] = -1 match.alliances_json = json.dumps(match.alliances) match.score_breakdown_json = None match.actual_time = None MatchManipulator.createOrUpdate(match) self._step += 1 elif self._step == 6: # After each SF match new_match = MatchHelper.play_order_sort_matches( self._played_matches['sf'])[self._substep] MatchManipulator.createOrUpdate(new_match) if not self._batch_advance: win_counts = { 'red': 0, 'blue': 0, } for i in xrange(new_match.match_number): win_counts[Match.get_by_id( Match.renderKeyName(new_match.event.id(), new_match.comp_level, new_match.set_number, i + 1)).winning_alliance] += 1 for alliance, wins in win_counts.items(): if wins == 2: self._advancement_alliances['f1'][ 'red' if new_match.set_number == 1 else 'blue'] = new_match.alliances[alliance]['teams'] for match_set, alliances in self._advancement_alliances.items( ): if match_set.startswith('f'): for i in xrange(3): for match in copy.deepcopy( self._all_matches['f']): key = '2016nytr_{}m{}'.format( match_set, i + 1) if match.key.id() == key: for color in ['red', 'blue']: match.alliances[color][ 'score'] = -1 match.alliances[color][ 'teams'] = alliances.get( color, []) match.alliances_json = json.dumps( match.alliances) match.score_breakdown_json = None match.actual_time = None MatchManipulator.createOrUpdate( match) # Backup robot introduced if self._substep == 3: EventDetailsManipulator.createOrUpdate( EventDetails(id='2016nytr', alliance_selections=self._event_details. alliance_selections)) if self._substep < len(self._played_matches['sf']) - 1: self._substep += 1 else: self._step += 1 if self._batch_advance else 2 self._substep = 0 elif self._step == 7: # F schedule added if self._batch_advance: for match in copy.deepcopy(self._all_matches['f']): for alliance in ['red', 'blue']: match.alliances[alliance]['score'] = -1 match.alliances_json = json.dumps(match.alliances) match.score_breakdown_json = None match.actual_time = None MatchManipulator.createOrUpdate(match) self._step += 1 elif self._step == 8: # After each F match MatchManipulator.createOrUpdate( MatchHelper.play_order_sort_matches( self._played_matches['f'])[self._substep]) if self._substep < len(self._played_matches['f']) - 1: self._substep += 1 else: self._step += 1 self._substep = 0 ndb.get_context().clear_cache() # Re fetch event matches event = Event.get_by_id('2016nytr') MatchHelper.deleteInvalidMatches(event.matches) ndb.get_context().clear_cache() self._update_rankings()
def parse(self, response): matches = response['Schedule'] event_key = '{}{}'.format(self.year, self.event_short) event = Event.get_by_id(event_key) if event.timezone_id: event_tz = pytz.timezone(event.timezone_id) else: logging.warning( "Event {} has no timezone! Match times may be wrong.".format( event_key)) event_tz = None parsed_matches = [] is_octofinals = len( matches) > 0 and 'Octofinal' in matches[0]['description'] for match in matches: if 'tournamentLevel' in match: # 2016+ level = match['tournamentLevel'] else: # 2015 level = match['level'] comp_level = get_comp_level(self.year, level, match['matchNumber'], is_octofinals) set_number, match_number = get_set_match_number( self.year, comp_level, match['matchNumber'], is_octofinals) red_teams = [] blue_teams = [] team_key_names = [] null_team = False sorted_teams = sorted( match['Teams'], key=lambda team: team['station'] ) # Sort by station to ensure correct ordering. Kind of hacky. for team in sorted_teams: if team['teamNumber'] is None: null_team = True team_key = 'frc{}'.format(team['teamNumber']) team_key_names.append(team_key) if 'Red' in team['station']: red_teams.append(team_key) elif 'Blue' in team['station']: blue_teams.append(team_key) if null_team and match['scoreRedFinal'] is None and match[ 'scoreBlueFinal'] is None: continue alliances = { 'red': { 'teams': red_teams, 'score': match['scoreRedFinal'] }, 'blue': { 'teams': blue_teams, 'score': match['scoreBlueFinal'] } } if not match[ 'startTime']: # no startTime means it's an unneeded rubber match continue time = datetime.datetime.strptime(match['startTime'].split('.')[0], TIME_PATTERN) actual_time_raw = match[ 'actualStartTime'] if 'actualStartTime' in match else None actual_time = None if event_tz is not None: time = time - event_tz.utcoffset(time) if actual_time_raw is not None: actual_time = datetime.datetime.strptime( actual_time_raw.split('.')[0], TIME_PATTERN) if event_tz is not None: actual_time = actual_time - event_tz.utcoffset(actual_time) parsed_matches.append( Match( id=Match.renderKeyName(event_key, comp_level, set_number, match_number), event=event.key, year=event.year, set_number=set_number, match_number=match_number, comp_level=comp_level, team_key_names=team_key_names, time=time, actual_time=actual_time, alliances_json=json.dumps(alliances), )) if self.year == 2015: # Fix null teams in elims (due to FMS API failure, some info not complete) # Should only happen for sf and f matches organized_matches = MatchHelper.organizeMatches(parsed_matches) for level in ['sf', 'f']: playoff_advancement = MatchHelper.generatePlayoffAdvancement2015( organized_matches) if playoff_advancement[LAST_LEVEL[level]] != []: for match in organized_matches[level]: if 'frcNone' in match.team_key_names: if level == 'sf': red_seed, blue_seed = QF_SF_MAP[ match.match_number] else: red_seed = 0 blue_seed = 1 red_teams = [ 'frc{}'.format(t) for t in playoff_advancement[ LAST_LEVEL[level]][red_seed][0] ] blue_teams = [ 'frc{}'.format(t) for t in playoff_advancement[ LAST_LEVEL[level]][blue_seed][0] ] alliances = match.alliances alliances['red']['teams'] = red_teams alliances['blue']['teams'] = blue_teams match.alliances_json = json.dumps(alliances) match.team_key_names = red_teams + blue_teams fixed_matches = [] for key, matches in organized_matches.items(): if key != 'num': for match in matches: if 'frcNone' not in match.team_key_names: fixed_matches.append(match) parsed_matches = fixed_matches return parsed_matches
def test_2017ncwin(self): event = Event(id='2017ncwin', event_short='ncwin', year=2017, event_type_enum=0, timezone_id='America/New_York') event.put() MatchManipulator.createOrUpdate( DatafeedFMSAPI('v2.0', sim_time=datetime.datetime( 2017, 3, 05, 21, 2)).getMatches('2017ncwin')) MatchHelper.deleteInvalidMatches(event.matches, event) sf_matches = Match.query(Match.event == ndb.Key(Event, '2017ncwin'), Match.comp_level == 'sf').fetch() self.assertEqual(len(sf_matches), 6) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').alliances['red']['score'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').alliances['blue']['score'], 150) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').score_breakdown['red'] ['totalPoints'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').score_breakdown['blue'] ['totalPoints'], 150) ndb.get_context().clear_cache( ) # Prevent data from leaking between tests MatchManipulator.createOrUpdate( DatafeedFMSAPI('v2.0', sim_time=datetime.datetime( 2017, 3, 05, 21, 30)).getMatches('2017ncwin')) MatchHelper.deleteInvalidMatches(event.matches, event) sf_matches = Match.query(Match.event == ndb.Key(Event, '2017ncwin'), Match.comp_level == 'sf').fetch() self.assertEqual(len(sf_matches), 6) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').alliances['red']['score'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').alliances['blue']['score'], 150) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').score_breakdown['red'] ['totalPoints'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').score_breakdown['blue'] ['totalPoints'], 150) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').alliances['red']['score'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').alliances['blue']['score'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').score_breakdown['red'] ['totalPoints'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').score_breakdown['blue'] ['totalPoints'], 205) ndb.get_context().clear_cache( ) # Prevent data from leaking between tests MatchManipulator.createOrUpdate( DatafeedFMSAPI('v2.0', sim_time=datetime.datetime( 2017, 3, 05, 21, 35)).getMatches('2017ncwin')) MatchHelper.deleteInvalidMatches(event.matches, event) sf_matches = Match.query(Match.event == ndb.Key(Event, '2017ncwin'), Match.comp_level == 'sf').fetch() self.assertEqual(len(sf_matches), 6) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').alliances['red']['score'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').alliances['blue']['score'], 150) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').score_breakdown['red'] ['totalPoints'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').score_breakdown['blue'] ['totalPoints'], 150) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').alliances['red']['score'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').alliances['blue']['score'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').score_breakdown['red'] ['totalPoints'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').score_breakdown['blue'] ['totalPoints'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m3').alliances['red']['score'], 145) self.assertEqual( Match.get_by_id('2017ncwin_sf2m3').alliances['blue']['score'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m3').score_breakdown['red'] ['totalPoints'], 145) self.assertEqual( Match.get_by_id('2017ncwin_sf2m3').score_breakdown['blue'] ['totalPoints'], 265) ndb.get_context().clear_cache( ) # Prevent data from leaking between tests MatchManipulator.createOrUpdate( DatafeedFMSAPI('v2.0', sim_time=datetime.datetime( 2017, 3, 05, 21, 51)).getMatches('2017ncwin')) MatchHelper.deleteInvalidMatches(event.matches, event) sf_matches = Match.query(Match.event == ndb.Key(Event, '2017ncwin'), Match.comp_level == 'sf').fetch() self.assertEqual(len(sf_matches), 7) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').alliances['red']['score'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').alliances['blue']['score'], 150) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').score_breakdown['red'] ['totalPoints'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m1').score_breakdown['blue'] ['totalPoints'], 150) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').alliances['red']['score'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').alliances['blue']['score'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').score_breakdown['red'] ['totalPoints'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m2').score_breakdown['blue'] ['totalPoints'], 205) self.assertEqual( Match.get_by_id('2017ncwin_sf2m3').alliances['red']['score'], 145) self.assertEqual( Match.get_by_id('2017ncwin_sf2m3').alliances['blue']['score'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m3').score_breakdown['red'] ['totalPoints'], 145) self.assertEqual( Match.get_by_id('2017ncwin_sf2m3').score_breakdown['blue'] ['totalPoints'], 265) self.assertEqual( Match.get_by_id('2017ncwin_sf2m4').alliances['red']['score'], 180) self.assertEqual( Match.get_by_id('2017ncwin_sf2m4').alliances['blue']['score'], 305) self.assertEqual( Match.get_by_id('2017ncwin_sf2m4').score_breakdown['red'] ['totalPoints'], 180) self.assertEqual( Match.get_by_id('2017ncwin_sf2m4').score_breakdown['blue'] ['totalPoints'], 305)
def _render(self, event_key): event = EventQuery(event_key).fetch() if not event: self.abort(404) event.prepAwardsMatchesTeams() event.prep_details() medias_future = media_query.EventTeamsPreferredMediasQuery(event_key).fetch_async() district_future = DistrictQuery(event.district_key.id()).fetch_async() if event.district_key else None event_medias_future = media_query.EventMediasQuery(event_key).fetch_async() status_sitevar_future = Sitevar.get_by_id_async('apistatus.down_events') event_divisions_future = None event_codivisions_future = None parent_event_future = None if event.divisions: event_divisions_future = ndb.get_multi_async(event.divisions) elif event.parent_event: parent_event_future = event.parent_event.get_async() event_codivisions_future = EventDivisionsQuery(event.parent_event.id()).fetch_async() awards = AwardHelper.organizeAwards(event.awards) cleaned_matches = MatchHelper.deleteInvalidMatches(event.matches, event) matches = MatchHelper.organizeMatches(cleaned_matches) teams = TeamHelper.sortTeams(event.teams) # Organize medias by team image_medias = MediaHelper.get_images([media for media in medias_future.get_result()]) team_medias = defaultdict(list) for image_media in image_medias: for reference in image_media.references: team_medias[reference].append(image_media) team_and_medias = [] for team in teams: team_and_medias.append((team, team_medias.get(team.key, []))) num_teams = len(team_and_medias) middle_value = num_teams / 2 if num_teams % 2 != 0: middle_value += 1 teams_a, teams_b = team_and_medias[:middle_value], team_and_medias[middle_value:] oprs = [i for i in event.matchstats['oprs'].items()] if (event.matchstats is not None and 'oprs' in event.matchstats) else [] oprs = sorted(oprs, key=lambda t: t[1], reverse=True) # sort by OPR oprs = oprs[:15] # get the top 15 OPRs if event.now: matches_recent = MatchHelper.recentMatches(cleaned_matches) matches_upcoming = MatchHelper.upcomingMatches(cleaned_matches) else: matches_recent = None matches_upcoming = None bracket_table = MatchHelper.generateBracket(matches, event, event.alliance_selections) playoff_advancement = None playoff_template = None double_elim_matches = None if EventHelper.is_2015_playoff(event_key): playoff_advancement = MatchHelper.generatePlayoffAdvancement2015(matches, event.alliance_selections) playoff_template = 'playoff_table' for comp_level in ['qf', 'sf']: if comp_level in bracket_table: del bracket_table[comp_level] elif event.playoff_type == PlayoffType.ROUND_ROBIN_6_TEAM: playoff_advancement = MatchHelper.generatePlayoffAdvancementRoundRobin(matches, event.year, event.alliance_selections) playoff_template = 'playoff_round_robin_6_team' comp_levels = bracket_table.keys() for comp_level in comp_levels: if comp_level != 'f': del bracket_table[comp_level] elif event.playoff_type == PlayoffType.BO3_FINALS or event.playoff_type == PlayoffType.BO5_FINALS: comp_levels = bracket_table.keys() for comp_level in comp_levels: if comp_level != 'f': del bracket_table[comp_level] elif event.playoff_type == PlayoffType.DOUBLE_ELIM_8_TEAM: double_elim_matches = MatchHelper.organizeDoubleElimMatches(matches) district_points_sorted = None if event.district_key and event.district_points: district_points_sorted = sorted(event.district_points['points'].items(), key=lambda (team, points): -points['total']) event_insights = event.details.insights if event.details else None event_insights_template = None if event_insights: event_insights_template = 'event_partials/event_insights_{}.html'.format(event.year) district = district_future.get_result() if district_future else None event_divisions = None if event_divisions_future: event_divisions = [e.get_result() for e in event_divisions_future] elif event_codivisions_future: event_divisions = event_codivisions_future.get_result() medias_by_slugname = MediaHelper.group_by_slugname([media for media in event_medias_future.get_result()]) has_time_predictions = matches_upcoming and any(match.predicted_time for match in matches_upcoming) status_sitevar = status_sitevar_future.get_result() self.template_values.update({ "event": event, "event_down": status_sitevar and event_key in status_sitevar.contents, "district_name": district.display_name if district else None, "district_abbrev": district.abbreviation if district else None, "matches": matches, "matches_recent": matches_recent, "matches_upcoming": matches_upcoming, 'has_time_predictions': has_time_predictions, "awards": awards, "teams_a": teams_a, "teams_b": teams_b, "num_teams": num_teams, "oprs": oprs, "bracket_table": bracket_table, "playoff_advancement": playoff_advancement, "playoff_template": playoff_template, "district_points_sorted": district_points_sorted, "event_insights_qual": event_insights['qual'] if event_insights else None, "event_insights_playoff": event_insights['playoff'] if event_insights else None, "event_insights_template": event_insights_template, "medias_by_slugname": medias_by_slugname, "event_divisions": event_divisions, 'parent_event': parent_event_future.get_result() if parent_event_future else None, 'double_elim_matches': double_elim_matches, 'double_elim_playoff_types': PlayoffType.DOUBLE_ELIM_TYPES, }) if event.within_a_day: self._cache_expiration = self.SHORT_CACHE_EXPIRATION return jinja2_engine.render('event_details.html', self.template_values)
def render_team_history(cls, handler, team, is_canonical): award_futures = award_query.TeamAwardsQuery( team.key.id()).fetch_async() event_futures = event_query.TeamEventsQuery( team.key.id()).fetch_async() participation_future = team_query.TeamParticipationQuery( team.key.id()).fetch_async() awards_by_event = {} for award in award_futures.get_result(): if award.event.id() not in awards_by_event: awards_by_event[award.event.id()] = [award] else: awards_by_event[award.event.id()].append(award) event_awards = [] current_event = None matches_upcoming = None short_cache = False years = set() for event in event_futures.get_result(): years.add(event.year) if event.now: current_event = event matches = match_query.TeamEventMatchesQuery( team.key.id(), event.key.id()).fetch() matches_upcoming = MatchHelper.upcomingMatches(matches) if event.within_a_day: short_cache = True if event.key_name in awards_by_event: sorted_awards = AwardHelper.organizeAwards( awards_by_event[event.key_name]) else: sorted_awards = [] event_awards.append((event, sorted_awards)) event_awards = sorted( event_awards, key=lambda (e, _): e.start_date if e.start_date else datetime.datetime(e.year, 12, 31)) last_competed = None participation_years = participation_future.get_result() if len(participation_years) > 0: last_competed = max(participation_years) current_year = datetime.date.today().year handler.template_values.update({ 'is_canonical': is_canonical, 'team': team, 'event_awards': event_awards, 'years': sorted(years), 'current_event': current_event, 'matches_upcoming': matches_upcoming, 'last_competed': last_competed, 'current_year': current_year }) if short_cache: handler._cache_expiration = handler.SHORT_CACHE_EXPIRATION return jinja2_engine.render('team_history.html', handler.template_values)
def get(self): self._require_registration() current_events = filter(lambda e: e.now, EventHelper.getEventsWithinADay()) popular_teams_events = TeamHelper.getPopularTeamsEvents(current_events) popular_team_keys = set() for team, _ in popular_teams_events: popular_team_keys.add(team.key.id()) for event in current_events: event.prep_details() event.prep_matches() finished_matches = [] current_matches = [] upcoming_matches = [] ranks = {} alliances = {} for event in current_events: if not event.details: continue finished_matches += MatchHelper.recentMatches(event.matches, num=1) for i, match in enumerate( MatchHelper.upcomingMatches(event.matches, num=3)): if not match.time: continue if not event.details.predictions or match.key.id( ) not in event.details.predictions['match_predictions'][ 'qual' if match.comp_level == 'qm' else 'playoff']: match.prediction = defaultdict(lambda: defaultdict(float)) match.bluezone_score = 0 else: match.prediction = event.details.predictions[ 'match_predictions']['qual' if match.comp_level == 'qm' else 'playoff'][ match.key.id()] match.bluezone_score = self.get_qual_bluezone_score( match.prediction ) if match.comp_level == 'qm' else self.get_elim_bluezone_score( match.prediction) if i == 0: current_matches.append(match) else: upcoming_matches.append(match) if event.details.rankings2: for rank in event.details.rankings2: ranks[rank['team_key']] = rank['rank'] if event.alliance_selections: for i, alliance in enumerate(event.alliance_selections): for pick in alliance['picks']: alliances[pick] = i + 1 finished_matches = sorted(finished_matches, key=lambda m: m.actual_time if m.actual_time else m.time) current_matches = sorted(current_matches, key=lambda m: m.predicted_time if m.predicted_time else m.time) upcoming_matches = sorted(upcoming_matches, key=lambda m: m.predicted_time if m.predicted_time else m.time) self.template_values.update({ 'finished_matches': finished_matches, 'current_matches': current_matches, 'upcoming_matches': upcoming_matches, 'ranks': ranks, 'alliances': alliances, 'popular_team_keys': popular_team_keys, }) self.response.out.write( jinja2_engine.render('match_suggestion.html', self.template_values))
def _render(self, event_key): event = Event.get_by_id(event_key) if not event: self.abort(404) event.prepAwardsMatchesTeams() awards = AwardHelper.organizeAwards(event.awards) cleaned_matches = MatchHelper.deleteInvalidMatches(event.matches) matches = MatchHelper.organizeMatches(cleaned_matches) teams = TeamHelper.sortTeams(event.teams) num_teams = len(teams) middle_value = num_teams / 2 if num_teams % 2 != 0: middle_value += 1 teams_a, teams_b = teams[:middle_value], teams[middle_value:] oprs = [i for i in event.matchstats['oprs'].items() ] if (event.matchstats is not None and 'oprs' in event.matchstats) else [] oprs = sorted(oprs, key=lambda t: t[1], reverse=True) # sort by OPR oprs = oprs[:15] # get the top 15 OPRs if event.within_a_day: matches_recent = MatchHelper.recentMatches(cleaned_matches) matches_upcoming = MatchHelper.upcomingMatches(cleaned_matches) else: matches_recent = None matches_upcoming = None bracket_table = {} qf_matches = matches['qf'] sf_matches = matches['sf'] f_matches = matches['f'] if qf_matches: bracket_table['qf'] = MatchHelper.generateBracket(qf_matches) if sf_matches: bracket_table['sf'] = MatchHelper.generateBracket(sf_matches) if f_matches: bracket_table['f'] = MatchHelper.generateBracket(f_matches) template_values = { "event": event, "matches": matches, "matches_recent": matches_recent, "matches_upcoming": matches_upcoming, "awards": awards, "teams_a": teams_a, "teams_b": teams_b, "num_teams": num_teams, "oprs": oprs, "bracket_table": bracket_table, } if event.within_a_day: self._cache_expiration = self.SHORT_CACHE_EXPIRATION path = os.path.join(os.path.dirname(__file__), '../templates/event_details.html') return template.render(path, template_values)
def parse(self, response): import pytz matches = response['Schedule'] event_key = '{}{}'.format(self.year, self.event_short) event = Event.get_by_id(event_key) if event.timezone_id: event_tz = pytz.timezone(event.timezone_id) else: logging.warning( "Event {} has no timezone! Match times may be wrong.".format( event_key)) event_tz = None parsed_matches = [] remapped_matches = {} # If a key changes due to a tiebreaker for match in matches: if 'tournamentLevel' in match: # 2016+ level = match['tournamentLevel'] else: # 2015 level = match['level'] comp_level = PlayoffType.get_comp_level(event.playoff_type, level, match['matchNumber']) set_number, match_number = PlayoffType.get_set_match_number( event.playoff_type, comp_level, match['matchNumber']) red_teams = [] blue_teams = [] red_surrogates = [] blue_surrogates = [] red_dqs = [] blue_dqs = [] team_key_names = [] null_team = False sorted_teams = sorted( match.get('teams', match.get('Teams', [])), key=lambda team: team['station'] ) # Sort by station to ensure correct ordering. Kind of hacky. for team in sorted_teams: if team['teamNumber'] is None: null_team = True team_key = 'frc{}'.format(team['teamNumber']) team_key_names.append(team_key) if 'Red' in team['station']: red_teams.append(team_key) if team['surrogate']: red_surrogates.append(team_key) if team['dq']: red_dqs.append(team_key) elif 'Blue' in team['station']: blue_teams.append(team_key) if team['surrogate']: blue_surrogates.append(team_key) if team['dq']: blue_dqs.append(team_key) if null_team and match['scoreRedFinal'] is None and match[ 'scoreBlueFinal'] is None: continue alliances = { 'red': { 'teams': red_teams, 'surrogates': red_surrogates, 'dqs': red_dqs, 'score': match['scoreRedFinal'] }, 'blue': { 'teams': blue_teams, 'surrogates': blue_surrogates, 'dqs': blue_dqs, 'score': match['scoreBlueFinal'] }, } if not match[ 'startTime']: # no startTime means it's an unneeded rubber match continue time = datetime.datetime.strptime(match['startTime'].split('.')[0], TIME_PATTERN) if event_tz is not None: time = time - event_tz.utcoffset(time) actual_time_raw = match[ 'actualStartTime'] if 'actualStartTime' in match else None actual_time = None if actual_time_raw is not None: actual_time = datetime.datetime.strptime( actual_time_raw.split('.')[0], TIME_PATTERN) if event_tz is not None: actual_time = actual_time - event_tz.utcoffset(actual_time) post_result_time_raw = match.get('postResultTime') post_result_time = None if post_result_time_raw is not None: post_result_time = datetime.datetime.strptime( post_result_time_raw.split('.')[0], TIME_PATTERN) if event_tz is not None: post_result_time = post_result_time - event_tz.utcoffset( post_result_time) key_name = Match.renderKeyName(event_key, comp_level, set_number, match_number) # Check for tiebreaker matches existing_match = Match.get_by_id(key_name) # Follow chain of existing matches while existing_match is not None and existing_match.tiebreak_match_key is not None: logging.info("Following Match {} to {}".format( existing_match.key.id(), existing_match.tiebreak_match_key.id())) existing_match = existing_match.tiebreak_match_key.get() # Check if last existing match needs to be tiebroken if existing_match and existing_match.comp_level != 'qm' and \ existing_match.has_been_played and \ existing_match.winning_alliance == '' and \ existing_match.actual_time != actual_time and \ not self.is_blank_match(existing_match): logging.warning("Match {} is tied!".format( existing_match.key.id())) # TODO: Only query within set if set_number ever gets indexed match_count = 0 for match_key in Match.query( Match.event == event.key, Match.comp_level == comp_level).fetch(keys_only=True): _, match_key = match_key.id().split('_') if match_key.startswith('{}{}'.format( comp_level, set_number)): match_count += 1 # Sanity check: Tiebreakers must be played after at least 3 matches, or 6 for finals if match_count < 3 or (match_count < 6 and comp_level == 'f'): logging.warning( "Match supposedly tied, but existing count is {}! Skipping match." .format(match_count)) continue match_number = match_count + 1 new_key_name = Match.renderKeyName(event_key, comp_level, set_number, match_number) remapped_matches[key_name] = new_key_name key_name = new_key_name # Point existing match to new tiebreaker match existing_match.tiebreak_match_key = ndb.Key(Match, key_name) parsed_matches.append(existing_match) logging.warning("Creating new match: {}".format(key_name)) elif existing_match: remapped_matches[key_name] = existing_match.key.id() key_name = existing_match.key.id() match_number = existing_match.match_number parsed_matches.append( Match( id=key_name, event=event.key, year=event.year, set_number=set_number, match_number=match_number, comp_level=comp_level, team_key_names=team_key_names, time=time, actual_time=actual_time, post_result_time=post_result_time, alliances_json=json.dumps(alliances), )) if self.year == 2015: # Fix null teams in elims (due to FMS API failure, some info not complete) # Should only happen for sf and f matches organized_matches = MatchHelper.organizeMatches(parsed_matches) for level in ['sf', 'f']: playoff_advancement = MatchHelper.generatePlayoffAdvancement2015( organized_matches) if playoff_advancement[LAST_LEVEL[level]] != []: for match in organized_matches[level]: if 'frcNone' in match.team_key_names: if level == 'sf': red_seed, blue_seed = QF_SF_MAP[ match.match_number] else: red_seed = 0 blue_seed = 1 red_teams = [ 'frc{}'.format(t) for t in playoff_advancement[ LAST_LEVEL[level]][red_seed][0] ] blue_teams = [ 'frc{}'.format(t) for t in playoff_advancement[ LAST_LEVEL[level]][blue_seed][0] ] alliances = match.alliances alliances['red']['teams'] = red_teams alliances['blue']['teams'] = blue_teams match.alliances_json = json.dumps(alliances) match.team_key_names = red_teams + blue_teams fixed_matches = [] for key, matches in organized_matches.items(): if key != 'num': for match in matches: if 'frcNone' not in match.team_key_names: fixed_matches.append(match) parsed_matches = fixed_matches return parsed_matches, remapped_matches
def _render(self, event_key): event = Event.get_by_id(event_key) if not event: self.abort(404) event.prepAwardsMatchesTeams() medias_future = media_query.EventTeamsPreferredMediasQuery(event_key).fetch_async() awards = AwardHelper.organizeAwards(event.awards) cleaned_matches = MatchHelper.deleteInvalidMatches(event.matches) matches = MatchHelper.organizeMatches(cleaned_matches) teams = TeamHelper.sortTeams(event.teams) # Organize medias by team image_medias = MediaHelper.get_images([media for media in medias_future.get_result()]) team_medias = defaultdict(list) for image_media in image_medias: for reference in image_media.references: team_medias[reference].append(image_media) team_and_medias = [] for team in teams: team_and_medias.append((team, team_medias.get(team.key, []))) num_teams = len(team_and_medias) middle_value = num_teams / 2 if num_teams % 2 != 0: middle_value += 1 teams_a, teams_b = team_and_medias[:middle_value], team_and_medias[middle_value:] oprs = [i for i in event.matchstats['oprs'].items()] if (event.matchstats is not None and 'oprs' in event.matchstats) else [] oprs = sorted(oprs, key=lambda t: t[1], reverse=True) # sort by OPR oprs = oprs[:15] # get the top 15 OPRs if event.now: matches_recent = MatchHelper.recentMatches(cleaned_matches) matches_upcoming = MatchHelper.upcomingMatches(cleaned_matches) else: matches_recent = None matches_upcoming = None bracket_table = MatchHelper.generateBracket(matches, event.alliance_selections) is_2015_playoff = EventHelper.is_2015_playoff(event_key) if is_2015_playoff: playoff_advancement = MatchHelper.generatePlayoffAdvancement2015(matches, event.alliance_selections) for comp_level in ['qf', 'sf']: if comp_level in bracket_table: del bracket_table[comp_level] else: playoff_advancement = None district_points_sorted = None if event.district_points: district_points_sorted = sorted(event.district_points['points'].items(), key=lambda (team, points): -points['total']) event_insights = EventInsightsHelper.calculate_event_insights(cleaned_matches, event.year) event_insights_template = None if event_insights: event_insights_template = 'event_partials/event_insights_{}.html'.format(event.year) # rankings processing for ranking score per match full_rankings = event.rankings rankings_enhanced = event.rankings_enhanced if rankings_enhanced is not None: rp_index = RankingIndexes.CUMULATIVE_RANKING_SCORE[event.year] matches_index = RankingIndexes.MATCHES_PLAYED[event.year] ranking_criterion_name = full_rankings[0][rp_index] full_rankings[0].append(ranking_criterion_name + "/Match*") for row in full_rankings[1:]: team = row[1] if rankings_enhanced["ranking_score_per_match"] is not None: rp_per_match = rankings_enhanced['ranking_score_per_match'][team] row.append(rp_per_match) if rankings_enhanced["match_offset"] is not None: match_offset = rankings_enhanced["match_offset"][team] if match_offset != 0: row[matches_index] = "{} ({})".format(row[matches_index], match_offset) self.template_values.update({ "event": event, "district_name": DistrictType.type_names.get(event.event_district_enum, None), "district_abbrev": DistrictType.type_abbrevs.get(event.event_district_enum, None), "matches": matches, "matches_recent": matches_recent, "matches_upcoming": matches_upcoming, "awards": awards, "teams_a": teams_a, "teams_b": teams_b, "num_teams": num_teams, "oprs": oprs, "bracket_table": bracket_table, "playoff_advancement": playoff_advancement, "district_points_sorted": district_points_sorted, "is_2015_playoff": is_2015_playoff, "event_insights_qual": event_insights['qual'] if event_insights else None, "event_insights_playoff": event_insights['playoff'] if event_insights else None, "event_insights_template": event_insights_template, }) if event.within_a_day: self._cache_expiration = self.SHORT_CACHE_EXPIRATION return jinja2_engine.render('event_details.html', self.template_values)
def render_team_details(cls, handler, team, year, is_canonical): hof_award_future = award_query.TeamEventTypeAwardsQuery( team.key.id(), EventType.CMP_FINALS, AwardType.CHAIRMANS).fetch_async() hof_video_future = media_query.TeamTagMediasQuery( team.key.id(), MediaTag.CHAIRMANS_VIDEO).fetch_async() hof_presentation_future = media_query.TeamTagMediasQuery( team.key.id(), MediaTag.CHAIRMANS_PRESENTATION).fetch_async() hof_essay_future = media_query.TeamTagMediasQuery( team.key.id(), MediaTag.CHAIRMANS_ESSAY).fetch_async() media_future = media_query.TeamYearMediaQuery(team.key.id(), year).fetch_async() social_media_future = media_query.TeamSocialMediaQuery( team.key.id()).fetch_async() robot_future = Robot.get_by_id_async("{}_{}".format( team.key.id(), year)) team_districts_future = team_query.TeamDistrictsQuery( team.key.id()).fetch_async() participation_future = team_query.TeamParticipationQuery( team.key.id()).fetch_async() hof_awards = hof_award_future.get_result() hof_video = hof_video_future.get_result() hof_presentation = hof_presentation_future.get_result() hof_essay = hof_essay_future.get_result() hall_of_fame = { "is_hof": len(hof_awards) > 0, "years": [award.year for award in hof_awards], "media": { "video": hof_video[0].youtube_url_link if len(hof_video) > 0 else None, "presentation": hof_presentation[0].youtube_url_link if len(hof_presentation) > 0 else None, "essay": hof_essay[0].external_link if len(hof_essay) > 0 else None, }, } events_sorted, matches_by_event_key, awards_by_event_key, valid_years = TeamDetailsDataFetcher.fetch( team, year, return_valid_years=True) if not events_sorted: return None participation = [] season_wlt_list = [] offseason_wlt_list = [] year_match_avg_list = [] current_event = None matches_upcoming = None short_cache = False for event in events_sorted: event_matches = matches_by_event_key.get(event.key, []) event_awards = AwardHelper.organizeAwards( awards_by_event_key.get(event.key, [])) matches_organized = MatchHelper.organizeMatches(event_matches) if event.now: current_event = event matches_upcoming = MatchHelper.upcomingMatches(event_matches) if event.within_a_day: short_cache = True if year == 2015: display_wlt = None match_avg = EventHelper.calculateTeamAvgScoreFromMatches( team.key_name, event_matches) year_match_avg_list.append(match_avg) qual_avg, elim_avg, _, _ = match_avg else: qual_avg = None elim_avg = None wlt = EventHelper.calculateTeamWLTFromMatches( team.key_name, event_matches) if event.event_type_enum in EventType.SEASON_EVENT_TYPES: season_wlt_list.append(wlt) else: offseason_wlt_list.append(wlt) if wlt["win"] + wlt["loss"] + wlt["tie"] == 0: display_wlt = None else: display_wlt = wlt team_rank = None if event.details and event.details.rankings2: for ranking in event.details.rankings2: if ranking['team_key'] == team.key.id(): team_rank = ranking['rank'] break participation.append({ "event": event, "matches": matches_organized, "wlt": display_wlt, "qual_avg": qual_avg, "elim_avg": elim_avg, "rank": team_rank, "awards": event_awards }) season_wlt = None offseason_wlt = None if year == 2015: year_wlt = None year_qual_scores = [] year_elim_scores = [] for _, _, event_qual_scores, event_elim_scores in year_match_avg_list: year_qual_scores += event_qual_scores year_elim_scores += event_elim_scores year_qual_avg = float(sum(year_qual_scores)) / len( year_qual_scores) if year_qual_scores != [] else None year_elim_avg = float(sum(year_elim_scores)) / len( year_elim_scores) if year_elim_scores != [] else None else: year_qual_avg = None year_elim_avg = None season_wlt = {"win": 0, "loss": 0, "tie": 0} offseason_wlt = {"win": 0, "loss": 0, "tie": 0} for wlt in season_wlt_list: season_wlt["win"] += wlt["win"] season_wlt["loss"] += wlt["loss"] season_wlt["tie"] += wlt["tie"] if season_wlt["win"] + season_wlt["loss"] + season_wlt["tie"] == 0: season_wlt = None for wlt in offseason_wlt_list: offseason_wlt["win"] += wlt["win"] offseason_wlt["loss"] += wlt["loss"] offseason_wlt["tie"] += wlt["tie"] if offseason_wlt["win"] + offseason_wlt["loss"] + offseason_wlt[ "tie"] == 0: offseason_wlt = None medias_by_slugname = MediaHelper.group_by_slugname( [media for media in media_future.get_result()]) avatar = MediaHelper.get_avatar(media_future.get_result()) image_medias = MediaHelper.get_images(media_future.get_result()) social_medias = sorted(social_media_future.get_result(), key=MediaHelper.social_media_sorter) preferred_image_medias = filter( lambda x: team.key in x.preferred_references, image_medias) district_name = None district_abbrev = None team_districts = team_districts_future.get_result() for district in team_districts: if district.year == year: district_abbrev = district.abbreviation district_name = district.display_name last_competed = None participation_years = participation_future.get_result() if len(participation_years) > 0: last_competed = max(participation_years) current_year = datetime.date.today().year handler.template_values.update({ "is_canonical": is_canonical, "team": team, "participation": participation, "year": year, "years": valid_years, "season_wlt": season_wlt, "offseason_wlt": offseason_wlt, "year_qual_avg": year_qual_avg, "year_elim_avg": year_elim_avg, "current_event": current_event, "matches_upcoming": matches_upcoming, "medias_by_slugname": medias_by_slugname, "avatar": avatar, "social_medias": social_medias, "image_medias": image_medias, "preferred_image_medias": preferred_image_medias, "robot": robot_future.get_result(), "district_name": district_name, "district_abbrev": district_abbrev, "last_competed": last_competed, "current_year": current_year, "max_year": tba_config.MAX_YEAR, "hof": hall_of_fame }) if short_cache: handler._cache_expiration = handler.SHORT_CACHE_EXPIRATION return jinja2_engine.render("team_details.html", handler.template_values)
def _render(self, event_key): event = Event.get_by_id(event_key) if not event: self.abort(404) event.prepAwardsMatchesTeams() awards = AwardHelper.organizeAwards(event.awards) cleaned_matches = MatchHelper.deleteInvalidMatches(event.matches) matches = MatchHelper.organizeMatches(cleaned_matches) teams = TeamHelper.sortTeams(event.teams) num_teams = len(teams) middle_value = num_teams / 2 if num_teams % 2 != 0: middle_value += 1 teams_a, teams_b = teams[:middle_value], teams[middle_value:] oprs = [i for i in event.matchstats['oprs'].items() ] if (event.matchstats is not None and 'oprs' in event.matchstats) else [] oprs = sorted(oprs, key=lambda t: t[1], reverse=True) # sort by OPR oprs = oprs[:15] # get the top 15 OPRs if event.now: matches_recent = MatchHelper.recentMatches(cleaned_matches) matches_upcoming = MatchHelper.upcomingMatches(cleaned_matches) else: matches_recent = None matches_upcoming = None bracket_table = MatchHelper.generateBracket(matches, event.alliance_selections) is_2015_playoff = EventHelper.is_2015_playoff(event_key) if is_2015_playoff: playoff_advancement = MatchHelper.generatePlayoffAdvancement2015( matches, event.alliance_selections) for comp_level in ['qf', 'sf']: if comp_level in bracket_table: del bracket_table[comp_level] else: playoff_advancement = None district_points_sorted = None if event.district_points: district_points_sorted = sorted( event.district_points['points'].items(), key=lambda (team, points): -points['total']) event_insights = EventInsightsHelper.calculate_event_insights( cleaned_matches, event.year) self.template_values.update({ "event": event, "matches": matches, "matches_recent": matches_recent, "matches_upcoming": matches_upcoming, "awards": awards, "teams_a": teams_a, "teams_b": teams_b, "num_teams": num_teams, "oprs": oprs, "bracket_table": bracket_table, "playoff_advancement": playoff_advancement, "district_points_sorted": district_points_sorted, "is_2015_playoff": is_2015_playoff, "event_insights": event_insights }) if event.within_a_day: self._cache_expiration = self.SHORT_CACHE_EXPIRATION return jinja2_engine.render('event_details.html', self.template_values)
def _render(self, event_key): event = Event.get_by_id(event_key) if not event or event.year != 2016: self.abort(404) event.get_matches_async() match_predictions = event.details.predictions.get('match_predictions', None) match_prediction_stats = event.details.predictions.get('match_prediction_stats', None) ranking_predictions = event.details.predictions.get('ranking_predictions', None) ranking_prediction_stats = event.details.predictions.get('ranking_prediction_stats', None) cleaned_matches = MatchHelper.deleteInvalidMatches(event.matches) matches = MatchHelper.organizeMatches(cleaned_matches) # If no matches but there are match predictions, create fake matches # For cases where FIRST doesn't allow posting of match schedule fake_matches = False if not matches['qm'] and match_predictions: fake_matches = True for i in xrange(len(match_predictions.keys())): match_number = i + 1 alliances = { 'red': { 'score': -1, 'teams': ['frc?', 'frc?', 'frc?'] }, 'blue': { 'score': -1, 'teams': ['frc?', 'frc?', 'frc?'] } } matches['qm'].append(Match( id=Match.renderKeyName( event_key, 'qm', 1, match_number), event=event.key, year=event.year, set_number=1, match_number=match_number, comp_level='qm', alliances_json=json.dumps(alliances), )) last_played_match_num = None if ranking_prediction_stats: last_played_match_key = ranking_prediction_stats.get('last_played_match', None) if last_played_match_key: last_played_match_num = last_played_match_key.split('_qm')[1] self.template_values.update({ "event": event, "matches": matches, "fake_matches": fake_matches, "match_predictions": match_predictions, "match_prediction_stats": match_prediction_stats, "ranking_predictions": ranking_predictions, "ranking_prediction_stats": ranking_prediction_stats, "last_played_match_num": last_played_match_num, }) if event.within_a_day: self._cache_expiration = self.SHORT_CACHE_EXPIRATION return jinja2_engine.render('event_insights.html', self.template_values)
def calculate_event_points(cls, event): match_key_futures = Match.query(Match.event == event.key).fetch_async(None, keys_only=True) award_key_futures = Award.query(Award.event == event.key).fetch_async(None, keys_only=True) match_futures = ndb.get_multi_async(match_key_futures.get_result()) award_futures = ndb.get_multi_async(award_key_futures.get_result()) POINTS_MULTIPLIER = 3 if event.event_type_enum == EventType.DISTRICT_CMP else 1 district_points = { 'points': defaultdict(lambda: { 'qual_points': 0, 'elim_points': 0, 'alliance_points': 0, 'award_points': 0, 'total': 0, }), 'tiebreakers': defaultdict(lambda: { # for tiebreaker stats that can't be calculated with 'points' 'qual_wins': 0, 'highest_qual_scores': [], }), } # match points if event.year == 2015: from helpers.match_helper import MatchHelper # circular import issue # qual match points are calculated by rank if event.rankings and len(event.rankings) > 1: rankings = event.rankings[1:] # skip title row num_teams = len(rankings) alpha = 1.07 for row in rankings: rank = int(row[0]) team = 'frc{}'.format(row[1]) qual_points = int(np.ceil(cls.inverf(float(num_teams - 2 * rank + 2) / (alpha * num_teams)) * (10.0 / cls.inverf(1.0 / alpha)) + 12)) district_points['points'][team]['qual_points'] = qual_points * POINTS_MULTIPLIER else: logging.warning("Event {} has no rankings for qual_points calculations!".format(event.key.id())) matches = MatchHelper.organizeMatches([mf.get_result() for mf in match_futures]) # qual match calculations. only used for tiebreaking for match in matches['qm']: for color in ['red', 'blue']: for team in match.alliances[color]['teams']: score = match.alliances[color]['score'] district_points['tiebreakers'][team]['highest_qual_scores'] = heapq.nlargest(3, district_points['tiebreakers'][team]['highest_qual_scores'] + [score]) # elim match point calculations # count number of matches played per team per comp level num_played = defaultdict(lambda: defaultdict(int)) for level in ['qf', 'sf']: for match in matches[level]: if not match.has_been_played: continue for color in ['red', 'blue']: for team in match.alliances[color]['teams']: num_played[level][team] += 1 # qf and sf points advancement = MatchHelper.generatePlayoffAdvancement2015(matches) for last_level, level in [('qf', 'sf'), ('sf', 'f')]: for (teams, _, _) in advancement[last_level]: teams = ['frc{}'.format(t) for t in teams] done = False for match in matches[level]: for color in ['red', 'blue']: if set(teams).intersection(set(match.alliances[color]['teams'])) != set(): for team in teams: points = 5.0 if last_level == 'qf' else 3.3 district_points['points'][team]['elim_points'] += int(np.ceil(points * num_played[last_level][team])) * POINTS_MULTIPLIER done = True break if done: break if done: break # final points num_wins = {'red': 0, 'blue': 0} team_matches_played = {'red': [], 'blue': []} for match in matches['f']: if not match.has_been_played or match.winning_alliance == '': continue num_wins[match.winning_alliance] += 1 for team in match.alliances[match.winning_alliance]['teams']: team_matches_played[match.winning_alliance].append(team) if num_wins[match.winning_alliance] >= 2: for team in team_matches_played[match.winning_alliance]: district_points['points'][team]['elim_points'] += 5 * POINTS_MULTIPLIER else: elim_num_wins = defaultdict(lambda: defaultdict(int)) elim_alliances = defaultdict(lambda: defaultdict(list)) for match_future in match_futures: match = match_future.get_result() if not match.has_been_played: continue if match.comp_level == 'qm': if match.winning_alliance == '': for team in match.team_key_names: district_points['points'][team]['qual_points'] += 1 * POINTS_MULTIPLIER else: for team in match.alliances[match.winning_alliance]['teams']: district_points['points'][team]['qual_points'] += 2 * POINTS_MULTIPLIER district_points['tiebreakers'][team]['qual_wins'] += 1 for color in ['red', 'blue']: for team in match.alliances[color]['teams']: score = match.alliances[color]['score'] district_points['tiebreakers'][team]['highest_qual_scores'] = heapq.nlargest(3, district_points['tiebreakers'][team]['highest_qual_scores'] + [score]) else: if match.winning_alliance == '': continue match_set_key = '{}_{}{}'.format(match.event.id(), match.comp_level, match.set_number) elim_num_wins[match_set_key][match.winning_alliance] += 1 elim_alliances[match_set_key][match.winning_alliance] += match.alliances[match.winning_alliance]['teams'] if elim_num_wins[match_set_key][match.winning_alliance] >= 2: for team in elim_alliances[match_set_key][match.winning_alliance]: district_points['points'][team]['elim_points'] += 5* POINTS_MULTIPLIER # alliance points if event.alliance_selections: selection_points = EventHelper.alliance_selections_to_points(event.alliance_selections) for team, points in selection_points.items(): district_points['points'][team]['alliance_points'] += points * POINTS_MULTIPLIER else: logging.warning("Event {} has no alliance selection district_points!".format(event.key.id())) # award points for award_future in award_futures: award = award_future.get_result() if award.award_type_enum not in AwardType.NON_JUDGED_NON_TEAM_AWARDS: if award.award_type_enum == AwardType.CHAIRMANS: point_value = 10 elif award.award_type_enum in {AwardType.ENGINEERING_INSPIRATION, AwardType.ROOKIE_ALL_STAR}: point_value = 8 else: point_value = 5 for team in award.team_list: district_points['points'][team.id()]['award_points'] += point_value * POINTS_MULTIPLIER for team, point_breakdown in district_points['points'].items(): for p in point_breakdown.values(): district_points['points'][team]['total'] += p return district_points
def test_event_smulator_batch_advance(self): es = EventSimulator(batch_advance=True) # Before anything has happened event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) self.assertEqual(event.details, None) self.assertEqual(event.matches, []) # Qual match schedule added es.step() event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) self.assertNotEqual(event.details, None) for rank in event.details.rankings2: self.assertEqual(rank['sort_orders'][0], 0) self.assertEqual(len(event.matches), 72) for match in event.matches: self.assertEqual(match.comp_level, 'qm') self.assertFalse(match.has_been_played) self.assertEqual(match.actual_time, None) # After each qual match for i in xrange(72): es.step() event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) self.assertEqual(event.details.alliance_selections, None) self.assertEqual(len(event.matches), 72) matches = MatchHelper.play_order_sort_matches(event.matches) for j, match in enumerate(matches): if j <= i: self.assertTrue(match.has_been_played) self.assertNotEqual(match.actual_time, None) else: self.assertFalse(match.has_been_played) # Check some final rankings self.assertEqual(event.details.rankings2[0]['sort_orders'][0], 22) self.assertEqual(event.details.rankings2[-1]['sort_orders'][0], 4) # After alliance selections es.step() event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) self.assertEqual(event.details.alliance_selections, self._alliance_selections) self.assertEqual(len(event.matches), 72) # QF schedule added es.step() event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) self.assertEqual(event.details.alliance_selections, self._alliance_selections) self.assertEqual(len(event.matches), 84) for match in event.matches: if match.comp_level == 'qm': self.assertTrue(match.has_been_played) self.assertNotEqual(match.actual_time, None) else: self.assertEqual(match.comp_level, 'qf') self.assertFalse(match.has_been_played) self.assertEqual(match.actual_time, None) # After each QF match for i in xrange(72, 82): es.step() event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) self.assertEqual(event.details.alliance_selections, self._alliance_selections) if i <= 75: self.assertEqual(len(event.matches), 84) elif i <= 77: self.assertEqual(len(event.matches), 83) else: self.assertEqual(len(event.matches), 82) matches = MatchHelper.play_order_sort_matches(event.matches) for j, match in enumerate(matches): if match.key.id() in {'2016nytr_qf1m3', '2016nytr_qf3m3'}: # Unneeded tiebreak matches self.assertFalse(match.has_been_played) elif j <= i: self.assertTrue(match.has_been_played) self.assertNotEqual(match.actual_time, None) else: self.assertFalse(match.has_been_played) # SF schedule added es.step() event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) self.assertEqual(event.details.alliance_selections, self._alliance_selections) self.assertEqual(len(event.matches), 88) for match in event.matches: if match.comp_level in {'qm', 'qf'}: self.assertTrue(match.has_been_played) self.assertNotEqual(match.actual_time, None) else: self.assertEqual(match.comp_level, 'sf') self.assertFalse(match.has_been_played) self.assertEqual(match.actual_time, None) # After each SF match for i in xrange(82, 87): es.step() event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) if i < 85: self.assertEqual(event.details.alliance_selections, self._alliance_selections) else: self.assertEqual(event.details.alliance_selections, self._alliance_selections_with_backup) if i <= 83: self.assertEqual(len(event.matches), 88) else: self.assertEqual(len(event.matches), 87) matches = MatchHelper.play_order_sort_matches(event.matches) for j, match in enumerate(matches): if match.key.id() == '2016nytr_sf1m3': # Unneeded tiebreak matches self.assertFalse(match.has_been_played) elif j <= i: self.assertTrue(match.has_been_played) self.assertNotEqual(match.actual_time, None) else: self.assertFalse(match.has_been_played) # F schedule added es.step() event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) self.assertEqual(event.details.alliance_selections, self._alliance_selections_with_backup) self.assertEqual(len(event.matches), 90) for match in event.matches: if match.comp_level in {'qm', 'qf', 'sf'}: self.assertTrue(match.has_been_played) self.assertNotEqual(match.actual_time, None) else: self.assertEqual(match.comp_level, 'f') self.assertFalse(match.has_been_played) self.assertEqual(match.actual_time, None) # After each F match for i in xrange(87, 90): es.step() event = Event.get_by_id('2016nytr') self.assertNotEqual(event, None) self.assertEqual(event.details.alliance_selections, self._alliance_selections_with_backup) self.assertEqual(len(event.matches), 90) matches = MatchHelper.play_order_sort_matches(event.matches) for j, match in enumerate(matches): if j <= i: self.assertTrue(match.has_been_played) self.assertNotEqual(match.actual_time, None) else: self.assertFalse(match.has_been_played)
def getMatches(self, event): matches_url = self.YEAR_MATCH_RESULTS_URL_PATTERN.get( event.year, self.DEFAULT_MATCH_RESULTS_URL_PATTERN) % ( event.year, self.EVENT_SHORT_EXCEPTIONS.get(event.event_short, event.event_short)) match_dicts, _ = self.parse( matches_url, self.YEAR_MATCH_PARSER.get(event.year, self.DEFAULT_MATCH_PARSER)) if not match_dicts: # Matches have not been played, but qual match schedule may be out # If this is run when there are already matches in the DB, it will overwrite scores! # Check to make sure event has no existing matches if len( Match.query(Match.event == event.key).fetch( 1, keys_only=True)) == 0: logging.warning( "No matches found for {}. Trying to parse qual match schedule." .format(event.key.id())) qual_match_sched_url = self.MATCH_SCHEDULE_QUAL_URL_PATTERN % ( event.year, self.EVENT_SHORT_EXCEPTIONS.get(event.event_short, event.event_short)) match_dicts, _ = self.parse(qual_match_sched_url, self.MATCH_SCHEDULE_PARSER) for match_dict in match_dicts: alliances = json.loads(match_dict['alliances_json']) if (alliances['red']['score'] == -1 or alliances['blue']['score'] == -1 or match_dict['comp_level'] in Match.ELIM_LEVELS): break else: # Only qual matches have been played and they have all been played # If this is run when there are already elim matches in the DB, it will overwrite scores! # Check to make sure event has no existing elim matches if len( Match.query(Match.event == event.key, Match.comp_level.IN(Match.ELIM_LEVELS)).fetch( 1, keys_only=True)) == 0: logging.warning( "No elim matches found for {}. Trying to parse elim match schedule." .format(event.key.id())) elim_match_sched_url = self.MATCH_SCHEDULE_ELIMS_URL_PATTERN % ( event.year, self.EVENT_SHORT_EXCEPTIONS.get(event.event_short, event.event_short)) elim_match_dicts, _ = self.parse(elim_match_sched_url, self.MATCH_SCHEDULE_PARSER) match_dicts += elim_match_dicts matches = [ Match(id=Match.renderKeyName(event.key.id(), match_dict.get("comp_level", None), match_dict.get("set_number", 0), match_dict.get("match_number", 0)), event=event.key, game=Match.FRC_GAMES_BY_YEAR.get(event.year, "frc_unknown"), set_number=match_dict.get("set_number", 0), match_number=match_dict.get("match_number", 0), comp_level=match_dict.get("comp_level", None), team_key_names=match_dict.get("team_key_names", None), time_string=match_dict.get("time_string", None), alliances_json=match_dict.get("alliances_json", None)) for match_dict in match_dicts ] MatchHelper.add_match_times(event, matches) return matches
def get(self, event_key): self._require_admin() event = Event.get_by_id(event_key) if not event: self.abort(404) event.prepAwardsMatchesTeams() reg_sitevar = Sitevar.get_by_id("cmp_registration_hacks") api_keys = ApiAuthAccess.query( ApiAuthAccess.event_list == ndb.Key(Event, event_key)).fetch() event_medias = Media.query(Media.references == event.key).fetch(500) playoff_template = PlayoffAdvancementHelper.getPlayoffTemplate(event) elim_bracket_html = jinja2_engine.render( "bracket_partials/bracket_table.html", { "bracket_table": event.playoff_bracket, "event": event }) advancement_html = jinja2_engine.render( "playoff_partials/{}.html".format(playoff_template), { "event": event, "playoff_advancement": event.playoff_advancement, "playoff_advancement_tiebreakers": PlayoffAdvancementHelper.ROUND_ROBIN_TIEBREAKERS.get( event.year), "bracket_table": event.playoff_bracket }) if playoff_template else "None" organized_matches = MatchHelper.organizeMatches(event.matches) match_stats = [] for comp_level in Match.COMP_LEVELS: level_matches = organized_matches[comp_level] if not level_matches: continue match_stats.append({ 'comp_level': comp_level, 'level_name': Match.COMP_LEVELS_VERBOSE_FULL[comp_level], 'total': len(level_matches), 'played': len(filter(lambda m: m.has_been_played, level_matches)), 'unplayed': len(filter(lambda m: not m.has_been_played, level_matches)), }) self.template_values.update({ "event": event, "medias": event_medias, "flushed": self.request.get("flushed"), "playoff_types": PlayoffType.type_names, "write_auths": api_keys, "event_sync_disable": reg_sitevar and event_key in reg_sitevar.contents.get('divisions_to_skip', []), "set_start_day_to_last": reg_sitevar and event_key in reg_sitevar.contents.get( 'set_start_to_last_day', []), "skip_eventteams": reg_sitevar and event_key in reg_sitevar.contents.get('skip_eventteams', []), "event_name_override": next( iter( filter(lambda e: e.get("event") == event_key, reg_sitevar.contents.get("event_name_override", []))), {}).get("name", ""), "elim_bracket_html": elim_bracket_html, "advancement_html": advancement_html, 'match_stats': match_stats, 'deleted_count': self.request.get('deleted'), }) path = os.path.join(os.path.dirname(__file__), '../../templates/admin/event_details.html') self.response.out.write(template.render(path, self.template_values))
def get(self): self._require_registration() user = self.user_bundle.account.key favorites = Favorite.query(ancestor=user).fetch() subscriptions = Subscription.query(ancestor=user).fetch() team_keys = set() team_fav = {} team_subs = {} event_keys = set() event_fav = {} event_subs = {} events = [] match_keys = set() match_event_keys = set() match_fav = {} match_subs = {} for item in favorites + subscriptions: if item.model_type == ModelType.TEAM: team_keys.add(ndb.Key(Team, item.model_key)) if type(item) == Favorite: team_fav[item.model_key] = item elif type(item) == Subscription: team_subs[item.model_key] = item elif item.model_type == ModelType.MATCH: match_keys.add(ndb.Key(Match, item.model_key)) match_event_keys.add(ndb.Key(Event, item.model_key.split('_')[0])) if type(item) == Favorite: match_fav[item.model_key] = item elif type(item) == Subscription: match_subs[item.model_key] = item elif item.model_type == ModelType.EVENT: if item.model_key.endswith('*'): # All year events wildcard event_year = int(item.model_key[:-1]) events.append(Event( # add fake event for rendering id=item.model_key, short_name='ALL EVENTS', event_short=item.model_key, year=event_year, start_date=datetime.datetime(event_year, 1, 1), end_date=datetime.datetime(event_year, 1, 1) )) else: event_keys.add(ndb.Key(Event, item.model_key)) if type(item) == Favorite: event_fav[item.model_key] = item elif type(item) == Subscription: event_subs[item.model_key] = item team_futures = ndb.get_multi_async(team_keys) event_futures = ndb.get_multi_async(event_keys) match_futures = ndb.get_multi_async(match_keys) match_event_futures = ndb.get_multi_async(match_event_keys) teams = sorted([team_future.get_result() for team_future in team_futures], key=lambda x: x.team_number) team_fav_subs = [] for team in teams: fav = team_fav.get(team.key.id(), None) subs = team_subs.get(team.key.id(), None) team_fav_subs.append((team, fav, subs)) events += [event_future.get_result() for event_future in event_futures] EventHelper.sort_events(events) event_fav_subs = [] for event in events: fav = event_fav.get(event.key.id(), None) subs = event_subs.get(event.key.id(), None) event_fav_subs.append((event, fav, subs)) matches = [match_future.get_result() for match_future in match_futures] match_events = [match_event_future.get_result() for match_event_future in match_event_futures] MatchHelper.natural_sort_matches(matches) match_fav_subs_by_event = {} for event in match_events: match_fav_subs_by_event[event.key.id()] = (event, []) for match in matches: event_key = match.key.id().split('_')[0] fav = match_fav.get(match.key.id(), None) subs = match_subs.get(match.key.id(), None) match_fav_subs_by_event[event_key][1].append((match, fav, subs)) event_match_fav_subs = sorted(match_fav_subs_by_event.values(), key=lambda x: EventHelper.distantFutureIfNoStartDate(x[0])) event_match_fav_subs = sorted(event_match_fav_subs, key=lambda x: EventHelper.distantFutureIfNoEndDate(x[0])) self.template_values['team_fav_subs'] = team_fav_subs self.template_values['event_fav_subs'] = event_fav_subs self.template_values['event_match_fav_subs'] = event_match_fav_subs self.template_values['status'] = self.request.get('status') self.template_values['year'] = datetime.datetime.now().year self.response.out.write(jinja2_engine.render('mytba.html', self.template_values))
def render_team_details(cls, handler, team, year, is_canonical): media_future = media_query.TeamYearMediaQuery(team.key.id(), year).fetch_async() social_media_future = media_query.TeamSocialMediaQuery(team.key.id()).fetch_async() robot_future = Robot.get_by_id_async('{}_{}'.format(team.key.id(), year)) team_districts_future = team_query.TeamDistrictsQuery(team.key.id()).fetch_async() participation_future = team_query.TeamParticipationQuery(team.key.id()).fetch_async() events_sorted, matches_by_event_key, awards_by_event_key, valid_years = TeamDetailsDataFetcher.fetch(team, year, return_valid_years=True) if not events_sorted: return None participation = [] season_wlt_list = [] offseason_wlt_list = [] year_match_avg_list = [] current_event = None matches_upcoming = None short_cache = False for event in events_sorted: event_matches = matches_by_event_key.get(event.key, []) event_awards = AwardHelper.organizeAwards(awards_by_event_key.get(event.key, [])) matches_organized = MatchHelper.organizeMatches(event_matches) if event.now: current_event = event matches_upcoming = MatchHelper.upcomingMatches(event_matches) if event.within_a_day: short_cache = True if year == 2015: display_wlt = None match_avg = EventHelper.calculateTeamAvgScoreFromMatches(team.key_name, event_matches) year_match_avg_list.append(match_avg) qual_avg, elim_avg, _, _ = match_avg else: qual_avg = None elim_avg = None wlt = EventHelper.calculateTeamWLTFromMatches(team.key_name, event_matches) if event.event_type_enum in EventType.SEASON_EVENT_TYPES: season_wlt_list.append(wlt) else: offseason_wlt_list.append(wlt) if wlt["win"] + wlt["loss"] + wlt["tie"] == 0: display_wlt = None else: display_wlt = wlt team_rank = None if event.rankings: for element in event.rankings: if str(element[1]) == str(team.team_number): team_rank = element[0] break participation.append({'event': event, 'matches': matches_organized, 'wlt': display_wlt, 'qual_avg': qual_avg, 'elim_avg': elim_avg, 'rank': team_rank, 'awards': event_awards}) season_wlt = None offseason_wlt = None if year == 2015: year_wlt = None year_qual_scores = [] year_elim_scores = [] for _, _, event_qual_scores, event_elim_scores in year_match_avg_list: year_qual_scores += event_qual_scores year_elim_scores += event_elim_scores year_qual_avg = float(sum(year_qual_scores)) / len(year_qual_scores) if year_qual_scores != [] else None year_elim_avg = float(sum(year_elim_scores)) / len(year_elim_scores) if year_elim_scores != [] else None else: year_qual_avg = None year_elim_avg = None season_wlt = {"win": 0, "loss": 0, "tie": 0} offseason_wlt = {"win": 0, "loss": 0, "tie": 0} for wlt in season_wlt_list: season_wlt["win"] += wlt["win"] season_wlt["loss"] += wlt["loss"] season_wlt["tie"] += wlt["tie"] if season_wlt["win"] + season_wlt["loss"] + season_wlt["tie"] == 0: season_wlt = None for wlt in offseason_wlt_list: offseason_wlt["win"] += wlt["win"] offseason_wlt["loss"] += wlt["loss"] offseason_wlt["tie"] += wlt["tie"] if offseason_wlt["win"] + offseason_wlt["loss"] + offseason_wlt["tie"] == 0: offseason_wlt = None medias_by_slugname = MediaHelper.group_by_slugname([media for media in media_future.get_result()]) image_medias = MediaHelper.get_images(media_future.get_result()) social_medias = sorted(social_media_future.get_result(), key=MediaHelper.social_media_sorter) preferred_image_medias = filter(lambda x: team.key in x.preferred_references, image_medias) district_name = None district_abbrev = None team_districts = team_districts_future.get_result() for district in team_districts: if district.year == year: district_abbrev = district.abbreviation district_name = district.display_name last_competed = None participation_years = participation_future.get_result() if len(participation_years) > 0: last_competed = max(participation_years) current_year = datetime.date.today().year handler.template_values.update({ "is_canonical": is_canonical, "team": team, "participation": participation, "year": year, "years": valid_years, "season_wlt": season_wlt, "offseason_wlt": offseason_wlt, "year_qual_avg": year_qual_avg, "year_elim_avg": year_elim_avg, "current_event": current_event, "matches_upcoming": matches_upcoming, "medias_by_slugname": medias_by_slugname, "social_medias": social_medias, "image_medias": image_medias, "preferred_image_medias": preferred_image_medias, "robot": robot_future.get_result(), "district_name": district_name, "district_abbrev": district_abbrev, "last_competed": last_competed, "current_year": current_year, }) if short_cache: handler._cache_expiration = handler.SHORT_CACHE_EXPIRATION return jinja2_engine.render('team_details.html', handler.template_values)
def test_2017flwp_sequence(self): event = Event(id='2017flwp', event_short='flwp', year=2017, event_type_enum=0, timezone_id='America/New_York') event.put() event_code = 'flwp' file_prefix = 'frc-api-response/v2.0/2017/schedule/{}/playoff/hybrid/'.format( event_code) context = ndb.get_context() result = context.urlfetch( 'https://www.googleapis.com/storage/v1/b/bucket/o?bucket=tbatv-prod-hrd.appspot.com&prefix={}' .format(file_prefix)).get_result() for item in json.loads(result.content)['items']: filename = item['name'] time_str = filename.replace(file_prefix, '').replace('.json', '').strip() file_time = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S.%f") query_time = file_time + datetime.timedelta(seconds=30) MatchManipulator.createOrUpdate(DatafeedFMSAPI( 'v2.0', sim_time=query_time).getMatches('2017{}'.format(event_code)), run_post_update_hook=False) MatchHelper.deleteInvalidMatches(event.matches, event) sf_matches = Match.query(Match.event == ndb.Key(Event, '2017flwp'), Match.comp_level == 'sf').fetch() self.assertEqual(len(sf_matches), 7) self.assertEqual( Match.get_by_id('2017flwp_sf1m1').alliances['red']['score'], 305) self.assertEqual( Match.get_by_id('2017flwp_sf1m1').alliances['blue']['score'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m1').score_breakdown['red'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017flwp_sf1m1').score_breakdown['blue'] ['totalPoints'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m2').alliances['red']['score'], 165) self.assertEqual( Match.get_by_id('2017flwp_sf1m2').alliances['blue']['score'], 258) self.assertEqual( Match.get_by_id('2017flwp_sf1m2').score_breakdown['red'] ['totalPoints'], 165) self.assertEqual( Match.get_by_id('2017flwp_sf1m2').score_breakdown['blue'] ['totalPoints'], 258) self.assertEqual( Match.get_by_id('2017flwp_sf1m3').alliances['red']['score'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m3').alliances['blue']['score'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m3').score_breakdown['red'] ['totalPoints'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m3').score_breakdown['blue'] ['totalPoints'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m4').alliances['red']['score'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m4').alliances['blue']['score'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m4').score_breakdown['red'] ['totalPoints'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m4').score_breakdown['blue'] ['totalPoints'], 255) self.assertEqual( Match.get_by_id('2017flwp_sf1m5').alliances['red']['score'], 165) self.assertEqual( Match.get_by_id('2017flwp_sf1m5').alliances['blue']['score'], 263) self.assertEqual( Match.get_by_id('2017flwp_sf1m5').score_breakdown['red'] ['totalPoints'], 165) self.assertEqual( Match.get_by_id('2017flwp_sf1m5').score_breakdown['blue'] ['totalPoints'], 263)
def render_team_history(cls, handler, team, is_canonical): event_team_keys_future = EventTeam.query( EventTeam.team == team.key).fetch_async(1000, keys_only=True) award_keys_future = Award.query( Award.team_list == team.key).fetch_async(1000, keys_only=True) event_teams_futures = ndb.get_multi_async( event_team_keys_future.get_result()) awards_futures = ndb.get_multi_async(award_keys_future.get_result()) event_keys = [ event_team_future.get_result().event for event_team_future in event_teams_futures ] events_futures = ndb.get_multi_async(event_keys) awards_by_event = {} for award_future in awards_futures: award = award_future.get_result() if award.event.id() not in awards_by_event: awards_by_event[award.event.id()] = [award] else: awards_by_event[award.event.id()].append(award) event_awards = [] current_event = None matches_upcoming = None short_cache = False for event_future in events_futures: event = event_future.get_result() if event.now: current_event = event team_matches_future = Match.query(Match.event == event.key, Match.team_key_names == team.key_name)\ .fetch_async(500, keys_only=True) matches = ndb.get_multi(team_matches_future.get_result()) matches_upcoming = MatchHelper.upcomingMatches(matches) if event.within_a_day: short_cache = True if event.key_name in awards_by_event: sorted_awards = AwardHelper.organizeAwards( awards_by_event[event.key_name]) else: sorted_awards = [] event_awards.append((event, sorted_awards)) event_awards = sorted( event_awards, key=lambda (e, _): e.start_date if e.start_date else datetime.datetime(e.year, 12, 31)) years = sorted( set([ et.get_result().year for et in event_teams_futures if et.get_result().year is not None ])) handler.template_values.update({ 'is_canonical': is_canonical, 'team': team, 'event_awards': event_awards, 'years': years, 'current_event': current_event, 'matches_upcoming': matches_upcoming }) if short_cache: handler._cache_expiration = handler.SHORT_CACHE_EXPIRATION path = os.path.join(os.path.dirname(__file__), '../templates/team_history.html') return template.render(path, handler.template_values)
def test_2017scmb(self): event = Event(id='2017scmb', event_short='scmb', year=2017, event_type_enum=0, timezone_id='America/New_York') event.put() MatchManipulator.createOrUpdate( DatafeedFMSAPI('v2.0', sim_time=datetime.datetime( 2017, 3, 04, 19, 17)).getMatches('2017scmb')) MatchHelper.deleteInvalidMatches(event.matches, event) qf_matches = Match.query(Match.event == ndb.Key(Event, '2017scmb'), Match.comp_level == 'qf').fetch() self.assertEqual(len(qf_matches), 12) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').alliances['red']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').alliances['blue']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').score_breakdown['red'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').score_breakdown['blue'] ['totalPoints'], 305) ndb.get_context().clear_cache( ) # Prevent data from leaking between tests MatchManipulator.createOrUpdate( DatafeedFMSAPI('v2.0', sim_time=datetime.datetime( 2017, 3, 04, 19, 50)).getMatches('2017scmb')) MatchHelper.deleteInvalidMatches(event.matches, event) qf_matches = Match.query(Match.event == ndb.Key(Event, '2017scmb'), Match.comp_level == 'qf').fetch() self.assertEqual(len(qf_matches), 12) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').alliances['red']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').alliances['blue']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').score_breakdown['red'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').score_breakdown['blue'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').alliances['red']['score'], 213) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').alliances['blue']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').score_breakdown['red'] ['totalPoints'], 213) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').score_breakdown['blue'] ['totalPoints'], 305) ndb.get_context().clear_cache( ) # Prevent data from leaking between tests MatchManipulator.createOrUpdate( DatafeedFMSAPI('v2.0', sim_time=datetime.datetime( 2017, 3, 04, 20, 12)).getMatches('2017scmb')) MatchHelper.deleteInvalidMatches(event.matches, event) qf_matches = Match.query(Match.event == ndb.Key(Event, '2017scmb'), Match.comp_level == 'qf').fetch() self.assertEqual(len(qf_matches), 12) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').alliances['red']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').alliances['blue']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').score_breakdown['red'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').score_breakdown['blue'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').alliances['red']['score'], 213) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').alliances['blue']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').score_breakdown['red'] ['totalPoints'], 213) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').score_breakdown['blue'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m3').alliances['red']['score'], 312) self.assertEqual( Match.get_by_id('2017scmb_qf4m3').alliances['blue']['score'], 255) self.assertEqual( Match.get_by_id('2017scmb_qf4m3').score_breakdown['red'] ['totalPoints'], 312) self.assertEqual( Match.get_by_id('2017scmb_qf4m3').score_breakdown['blue'] ['totalPoints'], 255) ndb.get_context().clear_cache( ) # Prevent data from leaking between tests MatchManipulator.createOrUpdate( DatafeedFMSAPI('v2.0', sim_time=datetime.datetime( 2017, 3, 04, 20, 48)).getMatches('2017scmb')) MatchHelper.deleteInvalidMatches(event.matches, event) qf_matches = Match.query(Match.event == ndb.Key(Event, '2017scmb'), Match.comp_level == 'qf').fetch() self.assertEqual(len(qf_matches), 13) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').alliances['red']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').alliances['blue']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').score_breakdown['red'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m1').score_breakdown['blue'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').alliances['red']['score'], 213) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').alliances['blue']['score'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').score_breakdown['red'] ['totalPoints'], 213) self.assertEqual( Match.get_by_id('2017scmb_qf4m2').score_breakdown['blue'] ['totalPoints'], 305) self.assertEqual( Match.get_by_id('2017scmb_qf4m3').alliances['red']['score'], 312) self.assertEqual( Match.get_by_id('2017scmb_qf4m3').alliances['blue']['score'], 255) self.assertEqual( Match.get_by_id('2017scmb_qf4m3').score_breakdown['red'] ['totalPoints'], 312) self.assertEqual( Match.get_by_id('2017scmb_qf4m3').score_breakdown['blue'] ['totalPoints'], 255) self.assertEqual( Match.get_by_id('2017scmb_qf4m4').alliances['red']['score'], 310) self.assertEqual( Match.get_by_id('2017scmb_qf4m4').alliances['blue']['score'], 306) self.assertEqual( Match.get_by_id('2017scmb_qf4m4').score_breakdown['red'] ['totalPoints'], 310) self.assertEqual( Match.get_by_id('2017scmb_qf4m4').score_breakdown['blue'] ['totalPoints'], 306)
def parse(self, response): """ This currently only works for the 2015 game, where elims matches are all part of one set. """ matches = response['Schedule'] event_key = '{}{}'.format(self.year, self.event_short) event = Event.get_by_id(event_key) if event.timezone_id: event_tz = pytz.timezone(event.timezone_id) else: logging.warning( "Event {} has no timezone! Match times may be wrong.".format( event_key)) event_tz = None set_number = 1 parsed_matches = [] for match in matches: comp_level = get_comp_level(match['level'], match['matchNumber']) match_number = get_match_number(comp_level, match['matchNumber']) red_teams = [] blue_teams = [] team_key_names = [] null_team = False for team in match['Teams']: if team['teamNumber'] is None: null_team = True team_key = 'frc{}'.format(team['teamNumber']) team_key_names.append(team_key) if 'Red' in team['station']: red_teams.append(team_key) elif 'Blue' in team['station']: blue_teams.append(team_key) if null_team and match['scoreRedFinal'] is None and match[ 'scoreBlueFinal'] is None: continue alliances = { 'red': { 'teams': red_teams, 'score': match['scoreRedFinal'] }, 'blue': { 'teams': blue_teams, 'score': match['scoreBlueFinal'] } } score_breakdown = { 'red': { 'auto_points': match['scoreRedAuto'], 'foul_points': match['scoreRedFoul'] }, 'blue': { 'auto_points': match['scoreBlueAuto'], 'foul_points': match['scoreBlueFoul'] } } time = datetime.datetime.strptime(match['startTime'], TIME_PATTERN) actual_time_raw = match[ 'actualStartTime'] if 'actualStartTime' in match else None actual_time = None if event_tz is not None: time = time - event_tz.utcoffset(time) if actual_time_raw is not None: actual_time = datetime.datetime.strptime( actual_time_raw, TIME_PATTERN) if event_tz is not None: actual_time = actual_time - event_tz.utcoffset(actual_time) parsed_matches.append( Match(id=Match.renderKeyName(event_key, comp_level, set_number, match_number), event=event.key, year=event.year, set_number=set_number, match_number=match_number, comp_level=comp_level, team_key_names=team_key_names, time=time, actual_time=actual_time, alliances_json=json.dumps(alliances), score_breakdown_json=json.dumps(score_breakdown))) # Fix null teams in elims (due to FMS API failure, some info not complete) # Should only happen for sf and f matches organized_matches = MatchHelper.organizeMatches(parsed_matches) for level in ['sf', 'f']: playoff_advancement = MatchHelper.generatePlayoffAdvancement2015( organized_matches) if playoff_advancement[LAST_LEVEL[level]] != []: for match in organized_matches[level]: if 'frcNone' in match.team_key_names: if level == 'sf': red_seed, blue_seed = QF_SF_MAP[match.match_number] else: red_seed = 0 blue_seed = 1 red_teams = [ 'frc{}'.format(t) for t in playoff_advancement[ LAST_LEVEL[level]][red_seed][0] ] blue_teams = [ 'frc{}'.format(t) for t in playoff_advancement[ LAST_LEVEL[level]][blue_seed][0] ] alliances = match.alliances alliances['red']['teams'] = red_teams alliances['blue']['teams'] = blue_teams match.alliances_json = json.dumps(alliances) match.team_key_names = red_teams + blue_teams fixed_matches = [] for key, matches in organized_matches.items(): if key != 'num': for match in matches: if 'frcNone' not in match.team_key_names: fixed_matches.append(match) return fixed_matches