Exemplo n.º 1
0
    def get_global_stats_by_rank(self, session, query_filter: QueryFilterBuilder, stats_query, stds_query,
                                 player_rank=None, redis=None):
        """
        Returns the global stats based on the rank of a player.

        Does modify the query_filter only setting rank.
        :param session: Session
        :param query_filter: a query filter.
        :param stats_query: A list of global stats
        :param stds_query: A list of global stats for standard deviations
        :param player_rank: The player that stats are associated with.  Uses unranked if rank is not found
        :param redis: The local cache
        :return:
        """
        rank_index = get_rank_number(player_rank)
        if redis is not None:
            stat_string = redis.get('global_stats')
            if stat_string is not None:
                stats_dict = json.loads(stat_string)
                global_stats = [stats_dict[s.field_name][rank_index]['mean'] for s in self.field_names]
                global_stds = [stats_dict[s.field_name][rank_index]['std'] for s in self.field_names]
                return global_stats, global_stds

        if player_rank is not None and not get_local_dev():
            logger.debug('Filtering by rank')
            query_filter.clean().with_rank(rank_index)
        else:
            query_filter.clean()

        return (query_filter.with_stat_query(stats_query).build_query(session).first(),
                query_filter.with_stat_query(stds_query).build_query(session).first())
    def get_global_stats_by_rank(self, session, query_filter: QueryFilterBuilder, stats_query, stds_query,
                                 player_rank=None, redis=None, ids=None, playlist=13):
        """
        Returns the global stats based on the rank of a player.

        Does modify the query_filter only setting rank.
        :param session: Session
        :param query_filter: a query filter.
        :param stats_query: A list of global stats
        :param stds_query: A list of global stats for standard deviations
        :param player_rank: The player that stats are associated with.  Uses unranked if rank is not found
        :param redis: The local cache
        :return:
        """

        if ids is None:
            # Set the correct rank index
            if player_rank is not None:
                if isinstance(player_rank, dict) or isinstance(player_rank, list):
                    rank_index = get_rank_tier(player_rank, playlist=playlist)
                else:
                    rank_index = player_rank
            else:
                rank_index = 0

            stat_list = self.get_player_stat_list()

            # Check to see if we have redis available (it usually is)
            if redis is not None:
                stat_string = redis.get('global_stats_by_rank')
                # Check to see if the key exists and if so load it
                if stat_string is not None:
                    stats_dict = json.loads(stat_string)
                    if playlist is not None:
                        playlist = str(playlist)
                    if playlist not in stats_dict:
                        raise UnsupportedPlaylist(404, 'Playlist does not exist in global stats')
                    stats_dict = stats_dict[playlist]
                    global_stats = []
                    global_stds = []
                    for stat in stat_list:
                        if stat.get_field_name() in stats_dict:
                            global_stats.append(stats_dict[stat.get_field_name()][rank_index]['mean'])
                            global_stds.append(stats_dict[stat.get_field_name()][rank_index]['std'])
                        else:
                            global_stats.append(1)
                            global_stds.append(1)
                    return global_stats, global_stds
            if is_local_dev():
                rank_index = 0
                stats = self.get_global_stats(session, with_rank=False)
                global_stats = [stats[stat.get_field_name()][rank_index]['mean'] for stat in stat_list]
                global_stds = [stats[stat.get_field_name()][rank_index]['std'] for stat in stat_list]
                return global_stats, global_stds
            raise CalculatedError(500, "Global stats unavailable or have not been calculated yet.")
        else:
            query_filter.clean().with_replay_ids(ids)
            return (query_filter.with_stat_query(stats_query).build_query(session).first(),
                    query_filter.with_stat_query(stds_query).build_query(session).first())
