import polars as pl import numpy as np import pandas as pd import api_scraper scrape = api_scraper.MLB_Scrape() import requests from shiny import App, reactive, ui, render from shiny.ui import h2, tags from shiny import App, reactive, ui, render from shiny.ui import h2, tags from shiny import App, ui, render # Import the MLB_Scrape class from the module from api_scraper import MLB_Scrape # Initialize the scraper scraper = MLB_Scrape() # Call the get_teams method teams = scraper.get_teams() print(teams) df_player = pl.concat([ scraper.get_players(sport_id=1,season=2025,game_type=['R']), scraper.get_players(sport_id=1,season=2024,game_type=['R']), scraper.get_players(sport_id=1,season=2023,game_type=['R']), scraper.get_players(sport_id=11,season=2025,game_type=['R']), scraper.get_players(sport_id=12,season=2025,game_type=['R']), scraper.get_players(sport_id=13,season=2025,game_type=['R']), scraper.get_players(sport_id=14,season=2025,game_type=['R']), scraper.get_players(sport_id=22,season=2024,game_type=['R']) ]).unique(subset=['player_id']) teams_mlb = teams.filter(pl.col("league_id").is_in([103,104])).sort("abbreviation") teams_dict = dict(zip(teams_mlb['team_id'],teams_mlb['abbreviation'])) teams_name_dict = dict(zip(teams_mlb['team_id'],teams_mlb['franchise'])) app_ui = ui.page_sidebar( ui.sidebar( ui.input_select( "team_id", "Select Team", choices=teams_dict ), ui.input_switch("nri_only", "NRI Only"), ui.div( ui.div({"style": "font-size:1.2em;"}, ui.markdown("Legend")), ui.div( style="display: inline-block; width: 20px; height: 20px; background-color: #b7e1cd; margin-right: 10px;" ), ui.span("NRI", style="vertical-align: top;"), style="padding: 10px;" ), ), ui.card( ui.div({"style": "font-size:2em;"}, ui.output_text("card_title")), ui.div({"style": "font-size:1.2em;"}, ui.markdown("By: [@TJStats](https://x.com/TJStats), Data: MLB")), ui.output_table("team_stats") ) ) def server(input, output, session): @render.text def card_title(): if input.nri_only(): return f"{teams_name_dict[int(input.team_id())]} — Spring Training Roster Non-Roster Invitees" else: return f"{teams_name_dict[int(input.team_id())]} — Spring Training Roster" @render.table def team_stats(): # Get the selected team's data i = int(input.team_id()) url = f'https://statsapi.mlb.com/api/v1/teams/{i}/roster/40man?season=2025' data = requests.get(url).json() # Normalize the roster data roster_df = pd.json_normalize(data['roster']) roster_df['nri'] = False roster_df['status.code'] = '' roster_df = roster_df.fillna('') url = f'https://statsapi.mlb.com/api/v1/teams/{i}/roster/nonRosterInvitees?season=2025' data = requests.get(url).json() # Normalize the roster data nri_roster_df = pd.json_normalize(data['roster']) nri_roster_df['nri'] = True nri_roster_df['parentTeamId'] = i nri_roster_df = nri_roster_df.fillna('') df_output = pd.concat([roster_df,nri_roster_df]) df_output.loc[df_output['position.abbreviation'] == 'DH', 'position.code'] = '6.5' df_output.loc[df_output['position.abbreviation'] == 'IF', 'position.code'] = '6.5' df_output.loc[df_output['position.abbreviation'] == 'TWP', 'position.code'] = '1' df_output = df_output.sort_values(by=['position.code', 'status.code']).reset_index(drop=True) if input.nri_only(): df_output = df_output[df_output['status.code'] == 'NRI'] df_output = df_output.merge(df_player.to_pandas(),left_on='person.id',right_on='player_id',how='left') conditions = [ (df_output['position.abbreviation'].isin(['P'])) & (~df_output.duplicated(subset=['position.abbreviation'], keep='first')), (df_output['position.abbreviation'] == 'C') & (~df_output.duplicated(subset=['position.abbreviation'], keep='first')), (df_output['position.abbreviation'].isin(['LF','CF','RF','OF'])) & (~df_output.duplicated(subset=['position.abbreviation'], keep='first')) ] choices = ['Pitchers', 'Infielders', 'Outfielders'] df_output['position_group'] = np.select(conditions, choices, default='') df_output.loc[df_output.duplicated(subset=['position_group'], keep='first'), 'position_group'] = '' df_output['team'] = df_output['parentTeamId'].map(teams_dict) df_output.loc[df_output['position.abbreviation'] == 'P', 'position.abbreviation'] = df_output['pitchHand'] + 'H' + df_output['position.abbreviation'] df_output['bat_throw'] = df_output['batSide'] + '/' + df_output['pitchHand'] df_output_small = df_output[['position_group','person.id', 'person.fullName', 'position.abbreviation','team', 'status.code', 'age','weight', 'height', 'bat_throw']] df_output_small['age'] = df_output_small['age'].replace('', np.nan).astype('Int64') df_output_small['weight'] = df_output_small['weight'].replace('', np.nan).astype('Int64') # # Insert blank rows with position group indicated # blank_rows = [] # for idx, row in df_output_small.iterrows(): # if row['position_group']: # blank_row = pd.Series([''] * len(df_output_small.columns), index=df_output_small.columns) # blank_row['position_group'] = row['position_group'] # blank_rows.append((idx, blank_row)) # for idx, blank_row in reversed(blank_rows): # df_output_small = pd.concat([df_output_small.iloc[:idx], pd.DataFrame([blank_row]), df_output_small.iloc[idx:]]).reset_index(drop=True) # df_output_small.loc[(df_output_small['position_group'] != '') & (df_output_small['person.fullName'] != ''), 'position_group'] = '' def highlight_nri(val): color = 'yellow' if val else '' return f'background-color: {color}' # Function to alternate row colors def highlight_alternate_rows(x): return ['background-color: #ebebeb' if i % 2 == 0 else '' for i in range(len(x))] # df_output_small.columns = ['Group','Player ID', 'Name', 'Pos','Team', 'Status','Age','Weight', 'Height', 'B/T'] style_df = (df_output_small.style.set_precision(1) .set_properties(**{'border': '3 px'}, overwrite=False) .set_table_styles([{ 'selector': 'caption', 'props': [ ('color', ''), ('fontname', 'Century Gothic'), ('font-size', '16px'), ('font-style', 'italic'), ('font-weight', ''), ('text-align', 'centre'), ] }, { 'selector': 'th', 'props': [('font-size', '16px'), ('text-align', 'center'), ('Height', 'px'), ('color', 'black'), ('border', '1px black solid !important')] }, { 'selector': 'td', 'props': [('text-align', 'center'), ('font-size', '16px'), ('color', 'black')] }], overwrite=False) .set_properties(**{'background-color': 'White', 'index': 'White', 'min-width': '72px'}, overwrite=False) .set_table_styles([{'selector': 'th:first-child', 'props': [('background-color', 'white')]}], overwrite=False) .set_table_styles([{'selector': 'tr:first-child', 'props': [('background-color', 'white')]}], overwrite=False) .set_table_styles([{'selector': 'tr', 'props': [('line-height', '20px')]}], overwrite=False) .set_properties(**{'Height': '8px'}, **{'text-align': 'center'}, overwrite=False) .hide_index() .set_properties(**{'border': '1px black solid'}) .set_table_styles([{'selector': 'thead th:nth-child(1)', 'props': [('min-width', '150px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '150px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(3)', 'props': [('min-width', '250px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th', 'props': [('height', '30px')]}], overwrite=False) .apply(highlight_alternate_rows, axis=0, subset=df_output_small.columns[1:]) .applymap(lambda x: 'background-color: #bdbdbd' if x != '' else '', subset=['Group']) .applymap(lambda x: 'background-color: #bdbdbd' if x else '', subset=['Group']) # .apply(lambda x: ['background-color: #bdbdbd' if x['Group'] != '' else '' for _ in x], axis=1) .set_properties( **{'background-color':'#bdbdbd'}, # Apply only right border subset=df_output_small.columns[0] # Only affects column 1 ) .set_properties( **{'border-top': 'none', 'border-bottom': 'none'}, subset=df_output_small.columns[0] # Apply only to column 1 ) # .format({'Age': '{:.0f}', 'Weight': '{:.0f}'}) ) def highlight_nri(s): return ['background-color: #b7e1cd' if s.name != 'Status' and s['Status'] == 'NRI' else '' for _ in s] # style_df = style_df.style.apply(highlight_nri, axis=1, subset=style_df.columns[1:]) if not input.nri_only(): style_df = style_df.apply(highlight_nri, axis=1, subset=df_output_small.columns[1:]) def add_top_border(s): return ['border-top: 3px solid black' if s['Group'] != '' else '' for _ in s] styled_df = style_df.apply(add_top_border, axis=1) def add_bottom_border(s): return ['border-bottom: 3px solid black' if s.name == len(df_output_small) - 1 else '' for _ in s] styled_df = style_df.apply(add_bottom_border, axis=1) return style_df app = App(app_ui, server)