def test_get_season_from_database_adds_season():
    mock_session = MagicMock()
    mock_session.query.return_value.filter.return_value.one_or_none.return_value = None

    expected_season = Season(season_of_year="spring", year=2017)

    Season.get_season_from_database(expected_season.season_of_year,
                                    expected_season.year, mock_session)
    args, _ = mock_session.add.call_args
    season_added = args[0]
    assert isinstance(season_added, Season)
    assert season_added.year == expected_season.year
    assert season_added.season_of_year == expected_season.season_of_year
Beispiel #2
0
def team_overview(
    season_str: str = season_str,
    year: int = year,
    filename: str = "lists/team_overview.txt",
) -> None:
    """
    Creates a formatted forum post for the team overview thread.
    """
    week: int = config.getint("weekly info", "current-week")
    with session_scope() as session:
        teams = Season.get_season_from_database(season_str, year, session).teams
        with open(filename, "w", encoding="utf-8") as f:
            f.write(f"Team List - FAL {season_str.capitalize()} {year}\n\n\n")
            for team in sorted(teams, key=lambda t: t.name.lower()):
                # Query all the anime on the team for this week
                base_query = (
                    session.query(TeamWeeklyAnime)
                    .filter(TeamWeeklyAnime.team_id == team.id)
                    .filter(TeamWeeklyAnime.week == week)
                )
                active_anime = base_query.filter(TeamWeeklyAnime.bench == 0).all()
                bench_anime = base_query.filter(TeamWeeklyAnime.bench == 1).all()
                print(f"writing {team.name} to overview")
                f.write(f"{team.name}\n---------------------------------\n")
                # List all active series on the team
                for anime in sorted(active_anime, key=lambda a: a.anime.name.lower()):
                    f.write(f"{anime.anime.name}\n")
                f.write("\n")
                # List all bench series on the team
                for anime in sorted(bench_anime, key=lambda a: a.anime.name.lower()):
                    f.write(f"{anime.anime.name}\n")
                f.write("\n\n")
            f.write("[/spoiler]")
Beispiel #3
0
def team_dist(
    season_str: str = season_str,
    year: int = year,
    filename: str = "lists/team_dist.txt",
) -> None:
    """
    Creates a statistic of the team distribution (how many people and who chose the same team)
    This function can also be used during the game to obtain the team distribution of the current week.
    """
    week: int = config.getint("weekly info", "current-week")
    filename = add_week_to_filename(filename, week)
    split_teams: Dict[Tuple[int, ...], List[Team]] = {}
    nonsplit_teams: Dict[Tuple[int, ...], List[Team]] = {}
    active_teams: Dict[Tuple[int, ...], List[Team]] = {}
    with session_scope() as session:
        teams = Season.get_season_from_database(season_str, year, session).teams
        for i, team in enumerate(teams, 1):
            # Query all the anime on the team for this week
            base_query = (
                session.query(TeamWeeklyAnime.anime_id)
                .filter(TeamWeeklyAnime.team_id == team.id)
                .filter(TeamWeeklyAnime.week == week)
            )
            series: List[int] = base_query.all()
            active: List[int] = base_query.filter(TeamWeeklyAnime.bench == 0).all()
            bench: List[int] = base_query.filter(TeamWeeklyAnime.bench == 1).all()
            # Split and sort team so the active ones are first
            s_team: Tuple[int, ...] = tuple(sorted(active) + sorted(bench))
            n_team: Tuple[int, ...] = tuple(sorted(series))
            a_team: Tuple[int, ...] = tuple(sorted(active))
            # Add team name to inverse dictionary (key: sorted list of series)
            if s_team not in split_teams:
                split_teams[s_team] = []
            split_teams[s_team].append(team)
            if n_team not in nonsplit_teams:
                nonsplit_teams[n_team] = []
            nonsplit_teams[n_team].append(team)
            if a_team not in active_teams:
                active_teams[a_team] = []
            active_teams[a_team].append(team)
            print(f"Processed team {i} - {team}")

        same_series_diff_team_dist, n_list_non = get_dist(nonsplit_teams)
        same_series_and_team_dist, n_list_split = get_dist(split_teams)
        teams_with_same_active_team, n_list_act = get_dist(active_teams)

        with open(filename, "w", encoding="utf-8") as f:
            write_teams_to_file(
                f, same_series_and_team_dist, n_list_split, SAME_SPLIT_TEXT
            )
            write_teams_to_file(
                f, same_series_diff_team_dist, n_list_non, SAME_NONSPLIT_TEXT
            )
            write_teams_to_file(
                f, teams_with_same_active_team, n_list_act, SAME_ACTIVE_TEXT
            )
            f.write("[/list]")