class PlayerStatWrapper(GlobalStatWrapper):
    def __init__(self, player_wrapper: PlayerWrapper):
        super().__init__()
        self.player_wrapper = player_wrapper

        # this Object needs to be pooled per a session so only one is used at a time
        self.player_stats_filter = QueryFilterBuilder()
        if not ignore_filtering():
            self.player_stats_filter.with_relative_start_time(
                days_ago=30 * 6).with_safe_checking().sticky()

    def get_stats(self,
                  session,
                  id_,
                  stats_query,
                  std_query,
                  rank=None,
                  redis=None,
                  raw=False,
                  replay_ids=None,
                  playlist=13,
                  win: bool = None):

        player_stats_filter = self.player_stats_filter.clean().clone()
        player_stats_filter.clean().with_stat_query(stats_query).with_players(
            [id_])
        if replay_ids is not None:
            player_stats_filter.with_replay_ids(replay_ids)
        else:
            player_stats_filter.with_playlists([playlist])
        query = player_stats_filter.build_query(session).filter(
            PlayerGame.time_in_game > 0).filter(
                PlayerGame.game != '').group_by(PlayerGame.player)
        if win is not None:
            query = query.filter(PlayerGame.win == win)
        stats = list(query.one())
        stats = [0 if s is None else s for s in stats]
        if raw:
            return [self.float_maybe(s) for s in stats]
        else:
            global_stats, global_stds = self.get_global_stats_by_rank(
                session,
                player_stats_filter,
                stats_query,
                std_query,
                player_rank=rank,
                redis=redis,
                ids=replay_ids,
                playlist=playlist)
            return self.compare_to_global(stats, global_stats, global_stds)

    def get_averaged_stats(self,
                           session,
                           id_: str,
                           rank: int = None,
                           redis=None,
                           raw: bool = False,
                           replay_ids: list = None,
                           playlist: int = 13,
                           win: bool = None):
        """
        Gets the averaged stats for the given ID, in either raw or standard deviation form.

        :param session: DBSession
        :param id_: Player ID to retrieve
        :param rank: rank in integer form (0-19) (optional)
        :param redis: optional redis instance, for caching/retrieving
        :param raw: return the values in raw form instead of compared to global
        :param replay_ids: replay ids to get stats from
        :param playlist: what playlist to filter by (only when replay_ids is None)
        :param win: filter by wins/losses (win = True)
        :return: stats
        """
        stats_query = self.get_player_stat_query()
        std_query = self.get_player_stat_std_query()
        total_games = self.player_wrapper.get_total_games(
            session, id_, replay_ids=replay_ids)
        if total_games > 0:
            stats = self.get_stats(session,
                                   id_,
                                   stats_query,
                                   std_query,
                                   rank=rank,
                                   redis=redis,
                                   raw=raw,
                                   replay_ids=replay_ids,
                                   playlist=playlist,
                                   win=win)
        else:
            stats = [0.0] * len(stats_query)
        return self.get_wrapped_stats(stats, self.player_stats)

    def get_progression_stats(self,
                              session,
                              id_,
                              time_unit: 'TimeUnit' = TimeUnit.MONTH,
                              start_date: datetime.datetime = None,
                              end_date: datetime.datetime = None,
                              playlist: int = 13):

        if time_unit == TimeUnit.MONTH:
            date = func.to_char(Game.match_date, 'YY-MM')
            time_format = "%y-%m"
        elif time_unit == TimeUnit.DAY:
            date = func.to_char(Game.match_date, 'YY-MM-DD')
            time_format = "%y-%m-%d"
        elif time_unit == TimeUnit.YEAR:
            date = func.to_char(Game.match_date, 'YY')
            time_format = "%y"
        elif time_unit == TimeUnit.QUARTER:
            date = func.concat(
                func.to_char(Game.match_date, 'YY'), '-',
                func.floor(
                    cast(func.extract('quarter', Game.match_date),
                         sqlalchemy.Numeric)))
            time_format = '%y-%m'
        else:
            # Mainly to help the linter know that time_unit is assigned.
            logger.error(
                f'Unknown time unit: {time_unit}. Falling back onto month.')
            date = func.to_char(Game.match_date, 'YY-MM')
            time_format = "%y-%m"

        date = date.label('date')
        mean_query = session.query(date, func.count(
            Game.hash), *self.get_player_stat_query()).join(PlayerGame).filter(
                PlayerGame.time_in_game > 0).filter(
                    PlayerGame.player == id_).group_by('date').order_by('date')
        std_query = session.query(
            date, func.count(Game.hash),
            *self.get_player_stat_std_query()).join(PlayerGame).filter(
                PlayerGame.time_in_game > 0).filter(
                    PlayerGame.player == id_).group_by('date').order_by('date')

        if start_date is not None:
            mean_query = mean_query.filter(Game.match_date > start_date)
            std_query = std_query.filter(Game.match_date > start_date)
        if end_date is not None:
            mean_query = mean_query.filter(Game.match_date < end_date)
            std_query = std_query.filter(Game.match_date < end_date)
        if playlist is not None:
            mean_query = mean_query.filter(Game.playlist == playlist)
            std_query = std_query.filter(Game.playlist == playlist)

        mean_query = mean_query.all()
        std_query = std_query.all()

        mean_query = [list(q) for q in mean_query]
        std_query = [list(q) for q in std_query]
        results = []
        for q, s in zip(mean_query, std_query):
            result = {
                'name':
                datetime.datetime.strptime(q[0], time_format),
                'average':
                self.get_wrapped_stats([self.float_maybe(qn) for qn in q[2:]],
                                       self.player_stats),
                'std_dev':
                self.get_wrapped_stats([self.float_maybe(qn) for qn in s[2:]],
                                       self.player_stats),
                'count':
                q[1]
            }
            if time_unit == 'quarter':
                date = result['name']
                result['name'] = datetime.datetime(date.year,
                                                   (date.month - 1) * 3 + 1, 1)
            result['name'] = result['name'].isoformat()
            results.append(result)
        return results

    @staticmethod
    def get_stat_spider_charts():
        titles = [  # 'Basic',
            'Aggressiveness', 'Chemistry', 'Skill', 'Tendencies', 'Luck'
        ]
        groups = [  # ['score', 'goals', 'assists', 'saves', 'turnovers'],  # basic
            [
                'shots', 'possession_time', 'total_hits', 'shots/hit',
                'boost_usage', 'average_speed'
            ],  # agressive
            [
                'total_boost_efficiency', 'assists', 'passes/hit',
                'total_passes', 'assists/hit'
            ],  # chemistry
            [
                'turnover_efficiency', 'useful/hits', 'total_aerials',
                'won_turnovers', 'average_hit_distance'
            ],  # skill
            [
                'time_in_attacking_third', 'time_in_attacking_half',
                'time_in_defending_half', 'time_in_defending_third',
                'time_behind_ball', 'time_in_front_ball'
            ]
        ]  # ,  # tendencies
        # ['luck1', 'luck2', 'luck3', 'luck4']]  # luck

        return [{
            'title': title,
            'group': group
        } for title, group in zip(titles, groups)]

    def _create_group_stats_from_query(self,
                                       session,
                                       query,
                                       player_filter=None,
                                       replay_ids=None):
        stats = QueryFilterBuilder().with_stat_query(query)
        if player_filter is not None:
            stats.with_players(player_filter)
        if replay_ids is not None:
            stats.with_replay_ids(replay_ids)
        stats = stats.build_query(session).filter(
            PlayerGame.time_in_game > 0).first()
        stats = {
            n.get_query_key(): round(float(s), 2)
            for n, s in zip(self.player_stats.stat_list, stats)
            if s is not None
        }
        return stats

    def _get_counts(self, session, player_filter=None, replay_ids=None):
        stats = QueryFilterBuilder().with_stat_query((PlayerGame, ))
        if player_filter is not None:
            stats.with_players(player_filter)
        if replay_ids is not None:
            stats.with_replay_ids(replay_ids)
        return stats.build_query(session).filter(
            PlayerGame.time_in_game > 0).count()

    def _split_by_category(self, stats):
        stats_dict = {}
        for chart_metadata in player_group_stats_metadata:
            if chart_metadata.stat_name not in stats:
                continue
            stats_dict[chart_metadata.stat_name] = {
                "value": stats[chart_metadata.stat_name],
                "subcategory": chart_metadata.subcategory
            }
        for stat in set(stats.keys()) - set(
            [stat.stat_name for stat in player_group_stats_metadata]):
            stats_dict[stat] = {"value": stats[stat], "subcategory": "Misc"}
        return stats_dict

    def _create_group_stats(self,
                            session,
                            player_filter=None,
                            replay_ids=None):
        average = self._create_group_stats_from_query(
            session, self.get_player_stat_query(), player_filter, replay_ids)
        individual = self._create_group_stats_from_query(
            session, self.player_stats.individual_query, player_filter,
            replay_ids)
        total = {}
        per_game = {}
        per_norm_game = {}
        per_minute = {}

        factor_per_game = self._get_counts(session, player_filter, replay_ids)
        factor_per_minute = individual[
            'time_in_game'] / 60.0 if 'time_in_game' in individual else None
        factor_per_norm_game = factor_per_minute / 5 if factor_per_minute is not None else None
        for stat in self.get_player_stat_list():
            stat_query_key = stat.get_query_key()
            if stat_query_key not in individual or stat_query_key not in average:
                continue
            if stat_query_key in self.replay_group_stats.grouped_stat_total:
                if stat.is_percent or stat.is_averaged:
                    total[stat_query_key] = average[stat_query_key]
                else:
                    total[stat_query_key] = individual[stat_query_key]
            if stat_query_key in self.replay_group_stats.grouped_stat_per_game and factor_per_game is not None:
                per_game[stat_query_key] = individual[
                    stat_query_key] / factor_per_game
            if stat_query_key in self.replay_group_stats.grouped_stat_per_minute and factor_per_minute is not None:
                per_minute[stat_query_key] = individual[
                    stat_query_key] / factor_per_minute
            if factor_per_norm_game is not None:
                if stat_query_key in self.replay_group_stats.grouped_stat_total \
                        and (stat.is_percent or stat.is_averaged):
                    per_norm_game[stat_query_key +
                                  " (Total)"] = average[stat_query_key]
                else:
                    per_norm_game[stat_query_key] = individual[
                        stat_query_key] / factor_per_norm_game
        return {
            'stats': {
                '(Total)': total,
                '(per Game)': per_game,
                '(per Norm Game)': per_norm_game,
                '(per Minute)': per_minute
            }
        }

    @with_session
    def get_group_team_stats(self, replay_ids, session=None):
        query = session.query(
            PlayerGame.game,
            func.array_agg(
                aggregate_order_by(
                    PlayerGame.player,
                    PlayerGame.player)).label("team")).filter(
                        PlayerGame.game.in_(replay_ids)).group_by(
                            PlayerGame.game).group_by(
                                PlayerGame.is_orange).subquery()
        teams = session.query(query.c.team, func.array_agg(
            query.c.game)).group_by(query.c.team).all()
        return {
            "teamStats": [{
                "team":
                team[0],
                "games":
                team[1],
                "names": [
                    name
                    for (name, ) in session.query(func.min(
                        PlayerGame.name)).filter(PlayerGame.game.in_(team[1])).
                    filter(PlayerGame.player.in_(team[0])).order_by(
                        PlayerGame.player).group_by(PlayerGame.player).all()
                ],
                **self._create_group_stats(session,
                                           player_filter=team[0],
                                           replay_ids=team[1])
            } for team in teams]
        }

    @with_session
    def get_group_stats(self, replay_ids, session=None):
        return_obj = {}

        # Players
        player_tuples: List[Tuple[str, str, int]] = session.query(
            PlayerGame.player, func.min(PlayerGame.name),
            func.count(PlayerGame.player)).filter(
                PlayerGame.game.in_(replay_ids)).group_by(
                    PlayerGame.player).all()
        return_obj['playerStats'] = []
        # ensemble are the players that do not have enough replays to make an individual analysis for them
        ensemble = []
        for player_tuple in player_tuples:
            player, name, count = player_tuple
            if count > 1:
                player_stats = self._create_group_stats(session,
                                                        player_filter=player,
                                                        replay_ids=replay_ids)
                player_stats['name'] = name
                player_stats['player'] = player
                return_obj['playerStats'].append(player_stats)
            else:
                ensemble.append(player)
        if len(ensemble) > 0:
            # create stats that only includes the ensemble
            ensemble_stats = self._create_group_stats(session,
                                                      player_filter=ensemble,
                                                      replay_ids=replay_ids)
            return_obj['ensembleStats'] = ensemble_stats
        # STATS
        # Global
        # create stats that include all the players in the game
        # global_stats = self._create_group_stats(session, ids=replay_ids)
        # return_obj['globalStats'] = global_stats

        num_replays = len(replay_ids)
        if num_replays > 1 and all(
            [player_tuple[2] == num_replays
             for player_tuple in player_tuples]):
            assert 'ensembleStats' not in return_obj
            # all players have played every game
            is_orange = {player_tuple[0]: [] for player_tuple in player_tuples}

            for replay_id in replay_ids:
                game: Game = session.query(Game).filter(
                    Game.hash == replay_id).first()
                if game is None:
                    raise ReplayNotFound()

                playergames: List = session.query(
                    func.max(PlayerGame.player),
                    func.bool_and(PlayerGame.is_orange)).filter(
                        PlayerGame.game == replay_id).group_by(
                            PlayerGame.player).all()

                for playergame in playergames:
                    assert len(playergame) == 2
                    player, is_orange_game = playergame
                    assert player in is_orange
                    is_orange[player].append(is_orange_game)

            # if the player is always in the same team
            if all([
                    len(set(player_is_orange)) == 1
                    for player_is_orange in is_orange.values()
            ]):
                for i in range(len(return_obj['playerStats'])):
                    player = return_obj['playerStats'][i]['player']
                    return_obj['playerStats'][i]['is_orange'] = is_orange[
                        player][0]

                return_obj['playerStats'] = sorted(
                    sorted(return_obj['playerStats'],
                           key=lambda x: x['name'].lower()),
                    key=lambda x: x['is_orange'])

        return return_obj
