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 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))
Example #3
0
    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 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 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 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)
Example #7
0
 def generate_team_at_event_status(cls, team_key, event, matches=None):
     """
     Generate a dict containing team@event status information
     :param team_key: Key name of the team to focus on
     :param event: Event object
     :param matches: Organized matches (via MatchHelper.organizeMatches) from the event, optional
     """
     event_details = event.details
     if not matches:
         matches = event.matches
     team_matches = [m for m in matches if team_key in m.team_key_names]
     next_match = MatchHelper.upcomingMatches(team_matches, num=1)
     last_match = MatchHelper.recentMatches(team_matches, num=1)
     matches = MatchHelper.organizeMatches(matches)
     return copy.deepcopy(
         {
             'qual':
             cls._build_qual_info(team_key, event_details, matches,
                                  event.year),
             'alliance':
             cls._build_alliance_info(team_key, event_details, matches),
             'playoff':
             cls._build_playoff_info(team_key, event_details, matches,
                                     event.year, event.playoff_type),
             'last_match_key':
             last_match[0].key_name if last_match else None,
             'next_match_key':
             next_match[0].key_name if next_match else None,
         }
     )  # TODO: Results are getting mixed unless copied. 2017-02-03 -fangeugene
    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_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 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 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 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)
Example #14
0
    def get(self, event_key):
        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)

        event_future = EventQuery(event_key).fetch_async(return_updated=True)
        matches_future = EventMatchesQuery(event_key).fetch_async(
            return_updated=True)

        event, _ = event_future.get_result()
        matches, _ = matches_future.get_result()

        cleaned_matches = MatchHelper.deleteInvalidMatches(matches, event)
        matches = MatchHelper.organizeMatches(cleaned_matches)
        bracket_table, playoff_advancement, _, _ = PlayoffAdvancementHelper.generatePlayoffAdvancement(
            event, matches)

        event_details = EventDetails(
            id=event.key_name,
            playoff_advancement={
                'advancement': playoff_advancement,
                'bracket': bracket_table,
            },
        )
        EventDetailsManipulator.createOrUpdate(event_details)

        self.response.out.write("New playoff advancement for {}\n{}".format(
            event.key_name,
            json.dumps(event_details.playoff_advancement,
                       indent=2,
                       sort_keys=True)))
Example #15
0
    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_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 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 get(self, event_key, comp_level, to_delete):
        self._require_admin()

        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)

        organized_matches = MatchHelper.organizeMatches(event.matches)
        if comp_level not in organized_matches:
            self.abort(400)
            return

        matches_to_delete = []
        if to_delete == 'all':
            matches_to_delete = [m for m in organized_matches[comp_level]]
        elif to_delete == 'unplayed':
            matches_to_delete = [
                m for m in organized_matches[comp_level]
                if not m.has_been_played
            ]

        delete_count = len(matches_to_delete)
        if matches_to_delete:
            MatchManipulator.delete(matches_to_delete)

        self.redirect("/admin/event/{}?deleted={}#matches".format(
            event_key, delete_count))