Beispiel #4
0
    def season(self, n):
        session = session_factory()
        _season = (session.query(Season).filter(
            Season.id == self.season_id).one_or_none())

        if not _season:
            _season = Season(
                id=self.season_id,
                season_of_year=random.choice(["spring", "fall"]),
                year=2018 + n,
            )
            session.add(_season)
        return _season
Beispiel #5
0
def headcount(
    season_str: str = season_str,
    year: int = year,
    filename: str = "lists/team_headcount.txt",
) -> None:
    """
    Creates a formatted forum post for the headcount thread.
    """
    with session_scope() as session:
        teams = Season.get_season_from_database(season_str, year, session).teams
        with open(filename, "w", encoding="utf-8") as f:
            f.write(HEADCOUNT_INTRO_TEXT.format(season_str.capitalize(), year))
            # Output participant names alphabetically
            for team in sorted(teams, key=lambda t: t.name.lower()):
                f.write(f"[b]{team.name}[/b]\n")
            f.write(HEADCOUNT_CONC_TEXT.format(len(teams)))
Beispiel #6
0
def test_add_anime_to_database():
    mock_session = MagicMock()
    mock_session.query.return_value.filter.return_value.one_or_none.return_value = None

    expected_anime = Anime(id=1234,
                           name="The Melancholy of Haruhi Suzumiya",
                           season_id=0)

    Anime.add_anime_to_database(
        expected_anime.id,
        expected_anime.name,
        Season(id=expected_anime.season_id),
        mock_session,
    )
    args, _ = mock_session.add.call_args
    anime_added = args[0]
    assert isinstance(anime_added, Anime)
    assert anime_added.id == expected_anime.id
    assert anime_added.name == expected_anime.name
    assert anime_added.season_id == expected_anime.season_id
Beispiel #7
0
def ptw_counter() -> None:
    # Ensure season is lowercase string and year is integer
    season_of_year = config["season info"]["season"].lower()
    year = int(config["season info"]["year"])

    today = date.today()

    # Database workflow
    with session_scope() as session:
        anime_list = Season.get_season_from_database(season_of_year, year,
                                                     session).anime
        print(f"Length of list of anime: {len(anime_list)}")

        # Store PTW of each anime in a list of tuples
        ptw = get_ptw_info(anime_list)
        pprint(ptw)
        output_ptw_info(season_of_year, year, ptw)

        print("Adding PTW entries to PTW table")
        for entry in ptw:
            ptw_count = int(entry.ptw_count.replace(",", ""))
            add_ptw_to_database(entry.id, today, ptw_count, session)
Beispiel #8
0
def load_aces(input_lines: Iterable[str]) -> None:
    """Takes in an iterable of "<teamname> <anime to ace>" inputs.
    Parses these inputs and sets the anime for the team to ace for the week
    if the anime has not been previously aced on that team and the score for that anime hasn't hit the cutoff.
    """

    season_of_year = config.get("season info", "season").lower()
    year = config.getint("season info", "year")
    week = config.getint("weekly info", "current-week")

    with session_scope() as session:
        season = Season.get_season_from_database(season_of_year, year, session)
        for line in input_lines:
            teamname, animename = line.split(" ", 1)
            team = Team.get_team_from_database(teamname, season, session)
            anime = Anime.get_anime_from_database_by_name(
                animename.strip(), session)
            assert anime
            if not team_anime_aced_already(team, anime, session):
                this_week_team_anime = (session.query(TeamWeeklyAnime).filter(
                    TeamWeeklyAnime.anime_id == anime.id,
                    TeamWeeklyAnime.team_id == team.id,
                    TeamWeeklyAnime.week == week,
                ).one())
                if ace_already_loaded_this_week(team, week, session):
                    print(
                        f"{team.name} tried to ace {anime.name}, but already has an anime aced this week"
                    )
                elif this_week_team_anime.bench == 1:
                    print(
                        f"{team.name} tried to ace {anime.name}, but it was benched"
                    )
                else:
                    this_week_team_anime.ace = 1

            else:
                print(
                    f"{team.name} tried to ace {anime.name}, but it has already been aced"
                )
