rairo commited on
Commit
f21a17d
·
verified ·
1 Parent(s): 70ff97c

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +125 -71
main.py CHANGED
@@ -530,6 +530,7 @@ def normalize_string(s):
530
  """Removes accent marks and converts to lowercase for consistent comparison."""
531
  if not isinstance(s, str):
532
  return str(s)
 
533
  s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('utf-8')
534
  return s.strip()
535
 
@@ -658,8 +659,7 @@ def get_player_index_brscraper():
658
  return df
659
 
660
  def _scrape_player_index_brscraper():
661
- # Prioritize getting real player data from recent seasons
662
- seasons_to_try_for_index = get_available_seasons_util(num_seasons=2) # Try current and previous season
663
 
664
  for season_str in seasons_to_try_for_index:
665
  end_year = int(season_str.split('–')[1])
@@ -669,7 +669,6 @@ def _scrape_player_index_brscraper():
669
 
670
  if not df.empty and 'Player' in df.columns:
671
  player_names = df['Player'].dropna().unique().tolist()
672
- # Normalize player names before returning
673
  player_names = [normalize_string(name) for name in player_names]
674
  logging.info(f"Successfully retrieved {len(player_names)} players for index from {season_str}.")
675
  return pd.DataFrame({'name': player_names})
@@ -678,35 +677,49 @@ def _scrape_player_index_brscraper():
678
  except Exception as e:
679
  logging.warning(f"Error fetching player index with BRScraper for {season_str}: {e}. Trying next season.")
680
 
681
- # Fallback to a curated list if recent seasons fail
682
  logging.error("Failed to fetch player index from recent seasons. Falling back to curated common players list.")
683
  common_players = [
684
  'LeBron James', 'Stephen Curry', 'Kevin Durant', 'Giannis Antetokounmpo',
685
- 'Nikola Jokic', # No accent here, as it will be normalized
686
- 'Joel Embiid', 'Jayson Tatum', 'Luka Doncic', # No accent here, as it will be normalized
687
  'Damian Lillard', 'Jimmy Butler', 'Kawhi Leonard', 'Paul George',
688
  'Anthony Davis', 'Rudy Gobert', 'Donovan Mitchell', 'Trae Young',
689
  'Devin Booker', 'Karl-Anthony Towns', 'Zion Williamson', 'Ja Morant',
690
  'Shai Gilgeous-Alexander', 'Tyrese Maxey', 'Anthony Edwards', 'Victor Wembanyama',
691
- 'Jalen Brunson', 'Paolo Banchero', 'Franz Wagner', 'Cade Cunningham'
 
692
  ]
693
  return pd.DataFrame({'name': common_players})
694
 
695
- def get_player_career_stats_brscraper(player_name, seasons_to_check=10, playoffs=False):
696
  if not BRSCRAPER_AVAILABLE:
697
  logging.error("BRScraper is not available. Cannot fetch player career stats.")
698
  return pd.DataFrame()
699
- all_rows = []
700
 
701
- # Normalize the input player name for consistent lookup
702
  normalized_player_name = normalize_string(player_name)
 
703
 
704
- seasons_to_try = get_available_seasons_util(seasons_to_check)
705
-
706
- for season_str in seasons_to_try:
707
  end_year = int(season_str.split('–')[1])
708
 
709
- # Implement retry logic for each season fetch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710
  for attempt in range(3): # Try up to 3 times
711
  try:
712
  logging.info(f"DEBUG: Attempt {attempt+1} for nba.get_stats for player '{player_name}' in season {season_str} (year: {end_year}, playoffs: {playoffs})...")
