Spaces:
Build error
Build error
James McCool commited on
Commit ·
b7931c0
1
Parent(s): b31b495
Add update-pm-files.ps1 script for EC2 Instance Connect file updates and enhance exposure_spread.py with salary eligibility checks for player replacements
Browse files- global_func/exposure_spread.py +42 -11
- update-pm-files.ps1 +112 -0
global_func/exposure_spread.py
CHANGED
|
@@ -148,6 +148,31 @@ def check_position_eligibility(sport, column_name, player_positions):
|
|
| 148 |
# Default fallback - assume exact position match
|
| 149 |
return column_name in player_positions
|
| 150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary_below, comp_salary_above, ignore_stacks, remove_teams, specific_replacements, specific_columns, projections_df, sport_var, type_var, salary_max, stacking_sports):
|
| 152 |
comparable_players = projections_df[projections_df['player_names'] == exposure_player]
|
| 153 |
|
|
@@ -295,7 +320,10 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 295 |
if check_position_eligibility(sport_var, col, replacement_player_positions):
|
| 296 |
suitable_replacements.append(candidate)
|
| 297 |
else:
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
if suitable_replacements:
|
| 301 |
insert_player = random.choice(suitable_replacements)
|
|
@@ -317,9 +345,12 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 317 |
replacement_made = True
|
| 318 |
break
|
| 319 |
else:
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
# Only increment counter if we actually made a replacement
|
| 325 |
if replacement_made:
|
|
@@ -378,22 +409,22 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 378 |
|
| 379 |
for col in working_columns:
|
| 380 |
if row_data[col] in comparable_player_list:
|
| 381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
if type_var == 'Classic':
|
| 383 |
-
|
| 384 |
exposure_player_positions = projections_df[projections_df['player_names'] == exposure_player]['position'].iloc[0].split('/')
|
| 385 |
-
|
| 386 |
-
# Check if the replacement player is eligible for this column
|
| 387 |
-
|
| 388 |
if check_position_eligibility(sport_var, col, exposure_player_positions):
|
| 389 |
working_frame.at[row, col] = exposure_player
|
| 390 |
change_counter += 1
|
| 391 |
break
|
| 392 |
else:
|
|
|
|
| 393 |
working_frame.at[row, col] = exposure_player
|
| 394 |
change_counter += 1
|
| 395 |
break
|
| 396 |
-
else:
|
| 397 |
-
continue
|
| 398 |
return working_frame
|
| 399 |
|
|
|
|
| 148 |
# Default fallback - assume exact position match
|
| 149 |
return column_name in player_positions
|
| 150 |
|
| 151 |
+
def get_effective_salary(player_name, column_name, projections_df, type_var):
|
| 152 |
+
"""Calculate the effective salary for a player in a specific column"""
|
| 153 |
+
base_salary = projections_df[projections_df['player_names'] == player_name]['salary'].iloc[0]
|
| 154 |
+
if type_var != 'Classic' and column_name == 'CPT':
|
| 155 |
+
return base_salary * 1.5
|
| 156 |
+
return base_salary
|
| 157 |
+
|
| 158 |
+
def find_player_column(player_name, row_data, working_columns):
|
| 159 |
+
"""Find which column contains the specified player"""
|
| 160 |
+
for col in working_columns:
|
| 161 |
+
if row_data[col] == player_name:
|
| 162 |
+
return col
|
| 163 |
+
return None
|
| 164 |
+
|
| 165 |
+
def calculate_salary_difference(current_player, new_player, column_name, projections_df, type_var):
|
| 166 |
+
"""Calculate the salary difference when replacing current_player with new_player in a specific column"""
|
| 167 |
+
current_salary = get_effective_salary(current_player, column_name, projections_df, type_var)
|
| 168 |
+
new_salary = get_effective_salary(new_player, column_name, projections_df, type_var)
|
| 169 |
+
return new_salary - current_salary
|
| 170 |
+
|
| 171 |
+
def check_salary_eligibility(current_lineup_salary, current_player, new_player, column_name, projections_df, type_var, salary_max):
|
| 172 |
+
"""Check if replacing current_player with new_player in the specified column stays within salary cap"""
|
| 173 |
+
salary_diff = calculate_salary_difference(current_player, new_player, column_name, projections_df, type_var)
|
| 174 |
+
return current_lineup_salary + salary_diff <= salary_max
|
| 175 |
+
|
| 176 |
def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary_below, comp_salary_above, ignore_stacks, remove_teams, specific_replacements, specific_columns, projections_df, sport_var, type_var, salary_max, stacking_sports):
|
| 177 |
comparable_players = projections_df[projections_df['player_names'] == exposure_player]
|
| 178 |
|
|
|
|
| 320 |
if check_position_eligibility(sport_var, col, replacement_player_positions):
|
| 321 |
suitable_replacements.append(candidate)
|
| 322 |
else:
|
| 323 |
+
# For non-Classic types, check salary eligibility using helper function
|
| 324 |
+
current_lineup_salary = working_frame.iloc[row]['Salary']
|
| 325 |
+
if check_salary_eligibility(current_lineup_salary, exposure_player, candidate, col, projections_df, type_var, salary_max):
|
| 326 |
+
suitable_replacements.append(candidate)
|
| 327 |
|
| 328 |
if suitable_replacements:
|
| 329 |
insert_player = random.choice(suitable_replacements)
|
|
|
|
| 345 |
replacement_made = True
|
| 346 |
break
|
| 347 |
else:
|
| 348 |
+
# For non-Classic types, check salary eligibility using helper function
|
| 349 |
+
current_lineup_salary = working_frame.iloc[row]['Salary']
|
| 350 |
+
if check_salary_eligibility(current_lineup_salary, exposure_player, insert_player, col, projections_df, type_var, salary_max):
|
| 351 |
+
working_frame.at[row, col] = insert_player
|
| 352 |
+
replacement_made = True
|
| 353 |
+
break
|
| 354 |
|
| 355 |
# Only increment counter if we actually made a replacement
|
| 356 |
if replacement_made:
|
|
|
|
| 409 |
|
| 410 |
for col in working_columns:
|
| 411 |
if row_data[col] in comparable_player_list:
|
| 412 |
+
current_lineup_salary = working_frame.iloc[row]['Salary']
|
| 413 |
+
current_player = row_data[col]
|
| 414 |
+
|
| 415 |
+
# Check salary eligibility using helper function
|
| 416 |
+
if check_salary_eligibility(current_lineup_salary, current_player, exposure_player, col, projections_df, type_var, salary_max):
|
| 417 |
if type_var == 'Classic':
|
| 418 |
+
# For Classic types, also check position eligibility
|
| 419 |
exposure_player_positions = projections_df[projections_df['player_names'] == exposure_player]['position'].iloc[0].split('/')
|
|
|
|
|
|
|
|
|
|
| 420 |
if check_position_eligibility(sport_var, col, exposure_player_positions):
|
| 421 |
working_frame.at[row, col] = exposure_player
|
| 422 |
change_counter += 1
|
| 423 |
break
|
| 424 |
else:
|
| 425 |
+
# For non-Classic types, salary check is sufficient (position eligibility handled elsewhere)
|
| 426 |
working_frame.at[row, col] = exposure_player
|
| 427 |
change_counter += 1
|
| 428 |
break
|
|
|
|
|
|
|
| 429 |
return working_frame
|
| 430 |
|
update-pm-files.ps1
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Simple script to update files on all instances using EC2 Instance Connect
|
| 2 |
+
#
|
| 3 |
+
# Usage Examples:
|
| 4 |
+
# .\update-exposure-spread.ps1 # Updates exposure_spread.py (default)
|
| 5 |
+
# .\update-exposure-spread.ps1 -FileName "predict_dupes.py" # Updates predict_dupes.py
|
| 6 |
+
# .\update-exposure-spread.ps1 -FileName "app.py" -TargetPath "/home/ec2-user/AWS_Portfolio_Manager/" # Updates app.py in root directory
|
| 7 |
+
#
|
| 8 |
+
param(
|
| 9 |
+
[Parameter(Mandatory=$false)]
|
| 10 |
+
[string]$FileName = "exposure_spread.py",
|
| 11 |
+
|
| 12 |
+
[Parameter(Mandatory=$false)]
|
| 13 |
+
[string]$TargetPath = "/home/ec2-user/AWS_Portfolio_Manager/global_func/"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# Handle special case: app.py maps to application.py on the remote instances
|
| 17 |
+
$remoteFileName = $FileName
|
| 18 |
+
if ($FileName -eq "app.py") {
|
| 19 |
+
$remoteFileName = "application.py"
|
| 20 |
+
Write-Host "Note: app.py will be uploaded as application.py on remote instances" -ForegroundColor Yellow
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
# Determine local file path - check global_func first, then root directory
|
| 24 |
+
$localFilePath = ""
|
| 25 |
+
if (Test-Path ".\global_func\$FileName") {
|
| 26 |
+
$localFilePath = ".\global_func\$FileName"
|
| 27 |
+
} elseif (Test-Path ".\$FileName") {
|
| 28 |
+
$localFilePath = ".\$FileName"
|
| 29 |
+
# If uploading from root and no target path specified, default to root on remote
|
| 30 |
+
if ($TargetPath -eq "/home/ec2-user/AWS_Portfolio_Manager/global_func/") {
|
| 31 |
+
$TargetPath = "/home/ec2-user/AWS_Portfolio_Manager/"
|
| 32 |
+
}
|
| 33 |
+
} else {
|
| 34 |
+
Write-Host "Error: File '$FileName' not found!" -ForegroundColor Red
|
| 35 |
+
Write-Host "Checked locations:" -ForegroundColor Yellow
|
| 36 |
+
Write-Host " .\global_func\$FileName" -ForegroundColor Cyan
|
| 37 |
+
Write-Host " .\$FileName" -ForegroundColor Cyan
|
| 38 |
+
Write-Host "`nAvailable files in global_func:" -ForegroundColor Yellow
|
| 39 |
+
Get-ChildItem ".\global_func\*.py" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " $($_.Name)" -ForegroundColor Cyan }
|
| 40 |
+
Write-Host "`nAvailable files in root:" -ForegroundColor Yellow
|
| 41 |
+
Get-ChildItem ".\*.py" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " $($_.Name)" -ForegroundColor Cyan }
|
| 42 |
+
exit 1
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
Write-Host "Updating file: $FileName -> $remoteFileName" -ForegroundColor Green
|
| 46 |
+
Write-Host "Local path: $localFilePath" -ForegroundColor Cyan
|
| 47 |
+
Write-Host "Target path: $TargetPath$remoteFileName" -ForegroundColor Cyan
|
| 48 |
+
|
| 49 |
+
# Get instance IPs and IDs
|
| 50 |
+
. .\get-ips.ps1
|
| 51 |
+
$instanceIds = aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names portfolio-manager-asg --query 'AutoScalingGroups[0].Instances[?HealthStatus==`Healthy`].InstanceId' --output text
|
| 52 |
+
|
| 53 |
+
if ($global:instances.Count -eq 0) {
|
| 54 |
+
Write-Host "No instances found" -ForegroundColor Red
|
| 55 |
+
exit 1
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
Write-Host "Found $($global:instances.Count) instances: $($global:instances -join ', ')" -ForegroundColor Green
|
| 59 |
+
|
| 60 |
+
# Generate a temporary SSH key for this session
|
| 61 |
+
Write-Host "Generating temporary SSH key..." -ForegroundColor Cyan
|
| 62 |
+
ssh-keygen -t rsa -f temp_update_key -N '""' -q
|
| 63 |
+
|
| 64 |
+
$instanceIdArray = $instanceIds -split "`t"
|
| 65 |
+
|
| 66 |
+
# Update each instance one at a time to maintain uptime
|
| 67 |
+
for ($i = 0; $i -lt $global:instances.Count; $i++) {
|
| 68 |
+
$ip = $global:instances[$i]
|
| 69 |
+
$instanceId = $instanceIdArray[$i]
|
| 70 |
+
|
| 71 |
+
Write-Host "Updating instance $instanceId ($ip)..." -ForegroundColor Yellow
|
| 72 |
+
|
| 73 |
+
# Send SSH public key via EC2 Instance Connect
|
| 74 |
+
Write-Host "Sending SSH key to $instanceId..."
|
| 75 |
+
aws ec2-instance-connect send-ssh-public-key --instance-id $instanceId --instance-os-user ec2-user --ssh-public-key file://temp_update_key.pub
|
| 76 |
+
|
| 77 |
+
if ($LASTEXITCODE -eq 0) {
|
| 78 |
+
# Copy the file using SCP
|
| 79 |
+
Write-Host "Copying $FileName -> $remoteFileName to $ip..."
|
| 80 |
+
scp -i temp_update_key -o StrictHostKeyChecking=no "$localFilePath" ec2-user@${ip}:$TargetPath$remoteFileName
|
| 81 |
+
|
| 82 |
+
if ($LASTEXITCODE -eq 0) {
|
| 83 |
+
Write-Host "$remoteFileName copied successfully to $ip" -ForegroundColor Green
|
| 84 |
+
|
| 85 |
+
# Only restart Streamlit if we're updating a Python file
|
| 86 |
+
if ($remoteFileName -like "*.py") {
|
| 87 |
+
Write-Host "Restarting Streamlit service on $ip..."
|
| 88 |
+
ssh -i temp_update_key -o StrictHostKeyChecking=no ec2-user@$ip "pkill -f 'streamlit run' && cd /home/ec2-user/AWS_Portfolio_Manager && nohup ./venv/bin/streamlit run application.py --server.port 5000 --server.address 0.0.0.0 > /dev/null 2>&1 &"
|
| 89 |
+
|
| 90 |
+
if ($LASTEXITCODE -eq 0) {
|
| 91 |
+
Write-Host "Service restarted successfully on $ip" -ForegroundColor Green
|
| 92 |
+
} else {
|
| 93 |
+
Write-Host "Failed to restart service on $ip" -ForegroundColor Red
|
| 94 |
+
}
|
| 95 |
+
} else {
|
| 96 |
+
Write-Host "Non-Python file - skipping service restart" -ForegroundColor Yellow
|
| 97 |
+
}
|
| 98 |
+
} else {
|
| 99 |
+
Write-Host "Failed to copy $remoteFileName to $ip" -ForegroundColor Red
|
| 100 |
+
}
|
| 101 |
+
} else {
|
| 102 |
+
Write-Host "Failed to send SSH key to $instanceId" -ForegroundColor Red
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
Write-Host "Waiting 15 seconds before next instance..." -ForegroundColor Cyan
|
| 106 |
+
Start-Sleep -Seconds 15
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
# Clean up temporary key files
|
| 110 |
+
Remove-Item temp_update_key, temp_update_key.pub -ErrorAction SilentlyContinue
|
| 111 |
+
|
| 112 |
+
Write-Host "All instances updated with rolling restart!" -ForegroundColor Green
|