Example #19
0
    def test_parse_playoff(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_playoff.json',
                  'r') as f:
            matches = FMSAPIHybridScheduleParser(2016, 'nyny').parse(
                json.loads(f.read()))

            self.assertTrue(isinstance(matches, list))
            self.assertEqual(len(matches), 15)

            # 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"]), 9)
            self.assertEqual(len(clean_matches["sf"]), 4)
            self.assertEqual(len(clean_matches["f"]), 2)
    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.within_a_day:
            matches_recent = MatchHelper.recentMatches(event.matches)
            matches_upcoming = MatchHelper.upcomingMatches(event.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 _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 _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 calc_rank_based_match_points(cls, event, district_points, matches,
                                     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
        rankings = event.details.rankings2
        if rankings:
            num_teams = len(rankings)
            alpha = 1.07
            for ranking in rankings:
                rank = ranking['rank']
                team = ranking['team_key']
                qual_points = int(
                    math.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:
            msg = "Event {} has no rankings for qual_points calculations!".format(
                event.key.id())
            if event.event_type_enum in EventType.SEASON_EVENT_TYPES:
                logging.warning(msg)
            else:
                logging.info(msg)

        matches = MatchHelper.organizeMatches(matches)

        # 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
        if event.year == 2015:
            cls.calc_elim_match_points_2015(district_points, matches,
                                            POINTS_MULTIPLIER)
        else:
            elim_matches = matches.get('qf', []) + matches.get(
                'sf', []) + matches.get('f', [])
            cls.calc_elim_match_points(district_points, elim_matches,
                                       POINTS_MULTIPLIER)
    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:]
        matches, event_details = yield TeamEventMatchesQuery(
            team_key,
            event.key.id()).fetch_async(), EventDetails.get_by_id_async(
                event.key.id())
        matches = MatchHelper.organizeMatches(matches)

        # Compute alliances
        alliance_number = cls._get_alliance_number(team_key, event_details)

        # Playoff Status
        status, short_playoff_status = cls._get_playoff_status(
            team_key, matches, alliance_number)

        # Still in quals or team did not make it to elims
        # Compute qual W-L-T
        wins, losses, ties, unplayed_qual = cls._get_qual_wlt(
            team_key, matches)
        if wins == 0 and losses == 0 and ties == 0:
            # No matches played yet
            status = "Team {} has not played any matches yet.".format(
                team_number) if not status else status

        # Compute rank & num_teams
        # Gets record from ranking data to account for surrogate matches
        rank, ranking_points, record, num_teams = cls._get_rank(
            team_number, event_details)
        rank_str = "Rank {} with {} RP".format(rank, ranking_points)

        # Compute final long status for nightbot, if one isn't already there
        if unplayed_qual > 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_number is None:
                status = "Team {} ended qualification matches at rank {}/{} with a record of {}.".format(
                    team_number, rank, num_teams, record)
            elif alliance_number == 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 alliance #{}.".format(
                    team_number, alliance_number)

        raise ndb.Return(status, record, rank_str, short_playoff_status)
    def _render(self, event_key):
        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)

        matches = MatchHelper.organizeMatches(event.matches)

        self.template_values.update({"event": event, "matches": matches, "datetime": datetime.datetime.now()})

        path = os.path.join(os.path.dirname(__file__), "../templates/event_rss.xml")
        self.response.headers["content-type"] = "application/xml; charset=UTF-8"
        return template.render(path, self.template_values)
    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
        if event.year == 2015:
            cls.calc_elim_match_points_2015(district_points, matches,
                                            POINTS_MULTIPLIER)
        else:
            elim_matches = matches.get('qf', []) + matches.get(
                'sf', []) + matches.get('f', [])
            cls.calc_elim_match_points(district_points, elim_matches,
                                       POINTS_MULTIPLIER)
    def post(self):
        self._require_admin()

        event_key = self.request.get("event_key").strip()
        event = Event.get_by_id(event_key)
        if not event:
            self.redirect("/admin/event/" + event.key_name)
            return

        if event.playoff_type != PlayoffType.ROUND_ROBIN_6_TEAM:
            logging.warning("Can't set advancement for non-round robin events")
            self.redirect("/admin/event/" + event.key_name)
            return

        advancement_csv = self.request.get("advancement_csv")
        comp_level = self.request.get("comp_level")
        matches_per_team = int(self.request.get("num_matches"))
        if comp_level not in Match.ELIM_LEVELS:
            logging.warning("Bad comp level: {}".format(comp_level))
            self.redirect("/admin/event/" + event.key_name)
            return
        parsed_advancement = CSVAdvancementParser.parse(
            advancement_csv, matches_per_team)
        advancement = PlayoffAdvancementHelper.generatePlayoffAdvancementFromCSV(
            event, parsed_advancement, comp_level)

        cleaned_matches = MatchHelper.deleteInvalidMatches(
            event.matches, event)
        matches = MatchHelper.organizeMatches(cleaned_matches)
        bracket_table = PlayoffAdvancementHelper.generateBracket(
            matches, event, event.alliance_selections)
        comp_levels = bracket_table.keys()
        for comp_level in comp_levels:
            if comp_level != 'f':
                del bracket_table[comp_level]

        existing_details = EventDetails.get_by_id(event.key_name)
        new_advancement = existing_details.playoff_advancement if existing_details and existing_details.playoff_advancement else {}
        new_advancement.update(advancement)
        event_details = EventDetails(
            id=event.key_name,
            playoff_advancement={
                'advancement': new_advancement,
                'bracket': bracket_table,
            },
        )
        EventDetailsManipulator.createOrUpdate(event_details)

        self.redirect("/admin/event/" + event.key_name)
        return
    def calc_rank_based_match_points(cls, event, district_points, matches, 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:
            msg = "Event {} has no rankings for qual_points calculations!".format(event.key.id())
            if event.event_type_enum in EventType.SEASON_EVENT_TYPES:
                logging.warning(msg)
            else:
                logging.info(msg)

        matches = MatchHelper.organizeMatches(matches)

        # 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
        if event.year == 2015:
            cls.calc_elim_match_points_2015(district_points, matches, POINTS_MULTIPLIER)
        else:
            elim_matches = matches.get("qf", []) + matches.get("sf", []) + matches.get("f", [])
            cls.calc_elim_match_points(district_points, elim_matches, POINTS_MULTIPLIER)
    def _render(self, event_key):
        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)

        matches = MatchHelper.organizeMatches(event.matches)

        self.template_values.update({
            "event": event,
            "matches": matches,
            "datetime": datetime.datetime.now()
        })

        self.response.headers['content-type'] = 'application/xml; charset=UTF-8'
        return jinja2_engine.render('event_rss.xml', self.template_values)
    def _render(self, event_key):
        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)

        matches = MatchHelper.organizeMatches(event.matches)

        self.template_values.update({
            "event": event,
            "matches": matches,
            "datetime": datetime.datetime.now()
        })

        self.response.headers['content-type'] = 'application/xml; charset=UTF-8'
        return jinja2_engine.render('event_rss.xml', self.template_values)
