rairo commited on
Commit
e460802
·
verified ·
1 Parent(s): ececa88

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +260 -89
main.py CHANGED
@@ -30,29 +30,33 @@ CORS(app)
30
 
31
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
32
 
33
- Firebase_DB = os.getenv("Firebase_DB")
34
- Firebase_Storage = os.getenv("Firebase_Storage")
 
 
 
35
 
36
  FIREBASE_INITIALIZED = False
 
37
 
38
  try:
39
- credentials_json_string = os.environ.get("FIREBASE")
40
- if credentials_json_string:
41
- credentials_json = json.loads(credentials_json_string)
42
  cred = credentials.Certificate(credentials_json)
43
  firebase_admin.initialize_app(cred, {
44
- 'databaseURL': f'{Firebase_DB}',
45
- 'storageBucket': f'{Firebase_Storage}'
46
  })
47
  FIREBASE_INITIALIZED = True
 
48
  logging.info("Firebase Admin SDK initialized successfully.")
49
  else:
50
- logging.warning("FIREBASE secret not set. Firebase Admin SDK not initialized.")
51
  except Exception as e:
52
  logging.error(f"Error initializing Firebase: {e}")
53
  traceback.print_exc()
 
54
 
55
- bucket = storage.bucket() if FIREBASE_INITIALIZED else None
56
 
57
  def verify_token(token):
58
  try:
@@ -63,21 +67,35 @@ def verify_token(token):
63
  return None
64
 
65
  def verify_admin(auth_header):
 
 
 
 
66
  if not auth_header or not auth_header.startswith('Bearer '):
67
  raise ValueError('Invalid token format')
68
  token = auth_header.split(' ')[1]
69
  uid = verify_token(token)
70
  if not uid:
71
  raise PermissionError('Invalid user token')
 
72
  user_ref = db.reference(f'users/{uid}')
73
  user_data = user_ref.get()
74
- if not user_data or not user_data.get('is_admin', False):
 
 
 
 
 
75
  raise PermissionError('Admin access required')
76
  return uid
77
 
78
  def credit_required(cost=1):
79
  def decorator(f):
80
  def wrapper(*args, **kwargs):
 
 
 
 
81
  auth_header = request.headers.get('Authorization', '')
82
  if not auth_header.startswith('Bearer '):
83
  return jsonify({'error': 'Authorization header missing or malformed'}), 401
@@ -88,8 +106,10 @@ def credit_required(cost=1):
88
 
89
  user_ref = db.reference(f'users/{uid}')
90
  user_data = user_ref.get()
 
91
  if not user_data:
92
- return jsonify({'error': 'User not found'}), 404
 
93
 
94
  if user_data.get('suspended', False):
95
  return jsonify({'error': 'Account suspended. Please contact support.'}), 403
@@ -113,6 +133,10 @@ def credit_required(cost=1):
113
 
114
  @app.route('/api/auth/signup', methods=['POST'])
115
  def signup():
 
 
 
 
116
  try:
117
  data = request.get_json()
118
  email = data.get('email')
@@ -120,15 +144,21 @@ def signup():
120
  if not email or not password:
121
  return jsonify({'error': 'Email and password are required'}), 400
122
 
 
123
  user = auth.create_user(email=email, password=password)
 
 
124
  user_ref = db.reference(f'users/{user.uid}')
125
  user_data = {
126
  'email': email,
127
  'credits': 10,
128
  'is_admin': False,
129
- 'created_at': datetime.utcnow().isoformat()
 
130
  }
131
  user_ref.set(user_data)
 
 