class PlayerStatWrapper(GlobalStatWrapper):
    def __init__(self, player_wrapper: PlayerWrapper):
        super().__init__()
        self.player_wrapper = player_wrapper

        # this Object needs to be pooled per a session so only one is used at a time
        self.player_stats_filter = QueryFilterBuilder()
        if not ignore_filtering():
            self.player_stats_filter.with_relative_start_time(
                days_ago=30 * 6).with_safe_checking().sticky()

    def get_stats(self,
                  session,
                  id_,
                  stats_query,
                  std_query,
                  rank=None,
                  redis=None,
                  raw=False,
                  replay_ids=None,
                  playlist=13,
                  win: bool = None):

        player_stats_filter = self.player_stats_filter.clean().clone()
        player_stats_filter.clean().with_stat_query(stats_query).with_players(
            [id_])
        if replay_ids is not None:
            player_stats_filter.with_replay_ids(replay_ids)
        else:
            player_stats_filter.with_playlists([playlist])
        query = player_stats_filter.build_query(session).filter(
            PlayerGame.time_in_game > 0).filter(
                PlayerGame.game != '').group_by(PlayerGame.player)
        if win is not None:
            query = query.filter(PlayerGame.win == win)
        if query.count() < 1:
            raise CalculatedError(404, 'User does not have enough replays.')
        stats = list(query.first())
        stats = [0 if s is None else s for s in stats]
        if raw:
            return [self.float_maybe(s) for s in stats]
        else:
            global_stats, global_stds = self.get_global_stats_by_rank(
                session,
                player_stats_filter,
                stats_query,
                std_query,
                player_rank=rank,
                redis=redis,
                ids=replay_ids,
                playlist=playlist)
            return self.compare_to_global(stats, global_stats, global_stds)

    def get_averaged_stats(self,
                           session,
                           id_: str,
                           rank: int = None,
                           redis=None,
                           raw: bool = False,
                           replay_ids: list = None,
                           playlist: int = 13,
                           win: bool = None):
        """
        Gets the averaged stats for the given ID, in either raw or standard deviation form.

        :param session: DBSession
        :param id_: Player ID to retrieve
        :param rank: rank in integer form (0-19) (optional)
        :param redis: optional redis instance, for caching/retrieving
        :param raw: return the values in raw form instead of compared to global
        :param replay_ids: replay ids to get stats from
        :param playlist: what playlist to filter by (only when replay_ids is None)
        :param win: filter by wins/losses (win = True)
        :return: stats
        """
        stats_query = self.get_player_stat_query()
        std_query = self.get_player_stat_std_query()
        total_games = self.player_wrapper.get_total_games(
            session, id_, replay_ids=replay_ids)
        if total_games > 0:
            stats = self.get_stats(session,
                                   id_,
                                   stats_query,
                                   std_query,
                                   rank=rank,
                                   redis=redis,
                                   raw=raw,
                                   replay_ids=replay_ids,
                                   playlist=playlist,
                                   win=win)
        else:
            stats = [0.0] * len(stats_query)
        return self.get_wrapped_stats(stats, self.player_stats)

    def get_progression_stats(self,
                              session,
                              id_,
                              time_unit: 'TimeUnit' = TimeUnit.MONTH,
                              start_date: datetime.datetime = None,
                              end_date: datetime.datetime = None,
                              playlist: int = 13):

        if time_unit == TimeUnit.MONTH:
            date = func.to_char(Game.match_date, 'YY-MM')
            time_format = "%y-%m"
        elif time_unit == TimeUnit.DAY:
            date = func.to_char(Game.match_date, 'YY-MM-DD')
            time_format = "%y-%m-%d"
        elif time_unit == TimeUnit.YEAR:
            date = func.to_char(Game.match_date, 'YY')
            time_format = "%y"
        elif time_unit == TimeUnit.QUARTER:
            date = func.concat(
                func.to_char(Game.match_date, 'YY'), '-',
                func.floor(
                    cast(func.extract('quarter', Game.match_date),
                         sqlalchemy.Numeric)))
            time_format = '%y-%m'
        else:
            # Mainly to help the linter know that time_unit is assigned.
            logger.error(
                f'Unknown time unit: {time_unit}. Falling back onto month.')
            date = func.to_char(Game.match_date, 'YY-MM')
            time_format = "%y-%m"

        date = date.label('date')
        mean_query = session.query(date, func.count(
            Game.hash), *self.get_player_stat_query()).join(PlayerGame).filter(
                PlayerGame.time_in_game > 0).filter(
                    PlayerGame.player == id_).group_by('date').order_by('date')
        std_query = session.query(
            date, func.count(Game.hash),
            *self.get_player_stat_std_query()).join(PlayerGame).filter(
                PlayerGame.time_in_game > 0).filter(
                    PlayerGame.player == id_).group_by('date').order_by('date')

        if start_date is not None:
            mean_query = mean_query.filter(Game.match_date > start_date)
            std_query = std_query.filter(Game.match_date > start_date)
        if end_date is not None:
            mean_query = mean_query.filter(Game.match_date < end_date)
            std_query = std_query.filter(Game.match_date < end_date)
        if playlist is not None:
            mean_query = mean_query.filter(Game.playlist == playlist)
            std_query = std_query.filter(Game.playlist == playlist)

        mean_query = mean_query.all()
        std_query = std_query.all()

        mean_query = [list(q) for q in mean_query]
        std_query = [list(q) for q in std_query]
        results = []
        for q, s in zip(mean_query, std_query):
            result = {
                'name':
                datetime.datetime.strptime(q[0], time_format),
                'average':
                self.get_wrapped_stats([self.float_maybe(qn) for qn in q[2:]],
                                       self.player_stats),
                'std_dev':
                self.get_wrapped_stats([self.float_maybe(qn) for qn in s[2:]],
                                       self.player_stats),
                'count':
                q[1]
            }
            if time_unit == 'quarter':
                date = result['name']
                result['name'] = datetime.datetime(date.year,
                                                   (date.month - 1) * 3 + 1, 1)
            result['name'] = result['name'].isoformat()
            results.append(result)
        return results

    @staticmethod
    def get_stat_spider_charts():
        titles = [  # 'Basic',
            'Aggressiveness', 'Chemistry', 'Skill', 'Tendencies', 'Luck'
        ]
        groups = [  # ['score', 'goals', 'assists', 'saves', 'turnovers'],  # basic
            [
                'shots', 'possession_time', 'total_hits', 'shots/hit',
                'boost_usage', 'average_speed'
            ],  # agressive
            [
                'total boost efficiency', 'assists', 'passes/hit',
                'total_passes', 'assists/hit'
            ],  # chemistry
            [
                'turnover efficiency', 'useful/hits', 'total_aerials',
                'won_turnovers', 'average_hit_distance'
            ],  # skill
            [
                'time_in_attacking_third', 'time_in_attacking_half',
                'time_in_defending_half', 'time_in_defending_third',
                'time_behind_ball', 'time_in_front_ball'
            ]
        ]  # ,  # tendencies
        # ['luck1', 'luck2', 'luck3', 'luck4']]  # luck

        return [{
            'title': title,
            'group': group
        } for title, group in zip(titles, groups)]

    def _create_stats(self, session, player_filter=None, replay_ids=None):
        average = QueryFilterBuilder().with_stat_query(
            self.get_player_stat_query())
        std_devs = QueryFilterBuilder().with_stat_query(
            self.get_player_stat_std_query())
        if player_filter is not None:
            average.with_players(player_filter)
            std_devs.with_players(player_filter)
        if replay_ids is not None:
            average.with_replay_ids(replay_ids)
            std_devs.with_replay_ids(replay_ids)
        average = average.build_query(session).filter(
            PlayerGame.time_in_game > 0).first()
        std_devs = std_devs.build_query(session).filter(
            PlayerGame.time_in_game > 0).first()
        average = {
            n.get_field_name(): round(float(s), 2)
            for n, s in zip(self.player_stats.stat_list, average)
            if s is not None
        }
        std_devs = {
            n.get_field_name(): round(float(s), 2)
            for n, s in zip(self.player_stats.stat_list, std_devs)
            if s is not None
        }
        return {'average': average, 'std_dev': std_devs}

    @with_session
    def get_group_stats(self, replay_ids, session=None):
        return_obj = {}
        # Players
        player_tuples: List[Tuple[str, str, int]] = session.query(
            PlayerGame.player, func.min(PlayerGame.name),
            func.count(PlayerGame.player)).filter(
                PlayerGame.game.in_(replay_ids)).group_by(
                    PlayerGame.player).all()
        return_obj['playerStats'] = {}
        # ensemble are the players that do not have enough replays to make an individual analysis for them
        ensemble = []
        for player_tuple in player_tuples:
            player, name, count = player_tuple
            if count > 1:
                player_stats = self._create_stats(session,
                                                  player_filter=player,
                                                  replay_ids=replay_ids)
                player_stats['name'] = name
                return_obj['playerStats'][player] = player_stats
            else:
                ensemble.append(player)
        if len(ensemble) > 0:
            # create stats that only includes the ensemble
            ensemble_stats = self._create_stats(session,
                                                player_filter=ensemble,
                                                replay_ids=replay_ids)
            return_obj['ensembleStats'] = ensemble_stats
        # STATS
        # Global
        # create stats that include all the players in the game
        # global_stats = self._create_stats(session, ids=replay_ids)
        # return_obj['globalStats'] = global_stats
        return return_obj