Example #31
0
    def _render(self, event_key):
        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)

        matches = MatchHelper.organizeMatches(event.matches)

        self.template_values.update({
            "event": event,
            "matches": matches,
            "datetime": datetime.datetime.now()
        })

        path = os.path.join(os.path.dirname(__file__), '../templates/event_rss.xml')
        self.response.headers['content-type'] = 'application/xml; charset=UTF-8'
        return template.render(path, self.template_values)
    def _render(self, event_key):
        event = Event.get_by_id(event_key)
        if not event:
            return self.redirect("/error/404")

        matches = MatchHelper.organizeMatches(event.matches)
    
        template_values = {
                "event": event,
                "matches": matches,
                "datetime": datetime.datetime.now()
        }

        path = os.path.join(os.path.dirname(__file__), '../templates/event_rss.xml')
        self.response.headers.add_header('content-type', 'application/xml', charset='utf-8')
        return template.render(path, template_values)
 def generate_team_at_event_status(cls, team_key, event, matches=None):
     """
     Generate a dict containing team@event status information
     :param team_key: Key name of the team to focus on
     :param event: Event object
     :param matches: Organized matches (via MatchHelper.organizeMatches) from the event, optional
     """
     event_details = event.details
     if not matches:
         matches = event.matches
         matches = MatchHelper.organizeMatches(matches)
     return copy.deepcopy({
         'qual': cls._build_qual_info(team_key, event_details, matches, event.year),
         'alliance': cls._build_alliance_info(team_key, event_details, matches),
         'playoff': cls._build_playoff_info(team_key, event_details, matches, event.year)
     })  # TODO: Results are getting mixed unless copied. 2017-02-03 -fangeugene
