Skip to content

Commit

Permalink
Total FE Overhaul (#4)
Browse files Browse the repository at this point in the history
* table don't work great

* streamlit addition

* changes to deploy streamlit

* remove matchup overview

* bunch of minor FE updates
  • Loading branch information
NathanEmb authored Dec 7, 2024
1 parent 6664089 commit 5abcc77
Show file tree
Hide file tree
Showing 12 changed files with 644 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"healthCheck": {
"command": [
"CMD-SHELL",
"curl -f 0.0.0.0:5006/liveness || exit 1"
"curl -f 0.0.0.0:5006/healthz || exit 1"
],
"interval": 30,
"timeout": 5,
Expand Down
3 changes: 2 additions & 1 deletion dockerignore → .dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.venv/*
.github/*
.vscode/*
.vscode/*
.aws/*
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ env:
ECR_REPOSITORY: spacejamprod/server # set this to your Amazon ECR repository name
ECS_SERVICE: spacejam # set this to your Amazon ECS service name
ECS_CLUSTER: SpacejamProd # set this to your Amazon ECS cluster name
ECS_TASK_DEFINITION: aws/spacejam-server-cli-input.json # set this to the path to your Amazon ECS task definition
ECS_TASK_DEFINITION: .aws/spacejam-server-cli-input.json # set this to the path to your Amazon ECS task definition
# file, e.g. .aws/task-definition.json
CONTAINER_NAME: spacejam-server # set this to the name of the container in the

Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ WORKDIR /app

ADD . /app

ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONPATH=.

RUN uv sync --frozen

CMD ["uv", "run", "panel", "serve", "src/frontend.py", "--address", "0.0.0.0", "--port", "5006", "--allow-websocket-origin=18.222.161.54:5006", "--liveness" ]
CMD ["uv", "run", "streamlit", "run", "src/streamlit_frontend.py", "--server.port", "5006" ]
16 changes: 16 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,20 @@ dependencies = [
"espn-api>=0.43.0",
"pandas>=2.2.3",
"panel>=1.5.4",
"streamlit-autorefresh>=1.0.1",
"streamlit>=1.40.2",
"watchfiles>=1.0.0",
"matplotlib>=3.9.3",
]

[tool.ruff]
line-length = 100
indent-width = 4

[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I"]
ignore = []

[tool.ruff.format]
quote-style = "double"
skip-magic-trailing-comma = true
Empty file added src/__init__.py
Empty file.
50 changes: 24 additions & 26 deletions src/backend.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import pandas as pd
from copy import deepcopy

from espn_api.basketball import League
import pandas as pd
from espn_api.basketball import League, Team

import constants as const
import src.constants as const


def get_league(league_id: int = const.SPACEJAM_LEAGUE_ID, year: int = const.YEAR):
return League(league_id, year)
league = League(league_id, year)
league.teams = {team.team_name: team for team in league.teams}
return league


def get_league_all_raw_stats_df(league: League):
league_stats = []
for team in league.teams:
temp_dict = team.stats.copy()
for team in league.teams.values():
temp_dict = deepcopy(team.stats)
temp_dict["Team"] = team.team_name
temp_dict["Standing"] = team.standing
league_stats.append(temp_dict)

df = pd.DataFrame(league_stats)

df = df.astype(const.ALL_RAW_DATA_TABLE_DEF)
return df[list(const.ALL_RAW_DATA_TABLE_DEF.keys())]
return df[list(const.ALL_RAW_DATA_TABLE_DEF.keys())].sort_values(by="Standing")


def get_league_all_raw_data_rankings(league: League):
Expand All @@ -34,29 +37,23 @@ def get_league_all_raw_data_rankings(league: League):
ranked_df["Avg. Cat. Rank"] = ranked_df.mean(axis=1)

# Concatenate with non-numeric columns
ranked_df = pd.concat(
[
ranked_df,
raw_stats_df.select_dtypes(exclude="number"),
],
axis=1,
)
ranked_df = pd.concat([ranked_df, raw_stats_df.select_dtypes(exclude="number")], axis=1)

return ranked_df[list(const.ALL_DATA_RANKED_TABLE_DEF.keys())]
return ranked_df[list(const.ALL_DATA_RANKED_TABLE_DEF.keys())].sort_values(by="Standing")


def get_league_cat_raw_stats_df(league: League):
league_stats = []
for team in league.teams:
temp_dict = team.stats.copy()
for team in league.teams.values():
temp_dict = deepcopy(team.stats)
temp_dict["Team"] = team.team_name
temp_dict["Standing"] = team.standing
league_stats.append(temp_dict)

df = pd.DataFrame(league_stats)

df = df.astype(const.CAT_ONLY_RAW_DATA_TABLE_DEF)
return df[list(const.CAT_ONLY_RAW_DATA_TABLE_DEF.keys())]
return df[list(const.CAT_ONLY_RAW_DATA_TABLE_DEF.keys())].sort_values(by="Standing")


def get_league_cat_data_rankings(league: League):
Expand All @@ -70,14 +67,15 @@ def get_league_cat_data_rankings(league: League):
ranked_df["Avg. Cat. Rank"] = ranked_df.mean(axis=1)

# Concatenate with non-numeric columns
ranked_df = pd.concat(
[
ranked_df,
raw_stats_df.select_dtypes(exclude="number"),
],
axis=1,
)
return ranked_df[list(const.CAT_ONLY_DATA_RANKED_TABLE_DEF.keys())]
ranked_df = pd.concat([ranked_df, raw_stats_df.select_dtypes(exclude="number")], axis=1)
return ranked_df[list(const.CAT_ONLY_DATA_RANKED_TABLE_DEF.keys())].sort_values(by="Standing")


def get_average_team_stats(team: Team, num_days: int):
"""Get Stats for team averaged over specified number of days From todays date."""
stat_key = f"{const.YEAR}_last_{num_days}"
player_avgs = {player.name: player.stats[stat_key].get("avg", {}) for player in team.roster}
return player_avgs


if __name__ == "__main__":
Expand Down
19 changes: 5 additions & 14 deletions src/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
SPACEJAM_LEAGUE_ID = 233677
YEAR = 2025

NINE_CATS = ["PTS", "BLK", "STL", "AST", "REB", "TO", "3PM", "FG%", "FT%"]

ALL_RAW_DATA_TABLE_DEF = {
"Team": str,
"Standing": int,
Expand Down Expand Up @@ -67,19 +69,8 @@
"FT%": int,
}

WANT_BIG_NUM = [
"PTS",
"BLK",
"STL",
"AST",
"REB",
"FGM",
"FGA",
"FTM",
"FTA",
"3PM",
"FG%",
"FT%",
]
WANT_BIG_NUM = ["PTS", "BLK", "STL", "AST", "REB", "FGM", "FGA", "FTM", "FTA", "3PM", "FG%", "FT%"]

WANT_BIG_NUM_CATS = ["PTS", "BLK", "STL", "AST", "REB", "3PM", "FG%", "FT%"]

WANT_SMALL_NUM = ["Standing", "TO"]
43 changes: 0 additions & 43 deletions src/frontend.py

This file was deleted.

62 changes: 62 additions & 0 deletions src/panel_frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import datetime as dt
import logging

import panel as pn

import backend as be

pn.extension("tabulator")

logger = logging.getLogger()


class LeagueData:
def __init__(self):
self.data = be.get_league()
self.last_updated = dt.datetime.now()

def update(self):
self.data = be.get_league()
self.last_updated = dt.datetime.now()


def get_dataframe(mode: str, league_data: LeagueData):
logger.info(f"Returning League Wide stats Dataframe for {league_data.data.league_id}")
if mode == "All Data - Raw Stats":
df = be.get_league_all_raw_stats_df(league_data.data)
elif mode == "All Data - Ranked":
df = be.get_league_all_raw_data_rankings(league_data.data)
elif mode == "Categories - Raw Stats":
df = be.get_league_cat_raw_stats_df(league_data.data)
elif mode == "Categories - Rankings":
df = be.get_league_cat_data_rankings(league_data.data)
return df


def app():
league_data = LeagueData()

mode = pn.widgets.Select(
name="Mode",
options=[
"Categories - Rankings",
"Categories - Raw Stats",
"All Data - Ranked",
"All Data - Raw Stats",
],
)
refresh_button = pn.widgets.Button(
name="Refresh Stats"
) # doesn't work. my interactive table is f-d beyond comprehension

interactive_df = pn.bind(get_dataframe, mode=mode, league_data=league_data)
interactive_table = pn.widgets.Tabulator(interactive_df, theme="site", show_index=False)
return pn.template.ReactTemplate(
title="Space Jam Dashboard",
sidebar=[refresh_button, mode],
main=[interactive_table],
sidebar_width=150,
)


app().servable()
69 changes: 69 additions & 0 deletions src/streamlit_frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import matplotlib.pyplot as plt
import pandas as pd
import streamlit as st
from streamlit_autorefresh import st_autorefresh

import src.backend as be
import src.constants as const

ryg = plt.colormaps["RdYlGn"]
gyr = plt.colormaps["RdYlGn"].reversed()
icon_url = "https://spacejam-dashboard.s3.us-east-2.amazonaws.com/assets/the-last-spacejam.jpg"
st.set_page_config(layout="wide", page_title="Spacejam Dashboard", page_icon=icon_url)
refresh_in_sec = 60
count = st_autorefresh(interval=refresh_in_sec * 1000, limit=100, key="statscounter")


@st.cache_data
def update_league_data():
return be.get_league()


league_data = update_league_data()

teams = [team.team_name for team in league_data.teams.values()]


matchup_df = be.get_league_cat_raw_stats_df(league_data)
team_stats_df = be.get_league_all_raw_data_rankings(league_data)


# Sidebar for page selection
st.sidebar.title("Navigation")
st.sidebar.markdown("# About")
st.sidebar.markdown("Welcome to the Space Jam Dashboard!")
st.sidebar.markdown(
"This is my attempt at democratizing data and having fun coding. Let me know what you think (as long as it is good if you think it sucks I hate you.)"
)
page = st.sidebar.radio(
"Select a Page", ("League Overview", "Team Overview")
) # , "Matchup Overview"))

# League Overview Page
if page == "League Overview":
league_df = be.get_league_cat_data_rankings(league_data)
league_df_styled = league_df.style.background_gradient(cmap=gyr)
st.title("Spacejam League Overview")
st.dataframe(league_df_styled, use_container_width=True, hide_index=True, height=460)
st.markdown(
"As you can imagine, green means that this team is good in that category, and red means the team is bad in that category."
)

# Team Overview Page
elif page == "Team Overview":
chosen_team = st.sidebar.selectbox("Team", teams)
team_data = league_data.teams[chosen_team]
st.title(f"Overview - {chosen_team}")
st.write("Stats for", chosen_team)
timeframe = st.radio(label="Time:", options=["30 Days", "15 Days", "7 Days"], horizontal=True)
timeframe_num = int(timeframe.split(" ")[0])
team_avg_stats = be.get_average_team_stats(team_data, timeframe_num)
df = pd.DataFrame(team_avg_stats).T.fillna(0)
show_cols = st.multiselect("Column Filter", options=df.columns, default=const.NINE_CATS)
st.dataframe(df[show_cols], use_container_width=True, height=460)

# Matchup Overview Page
elif page == "Matchup Overview":
st.title(f"Matchup Overview - {chosen_team}")
st.write("Matchup Stats for", chosen_team)
st.table(matchup_df.loc[matchup_df["Team"] == chosen_team])
Loading

0 comments on commit 5abcc77

Please sign in to comment.