Example #1
0
    def parse(cls, zebra_motion_json):
        """
        Parse JSON that contains Zebra MotionWorks data
        Format is as follows:
        [{
            key: '2020casj_qm1',
            times: [<elapsed time (float seconds)>, 0.0, 0.5, 1.0, 1.5, ...],
            alliances: {
                red: [
                    {
                        team_key: "frc254",
                        xs: [<float feet or null>, null, 1.2, 1.3, 1.4, ...],
                        ys: [<float feet or null>, null, 0.1, 0.1, 0.1, ...],
                    },
                    {
                        team_key: "frc971",
                        xs: [1.1, 1.2, 1.3, 1.4, ...],
                        ys: [0.1, 0.1, 0.1, 0.1, ...],
                    },
                    ...
                ],
                blue: [...],
            }
        }]
        """
        try:
            data = json.loads(zebra_motion_json)
        except Exception:
            raise ParserInputException("Invalid JSON. Please check input.")

        parsed_data = []
        for zebra_data in data:
            # Check 'times' is an array of floats
            times = zebra_data.get('times', [])
            if not isinstance(times, list):
                raise ParserInputException("Zebra MotionWorks data must have a array of 'times'.")
            data_length = len(times)
            if data_length == 0:
                raise ParserInputException("Length of 'times' must be non-zero.")
            for time in times:
                if not isinstance(time, float):
                    raise ParserInputException("'times' must be an array of floats.")

            # Check 'alliances'
            alliances = zebra_data.get('alliances')
            if not isinstance(alliances, dict):
                raise ParserInputException("Zebra MotionWorks data must have a dictionary of 'alliances'.")

            red = cls._parse_alliance(alliances.get('red', []), data_length)
            blue = cls._parse_alliance(alliances.get('blue', []), data_length)

            parsed_data.append({
                'key': zebra_data.get('key'),
                'times': times,
                'alliances': {
                    'red': red,
                    'blue': blue,
                },
            })
        return parsed_data
Example #2
0
    def parse(self, alliances_json):
        """
        Parse JSON that contains team_keys
        Format is as follows:
        [[captain1, pick1-1, pick1-2(, ...)],
        ['frc254', 'frc971', 'frc604'],
        ...
        [captain8, pick8-1, pick8-2(, ...)]]
        """
        try:
            alliances = json.loads(alliances_json)
        except:
            raise ParserInputException("Invalid JSON. Please check input.")

        alliance_selections = []
        for alliance in alliances:
            selection = {'picks': [], 'declines': []}
            for team_key in alliance:
                if not re.match(r'frc\d+', str(team_key)):
                    raise ParserInputException(
                        "Bad team_key: '{}'. Must follow format: 'frcXXX'".
                        format(team_key))
                else:
                    selection['picks'].append(team_key)
            alliance_selections.append(selection)
        return alliance_selections
Example #3
0
 def _parse_coord(cls, coords, data_length):
     if len(coords) != data_length:
         raise ParserInputException("Length of coordinate data must be consistent.")
     for coord in coords:
         if not (isinstance(coord, float) or coord is None):
             raise ParserInputException("Coordinate data must be an array of floats or nulls.")
     return coords