132
  return jsonify({
133
  'success': True,
134
  'user': {
@@ -138,10 +168,17 @@ def signup():
138
  }), 201
139
  except Exception as e:
140
  logging.error(f"Signup error: {e}")
 
 
 
141
  return jsonify({'error': str(e)}), 400
142
 
143
  @app.route('/api/user/profile', methods=['GET'])
144
  def get_user_profile():
 
 
 
 
145
  try:
146
  auth_header = request.headers.get('Authorization', '')
147
  if not auth_header.startswith('Bearer '):
@@ -154,13 +191,15 @@ def get_user_profile():
154
 
155
  user_data = db.reference(f'users/{uid}').get()
156
  if not user_data:
157
- return jsonify({'error': 'User not found'}), 404
 
158
 
159
  return jsonify({
160
  'uid': uid,
161
  'email': user_data.get('email'),
162
  'credits': user_data.get('credits', 0),
163
- 'is_admin': user_data.get('is_admin', False)
 
164
  })
165
  except Exception as e:
166
  logging.error(f"Error fetching user profile: {e}")
@@ -168,6 +207,10 @@ def get_user_profile():
168
 
169
  @app.route('/api/auth/google-signin', methods=['POST'])
170
  def google_signin():
 
 
 
 
171
  try:
172
  auth_header = request.headers.get('Authorization', '')
173
  if not auth_header.startswith('Bearer '):
@@ -182,13 +225,18 @@ def google_signin():
182
  user_data = user_ref.get()
183
 
184
  if not user_data:
 
185
  user_data = {
186
  'email': email,
187
  'credits': 10,
188
  'is_admin': False,
189
  'created_at': datetime.utcnow().isoformat(),
 
190
  }
191
  user_ref.set(user_data)
 
 
 
192
 
193
  return jsonify({
194
  'success': True,
@@ -205,6 +253,10 @@ def google_signin():
205
  @app.route('/api/user/request-credits', methods=['POST'])
206
  @credit_required(cost=0)
207
  def request_credits():
 
 
 
 
208
  try:
209
  auth_header = request.headers.get('Authorization', '')
210
  token = auth_header.split(' ')[1]
@@ -231,6 +283,10 @@ def request_credits():
231
  @credit_required(cost=0)
232
  @cross_origin()
233
  def submit_feedback():
 
 
 
 
234
  try:
235
  auth_header = request.headers.get('Authorization', '')
236
  token = auth_header.split(' ')[1]
@@ -262,6 +318,10 @@ def submit_feedback():
262
 
263
  @app.route('/api/admin/profile', methods=['GET'])
264
  def get_admin_profile():
 
 
 
 
265
  try:
266
  admin_uid = verify_admin(request.headers.get('Authorization', ''))
267
  admin_data = db.reference(f'users/{admin_uid}').get()
@@ -300,6 +360,10 @@ def get_admin_profile():
300
 
301
  @app.route('/api/admin/credit_requests', methods=['GET'])
302
  def list_credit_requests():
 
 
 
 
303
  try:
304
  verify_admin(request.headers.get('Authorization', ''))
305
  requests_ref = db.reference('credit_requests')
@@ -312,6 +376,10 @@ def list_credit_requests():
312
 
313
  @app.route('/api/admin/credit_requests/<string:request_id>', methods=['PUT'])
314
  def process_credit_request(request_id):
 
 
 
 
315
  try:
316
  admin_uid = verify_admin(request.headers.get('Authorization', ''))
317
  req_ref = db.reference(f'credit_requests/{request_id}')
@@ -350,6 +418,10 @@ def process_credit_request(request_id):
350
 
351
  @app.route('/api/admin/users', methods=['GET'])
352
  def admin_list_users():
 
 
 
 
353
  try:
354
  verify_admin(request.headers.get('Authorization', ''))
355
  users_ref = db.reference('users')
@@ -372,6 +444,10 @@ def admin_list_users():
372
 
373
  @app.route('/api/admin/users/search', methods=['GET'])
374
  def admin_search_users():
 
 
 
 
375
  try:
376
  verify_admin(request.headers.get('Authorization', ''))
377
  email_query = request.args.get('email', '').lower().strip()
@@ -400,6 +476,10 @@ def admin_search_users():
400
 
401
  @app.route('/api/admin/users/<string:uid>/suspend', methods=['PUT'])
402
  def admin_suspend_user(uid):
 
 
 
 
403
  try:
404
  verify_admin(request.headers.get('Authorization', ''))
405
  data = request.get_json()
@@ -424,6 +504,10 @@ def admin_suspend_user(uid):
424
 
425
  @app.route('/api/admin/notifications', methods=['POST'])
426
  def send_notifications():
 
 
 
 
427
  try:
428
  admin_uid = verify_admin(request.headers.get('Authorization', ''))
429
  data = request.get_json()
@@ -471,6 +555,10 @@ def send_notifications():
471
 
472
  @app.route('/api/admin/feedback', methods=['GET'])
473
  def admin_view_feedback():
 
 
 
 
474
  try:
475
  admin_uid = verify_admin(request.headers.get('Authorization', ''))
476
  feedback_type = request.args.get('type')
@@ -502,6 +590,10 @@ def admin_view_feedback():
502
 
503
  @app.route('/api/admin/users/<string:uid>/credits', methods=['PUT'])
504
  def admin_update_credits(uid):
 
 
 
 
505
  try:
506
  verify_admin(request.headers.get('Authorization', ''))
507
  data = request.get_json()
@@ -658,8 +750,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 +760,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,12 +768,11 @@ 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',
@@ -692,22 +781,30 @@ def _scrape_player_index_brscraper():
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})...")
713
 
@@ -715,41 +812,49 @@ def get_player_career_stats_brscraper(player_name, seasons_to_check=10, playoffs
715
 
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,13 +874,13 @@ 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
 
776
  def get_dashboard_info_brscraper():
777
- if not BRSCRAPER_AVAILABLE:
778
- logging.error("BRScraper not available for dashboard info.")
779
  return {}
780
 
781
  if not FIREBASE_INITIALIZED:
@@ -947,22 +1052,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 +1103,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 +1115,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 +1141,44 @@ 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 +1189,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 +1201,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'),
@@ -1147,6 +1282,10 @@ def get_team_stats():
1147
  @credit_required(cost=0)
1148
  @cross_origin()
1149
  def dashboard_info():
 
 
 
 
1150
  try:
1151
  dashboard_data = get_dashboard_info_brscraper()
1152
  if not dashboard_data:
@@ -1160,6 +1299,10 @@ def dashboard_info():
1160
  @credit_required(cost=1)
1161
  @cross_origin()
1162
  def perplexity_explain():
 
 
 
 
1163
  try:
1164
  data = request.get_json()
1165
  prompt = data.get('prompt')
@@ -1197,6 +1340,10 @@ def perplexity_explain():
1197
  @credit_required(cost=0)
1198
  @cross_origin()
1199
  def get_user_analyses():
 
 
 
 
1200
  try:
1201
  auth_header = request.headers.get('Authorization', '')
1202
  token = auth_header.split(' ')[1]
@@ -1228,6 +1375,10 @@ def get_user_analyses():
1228
  @credit_required(cost=0)
1229
  @cross_origin()
1230
  def delete_user_analysis(analysis_id):
 
 
 
 
1231
  try:
1232
  auth_header = request.headers.get('Authorization', '')
1233
  token = auth_header.split(' ')[1]
@@ -1254,6 +1405,10 @@ def delete_user_analysis(analysis_id):
1254
  @credit_required(cost=1)
1255
  @cross_origin()
1256
  def perplexity_chat():
 
 
 
 
1257
  try:
1258
  data = request.get_json()
1259
  prompt = data.get('prompt')
@@ -1294,6 +1449,10 @@ def perplexity_chat():
1294
  @credit_required(cost=1)
1295
  @cross_origin()
1296
  def awards_predictor():
 
 
 
 
1297
  try:
1298
  data = request.get_json()
1299
  award_type = data.get('award_type')
@@ -1316,6 +1475,10 @@ def awards_predictor():
1316
  @credit_required(cost=1)
1317
  @cross_origin()
1318
  def young_player_projection():
 
 
 
 
1319
  try:
1320
  data = request.get_json()
1321
  player_name = data.get('player_name')
@@ -1349,6 +1512,10 @@ def young_player_projection():
1349
  @credit_required(cost=1)
1350
  @cross_origin()
1351
  def similar_players():
 
 
 
 
1352
  try:
1353
  data = request.get_json()
1354
  target_player = data.get('target_player')
@@ -1393,6 +1560,10 @@ def similar_players():
1393
  @credit_required(cost=1)
1394
  @cross_origin()
1395
  def manual_player_compare():
 
 
 
 
1396
  try:
1397
  data = request.get_json()
1398
  player1_name = data.get('player1_name')
@@ -1435,4 +1606,4 @@ def manual_player_compare():
1435
 
1436
 
1437
  if __name__ == '__main__':
1438
- app.run(debug=True, host="0.0.0.0", port=7860)
 
30
 
31
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
32
 
33
+ # --- Firebase Initialization (CRITICAL FIXES HERE) ---
34
+ # Ensure these environment variables are correctly set in your Hugging Face Space
35
+ FIREBASE_CREDENTIALS_JSON_STRING = os.getenv("FIREBASE")
36
+ FIREBASE_DB_URL = os.getenv("Firebase_DB") # Using a more descriptive name
37
+ FIREBASE_STORAGE_BUCKET = os.getenv("Firebase_Storage") # Using a more descriptive name
38
 
39
  FIREBASE_INITIALIZED = False
40
+ bucket = None # Initialize bucket to None
41
 
42
  try:
43
+ if FIREBASE_CREDENTIALS_JSON_STRING and FIREBASE_DB_URL and FIREBASE_STORAGE_BUCKET:
44
+ credentials_json = json.loads(FIREBASE_CREDENTIALS_JSON_STRING)
 
45
  cred = credentials.Certificate(credentials_json)
46
  firebase_admin.initialize_app(cred, {
47
+ 'databaseURL': FIREBASE_DB_URL,
48
+ 'storageBucket': FIREBASE_STORAGE_BUCKET
49
  })
50
  FIREBASE_INITIALIZED = True
51
+ bucket = storage.bucket() # Initialize bucket only if Firebase is initialized
52
  logging.info("Firebase Admin SDK initialized successfully.")
53
  else:
54
+ logging.error("Firebase environment variables (FIREBASE, Firebase_DB, Firebase_Storage) not fully set. Firebase Admin SDK not initialized.")
55
  except Exception as e:
56
  logging.error(f"Error initializing Firebase: {e}")
57
  traceback.print_exc()
58
+ # --- END Firebase Initialization Fixes ---
59
 
 
60
 
61
  def verify_token(token):
62
  try:
 
67
  return None
68
 
69
  def verify_admin(auth_header):
70
+ if not FIREBASE_INITIALIZED:
71
+ logging.error("Firebase not initialized. Admin verification skipped.")
72
+ raise PermissionError('Server configuration error: Firebase not ready.')
73
+
74
  if not auth_header or not auth_header.startswith('Bearer '):
75
  raise ValueError('Invalid token format')
76
  token = auth_header.split(' ')[1]
77
  uid = verify_token(token)
78
  if not uid:
79
  raise PermissionError('Invalid user token')
80
+
81
  user_ref = db.reference(f'users/{uid}')
82
  user_data = user_ref.get()
83
+
84
+ if not user_data:
85
+ logging.warning(f"User {uid} found in Auth but not in Realtime DB. Cannot verify admin status.")
86
+ raise PermissionError('User profile not found in database. Admin access denied.')
87
+
88
+ if not user_data.get('is_admin', False):
89
  raise PermissionError('Admin access required')
90
  return uid
91
 
92
  def credit_required(cost=1):
93
  def decorator(f):
94
  def wrapper(*args, **kwargs):
95
+ if not FIREBASE_INITIALIZED:
96
+ logging.error("Firebase not initialized. Credit deduction skipped.")
97
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
98
+
99
  auth_header = request.headers.get('Authorization', '')
100
  if not auth_header.startswith('Bearer '):
101
  return jsonify({'error': 'Authorization header missing or malformed'}), 401
 
106
 
107
  user_ref = db.reference(f'users/{uid}')
108
  user_data = user_ref.get()
109
+
110
  if not user_data:
111
+ logging.warning(f"User {uid} found in Auth but not in Realtime DB. Cannot process credits.")
112
+ return jsonify({'error': 'User profile not found in database. Please try logging in again.'}), 404
113
 
114
  if user_data.get('suspended', False):
115
  return jsonify({'error': 'Account suspended. Please contact support.'}), 403
 
133
 
134
  @app.route('/api/auth/signup', methods=['POST'])
135
  def signup():
136
+ if not FIREBASE_INITIALIZED:
137
+ logging.error("Firebase not initialized. Signup skipped.")
138
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
139
+
140
  try:
141
  data = request.get_json()
142
  email = data.get('email')
 
144
  if not email or not password:
145
  return jsonify({'error': 'Email and password are required'}), 400
146
 
147
+ # Create user in Firebase Authentication
148
  user = auth.create_user(email=email, password=password)
149
+
150
+ # Create corresponding user entry in Firebase Realtime Database
151
  user_ref = db.reference(f'users/{user.uid}')
152
  user_data = {
153
  'email': email,
154
  'credits': 10,
155
  'is_admin': False,
156
+ 'created_at': datetime.utcnow().isoformat(),
157
+ 'suspended': False # Ensure new accounts are not suspended by default
158
  }
159
  user_ref.set(user_data)
160
+ logging.info(f"New user {user.uid} signed up and DB entry created.")
161
+
162
  return jsonify({
163
  'success': True,
164
  'user': {
 
168
  }), 201
169
  except Exception as e:
170
  logging.error(f"Signup error: {e}")
171
+ # Firebase Auth errors can be specific, e.g., email-already-in-use
172
+ if 'email-already-in-use' in str(e):
173
+ return jsonify({'error': 'Email already in use.'}), 409 # Conflict
174
  return jsonify({'error': str(e)}), 400
175
 
176
  @app.route('/api/user/profile', methods=['GET'])
177
  def get_user_profile():
178
+ if not FIREBASE_INITIALIZED:
179
+ logging.error("Firebase not initialized. Profile retrieval skipped.")
180
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
181
+
182
  try:
183
  auth_header = request.headers.get('Authorization', '')
184
  if not auth_header.startswith('Bearer '):
 
191
 
192
  user_data = db.reference(f'users/{uid}').get()
193
  if not user_data:
194
+ logging.warning(f"User {uid} found in Auth but not in Realtime DB when fetching profile.")
195
+ return jsonify({'error': 'User profile not found in database. Please try logging in again.'}), 404
196
 
197
  return jsonify({
198
  'uid': uid,
199
  'email': user_data.get('email'),
200
  'credits': user_data.get('credits', 0),
201
+ 'is_admin': user_data.get('is_admin', False),
202
+ 'suspended': user_data.get('suspended', False) # Include suspended status
203
  })
204
  except Exception as e:
205
  logging.error(f"Error fetching user profile: {e}")
 
207
 
208
  @app.route('/api/auth/google-signin', methods=['POST'])
209
  def google_signin():
210
+ if not FIREBASE_INITIALIZED:
211
+ logging.error("Firebase not initialized. Google Sign-In skipped.")
212
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
213
+
214
  try:
215
  auth_header = request.headers.get('Authorization', '')
216
  if not auth_header.startswith('Bearer '):
 
225
  user_data = user_ref.get()
226
 
227
  if not user_data:
228
+ # If user doesn't exist in DB, create a new entry
229
  user_data = {
230
  'email': email,
231
  'credits': 10,
232
  'is_admin': False,
233
  'created_at': datetime.utcnow().isoformat(),
234
+ 'suspended': False
235
  }
236
  user_ref.set(user_data)
237
+ logging.info(f"New Google user {uid} signed up and DB entry created.")
238
+ else:
239
+ logging.info(f"Existing Google user {uid} logged in.")
240
 
241
  return jsonify({
242
  'success': True,
 
253
  @app.route('/api/user/request-credits', methods=['POST'])
254
  @credit_required(cost=0)
255
  def request_credits():
256
+ if not FIREBASE_INITIALIZED:
257
+ logging.error("Firebase not initialized. Request credits skipped.")
258
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
259
+
260
  try:
261
  auth_header = request.headers.get('Authorization', '')
262
  token = auth_header.split(' ')[1]
 
283
  @credit_required(cost=0)
284
  @cross_origin()
285
  def submit_feedback():
286
+ if not FIREBASE_INITIALIZED:
287
+ logging.error("Firebase not initialized. Submit feedback skipped.")
288
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
289
+
290
  try:
291
  auth_header = request.headers.get('Authorization', '')
292
  token = auth_header.split(' ')[1]
 
318
 
319
  @app.route('/api/admin/profile', methods=['GET'])
320
  def get_admin_profile():
321
+ if not FIREBASE_INITIALIZED:
322
+ logging.error("Firebase not initialized. Admin profile skipped.")
323
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
324
+
325
  try:
326
  admin_uid = verify_admin(request.headers.get('Authorization', ''))
327
  admin_data = db.reference(f'users/{admin_uid}').get()
 
360
 
361
  @app.route('/api/admin/credit_requests', methods=['GET'])
362
  def list_credit_requests():
363
+ if not FIREBASE_INITIALIZED:
364
+ logging.error("Firebase not initialized. List credit requests skipped.")
365
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
366
+
367
  try:
368
  verify_admin(request.headers.get('Authorization', ''))
369
  requests_ref = db.reference('credit_requests')
 
376
 
377
  @app.route('/api/admin/credit_requests/<string:request_id>', methods=['PUT'])
378
  def process_credit_request(request_id):
379
+ if not FIREBASE_INITIALIZED:
380
+ logging.error("Firebase not initialized. Process credit request skipped.")
381
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
382
+
383
  try:
384
  admin_uid = verify_admin(request.headers.get('Authorization', ''))
385
  req_ref = db.reference(f'credit_requests/{request_id}')
 
418
 
419
  @app.route('/api/admin/users', methods=['GET'])
420
  def admin_list_users():
421
+ if not FIREBASE_INITIALIZED:
422
+ logging.error("Firebase not initialized. Admin list users skipped.")
423
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
424
+
425
  try:
426
  verify_admin(request.headers.get('Authorization', ''))
427
  users_ref = db.reference('users')
 
444
 
445
  @app.route('/api/admin/users/search', methods=['GET'])
446
  def admin_search_users():
447
+ if not FIREBASE_INITIALIZED:
448
+ logging.error("Firebase not initialized. Admin search users skipped.")
449
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
450
+
451
  try:
452
  verify_admin(request.headers.get('Authorization', ''))
453
  email_query = request.args.get('email', '').lower().strip()
 
476
 
477
  @app.route('/api/admin/users/<string:uid>/suspend', methods=['PUT'])
478
  def admin_suspend_user(uid):
479
+ if not FIREBASE_INITIALIZED:
480
+ logging.error("Firebase not initialized. Admin suspend user skipped.")
481
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
482
+
483
  try:
484
  verify_admin(request.headers.get('Authorization', ''))
485
  data = request.get_json()
 
504
 
505
  @app.route('/api/admin/notifications', methods=['POST'])
506
  def send_notifications():
507
+ if not FIREBASE_INITIALIZED:
508
+ logging.error("Firebase not initialized. Send notifications skipped.")
509
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
510
+
511
  try:
512
  admin_uid = verify_admin(request.headers.get('Authorization', ''))
513
  data = request.get_json()
 
555
 
556
  @app.route('/api/admin/feedback', methods=['GET'])
557
  def admin_view_feedback():
558
+ if not FIREBASE_INITIALIZED:
559
+ logging.error("Firebase not initialized. Admin view feedback skipped.")
560
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
561
+
562
  try:
563
  admin_uid = verify_admin(request.headers.get('Authorization', ''))
564
  feedback_type = request.args.get('type')
 
590
 
591
  @app.route('/api/admin/users/<string:uid>/credits', methods=['PUT'])
592
  def admin_update_credits(uid):
593
+ if not FIREBASE_INITIALIZED:
594
+ logging.error("Firebase not initialized. Admin update credits skipped.")
595
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
596
+
597
  try:
598
  verify_admin(request.headers.get('Authorization', ''))
599
  data = request.get_json()
 
750
  return df
751
 
752
  def _scrape_player_index_brscraper():
753
+ seasons_to_try_for_index = get_available_seasons_util(num_seasons=2)
 
754
 
755
  for season_str in seasons_to_try_for_index:
756
  end_year = int(season_str.split('–')[1])
 
760
 
761
  if not df.empty and 'Player' in df.columns:
762
  player_names = df['Player'].dropna().unique().tolist()
 
763
  player_names = [normalize_string(name) for name in player_names]
764
  logging.info(f"Successfully retrieved {len(player_names)} players for index from {season_str}.")
765
  return pd.DataFrame({'name': player_names})
 
768
  except Exception as e:
769
  logging.warning(f"Error fetching player index with BRScraper for {season_str}: {e}. Trying next season.")
770
 
 
771
  logging.error("Failed to fetch player index from recent seasons. Falling back to curated common players list.")
772
  common_players = [
773
  'LeBron James', 'Stephen Curry', 'Kevin Durant', 'Giannis Antetokounmpo',
774
+ 'Nikola Jokic',
775
+ 'Joel Embiid', 'Jayson Tatum', 'Luka Doncic',
776
  'Damian Lillard', 'Jimmy Butler', 'Kawhi Leonard', 'Paul George',
777
  'Anthony Davis', 'Rudy Gobert', 'Donovan Mitchell', 'Trae Young',
778
  'Devin Booker', 'Karl-Anthony Towns', 'Zion Williamson', 'Ja Morant',
 
781
  ]
782
  return pd.DataFrame({'name': common_players})
783
 
784
+ def get_player_career_stats_brscraper(player_name, seasons_to_fetch: list[str], playoffs=False):
785
  if not BRSCRAPER_AVAILABLE:
786
  logging.error("BRScraper is not available. Cannot fetch player career stats.")
787
  return pd.DataFrame()
 
788
 
 
789
  normalized_player_name = normalize_string(player_name)
790
+ all_rows = []
791
 
792
+ for season_str in seasons_to_fetch:
 
 
793
  end_year = int(season_str.split('–')[1])
794
 
795
+ cache_key = f"{normalized_player_name}_{end_year}_{'playoffs' if playoffs else 'regular'}"
796
+ db_ref = db.reference(f'scraped_data/player_season_stats/{cache_key}')
797
+
798
+ if FIREBASE_INITIALIZED:
799
+ cached_data = db_ref.get()
800
+ if cached_data and not is_data_stale(cached_data.get('last_updated'), max_age_hours=24*7):
801
+ logging.info(f"Loading stats for {player_name} in {season_str} (playoffs: {playoffs}) from Firebase cache.")
802
+ all_rows.append(pd.DataFrame.from_records(cached_data['data']))
803
+ continue # Skip scraping for this season if found in cache
804
+ else:
805
+ logging.info(f"Stats for {player_name} in {season_str} cache stale or not found. Scraping...")
806
+
807
+ for attempt in range(3):
808
  try:
809
  logging.info(f"DEBUG: Attempt {attempt+1} for nba.get_stats for player '{player_name}' in season {season_str} (year: {end_year}, playoffs: {playoffs})...")
810
 
 
812
 
813
  if df_season.empty:
814
  logging.warning(f"DEBUG: nba.get_stats returned empty DataFrame for {player_name} in {season_str} on attempt {attempt+1}. Retrying...")
815
+ time.sleep(1)
816
+ continue
817
 
818
  if 'Player' not in df_season.columns:
819
  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...")
820
  time.sleep(1)
821
  continue
822
 
 
823
  df_season['Player_Normalized'] = df_season['Player'].apply(normalize_string)
 
824
  row = df_season[df_season['Player_Normalized'] == normalized_player_name]
825
 
826
  if not row.empty:
827
  row = row.copy()
828
  row['Season'] = season_str
 
829
  row = row.drop(columns=['Player_Normalized'], errors='ignore')
830
+
831
+ if FIREBASE_INITIALIZED:
832
+ df_cleaned_for_firebase = clean_df_for_firebase(row.copy())
833
+ db_ref.set({
834
+ 'last_updated': datetime.utcnow().isoformat(),
835
+ 'data': df_cleaned_for_firebase.to_dict(orient='records')
836
+ })
837
+ logging.info(f"Stats for {player_name} in {season_str} saved to Firebase cache.")
838
+
839
  all_rows.append(row)
840
  logging.info(f"DEBUG: Found stats for {player_name} in {season_str} on attempt {attempt+1}. Appending row.")
841
+ break
842
  else:
843
  logging.info(f"DEBUG: Player {player_name} not found in {season_str} stats (after getting season data) on attempt {attempt+1}. Retrying...")
844
  time.sleep(1)
845
+ continue
846
 
847
  except Exception as e:
848
  logging.warning(f"DEBUG: Exception on attempt {attempt+1} when fetching {season_str} {'playoff' if playoffs else 'regular season'} stats for {player_name}: {e}")
849
+ time.sleep(1)
850
+ if attempt == 2:
851
  logging.error(f"DEBUG: All 3 attempts failed for {player_name} in {season_str}. Giving up on this season.")
852
+ continue
853
 
854
+ time.sleep(0.5) # Delay between seasons
855
+
856
  if not all_rows:
857
+ logging.warning(f"DEBUG: No stats found for {player_name} in the requested seasons: {seasons_to_fetch}. Returning empty DataFrame.")
858
  return pd.DataFrame()
859
 
860
  df = pd.concat(all_rows, ignore_index=True)
 
874
  if col not in non_num:
875
  df[col] = pd.to_numeric(df[col], errors='coerce')
876
 
877
+ df['Player'] = player_name
878
  df = df.replace({np.nan: None})
879
  return df
880
 
881
  def get_dashboard_info_brscraper():
882
+ if not FIREBASE_INITIALIZED:
883
+ logging.error("Firebase not initialized. Dashboard info skipped.")
884
  return {}
885
 
886
  if not FIREBASE_INITIALIZED:
 
1052
  all_player_season_data = []
1053
  players_with_no_data = []
1054
 
1055
+ # Handle individual player stats (1 player, 1 season)
1056
+ if len(selected_players) == 1 and len(selected_seasons) == 1:
1057
+ player_name = selected_players[0]
1058
+ season_str = selected_seasons[0]
1059
+ df_player_data = get_player_career_stats_brscraper(player_name, seasons_to_fetch=[season_str], playoffs=False)
1060
 
1061
+ if not df_player_data.empty:
1062
+ all_player_season_data.append(df_player_data)
1063
+ logging.info(f"Successfully retrieved data for {player_name} in {season_str}.")
1064
+ else:
1065
  players_with_no_data.append(player_name)
1066
+ logging.info(f"No data found for {player_name} in {season_str}.")
1067
+
1068
+ # Handle comparison (2 players, 2 seasons)
1069
+ elif len(selected_players) == 2 and len(selected_seasons) == 2:
1070
+ player1_name = selected_players[0]
1071
+ player1_season = selected_seasons[0]
1072
+ player2_name = selected_players[1]
1073
+ player2_season = selected_seasons[1]
1074
+
1075
+ df_player1_data = get_player_career_stats_brscraper(player1_name, seasons_to_fetch=[player1_season], playoffs=False)
1076
+ if not df_player1_data.empty:
1077
+ all_player_season_data.append(df_player1_data)
1078
+ logging.info(f"Successfully retrieved data for {player1_name} in {player1_season}.")
1079
+ else:
1080
+ players_with_no_data.append(player1_name)
1081
+ logging.info(f"No data found for {player1_name} in {player1_season}.")
1082
 
1083
+ # Add a delay between fetching data for player 1 and player 2
1084
+ time.sleep(2) # Introduce a 2-second delay
1085
+
1086
+ df_player2_data = get_player_career_stats_brscraper(player2_name, seasons_to_fetch=[player2_season], playoffs=False)
1087
+ if not df_player2_data.empty:
1088
+ all_player_season_data.append(df_player2_data)
1089
+ logging.info(f"Successfully retrieved data for {player2_name} in {player2_season}.")
1090
  else:
1091
+ players_with_no_data.append(player2_name)
1092
+ logging.info(f"No data found for {player2_name} in {player2_season}.")
1093
+ else:
1094
+ return jsonify({'error': 'Invalid combination of players and seasons. Expected 1 player/1 season or 2 players/2 seasons.'}), 400
1095
+
1096
 
1097
  if not all_player_season_data:
1098
  logging.warning("After processing all players, 'all_player_season_data' is empty. Returning 404.")
 
1103
 
1104
  comparison_df_raw = pd.concat(all_player_season_data, ignore_index=True)
1105
 
1106
+ basic_display_df = comparison_df_raw.copy()
 
 
 
 
1107
  basic_cols = ['Player', 'Season', 'GP', 'MIN', 'PTS', 'REB', 'AST', 'STL', 'BLK', 'FG_PCT', 'FT_PCT', 'FG3_PCT']
1108
  basic_display_df = basic_display_df[[c for c in basic_cols if c in basic_display_df.columns]].round(2)
1109
 
 
1115
  lambda r: r['PTS'] / (2 * (r['FGA'] + 0.44 * r['FTA'])) if (r['FGA'] + 0.44 * r['FTA']) else 0,
1116
  axis=1
1117
  )
 
 
 
 
 
1118
  advanced_cols = ['Player', 'Season', 'PTS', 'REB', 'AST', 'FG_PCT', 'TS_PCT']
1119
+ advanced_display_df = advanced_df[[c for c in advanced_cols if c in advanced_df.columns]].round(3)
1120
 
1121
  return jsonify({
1122
  'basic_stats': basic_display_df.to_dict(orient='records'),
 
1141
  all_player_season_data = []
1142
  players_with_no_data = []
1143
 
1144
+ if len(selected_players) == 1 and len(selected_seasons) == 1:
1145
+ player_name = selected_players[0]
1146
+ season_str = selected_seasons[0]
1147
+ df_player_data = get_player_career_stats_brscraper(player_name, seasons_to_fetch=[season_str], playoffs=True)
 
 
 
 
1148
 
1149
+ if not df_player_data.empty:
1150
+ all_player_season_data.append(df_player_data)
1151
+ logging.info(f"Successfully retrieved playoff data for {player_name} in {season_str}.")
1152
  else:
 
1153
  players_with_no_data.append(player_name)
1154
+ logging.info(f"No playoff data found for {player_name} in {season_str}.")
1155
+
1156
+ elif len(selected_players) == 2 and len(selected_seasons) == 2:
1157
+ player1_name = selected_players[0]
1158
+ player1_season = selected_seasons[0]
1159
+ player2_name = selected_players[1]
1160
+ player2_season = selected_seasons[1]
1161
+
1162
+ df_player1_data = get_player_career_stats_brscraper(player1_name, seasons_to_fetch=[player1_season], playoffs=True)
1163
+ if not df_player1_data.empty:
1164
+ all_player_season_data.append(df_player1_data)
1165
+ logging.info(f"Successfully retrieved playoff data for {player1_name} in {player1_season}.")
1166
+ else:
1167
+ players_with_no_data.append(player1_name)
1168
+ logging.info(f"No playoff data found for {player1_name} in {player1_season}.")
1169
+
1170
+ time.sleep(2) # Introduce a 2-second delay
1171
+
1172
+ df_player2_data = get_player_career_stats_brscraper(player2_name, seasons_to_fetch=[player2_season], playoffs=True)
1173
+ if not df_player2_data.empty:
1174
+ all_player_season_data.append(df_player2_data)
1175
+ logging.info(f"Successfully retrieved playoff data for {player2_name} in {player2_season}.")
1176
+ else:
1177
+ players_with_no_data.append(player2_name)
1178
+ logging.info(f"No playoff data found for {player2_name} in {player2_season}.")
1179
+ else:
1180
+ return jsonify({'error': 'Invalid combination of players and seasons. Expected 1 player/1 season or 2 players/2 seasons.'}), 400
1181
+
1182
 
1183
  if not all_player_season_data:
1184
  logging.warning("After processing all players, 'all_player_season_data' is empty for playoffs. Returning 404.")
 
1189
 
1190
  comparison_df_raw = pd.concat(all_player_season_data, ignore_index=True)
1191
 
1192
+ basic_display_df = comparison_df_raw.copy()
 
 
 
 
1193
  basic_cols = ['Player', 'Season', 'GP', 'MIN', 'PTS', 'REB', 'AST', 'STL', 'BLK', 'FG_PCT', 'FT_PCT', 'FG3_PCT']
1194
  basic_display_df = basic_display_df[[c for c in basic_cols if c in basic_display_df.columns]].round(2)
1195
 
 
1201
  lambda r: r['PTS'] / (2 * (r['FGA'] + 0.44 * r['FTA'])) if (r['FGA'] + 0.44 * r['FTA']) else 0,
1202
  axis=1
1203
  )
 
 
 
 
 
1204
  advanced_cols = ['Player', 'Season', 'PTS', 'REB', 'AST', 'FG_PCT', 'TS_PCT']
1205
+ advanced_display_df = advanced_df[[c for c in advanced_cols if c in advanced_df.columns]].round(3)
1206
 
1207
  return jsonify({
1208
  'basic_stats': basic_display_df.to_dict(orient='records'),
 
1282
  @credit_required(cost=0)
1283
  @cross_origin()
1284
  def dashboard_info():
1285
+ if not FIREBASE_INITIALIZED:
1286
+ logging.error("Firebase not initialized. Dashboard info skipped.")
1287
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
1288
+
1289
  try:
1290
  dashboard_data = get_dashboard_info_brscraper()
1291
  if not dashboard_data:
 
1299
  @credit_required(cost=1)
1300
  @cross_origin()
1301
  def perplexity_explain():
1302
+ if not FIREBASE_INITIALIZED:
1303
+ logging.error("Firebase not initialized. Perplexity explain skipped.")
1304
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
1305
+
1306
  try:
1307
  data = request.get_json()
1308
  prompt = data.get('prompt')
 
1340
  @credit_required(cost=0)
1341
  @cross_origin()
1342
  def get_user_analyses():
1343
+ if not FIREBASE_INITIALIZED:
1344
+ logging.error("Firebase not initialized. Get user analyses skipped.")
1345
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
1346
+
1347
  try:
1348
  auth_header = request.headers.get('Authorization', '')
1349
  token = auth_header.split(' ')[1]
 
1375
  @credit_required(cost=0)
1376
  @cross_origin()
1377
  def delete_user_analysis(analysis_id):
1378
+ if not FIREBASE_INITIALIZED:
1379
+ logging.error("Firebase not initialized. Delete user analysis skipped.")
1380
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
1381
+
1382
  try:
1383
  auth_header = request.headers.get('Authorization', '')
1384
  token = auth_header.split(' ')[1]
 
1405
  @credit_required(cost=1)
1406
  @cross_origin()
1407
  def perplexity_chat():
1408
+ if not FIREBASE_INITIALIZED:
1409
+ logging.error("Firebase not initialized. Perplexity chat skipped.")
1410
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
1411
+
1412
  try:
1413
  data = request.get_json()
1414
  prompt = data.get('prompt')
 
1449
  @credit_required(cost=1)
1450
  @cross_origin()
1451
  def awards_predictor():
1452
+ if not FIREBASE_INITIALIZED:
1453
+ logging.error("Firebase not initialized. Awards predictor skipped.")
1454
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
1455
+
1456
  try:
1457
  data = request.get_json()
1458
  award_type = data.get('award_type')
 
1475
  @credit_required(cost=1)
1476
  @cross_origin()
1477
  def young_player_projection():
1478
+ if not FIREBASE_INITIALIZED:
1479
+ logging.error("Firebase not initialized. Young player projection skipped.")
1480
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
1481
+
1482
  try:
1483
  data = request.get_json()
1484
  player_name = data.get('player_name')
 
1512
  @credit_required(cost=1)
1513
  @cross_origin()
1514
  def similar_players():
1515
+ if not FIREBASE_INITIALIZED:
1516
+ logging.error("Firebase not initialized. Similar players skipped.")
1517
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
1518
+
1519
  try:
1520
  data = request.get_json()
1521
  target_player = data.get('target_player')
 
1560
  @credit_required(cost=1)
1561
  @cross_origin()
1562
  def manual_player_compare():
1563
+ if not FIREBASE_INITIALIZED:
1564
+ logging.error("Firebase not initialized. Manual player compare skipped.")
1565
+ return jsonify({'error': 'Server configuration error: Firebase not ready.'}), 500
1566
+
1567
  try:
1568
  data = request.get_json()
1569
  player1_name = data.get('player1_name')
 
1606
 
1607
 
1608
  if __name__ == '__main__':
1609
+ app.run(debug=True, host="0.0.0.0", port=7860)