JonSolow commited on
Commit
0ed8b3a
·
unverified ·
1 Parent(s): 09fa8fe

Draft page (#19)

Browse files

* Add blank draft page

* Fix uneeded f-string

* Load yahoo_con into session

* Enable draft view with 2023 and 2024 for testing

* Apply black

* Load league settings

* Implement selection of year and refresh button

* Implement view of roster positions with caching of league settings

* Move ECR to another file to share

* Add export of fp tiers csv

* Get ecr data loaded and merged withd draft results

* Sort into columns by pos

* Remove unused fp ecr

* Split into tiers

* Filter out some positions

* Put filters in expander

* Format red if drafted

src/config.py CHANGED
@@ -3,3 +3,4 @@ DEFAULT_ICON = "🏉"
3
  LEAGUE_NUMBER_TEAMS = 12
4
 
5
  KEEPER_DATA_URL = "../../tests/mocks/2024_keepers.csv"
 
 
3
  LEAGUE_NUMBER_TEAMS = 12
4
 
5
  KEEPER_DATA_URL = "../../tests/mocks/2024_keepers.csv"
6
+ SEASON = 2024
src/ecr.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ import streamlit as st
4
+
5
+ from streamlit_filter import filter_dataframe
6
+
7
+
8
+ def load_adp() -> pd.DataFrame:
9
+ df = pd.read_csv(r"https://raw.githubusercontent.com/dynastyprocess/data/master/files/db_fpecr_latest.csv")
10
+ df["ranking_type"] = df["fp_page"].apply(lambda x: os.path.split(x)[-1].replace(".php", ""))
11
+ return df
12
+
13
+
14
+ @st.cache_data(ttl=60 * 60 * 24)
15
+ def load_adp_data():
16
+ # Merge ADP
17
+ data = load_adp()
18
+ ranking_type_list = sorted(list(data.ranking_type.unique()))
19
+ return data, ranking_type_list
20
+
21
+
22
+ def filtered_ecr_dataframe(data: pd.DataFrame, ranking_type_list: list[str]):
23
+ default_ix = ranking_type_list.index("ppr-superflex-cheatsheets")
24
+ ranking_type_selected = st.selectbox("ECR Format:", ranking_type_list, index=default_ix)
25
+ ranking_type_filter = data["ranking_type"] == ranking_type_selected
26
+
27
+ is_advanced = st.checkbox("Show Advanced View")
28
+
29
+ id_cols = [
30
+ # "player_square_image_url",
31
+ "player",
32
+ "pos",
33
+ "team",
34
+ ]
35
+
36
+ id_cols_advanced = [
37
+ "bye",
38
+ "player_owned_yahoo",
39
+ ]
40
+
41
+ adp_cols: list[str] = [
42
+ "ecr",
43
+ ]
44
+
45
+ adp_cols_advanced = ["sd", "best", "worst"]
46
+
47
+ if is_advanced:
48
+ show_columns = id_cols + id_cols_advanced + adp_cols + adp_cols_advanced
49
+ else:
50
+ show_columns = id_cols + adp_cols
51
+
52
+ data_filtered_by_ranking_type = data.loc[ranking_type_filter]
53
+ latest_scrape_date = data_filtered_by_ranking_type.scrape_date.max()
54
+ st.write(f"Scraped data as of: {latest_scrape_date}")
55
+
56
+ filtered_data = filter_dataframe(data.loc[ranking_type_filter, show_columns])
57
+ st.dataframe(
58
+ filtered_data,
59
+ hide_index=True,
60
+ height=35 * (len(filtered_data) + 1) + 12,
61
+ use_container_width=True,
62
+ column_config={
63
+ # "player_square_image_url": st.column_config.ImageColumn(label="", help="Player image"),
64
+ },
65
+ )
66
+
67
+ st.write("Source: https://github.com/dynastyprocess/data")
src/login_component.py CHANGED
@@ -41,6 +41,7 @@ def get_authorization_button():
41
  st.session_state.token = result.get("token")
42
  yahoo_con = YahooFantasyClient(oauth2, st.session_state.token)
43
  st.session_state.logged_in_guid = yahoo_con.get_guid_for_logged_in_user()
 
44
  st.rerun()
45
  else:
46
  # # If token exists in session state, allow logout
@@ -48,6 +49,7 @@ def get_authorization_button():
48
  if st.button("Logout"):
49
  del st.session_state.token
50
  del st.session_state.logged_in_guid
 
51
  st.rerun()
52
  # # If token exists in session state, show the token
53
  # token = st.session_state["token"]
 
41
  st.session_state.token = result.get("token")
42
  yahoo_con = YahooFantasyClient(oauth2, st.session_state.token)
43
  st.session_state.logged_in_guid = yahoo_con.get_guid_for_logged_in_user()
44
+ st.session_state.yahoo_client = yahoo_con
45
  st.rerun()
46
  else:
47
  # # If token exists in session state, allow logout
 
49
  if st.button("Logout"):
50
  del st.session_state.token
51
  del st.session_state.logged_in_guid
52
+ del st.session_state.yahoo_client
53
  st.rerun()
54
  # # If token exists in session state, show the token
55
  # token = st.session_state["token"]
src/pages/3_Draft_View.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import pandas as pd
3
+ import requests
4
+ import streamlit as st
5
+
6
+ from config import DEFAULT_ICON, SEASON
7
+ from login_component import is_token_in_session
8
+ from shared_page import common_page_config
9
+ from streamlit_filter import filter_dataframe
10
+
11
+
12
+ @st.cache_data(ttl=60 * 60 * 24)
13
+ def load_yahoo_to_fp_id_map() -> dict[str, str]:
14
+ df = pd.read_csv(r"https://raw.githubusercontent.com/dynastyprocess/data/master/files/db_playerids.csv")
15
+ for id_col in ["yahoo_id", "fantasypros_id"]:
16
+ df[id_col] = df[id_col].fillna(-9999).apply(lambda x: str(int(x)))
17
+ return df.set_index("yahoo_id")["fantasypros_id"].to_dict()
18
+
19
+
20
+ def extract_ecr_var_data(request_text_str, var_name: str):
21
+ start_str = f"""var {var_name} = """
22
+ end_str = """};"""
23
+ end_offset = 1 # include closing bracket
24
+
25
+ start_slice_pos = request_text_str.find(start_str) + len(start_str)
26
+ first_slice = request_text_str[start_slice_pos:]
27
+ end_slice_pos = first_slice.find(end_str) + end_offset
28
+ dom_str = first_slice[:end_slice_pos]
29
+ var_json = json.loads(dom_str)
30
+ return var_json
31
+
32
+
33
+ @st.cache_data(ttl=60 * 60 * 24)
34
+ def load_ecr_data():
35
+ request_text = get_ecr_request_text()
36
+ ecr_data = extract_ecr_var_data(request_text, "ecrData")
37
+ ecr_columns = [
38
+ "player_id",
39
+ "player_name",
40
+ "player_position_id",
41
+ "player_bye_week",
42
+ "rank_ecr",
43
+ "pos_rank",
44
+ "tier",
45
+ ]
46
+ df_ecr = pd.DataFrame(ecr_data["players"])[ecr_columns]
47
+ df_ecr["player_id"] = df_ecr["player_id"].apply(lambda x: str(int(x)))
48
+
49
+ # sos_data = extract_ecr_var_data(request_text, "sosData")
50
+ # adp_data = extract_ecr_var_data(request_text, "adpData")
51
+ return df_ecr
52
+
53
+
54
+ @st.cache_data(ttl=60 * 60 * 24)
55
+ def get_ecr_request_text():
56
+ r = requests.get("https://www.fantasypros.com/nfl/rankings/half-point-ppr-cheatsheets.php")
57
+ return r.text
58
+
59
+
60
+ @st.cache_resource(ttl=60 * 60 * 24)
61
+ def get_league_settings_with_cache(selected_league):
62
+ return st.session_state.yahoo_client.parse_league_settings(selected_league)
63
+
64
+
65
+ def highlight_drafted(data_row_series):
66
+ return ["background-color: red" if data_row_series.is_drafted else "" for _ in range(len(data_row_series))]
67
+
68
+
69
+ def display_formatted_tiers(df):
70
+ st.dataframe(
71
+ df.style.apply(highlight_drafted, axis=1),
72
+ hide_index=True,
73
+ height=35 * (len(df) + 1) + 5,
74
+ column_order=[
75
+ "rank_ecr",
76
+ "player_name",
77
+ # "player_position_id",
78
+ "player_bye_week",
79
+ # "tier",
80
+ # "is_drafted",
81
+ ],
82
+ column_config={
83
+ "player_name": st.column_config.TextColumn(label="Name", help="Player's name"),
84
+ "player_position_id": st.column_config.TextColumn(label="Position", help="Player's position"),
85
+ "player_bye_week": st.column_config.NumberColumn(label="Bye", help="Player's Bye Week"),
86
+ "rank_ecr": st.column_config.NumberColumn(label="Rank", help="Player ECR Rank"),
87
+ "tier": st.column_config.NumberColumn(label="Tier", help="Player Tier"),
88
+ "is_drafted": st.column_config.CheckboxColumn(label="Drafted", help="Has been drafted"),
89
+ },
90
+ )
91
+
92
+
93
+ def get_page():
94
+ page_title = "Yahoo Draft Live Summary"
95
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
96
+ common_page_config()
97
+ st.title(page_title)
98
+
99
+ if not is_token_in_session():
100
+ st.write(
101
+ "You must authorize the application to access your account in order to use this feature."
102
+ " Please click Login button above."
103
+ )
104
+
105
+ else:
106
+ selected_season = st.selectbox("Select Season", list(range(SEASON, 2012, -1)))
107
+ user_leagues = st.session_state.yahoo_client.find_all_leagues_for_logged_in_user(season=selected_season)
108
+ selected_league = st.selectbox("Select league", user_leagues)
109
+ league_settings = get_league_settings_with_cache(selected_league)
110
+ st.header(f"{league_settings.name} - {league_settings.season}")
111
+ with st.expander("Show Positions"):
112
+ st.dataframe(
113
+ pd.DataFrame(league_settings.roster_positions).set_index("position")["count"],
114
+ )
115
+
116
+ draft_result = pd.DataFrame(st.session_state.yahoo_client.get_draft(selected_league))
117
+ if st.button("Load / Refresh"):
118
+ draft_result = pd.DataFrame(st.session_state.yahoo_client.get_draft(selected_league))
119
+ if "player_key" in draft_result:
120
+ draft_result["player_id"] = draft_result["player_key"].apply(lambda x: x.rsplit(".", 1)[-1] if x else x)
121
+ else:
122
+ draft_result["player_id"] = ""
123
+ draft_result["fp_id"] = draft_result["player_id"].apply(lambda x: load_yahoo_to_fp_id_map().get(x))
124
+
125
+ if "team_key" not in draft_result:
126
+ draft_result["team_key"] = ""
127
+
128
+ with st.expander("Show Draft Results"):
129
+ st.dataframe(draft_result)
130
+
131
+ st.header("ECR Tiers")
132
+ ecr_data = load_ecr_data()
133
+ draft_result_merge_cols = [
134
+ "fp_id",
135
+ "team_key",
136
+ ]
137
+ ecr_with_draft = ecr_data.merge(
138
+ draft_result[draft_result_merge_cols], how="left", left_on="player_id", right_on="fp_id"
139
+ )
140
+ ecr_with_draft["is_drafted"] = ecr_with_draft["fp_id"].notna()
141
+ with st.expander("Filters"):
142
+ filtered_data = filter_dataframe(
143
+ ecr_with_draft, force_on=True, force_on_columns=["is_drafted", "player_position_id"]
144
+ )
145
+
146
+ position_list = [
147
+ x for x in ["QB", "RB", "WR", "TE", "DST", "K"] if x in filtered_data.player_position_id.unique()
148
+ ]
149
+ columns_list = st.columns(len(position_list))
150
+ for pos, col in zip(position_list, columns_list):
151
+ with col:
152
+ st.header(pos)
153
+ df_pos = filtered_data[filtered_data.player_position_id == pos]
154
+ for tier, df_tier in df_pos.groupby("tier"):
155
+ st.header(f"Tier {tier}")
156
+ display_formatted_tiers(df_tier)
157
+
158
+
159
+ if __name__ == "__main__":
160
+ get_page()
src/pages/3_ECR.py CHANGED
@@ -1,76 +1,8 @@
1
- import os
2
- import pandas as pd
3
  import streamlit as st
4
 
5
  from config import DEFAULT_ICON
6
  from shared_page import common_page_config
7
- from streamlit_filter import filter_dataframe
8
-
9
-
10
- KEEPER_DATA_URL = "../../tests/mocks/2023_keepers.csv"
11
- HEADSHOT_DATA_URL = "../../tests/mocks/2023_player_headshots.csv"
12
-
13
-
14
- def load_adp() -> pd.DataFrame:
15
- df = pd.read_csv(r"https://raw.githubusercontent.com/dynastyprocess/data/master/files/db_fpecr_latest.csv")
16
- df["ranking_type"] = df["fp_page"].apply(lambda x: os.path.split(x)[-1].replace(".php", ""))
17
- return df
18
-
19
-
20
- @st.cache_data(ttl=60 * 60 * 24)
21
- def load_data():
22
- # Merge ADP
23
- data = load_adp()
24
- ranking_type_list = sorted(list(data.ranking_type.unique()))
25
- return data, ranking_type_list
26
-
27
-
28
- def filtered_ecr_dataframe(data: pd.DataFrame, ranking_type_list: list[str]):
29
- default_ix = ranking_type_list.index("ppr-superflex-cheatsheets")
30
- ranking_type_selected = st.selectbox("ECR Format:", ranking_type_list, index=default_ix)
31
- ranking_type_filter = data["ranking_type"] == ranking_type_selected
32
-
33
- is_advanced = st.checkbox("Show Advanced View")
34
-
35
- id_cols = [
36
- # "player_square_image_url",
37
- "player",
38
- "pos",
39
- "team",
40
- ]
41
-
42
- id_cols_advanced = [
43
- "bye",
44
- "player_owned_yahoo",
45
- ]
46
-
47
- adp_cols: list[str] = [
48
- "ecr",
49
- ]
50
-
51
- adp_cols_advanced = ["sd", "best", "worst"]
52
-
53
- if is_advanced:
54
- show_columns = id_cols + id_cols_advanced + adp_cols + adp_cols_advanced
55
- else:
56
- show_columns = id_cols + adp_cols
57
-
58
- data_filtered_by_ranking_type = data.loc[ranking_type_filter]
59
- latest_scrape_date = data_filtered_by_ranking_type.scrape_date.max()
60
- st.write(f"Scraped data as of: {latest_scrape_date}")
61
-
62
- filtered_data = filter_dataframe(data.loc[ranking_type_filter, show_columns])
63
- st.dataframe(
64
- filtered_data,
65
- hide_index=True,
66
- height=35 * (len(filtered_data) + 1) + 12,
67
- use_container_width=True,
68
- column_config={
69
- # "player_square_image_url": st.column_config.ImageColumn(label="", help="Player image"),
70
- },
71
- )
72
-
73
- st.write("Source: https://github.com/dynastyprocess/data")
74
 
75
 
76
  def get_keeper_app():
@@ -78,7 +10,7 @@ def get_keeper_app():
78
  st.set_page_config(page_title=keeper_title, page_icon=DEFAULT_ICON, layout="wide")
79
  common_page_config()
80
  st.title(keeper_title)
81
- data, ecr_type_list = load_data()
82
 
83
  with st.container():
84
  filtered_ecr_dataframe(data, ecr_type_list)
 
 
 
1
  import streamlit as st
2
 
3
  from config import DEFAULT_ICON
4
  from shared_page import common_page_config
5
+ from ecr import load_adp_data, filtered_ecr_dataframe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
 
8
  def get_keeper_app():
 
10
  st.set_page_config(page_title=keeper_title, page_icon=DEFAULT_ICON, layout="wide")
11
  common_page_config()
12
  st.title(keeper_title)
13
+ data, ecr_type_list = load_adp_data()
14
 
15
  with st.container():
16
  filtered_ecr_dataframe(data, ecr_type_list)
src/yahoo_client.py CHANGED
@@ -2,7 +2,7 @@ import asyncio
2
  import pandas as pd
3
  from pydantic import BaseModel
4
  import re
5
- from typing import List, Mapping, Tuple, Union
6
  import xml.etree.ElementTree as ET
7
  from streamlit_oauth import OAuth2Component
8
 
@@ -17,6 +17,7 @@ class LeagueSettings(BaseModel):
17
  playoff_start_week: int
18
  num_playoff_teams: int
19
  num_playoff_consolation_teams: int
 
20
 
21
  @classmethod
22
  def from_xml(cls, xml_settings: ET.Element) -> "LeagueSettings":
@@ -37,6 +38,19 @@ class LeagueSettings(BaseModel):
37
  ]
