From 7eb5935ee181d34c0790c8e13b5e48d626607c1d Mon Sep 17 00:00:00 2001 From: Ritvik Uppal Date: Sun, 21 Dec 2025 13:38:37 -0500 Subject: [PATCH 1/3] Adding more Unit tests and fixing workflows --- .github/workflows/weekly-database-refresh.yml | 23 ++++ check_weekly_refresh.py | 38 +++---- src/database/analyze_performance_gaps.py | 54 +++++----- src/database/check_fixture_quality.py | 22 ++-- src/database/database_refresh.py | 25 +++-- src/database/export_complete_data.py | 87 ++++++++------- src/database/fast_performance_refresh.py | 37 +++---- src/database/verify_fix.py | 14 +-- src/database/verify_player_performances.py | 15 +-- src/test_real_players.py | 2 +- .../test_basic.cpython-313-pytest-8.4.2.pyc | Bin 20485 -> 20477 bytes .../test_simple.cpython-313-pytest-8.4.2.pyc | Bin 19228 -> 19228 bytes src/tests/test_database_refresh.py | 102 ++++++++++++++++++ src/tests/test_update_player_prices.py | 94 ++++++++++++++++ 14 files changed, 371 insertions(+), 142 deletions(-) create mode 100644 src/tests/test_database_refresh.py create mode 100644 src/tests/test_update_player_prices.py diff --git a/.github/workflows/weekly-database-refresh.yml b/.github/workflows/weekly-database-refresh.yml index 015a70b..b704a03 100644 --- a/.github/workflows/weekly-database-refresh.yml +++ b/.github/workflows/weekly-database-refresh.yml @@ -17,6 +17,16 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + # Validate required repository secrets are available + - name: Validate required secrets + run: | + echo "Checking required repository secrets..." + if [ -z "${{ secrets.SUPABASE_URL }}" ] || [ -z "${{ secrets.SUPABASE_KEY }}" ] || [ -z "${{ secrets.API_KEY }}" ]; then + echo "ERROR: One or more required secrets are missing." + echo "Please add the following repository secrets: SUPABASE_URL, SUPABASE_KEY, API_KEY" + exit 1 + fi + # Set up Python - name: Set up Python uses: actions/setup-python@v5 @@ -115,6 +125,19 @@ jobs: print('āš ļø Database credentials not available for logging') " + # Run unit tests to validate the codebase after refresh + - name: Run tests + env: + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} + API_KEY: ${{ secrets.API_KEY }} + run: | + echo "Installing test dependencies..." + python -m pip install --upgrade pip + pip install pytest pytest-mock + echo "Running pytest..." + pytest src/tests/ --maxfail=1 -q + # Create success summary with timestamp - name: Create success summary if: success() diff --git a/check_weekly_refresh.py b/check_weekly_refresh.py index 26edf84..39dd399 100644 --- a/check_weekly_refresh.py +++ b/check_weekly_refresh.py @@ -17,7 +17,7 @@ def check_refresh_status(): """Check the status of the weekly database refresh.""" - print("šŸ” Weekly Database Refresh Status Check") + print("Weekly Database Refresh Status Check") print("=" * 50) # 1. Check database refresh logs @@ -53,16 +53,16 @@ def check_refresh_status(): status = log.get("status", "Unknown") refresh_type = log.get("refresh_type", "Unknown") - status_icon = "āœ…" if status == "completed" else "āŒ" - print(f" {status_icon} {timestamp} - {refresh_type} - {status}") + status_label = "COMPLETED" if status == "completed" else "FAILED" + print(f" {status_label} {timestamp} - {refresh_type} - {status}") else: - print(" ā„¹ļø No recent refresh logs found") + print(" INFO: No recent refresh logs found") except Exception as e: print(f" āš ļø Could not fetch refresh logs: {e}") # 2. Check data freshness - print("\nšŸ“… Data Freshness Check:") + print("\nData Freshness Check:") try: # Check latest player performance data latest_perf = ( @@ -75,7 +75,7 @@ def check_refresh_status(): if latest_perf.data: latest_time = latest_perf.data[0]["created_at"] - print(f" šŸ“ˆ Latest performance data: {latest_time}") + print(f" Latest performance data: {latest_time}") # Check if data is recent (within last 3 days) latest_dt = datetime.fromisoformat(latest_time.replace("Z", "+00:00")) @@ -84,17 +84,17 @@ def check_refresh_status(): ).days if days_old <= 3: - print(f" āœ… Data is fresh ({days_old} days old)") + print(f" Data is fresh ({days_old} days old)") else: - print(f" āš ļø Data may be stale ({days_old} days old)") + print(f" WARNING: Data may be stale ({days_old} days old)") else: - print(" āŒ No performance data found") + print(" ERROR: No performance data found") except Exception as e: print(f" āš ļø Could not check data freshness: {e}") # 3. Check current gameweek data - print("\nšŸŽ® Current Gameweek Check:") + print("\nCurrent Gameweek Check:") try: current_gw = ( supabase.table("players").select("current_gameweek").limit(1).execute() @@ -102,9 +102,9 @@ def check_refresh_status(): if current_gw.data and current_gw.data[0].get("current_gameweek"): gw = current_gw.data[0]["current_gameweek"] - print(f" šŸŽÆ Current gameweek: {gw}") + print(f" Current gameweek: {gw}") else: - print(" ā„¹ļø Gameweek data not available") + print(" INFO: Gameweek data not available") except Exception as e: print(f" āš ļø Could not check gameweek: {e}") @@ -114,19 +114,19 @@ def check_refresh_status(): return False # 4. Manual verification instructions - print("\nšŸ”— Manual Verification:") + print("\nManual Verification:") print( " 1. GitHub Actions: https://github.com/ritvikiscool9/fpl-predictor/actions" ) print(" 2. Look for 'Weekly Database Refresh' workflows") - print(" 3. Check for green checkmarks āœ… indicating success") - print(" 4. Review logs if any runs show red X āŒ") + print(" 3. Check for successful runs indicating success") + print(" 4. Review logs if any runs show failures") - print("\nšŸ’” Next Steps:") - print(" • If no recent refresh: Check GitHub Actions for failures") - print(" • If data is stale: Run manual refresh with:") + print("\nNext Steps:") + print(" - If no recent refresh: Check GitHub Actions for failures") + print(" - If data is stale: Run manual refresh with:") print(" python src/database/fast_performance_refresh.py") - print(" • If errors persist: Check Supabase credentials and API limits") + print(" - If errors persist: Check Supabase credentials and API limits") return True diff --git a/src/database/analyze_performance_gaps.py b/src/database/analyze_performance_gaps.py index ac58d8e..f1c2c1b 100644 --- a/src/database/analyze_performance_gaps.py +++ b/src/database/analyze_performance_gaps.py @@ -16,7 +16,7 @@ def analyze_player_performances_gaps(): """Analyze what gameweeks are missing in player_performances""" - print("šŸ” Analyzing player_performances data gaps...") + print("Analyzing player_performances data gaps...") print("=" * 60) try: @@ -24,7 +24,7 @@ def analyze_player_performances_gaps(): result = supabase.table("player_performances").select("gameweek_id").execute() if not result.data: - print("āŒ No player_performances data found!") + print("ERROR: No player_performances data found!") return df = pd.DataFrame(result.data) @@ -33,12 +33,12 @@ def analyze_player_performances_gaps(): gameweeks_present = sorted(df["gameweek_id"].unique()) total_records = len(df) - print(f"šŸ“Š Total records: {total_records:,}") - print(f"šŸŽÆ Gameweeks with data: {gameweeks_present}") + print(f"Total records: {total_records:,}") + print(f"Gameweeks with data: {gameweeks_present}") # Count records per gameweek gw_counts = df["gameweek_id"].value_counts().sort_index() - print(f"\nšŸ“ˆ Records per gameweek:") + print(f"\nRecords per gameweek:") for gw, count in gw_counts.items(): print(f" GW {gw}: {count:,} records") @@ -48,9 +48,9 @@ def analyze_player_performances_gaps(): missing_gws = [gw for gw in expected_gws if gw not in gameweeks_present] if missing_gws: - print(f"\nāš ļø Missing gameweeks: {missing_gws}") + print(f"\nMissing gameweeks: {missing_gws}") else: - print(f"\nāœ… All gameweeks 1-{max(gameweeks_present)} are present") + print(f"\nAll gameweeks 1-{max(gameweeks_present)} are present") # Check for consistency if len(gw_counts) > 1: @@ -61,14 +61,14 @@ def analyze_player_performances_gaps(): if count < avg_records * 0.8: # Less than 80% of average inconsistent_gws.append(f"GW{gw}({count})") - if inconsistent_gws: - print(f"\nāš ļø Gameweeks with low record counts: {inconsistent_gws}") - print(f" Average records per GW: {avg_records:.0f}") + if inconsistent_gws: + print(f"\nGameweeks with low record counts: {inconsistent_gws}") + print(f" Average records per GW: {avg_records:.0f}") return gameweeks_present, gw_counts.to_dict() except Exception as e: - print(f"āŒ Error analyzing data: {e}") + print(f"ERROR: Error analyzing data: {e}") return None, None @@ -76,7 +76,7 @@ def check_fpl_api_availability(): """Check what gameweeks are available from FPL API""" import requests - print(f"\n🌐 Checking FPL API availability...") + print(f"\nChecking FPL API availability...") try: # Get current season info @@ -91,8 +91,8 @@ def check_fpl_api_availability(): (gw["id"] for gw in data["events"] if gw.get("is_current", False)), None ) - print(f"āœ… Finished gameweeks available: {finished_gws}") - print(f"šŸ“ Current gameweek: {current_gw}") + print(f"Finished gameweeks available: {finished_gws}") + print(f"Current gameweek: {current_gw}") # Test API access for a specific gameweek if finished_gws: @@ -100,26 +100,22 @@ def check_fpl_api_availability(): test_url = f"https://fantasy.premierleague.com/api/event/{test_gw}/live/" test_response = requests.get(test_url) - if test_response.status_code == 200: + if test_response.status_code == 200: test_data = test_response.json() player_count = len(test_data.get("elements", [])) - print( - f"āœ… API test successful - GW{test_gw} has {player_count} player records" - ) + print(f"API test successful - GW{test_gw} has {player_count} player records") else: - print( - f"āš ļø API test failed for GW{test_gw}: HTTP {test_response.status_code}" - ) + print(f"API test failed for GW{test_gw}: HTTP {test_response.status_code}") return finished_gws, current_gw except Exception as e: - print(f"āŒ Error checking FPL API: {e}") + print(f"ERROR: Error checking FPL API: {e}") return None, None def main(): - print("šŸ† FPL Database - Player Performances Gap Analysis") + print("FPL Database - Player Performances Gap Analysis") print("=" * 70) # Analyze current database @@ -132,17 +128,17 @@ def main(): if db_gameweeks and api_gameweeks: missing_from_db = [gw for gw in api_gameweeks if gw not in db_gameweeks] - print(f"\nšŸŽÆ RECOMMENDATION:") + print(f"\nRECOMMENDATION:") if missing_from_db: - print(f" āš ļø Your database is missing data for: {missing_from_db}") - print(f" šŸ’” Run: echo 3 | python database_refresh.py") + print(f" Your database is missing data for: {missing_from_db}") + print(f" TIP: Run: echo 3 | python database_refresh.py") print(f" This will populate all missing gameweeks") else: - print(f" āœ… Your database has all available gameweeks!") + print(f" Your database has all available gameweeks!") if db_counts and len(set(db_counts.values())) > 1: - print(f" āš ļø Some gameweeks have inconsistent record counts") - print(f" šŸ’” Consider refreshing to ensure data quality") + print(f" Some gameweeks have inconsistent record counts") + print(f" TIP: Consider refreshing to ensure data quality") if __name__ == "__main__": diff --git a/src/database/check_fixture_quality.py b/src/database/check_fixture_quality.py index cd26ce1..f7a09ba 100644 --- a/src/database/check_fixture_quality.py +++ b/src/database/check_fixture_quality.py @@ -17,11 +17,11 @@ def check_fixture_data_quality(): """Check fixture data for inconsistencies""" - print("šŸ” Checking Fixture Data Quality...") + print("Checking Fixture Data Quality...") print("=" * 50) # Check gameweeks table - print("\nšŸ“… GAMEWEEKS TABLE:") + print("\nGAMEWEEKS TABLE:") try: gameweeks_result = supabase.table("gameweeks").select("*").order("id").execute() if gameweeks_result.data: @@ -33,19 +33,19 @@ def check_fixture_data_quality(): recent_gws = gw_df.tail(5) print("\nRecent gameweeks:") for _, row in recent_gws.iterrows(): - status = "āœ… FINISHED" if row.get("is_finished") else "ā³ UPCOMING" - current = "šŸ“ CURRENT" if row.get("is_current") else "" + status = "FINISHED" if row.get("is_finished") else "UPCOMING" + current = "CURRENT" if row.get("is_current") else "" print( f" GW {row.get('id', 'N/A')}: {row.get('deadline_time', 'N/A')} - {status} {current}" ) else: - print("āŒ No gameweeks found") + print("No gameweeks found") except Exception as e: print(f"āŒ Error checking gameweeks: {e}") # Check fixtures table - print("\nšŸŸļø FIXTURES TABLE:") + print("\nFIXTURES TABLE:") try: fixtures_result = ( supabase.table("fixtures") @@ -109,13 +109,13 @@ def check_fixture_data_quality(): print(fixtures_df[available_cols].head(10).to_string(index=False)) else: - print("āŒ No fixtures found") + print("No fixtures found") except Exception as e: print(f"āŒ Error checking fixtures: {e}") # Check for data inconsistencies - print(f"\n🚨 INCONSISTENCY CHECK:") + print(f"\nINCONSISTENCY CHECK:") try: # Find gameweeks marked as finished finished_gws_result = ( @@ -152,15 +152,15 @@ def check_fixture_data_quality(): if finished_count == 0 or with_scores == 0: print( - f" āš ļø GW {gw}: Marked finished but {finished_count}/{len(fixtures_df)} fixtures finished, {with_scores}/{len(fixtures_df)} have scores" + f" GW {gw}: Marked finished but {finished_count}/{len(fixtures_df)} fixtures finished, {with_scores}/{len(fixtures_df)} have scores" ) else: print( - f" āœ… GW {gw}: {finished_count}/{len(fixtures_df)} fixtures finished, {with_scores}/{len(fixtures_df)} have scores" + f" GW {gw}: {finished_count}/{len(fixtures_df)} fixtures finished, {with_scores}/{len(fixtures_df)} have scores" ) except Exception as e: - print(f"āŒ Error checking inconsistencies: {e}") + print(f"Error checking inconsistencies: {e}") if __name__ == "__main__": diff --git a/src/database/database_refresh.py b/src/database/database_refresh.py index 8ead23b..6ef5317 100644 --- a/src/database/database_refresh.py +++ b/src/database/database_refresh.py @@ -30,8 +30,13 @@ def __init__(self): def log(self, message: str, level: str = "INFO"): """Enhanced logging with timestamps""" timestamp = datetime.now().strftime("%H:%M:%S") - prefix = {"INFO": "ā„¹ļø", "SUCCESS": "āœ…", "WARNING": "āš ļø", "ERROR": "āŒ"} - print(f"[{timestamp}] {prefix.get(level, 'ā„¹ļø')} {message}") + prefix = { + "INFO": "INFO", + "SUCCESS": "SUCCESS", + "WARNING": "WARNING", + "ERROR": "ERROR", + } + print(f"[{timestamp}] {prefix.get(level, 'INFO')} {message}") def clear_table(self, table_name: str, condition: dict = None): """Safely clear table data with optional condition""" @@ -462,7 +467,7 @@ def refresh_player_performances_current_season(self): def full_database_refresh(self, include_historical: bool = True): """Perform complete database refresh""" - self.log("šŸš€ Starting FULL DATABASE REFRESH", "SUCCESS") + self.log("Starting FULL DATABASE REFRESH", "SUCCESS") self.log("=" * 60) start_time = time.time() @@ -487,14 +492,14 @@ def full_database_refresh(self, include_historical: bool = True): # Summary elapsed = time.time() - start_time self.log("=" * 60) - self.log(f"šŸŽ‰ DATABASE REFRESH COMPLETE! ({elapsed:.1f}s)", "SUCCESS") + self.log(f"DATABASE REFRESH COMPLETE! ({elapsed:.1f}s)", "SUCCESS") # Verify refresh self.verify_database_health() def verify_database_health(self): """Verify database has been properly refreshed""" - self.log("\nšŸ” Database Health Check:") + self.log("\nDatabase Health Check:") tables_to_check = [ ("teams", 20), @@ -516,10 +521,10 @@ def verify_database_health(self): count = result.count if result.count else 0 if count >= expected_min: - self.log(f" āœ… {table}: {count:,} records", "SUCCESS") + self.log(f" {table}: {count:,} records", "SUCCESS") else: self.log( - f" āš ļø {table}: {count:,} records (expected >{expected_min:,})", + f" {table}: {count:,} records (expected >{expected_min:,})", "WARNING", ) all_healthy = False @@ -529,14 +534,14 @@ def verify_database_health(self): all_healthy = False if all_healthy: - self.log("\nšŸŽ‰ All tables are healthy!", "SUCCESS") + self.log("\nAll tables are healthy!", "SUCCESS") else: - self.log("\nāš ļø Some tables may need attention", "WARNING") + self.log("\nSome tables may need attention", "WARNING") def main(): """Main execution function""" - print("šŸ† FPL Database Refresh System") + print("FPL Database Refresh System") print("=" * 60) refresher = FPLDatabaseRefresh() diff --git a/src/database/export_complete_data.py b/src/database/export_complete_data.py index 8338e90..5a1901c 100644 --- a/src/database/export_complete_data.py +++ b/src/database/export_complete_data.py @@ -8,7 +8,12 @@ import pandas as pd from datetime import datetime -sys.path.append(os.path.join(os.path.dirname(__file__), "config")) +config_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", "config") +) +if config_path not in sys.path: + sys.path.append(config_path) + from supabase_client import get_supabase_client @@ -18,7 +23,7 @@ def __init__(self): def export_player_performances(self, output_path=None): """Export complete player_performances data with pagination""" - print("šŸ” Exporting complete player_performances data...") + print("Exporting complete player_performances data...") if not output_path: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") @@ -28,7 +33,7 @@ def export_player_performances(self, output_path=None): batch_size = 1000 offset = 0 - print(f"šŸ“Š Using pagination (batches of {batch_size})...") + print(f"Using pagination (batches of {batch_size})...") while True: try: @@ -43,7 +48,7 @@ def export_player_performances(self, output_path=None): ) if not result.data or len(result.data) == 0: - print(f" šŸ“„ No more data at offset {offset}") + print(f" No more data at offset {offset}") break batch_count = len(result.data) @@ -55,7 +60,7 @@ def export_player_performances(self, output_path=None): max_gw = max(gameweeks) print( - f" šŸ“„ Batch {offset//batch_size + 1}: {batch_count} records (GW {min_gw}-{max_gw})" + f" Batch {offset//batch_size + 1}: {batch_count} records (GW {min_gw}-{max_gw})" ) # Move to next batch @@ -63,14 +68,14 @@ def export_player_performances(self, output_path=None): # Safety break to avoid infinite loops if offset > 20000: - print(" āš ļø Safety limit reached (20k records)") + print(" SAFETY LIMIT reached (20k records)") break except Exception as e: - print(f" āŒ Error at offset {offset}: {e}") + print(f" ERROR at offset {offset}: {e}") break - print(f"\nšŸ“Š Export Summary:") + print(f"\nExport Summary:") print(f" Total records collected: {len(all_records)}") if all_records: @@ -91,9 +96,9 @@ def export_player_performances(self, output_path=None): df = pd.DataFrame(all_records) df.to_csv(output_path, index=False) - print(f"\nāœ… Data exported to: {output_path}") - print(f"šŸ“„ File contains {len(df)} rows Ɨ {len(df.columns)} columns") - print(f"šŸŽÆ Columns: {', '.join(df.columns.tolist())}") + print(f"\nData exported to: {output_path}") + print(f"File contains {len(df)} rows Ɨ {len(df.columns)} columns") + print(f"Columns: {', '.join(df.columns.tolist())}") return output_path, len(all_records), unique_gameweeks else: @@ -102,7 +107,7 @@ def export_player_performances(self, output_path=None): def export_by_gameweek(self, output_dir=None): """Export data gameweek by gameweek (alternative method)""" - print("šŸ” Exporting by individual gameweeks...") + print("Exporting by individual gameweeks...") if not output_dir: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") @@ -115,7 +120,7 @@ def export_by_gameweek(self, output_dir=None): for gw in range(1, 11): try: - print(f" šŸ“„ Exporting Gameweek {gw}...") + print(f" Exporting Gameweek {gw}...") result = ( self.supabase.table("player_performances") @@ -126,7 +131,7 @@ def export_by_gameweek(self, output_dir=None): if result.data: count = len(result.data) - print(f" āœ… {count} records") + print(f" {count} records") # Save individual gameweek file gw_df = pd.DataFrame(result.data) @@ -137,10 +142,10 @@ def export_by_gameweek(self, output_dir=None): # Add to combined data all_data.extend(result.data) else: - print(f" āŒ No data for GW {gw}") + print(f" No data for GW {gw}") except Exception as e: - print(f" āŒ Error exporting GW {gw}: {e}") + print(f" ERROR exporting GW {gw}: {e}") # Create combined file if all_data: @@ -148,20 +153,20 @@ def export_by_gameweek(self, output_dir=None): combined_file = os.path.join(output_dir, "all_gameweeks_combined.csv") combined_df.to_csv(combined_file, index=False) - print(f"\nāœ… Gameweek exports complete!") - print(f"šŸ“ Directory: {output_dir}") - print(f"šŸ“„ Combined file: {combined_file}") - print(f"šŸ“Š Total records: {len(combined_df)}") - print(f"šŸ“„ Individual files: {len(gameweek_files)}") + print(f"\nGameweek exports complete") + print(f"Directory: {output_dir}") + print(f"Combined file: {combined_file}") + print(f"Total records: {len(combined_df)}") + print(f"Individual files: {len(gameweek_files)}") return output_dir, combined_file, len(all_data) else: - print("āŒ No data exported") + print("No data exported") return None, None, 0 def verify_export_vs_database(self, export_file): """Verify exported data matches database counts""" - print(f"šŸ” Verifying export: {export_file}") + print(f"Verifying export: {export_file}") # Read export file try: @@ -169,7 +174,7 @@ def verify_export_vs_database(self, export_file): export_total = len(df) export_gameweeks = sorted(df["gameweek_id"].unique()) - print(f"šŸ“„ Export file: {export_total} records, GWs {export_gameweeks}") + print(f"Export file: {export_total} records, GWs {export_gameweeks}") except Exception as e: print(f"āŒ Error reading export: {e}") return False @@ -183,7 +188,7 @@ def verify_export_vs_database(self, export_file): ) db_total = db_result.count - print(f"šŸ’¾ Database: {db_total} records") + print(f"Database: {db_total} records") # Check individual gameweeks db_gameweeks = [] @@ -197,57 +202,57 @@ def verify_export_vs_database(self, export_file): if gw_result.count > 0: db_gameweeks.append(gw) - print(f"šŸ’¾ Database gameweeks: {db_gameweeks}") + print(f"Database gameweeks: {db_gameweeks}") # Compare if export_total == db_total and export_gameweeks == db_gameweeks: - print("āœ… Export matches database perfectly!") + print("Export matches database perfectly") return True else: - print("āš ļø Export doesn't match database:") + print("Export doesn't match database:") print(f" Records: Export {export_total} vs DB {db_total}") print(f" Gameweeks: Export {export_gameweeks} vs DB {db_gameweeks}") return False except Exception as e: - print(f"āŒ Error checking database: {e}") + print(f"ERROR checking database: {e}") return False def main(): """Main export function""" - print("šŸ† Complete FPL Data Exporter") + print("Complete FPL Data Exporter") print("============================================================") exporter = CompleteDataExporter() # Method 1: Paginated export (recommended) - print("\nšŸ”„ Method 1: Paginated Export") + print("\nMethod 1: Paginated Export") try: export_file, record_count, gameweeks = exporter.export_player_performances() if export_file: - print(f"āœ… Successfully exported {record_count} records") + print(f"Successfully exported {record_count} records") # Verify the export - print(f"\nšŸ” Verifying export quality...") + print(f"\nVerifying export quality...") exporter.verify_export_vs_database(export_file) else: - print("āŒ Paginated export failed") - except Exception as e: - print(f"āŒ Paginated export error: {e}") + print("Paginated export failed") + except Exception as e: + print(f"Paginated export error: {e}") # Method 2: Gameweek-by-gameweek export (backup) - print(f"\nšŸ”„ Method 2: Gameweek-by-Gameweek Export") + print(f"\nMethod 2: Gameweek-by-Gameweek Export") try: output_dir, combined_file, total_records = exporter.export_by_gameweek() if combined_file: - print(f"āœ… Successfully exported {total_records} records by gameweek") + print(f"Successfully exported {total_records} records by gameweek") else: print("āŒ Gameweek export failed") - except Exception as e: - print(f"āŒ Gameweek export error: {e}") + except Exception as e: + print(f"Gameweek export error: {e}") - print(f"\nšŸŽÆ Export complete! Check the generated CSV files.") + print(f"\nExport complete. Check the generated CSV files.") if __name__ == "__main__": diff --git a/src/database/fast_performance_refresh.py b/src/database/fast_performance_refresh.py index ede305f..84a0847 100644 --- a/src/database/fast_performance_refresh.py +++ b/src/database/fast_performance_refresh.py @@ -28,8 +28,13 @@ def __init__(self): def log(self, message: str, level: str = "INFO"): """Enhanced logging with timestamps""" timestamp = datetime.now().strftime("%H:%M:%S") - prefix = {"INFO": "ā„¹ļø", "SUCCESS": "āœ…", "WARNING": "āš ļø", "ERROR": "āŒ"} - print(f"[{timestamp}] {prefix.get(level, 'ā„¹ļø')} {message}") + prefix = { + "INFO": "INFO", + "SUCCESS": "SUCCESS", + "WARNING": "WARNING", + "ERROR": "ERROR", + } + print(f"[{timestamp}] {prefix.get(level, 'INFO')} {message}") def get_valid_player_ids(self): """Get all valid player IDs from database once""" @@ -137,9 +142,7 @@ def process_gameweek(self, gw: int, valid_player_ids: set): performances.append(performance) processed_count += 1 - self.log( - f" šŸ“Š GW{gw}: {processed_count}/{len(elements)} players processed" - ) + self.log(f" GW{gw}: {processed_count}/{len(elements)} players processed") return performances except Exception as e: @@ -153,7 +156,7 @@ def insert_performances_batch(self, performances: list, batch_name: str = ""): try: supabase.table("player_performances").insert(performances).execute() - self.log(f"āœ… Inserted {len(performances)} records {batch_name}", "SUCCESS") + self.log(f"Inserted {len(performances)} records {batch_name}", "SUCCESS") return True except Exception as e: self.log(f"Error inserting batch {batch_name}: {e}", "ERROR") @@ -161,7 +164,7 @@ def insert_performances_batch(self, performances: list, batch_name: str = ""): def refresh_all_gameweeks(self): """Main function to refresh all gameweek performances""" - self.log("šŸš€ Starting Fast Player Performance Refresh", "SUCCESS") + self.log("Starting Fast Player Performance Refresh", "SUCCESS") self.log("=" * 60) start_time = time.time() @@ -222,21 +225,19 @@ def refresh_all_gameweeks(self): ) total_records = result.count if result.count else 0 - self.log(f"šŸŽ‰ Refresh Complete! ({elapsed:.1f}s)", "SUCCESS") - self.log(f"šŸ“Š Total records inserted: {total_records:,}") - self.log(f"šŸŽÆ Gameweeks processed: {successful_gws}") + self.log(f"Refresh Complete! ({elapsed:.1f}s)", "SUCCESS") + self.log(f"Total records inserted: {total_records:,}") + self.log(f"Gameweeks processed: {successful_gws}") # Calculate expected records expected_per_gw = len(valid_player_ids) expected_total = expected_per_gw * len(successful_gws) if total_records >= expected_total * 0.9: # 90% threshold - self.log( - f"āœ… Data quality: EXCELLENT ({total_records}/{expected_total})" - ) + self.log(f"Data quality: EXCELLENT ({total_records}/{expected_total})") else: self.log( - f"āš ļø Data quality: NEEDS REVIEW ({total_records}/{expected_total})" + f"Data quality: NEEDS REVIEW ({total_records}/{expected_total})" ) return True @@ -248,7 +249,7 @@ def refresh_all_gameweeks(self): def main(): """Main execution""" - print("šŸ† Fast Player Performance Refresh") + print("Fast Player Performance Refresh") print("Optimized version to fix slow database loading") print("=" * 60) @@ -256,10 +257,10 @@ def main(): success = refresher.refresh_all_gameweeks() if success: - print(f"\nšŸŽÆ SUCCESS: Your player_performances table now has complete data!") - print(f"šŸ’” You can now test your AI with: python src/test_real_players.py") + print(f"\nSUCCESS: Your player_performances table now has complete data!") + print(f"You can now test your AI with: python src/test_real_players.py") else: - print(f"\nāŒ FAILED: Check the errors above and try again") + print(f"\nFAILED: Check the errors above and try again") if __name__ == "__main__": diff --git a/src/database/verify_fix.py b/src/database/verify_fix.py index 65d0d4a..64c53a7 100644 --- a/src/database/verify_fix.py +++ b/src/database/verify_fix.py @@ -6,7 +6,7 @@ load_dotenv() supabase = create_client(os.getenv("SUPABASE_URL"), os.getenv("SUPABASE_KEY")) -print("šŸ” Final Database Verification:") +print("Final Database Verification:") print("=" * 40) # Check player_performances (with fresh query) @@ -20,15 +20,15 @@ gameweeks = sorted(df["gameweek_id"].unique()) if not df.empty else [] gw_counts = df["gameweek_id"].value_counts().sort_index() if not df.empty else {} -print(f"šŸ“Š Total records: {total_records:,}") -print(f"šŸŽÆ Gameweeks: {gameweeks}") -print(f"šŸ“ˆ Records per GW:") +print(f"Total records: {total_records:,}") +print(f"Gameweeks: {gameweeks}") +print(f"Records per GW:") for gw, count in gw_counts.items(): print(f" GW {gw}: {count:,} records") print(f"") if total_records >= 7000: - print("āœ… DATABASE STATUS: EXCELLENT!") - print("šŸŽ‰ All gameweeks populated successfully!") + print("DATABASE STATUS: EXCELLENT!") + print("All gameweeks populated successfully!") else: - print("āš ļø DATABASE STATUS: Incomplete") + print("DATABASE STATUS: Incomplete") diff --git a/src/database/verify_player_performances.py b/src/database/verify_player_performances.py index afff150..c22c8dc 100644 --- a/src/database/verify_player_performances.py +++ b/src/database/verify_player_performances.py @@ -5,7 +5,10 @@ import sys import os -sys.path.append(os.path.join(os.path.dirname(__file__), "config")) +config_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "config")) +if config_path not in sys.path: + sys.path.append(config_path) + from supabase_client import get_supabase_client @@ -13,10 +16,10 @@ def verify_player_performances(): """Verify the state of player_performances table""" supabase = get_supabase_client() - print("šŸ” Verifying player_performances table...") + print("Verifying player_performances table...") # Method 1: Count per gameweek using individual queries - print("\nšŸ“Š Method 1: Individual gameweek queries") + print("\nMethod 1: Individual gameweek queries") total_individual = 0 gameweeks_with_data = [] @@ -40,7 +43,7 @@ def verify_player_performances(): print(f" Gameweeks with data: {gameweeks_with_data}") # Method 2: Total count query - print("\nšŸ“Š Method 2: Total count query") + print("\nMethod 2: Total count query") try: total_result = supabase.table("player_performances").select("id").execute() total_count = len(total_result.data) if total_result.data else 0 @@ -49,7 +52,7 @@ def verify_player_performances(): print(f" Error getting total count: {e}") # Method 3: Get unique gameweeks - print("\nšŸ“Š Method 3: Unique gameweeks from full data") + print("\nMethod 3: Unique gameweeks from full data") try: unique_result = ( supabase.table("player_performances").select("gameweek_id").execute() @@ -66,7 +69,7 @@ def verify_player_performances(): print(f" Error getting unique gameweeks: {e}") # Method 4: Sample recent records - print("\nšŸ“Š Method 4: Sample recent records") + print("\nMethod 4: Sample recent records") try: sample_result = ( supabase.table("player_performances") diff --git a/src/test_real_players.py b/src/test_real_players.py index 957063b..dba49a6 100644 --- a/src/test_real_players.py +++ b/src/test_real_players.py @@ -369,7 +369,7 @@ def select_optimal_squad(self, players_df): f"WARNING: Squad has {len(selected_players)} players instead of required 15!" ) else: - print("āœ… Squad size requirement met: 15 players selected") + print("SUCCESS: Squad size requirement met: 15 players selected") return pd.DataFrame(selected_players) diff --git a/src/tests/__pycache__/test_basic.cpython-313-pytest-8.4.2.pyc b/src/tests/__pycache__/test_basic.cpython-313-pytest-8.4.2.pyc index f7d9e6da25993d2a7d97137f5a3592fb882f7c4e..d1d856364e0e30818ee9a6832b2ccb3d41296e73 100644 GIT binary patch delta 2597 zcmbuBdu&rx7{Gh_TDv~Bwc8l$Hp{w=mTtlr2#h%kbPtd{AP`WIYuDbEE9--EZ-GK| z3O-|uk`o0@P<(_0UkKL_{=mO9F(wA(FT?d8@DCHCCT@l-#t{6zbGt|C;2&?YU(dbY zcfRwT^F7XRa3~{KxSbj8G9r)eFzOuD@D{<&WR0D?3*=QZRInZVf*{Gkj+k;EnQ5HrtBLavP zfNTT!iw|vON9{X_mtC{JLxSu??PB%ia=+1m{?X!UUpRw9C9Ba6kcY~sAAU4@EGG#$ zD#b;*8mFy7l&o0>-{zJKxduohJK%b~xTMb=F-~;jG&|r|0!Vf7#}p}gfkHQeA5Ez{ zH6WRB$C;3lNhzKmS0qu^)%nDGc?8p$Ryj!8SfJ_@ICZ6JAlinjtw5|n^a8XRL6DW0 zBE^N6qEKlpuZXfB&UG3~X$`(+uXG^u);u^{OX18^P`x|DMuK@H2(z!ECKO zDN$JwGO@I%Ib<=O%_R5>9YNc}h?4!3MDWSJH&DPyG$HAI>OGq_~ z`05>x;D<*M>zU|V>sbWDkeOED7Z0MEo%i{zbvRtis_foHs6p!yK12f`+rm=TnJS4) z0{R40i^Crz>0wVl8)&N_L+Y-!J0?=jb}j3uJq{9XKSVTgSjO`4Lt($YY|9WvofrHw zhR+XNuDf`YTig*cYNq{SK`W!;L6OQLHKK`Qh`XQian>33Ht6|)nvIAi#C-tXcb*70 zF1_1vayRDnoe#tPWK{hv+%p^fjx$zrjQ!lTVuFD|3;{gVk>a2wGA|8ekWe0Q_gFrW z5)~mg9xK3BXdWep5QhQ7^LDmC>Sz@?DyF4;dS?1LJ6bgv^k%v<Qx(NBjCa zc@1c$frnRW=Aq5ePRh&SG_Jv8G|e7d#H5%>plB}0Ge?n{S4Q+C z`Z90B^c1f39^!q(|Jm{%y<16I?S9Bt- zJ1r}x8eEppttPi+xn4b}rdJOPpoE_C^r&Q`&qy4sG1_6}{q7(ejn;R~Z@^9^tHj2I zTvo~`vgS-dBbN0fNzjkMjD8x;>~u5?9pc;Q&0-7ty6<=L0FTc{pryHDP+zf>D9G_F z73n89^HaoUfMIAun8UOd!LRS0*;`CZ=ahmzO{SMnfuDafjxHcB0^~A4F})zM1_ysC zy7A!VZKiEl&qo|PL|O5V^6H@HaUxHHc@ zi~qUkHCs?5b~VixNF+lN|sF;0MnzSo&>5U X4Z!K{`^oZ217LcLT^|XWaRdJXa)B%` delta 2545 zcmb7`TWnNC7{_;Zw|n1SmbSEPTefU(rx&EQg<30YVcP|SZHe-LqKBn>wk)*WWzH$E zU{VtBnrI+n08K=qCWLqaIYcEvUVM-z6CR9scrY;rL*heQqg9N-`Ohx4Ea2rl{5m_| z%s1aR^Pg|}=iB7@Q>5@`uh(Vb-z(qG>|muI7w#a$uJ0uov-ze&pRtq@TQRrNBIYUR zCc~`1;1qG`HwwNn+dSwmWM4R1OG?lz1}YwV{<14zcDpe;x8Ma&)5g37=~8Z&?6j8uF`Th< zQclz4utJw(S{IPFrk(fAEh%{;q>AnK9?31~FASL{AH_6m%}v5F(jh)eC>I5cMS$3y z{BX*-L7Zd*}B0ilN#cN4D3znSVL0u||gd4G1JJ1cR;xNi& zS=C}%a#)UO8clAW&=ggcX&(kiKm%aC13QcOme_>B%BJfF*=1i5S*m~KOZE z-3df!IMXRLsU=h6>X}?}&!XFs&UA>uw+dsbs!%OZ-;~aDODZhgbeNBkT;eR~1aDIX zNB|=M5~MqTo$N}mjVJbNFhmCQ6?I`UDNY{Zh)Pbn8iUpVVW1xv0Gn)3?^(jMZMyI^%N{^!!p`D+p#Kh+&ADOZry*W)jth8g5&Nn+-L$l^^E4 z!nZ^WvQOnZZwl}dz?#=DfGk95IA}Vz`?HGk2dKoRnimZo!juPZFg)J8%|FRz*whr& zlsJ8mQbcLm(o)^{pgqhlO>eafkWKmzExkelZ5M6i5WC*7OnIFzirwd5X(-pI3(eDl z#w|jZM&b5}_=uv(>Csq5p(@=2%RV5(5v67{MUMIUVd>6=j5r(DWXq~ItAW?}c!Q+r zG0Zy-2pQ}|Yd;{gZ~(2jEbG~HXE~RKqiZdZ^-W!89h1fv8P_{jdCbv&)%_9peiL{PhyZ!IpF|%JWv}*CXDjI>ucYhsTYY{r z@v!o6TazeMd-uT@F4EAK6-(}A^eAI#Et8$5 z(oZoZyEIzo0i8p&bId)CeQU4h!dSj;lSSOW{q83fRSOIW4byX&D2{aAJt)pSln9uL zYn~sb)3ET1fPWMQvO(rv75zBU kR!^FzO&qgPQbU5iX%pvch%6-!O`AAox3Q~(O;%*^7gUNUM*si- diff --git a/src/tests/__pycache__/test_simple.cpython-313-pytest-8.4.2.pyc b/src/tests/__pycache__/test_simple.cpython-313-pytest-8.4.2.pyc index 910b3ee6b767f0ba5a969168c5127f725ee99c92..eda8143bdec20b1e4e6ec1e801d3d3abf30ecde5 100644 GIT binary patch delta 453 zcmbO;jd9L2M!wIyyj%=G5YzZOV}tNUK3*n9-pw*hiT=OS0W5#Af-NPJAFS$2=GgV|{n+CM6Xy#;D^An5(o1dAtGcnGeoM)xT=stOZ zRX1bZW*KWEX11juMaw3K*wwPF0WsH3zHZmd*tj{`{x~!DW{}7#5V3l)uCt2yIuNrQ zL{tC?zakMJsmWO+3liB1BDMpGTYM#{#U=6SiMgresj1n;McXF7w9}rv+1ZA%d-Dh9 zo6Kxb6VJOVo8U9?0La8cAQQR4CKe~>7o`>#9RwQ}=wZP)b#j--Gwux_WjjH{5uo=Z zxE6t!Afj&aTu&v&MUxMB&H|FrUK)~DfTD~*T-**MJ}@&fGJa%YVr2QwF?o)cofw-r aqwYrrAo0C~(S%XsBLk55o;Ue}mkt0Ahle%* delta 453 zcmbO;jd9L2M!wIyyj%=GF#X}D40n-@e7sDI+?!>X7BMp>Pu|bEjgfD28QVW*Mv2X( zTplcpiktWGonU6v+T1SG%gSi9SyW;sBO~|Z^^!9fT{e42DKIm-Z_bnzVq^5%T%){; zl`(F!x#mx1#?;M*x`#QKidZ*)GgV|{n+mkAXvSn&^An7@o1dAtGcnGaoM)xT=rVbN zRX1bRW*KWEX0|0DMN21#*wwPF1~J!6zHZmdSi3pe{x~!DCXmQV5V2~quCt2yS`f1w zL{tC?zakMJsmWO+3liA^BDMjETYM#{#U=6SiMgresj1n;MO!Dow9}rv+1ZA%ee(zB zo6Kxb6VJOVo8U8XKgh&`AQQR4CKe~>7o`>#9RM2_=wZP)adMZ(Gw$^uWjjE`VW9UU zxE6w#AfjsWTu&v&g_93>&H|FrUK)~@fuf8+T-**MJ}@&fGJa%YVr2QwHhGShofwNb aqwYrrAo0D3(S%XsBLk55o-_G_mkt1t_=cze diff --git a/src/tests/test_database_refresh.py b/src/tests/test_database_refresh.py new file mode 100644 index 0000000..a8c1f57 --- /dev/null +++ b/src/tests/test_database_refresh.py @@ -0,0 +1,102 @@ +import json +import types +import pytest + +from src.database import database_refresh + + +class FakeResponse: + def __init__(self, status_code=200, data=None): + self.status_code = status_code + self._data = data or {} + + def json(self): + return self._data + + +class FakeTable: + def __init__(self, name, supabase): + self.name = name + self.supabase = supabase + self._last_insert = None + + def select(self, *args, **kwargs): + self._select_args = args + return self + + def execute(self): + # Return players list when selecting from players + if self.name == "players": + return types.SimpleNamespace(data=self.supabase._players_data) + # For inserts return a simple object + if self._last_insert is not None: + return types.SimpleNamespace(data=self._last_insert) + return types.SimpleNamespace(data=[]) + + def insert(self, data): + # capture insert data and pretend it succeeded + self._last_insert = data + # store inserted for test inspection + self.supabase.inserted.setdefault(self.name, []).append(data) + return self + + # simple no-op for methods used elsewhere + def range(self, *a, **kw): + return self + + def eq(self, *a, **kw): + return self + + +class FakeSupabase: + def __init__(self, players_ids): + # players_ids: iterable of fpl_player_id ints + self._players_data = [{"fpl_player_id": pid} for pid in players_ids] + self.inserted = {} + + def table(self, name): + return FakeTable(name, self) + + +def test_refresh_player_performances_preloads_and_skips_unknown(monkeypatch): + # Prepare fake supabase with only player id 1 present + fake = FakeSupabase(players_ids=[1]) + database_refresh.supabase = fake + + # Mock requests.get to return bootstrap-static then event/1/live + def fake_get(url, *args, **kwargs): + if url.endswith("bootstrap-static/"): + # one finished GW with id=1 + return FakeResponse(200, {"events": [{"id": 1, "finished": True}]}) + elif "event/1/live/" in url: + # elements include a known id (1) and an unknown id (99) + return FakeResponse( + 200, + { + "elements": [ + {"id": 1, "stats": {"minutes": 90, "total_points": 5}}, + {"id": 99, "stats": {"minutes": 10, "total_points": 0}}, + ] + }, + ) + return FakeResponse(404, {}) + + monkeypatch.setattr(database_refresh.requests, "get", fake_get) + + refresher = database_refresh.FPLDatabaseRefresh() + + # Run only the player performance refresh (should insert only 1 player's data) + refresher.refresh_player_performances_current_season() + + # Inspect inserted data captured by fake supabase + inserted = fake.inserted.get("player_performances", []) + # inserted may be nested lists depending on batching; flatten + flattened = [ + item + for batch in inserted + for item in (batch if isinstance(batch, list) else [batch]) + ] + + # Should have inserted at least one record for player id 1 and none for 99 + assert any(rec.get("player_id") == 1 for rec in flattened) + assert not any(rec.get("player_id") == 99 for rec in flattened) diff --git a/src/tests/test_update_player_prices.py b/src/tests/test_update_player_prices.py new file mode 100644 index 0000000..b1eba40 --- /dev/null +++ b/src/tests/test_update_player_prices.py @@ -0,0 +1,94 @@ +import types +import pytest + +from src.database import update_player_prices + + +class FakeTableUpdate: + def __init__(self, name, supabase): + self.name = name + self.supabase = supabase + self._last_update = None + self._last_eq = None + + def select(self, *args, **kwargs): + return self + + def execute(self): + # For select('id, fpl_player_id') return mapping + if self.name == "players": + return types.SimpleNamespace(data=self.supabase._players) + # For update calls return a fake data list to indicate rows updated + if self._last_update is not None: + # store the update for assertion + db_id = self._last_eq + self.supabase.updated.setdefault(db_id, []).append(self._last_update) + return types.SimpleNamespace(data=[{"updated": True}]) + return types.SimpleNamespace(data=[]) + + def update(self, payload): + self._last_update = payload + return self + + def eq(self, column, value): + # capture the db player id used in .eq('player_id', db_player_id) + self._last_eq = value + return self + + +class FakeSupabaseUpdate: + def __init__(self, players_mapping): + # players_mapping: list of dicts with fpl_player_id and id + self._players = players_mapping + self.updated = {} + + def table(self, name): + return FakeTableUpdate(name, self) + + +def test_update_player_prices_normalizes_and_skips_out_of_range(monkeypatch): + # Prepare fake supabase client with 3 players + players_mapping = [ + {"fpl_player_id": 10, "id": 101}, + {"fpl_player_id": 11, "id": 102}, + {"fpl_player_id": 12, "id": 103}, + ] + fake_supabase = FakeSupabaseUpdate(players_mapping) + + # Monkeypatch get_supabase_client used in the module + monkeypatch.setattr( + update_player_prices, "get_supabase_client", lambda: fake_supabase + ) + + # Create an updater and monkeypatch its get_current_fpl_data to return test rows + updater = update_player_prices.PlayerPriceUpdater() + + test_fpl_players = [ + { + "id": 10, + "now_cost": 4.5, + "selected_by_percent": "12.3", + }, # should normalize to 45 + {"id": 11, "now_cost": 45, "selected_by_percent": "5.0"}, # stays 45 + { + "id": 12, + "now_cost": 200, + "selected_by_percent": "0", + }, # out of range -> skipped + ] + + monkeypatch.setattr(updater, "get_current_fpl_data", lambda: test_fpl_players) + + # Run update + success = updater.update_player_prices() + + # Ensure updates were made and out-of-range player skipped + # db id 101 and 102 should have updates recorded + assert 101 in fake_supabase.updated + assert 102 in fake_supabase.updated + # 103 (player id 12) should not have been updated + assert 103 not in fake_supabase.updated + + # Check normalized now_cost for player 10 -> 45 + updates_101 = fake_supabase.updated[101] + assert any(up.get("now_cost") == 45 for up in updates_101) From 385afd0eecb1eaadab0d74ffe6afba2750bbfffe Mon Sep 17 00:00:00 2001 From: Ritvik Uppal Date: Sun, 21 Dec 2025 14:04:56 -0500 Subject: [PATCH 2/3] Cleaning up code to pass the linter --- .flake8 | 4 + src/ai/data_loader.py | 14 +- src/ai/feature_engineering.py | 2 - src/database/analyze_performance_gaps.py | 22 ++- src/database/check_fixture_quality.py | 19 +- src/database/database_refresh.py | 4 +- src/database/export_complete_data.py | 16 +- src/database/fast_performance_refresh.py | 2 - src/database/update_player_prices.py | 21 +-- src/fpl/player_recommender.py | 1 - src/fpl_predictor.py | 230 +++++++++-------------- src/test_real_players.py | 31 +-- src/tests/test_database_refresh.py | 2 - src/tests/test_update_player_prices.py | 3 +- 14 files changed, 167 insertions(+), 204 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..569c106 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 120 +extend-ignore = E402,F541,E203 +exclude = env,venv,dist,build,__pycache__,*.pyc diff --git a/src/ai/data_loader.py b/src/ai/data_loader.py index 5f5b608..db5829a 100644 --- a/src/ai/data_loader.py +++ b/src/ai/data_loader.py @@ -1,7 +1,6 @@ import pandas as pd -import numpy as np -from typing import Dict, List, Tuple -from supabase import create_client, Client +from typing import List, Tuple +from supabase import create_client from dotenv import load_dotenv import os @@ -251,9 +250,12 @@ def load_current_team_stats(self) -> pd.DataFrame: def test_connection(self): """Test supabase connection""" try: - response = self.supabase.table("players").select("*").limit(1).execute() - print("connected to supabase") - return True + resp = self.supabase.table("players").select("*").limit(1).execute() + if resp and getattr(resp, "data", None): + print("connected to supabase") + return True + print("No response from supabase test query") + return False except Exception as e: print(f"Connection failed: {e}") return False diff --git a/src/ai/feature_engineering.py b/src/ai/feature_engineering.py index 0a97360..ad63ea3 100644 --- a/src/ai/feature_engineering.py +++ b/src/ai/feature_engineering.py @@ -3,8 +3,6 @@ """ import pandas as pd -import numpy as np -from typing import List, Dict import sys import os diff --git a/src/database/analyze_performance_gaps.py b/src/database/analyze_performance_gaps.py index f1c2c1b..b665e0f 100644 --- a/src/database/analyze_performance_gaps.py +++ b/src/database/analyze_performance_gaps.py @@ -24,7 +24,7 @@ def analyze_player_performances_gaps(): result = supabase.table("player_performances").select("gameweek_id").execute() if not result.data: - print("ERROR: No player_performances data found!") + print("ERROR: No player_performances data found!") return df = pd.DataFrame(result.data) @@ -61,14 +61,14 @@ def analyze_player_performances_gaps(): if count < avg_records * 0.8: # Less than 80% of average inconsistent_gws.append(f"GW{gw}({count})") - if inconsistent_gws: - print(f"\nGameweeks with low record counts: {inconsistent_gws}") - print(f" Average records per GW: {avg_records:.0f}") + if inconsistent_gws: + print(f"\nGameweeks with low record counts: {inconsistent_gws}") + print(f" Average records per GW: {avg_records:.0f}") return gameweeks_present, gw_counts.to_dict() except Exception as e: - print(f"ERROR: Error analyzing data: {e}") + print(f"ERROR: Error analyzing data: {e}") return None, None @@ -100,17 +100,21 @@ def check_fpl_api_availability(): test_url = f"https://fantasy.premierleague.com/api/event/{test_gw}/live/" test_response = requests.get(test_url) - if test_response.status_code == 200: + if test_response.status_code == 200: test_data = test_response.json() player_count = len(test_data.get("elements", [])) - print(f"API test successful - GW{test_gw} has {player_count} player records") + print( + f"API test successful - GW{test_gw} has {player_count} player records" + ) else: - print(f"API test failed for GW{test_gw}: HTTP {test_response.status_code}") + print( + f"API test failed for GW{test_gw}: HTTP {test_response.status_code}" + ) return finished_gws, current_gw except Exception as e: - print(f"ERROR: Error checking FPL API: {e}") + print(f"ERROR: Error checking FPL API: {e}") return None, None diff --git a/src/database/check_fixture_quality.py b/src/database/check_fixture_quality.py index f7a09ba..4e139a8 100644 --- a/src/database/check_fixture_quality.py +++ b/src/database/check_fixture_quality.py @@ -6,7 +6,8 @@ from dotenv import load_dotenv from supabase import create_client import pandas as pd -from datetime import datetime + +# datetime not used in this module load_dotenv() @@ -42,7 +43,7 @@ def check_fixture_data_quality(): print("No gameweeks found") except Exception as e: - print(f"āŒ Error checking gameweeks: {e}") + print(f"Error checking gameweeks: {e}") # Check fixtures table print("\nFIXTURES TABLE:") @@ -112,7 +113,7 @@ def check_fixture_data_quality(): print("No fixtures found") except Exception as e: - print(f"āŒ Error checking fixtures: {e}") + print(f"Error checking fixtures: {e}") # Check for data inconsistencies print(f"\nINCONSISTENCY CHECK:") @@ -151,13 +152,17 @@ def check_fixture_data_quality(): ) if finished_count == 0 or with_scores == 0: - print( - f" GW {gw}: Marked finished but {finished_count}/{len(fixtures_df)} fixtures finished, {with_scores}/{len(fixtures_df)} have scores" + msg = ( + f"GW {gw}: Marked finished but {finished_count}/{len(fixtures_df)} " + f"fixtures finished, {with_scores}/{len(fixtures_df)} have scores" ) + print(f" {msg}") else: - print( - f" GW {gw}: {finished_count}/{len(fixtures_df)} fixtures finished, {with_scores}/{len(fixtures_df)} have scores" + msg = ( + f"GW {gw}: {finished_count}/{len(fixtures_df)} fixtures finished, " + f"{with_scores}/{len(fixtures_df)} have scores" ) + print(f" {msg}") except Exception as e: print(f"Error checking inconsistencies: {e}") diff --git a/src/database/database_refresh.py b/src/database/database_refresh.py index 6ef5317..85877b8 100644 --- a/src/database/database_refresh.py +++ b/src/database/database_refresh.py @@ -6,11 +6,11 @@ """ import os -import sys + import requests import pandas as pd import time -from datetime import datetime, timezone +from datetime import datetime from dotenv import load_dotenv from supabase import create_client, Client diff --git a/src/database/export_complete_data.py b/src/database/export_complete_data.py index 5a1901c..3c869ab 100644 --- a/src/database/export_complete_data.py +++ b/src/database/export_complete_data.py @@ -8,9 +8,7 @@ import pandas as pd from datetime import datetime -config_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), "..", "config") -) +config_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "config")) if config_path not in sys.path: sys.path.append(config_path) @@ -102,7 +100,7 @@ def export_player_performances(self, output_path=None): return output_path, len(all_records), unique_gameweeks else: - print(" āŒ No data to export") + print(" No data to export") return None, 0, [] def export_by_gameweek(self, output_dir=None): @@ -238,8 +236,8 @@ def main(): exporter.verify_export_vs_database(export_file) else: print("Paginated export failed") - except Exception as e: - print(f"Paginated export error: {e}") + except Exception as e: + print(f"Paginated export error: {e}") # Method 2: Gameweek-by-gameweek export (backup) print(f"\nMethod 2: Gameweek-by-Gameweek Export") @@ -248,9 +246,9 @@ def main(): if combined_file: print(f"Successfully exported {total_records} records by gameweek") else: - print("āŒ Gameweek export failed") - except Exception as e: - print(f"Gameweek export error: {e}") + print("Gameweek export failed") + except Exception as e: + print(f"Gameweek export error: {e}") print(f"\nExport complete. Check the generated CSV files.") diff --git a/src/database/fast_performance_refresh.py b/src/database/fast_performance_refresh.py index 84a0847..0ca3983 100644 --- a/src/database/fast_performance_refresh.py +++ b/src/database/fast_performance_refresh.py @@ -5,9 +5,7 @@ """ import os -import sys import requests -import pandas as pd import time from datetime import datetime from dotenv import load_dotenv diff --git a/src/database/update_player_prices.py b/src/database/update_player_prices.py index 4743c1a..34be143 100644 --- a/src/database/update_player_prices.py +++ b/src/database/update_player_prices.py @@ -7,7 +7,6 @@ import os import sys import requests -import pandas as pd from datetime import datetime from dotenv import load_dotenv @@ -92,17 +91,16 @@ def update_player_prices(self): # Validate within expected FPL range (35 = Ā£3.5m to 150 = Ā£15.0m) if not (35 <= now_cost <= 150): - self.log( - f"WARNING: now_cost {now_cost} out of expected range (35-150) for player {fpl_player_id}. Skipping.", - "WARNING", + msg = ( + f"now_cost {now_cost} out of expected range (35-150) for player " + f"{fpl_player_id}. Skipping." ) + self.log(msg, "WARNING") continue except Exception as e: - self.log( - f"WARNING: Could not parse now_cost for player {fpl_player_id}: {e}. Skipping.", - "WARNING", - ) + msg = f"Could not parse now_cost for player {fpl_player_id}: {e}. Skipping." + self.log(msg, "WARNING") continue # Parse selected_by_percent safely @@ -203,9 +201,10 @@ def verify_updates(self): self.log("Sample updated prices:") for record in result.data: cost_in_millions = record["now_cost"] / 10.0 - self.log( - f" Player {record['player_id']}: Ā£{cost_in_millions}m, {record['selected_by_percent']}% selected" - ) + player_id = record.get("player_id") + selected_pct = record.get("selected_by_percent") + msg = f" Player {player_id}: Ā£{cost_in_millions}m, {selected_pct}% selected" + self.log(msg) # Count total updated records count_result = ( diff --git a/src/fpl/player_recommender.py b/src/fpl/player_recommender.py index 42953c3..a84f34c 100644 --- a/src/fpl/player_recommender.py +++ b/src/fpl/player_recommender.py @@ -1,5 +1,4 @@ import pandas as pd -import numpy as np import sys import os diff --git a/src/fpl_predictor.py b/src/fpl_predictor.py index 2f9ad11..045dd19 100644 --- a/src/fpl_predictor.py +++ b/src/fpl_predictor.py @@ -1,10 +1,7 @@ # env\Scripts\activate import requests -import pandas as pd -import json import os -import time -from datetime import datetime, timedelta +from datetime import datetime from dotenv import load_dotenv from supabase import create_client, Client @@ -32,19 +29,15 @@ def save_teams_to_db(teams_data): try: for team in teams_data: # Use upsert to avoid duplicates - result = ( - supabase.table("teams") - .upsert( - { - "fpl_team_id": team["id"], - "name": team["name"], - "short_name": team["short_name"], - "code": team.get("code"), - }, - on_conflict="fpl_team_id", - ) - .execute() - ) + supabase.table("teams").upsert( + { + "fpl_team_id": team["id"], + "name": team["name"], + "short_name": team["short_name"], + "code": team.get("code"), + }, + on_conflict="fpl_team_id", + ).execute() print(f"Successfully saved {len(teams_data)} teams to database") except Exception as e: print(f"Error saving teams to database: {e}") @@ -81,22 +74,18 @@ def save_players_to_db(players_data, team_mapping): team_id = team_result.data[0]["id"] if team_id: - result = ( - supabase.table("players") - .upsert( - { - "fpl_player_id": player["id"], - "first_name": player["first_name"], - "second_name": player["second_name"], - "web_name": player["web_name"], - "team_id": team_id, - "element_type": player["element_type"], - "status": player.get("status", "a"), - }, - on_conflict="fpl_player_id", - ) - .execute() - ) + supabase.table("players").upsert( + { + "fpl_player_id": player["id"], + "first_name": player["first_name"], + "second_name": player["second_name"], + "web_name": player["web_name"], + "team_id": team_id, + "element_type": player["element_type"], + "status": player.get("status", "a"), + }, + on_conflict="fpl_player_id", + ).execute() saved_count += 1 print(f"Successfully saved {saved_count} players to database") @@ -224,23 +213,19 @@ def save_gameweek_info(gameweek_data): """Save gameweek information to database""" try: for gw in gameweek_data: - result = ( - supabase.table("gameweeks") - .upsert( - { - "gameweek_number": gw["id"], - "season": "2025-26", - "name": gw.get("name", f"Gameweek {gw['id']}"), - "deadline_time": gw.get("deadline_time"), - "is_finished": gw.get("finished", False), - "is_current": gw.get("is_current", False), - "average_entry_score": gw.get("average_entry_score", 0), - "highest_score": gw.get("highest_score", 0), - }, - on_conflict="gameweek_number,season", - ) - .execute() - ) + supabase.table("gameweeks").upsert( + { + "gameweek_number": gw["id"], + "season": "2025-26", + "name": gw.get("name", f"Gameweek {gw['id']}"), + "deadline_time": gw.get("deadline_time"), + "is_finished": gw.get("finished", False), + "is_current": gw.get("is_current", False), + "average_entry_score": gw.get("average_entry_score", 0), + "highest_score": gw.get("highest_score", 0), + }, + on_conflict="gameweek_number,season", + ).execute() print(f"Saved {len(gameweek_data)} gameweeks to database") except Exception as e: print(f"Error saving gameweeks: {e}") @@ -264,52 +249,46 @@ def save_current_player_stats(players_data, current_gameweek_id): player_db_id = player_result.data[0]["id"] # Save current stats - result = ( - supabase.table("current_player_stats") - .upsert( - { - "player_id": player_db_id, - "gameweek_id": current_gameweek_id, - "total_points": player.get("total_points", 0), - "minutes": player.get("minutes", 0), - "goals_scored": player.get("goals_scored", 0), - "assists": player.get("assists", 0), - "clean_sheets": player.get("clean_sheets", 0), - "goals_conceded": player.get("goals_conceded", 0), - "own_goals": player.get("own_goals", 0), - "penalties_saved": player.get("penalties_saved", 0), - "penalties_missed": player.get("penalties_missed", 0), - "yellow_cards": player.get("yellow_cards", 0), - "red_cards": player.get("red_cards", 0), - "saves": player.get("saves", 0), - "bonus": player.get("bonus", 0), - "bps": player.get("bps", 0), - "influence": float(player.get("influence", 0)), - "creativity": float(player.get("creativity", 0)), - "threat": float(player.get("threat", 0)), - "ict_index": float(player.get("ict_index", 0)), - "now_cost": player.get("now_cost", 50), - "selected_by_percent": float( - player.get("selected_by_percent", 0) - ), - "transfers_in": player.get("transfers_in", 0), - "transfers_out": player.get("transfers_out", 0), - "form": float(player.get("form", 0)), - "points_per_game": float(player.get("points_per_game", 0)), - "status": player.get("status", "a"), - "news": player.get("news", ""), - "chance_of_playing_this_round": player.get( - "chance_of_playing_this_round" - ), - "chance_of_playing_next_round": player.get( - "chance_of_playing_next_round" - ), - "data_updated_at": datetime.now().isoformat(), - }, - on_conflict="player_id,gameweek_id", - ) - .execute() - ) + supabase.table("current_player_stats").upsert( + { + "player_id": player_db_id, + "gameweek_id": current_gameweek_id, + "total_points": player.get("total_points", 0), + "minutes": player.get("minutes", 0), + "goals_scored": player.get("goals_scored", 0), + "assists": player.get("assists", 0), + "clean_sheets": player.get("clean_sheets", 0), + "goals_conceded": player.get("goals_conceded", 0), + "own_goals": player.get("own_goals", 0), + "penalties_saved": player.get("penalties_saved", 0), + "penalties_missed": player.get("penalties_missed", 0), + "yellow_cards": player.get("yellow_cards", 0), + "red_cards": player.get("red_cards", 0), + "saves": player.get("saves", 0), + "bonus": player.get("bonus", 0), + "bps": player.get("bps", 0), + "influence": float(player.get("influence", 0)), + "creativity": float(player.get("creativity", 0)), + "threat": float(player.get("threat", 0)), + "ict_index": float(player.get("ict_index", 0)), + "now_cost": player.get("now_cost", 50), + "selected_by_percent": float(player.get("selected_by_percent", 0)), + "transfers_in": player.get("transfers_in", 0), + "transfers_out": player.get("transfers_out", 0), + "form": float(player.get("form", 0)), + "points_per_game": float(player.get("points_per_game", 0)), + "status": player.get("status", "a"), + "news": player.get("news", ""), + "chance_of_playing_this_round": player.get( + "chance_of_playing_this_round" + ), + "chance_of_playing_next_round": player.get( + "chance_of_playing_next_round" + ), + "data_updated_at": datetime.now().isoformat(), + }, + on_conflict="player_id,gameweek_id", + ).execute() saved_count += 1 print(f"Saved current stats for {saved_count} players") @@ -338,33 +317,7 @@ def get_fpl_data_from_db(): } ) - # Get players with current stats - players_query = """ - SELECT - p.fpl_player_id as id, - p.first_name, - p.second_name, - p.web_name, - p.team_id, - p.element_type, - p.status, - t.fpl_team_id as team, - COALESCE(cps.total_points, 0) as total_points, - COALESCE(cps.minutes, 0) as minutes, - COALESCE(cps.goals_scored, 0) as goals_scored, - COALESCE(cps.assists, 0) as assists, - COALESCE(cps.clean_sheets, 0) as clean_sheets, - COALESCE(cps.now_cost, 50) as now_cost, - COALESCE(cps.selected_by_percent, 0) as selected_by_percent, - COALESCE(cps.form, 0) as form, - COALESCE(cps.points_per_game, 0) as points_per_game - FROM players p - JOIN teams t ON p.team_id = t.id - LEFT JOIN current_player_stats cps ON p.id = cps.player_id - ORDER BY p.fpl_player_id - """ - - # Use simpler approach without complex SQL + # Use simpler approach without complex SQL (previous SQL query removed) players_result = None if not players_result: @@ -445,9 +398,9 @@ def get_fpl_data_from_db(): # Test code to ensure endpoints return correct information headers = {"X-Auth-Token": API_KEY} -response = requests.get(urlFixtures, headers=headers) +# Create a mapping between football-data.org and the FPL API # Create a mapping between football-data.org and the FPL API def create_team_mapping(): """Create team mapping using Supabase database instead of JSON cache""" @@ -1318,7 +1271,7 @@ def get_best_players_by_position(match_data, top_n=5): try: prediction = predict_player_points(player, match_data) predictions.append(prediction) - except Exception as e: + except Exception: continue # Skip problematic players # Sort by predicted points @@ -1356,7 +1309,7 @@ def generate_transfer_recommendations(match_data, budget=1000, top_n=3): prediction = predict_player_points(player, match_data) if prediction["predicted_points"] > 3: # Only consider decent predictions all_predictions.append(prediction) - except Exception as e: + except Exception: continue if duplicates_found > 0: @@ -1419,7 +1372,7 @@ def build_optimal_team(match_data, budget=1000): # Ā£100m budget prediction = predict_player_points(player, match_data) prediction["team_name"] = teams.get(prediction["team_id"], "Unknown") all_predictions.append(prediction) - except Exception as e: + except Exception: continue # Sort by predicted points within each position @@ -1578,7 +1531,7 @@ def select_squad_with_constraints(): best_players = get_best_players_by_position(match_data, top_n=3) for position, players in best_players.items(): - print(f"\nšŸ† TOP 3 {position.upper()}:") + print(f"\nTOP 3 {position.upper()}:") for i, player in enumerate(players, 1): print( f" {i}. {player['name']} - {player['predicted_points']} pts " @@ -1586,13 +1539,13 @@ def select_squad_with_constraints(): ) # Generate transfer recommendations - print(f"\nšŸ’° TRANSFER RECOMMENDATIONS (Budget: Ā£10.0m):") + print(f"\nTRANSFER RECOMMENDATIONS (Budget: Ā£10.0m):") recommendations = generate_transfer_recommendations( match_data, budget=100 ) # Ā£10m budget if recommendations["best_value"]: - print("\nšŸŽÆ Best Value Picks:") + print("\nBest Value Picks:") for i, player in enumerate(recommendations["best_value"][:3], 1): print( f" {i}. {player['name']} - {player['predicted_points']} pts " @@ -1602,7 +1555,7 @@ def select_squad_with_constraints(): print("\nšŸŽÆ Best Value Picks: No affordable players found") if recommendations["highest_predicted"]: - print("\nšŸ“ˆ Highest Predicted Points:") + print("\nHighest Predicted Points:") highest_list = recommendations["highest_predicted"][:3] # Take slice first for i, player in enumerate(highest_list, 1): print( @@ -1613,7 +1566,7 @@ def select_squad_with_constraints(): print("\nšŸ“ˆ Highest Predicted Points: No predictions available") if recommendations["differential_picks"]: - print("\nšŸ”„ Differential Picks (<5% owned):") + print("\nDifferential Picks (<5% owned):") for i, player in enumerate(recommendations["differential_picks"][:3], 1): print( f" {i}. {player['name']} - {player['predicted_points']} pts " @@ -1634,7 +1587,7 @@ def select_squad_with_constraints(): print(f"⚔ PREDICTED POINTS: {optimal_team['predicted_points']:.1f}") # Display Starting XI - print(f"\nšŸ”„ STARTING XI ({optimal_team['formation']['name']}):") + print(f"\nSTARTING XI ({optimal_team['formation']['name']}):") starting_xi = optimal_team["starting_xi"] position_names = {1: "GK", 2: "DEF", 3: "MID", 4: "FWD"} @@ -1646,11 +1599,12 @@ def select_squad_with_constraints(): print(f"\n {pos_name}:") for player in position_players: print( - f" • {player['name']} ({player['team_name']}) - {player['predicted_points']:.1f}pts - Ā£{player['price']:.1f}m" + f" - {player['name']} ({player['team_name']}) - {player['predicted_points']:.1f}pts " + f"- Ā£{player['price']:.1f}m" ) # Display Bench - print(f"\nšŸŖ‘ BENCH (4 players):") + print(f"\nBENCH (4 players):") bench = optimal_team["bench"] for i, player in enumerate(bench, 1): pos_name = position_names.get(player["position"], "UNK") @@ -1659,7 +1613,7 @@ def select_squad_with_constraints(): ) # Team summary stats - print(f"\nšŸ“ˆ TEAM STATISTICS:") + print(f"\nTEAM STATISTICS:") team_count = {} for player in optimal_team["squad"]: team_name = player["team_name"] @@ -1675,7 +1629,9 @@ def select_squad_with_constraints(): pos_breakdown[player["position"]] += 1 print( - f" Squad Composition: {pos_breakdown[1]} GK, {pos_breakdown[2]} DEF, {pos_breakdown[3]} MID, {pos_breakdown[4]} FWD" + " Squad Composition: " + f"{pos_breakdown[1]} GK, {pos_breakdown[2]} DEF, " + f"{pos_breakdown[3]} MID, {pos_breakdown[4]} FWD" ) except Exception as e: diff --git a/src/test_real_players.py b/src/test_real_players.py index dba49a6..46ec066 100644 --- a/src/test_real_players.py +++ b/src/test_real_players.py @@ -1,7 +1,6 @@ import sys import os import pandas as pd -import numpy as np from collections import defaultdict sys.path.append(os.path.dirname(os.path.dirname(__file__))) @@ -153,7 +152,7 @@ def select_optimal_squad(self, players_df): ) # Good price + good points ) - premium_players = players_df[players_df["is_premium"] == True].copy() + premium_players = players_df[players_df["is_premium"]].copy() if len(premium_players) > 0: # Sort by a combination of predicted points and proven quality metrics premium_players["premium_score"] = ( @@ -183,11 +182,12 @@ def select_optimal_squad(self, players_df): team_counts[player["team_id"]] += 1 premium_selected += 1 - print( - f"Selected (Premium): {player['web_name']:<15} ({position}) - Ā£{player['price_display']:.1f}m - " - f"Pred: {player['predicted_points']:.2f} pts - " + msg = ( + f"Selected (Premium): {player['web_name']:<15} ({position}) - " + f"Ā£{player['price_display']:.1f}m | Pred: {player['predicted_points']:.2f} pts | " f"Value: {player['predicted_points_per_million']:.3f}" ) + print(msg) # Phase 2: Fill with quality players using data-driven team strength remaining_players = players_df[ @@ -211,7 +211,7 @@ def select_optimal_squad(self, players_df): # Prefer quality players but allow budget options quality_candidates = remaining_players[ - (remaining_players["is_quality"] == True) + (remaining_players["is_quality"]) & (remaining_players["price_display"] >= 4.0) # Minimum viability & (remaining_players["price_display"] <= 15.0) # Maximum reasonable & (remaining_players["selected_by_percent"] >= 0.5) # Some ownership @@ -267,7 +267,7 @@ def select_optimal_squad(self, players_df): f"\nNeed {self.squad_size - len(selected_players)} more players. Relaxing constraints..." ) - remaining_needed = self.squad_size - len(selected_players) + # remaining_needed not used directly; compute inline when needed fallback_players = players_df[ ~players_df["fpl_player_id"].isin( [p["fpl_player_id"] for p in selected_players] @@ -299,16 +299,18 @@ def select_optimal_squad(self, players_df): team_counts.get(player["team_id"], 0) + 1 ) - print( - f"Selected (Fallback): {player['web_name']:<15} ({position}) - Ā£{player['price_display']:.1f}m - " - f"Pred: {player['predicted_points']:.2f} pts - " + msg = ( + f"Selected (Fallback): {player['web_name']:<15} ({position}) - " + f"Ā£{player['price_display']:.1f}m | Pred: {player['predicted_points']:.2f} pts | " f"Value: {player['predicted_points_per_million']:.3f}" ) + print(msg) # Final check - if still not 15 players, FORCE complete the squad if len(selected_players) < self.squad_size: print( - f"\nFORCE filling remaining {self.squad_size - len(selected_players)} slots - ignoring budget if needed..." + f"\nFORCE filling remaining {self.squad_size - len(selected_players)} slots - " + f"ignoring budget if needed..." ) # Calculate minimum budget needed for remaining positions @@ -348,10 +350,11 @@ def select_optimal_squad(self, players_df): team_counts.get(player["team_id"], 0) + 1 ) - print( - f"Selected (FORCE): {player['web_name']:<15} ({needed_position}) - Ā£{player['price_display']:.1f}m - " - f"Pred: {player['predicted_points']:.2f} pts" + msg = ( + f"Selected (FORCE): {player['web_name']:<15} ({needed_position}) - " + f"Ā£{player['price_display']:.1f}m | Pred: {player['predicted_points']:.2f} pts" ) + print(msg) # Final squad summary total_players = sum(position_counts.values()) diff --git a/src/tests/test_database_refresh.py b/src/tests/test_database_refresh.py index a8c1f57..075ddb1 100644 --- a/src/tests/test_database_refresh.py +++ b/src/tests/test_database_refresh.py @@ -1,6 +1,4 @@ -import json import types -import pytest from src.database import database_refresh diff --git a/src/tests/test_update_player_prices.py b/src/tests/test_update_player_prices.py index b1eba40..0d05e1a 100644 --- a/src/tests/test_update_player_prices.py +++ b/src/tests/test_update_player_prices.py @@ -1,5 +1,4 @@ import types -import pytest from src.database import update_player_prices @@ -80,7 +79,7 @@ def test_update_player_prices_normalizes_and_skips_out_of_range(monkeypatch): monkeypatch.setattr(updater, "get_current_fpl_data", lambda: test_fpl_players) # Run update - success = updater.update_player_prices() + updater.update_player_prices() # Ensure updates were made and out-of-range player skipped # db id 101 and 102 should have updates recorded From 0007d1e4e9f9a6ef9cd9d8964e8bec28b2f6d8a2 Mon Sep 17 00:00:00 2001 From: Ritvik Uppal Date: Sun, 21 Dec 2025 14:09:10 -0500 Subject: [PATCH 3/3] Fixing workflow --- .github/workflows/python-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index 078870c..202f1bb 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -53,6 +53,7 @@ jobs: SUPABASE_URL: "https://test.supabase.co" SUPABASE_KEY: "test_key" API_KEY: "test_api_key" + PYTHONPATH: "src" run: | pip install pytest pytest-mock pytest src/tests/ --maxfail=3 --disable-warnings -v \ No newline at end of file