Example #34
0
    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 = MatchHelper.generateBracket(matches, event, event.alliance_selections)
            if 'f' in bracket:
                winning_alliance = '{}_alliance'.format(bracket['f'][1]['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'][1][winning_alliance] if team.isdigit()],
                    recipient_json_list=[json.dumps({'team_number': team, 'awardee': None}) for team in bracket['f'][1][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'][1][losing_alliance] if team.isdigit()],
                    recipient_json_list=[json.dumps({'team_number': team, 'awardee': None}) for team in bracket['f'][1][losing_alliance]],
                ))
                AwardManipulator.createOrUpdate(awards)

        self.response.out.write("Finished post-event tasks for {}. Created awards: {}".format(event_key, awards))
 def generate_team_at_event_status(cls, team_key, event, matches=None):
     """
     Generate a dict containing team@event status information
     :param team_key: Key name of the team to focus on
     :param event: Event object
     :param matches: Organized matches (via MatchHelper.organizeMatches) from the event, optional
     """
     event_details = event.details
     if not matches:
         matches = event.matches
         matches = [match for match in matches if match.comp_level in Match.ELIM_LEVELS]
         matches = MatchHelper.organizeMatches(matches)
     return {
         'rank': cls._build_ranking_info(team_key, event_details),
         'alliance': cls._build_alliance_info(team_key, event_details),
         'playoff': cls._build_playoff_status(team_key, event_details, matches)
     }
    def calc_rank_based_match_points(cls, event, district_points, matches, 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
        rankings = event.details.rankings2
        if rankings:
            num_teams = len(rankings)
            alpha = 1.07
            for ranking in rankings:
                rank = ranking['rank']
                team = ranking['team_key']
                qual_points = int(math.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:
            msg = "Event {} has no rankings for qual_points calculations!".format(event.key.id())
            if event.event_type_enum in EventType.SEASON_EVENT_TYPES:
                logging.warning(msg)
            else:
                logging.info(msg)

        matches = MatchHelper.organizeMatches(matches)

        # 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
        if event.year == 2015:
            cls.calc_elim_match_points_2015(district_points, matches, POINTS_MULTIPLIER)
        else:
            elim_matches = matches.get('qf', []) + matches.get('sf', []) + matches.get('f', [])
            cls.calc_elim_match_points(district_points, elim_matches, POINTS_MULTIPLIER)
    def generateTeamAtEventStatus(cls, team_key, event):
        """
        Generate Team@Event status items
        :return: a tuple <long summary string, qual record, qual ranking, playoff status>
        """
        team_number = team_key[3:]
        matches_future = TeamEventMatchesQuery(team_key, event.key.id()).fetch_async()
        matches = MatchHelper.organizeMatches(matches_future.get_result())

        # Compute alliances
        alliance_number = cls._get_alliance_number(team_key, event)

        # Playoff Status
        status, short_playoff_status = cls._get_playoff_status(team_key, matches, alliance_number)

        # Still in quals or team did not make it to elims
        # Compute qual W-L-T
        wins, losses, ties, unplayed_qual = cls._get_qual_wlt(team_key, matches)
        if wins == 0 and losses == 0 and ties == 0:
            # No matches played yet
            status = "Team {} has not played any matches yet.".format(team_number) if not status else status

        # Compute rank & num_teams
        # Gets record from ranking data to account for surrogate matches
        rank, ranking_points, record, num_teams = cls._get_rank(team_number, event)
        rank_str = "Rank {} with {} RP".format(rank, ranking_points)

        # Compute final long status for nightbot, if one isn't already there
        if unplayed_qual > 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_number is None:
                status = "Team {} ended qualification matches at rank {}/{} with a record of {}.".format(team_number, rank, num_teams, record)
            elif alliance_number == 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 alliance #{}.".format(team_number, alliance_number)

        return status, record, rank_str, short_playoff_status
 def get(self, event_key):
     memcache_key = "event_detail_%s" % event_key
     html = memcache.get(memcache_key)
     
     if html is None:
         event = Event.get_by_key_name(event_key)
         matches = MatchHelper.organizeMatches(event.match_set)
         teams = TeamHelper.sortTeams([a.team for a in event.teams])
     
         template_values = {
             "event": event,
             "matches": matches,
             "teams": teams,
         }
             
         path = os.path.join(os.path.dirname(__file__), '../templates/events/details.html')
         html = template.render(path, template_values)
         memcache.set(memcache_key, html, 300)
     
     self.response.out.write(html)
 def generate_team_at_event_status(cls, team_key, event, matches=None):
     """
     Generate a dict containing team@event status information
     :param team_key: Key name of the team to focus on
     :param event: Event object
     :param matches: Organized matches (via MatchHelper.organizeMatches) from the event, optional
     """
     event_details = event.details
     if not matches:
         matches = event.matches
     team_matches = [m for m in matches if team_key in m.team_key_names]
     next_match = MatchHelper.upcomingMatches(team_matches, num=1)
     last_match = MatchHelper.recentMatches(team_matches, num=1)
     matches = MatchHelper.organizeMatches(matches)
     return copy.deepcopy({
         'qual': cls._build_qual_info(team_key, event_details, matches, event.year),
         'alliance': cls._build_alliance_info(team_key, event_details, matches),
         'playoff': cls._build_playoff_info(team_key, event_details, matches, event.year, event.playoff_type),
         'last_match_key': last_match[0].key_name if last_match else None,
         'next_match_key': next_match[0].key_name if next_match else None,
     })  # TODO: Results are getting mixed unless copied. 2017-02-03 -fangeugene
 def get(self, event_key):
     memcache_key = "event_rss_%s" % event_key
     html = memcache.get(memcache_key)
     
     if html is None:
         event = Event.get_by_key_name(event_key)
         matches = MatchHelper.organizeMatches(event.match_set)
         
         rss_items = []
         # Loop through and generate RSS items for each match
         for match in matches['f'] + matches['sf'] + matches['qf'] + matches['ef'] + matches['qm']:
             match.unpack_json()
             new_item = PyRSS2Gen.RSSItem(
                 title = str(match.verbose_name()),
                 link = 'http://www.thebluealliance.com/match/' + match.get_key_name() + '',
                 
                 # List the red and blue alliance teams and their score
                 # TODO: Make this generic in case there's ever not just red/blue -gregmarra 12 Mar 2011
                 # TODO: Make this output format something either very machine or very human readable.
                 # Probably opt for human, since machines should be using the API. -gregmarra 12 Mar 2011
                 description = "Red Alliance: " + ' '.join(match.alliances["red"]["teams"]) + " "
                               + "Score: " + str(match.alliances["red"]["score"]) + " "
                               + "Blue Alliance: " + ' '.join(match.alliances["blue"]["teams"]) + " "
                               + "Score: " + str(match.alliances["blue"]["score"])
      
             )
             rss_items.append(new_item)
         
         # Create final rss document
         rss = PyRSS2Gen.RSS2(
             title = event.name + "-- " + str(event.year),
             link = 'http://www.thebluealliance.com/event/' + str(event.get_key_name()) + '',
             description = "RSS feed for the " + event.name + " provided by The Blue Alliance." ,
             lastBuildDate = datetime.datetime.now(),
             items = rss_items
         )
         html = rss.to_xml()
         memcache.set(memcache_key, html, 300)
     
     self.response.out.write(html)
 def generate_team_at_event_status(cls, team_key, event, matches=None):
     """
     Generate a dict containing team@event status information
     :param team_key: Key name of the team to focus on
     :param event: Event object
     :param matches: Organized matches (via MatchHelper.organizeMatches) from the event, optional
     """
     event_details = event.details
     if not matches:
         matches = event.matches
         matches = MatchHelper.organizeMatches(matches)
     return copy.deepcopy(
         {
             'qual':
             cls._build_qual_info(team_key, event_details, matches,
                                  event.year),
             'alliance':
             cls._build_alliance_info(team_key, event_details, matches),
             'playoff':
             cls._build_playoff_info(team_key, event_details, matches,
                                     event.year)
         }
     )  # TODO: Results are getting mixed unless copied. 2017-02-03 -fangeugene
    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 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 = []
        remapped_matches = {}  # If a key changes due to a tiebreaker
        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 = []
            red_surrogates = []
            blue_surrogates = []
            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)
                    if team['surrogate']:
                        red_surrogates.append(team_key)
                elif 'Blue' in team['station']:
                    blue_teams.append(team_key)
                    if team['surrogate']:
                        blue_surrogates.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,
                    'score': match['scoreRedFinal']
                },
                'blue': {
                    'teams': blue_teams,
                    'surrogates': blue_surrogates,
                    '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 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 = {}
                sampled_rp2 = {}
                sampled_tiebreaker = {}
                # 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']
                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']

                # 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, 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
            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']
                }
            }

            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 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))
