from dataclasses import dataclass import pandas as pd import streamlit as st from domain.playoffs import PLAYOFF_TEAM_DEF_PLAYER from queries.nflverse.github_data import get_player_kicking_stats, get_player_stats, get_team_defense_stats @dataclass class StatType: key: str score: float def __post_init__(self): STAT_KEY_MAP[self.key] = self STAT_KEY_MAP: dict[str, StatType] = {} RUSH_TD = StatType(key="RUSH TD", score=6.0) REC_TD = StatType(key="REC TD", score=6.0) OFF_FUM_TD = StatType(key="OFF FUM TD", score=6.0) PASS_TD = StatType(key="PASS TD", score=4.0) FG_0_49 = StatType(key="FG 0-49", score=3.0) FG_50_ = StatType(key="FG 50+", score=5.0) TWO_PT = StatType(key="2 PT", score=2.0) RECEPTION = StatType(key="REC", score=1.0) RUSH_YD = StatType(key="RUSH YD", score=0.1) REC_YD = StatType(key="REC YD", score=0.1) PASS_YD = StatType(key="PASS YD", score=0.04) XP = StatType(key="XP", score=1.0) FUM_LOST = StatType(key="FUM LOST", score=-2.0) PASS_INT = StatType(key="PASS INT", score=-2.0) RET_TD = StatType(key="RET TD", score=6.0) DEF_TD = StatType(key="DEF TD", score=6.0) DEF_INT = StatType(key="DEF INT", score=2.0) FUM_REC = StatType(key="FUM REC", score=2.0) SAFETY = StatType(key="SAFETY", score=2.0) SACK = StatType(key="SACK", score=1.0) PTS_ALLOW_0 = StatType(key="PTS ALLOW 0", score=10.0) PTS_ALLOW_1_6 = StatType(key="PTS ALLOW 1-6", score=7.0) PTS_ALLOW_7_13 = StatType(key="PTS ALLOW 7-13", score=4.0) PTS_ALLOW_14_20 = StatType(key="PTS ALLOW 14-20", score=1.0) PTS_ALLOW_21_27 = StatType(key="PTS ALLOW 21-27", score=0.0) PTS_ALLOW_28_34 = StatType(key="PTS ALLOW 28-34", score=-1.0) PTS_ALLOW_35_ = StatType(key="PTS ALLOW 35+", score=-4.0) TEAM_WIN = StatType(key="TEAM WIN", score=5.0) ST_TD = StatType(key="ST TD", score=6.0) NFLVERSE_STAT_COL_TO_ID: dict[str, str] = { "passing_tds": PASS_TD.key, "passing_yards": PASS_YD.key, "passing_2pt_conversions": TWO_PT.key, "sack_fumbles_lost": FUM_LOST.key, "interceptions": PASS_INT.key, "rushing_tds": RUSH_TD.key, "rushing_yards": RUSH_YD.key, "rushing_2pt_conversions": TWO_PT.key, "rushing_fumbles_lost": FUM_LOST.key, "receptions": RECEPTION.key, "receiving_tds": REC_TD.key, "receiving_yards": REC_YD.key, "receiving_2pt_conversions": TWO_PT.key, "receiving_fumbles_lost": FUM_LOST.key, "special_teams_tds": ST_TD.key, "pat_made": XP.key, "fg_made_0_19": FG_0_49.key, "fg_made_20_29": FG_0_49.key, "fg_made_30_39": FG_0_49.key, "fg_made_40_49": FG_0_49.key, "fg_made_50_59": FG_50_.key, "fg_made_60_": FG_50_.key, "def_sacks": SACK.key, "def_interceptions": DEF_INT.key, "def_tds": DEF_TD.key, "def_fumble_recovery_opp": FUM_REC.key, "def_safety": SAFETY.key, } NFLVERSE_STAT_WEEK_TO_PLAYOFF_WEEK = { 19: 1, 20: 2, 21: 3, 22: 4, } def add_stats_from_player_df_to_stat_map(df: pd.DataFrame, stat_map): df_playoffs = df[df.week.isin(NFLVERSE_STAT_WEEK_TO_PLAYOFF_WEEK.keys())] df_playoffs.week = df_playoffs.week.apply(lambda x: NFLVERSE_STAT_WEEK_TO_PLAYOFF_WEEK[x]) for week_player_id_tuple, row in df_playoffs.set_index(["week", "player_id"]).iterrows(): if isinstance(week_player_id_tuple, tuple): week, player_id = week_player_id_tuple else: # this won't happen but makes mypy happy continue player_stats: dict[str, float] = {} for k, v in row.to_dict().items(): if k in NFLVERSE_STAT_COL_TO_ID: if (mapped_k := NFLVERSE_STAT_COL_TO_ID[k]) in player_stats: player_stats[mapped_k] += v else: player_stats[mapped_k] = v if player_id not in stat_map[week]: stat_map[week][player_id] = player_stats else: stat_map[week][player_id].update(player_stats) def add_stats_from_team_def_df_to_stat_map(df: pd.DataFrame, stat_map): short_team_names_to_player_id = {t.rosters_short_name: p for t, p in PLAYOFF_TEAM_DEF_PLAYER} df_playoffs = df[ (df.week.isin(NFLVERSE_STAT_WEEK_TO_PLAYOFF_WEEK.keys()) & df.team.isin(short_team_names_to_player_id.keys())) ] df_playoffs.week = df_playoffs.week.apply(lambda x: NFLVERSE_STAT_WEEK_TO_PLAYOFF_WEEK[x]) for week_team_tuple, row in df_playoffs.set_index(["week", "team"]).iterrows(): if isinstance(week_team_tuple, tuple): week, team = week_team_tuple else: # this won't happen but makes mypy happy continue player_stats: dict[str, float] = {} player_id = short_team_names_to_player_id[team] for k, v in row.to_dict().items(): if k in NFLVERSE_STAT_COL_TO_ID: if (mapped_k := NFLVERSE_STAT_COL_TO_ID[k]) in player_stats: player_stats[mapped_k] += v else: player_stats[mapped_k] = v if player_id not in stat_map[week]: stat_map[week][player_id] = player_stats else: stat_map[week][player_id].update(player_stats) # 24 hour cache @st.cache_data(ttl=60 * 60 * 24) def assemble_nflverse_stats() -> dict[int, dict[str, dict[str, float]]]: # map week -> player_id -> stat_key -> stat value stat_map: dict[int, dict[str, dict[str, float]]] = {w: {} for w in NFLVERSE_STAT_WEEK_TO_PLAYOFF_WEEK.values()} df_player_stats = get_player_stats() df_kicking_stats = get_player_kicking_stats() df_def_stats = get_team_defense_stats() add_stats_from_player_df_to_stat_map(df_player_stats, stat_map) add_stats_from_player_df_to_stat_map(df_kicking_stats, stat_map) add_stats_from_team_def_df_to_stat_map(df_def_stats, stat_map) return stat_map def get_live_stats() -> dict[int, dict[str, dict[str, float]]]: stat_map: dict[int, dict[str, dict[str, float]]] = {w: {} for w in NFLVERSE_STAT_WEEK_TO_PLAYOFF_WEEK.values()} # TODO - implement live stats return stat_map # 10 minute cache @st.cache_data(ttl=60 * 10) def get_stats_map() -> dict[int, dict[str, dict[str, float]]]: # use live stats if available stat_map = get_live_stats() # use more permanent nflverse stats over live nflverse_stats = assemble_nflverse_stats() # we overwrite the live stats with nflverse stats if they exist for the same player for week, week_stats in nflverse_stats.items(): for player_id, player_stats in week_stats.items(): stat_map[week][player_id] = player_stats return stat_map # 10 minute cache @st.cache_data(ttl=60 * 10) def get_scores_map() -> dict[int, dict[str, float]]: scores_map: dict[int, dict[str, float]] = {w: {} for w in NFLVERSE_STAT_WEEK_TO_PLAYOFF_WEEK.values()} stat_map = get_stats_map() for week, week_stats in stat_map.items(): for player_id, player_stats in week_stats.items(): score = 0.0 for stat_key, stat_value in player_stats.items(): stat_type = STAT_KEY_MAP[stat_key] score += stat_type.score * stat_value scores_map[week][player_id] = score return scores_map