Update main.py
Browse files
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 |
-
|
| 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',
|
| 686 |
-
'Joel Embiid', 'Jayson Tatum', 'Luka Doncic',
|
| 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,
|
| 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 |
-
|
| 705 |
-
|
| 706 |
-
for season_str in seasons_to_try:
|
| 707 |
end_year = int(season_str.split('–')[1])
|
| 708 |
|
| 709 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
| 735 |
-
|
| 736 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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}
|
| 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
|
| 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 |
-
|
| 951 |
-
|
|
|
|
|
|
|
|
|
|
| 952 |
|
| 953 |
-
if
|
| 954 |
-
|
|
|
|
|
|
|
| 955 |
players_with_no_data.append(player_name)
|
| 956 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 957 |
|
| 958 |
-
|
| 959 |
-
|
| 960 |
-
|
| 961 |
-
|
| 962 |
-
|
|
|
|
|
|
|
| 963 |
else:
|
| 964 |
-
|
| 965 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 =
|
| 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 |
-
|
| 1024 |
-
|
| 1025 |
-
|
| 1026 |
-
|
| 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
|
| 1033 |
-
all_player_season_data.append(
|
| 1034 |
-
logging.info(f"Successfully
|
| 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 |
-
|
| 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 =
|
| 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'),
|