Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -31,7 +31,7 @@ colour_palette = ['#FFB000','#648FFF','#785EF0',
|
|
| 31 |
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
|
| 32 |
cmap_sum = mcolors.LinearSegmentedColormap.from_list("", ['#648FFF', '#FFFFFF', '#FFB000'])
|
| 33 |
|
| 34 |
-
year_list = [2025,2024,2023,2022,2021,2020,2019,2018,2017]
|
| 35 |
|
| 36 |
|
| 37 |
type_dict = {'R':'Regular',
|
|
@@ -157,73 +157,11 @@ dict_pitch_alpha = dict(sorted(dict_pitch.items(), key=lambda item: item[1]))
|
|
| 157 |
|
| 158 |
import requests
|
| 159 |
|
| 160 |
-
import os
|
| 161 |
-
CAMPAIGN_ID = os.getenv("CAMPAIGN_ID")
|
| 162 |
-
ACCESS_TOKEN = os.getenv("ACCESS_TOKEN")
|
| 163 |
-
BACKUP_PW = os.getenv("BACKUP_PW")
|
| 164 |
-
ADMIN_PW = os.getenv("ADMIN_PW")
|
| 165 |
-
|
| 166 |
-
url = f"https://www.patreon.com/api/oauth2/v2/campaigns/{CAMPAIGN_ID}/members"
|
| 167 |
-
|
| 168 |
-
headers = {
|
| 169 |
-
"Authorization": f"Bearer {ACCESS_TOKEN}"
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
-
# Simple parameters, requesting the member's email and currently entitled tiers
|
| 173 |
-
params = {
|
| 174 |
-
"fields[member]": "full_name,email", # Request the member's email
|
| 175 |
-
"include": "currently_entitled_tiers", # Include the currently entitled tiers
|
| 176 |
-
"page[size]": 10000 # Fetch up to 1000 patrons per request
|
| 177 |
-
}
|
| 178 |
-
|
| 179 |
-
response = requests.get(url, headers=headers, params=params)
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
VALID_PASSWORDS = []
|
| 183 |
-
if response.status_code == 200:
|
| 184 |
-
data = response.json()
|
| 185 |
-
for patron in data['data']:
|
| 186 |
-
try:
|
| 187 |
-
tiers = patron['relationships']['currently_entitled_tiers']['data']
|
| 188 |
-
if any(tier['id'] == '9078921' for tier in tiers):
|
| 189 |
-
full_name = patron['attributes']['email']
|
| 190 |
-
VALID_PASSWORDS.append(full_name)
|
| 191 |
-
except KeyError:
|
| 192 |
-
continue
|
| 193 |
-
VALID_PASSWORDS.append(BACKUP_PW)
|
| 194 |
-
VALID_PASSWORDS.append(ADMIN_PW)
|
| 195 |
-
|
| 196 |
|
| 197 |
from shiny import App, reactive, ui, render
|
| 198 |
from shiny.ui import h2, tags
|
| 199 |
|
| 200 |
|
| 201 |
-
|
| 202 |
-
# Define the login UI
|
| 203 |
-
login_ui = ui.page_fluid(
|
| 204 |
-
ui.card(
|
| 205 |
-
ui.h2([
|
| 206 |
-
"TJStats Daily Pitching Summary App ",
|
| 207 |
-
ui.tags.a("(@TJStats)", href="https://twitter.com/TJStats", target="_blank")
|
| 208 |
-
]),
|
| 209 |
-
ui.p(
|
| 210 |
-
"This App is available to Superstar Patrons. Please enter your Patreon email address in the box below. If you're having trouble, please refer to the ",
|
| 211 |
-
ui.tags.a("Patreon post", href="https://www.patreon.com/posts/122860440", target="_blank"),
|
| 212 |
-
"."
|
| 213 |
-
),
|
| 214 |
-
ui.input_password("password", "Enter Patreon Email (or Password from Link):", width="25%"),
|
| 215 |
-
ui.tags.input(
|
| 216 |
-
type="checkbox",
|
| 217 |
-
id="authenticated",
|
| 218 |
-
value=False,
|
| 219 |
-
disabled=True
|
| 220 |
-
),
|
| 221 |
-
ui.input_action_button("login", "Login", class_="btn-primary"),
|
| 222 |
-
ui.output_text("login_message"),
|
| 223 |
-
)
|
| 224 |
-
)
|
| 225 |
-
|
| 226 |
-
|
| 227 |
# Define the UI layout for the app
|
| 228 |
main_ui = ui.page_fluid(
|
| 229 |
ui.layout_sidebar(
|
|
@@ -249,7 +187,7 @@ main_ui = ui.page_fluid(
|
|
| 249 |
target="_blank"
|
| 250 |
)))),
|
| 251 |
ui.row(
|
| 252 |
-
ui.column(4, ui.input_select('year_input', 'Select Season', year_list, selected=
|
| 253 |
ui.column(4, ui.input_select('level_input', 'Select Level', level_dict)),
|
| 254 |
ui.column(4, ui.input_select('type_input', 'Select Type', type_dict,selected='R'))
|
| 255 |
),
|
|
@@ -263,6 +201,22 @@ main_ui = ui.page_fluid(
|
|
| 263 |
ui.row(
|
| 264 |
ui.column(6, ui.input_select('split_id', 'Select Split', split_dict, multiple=False)),
|
| 265 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
ui.row( ui.column(6,ui.input_select(
|
| 267 |
"new_pitch_type",
|
| 268 |
"Update Pitch Type",
|
|
@@ -439,14 +393,8 @@ app_ui = ui.page_fluid(
|
|
| 439 |
ui.tags.script(src="script.js")
|
| 440 |
),
|
| 441 |
|
| 442 |
-
ui.panel_conditional(
|
| 443 |
-
"!input.authenticated",
|
| 444 |
-
login_ui
|
| 445 |
-
),
|
| 446 |
-
ui.panel_conditional(
|
| 447 |
-
"input.authenticated",
|
| 448 |
main_ui
|
| 449 |
-
|
| 450 |
)
|
| 451 |
|
| 452 |
|
|
@@ -454,20 +402,6 @@ app_ui = ui.page_fluid(
|
|
| 454 |
def server(input, output, session):
|
| 455 |
# This code should be inserted in your server function
|
| 456 |
|
| 457 |
-
@reactive.Effect
|
| 458 |
-
@reactive.event(input.login)
|
| 459 |
-
def check_password():
|
| 460 |
-
if input.password() in VALID_PASSWORDS:
|
| 461 |
-
ui.update_checkbox("authenticated", value=True)
|
| 462 |
-
ui.update_text("login_message", value="")
|
| 463 |
-
else:
|
| 464 |
-
ui.update_text("login_message", value="Invalid password!")
|
| 465 |
-
ui.update_text("password", value="")
|
| 466 |
-
|
| 467 |
-
@output
|
| 468 |
-
@render.text
|
| 469 |
-
def login_message():
|
| 470 |
-
return ""
|
| 471 |
|
| 472 |
|
| 473 |
|
|
@@ -669,6 +603,19 @@ def server(input, output, session):
|
|
| 669 |
end=f"{int(input.year_input())}-12-31",
|
| 670 |
min=f"{int(input.year_input())}-01-01",
|
| 671 |
max=f"{int(input.year_input())}-12-31")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
@output
|
| 673 |
@render.text
|
| 674 |
def status():
|
|
@@ -954,6 +901,50 @@ def server(input, output, session):
|
|
| 954 |
return fig
|
| 955 |
|
| 956 |
df = df.clone()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 957 |
|
| 958 |
# Create plot
|
| 959 |
p.set(0.6, "Creating plot...")
|
|
@@ -962,7 +953,8 @@ def server(input, output, session):
|
|
| 962 |
pitcher_id=player_input,
|
| 963 |
plot_picker='short_form_movement',
|
| 964 |
sport_id=sport_id,
|
| 965 |
-
game_type=[input.type_input()]
|
|
|
|
| 966 |
)
|
| 967 |
|
| 968 |
|
|
|
|
| 31 |
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
|
| 32 |
cmap_sum = mcolors.LinearSegmentedColormap.from_list("", ['#648FFF', '#FFFFFF', '#FFB000'])
|
| 33 |
|
| 34 |
+
year_list = [2026,2025,2024,2023,2022,2021,2020,2019,2018,2017]
|
| 35 |
|
| 36 |
|
| 37 |
type_dict = {'R':'Regular',
|
|
|
|
| 157 |
|
| 158 |
import requests
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
from shiny import App, reactive, ui, render
|
| 162 |
from shiny.ui import h2, tags
|
| 163 |
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
# Define the UI layout for the app
|
| 166 |
main_ui = ui.page_fluid(
|
| 167 |
ui.layout_sidebar(
|
|
|
|
| 187 |
target="_blank"
|
| 188 |
)))),
|
| 189 |
ui.row(
|
| 190 |
+
ui.column(4, ui.input_select('year_input', 'Select Season', year_list, selected=2026)),
|
| 191 |
ui.column(4, ui.input_select('level_input', 'Select Level', level_dict)),
|
| 192 |
ui.column(4, ui.input_select('type_input', 'Select Type', type_dict,selected='R'))
|
| 193 |
),
|
|
|
|
| 201 |
ui.row(
|
| 202 |
ui.column(6, ui.input_select('split_id', 'Select Split', split_dict, multiple=False)),
|
| 203 |
),
|
| 204 |
+
|
| 205 |
+
# Comparison mode toggle and options
|
| 206 |
+
ui.hr(),
|
| 207 |
+
ui.row(
|
| 208 |
+
ui.column(12, ui.input_switch("compare_mode", "Compare to Another Year", value=False)),
|
| 209 |
+
),
|
| 210 |
+
ui.panel_conditional(
|
| 211 |
+
"input.compare_mode",
|
| 212 |
+
ui.row(
|
| 213 |
+
ui.column(6, ui.input_select('compare_year', 'Compare Year', year_list, selected=2024)),
|
| 214 |
+
ui.column(6, ui.input_select('compare_type', 'Compare Type', type_dict, selected='R')),
|
| 215 |
+
),
|
| 216 |
+
ui.row(ui.column(12, ui.output_ui('compare_date_id', 'Compare Date Range'))),
|
| 217 |
+
),
|
| 218 |
+
ui.hr(),
|
| 219 |
+
|
| 220 |
ui.row( ui.column(6,ui.input_select(
|
| 221 |
"new_pitch_type",
|
| 222 |
"Update Pitch Type",
|
|
|
|
| 393 |
ui.tags.script(src="script.js")
|
| 394 |
),
|
| 395 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
main_ui
|
| 397 |
+
|
| 398 |
)
|
| 399 |
|
| 400 |
|
|
|
|
| 402 |
def server(input, output, session):
|
| 403 |
# This code should be inserted in your server function
|
| 404 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
|
| 406 |
|
| 407 |
|
|
|
|
| 603 |
end=f"{int(input.year_input())}-12-31",
|
| 604 |
min=f"{int(input.year_input())}-01-01",
|
| 605 |
max=f"{int(input.year_input())}-12-31")
|
| 606 |
+
|
| 607 |
+
@render.ui
|
| 608 |
+
@reactive.event(input.compare_year, input.compare_mode, ignore_none=False)
|
| 609 |
+
def compare_date_id():
|
| 610 |
+
# Create a date range input for the comparison year
|
| 611 |
+
if not input.compare_mode():
|
| 612 |
+
return None
|
| 613 |
+
return ui.input_date_range("compare_date_id", "Compare Date Range",
|
| 614 |
+
start=f"{int(input.compare_year())}-01-01",
|
| 615 |
+
end=f"{int(input.compare_year())}-12-31",
|
| 616 |
+
min=f"{int(input.compare_year())}-01-01",
|
| 617 |
+
max=f"{int(input.compare_year())}-12-31")
|
| 618 |
+
|
| 619 |
@output
|
| 620 |
@render.text
|
| 621 |
def status():
|
|
|
|
| 901 |
return fig
|
| 902 |
|
| 903 |
df = df.clone()
|
| 904 |
+
|
| 905 |
+
# Fetch comparison data if compare_mode is enabled
|
| 906 |
+
compare_df = None
|
| 907 |
+
if input.compare_mode():
|
| 908 |
+
try:
|
| 909 |
+
p.set(0.5, "Fetching comparison data...")
|
| 910 |
+
compare_year = int(input.compare_year())
|
| 911 |
+
compare_game_type = [input.compare_type()]
|
| 912 |
+
compare_start = str(input.compare_date_id()[0])
|
| 913 |
+
compare_end = str(input.compare_date_id()[1])
|
| 914 |
+
|
| 915 |
+
compare_game_list = scrape.get_player_games_list(
|
| 916 |
+
sport_id=sport_id,
|
| 917 |
+
season=compare_year,
|
| 918 |
+
player_id=player_input,
|
| 919 |
+
start_date=compare_start,
|
| 920 |
+
end_date=compare_end,
|
| 921 |
+
game_type=compare_game_type
|
| 922 |
+
)
|
| 923 |
+
|
| 924 |
+
compare_data_list = scrape.get_data(game_list_input=compare_game_list[:])
|
| 925 |
+
|
| 926 |
+
compare_df = (stuff_apply.stuff_apply(
|
| 927 |
+
fe.feature_engineering(
|
| 928 |
+
update.update(
|
| 929 |
+
scrape.get_data_df(data_list=compare_data_list).filter(
|
| 930 |
+
(pl.col("pitcher_id") == player_input) &
|
| 931 |
+
(pl.col("is_pitch") == True) &
|
| 932 |
+
(pl.col("start_speed") >= 50) &
|
| 933 |
+
(pl.col('batter_hand').is_in(split_dict_hand[input.split_id()]))
|
| 934 |
+
)[:]
|
| 935 |
+
)
|
| 936 |
+
)
|
| 937 |
+
)).with_columns(
|
| 938 |
+
pl.col('pitch_type').count().over('pitch_type').alias('pitch_count')
|
| 939 |
+
)
|
| 940 |
+
|
| 941 |
+
compare_df = compare_df.with_columns(
|
| 942 |
+
prop_percent=(pl.col('is_pitch') / pl.col('is_pitch').sum()).over("pitch_type"),
|
| 943 |
+
prop=pl.col('is_pitch').sum().over("pitch_type")
|
| 944 |
+
)
|
| 945 |
+
except Exception as e:
|
| 946 |
+
print(f"Error fetching comparison data: {e}")
|
| 947 |
+
compare_df = None
|
| 948 |
|
| 949 |
# Create plot
|
| 950 |
p.set(0.6, "Creating plot...")
|
|
|
|
| 953 |
pitcher_id=player_input,
|
| 954 |
plot_picker='short_form_movement',
|
| 955 |
sport_id=sport_id,
|
| 956 |
+
game_type=[input.type_input()],
|
| 957 |
+
compare_df=compare_df
|
| 958 |
)
|
| 959 |
|
| 960 |
|