38
  settings_fields_dict = {f: xml_settings.findtext(f"./league/settings/{f}") for f in settings_fields_list}
39
  league_settings_dict = {**base_fields_dict, **settings_fields_dict}
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  return cls(
41
  league_key=league_settings_dict["league_key"] or "",
42
  league_id=league_settings_dict["league_id"] or "",
@@ -47,6 +61,7 @@ class LeagueSettings(BaseModel):
47
  playoff_start_week=int(league_settings_dict["playoff_start_week"] or 0),
48
  num_playoff_teams=int(league_settings_dict["num_playoff_teams"] or 0),
49
  num_playoff_consolation_teams=int(league_settings_dict["num_playoff_consolation_teams"] or 0),
 
50
  )
51
 
52
 
@@ -73,8 +88,14 @@ class YahooFantasyClient:
73
  root = ET.fromstring(xmlstring)
74
  return root, xmlstring
75
 
76
- def find_all_leagues_for_logged_in_user(self) -> List[str]:
77
- url = "https://fantasysports.yahooapis.com/fantasy/v2/users;use_login=1/games;game_keys=nfl/leagues"
 
 
 
 
 
 
78
  root, _ = self.yahoo_request_to_xml(url)
79
  league_keys = list(
80
  filter(None, [x.text for x in root.findall("./users/user/games/game/leagues/league/league_key")])
@@ -97,6 +118,20 @@ class YahooFantasyClient:
97
  parsed_league_settings = LeagueSettings.from_xml(league_settings)
98
  return parsed_league_settings
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  def parse_weeks_matchups(self, week: str, league_key: str) -> List[Mapping[str, Union[str, float]]]:
101
  url = f"https://fantasysports.yahooapis.com/fantasy/v2/leagues;league_keys={league_key}/scoreboard;week={week}"
102
  week_scoreboard, _ = self.yahoo_request_to_xml(url)
 
2
  import pandas as pd
3
  from pydantic import BaseModel
4
  import re
5
+ from typing import List, Mapping, Tuple, Union, Optional
6
  import xml.etree.ElementTree as ET
7
  from streamlit_oauth import OAuth2Component
8
 
 
17
  playoff_start_week: int
18
  num_playoff_teams: int
19
  num_playoff_consolation_teams: int
20
+ roster_positions: list[dict]
21
 
22
  @classmethod
23
  def from_xml(cls, xml_settings: ET.Element) -> "LeagueSettings":
 
38
  ]
