def parse(self, response):
        matches = response['MatchScores']

        event_key = '{}{}'.format(self.year, self.event_short)
        event = Event.get_by_id(event_key)

        match_details_by_key = {}

        for match in matches:
            comp_level = PlayoffType.get_comp_level(event.playoff_type,
                                                    match['matchLevel'],
                                                    match['matchNumber'])
            set_number, match_number = PlayoffType.get_set_match_number(
                event.playoff_type, comp_level, match['matchNumber'])
            breakdown = {
                'red': {},
                'blue': {},
            }
            if 'coopertition' in match:
                breakdown['coopertition'] = match['coopertition']
            if 'coopertitionPoints' in match:
                breakdown['coopertition_points'] = match['coopertitionPoints']
            for alliance in match['Alliances']:
                color = alliance['alliance'].lower()
                for key, value in alliance.items():
                    if key != 'alliance':
                        breakdown[color][key] = value

            match_details_by_key[Match.renderKeyName(
                '{}{}'.format(self.year, self.event_short), comp_level,
                set_number, match_number)] = breakdown

        return match_details_by_key
    def parse(self, response):
        matches = response['MatchScores']

        event_key = '{}{}'.format(self.year, self.event_short)
        event = Event.get_by_id(event_key)

        match_details_by_key = {}

        for match in matches:
            comp_level = PlayoffType.get_comp_level(event.playoff_type, match['matchLevel'], match['matchNumber'])
            set_number, match_number = PlayoffType.get_set_match_number(event.playoff_type, comp_level, match['matchNumber'])
            breakdown = {
                'red': {},
                'blue': {},
            }
            if 'coopertition' in match:
                breakdown['coopertition'] = match['coopertition']
            if 'coopertitionPoints' in match:
                breakdown['coopertition_points'] = match['coopertitionPoints']
            for alliance in match['Alliances']:
                color = alliance['alliance'].lower()
                for key, value in alliance.items():
                    if key != 'alliance':
                        breakdown[color][key] = value

            match_details_by_key[Match.renderKeyName(
                '{}{}'.format(self.year, self.event_short),
                comp_level,
                set_number,
                match_number)] = breakdown

        return match_details_by_key
Exemplo n.º 3
0
    def test_BRACKET_8_TEAM(self):
        playoff_type = PlayoffType.BRACKET_8_TEAM

        # Qual
        for i in xrange(50):
            self.assertEqual(
                PlayoffType.get_comp_level(playoff_type, 'Qualification', i + 1),
                'qm'
            )
            self.assertEqual(
                PlayoffType.get_set_match_number(playoff_type, 'qm', i + 1),
                (1, i + 1)
            )

        # Playoff
        expected = [
            'qf', 'qf', 'qf',
            'qf', 'qf', 'qf',
            'qf', 'qf', 'qf',
            'qf', 'qf', 'qf',
            'sf', 'sf', 'sf',
            'sf', 'sf', 'sf',
            'f', 'f', 'f',
        ]
        for i in xrange(21):
            self.assertEqual(
                PlayoffType.get_comp_level(playoff_type, 'Playoff', i + 1),
                expected[i]
            )

        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'qf', 1),
            (1, 1)
        )
        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'qf', 12),
            (4, 3)
        )
        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'sf', 13),
            (1, 1)
        )
        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'sf', 18),
            (2, 3)
        )
        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'f', 19),
            (1, 1)
        )
        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'f', 21),
            (1, 3)
        )
    def parse(self, response):
        matches = response['MatchScores']

        event_key = '{}{}'.format(self.year, self.event_short)
        event = Event.get_by_id(event_key)

        match_details_by_key = {}

        for match in matches:
            comp_level = PlayoffType.get_comp_level(event.playoff_type, match['matchLevel'], match['matchNumber'])
            set_number, match_number = PlayoffType.get_set_match_number(event.playoff_type, comp_level, match['matchNumber'])
            breakdown = {
                'red': {},
                'blue': {},
            }
            if 'coopertition' in match:
                breakdown['coopertition'] = match['coopertition']
            if 'coopertitionPoints' in match:
                breakdown['coopertition_points'] = match['coopertitionPoints']

            game_data = None
            if self.year == 2018:
                # Switches should be the same, but parse individually in case FIRST change things
                right_switch_red = match['switchRightNearColor'] == 'Red'
                scale_red = match['scaleNearColor'] == 'Red'
                left_switch_red = match['switchLeftNearColor'] == 'Red'
                game_data = '{}{}{}'.format(
                    'L' if right_switch_red else 'R',
                    'L' if scale_red else 'R',
                    'L' if left_switch_red else 'R',
                )

            for alliance in match.get('alliances', match.get('Alliances', [])):
                color = alliance['alliance'].lower()
                for key, value in alliance.items():
                    if key != 'alliance':
                        breakdown[color][key] = value

                if game_data is not None:
                    breakdown[color]['tba_gameData'] = game_data

            match_details_by_key[Match.renderKeyName(
                '{}{}'.format(self.year, self.event_short),
                comp_level,
                set_number,
                match_number)] = breakdown

        return match_details_by_key