def load_teams(registration_data: Sequence[str]) -> None:
    """Takes the contents of registration.txt (read into a list already) and marshalls them into the database"""

    assert (config.getint("weekly info", "current-week") <=
            1), "Cannot add teams after week 1"

    # group the contents of the input registration file into separate teams,
    # loaded into TeamLines objects
    accumulated_team_input: List[str] = []
    team_lines_list: List[TeamLines] = []
    for line_num, line in enumerate(registration_data, 1):
        if line.strip() == "":
            assert (
                accumulated_team_input
            ), f"Hit a line of whitespace at line {line_num} but no team was assembled"
            team_lines_list.append(slice_up_team_input(accumulated_team_input))
            accumulated_team_input = []
        else:
            accumulated_team_input.append(line)

    # one more time in case we don't have a trailing whitespace line
    if accumulated_team_input:
        team_lines_list.append(slice_up_team_input(accumulated_team_input))

    # take the TeamLines objects and load them into the database
    with session_scope() as session:
        current_season = Season.get_season_from_database(
            config["season info"]["season"],
            config.getint("season info", "year"),
            session,
        )

        for team_lines in team_lines_list:
            print(f"Adding {team_lines.teamname} to database")
            team = Team.get_team_from_database(team_lines.teamname,
                                               current_season, session)
            add_anime_to_team(team, team_lines.active, 0, session)
            add_anime_to_team(team, team_lines.bench, 1, session)
Beispiel #10
0
def collect_series() -> None:
    config = configparser.ConfigParser()
    config.read("config.ini")

    # Ensure season is lowercase string and year is integer
    season_of_year = config["season info"]["season"].lower()
    year = int(config["season info"]["year"])

    series_dict = get_series(season_of_year, year)

    # text files workflow
    series = series_dict.items()
    print(len(series))

    output_series(series, "series.txt")
    output_series_titles(series_dict.values(), "series_sorted.txt")

    # database workflow
    print("adding anime to database")
    with session_scope() as session:
        season = Season.get_season_from_database(season_of_year, year, session)
        for anime_id, anime_name in series_dict.items():
            Anime.add_anime_to_database(anime_id, anime_name, season, session)
Beispiel #11
0
def team_stats(
    season_str: str = season_str,
    year: int = year,
    filename: str = "lists/team_stats.txt",
) -> None:
    """
    Creates a statistic of the titles distribution for the team overview thread.
    This function can also be used during the game to obtain the distribution
    of the current week.
    """
    week: int = config.getint("weekly info", "current-week")
    filename = add_week_to_filename(filename, week)
    with session_scope() as session:
        season: Season = Season.get_season_from_database(season_str, year, session)
        # Query anime name and number of times it is on a team this week
        base_query = (
            session.query(Anime.name, func.count("*"))
            .join(TeamWeeklyAnime.anime)
            .order_by(func.count("*").desc(), Anime.name)
            .filter(TeamWeeklyAnime.week == week)
            .filter(Anime.season_id == season.id)
            .group_by(Anime.name)
        )
        # Group the counts by the name
        anime_counts: List[Tuple[str, int]] = base_query.all()
        # Filter to only get active count and group by name
        active_counts: Dict[str, int] = dict(
            base_query.filter(TeamWeeklyAnime.bench == 0).all()
        )
        print(f"Anime Counts:\n{anime_counts}")
        print(f"Active Counts:\n{active_counts}")
        with open(filename, "w", encoding="utf-8") as f:
            f.write(TEAM_STATS_TEXT)
            # Output counts for each anime
            for i, (anime, count) in enumerate(anime_counts, 1):
                active_count = active_counts[anime] if anime in active_counts else 0
                f.write(f"{i} - {anime}: {count} ({active_count})\n")
def calculate_team_scores() -> None:
    """
    For every team "this week" in this season, calculate its points based on
    the criteria of the week.
    """

    season_of_year = config.get("season info", "season").lower()
    year = config.getint("season info", "year")
    week = config.getint("weekly info", "current-week")

    with session_scope() as session:
        teams = Season.get_season_from_database(season_of_year, year,
                                                session).teams

        assert isinstance(teams, list)
        for team in teams:
            this_week_points = TeamWeeklyPoints(team_id=team.id, week=week)
            session.add(this_week_points)
            add_team_anime_scores_and_ace_to_weekly_points(
                this_week_points, session)
            this_week_points.total_points = calculate_team_total_score(
                team, session)

        for count, team_id in get_team_scores_counts_this_week(week, session):
            if count == 1 and not already_got_high_bonus(team_id, session):
                top_unique_awarded = (session.query(TeamWeeklyPoints).filter(
                    TeamWeeklyPoints.week == week,
                    TeamWeeklyPoints.team_id == team_id,
                ).one())

                top_unique_awarded.weekly_points += config.getint(
                    "scoring info", "highest-unique")
                top_unique_awarded.total_points += config.getint(
                    "scoring info", "highest-unique")
                top_unique_awarded.is_highest = 1
                break