Example #47
0
    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']
                        elif match.year == 2020:
                            sampled_rp1[alliance_color] = match.score_breakdown[
                                alliance_color]['shieldEnergizedRankingPoint']
                            sampled_rp2[
                                alliance_color] = match.score_breakdown[
                                    alliance_color][
                                        'shieldOperationalRankingPoint']
                            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']
                        elif match.year == 2020:
                            sampled_rp1[alliance_color] = np.random.uniform(
                                high=1) < prediction[alliance_color][
                                    'prob_shield_energized']
                            sampled_rp2[alliance_color] = np.random.uniform(
                                high=1) < prediction[alliance_color][
                                    'prob_shield_operational']
                            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 _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()

        team_key = 'frc{}'.format(team_number)
        matches_future = TeamEventMatchesQuery(team_key, event.key.id()).fetch_async()
        matches = MatchHelper.organizeMatches(matches_future.get_result())

        # Compute alliances
        alliance_number = None
        if event.alliance_selections:
            for i, alliance in enumerate(event.alliance_selections):
                if team_key in alliance['picks']:
                    alliance_number = i + 1
                    break
            else:
                alliance_number = 0  # Team didn't make it to elims

        level_map = {
            'qf': 'quarters',
            'sf': 'semis',
            'f': 'the finals',
        }
        for comp_level in ['f', 'sf', 'qf']:  # playoffs
            level_str = level_map[comp_level]
            if matches[comp_level]:
                wins = 0
                losses = 0
                for match in matches[comp_level]:
                    if match.has_been_played:
                        if team_key in match.alliances[match.winning_alliance]['teams']:
                            wins += 1
                        else:
                            losses += 1
                if wins == 2:
                    if comp_level == 'f':
                        return "{}[{}] Team {} won the event on alliance #{}.".format(user_str, event_code_upper, team_number, alliance_number)
                    else:
                        return "{}[{}] Team {} won {} on alliance #{}.".format(user_str, event_code_upper, team_number, level_str, alliance_number)
                elif losses == 2:
                    return "{}[{}] Team {} got knocked out in {} on alliance #{}.".format(user_str, event_code_upper, team_number, level_str, alliance_number)
                else:
                    return "{}[{}] Team {} is currently {}-{} in {} on alliance #{}.".format(user_str, event_code_upper, team_number, wins, losses, level_str, alliance_number)

        # Still in quals or team did not make it to elims
        # Compute qual W-L-T
        wins = 0
        losses = 0
        ties = 0
        unplayed_qual = 0
        for match in matches['qm']:
            if match.has_been_played:
                if match.winning_alliance == '':
                    ties += 1
                elif team_key in match.alliances[match.winning_alliance]['teams']:
                    wins += 1
                else:
                    losses += 1
            else:
                unplayed_qual += 1

        if wins == 0 and losses == 0 and ties == 0:
            # No matches played yet
            return "{}[{}] Team {} has not played any matches yet.".format(user_str, event_code_upper, team_number)

        # Compute rank & num_teams
        rank = None
        ranking_points = None
        if event.rankings:
            num_teams = len(event.rankings) - 1
            for i, row in enumerate(event.rankings):
                if row[1] == team_number:
                    rank = i
                    ranking_points = int(float(row[2]))
                    break

        if unplayed_qual > 0:
            if rank is not None:
                return "{}[{}] Team {} is currently rank {}/{} with a record of {}-{}-{} and {} ranking points.".format(user_str, event_code_upper, team_number, rank, num_teams, wins, losses, ties, ranking_points)
            else:
                return "{}[{}] Team {} currently has a record of {}-{}-{}.".format(user_str, event_code_upper, team_number, wins, losses, ties)
        else:
            if alliance_number is None:
                return "{}[{}] Team {} ended qualification matches at rank {}/{} with a record of {}-{}-{}.".format(user_str, event_code_upper, team_number, rank, num_teams, wins, losses, ties)
            elif alliance_number == 0:
                return "{}[{}] Team {} ended qualification matches at rank {}/{} with a record of {}-{}-{} and was not picked for playoff matches.".format(user_str, event_code_upper, team_number, rank, num_teams, wins, losses, ties)
            else:
                return "{}[{}] Team {} will be competing in the playoff matches on alliance #{}.".format(user_str, event_code_upper, team_number, alliance_number)
    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
        event.prep_matches()
        qual_match_count = 0
        playoff_match_count = 0
        playoff_matches = []
        matches = event.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)