Exemplo n.º 5
0
 def organizeDoubleElimMatches(cls, organized_matches):
     matches = defaultdict(lambda: defaultdict(list))
     for level in Match.COMP_LEVELS:
         level_matches = organized_matches[level]
         if level == 'qm':
             matches['qm'] = level_matches
             continue
         for match in level_matches:
             bracket = PlayoffType.get_double_elim_bracket(level, match.set_number)
             matches[bracket][level].append(match)
     return matches
Exemplo n.º 6
0
 def organizeDoubleElimMatches(cls, organized_matches):
     matches = defaultdict(lambda: defaultdict(list))
     for level in Match.COMP_LEVELS:
         level_matches = organized_matches[level]
         if level == 'qm':
             matches['qm'] = level_matches
             continue
         for match in level_matches:
             bracket = PlayoffType.get_double_elim_bracket(level, match.set_number)
             matches[bracket][level].append(match)
     return matches
Exemplo n.º 7
0
    def test_ROUND_ROBIN_6_TEAM(self):
        playoff_type = PlayoffType.ROUND_ROBIN_6_TEAM

        # Qual
        for i in xrange(50):
            self.assertEqual(
                PlayoffType.get_comp_level(playoff_type, 'Qualification', i + 1),
                'qm'
            )
            self.assertEqual(
                PlayoffType.get_set_match_number(playoff_type, 'qm', i + 1),
                (1, i + 1)
            )

        # Playoff
        expected = [
            'sf', 'sf', 'sf',
            'sf', 'sf', 'sf',
            'sf', 'sf', 'sf',
            'sf', 'sf', 'sf',
            'sf', 'sf', 'sf',
            'f', 'f', 'f',
        ]
        for i in xrange(18):
            self.assertEqual(
                PlayoffType.get_comp_level(playoff_type, 'Playoff', i + 1),
                expected[i]
            )

        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'sf', 1),
            (1, 1)
        )
        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'sf', 15),
            (1, 15)
        )
        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'f', 16),
            (1, 1)
        )
        self.assertEqual(
            PlayoffType.get_set_match_number(playoff_type, 'f', 18),
            (1, 3)
        )
    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
        for match in matches:
            if 'tournamentLevel' in match:  # 2016+
                level = match['tournamentLevel']
            else:  # 2015
                level = match['level']
            comp_level = PlayoffType.get_comp_level(event.playoff_type, level,
                                                    match['matchNumber'])
            set_number, match_number = PlayoffType.get_set_match_number(
                event.playoff_type, comp_level, match['matchNumber'])

            red_teams = []
            blue_teams = []
            red_surrogates = []
            blue_surrogates = []
            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 parse(self, response):
        matches = response['MatchScores']

        event_key = '{}{}'.format(self.year, self.event_short)
        event = Event.get_by_id(event_key)

        match_details_by_key = {}

        for match in matches:
            comp_level = PlayoffType.get_comp_level(event.playoff_type,
                                                    match['matchLevel'],
                                                    match['matchNumber'])
            set_number, match_number = PlayoffType.get_set_match_number(
                event.playoff_type, comp_level, match['matchNumber'])
            breakdown = {
                'red': {},
                'blue': {},
            }
            if 'coopertition' in match:
                breakdown['coopertition'] = match['coopertition']
            if 'coopertitionPoints' in match:
                breakdown['coopertition_points'] = match['coopertitionPoints']

            game_data = None
            if self.year == 2018:
                # Switches should be the same, but parse individually in case FIRST change things
                right_switch_red = match['switchRightNearColor'] == 'Red'
                scale_red = match['scaleNearColor'] == 'Red'
                left_switch_red = match['switchLeftNearColor'] == 'Red'
                game_data = '{}{}{}'.format(
                    'L' if right_switch_red else 'R',
                    'L' if scale_red else 'R',
                    'L' if left_switch_red else 'R',
                )

            for alliance in match.get('alliances', match.get('Alliances', [])):
                color = alliance['alliance'].lower()
                for key, value in alliance.items():
                    if key != 'alliance':
                        breakdown[color][key] = value

                if game_data is not None:
                    breakdown[color]['tba_gameData'] = game_data

                if self.year == 2019:
                    # Derive incorrect completedRocketFar and completedRocketNear returns from FIRST API
                    for side1 in ['Near', 'Far']:
                        completedRocket = True
                        for side2 in ['Left', 'Right']:
                            for level in ['low', 'mid', 'top']:
                                if breakdown[color]['{}{}Rocket{}'.format(
                                        level, side2,
                                        side1)] != 'PanelAndCargo':
                                    completedRocket = False
                                    break
                            if not completedRocket:
                                break
                        breakdown[color]['completedRocket{}'.format(
                            side1)] = completedRocket

            match_details_by_key[Match.renderKeyName(
                '{}{}'.format(self.year, self.event_short), comp_level,
                set_number, match_number)] = breakdown

        return match_details_by_key
    def parse(self, response):
        import pytz

        matches = response['Schedule']

        event_key = '{}{}'.format(self.year, self.event_short)
        event = Event.get_by_id(event_key)
        if event.timezone_id:
            event_tz = pytz.timezone(event.timezone_id)
        else:
            logging.warning("Event {} has no timezone! Match times may be wrong.".format(event_key))
            event_tz = None

        parsed_matches = []
        remapped_matches = {}  # If a key changes due to a tiebreaker
        for match in matches:
            if 'tournamentLevel' in match:  # 2016+
                level = match['tournamentLevel']
            else:  # 2015
                level = match['level']
            comp_level = PlayoffType.get_comp_level(event.playoff_type, level, match['matchNumber'])
            set_number, match_number = PlayoffType.get_set_match_number(event.playoff_type, comp_level, match['matchNumber'])

            red_teams = []
            blue_teams = []
            red_surrogates = []
            blue_surrogates = []
            red_dqs = []
            blue_dqs = []
            team_key_names = []
            null_team = False
            sorted_teams = sorted(match.get('teams', match.get('Teams', [])), key=lambda team: team['station'])  # Sort by station to ensure correct ordering. Kind of hacky.
            for team in sorted_teams:
                if team['teamNumber'] is None:
                    null_team = True
                team_key = 'frc{}'.format(team['teamNumber'])
                team_key_names.append(team_key)
                if 'Red' in team['station']:
                    red_teams.append(team_key)
                    if team['surrogate']:
                        red_surrogates.append(team_key)
                    if team['dq']:
                        red_dqs.append(team_key)
                elif 'Blue' in team['station']:
                    blue_teams.append(team_key)
                    if team['surrogate']:
                        blue_surrogates.append(team_key)
                    if team['dq']:
                        blue_dqs.append(team_key)

            if null_team and match['scoreRedFinal'] is None and match['scoreBlueFinal'] is None:
                continue

            alliances = {
                'red': {
                    'teams': red_teams,
                    'surrogates': red_surrogates,
                    'dqs': red_dqs,
                    'score': match['scoreRedFinal']
                },
                'blue': {
                    'teams': blue_teams,
                    'surrogates': blue_surrogates,
                    'dqs': blue_dqs,
                    'score': match['scoreBlueFinal']
                },
            }

            if not match['startTime']:  # no startTime means it's an unneeded rubber match
                continue

            time = datetime.datetime.strptime(match['startTime'].split('.')[0], TIME_PATTERN)
            if event_tz is not None:
                time = time - event_tz.utcoffset(time)

            actual_time_raw = match['actualStartTime'] if 'actualStartTime' in match else None
            actual_time = None
            if actual_time_raw is not None:
                actual_time = datetime.datetime.strptime(actual_time_raw.split('.')[0], TIME_PATTERN)
                if event_tz is not None:
                    actual_time = actual_time - event_tz.utcoffset(actual_time)

            post_result_time_raw = match.get('postResultTime')
            post_result_time = None
            if post_result_time_raw is not None:
                post_result_time = datetime.datetime.strptime(post_result_time_raw.split('.')[0], TIME_PATTERN)
                if event_tz is not None:
                    post_result_time = post_result_time - event_tz.utcoffset(post_result_time)

            key_name = Match.renderKeyName(
                event_key,
                comp_level,
                set_number,
                match_number)

            # Check for tiebreaker matches
            existing_match = Match.get_by_id(key_name)
            # Follow chain of existing matches
            while existing_match is not None and existing_match.tiebreak_match_key is not None:
                logging.info("Following Match {} to {}".format(existing_match.key.id(), existing_match.tiebreak_match_key.id()))
                existing_match = existing_match.tiebreak_match_key.get()
            # Check if last existing match needs to be tiebroken
            if existing_match and existing_match.comp_level != 'qm' and \
                    existing_match.has_been_played and \
                    existing_match.winning_alliance == '' and \
                    existing_match.actual_time != actual_time and \
                    not self.is_blank_match(existing_match):
                logging.warning("Match {} is tied!".format(existing_match.key.id()))

                # TODO: Only query within set if set_number ever gets indexed
                match_count = 0
                for match_key in Match.query(Match.event==event.key, Match.comp_level==comp_level).fetch(keys_only=True):
                    _, match_key = match_key.id().split('_')
                    if match_key.startswith('{}{}'.format(comp_level, set_number)):
                        match_count += 1

                # Sanity check: Tiebreakers must be played after at least 3 matches if not finals
                if match_count < 3 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 parse(self, response):
        matches = response['MatchScores']

        event_key = '{}{}'.format(self.year, self.event_short)
        event = Event.get_by_id(event_key)

        match_details_by_key = {}

        for match in matches:
            comp_level = PlayoffType.get_comp_level(event.playoff_type, match['matchLevel'], match['matchNumber'])
            set_number, match_number = PlayoffType.get_set_match_number(event.playoff_type, comp_level, match['matchNumber'])
            breakdown = {
                'red': {},
                'blue': {},
            }
            if 'coopertition' in match:
                breakdown['coopertition'] = match['coopertition']
            if 'coopertitionPoints' in match:
                breakdown['coopertition_points'] = match['coopertitionPoints']

            game_data = None
            if self.year == 2018:
                # Switches should be the same, but parse individually in case FIRST change things
                right_switch_red = match['switchRightNearColor'] == 'Red'
                scale_red = match['scaleNearColor'] == 'Red'
                left_switch_red = match['switchLeftNearColor'] == 'Red'
                game_data = '{}{}{}'.format(
                    'L' if right_switch_red else 'R',
                    'L' if scale_red else 'R',
                    'L' if left_switch_red else 'R',
                )

            for alliance in match.get('alliances', match.get('Alliances', [])):
                color = alliance['alliance'].lower()
                for key, value in alliance.items():
                    if key != 'alliance':
                        breakdown[color][key] = value

                if game_data is not None:
                    breakdown[color]['tba_gameData'] = game_data

                if self.year == 2019:
                    # Derive incorrect completedRocketFar and completedRocketNear returns from FIRST API
                    for side1 in ['Near', 'Far']:
                        completedRocket = True
                        for side2 in ['Left', 'Right']:
                            for level in ['low', 'mid', 'top']:
                                if breakdown[color]['{}{}Rocket{}'.format(level, side2, side1)] != 'PanelAndCargo':
                                    completedRocket = False
                                    break
                            if not completedRocket:
                                break
                        breakdown[color]['completedRocket{}'.format(side1)] = completedRocket

            match_details_by_key[Match.renderKeyName(
                '{}{}'.format(self.year, self.event_short),
                comp_level,
                set_number,
                match_number)] = breakdown

        return match_details_by_key