Exemplo n.º 5
0
class GlobalStatWrapper(SharedStatsWrapper):
    """
    A database wrapper for global stats.  Acts additionally on global stats in addition to player stats
    """
    def __init__(self):
        super().__init__()

        # this Object needs to be pooled per a session so only one is used at a time
        self.base_query = QueryFilterBuilder().with_relative_start_time(
            days_ago=self.get_timeframe()).with_safe_checking().sticky()

    def get_global_stats(self, sess, with_rank=True):
        """
        :return: A list of stats by rank for every field.
        """
        if with_rank:
            ranks = list(range(20))
        else:
            ranks = [0]

        playlist_results = {}
        for playlist in GLOBAL_STATS_PLAYLISTS:
            results = {}
            for column, q in zip(self.get_player_stat_list(),
                                 self.get_player_stat_query()):
                column_results = []
                # set the column result
                self.base_query.clean().with_stat_query(
                    [PlayerGame.player, q.label('avg')])
                for rank in ranks:
                    result = self._get_global_stats_result(self.base_query,
                                                           playlist,
                                                           rank,
                                                           sess,
                                                           with_rank=with_rank)
                    column_results.append({
                        'mean': self.float_maybe(result[0]),
                        'std': self.float_maybe(result[1])
                    })
                results[column.get_field_name()] = column_results
            playlist_results[playlist] = results
        return playlist_results

    def _get_global_stats_result(self,
                                 query,
                                 playlist,
                                 rank,
                                 session,
                                 with_rank=True):
        if with_rank:
            query = query.with_rank(rank)
        if not ignore_filtering():
            query.with_playlists([playlist])
        query = query.build_query(session)
        query = query.group_by(PlayerGame.player)
        if ignore_filtering():
            query = query.subquery()
        else:
            query = query.filter(PlayerGame.game != "").filter(
                PlayerGame.time_in_game > 0).having(
                    func.count(PlayerGame.player) > 5).subquery()

        return session.query(func.avg(query.c.avg),
                             func.stddev_samp(query.c.avg)).first()

    def get_global_stats_by_rank(self,
                                 session,
                                 query_filter: QueryFilterBuilder,
                                 stats_query,
                                 stds_query,
                                 player_rank=None,
                                 redis=None,
                                 ids=None,
                                 playlist=13):
        """
        Returns the global stats based on the rank of a player.

        Does modify the query_filter only setting rank.
        :param session: Session
        :param query_filter: a query filter.
        :param stats_query: A list of global stats
        :param stds_query: A list of global stats for standard deviations
        :param player_rank: The player that stats are associated with.  Uses unranked if rank is not found
        :param redis: The local cache
        :return:
        """

        if ids is None:
            # Set the correct rank index
            if player_rank is not None:
                if isinstance(player_rank, dict) or isinstance(
                        player_rank, list):
                    rank_index = get_rank_tier(player_rank, playlist=playlist)
                else:
                    rank_index = player_rank
            else:
                rank_index = 0

            stat_list = self.get_player_stat_list()

            # Check to see if we have redis available (it usually is)
            if redis is not None:
                stat_string = redis.get('global_stats')
                # Check to see if the key exists and if so load it
                if stat_string is not None:
                    stats_dict = json.loads(stat_string)
                    if playlist is not None:
                        playlist = str(playlist)
                    if playlist not in stats_dict:
                        raise CalculatedError(
                            404, 'Playlist does not exist in global stats')
                    stats_dict = stats_dict[playlist]
                    global_stats = []
                    global_stds = []
                    for stat in stat_list:
                        if stat.get_field_name() in stats_dict:
                            global_stats.append(stats_dict[
                                stat.get_field_name()][rank_index]['mean'])
                            global_stds.append(stats_dict[
                                stat.get_field_name()][rank_index]['std'])
                        else:
                            global_stats.append(1)
                            global_stds.append(1)
                    return global_stats, global_stds
            if is_local_dev():
                rank_index = 0
                stats = self.get_global_stats(session, with_rank=False)
                global_stats = [
                    stats[stat.get_field_name()][rank_index]['mean']
                    for stat in stat_list
                ]
                global_stds = [
                    stats[stat.get_field_name()][rank_index]['std']
                    for stat in stat_list
                ]
                return global_stats, global_stds
            raise CalculatedError(
                500,
                "Global stats unavailable or have not been calculated yet.")
        else:
            query_filter.clean().with_replay_ids(ids)
            return (query_filter.with_stat_query(stats_query).build_query(
                session).first(), query_filter.with_stat_query(
                    stds_query).build_query(session).first())

    @staticmethod
    def get_timeframe():
        """Returns the number of days we accept old stats"""
        try:
            from flask import current_app
            return current_app.config['STAT_DAY_LIMIT']
        except:
            return 30 * 6