Example #4
0
    def parse(self, awards_json, event_key):
        """
        Parse JSON that contains a list of awards where each award is a dict of:
        name_str: String of award name. ex: "Tournament Winner" or "Dean's List Finalist"
        team_key: String in the format "frcXXX" for the team that won the award. Can be null.
        awardee: String corresponding to the name of an individual that won the award. Can be null.
        """
        try:
            awards = json.loads(awards_json)
        except:
            raise ParserInputException("Invalid JSON. Please check input.")

        awards_by_key = {}
        for award in awards:
            if type(award) is not dict:
                raise ParserInputException("Awards must be dicts.")

            name_str = award.get('name_str', None)
            team_key = award.get('team_key', None)
            awardee = award.get('awardee', None)

            if name_str is None:
                raise ParserInputException("Award must have a 'name_str'")

            if team_key and not re.match(r'frc\d+', str(team_key)):
                raise ParserInputException("Bad team_key: '{}'. Must follow format 'frcXXX' or be null.".format(team_key))

            award_type_enum = AwardHelper.parse_award_type(name_str)
            if award_type_enum is None:
                raise ParserInputException("Cannot determine award type from: '{}'. Please contact a www.thebluealliance.com admin.".format(name_str))

            recipient_json = json.dumps({
                'team_number': int(team_key[3:]) if team_key else None,
                'awardee': awardee,
            })

            award_key_name = Award.render_key_name(event_key, award_type_enum)
            if award_key_name in awards_by_key:
                if team_key is not None:
                    awards_by_key[award_key_name]['team_key_list'].append(team_key)
                awards_by_key[award_key_name]['recipient_json_list'].append(recipient_json)
            else:
                awards_by_key[award_key_name] = {
                    'name_str': name_str,
                    'award_type_enum': award_type_enum,
                    'team_key_list': [team_key] if team_key else [],
                    'recipient_json_list': [recipient_json],
                }

        return awards_by_key.values()
Example #5
0
    def parse(self, team_list_json):
        """
        Parse JSON that contains team_keys in the format "frcXXX"
        Format is as follows:
        [team1, team1, team3, ...]
        """
        try:
            team_keys = json.loads(team_list_json)
        except:
            raise ParserInputException("Invalid JSON. Please check input.")

        for team_key in team_keys:
            if not re.match(r'frc\d+', str(team_key)):
                raise ParserInputException("Bad team_key: '{}'. Must follow format 'frcXXX' or be null.".format(team_key))

        return team_keys
Example #6
0
    def _parse_alliance(cls, alliance, data_length):
        if len(alliance) != 3:
            # Not necessarily true in the future, but encofing this for now
            raise ParserInputException("Must have 3 teams per alliance.")

        parsed_alliance = []
        for team in alliance:
            parsed_alliance.append(cls._parse_team(team, data_length))
        return parsed_alliance
Example #7
0
    def _parse_team(cls, team, data_length):
        # Check team_key format
        team_key = team.get('team_key')
        if not re.match(r'frc\d+', str(team_key)):
            raise ParserInputException("Bad 'team_key': '{}'. Must follow format 'frcXXX'.".format(team_key))

        # Check xs, ys format
        xs = cls._parse_coord(team.get('xs', []), data_length)
        ys = cls._parse_coord(team.get('ys', []), data_length)
        for x, y in zip(xs, ys):
            if (x is None and y is not None) or (x is not None and y is None):
                raise ParserInputException("Coordinate data at a given time index for a given team may not have a mix of null and non-null values.")

        parsed_team = {
            'team_key': team_key,
            'xs': xs,
            'ys': ys,
        }
        return parsed_team
    def parse(self, rankings_json):
        """
        Parse JSON that contains a dict of:
        breakdowns: List of ranking breakdowns, such as "QS", "Auton", "Teleop", etc. Breakdowns will be shown in the order given.
        rankings: List of ranking dicts

        Ranking dict format:
        team_key: String corresponding to a particular team in the format "frcXXX"
        rank: Integer rank of the particular team
        wins: Integer of the number of non-surrogate wins
        losses: Integer of the number of non-surrogate losses
        ties: Integer of the number of non-surrogate ties
        played: Integer of the number of non-surrogate matches played
        dqs: Integer of the number of non-surrogate DQed matches
        breakdown: Dict where the key is a breakdown and the value is its value
        """
        try:
            data = json.loads(rankings_json)
        except:
            raise ParserInputException("Invalid JSON. Please check input.")

        if type(data) is not dict:
            raise ParserInputException("Data must be a dict.")
        if 'breakdowns' not in data or type(data['breakdowns']) is not list:
            raise ParserInputException("Data must have a list 'breakdowns'")
        if 'rankings' not in data or type(data['rankings']) is not list:
            raise ParserInputException("Data must have a list 'rankings'")

        rankings = [['Rank', 'Team'] + data['breakdowns'] +
                    ['Record (W-L-T)', 'DQ', 'Played']]
        for ranking in data['rankings']:
            if type(ranking) is not dict:
                raise ParserInputException("Ranking must be a dict.")
            if 'team_key' not in ranking or not re.match(
                    r'frc\d+', str(ranking['team_key'])):
                raise ParserInputException(
                    "Ranking must have a 'team_key' that follows the format 'frcXXX'"
                )
            for attr in ['rank', 'wins', 'losses', 'ties', 'played', 'dqs']:
                if attr not in ranking or type(ranking[attr]) is not int:
                    raise ParserInputException(
                        "Ranking must have a integer '{}'".format(attr))

            row = [ranking['rank'], ranking['team_key'][3:]]
            for b in data['breakdowns']:
                row.append(ranking.get(b, '--'))
            row.extend([
                '{}-{}-{}'.format(ranking['wins'], ranking['losses'],
                                  ranking['ties']), ranking['dqs'],
                ranking['played']
            ])
            rankings.append(row)

        return rankings