@@ -716,40 +729,50 @@ def get_player_career_stats_brscraper(player_name, seasons_to_check=10, playoffs
716
  if df_season.empty:
717
  logging.warning(f"DEBUG: nba.get_stats returned empty DataFrame for {player_name} in {season_str} on attempt {attempt+1}. Retrying...")
718
  time.sleep(1) # Wait a bit before retrying
719
- continue # Go to next attempt
720
 
721
  if 'Player' not in df_season.columns:
722
  logging.warning(f"DEBUG: DataFrame for {player_name} in {season_str} has no 'Player' column on attempt {attempt+1}. Columns: {df_season.columns.tolist()}. Retrying...")
723
  time.sleep(1)
724
  continue
725
 
726
- # Normalize player names in the DataFrame for comparison
727
  df_season['Player_Normalized'] = df_season['Player'].apply(normalize_string)
728
-
729
  row = df_season[df_season['Player_Normalized'] == normalized_player_name]
730
 
731
  if not row.empty:
732
  row = row.copy()
733
  row['Season'] = season_str
734
- # Remove the temporary normalized column before appending
735
- row = row.drop(columns=['Player_Normalized'], errors='ignore')
736
- all_rows.append(row)
 
 
 
 
 
 
 
 
 
737
  logging.info(f"DEBUG: Found stats for {player_name} in {season_str} on attempt {attempt+1}. Appending row.")
738
- break # Break retry loop if successful
739
  else:
740
  logging.info(f"DEBUG: Player {player_name} not found in {season_str} stats (after getting season data) on attempt {attempt+1}. Retrying...")
741
  time.sleep(1)
742
- continue # Go to next attempt
743
 
744
  except Exception as e:
745
  logging.warning(f"DEBUG: Exception on attempt {attempt+1} when fetching {season_str} {'playoff' if playoffs else 'regular season'} stats for {player_name}: {e}")
746
  time.sleep(1) # Wait before next retry
747
- if attempt == 2: # If last attempt failed
748
  logging.error(f"DEBUG: All 3 attempts failed for {player_name} in {season_str}. Giving up on this season.")
749
  continue # Go to next attempt
750
 
 
 
 
751
  if not all_rows:
752
- logging.warning(f"DEBUG: No stats found for {player_name} across all attempted seasons. Returning empty DataFrame.")
753
  return pd.DataFrame()
754
 
755
  df = pd.concat(all_rows, ignore_index=True)
@@ -769,7 +792,7 @@ def get_player_career_stats_brscraper(player_name, seasons_to_check=10, playoffs
769
  if col not in non_num:
770
  df[col] = pd.to_numeric(df[col], errors='coerce')
771
 
772
- df['Player'] = player_name # Ensure original player name is kept
773
  df = df.replace({np.nan: None})
774
  return df
775
 
@@ -947,22 +970,47 @@ def get_player_stats():
947
  all_player_season_data = []
948
  players_with_no_data = []
949
 
950
- for player_name in selected_players:
951
- df_player_career = get_player_career_stats_brscraper(player_name, playoffs=False)
 
 
 
952
 
953
- if df_player_career.empty:
954
- logging.info(f"No career data found for {player_name}. Adding to no_data list.")
 
 
955
  players_with_no_data.append(player_name)
956
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
957
 
958
- filtered_df = df_player_career[df_player_career['Season'].isin(selected_seasons)].copy()
959
-
960
- if not filtered_df.empty:
961
- all_player_season_data.append(filtered_df)
962
- logging.info(f"Successfully filtered data for {player_name} in requested seasons.")
 
 
963
  else:
964
- logging.info(f"No data found for {player_name} in the specific requested seasons: {selected_seasons}. Adding to no_data list.")
965
- players_with_no_data.append(player_name)
 
 
 
966
 
967
  if not all_player_season_data:
968
  logging.warning("After processing all players, 'all_player_season_data' is empty. Returning 404.")
@@ -973,11 +1021,7 @@ def get_player_stats():
973
 
974
  comparison_df_raw = pd.concat(all_player_season_data, ignore_index=True)
975
 
976
- if len(selected_seasons) > 1:
977
- basic_display_df = comparison_df_raw.groupby('Player').mean(numeric_only=True).reset_index()
978
- else:
979
- basic_display_df = comparison_df_raw.copy()
980
-
981
  basic_cols = ['Player', 'Season', 'GP', 'MIN', 'PTS', 'REB', 'AST', 'STL', 'BLK', 'FG_PCT', 'FT_PCT', 'FG3_PCT']
982
  basic_display_df = basic_display_df[[c for c in basic_cols if c in basic_display_df.columns]].round(2)
983
 
@@ -989,13 +1033,8 @@ def get_player_stats():
989
  lambda r: r['PTS'] / (2 * (r['FGA'] + 0.44 * r['FTA'])) if (r['FGA'] + 0.44 * r['FTA']) else 0,
990
  axis=1
991
  )
992
- if len(selected_seasons) > 1:
993
- advanced_display_df = advanced_df.groupby('Player').mean(numeric_only=True).reset_index()
994
- else:
995
- advanced_display_df = advanced_df.copy()
996
-
997
  advanced_cols = ['Player', 'Season', 'PTS', 'REB', 'AST', 'FG_PCT', 'TS_PCT']
998
- advanced_display_df = advanced_display_df[[c for c in advanced_cols if c in advanced_display_df.columns]].round(3)
999
 
1000
  return jsonify({
1001
  'basic_stats': basic_display_df.to_dict(orient='records'),
@@ -1020,21 +1059,45 @@ def get_player_playoff_stats():
1020
  all_player_season_data = []
1021
  players_with_no_data = []
1022
 
1023
- for player_name in selected_players:
1024
- df_player_career = get_player_career_stats_brscraper(player_name, playoffs=True)
1025
- if df_player_career.empty:
1026
- logging.info(f"No career playoff data found for {player_name}. Adding to no_data list.")
1027
- players_with_no_data.append(player_name)
1028
- continue
1029
-
1030
- filtered_df = df_player_career[df_player_career['Season'].isin(selected_seasons)].copy()
1031
 
1032
- if not filtered_df.empty:
1033
- all_player_season_data.append(filtered_df)
1034
- logging.info(f"Successfully filtered playoff data for {player_name} in requested seasons.")
1035
  else:
1036
- logging.info(f"No playoff data found for {player_name} in the specific requested seasons: {selected_seasons}. Adding to no_data list.")
1037
  players_with_no_data.append(player_name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1038
 
1039
  if not all_player_season_data:
1040
  logging.warning("After processing all players, 'all_player_season_data' is empty for playoffs. Returning 404.")
@@ -1045,11 +1108,7 @@ def get_player_playoff_stats():
1045
 
1046
  comparison_df_raw = pd.concat(all_player_season_data, ignore_index=True)
1047
 
1048
- if len(selected_seasons) > 1:
1049
- basic_display_df = comparison_df_raw.groupby('Player').mean(numeric_only=True).reset_index()
1050
- else:
1051
- basic_display_df = comparison_df_raw.copy()
1052
-
1053
  basic_cols = ['Player', 'Season', 'GP', 'MIN', 'PTS', 'REB', 'AST', 'STL', 'BLK', 'FG_PCT', 'FT_PCT', 'FG3_PCT']
1054
  basic_display_df = basic_display_df[[c for c in basic_cols if c in basic_display_df.columns]].round(2)
1055
 
@@ -1061,13 +1120,8 @@ def get_player_playoff_stats():
1061
  lambda r: r['PTS'] / (2 * (r['FGA'] + 0.44 * r['FTA'])) if (r['FGA'] + 0.44 * r['FTA']) else 0,
1062
  axis=1
1063
  )
1064
- if len(selected_seasons) > 1:
1065
- advanced_display_df = advanced_df.groupby('Player').mean(numeric_only=True).reset_index()
1066
- else:
1067
- advanced_display_df = advanced_df.copy()
1068
-
1069
  advanced_cols = ['Player', 'Season', 'PTS', 'REB', 'AST', 'FG_PCT', 'TS_PCT']
1070
- advanced_display_df = advanced_display_df[[c for c in advanced_cols if c in advanced_display_df.columns]].round(3)
1071
 
1072
  return jsonify({
1073
  'basic_stats': basic_display_df.to_dict(orient='records'),
 
530
  """Removes accent marks and converts to lowercase for consistent comparison."""
531
  if not isinstance(s, str):
532
  return str(s)
533
+ # Normalize to NFD (Canonical Decomposition) and remove combining characters
534
  s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('utf-8')
535
  return s.strip()
536
 
 
659
  return df
660
 
661
  def _scrape_player_index_brscraper():
662
+ seasons_to_try_for_index = get_available_seasons_util(num_seasons=3)
 
663
 
664
  for season_str in seasons_to_try_for_index:
665
  end_year = int(season_str.split('–')[1])
 
669
 
670
  if not df.empty and 'Player' in df.columns:
671
  player_names = df['Player'].dropna().unique().tolist()
 
672
  player_names = [normalize_string(name) for name in player_names]
673
  logging.info(f"Successfully retrieved {len(player_names)} players for index from {season_str}.")
674
  return pd.DataFrame({'name': player_names})
 
677
  except Exception as e:
678
  logging.warning(f"Error fetching player index with BRScraper for {season_str}: {e}. Trying next season.")
679
 
 
680
  logging.error("Failed to fetch player index from recent seasons. Falling back to curated common players list.")
681
  common_players = [
682
  'LeBron James', 'Stephen Curry', 'Kevin Durant', 'Giannis Antetokounmpo',
683
+ 'Nikola Jokic',
684
+ 'Joel Embiid', 'Jayson Tatum', 'Luka Doncic',
685
  'Damian Lillard', 'Jimmy Butler', 'Kawhi Leonard', 'Paul George',
686
  'Anthony Davis', 'Rudy Gobert', 'Donovan Mitchell', 'Trae Young',
687
  'Devin Booker', 'Karl-Anthony Towns', 'Zion Williamson', 'Ja Morant',
688
  'Shai Gilgeous-Alexander', 'Tyrese Maxey', 'Anthony Edwards', 'Victor Wembanyama',
689
+ 'Jalen Brunson', 'Paolo Banchero', 'Franz Wagner', 'Cade Cunningham',
690
+ 'Michael Jordan', 'Kobe Bryant', 'Larry Bird', 'Magic Johnson', 'Kareem Abdul-Jabbar'
691
  ]
692
  return pd.DataFrame({'name': common_players})
693
 
694
+ def get_player_career_stats_brscraper(player_name, seasons_to_fetch: list[str], playoffs=False):
695
  if not BRSCRAPER_AVAILABLE:
696
  logging.error("BRScraper is not available. Cannot fetch player career stats.")
697
  return pd.DataFrame()
 
698
 
 
699
  normalized_player_name = normalize_string(player_name)
700
+ all_rows = []
701
 
702
+ for season_str in seasons_to_fetch: # Iterate only through the requested seasons
 
 
703
  end_year = int(season_str.split('–')[1])
704
 
705
+ # Define cache key for this specific player-season-playoff combination
706
+ cache_key = f"{normalized_player_name}_{end_year}_{'playoffs' if playoffs else 'regular'}"
707
+ db_ref = db.reference(f'scraped_data/player_season_stats/{cache_key}')
708
+
709
+ # Check Firebase cache first for this specific season
710
+ if FIREBASE_INITIALIZED:
711
+ cached_data = db_ref.get()
712
+ if cached_data and not is_data_stale(cached_data.get('last_updated'), max_age_hours=24*7): # Cache for 7 days
713
+ logging.info(f"Loading stats for {player_name} in {season_str} (playoffs: {playoffs}) from Firebase cache.")
714
+ # Return a DataFrame with a single row for this season
715
+ # Note: This function is designed to return a concatenated DF of ALL requested seasons.
716
+ # So, if a single season is found in cache, we append it and continue to check others.
717
+ all_rows.append(pd.DataFrame.from_records(cached_data['data']))
718
+ continue # Skip scraping for this season if found in cache
719
+ else:
720
+ logging.info(f"Stats for {player_name} in {season_str} cache stale or not found. Scraping...")
721
+
722
+ # If not in cache or stale, attempt to scrape
723
  for attempt in range(3): # Try up to 3 times
724
  try:
725
  logging.info(f"DEBUG: Attempt {attempt+1} for nba.get_stats for player '{player_name}' in season {season_str} (year: {end_year}, playoffs: {playoffs})...")
 
729
  if df_season.empty:
730
  logging.warning(f"DEBUG: nba.get_stats returned empty DataFrame for {player_name} in {season_str} on attempt {attempt+1}. Retrying...")
731
  time.sleep(1) # Wait a bit before retrying
732
+ continue
733
 
734
  if 'Player' not in df_season.columns:
735
  logging.warning(f"DEBUG: DataFrame for {player_name} in {season_str} has no 'Player' column on attempt {attempt+1}. Columns: {df_season.columns.tolist()}. Retrying...")
736
  time.sleep(1)
737
  continue
738
 
 
739
  df_season['Player_Normalized'] = df_season['Player'].apply(normalize_string)
 
740
  row = df_season[df_season['Player_Normalized'] == normalized_player_name]
741
 
742
  if not row.empty:
743
  row = row.copy()
744
  row['Season'] = season_str
745
+ row = row.drop(columns=['Player_Normalized'], errors='ignore') # Remove temp column
746
+
747
+ # Save this specific season's data to Firebase cache
748
+ if FIREBASE_INITIALIZED:
749
+ df_cleaned_for_firebase = clean_df_for_firebase(row.copy())
750
+ db_ref.set({
751
+ 'last_updated': datetime.utcnow().isoformat(),
752
+ 'data': df_cleaned_for_firebase.to_dict(orient='records')
753
+ })
754
+ logging.info(f"Stats for {player_name} in {season_str} saved to Firebase cache.")
755
+
756
+ all_rows.append(row) # Add to results for current request
757
  logging.info(f"DEBUG: Found stats for {player_name} in {season_str} on attempt {attempt+1}. Appending row.")
758
+ break # Break retry loop if successful for this season
759
  else:
760
  logging.info(f"DEBUG: Player {player_name} not found in {season_str} stats (after getting season data) on attempt {attempt+1}. Retrying...")
761
  time.sleep(1)
762
+ continue
763
 
764
  except Exception as e:
765
  logging.warning(f"DEBUG: Exception on attempt {attempt+1} when fetching {season_str} {'playoff' if playoffs else 'regular season'} stats for {player_name}: {e}")
766
  time.sleep(1) # Wait before next retry
767
+ if attempt == 2: # If last attempt failed for this season
768
  logging.error(f"DEBUG: All 3 attempts failed for {player_name} in {season_str}. Giving up on this season.")
769
  continue # Go to next attempt
770
 
771
+ # Add a small delay between processing different seasons for the same player
772
+ time.sleep(0.5) # Delay between seasons
773
+
774
  if not all_rows:
775
+ logging.warning(f"DEBUG: No stats found for {player_name} in the requested seasons: {seasons_to_fetch}. Returning empty DataFrame.")
776
  return pd.DataFrame()
777
 
778
  df = pd.concat(all_rows, ignore_index=True)
 
792
  if col not in non_num:
793
  df[col] = pd.to_numeric(df[col], errors='coerce')
794
 
795
+ df['Player'] = player_name
796
  df = df.replace({np.nan: None})
797
  return df
798
 
 
970
  all_player_season_data = []
971
  players_with_no_data = []
972
 
973
+ # Handle individual player stats (1 player, 1 season)
974
+ if len(selected_players) == 1 and len(selected_seasons) == 1:
975
+ player_name = selected_players[0]
976
+ season_str = selected_seasons[0]
977
+ df_player_data = get_player_career_stats_brscraper(player_name, seasons_to_fetch=[season_str], playoffs=False)
978
 
979
+ if not df_player_data.empty:
980
+ all_player_season_data.append(df_player_data)
981
+ logging.info(f"Successfully retrieved data for {player_name} in {season_str}.")
982
+ else:
983
  players_with_no_data.append(player_name)
984
+ logging.info(f"No data found for {player_name} in {season_str}.")
985
+
986
+ # Handle comparison (2 players, 2 seasons)
987
+ elif len(selected_players) == 2 and len(selected_seasons) == 2:
988
+ player1_name = selected_players[0]
989
+ player1_season = selected_seasons[0]
990
+ player2_name = selected_players[1]
991
+ player2_season = selected_seasons[1]
992
+
993
+ df_player1_data = get_player_career_stats_brscraper(player1_name, seasons_to_fetch=[player1_season], playoffs=False)
994
+ if not df_player1_data.empty:
995
+ all_player_season_data.append(df_player1_data)
996
+ logging.info(f"Successfully retrieved data for {player1_name} in {player1_season}.")
997
+ else:
998
+ players_with_no_data.append(player1_name)
999
+ logging.info(f"No data found for {player1_name} in {player1_season}.")
1000
 
1001
+ # Add a delay between fetching data for player 1 and player 2
1002
+ time.sleep(2) # Introduce a 2-second delay
1003
+
1004
+ df_player2_data = get_player_career_stats_brscraper(player2_name, seasons_to_fetch=[player2_season], playoffs=False)
1005
+ if not df_player2_data.empty:
1006
+ all_player_season_data.append(df_player2_data)
1007
+ logging.info(f"Successfully retrieved data for {player2_name} in {player2_season}.")
1008
  else:
1009
+ players_with_no_data.append(player2_name)
1010
+ logging.info(f"No data found for {player2_name} in {player2_season}.")
1011
+ else:
1012
+ return jsonify({'error': 'Invalid combination of players and seasons. Expected 1 player/1 season or 2 players/2 seasons.'}), 400
1013
+
1014
 
1015
  if not all_player_season_data:
1016
  logging.warning("After processing all players, 'all_player_season_data' is empty. Returning 404.")
 
1021
 
1022
  comparison_df_raw = pd.concat(all_player_season_data, ignore_index=True)
1023
 
1024
+ basic_display_df = comparison_df_raw.copy()
 
 
 
 
1025
  basic_cols = ['Player', 'Season', 'GP', 'MIN', 'PTS', 'REB', 'AST', 'STL', 'BLK', 'FG_PCT', 'FT_PCT', 'FG3_PCT']
1026
  basic_display_df = basic_display_df[[c for c in basic_cols if c in basic_display_df.columns]].round(2)
1027
 
 
1033
  lambda r: r['PTS'] / (2 * (r['FGA'] + 0.44 * r['FTA'])) if (r['FGA'] + 0.44 * r['FTA']) else 0,
1034
  axis=1
1035
  )
 
 
 
 
 
1036
  advanced_cols = ['Player', 'Season', 'PTS', 'REB', 'AST', 'FG_PCT', 'TS_PCT']
1037
+ advanced_display_df = advanced_df[[c for c in advanced_cols if c in advanced_df.columns]].round(3)
1038
 
1039
  return jsonify({
1040
  'basic_stats': basic_display_df.to_dict(orient='records'),
 
1059
  all_player_season_data = []
1060
  players_with_no_data = []
1061
 
1062
+ if len(selected_players) == 1 and len(selected_seasons) == 1:
1063
+ player_name = selected_players[0]
1064
+ season_str = selected_seasons[0]
1065
+ df_player_data = get_player_career_stats_brscraper(player_name, seasons_to_fetch=[season_str], playoffs=True)
 
 
 
 
1066
 
1067
+ if not df_player_data.empty:
1068
+ all_player_season_data.append(df_player_data)
1069
+ logging.info(f"Successfully retrieved playoff data for {player_name} in {season_str}.")
1070
  else:
 
1071
  players_with_no_data.append(player_name)
1072
+ logging.info(f"No playoff data found for {player_name} in {season_str}.")
1073
+
1074
+ elif len(selected_players) == 2 and len(selected_seasons) == 2:
1075
+ player1_name = selected_players[0]
1076
+ player1_season = selected_seasons[0]
1077
+ player2_name = selected_players[1]
1078
+ player2_season = selected_seasons[1]
1079
+
1080
+ df_player1_data = get_player_career_stats_brscraper(player1_name, seasons_to_fetch=[player1_season], playoffs=True)
1081
+ if not df_player1_data.empty:
1082
+ all_player_season_data.append(df_player1_data)
1083
+ logging.info(f"Successfully retrieved playoff data for {player1_name} in {player1_season}.")
1084
+ else:
1085
+ players_with_no_data.append(player1_name)
1086
+ logging.info(f"No playoff data found for {player1_name} in {player1_season}.")
1087
+
1088
+ # Add a delay between fetching data for player 1 and player 2
1089
+ time.sleep(2) # Introduce a 2-second delay
1090
+
1091
+ df_player2_data = get_player_career_stats_brscraper(player2_name, seasons_to_fetch=[player2_season], playoffs=True)
1092
+ if not df_player2_data.empty:
1093
+ all_player_season_data.append(df_player2_data)
1094
+ logging.info(f"Successfully retrieved playoff data for {player2_name} in {player2_season}.")
1095
+ else:
1096
+ players_with_no_data.append(player2_name)
1097
+ logging.info(f"No playoff data found for {player2_name} in {player2_season}.")
1098
+ else:
1099
+ return jsonify({'error': 'Invalid combination of players and seasons. Expected 1 player/1 season or 2 players/2 seasons.'}), 400
1100
+
1101
 
1102
  if not all_player_season_data:
1103
  logging.warning("After processing all players, 'all_player_season_data' is empty for playoffs. Returning 404.")
 
1108
 
1109
  comparison_df_raw = pd.concat(all_player_season_data, ignore_index=True)
1110
 
1111
+ basic_display_df = comparison_df_raw.copy()
 
 
 
 
1112
  basic_cols = ['Player', 'Season', 'GP', 'MIN', 'PTS', 'REB', 'AST', 'STL', 'BLK', 'FG_PCT', 'FT_PCT', 'FG3_PCT']
1113
  basic_display_df = basic_display_df[[c for c in basic_cols if c in basic_display_df.columns]].round(2)
1114
 
 
1120
  lambda r: r['PTS'] / (2 * (r['FGA'] + 0.44 * r['FTA'])) if (r['FGA'] + 0.44 * r['FTA']) else 0,
1121
  axis=1
1122
  )
 
 
 
 
 
1123
  advanced_cols = ['Player', 'Season', 'PTS', 'REB', 'AST', 'FG_PCT', 'TS_PCT']
1124
+ advanced_display_df = advanced_df[[c for c in advanced_cols if c in advanced_df.columns]].round(3)
1125
 
1126
  return jsonify({
1127
  'basic_stats': basic_display_df.to_dict(orient='records'),