class PlayerStatWrapper(GlobalStatWrapper):
    def __init__(self, player_wrapper: PlayerWrapper):
        super().__init__()
        self.player_wrapper = player_wrapper

        # this Object needs to be pooled per a session so only one is used at a time
        self.player_stats_filter = QueryFilterBuilder()
        if not ignore_filtering():
            self.player_stats_filter.with_relative_start_time(
                days_ago=30 *
                6).with_team_size(3).with_safe_checking().sticky()

    def get_wrapped_stats(self, stats):
        zipped_stats = dict()

        for i in range(len(self.stat_list)):
            zipped_stats[self.stat_list[i].get_field_name()] = stats[i]

        return zipped_stats

    def get_stats(self,
                  session,
                  id_,
                  stats_query,
                  std_query,
                  rank=None,
                  redis=None,
                  raw=False,
                  replay_ids=None):
        player_stats_filter = self.player_stats_filter.clean().clone()
        global_stats, global_stds = self.get_global_stats_by_rank(
            session,
            player_stats_filter,
            stats_query,
            std_query,
            player_rank=rank,
            redis=redis,
            ids=replay_ids)
        player_stats_filter.clean().with_stat_query(stats_query).with_players(
            [id_])
        if replay_ids is not None:
            player_stats_filter.with_replay_ids(replay_ids)
        query = player_stats_filter.build_query(session).filter(
            PlayerGame.time_in_game > 0).filter(
                PlayerGame.game != '').group_by(PlayerGame.player)
        stats = list(query.first())
        stats = [0 if s is None else s for s in stats]
        if raw:
            return [float(s) for s in stats], [float(s) for s in global_stats]
        else:
            return self.compare_to_global(stats, global_stats,
                                          global_stds), len(stats) * [0.0]

    def get_averaged_stats(self,
                           session,
                           id_,
                           rank=None,
                           redis=None,
                           raw=False,
                           replay_ids=None):
        stats_query = self.stats_query
        std_query = self.std_query
        total_games = self.player_wrapper.get_total_games(
            session, id_, replay_ids=replay_ids)
        if total_games > 0:
            stats, global_stats = self.get_stats(session,
                                                 id_,
                                                 stats_query,
                                                 std_query,
                                                 rank=rank,
                                                 redis=redis,
                                                 raw=raw,
                                                 replay_ids=replay_ids)
        else:
            stats = [0.0] * len(stats_query)
            global_stats = [0.0] * len(stats_query)
        return self.get_wrapped_stats(stats), self.get_wrapped_stats(
            global_stats)

    def get_progression_stats(self, session, id_):
        def float_maybe(f):
            if f is None:
                return None
            else:
                return float(f)

        mean_query = session.query(
            func.to_char(Game.match_date,
                         'YY-MM').label('date'), *self.stats_query
        ).join(PlayerGame).filter(PlayerGame.time_in_game > 0).filter(
            PlayerGame.player == id_).group_by('date').order_by('date').all()
        std_query = session.query(
            func.to_char(Game.match_date,
                         'YY-MM').label('date'), *self.std_query
        ).join(PlayerGame).filter(PlayerGame.time_in_game > 0).filter(
            PlayerGame.player == id_).group_by('date').order_by('date').all()
        mean_query = [list(q) for q in mean_query]
        std_query = [list(q) for q in std_query]
        results = []
        for q, s in zip(mean_query, std_query):
            result = {
                'name':
                datetime.datetime.strptime(q[0], '%y-%m').isoformat(),
                'average':
                self.get_wrapped_stats([float_maybe(qn) for qn in q[1:]]),
                'std_dev':
                self.get_wrapped_stats([float_maybe(qn) for qn in s[1:]])
            }
            results.append(result)
        return results

    @staticmethod
    def get_stat_spider_charts():
        titles = [  # 'Basic',
            'Aggressiveness', 'Chemistry', 'Skill', 'Tendencies', 'Luck'
        ]
        groups = [  # ['score', 'goals', 'assists', 'saves', 'turnovers'],  # basic
            [
                'shots', 'possession', 'hits', 'shots/hit', 'boost usage',
                'speed'
            ],  # agressive
            [
                'total boost efficiency', 'assists', 'passes/hit', 'passes',
                'assists/hit'
            ],  # chemistry
            [
                'turnover efficiency', 'useful/hits', 'aerials',
                'won turnovers', 'avg hit dist'
            ],  # skill
            ['att 1/3', 'att 1/2', 'def 1/2', 'def 1/3', '< ball', '> ball']
        ]  # ,  # tendencies
        # ['luck1', 'luck2', 'luck3', 'luck4']]  # luck

        return [{
            'title': title,
            'group': group
        } for title, group in zip(titles, groups)]

    def _create_stats(self, session, player_filter=None, replay_ids=None):
        average = QueryFilterBuilder().with_stat_query(self.stats_query)
        std_devs = QueryFilterBuilder().with_stat_query(self.std_query)
        if player_filter is not None:
            average.with_players(player_filter)
            std_devs.with_players(player_filter)
        if replay_ids is not None:
            average.with_replay_ids(replay_ids)
            std_devs.with_replay_ids(replay_ids)
        average = average.build_query(session).filter(
            PlayerGame.time_in_game > 0).first()
        std_devs = std_devs.build_query(session).filter(
            PlayerGame.time_in_game > 0).first()

        average = {
            n.field_name: round(float(s), 2)
            for n, s in zip(self.field_names, average) if s is not None
        }
        std_devs = {
            n.field_name: round(float(s), 2)
            for n, s in zip(self.field_names, std_devs) if s is not None
        }
        return {'average': average, 'std_dev': std_devs}

    def get_group_stats(self, session, replay_ids):
        return_obj = {}
        # Players
        player_tuples: List[Tuple[str, str, int]] = session.query(
            PlayerGame.player, func.min(PlayerGame.name),
            func.count(PlayerGame.player)).filter(
                PlayerGame.game.in_(replay_ids)).group_by(
                    PlayerGame.player).all()
        return_obj['playerStats'] = {}
        # ensemble are the players that do not have enough replays to make an individual analysis for them
        ensemble = []
        for player_tuple in player_tuples:
            player, name, count = player_tuple
            if count > 1:
                player_stats = self._create_stats(session,
                                                  player_filter=player,
                                                  replay_ids=replay_ids)
                player_stats['name'] = name
                return_obj['playerStats'][player] = player_stats
            else:
                ensemble.append(player)
        if len(ensemble) > 0:
            # create stats that only includes the ensemble
            ensemble_stats = self._create_stats(session,
                                                player_filter=ensemble,
                                                replay_ids=replay_ids)
            return_obj['ensembleStats'] = ensemble_stats
        # STATS
        # Global
        # create stats that include all the players in the game
        # global_stats = self._create_stats(session, ids=replay_ids)
        # return_obj['globalStats'] = global_stats
        return return_obj