def team_ages() -> None:
    """
    Potentially can help spot alt accounts
    """
    jikan = jikanpy.Jikan()
    season_of_year = config.get("season info", "season")
    year = config.getint("season info", "year")

    with session_scope() as session:
        teams = Season.get_season_from_database(season_of_year, year,
                                                session).teams
        for team in cast(Iterable[Team], teams):
            if not team.mal_join_date:
                print(f"Getting join date for {team.name}")
                assert team.name
                try:
                    team.mal_join_date = parse(jikan.user(team.name)["joined"])
                    session.commit()
                    # time.sleep(config.get('jikanpy', 'request-interval'))
                except Exception as e:
                    print(
                        f"Ran into issues figuring out join date with {team.name}: {str(e)}"
                    )
                    continue
def populate_anime_weekly_stats(
    simulcast_lines: Optional[Iterable[str]] = None,
    licenses_lines: Optional[Iterable[str]] = None,
) -> None:
    """
    Populates the AnimeWeeklyStat table with a row for each anime
    using data from Jikan.
    """

    season_of_year = config.get("season info", "season").lower()
    year = config.getint("season info", "year")
    week = config.getint("weekly info", "current-week")

    if is_week_to_calculate("scoring.simulcast",
                            week) and simulcast_lines is None:
        raise ValueError(f"simulcast file is required for week {week}")
    if is_week_to_calculate("scoring.license",
                            week) and licenses_lines is None:
        raise ValueError(f"licenses file is required for week {week}")

    with session_scope() as session:
        anime_simulcast_region_counts = get_anime_simulcast_region_counts(
            simulcast_lines, session)
        licensed_anime = get_licensed_anime(licenses_lines, session)

        season = Season.get_season_from_database(season_of_year, year, session)
        anime_list = cast(Iterable[Anime], season.anime)

        anime_ids_collected = [
            row[0] for row in session.query(AnimeWeeklyStat.anime_id).join(
                Anime).filter(AnimeWeeklyStat.week == week).filter(
                    Anime.season_id == season.id).all()
        ]

        if anime_ids_collected:
            action = input(
                "At least some anime stats have been collected for this week"
                " already. How should we proceed (overwrite/collect-missing/abort)?"
            )
            if action == "collect-missing":
                anime_list = (anime for anime in anime_list
                              if anime.id not in anime_ids_collected)
            elif action == "overwrite":
                pass
            else:
                return

        # for each anime, get the number of teams that have it on active
        anime_active_counts = dict(
            session.query(Anime.id,
                          func.count("*")).join(TeamWeeklyAnime.anime).filter(
                              TeamWeeklyAnime.week == week).filter(
                                  Anime.season_id == season.id).filter(
                                      TeamWeeklyAnime.bench == 0).group_by(
                                          Anime.id).all())
        double_score_max_num_teams = math.floor(
            config.getint("scoring info", "percent-ownership-for-double-score")
            / 100 * len(cast(Sequence[Team], season.teams)))

        # casting until update in sqlalchemy-stubs
        for anime in cast(List[Anime], anime_list):
            print(f"Populating stats for {anime}")
            try:
                stat_data = get_anime_stats_from_jikan(anime)
            except jikanpy.exceptions.APIException as e:
                print(
                    f"Jikan servers did not handle our request very well, skipping: {e}"
                )
                continue

            if (is_week_to_calculate("scoring.simulcast", week)
                    and anime.id not in anime_simulcast_region_counts):
                print(
                    f"{anime.id}-{anime.name} doesn't have an entry in simulcast file"
                )

            stat_data.week = week
            stat_data.anime_id = anime.id
            stat_data.total_points = calculate_anime_weekly_points(
                stat_data,
                anime_active_counts.get(anime.id, 0),
                double_score_max_num_teams,
                anime_simulcast_region_counts.get(anime.id, 0),
                anime.id in licensed_anime,
            )

            anime_weekly_stat = AnimeWeeklyStat()
            for key, value in dataclasses.asdict(stat_data).items():
                setattr(anime_weekly_stat, key, value)

            session.merge(anime_weekly_stat)
            time.sleep(config.getint("jikanpy", "request-interval"))