Example #9
0
    def _process_request(self, request, event_key):
        try:
            event_info = json.loads(request.body)
        except Exception:
            self._errors = json.dumps({"Error": "Invalid json. Check input."})
            self.abort(400)

        if not isinstance(event_info, dict) or not event_info:
            self._errors = json.dumps({"Error": "Invalid json. Check input."})
            self.abort(400)

        event = Event.get_by_id(event_key)
        if not event:
            self._errors = json.dumps(
                {"Error": "Event {} not found".format(event_key)})
            self.abort(404)

        do_team_remap = False
        for field, value in event_info.iteritems():
            if field not in self.ALLOWED_EVENT_PARAMS:
                continue

            if field == "webcasts":
                # Do special processing here because webcasts are janky
                if not isinstance(value, list):
                    self._errors = json.dumps(
                        {"Error": "Invalid json. Check input"})
                    self.abort(400)
                    return
                webcast_list = []
                for webcast in value:
                    if not isinstance(webcast, dict):
                        self._errors = json.dumps(
                            {"Error": "Invalid json. Check input"})
                        self.abort(400)
                        return
                    if 'url' in webcast:
                        webcast_list.append(
                            WebcastParser.webcast_dict_from_url(
                                webcast['url']))
                    elif 'type' in webcast and 'channel' in webcast:
                        webcast_list.append(webcast)

                webcast_list = [w for w in webcast_list if w is not None]
                EventWebcastAdder.add_webcast(
                    event,
                    webcast_list,
                    False,  # Don't createOrUpdate yet
                )
            elif field == "remap_teams":
                # Validate remap_teams
                if not isinstance(value, dict):
                    raise ParserInputException(
                        "Invalid reamap_teams. Check input")
                for temp_team, remapped_team in value.items():
                    temp_match = re.match(r'frc\d+', str(temp_team))
                    remapped_match = re.match(r'frc\d+[B-Z]?',
                                              str(remapped_team))
                    if not temp_match or (
                            temp_match and
                        (temp_match.group(0) != str(temp_team))):
                        raise ParserInputException(
                            "Bad team: '{}'. Must follow format 'frcXXX'.".
                            format(temp_team))
                    if not remapped_match or (
                            remapped_match and
                        (remapped_match.group(0) != str(remapped_team))):
                        raise ParserInputException(
                            "Bad team: '{}'. Must follow format 'frcXXX' or 'frcXXX[B-Z]'."
                            .format(remapped_team))
                do_team_remap = True
                setattr(event, field, value)
            else:
                try:
                    if field == "first_event_code":
                        event.official = value is not None
                        field = "first_code"  # Internal property is different
                    setattr(event, field, value)
                except Exception, e:
                    self._errors({
                        "Error": "Unable to set event field",
                        "Message": str(e)
                    })
                    self.abort(400)
    def parse(self, matches_json, year):
        """
        Parse JSON that contains a list of matches for a given year where each match is a dict of:
        comp_level: String in the set {"qm", "ef", "qf", "sf", "f"}
        set_number: Integer identifying the elim set number. Ignored for qual matches. ex: the 4 in qf4m2
        match_number: Integer identifying the match number within a set. ex: the 2 in qf4m2
        alliances: Dict of {'red': {'teams': ['frcXXX'...], 'score': S, 'surrogates': ['frcXXX'...], 'dqs': ['frcXXX'...]}, 'blue': {...}}. Where scores (S) are integers. Null scores indicate that a match has not yet been played. surrogates and dqs are optional.
        score_breakdown: Dict of {'red': {K1: V1, K2: V2, ...}, 'blue': {...}}. Where Kn are keys and Vn are values for those keys.
        time_string: String in the format "(H)H:MM AM/PM" for when the match will be played in the event's local timezone. ex: "9:15 AM"
        time: UTC time of the match as a string in ISO 8601 format (YYYY-MM-DDTHH:MM:SS).
        """
        try:
            matches = json.loads(matches_json)
        except:
            raise ParserInputException("Invalid JSON. Please check input.")

        parsed_matches = []
        for match in matches:
            if type(match) is not dict:
                raise ParserInputException("Matches must be dicts.")

            comp_level = match.get('comp_level', None)
            set_number = match.get('set_number', None)
            match_number = match.get('match_number', None)
            alliances = match.get('alliances', None)
            score_breakdown = match.get('score_breakdown', None)
            time_string = match.get('time_string', None)
            time_utc = match.get('time_utc', None)

            if comp_level is None:
                raise ParserInputException("Match must have a 'comp_level'")
            if comp_level not in Match.COMP_LEVELS:
                raise ParserInputException(
                    "'comp_level' must be one of: {}".format(
                        Match.COMP_LEVELS))

            if comp_level == 'qm':
                set_number = 1
            elif set_number is None or type(set_number) is not int:
                raise ParserInputException(
                    "Match must have an integer 'set_number'")

            if match_number is None or type(match_number) is not int:
                raise ParserInputException(
                    "Match must have an integer 'match_number'")

            if type(alliances) is not dict:
                raise ParserInputException("'alliances' must be a dict")
            else:
                for color, details in alliances.items():
                    if color not in {'red', 'blue'}:
                        raise ParserInputException(
                            "Alliance color '{}' not recognized".format(color))
                    if 'teams' not in details:
                        raise ParserInputException(
                            "alliances[color] must have key 'teams'")
                    if 'score' not in details:
                        raise ParserInputException(
                            "alliances[color] must have key 'score'")
                    for team_key in details['teams']:
                        if not re.match(r'frc\d+', str(team_key)):
                            raise ParserInputException(
                                "Bad team: '{}'. Must follow format 'frcXXX'.".
                                format(team_key))
                    if details['score'] is not None and type(
                            details['score']) is not int:
                        raise ParserInputException(
                            "alliances[color]['score'] must be an integer or null"
                        )

                    for team_key in details.get('surrogates', []):
                        if not re.match(r'frc\d+', str(team_key)):
                            raise ParserInputException(
                                "Bad surrogate team: '{}'. Must follow format 'frcXXX'."
                                .format(team_key))
                        if team_key not in details['teams']:
                            raise ParserInputException(
                                "Bad surrogate team: '{}'. Must be a team in the match.'."
                                .format(team_key))
                    for team_key in details.get('dqs', []):
                        if not re.match(r'frc\d+', str(team_key)):
                            raise ParserInputException(
                                "Bad dq team: '{}'. Must follow format 'frcXXX'."
                                .format(team_key))
                        if team_key not in details['teams']:
                            raise ParserInputException(
                                "Bad dq team: '{}'. Must be a team in the match.'."
                                .format(team_key))

            if score_breakdown is not None:
                if type(score_breakdown) is not dict:
                    raise ParserInputException(
                        "'score_breakdown' must be a dict")
                else:
                    for color, breakdown in score_breakdown.items():
                        if color not in {'red', 'blue'}:
                            raise ParserInputException(
                                "Alliance color '{}' not recognized".format(
                                    color))
                        for k in breakdown.keys():
                            is_valid = MatchHelper.is_valid_score_breakdown_key(
                                k, year)
                            if is_valid != True:
                                raise ParserInputException(
                                    "Valid score breakdowns for {} are: {}".
                                    format(year, is_valid))

            datetime_utc = None
            if time_utc is not None:
                try:
                    import iso8601
                    datetime_utc = iso8601.parse_date(time_utc)
                    # remove timezone info because DatetimeProperty can't handle timezones
                    datetime_utc = datetime_utc.replace(tzinfo=None)
                except Exception:
                    raise ParserInputException(
                        "Could not parse 'time_utc'. Check that it is in ISO 8601 format."
                    )

            # validation passed. build new dicts to sanitize
            parsed_alliances = {
                'red': {
                    'teams': alliances['red']['teams'],
                    'score': alliances['red']['score'],
                    'surrogates': alliances['red'].get('surrogates', []),
                    'dqs': alliances['red'].get('dqs', []),
                },
                'blue': {
                    'teams': alliances['blue']['teams'],
                    'score': alliances['blue']['score'],
                    'surrogates': alliances['blue'].get('surrogates', []),
                    'dqs': alliances['blue'].get('dqs', []),
                },
            }
            parsed_match = {
                'comp_level':
                comp_level,
                'set_number':
                set_number,
                'match_number':
                match_number,
                'alliances_json':
                json.dumps(parsed_alliances),
                'score_breakdown_json':
                json.dumps(score_breakdown)
                if score_breakdown is not None else None,
                'time_string':
                time_string,
                'time':
                datetime_utc,
                'team_key_names':
                parsed_alliances['red']['teams'] +
                parsed_alliances['blue']['teams'],
            }

            parsed_matches.append(parsed_match)
        return parsed_matches
    def parse(self, rankings_json):
        """
        Parse JSON that contains a dict of:
        breakdowns: List of ranking breakdowns, such as "Wins", "Qual Avg", "QS", "Auton", "Teleop", etc. Breakdowns will be shown in the order given.
        rankings: List of ranking dicts

        Ranking dict format:
        team_key: String corresponding to a particular team in the format "frcXXX"
        rank: Integer rank of the particular team
        played: Integer of the number of non-surrogate matches played
        dqs: Integer of the number of non-surrogate DQed matches
        breakdown: Dict where the key is a breakdown and the value is its value
        """
        try:
            data = json.loads(rankings_json)
        except:
            raise ParserInputException("Invalid JSON. Please check input.")

        if type(data) is not dict:
            raise ParserInputException("Data must be a dict.")
        if 'breakdowns' not in data or type(data['breakdowns']) is not list:
            raise ParserInputException("Data must have a list 'breakdowns'")
        if 'rankings' not in data or type(data['rankings']) is not list:
            raise ParserInputException("Data must have a list 'rankings'")

        # Account for wins/losses/ties to be present and show them as Record (W-L-T)
        post_breakdowns = ['DQ', 'Played']
        has_record = False
        if "wins" in data["breakdowns"] and "losses" in data[
                "breakdowns"] and "ties" in data["breakdowns"]:
            post_breakdowns.insert(0, 'Record (W-L-T)')
            has_record = True

            # remove wlt from breakdown list so they're not added twice
            data["breakdowns"].remove("wins")
            data["breakdowns"].remove("losses")
            data["breakdowns"].remove("ties")

        rankings = [['Rank', 'Team'] + data['breakdowns'] + post_breakdowns]
        for ranking in data['rankings']:
            if type(ranking) is not dict:
                raise ParserInputException("Ranking must be a dict.")
            if 'team_key' not in ranking or not re.match(
                    r'frc\d+', str(ranking['team_key'])):
                raise ParserInputException(
                    "Ranking must have a 'team_key' that follows the format 'frcXXX'"
                )
            for attr in ['rank', 'played', 'dqs']:
                if attr not in ranking or type(ranking[attr]) is not int:
                    raise ParserInputException(
                        "Ranking must have a integer '{}'".format(attr))

            row = [ranking['rank'], ranking['team_key'][3:]]
            for b in data['breakdowns']:
                row.append(ranking.get(b, '--'))

            # Special case for when wlt passed
            if has_record:
                row.extend([
                    '{}-{}-{}'.format(ranking['wins'], ranking['losses'],
                                      ranking['ties']), ranking['dqs'],
                    ranking['played']
                ])
            else:
                row.extend([ranking['dqs'], ranking['played']])
            rankings.append(row)

        return rankings