Exemplo n.º 7
0
class GlobalStatWrapper(SharedStatsWrapper):
    """
    A database wrapper for global stats.  Acts additionally on global stats in addition to player stats
    """

    def __init__(self):
        super().__init__()

        # this Object needs to be pooled per a session so only one is used at a time
        self.base_query = QueryFilterBuilder().with_relative_start_time(days_ago=self.get_timeframe()).with_team_size(
            3).sticky()

    def get_global_stats(self, sess):
        """
        :return: A list of stats by rank for every field.
        """
        results = {}
        ranks = list(range(20))

        def float_maybe(f):
            if f is None:
                return None
            else:
                return float(f)

        for column, q in zip(self.field_names, self.stats_query):
            column_results = []
            # set the column result
            self.base_query.clean().with_stat_query([PlayerGame.player, q.label('avg')])
            for rank in ranks:
                query = self.base_query.with_rank(rank).build_query(sess)
                query = query.group_by(PlayerGame.player).having(func.count(PlayerGame.player) > 5).subquery()

                result = sess.query(func.avg(query.c.avg), func.stddev_samp(query.c.avg)).first()
                column_results.append({'mean': float_maybe(result[0]), 'std': float_maybe(result[1])})
            results[column.field_name] = column_results
        return results

    def get_global_stats_by_rank(self, session, query_filter: QueryFilterBuilder, stats_query, stds_query,
                                 player_rank=None, redis=None):
        """
        Returns the global stats based on the rank of a player.

        Does modify the query_filter only setting rank.
        :param session: Session
        :param query_filter: a query filter.
        :param stats_query: A list of global stats
        :param stds_query: A list of global stats for standard deviations
        :param player_rank: The player that stats are associated with.  Uses unranked if rank is not found
        :param redis: The local cache
        :return:
        """
        rank_index = get_rank_number(player_rank)
        if redis is not None:
            stat_string = redis.get('global_stats')
            if stat_string is not None:
                stats_dict = json.loads(stat_string)
                global_stats = [stats_dict[s.field_name][rank_index]['mean'] for s in self.field_names]
                global_stds = [stats_dict[s.field_name][rank_index]['std'] for s in self.field_names]
                return global_stats, global_stds

        if player_rank is not None and not get_local_dev():
            logger.debug('Filtering by rank')
            query_filter.clean().with_rank(rank_index)
        else:
            query_filter.clean()

        return (query_filter.with_stat_query(stats_query).build_query(session).first(),
                query_filter.with_stat_query(stds_query).build_query(session).first())

    @staticmethod
    def get_timeframe():
        """Returns the number of days we accept old stats"""
        try:
            from flask import current_app
            return current_app.config['STAT_DAY_LIMIT']
        except:
            return 30 * 6