Example #50
0
    def render_team_details(cls, handler, team, year, is_canonical):
        media_future = media_query.TeamYearMediaQuery(team.key.id(),
                                                      year).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()

        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 = []
        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 for media in media_future.get_result()])
        image_medias = MediaHelper.get_images(
            [media for media in media_future.get_result()])

        district_name = None
        district_abbrev = None
        team_districts = team_districts_future.get_result()
        if year in team_districts:
            district_key = team_districts[year]
            district_abbrev = district_key[4:]
            district_type = DistrictType.abbrevs[district_abbrev]
            district_name = DistrictType.type_names[district_type]

        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,
            "image_medias": image_medias,
            "robot": robot_future.get_result(),
            "district_name": district_name,
            "district_abbrev": district_abbrev,
        })

        if short_cache:
            handler._cache_expiration = handler.SHORT_CACHE_EXPIRATION

        return jinja2_engine.render('team_details.html',
                                    handler.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()
        if year in team_districts:
            district_key = team_districts[year]
            district_abbrev = district_key[4:]
            district_type = DistrictType.abbrevs[district_abbrev]
            district_name = DistrictType.type_names[district_type]

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

        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)
Example #53
0
    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

        district_name = None
        district_abbrev = None
        team_district_points = None
        team_districts = team_districts_future.get_result()
        for district in team_districts:
            if district and district.year == year:
                district_abbrev = district.abbreviation
                district_name = district.display_name
                if district.rankings:
                    team_district_points = next(
                        iter(filter(lambda r: r['team_key'] == team.key_name,
                               district.rankings)), None)
                break

        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

            video_ids = []
            playlist = ""
            for level in Match.COMP_LEVELS:
                matches = matches_organized[level]
                for match in matches:
                    video_ids += [video.split("?")[0] for video in match.youtube_videos]
            if video_ids:
                playlist_title = u"{} (Team {})".format(event.name, team.team_number)
                playlist = u"https://www.youtube.com/watch_videos?video_ids={}&title={}"
                playlist = playlist.format(u",".join(video_ids), playlist_title)

            district_points = None
            if team_district_points:
                district_points = next(
                    iter(
                        filter(lambda e: e['event_key'] == event.key_name,
                               team_district_points['event_points'])), None)

            participation.append({
                "event": event,
                "matches": matches_organized,
                "wlt": display_wlt,
                "qual_avg": qual_avg,
                "elim_avg": elim_avg,
                "rank": team_rank,
                "awards": event_awards,
                "playlist": playlist,
                "district_points": district_points,
            })

        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)

        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,
            "team_district_points": team_district_points,
        })

        if short_cache:
            handler._cache_expiration = handler.SHORT_CACHE_EXPIRATION

        return jinja2_engine.render("team_details.html", handler.template_values)
    def _render(self, team_number):
        self.response.headers['content-type'] = 'text/plain; charset="utf-8"'

        team_event_or_error = validate_team(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()

        team_key = 'frc{}'.format(team_number)
        matches_future = TeamEventMatchesQuery(team_key, event.key.id()).fetch_async()
        matches = MatchHelper.organizeMatches(matches_future.get_result())

        # Compute alliances
        alliance_number = None
        if event.alliance_selections:
            for i, alliance in enumerate(event.alliance_selections):
                if team_key in alliance['picks']:
                    alliance_number = i + 1
                    break
            else:
                alliance_number = 0  # Team didn't make it to elims

        level_map = {
            'qf': 'quarters',
            'sf': 'semis',
            'f': 'the finals',
        }
        for comp_level in ['f', 'sf', 'qf']:  # playoffs
            level_str = level_map[comp_level]
            if matches[comp_level]:
                wins = 0
                losses = 0
                for match in matches[comp_level]:
                    if match.has_been_played:
                        if team_key in match.alliances[match.winning_alliance]['teams']:
                            wins += 1
                        else:
                            losses += 1
                if wins == 2:
                    if comp_level == 'f':
                        return "[{}]: Team {} won the event on alliance #{}.".format(event_code_upper, team_number, alliance_number)
                    else:
                        return "[{}]: Team {} won {} on alliance #{}.".format(event_code_upper, team_number, level_str, alliance_number)
                elif losses == 2:
                    return "[{}]: Team {} got knocked out in {} on alliance #{}.".format(event_code_upper, team_number, level_str, alliance_number)
                else:
                    return "[{}]: Team {} is currently {}-{} in {} on alliance #{}.".format(event_code_upper, team_number, wins, losses, level_str, alliance_number)

        # Still in quals or team did not make it to elims
        # Compute qual W-L-T
        wins = 0
        losses = 0
        ties = 0
        unplayed_qual = 0
        for match in matches['qm']:
            if match.has_been_played:
                if match.winning_alliance == '':
                    ties += 1
                elif team_key in match.alliances[match.winning_alliance]['teams']:
                    wins += 1
                else:
                    losses += 1
            else:
                unplayed_qual += 1

        # Compute rank & num_teams
        rank = None
        if event.rankings:
            num_teams = len(event.rankings) - 1
            for i, row in enumerate(event.rankings):
                if row[1] == team_number:
                    rank = i

        if unplayed_qual > 0:
            if rank is not None:
                return "[{}]: Team {} is currently rank {}/{} with a record of {}-{}-{}.".format(event_code_upper, team_number, rank, num_teams, wins, losses, ties)
            else:
                return "[{}]: Team {} currently has a record of {}-{}-{} at [{}].".format(event_code_upper, team_number, wins, losses, ties)
        else:
            if alliance_number is None:
                return "[{}]: Team {} ended qualification matches at rank {}/{} with a record of {}-{}-{}.".format(event_code_upper, team_number, rank, num_teams, wins, losses, ties)
            elif alliance_number == 0:
                return "[{}]: Team {} ended qualification matches at rank {}/{} with a record of {}-{}-{} and was not picked for playoff matches.".format(event_code_upper, team_number, rank, num_teams, wins, losses, ties)
            else:
                return "[{}]: Team {} will be competing in the playoff matches on alliance #{}.".format(event_code_upper, team_number, alliance_number)
    def _render(self, event_key):
        event = Event.get_by_id(event_key)

        if not event or event.year < 2016 or not event.details 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)