39
  settings_fields_dict = {f: xml_settings.findtext(f"./league/settings/{f}") for f in settings_fields_list}
40
  league_settings_dict = {**base_fields_dict, **settings_fields_dict}
41
+
42
+ roster_fields_list = [
43
+ "position",
44
+ "position_type",
45
+ "count",
46
+ "is_starting_position",
47
+ ]
48
+
49
+ roster_positions = [
50
+ {f: x.findtext(f"./{f}") for f in roster_fields_list}
51
+ for x in xml_settings.findall("./league/settings/roster_positions/*")
52
+ ]
53
+
54
  return cls(
55
  league_key=league_settings_dict["league_key"] or "",
56
  league_id=league_settings_dict["league_id"] or "",
 
61
  playoff_start_week=int(league_settings_dict["playoff_start_week"] or 0),
62
  num_playoff_teams=int(league_settings_dict["num_playoff_teams"] or 0),
63
  num_playoff_consolation_teams=int(league_settings_dict["num_playoff_consolation_teams"] or 0),
64
+ roster_positions=roster_positions,
65
  )
66
 
67
 
 
88
  root = ET.fromstring(xmlstring)
89
  return root, xmlstring
90
 
91
+ def find_all_leagues_for_logged_in_user(self, season: Optional[str]) -> List[str]:
92
+ if season:
93
+ season_str = f";seasons={season}"
94
+ else:
95
+ season_str = ""
96
+ url = (
97
+ f"https://fantasysports.yahooapis.com/fantasy/v2/users;use_login=1/games{season_str};game_codes=nfl/leagues"
98
+ )
99
  root, _ = self.yahoo_request_to_xml(url)
100
  league_keys = list(
101
  filter(None, [x.text for x in root.findall("./users/user/games/game/leagues/league/league_key")])
 
118
  parsed_league_settings = LeagueSettings.from_xml(league_settings)
119
  return parsed_league_settings
120
 
121
+ def get_draft(self, league_key: str):
122
+ url = f"https://fantasysports.yahooapis.com/fantasy/v2/league/{league_key}/draftresults"
123
+ draft_results_xml, _ = self.yahoo_request_to_xml(url)
124
+ parsed_draft = [
125
+ {
126
+ "pick": x.findtext("./pick"),
127
+ "round": x.findtext("./round"),
128
+ "team_key": x.findtext("./team_key"),
129
+ "player_key": x.findtext("./player_key"),
130
+ }
131
+ for x in draft_results_xml.findall("./league/draft_results/*")
132
+ ]
133
+ return parsed_draft
134
+
135
  def parse_weeks_matchups(self, week: str, league_key: str) -> List[Mapping[str, Union[str, float]]]:
136
  url = f"https://fantasysports.yahooapis.com/fantasy/v2/leagues;league_keys={league_key}/scoreboard;week={week}"
137
  week_scoreboard, _ = self.yahoo_request_to_xml(url)