class PlayerStatWrapper(GlobalStatWrapper):
    def __init__(self, player_wrapper: PlayerWrapper):
        super().__init__()
        self.player_wrapper = player_wrapper

        # this Object needs to be pooled per a session so only one is used at a time
        self.player_stats_filter = QueryFilterBuilder()
        self.player_stats_filter.with_relative_start_time(
            days_ago=30 * 6).with_team_size(3).with_safe_checking().sticky()

    def get_wrapped_stats(self, stats):
        zipped_stats = dict()

        for i in range(len(self.field_names)):
            zipped_stats[self.field_names[i].field_name] = stats[i]

        return zipped_stats

    def get_stats(self,
                  session,
                  id_,
                  stats_query,
                  std_query,
                  rank=None,
                  redis=None):
        global_stats, global_stds = self.get_global_stats_by_rank(
            session,
            self.player_stats_filter,
            stats_query,
            std_query,
            player_rank=rank,
            redis=redis)

        self.player_stats_filter.clean().with_stat_query(
            stats_query).with_players([id_])

        stats = list(self.player_stats_filter.build_query(session).first())

        return self.compare_to_global(stats, global_stats, global_stds)

    def get_averaged_stats(self, session, id_, rank=None, redis=None):
        stats_query = self.stats_query
        std_query = self.std_query
        total_games = self.player_wrapper.get_total_games(session, id_)
        if total_games > 0:
            stats = self.get_stats(session,
                                   id_,
                                   stats_query,
                                   std_query,
                                   rank=rank,
                                   redis=redis)
        else:
            stats = [0.0] * len(stats_query)
        return self.get_wrapped_stats(stats)

    @staticmethod
    def get_stat_spider_charts():
        titles = [  # 'Basic',
            'Aggressiveness', 'Chemistry', 'Skill', 'Tendencies', 'Luck'
        ]
        groups = [  # ['score', 'goals', 'assists', 'saves', 'turnovers'],  # basic
            [
                'shots', 'possession', 'hits', 'shots/hit', 'boost usage',
                'speed'
            ],  # agressive
            ['boost wasted', 'assists', 'passes/hit', 'passes',
             'assists/hit'],  # chemistry
            [
                'turnovers', 'useful/hits', 'aerials', 'won turnovers',
                'avg hit dist'
            ],  # skill
            ['att 1/3', 'att 1/2', 'def 1/2', 'def 1/3', '< ball', '> ball']
        ]  # ,  # tendencies
        # ['luck1', 'luck2', 'luck3', 'luck4']]  # luck

        return [{
            'title': title,
            'group': group
        } for title, group in zip(titles, groups)]

    def _create_stats(self, session, query_filter=None, ids=None):
        if query_filter is not None and ids is not None:
            query_filter = and_(query_filter, PlayerGame.game.in_(ids))
        elif ids is not None:
            query_filter = PlayerGame.game.in_(ids)
        average = session.query(*self.stats_query)
        std_devs = session.query(*self.std_query)
        if query_filter is not None:
            average = average.filter(query_filter)
            std_devs = std_devs.filter(query_filter)
        average = average.first()
        std_devs = std_devs.first()

        average = {
            n.field_name: round(float(s), 2)
            for n, s in zip(self.field_names, average) if s is not None
        }
        std_devs = {
            n.field_name: round(float(s), 2)
            for n, s in zip(self.field_names, std_devs) if s is not None
        }
        return {'average': average, 'std_dev': std_devs}

    def get_group_stats(self, session, ids):
        return_obj = {}
        # Players
        player_tuples = session.query(PlayerGame.player,
                                      func.count(PlayerGame.player)).filter(
                                          PlayerGame.game.in_(ids)).group_by(
                                              PlayerGame.player).all()
        return_obj['playerStats'] = {}
        ensemble = []
        for player_tuple in player_tuples:
            player, count = player_tuple
            if count > 1:
                player_stats = self._create_stats(session,
                                                  PlayerGame.player == player,
                                                  ids=ids)
                return_obj['playerStats'][player] = player_stats
            else:
                ensemble.append(player)
        if len(ensemble) > 0:
            ensemble_stats = self._create_stats(
                session, PlayerGame.player.in_(ensemble), ids=ids)
            return_obj['ensembleStats'] = ensemble_stats
        # STATS
        # Global
        global_stats = self._create_stats(session, ids=ids)
        return_obj['globalStats'] = global_stats
        return return_obj