|
import pandas as pd |
|
import streamlit as st |
|
import streamlit.components.v1 as components |
|
|
|
|
|
from config import DEFAULT_ICON |
|
from shared_page import common_page_config, get_local_style |
|
from maximum_roster_strategy import data_loader |
|
|
|
|
|
MINIMUM_WEEK = 6 |
|
MAXIMUM_WEEK = 7 |
|
|
|
MIN_TIER = 1 |
|
MAX_TIER = 4 |
|
|
|
POSITION_OPTIONS = ["RB", "WR", "TE", "QB"] |
|
|
|
POSITION_ABBR_FULL_NAME_MAP = { |
|
"RB": "Running Backs", |
|
"WR": "Wide Receivers", |
|
"TE": "Tight Ends", |
|
"QB": "Quarterbacks (Superflex / 2QB Leagues Only)", |
|
} |
|
|
|
|
|
@st.cache_data(ttl=5 * 60) |
|
def load_data(): |
|
return data_loader.get_google_sheet_data(), data_loader.get_timeslot_labels() |
|
|
|
|
|
def get_player_grid_div(player_series: pd.Series) -> str: |
|
player_notes = player_series["Hold Condition"] |
|
if (outcome := player_series["Outcome"]) == "Drop": |
|
player_class = "drop-player" |
|
elif outcome == "Light Hold": |
|
player_class = "light-hold-player" |
|
elif outcome == "Hold": |
|
player_class = "hold-player" |
|
else: |
|
player_class = "undetermined-player" |
|
|
|
if isinstance(player_weekly_note := player_series["Article Notes"], str): |
|
player_notes += "<br><br>" + player_weekly_note |
|
return f""" |
|
<details class="mrs-grid-player content"> |
|
<summary class="{player_class}"> |
|
{player_series["Formatted"]} |
|
</summary> |
|
<p> |
|
{player_notes} |
|
</p> |
|
</details> |
|
""" |
|
|
|
|
|
def get_time_slot_div(time_slot_list: list[str]) -> str: |
|
code_str = "" |
|
for time_slot_idx, time_slot in enumerate(time_slot_list): |
|
code_str += f"""<div class="timeslot{time_slot_idx + 1} timeslot">{time_slot}</div>\n""" |
|
return code_str |
|
|
|
|
|
def get_tier_div(tier_str: str | int, tier_num: str | int) -> str: |
|
return f"""<div class="tier{tier_num} tier">Tier {tier_str}</div>""" |
|
|
|
|
|
def get_player_container(df_players: pd.DataFrame, slot_number: int | str) -> str: |
|
if len(df_players) == 0: |
|
player_code_str = "<br>" |
|
else: |
|
player_code_str = "\n".join(df_players.apply(get_player_grid_div, axis=1).tolist()) |
|
return f"""<div class="playerslot{slot_number} playerslot">{player_code_str}</div>""" |
|
|
|
|
|
def get_position_breakdown(df: pd.DataFrame, position_abbr: str, position_full_str: str, time_slots: list[str]): |
|
with st.container(): |
|
st.header(position_full_str) |
|
df_pos = df[df["Position"] == position_abbr] |
|
|
|
grid_code_str = "" |
|
grid_code_str += get_time_slot_div(time_slots) |
|
|
|
tier_list = list(range(MIN_TIER, MAX_TIER + 1)) |
|
slot_number = 0 |
|
for tier_idx, tier in enumerate(tier_list): |
|
grid_code_str += get_tier_div(tier, tier_idx + 1) |
|
for time_slot in time_slots: |
|
df_tier_slot = df_pos[(df_pos["TimeSlotName"] == time_slot) & (df_pos["Tier"] == tier)] |
|
slot_number += 1 |
|
grid_code_str += get_player_container(df_tier_slot, slot_number) |
|
|
|
components.html( |
|
f""" |
|
{get_local_style()} |
|
<div class="grid-container-{len(time_slots)}"> |
|
{grid_code_str} |
|
</div> |
|
<br> |
|
<div class="grid-legend">Colors Legend: |
|
<div class="drop-player">Drop Player</div> | |
|
<div class="light-hold-player">Light Hold Player</div> | |
|
<div class="hold-player">Strong Hold Player</div> |
|
</div> |
|
""", |
|
height=1000, |
|
scrolling=True, |
|
) |
|
|
|
|
|
def get_page(): |
|
page_title = "Maximum Roster Strategy" |
|
st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide") |
|
common_page_config() |
|
st.title(page_title) |
|
|
|
with st.expander(label="Instructions"): |
|
st.write( |
|
""" |
|
To get started with MRS: https://solowfantasyfootball.wordpress.com/2023/09/07/maximum-roster-strategy-explained/ |
|
|
|
Players are organized by game time slot, position, and tier. |
|
|
|
Pick up a player during their game's time slot for potential upside if particular circumstances are met. |
|
|
|
After the game, players will be colored by outcome: Drop (Red), Light Hold (Yellow), or Strong Hold (Green).""" |
|
) |
|
col_select, week_select = st.columns(2, gap="small") |
|
url_params = st.experimental_get_query_params() |
|
initial_position_index = 0 |
|
if url_position := url_params.get("position"): |
|
selected_position = url_position[0] |
|
if selected_position in POSITION_OPTIONS: |
|
initial_position_index = POSITION_OPTIONS.index(selected_position) |
|
|
|
week_options = list(range(MAXIMUM_WEEK, MINIMUM_WEEK - 1, -1)) |
|
initial_week_index = 0 |
|
if url_week := url_params.get("week"): |
|
try: |
|
selected_week = int(url_week[0]) |
|
except Exception: |
|
st.warning("Week parameter must be integer value", icon="⚠️") |
|
selected_week = MAXIMUM_WEEK |
|
if selected_week in week_options: |
|
initial_week_index = week_options.index(selected_week) |
|
|
|
with col_select: |
|
position = st.selectbox(label="Position", options=POSITION_OPTIONS, index=initial_position_index) |
|
with week_select: |
|
week = st.selectbox(label="Week", options=week_options, index=initial_week_index) |
|
url_params.update({"position": position, "week": week}) |
|
st.experimental_set_query_params(**url_params) |
|
if st.experimental_get_query_params().get("refresh"): |
|
st.cache_data.clear() |
|
df_mrs, all_time_slots_df = load_data() |
|
df_mrs = df_mrs[df_mrs["Week"] == week] |
|
current_week_timeslots = ( |
|
all_time_slots_df[all_time_slots_df["Week"] == week].sort_values("WeekTimeSlotIndex").TimeSlotName.tolist() |
|
) |
|
|
|
get_position_breakdown(df_mrs, position, POSITION_ABBR_FULL_NAME_MAP[position], current_week_timeslots) |
|
|
|
|
|
if __name__ == "__main__": |
|
get_page() |
|
|