Spaces:
Build error
Build error
James McCool commited on
Commit ·
4314382
1
Parent(s): fac0021
Add opponent mapping and conflict checks in exposure spread and lineup optimization functions; refactor data loading for MMA projections
Browse files- app.py +58 -10
- database_queries.py +15 -5
- global_func/exposure_spread.py +53 -29
- global_func/optimize_lineup.py +43 -6
app.py
CHANGED
|
@@ -42,6 +42,7 @@ pos_parse_mapping = {
|
|
| 42 |
|
| 43 |
pos_parse_options = list(pos_parse_mapping.keys())
|
| 44 |
|
|
|
|
| 45 |
showdown_selections = ['Showdown #1', 'Showdown #2', 'Showdown #3', 'Showdown #4', 'Showdown #5', 'Showdown #6', 'Showdown #7', 'Showdown #8', 'Showdown #9', 'Showdown #10', 'Showdown #11', 'Showdown #12', 'Showdown #13', 'Showdown #14', 'Showdown #15']
|
| 46 |
dk_db_nfl_showdown_selections = ['DK_NFL_SD_seed_frame_Showdown #1', 'DK_NFL_SD_seed_frame_Showdown #2', 'DK_NFL_SD_seed_frame_Showdown #3', 'DK_NFL_SD_seed_frame_Showdown #4', 'DK_NFL_SD_seed_frame_Showdown #5', 'DK_NFL_SD_seed_frame_Showdown #6',
|
| 47 |
'DK_NFL_SD_seed_frame_Showdown #7', 'DK_NFL_SD_seed_frame_Showdown #8', 'DK_NFL_SD_seed_frame_Showdown #9', 'DK_NFL_SD_seed_frame_Showdown #10', 'DK_NFL_SD_seed_frame_Showdown #11', 'DK_NFL_SD_seed_frame_Showdown #12', 'DK_NFL_SD_seed_frame_Showdown #13',
|
|
@@ -74,6 +75,7 @@ fd_db_pga_showdown_selections = ['FD_PGA_SD_seed_frame_Showdown #1', 'FD_PGA_SD_
|
|
| 74 |
'FD_PGA_SD_seed_frame_Showdown #7', 'FD_PGA_SD_seed_frame_Showdown #8', 'FD_PGA_SD_seed_frame_Showdown #9', 'FD_PGA_SD_seed_frame_Showdown #10', 'FD_PGA_SD_seed_frame_Showdown #11', 'FD_PGA_SD_seed_frame_Showdown #12', 'FD_PGA_SD_seed_frame_Showdown #13',
|
| 75 |
'FD_PGA_SD_seed_frame_Showdown #14', 'FD_PGA_SD_seed_frame_Showdown #15']
|
| 76 |
|
|
|
|
| 77 |
dk_nfl_showdown_db_translation = dict(zip(showdown_selections, dk_db_nfl_showdown_selections))
|
| 78 |
fd_nfl_showdown_db_translation = dict(zip(showdown_selections, fd_db_nfl_showdown_selections))
|
| 79 |
dk_nba_showdown_db_translation = dict(zip(showdown_selections, dk_db_nba_showdown_selections))
|
|
@@ -1611,7 +1613,7 @@ if selected_tab == 'Data Load':
|
|
| 1611 |
proj_options = st.selectbox("Select a projections source", options=['Paydirt DB', 'User Upload'])
|
| 1612 |
|
| 1613 |
upload_col, template_col = st.columns([3, 1])
|
| 1614 |
-
|
| 1615 |
with upload_col:
|
| 1616 |
if 'portfolio' not in st.session_state:
|
| 1617 |
st.session_state['projections_loaded'] = False
|
|
@@ -1620,6 +1622,7 @@ if selected_tab == 'Data Load':
|
|
| 1620 |
st.session_state['db_projections_file'] = projections_file
|
| 1621 |
st.session_state['projections_loaded'] = True
|
| 1622 |
elif proj_options == 'Paydirt DB':
|
|
|
|
| 1623 |
if st.button("Load from Database"):
|
| 1624 |
if sport_var == 'NBA':
|
| 1625 |
if site_var == 'Draftkings':
|
|
@@ -1658,11 +1661,13 @@ if selected_tab == 'Data Load':
|
|
| 1658 |
if site_var == 'Draftkings':
|
| 1659 |
if type_var == 'Classic':
|
| 1660 |
projections_file = init_mma_baselines(type_var, site_var, slate_var3)[0]
|
|
|
|
| 1661 |
elif type_var == 'Showdown':
|
| 1662 |
projections_file = init_mma_baselines(type_var, site_var, slate_var3)[2]
|
| 1663 |
elif site_var == 'Fanduel':
|
| 1664 |
if type_var == 'Classic':
|
| 1665 |
projections_file = init_mma_baselines(type_var, site_var, slate_var3)[1]
|
|
|
|
| 1666 |
elif type_var == 'Showdown':
|
| 1667 |
projections_file = init_mma_baselines(type_var, site_var, slate_var3)[3]
|
| 1668 |
elif sport_var == 'GOLF':
|
|
@@ -1795,6 +1800,11 @@ if selected_tab == 'Data Load':
|
|
| 1795 |
type_var,
|
| 1796 |
sport_var
|
| 1797 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1798 |
|
| 1799 |
st.session_state['portfolio'] = st.session_state['portfolio'].astype(str)
|
| 1800 |
st.session_state['portfolio'] = st.session_state['portfolio'][~st.session_state['portfolio'].isin(['', 'nan', 'None', 'NaN']).any(axis=1)].reset_index(drop=True)
|
|
@@ -2879,7 +2889,8 @@ if selected_tab == 'Manage Portfolio':
|
|
| 2879 |
sport_var,
|
| 2880 |
type_var,
|
| 2881 |
salary_max,
|
| 2882 |
-
stacking_sports
|
|
|
|
| 2883 |
)
|
| 2884 |
|
| 2885 |
# Update the original dataframe with the modified rows
|
|
@@ -2939,7 +2950,8 @@ if selected_tab == 'Manage Portfolio':
|
|
| 2939 |
sport_var,
|
| 2940 |
type_var,
|
| 2941 |
salary_max,
|
| 2942 |
-
stacking_sports
|
|
|
|
| 2943 |
)
|
| 2944 |
|
| 2945 |
parsed_frame.loc[containing_mask] = modified_rows.values
|
|
@@ -2979,7 +2991,24 @@ if selected_tab == 'Manage Portfolio':
|
|
| 2979 |
if reg_submitted:
|
| 2980 |
st.session_state['settings_base'] = False
|
| 2981 |
working_frame_prepared = prepare_dataframe_for_exposure_spread(st.session_state['working_frame'], st.session_state['player_columns'])
|
| 2982 |
-
parsed_frame = exposure_spread(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2983 |
|
| 2984 |
# Use consolidated calculation function
|
| 2985 |
parsed_frame = calculate_lineup_metrics(
|
|
@@ -3014,7 +3043,24 @@ if selected_tab == 'Manage Portfolio':
|
|
| 3014 |
elif exp_submitted:
|
| 3015 |
st.session_state['settings_base'] = False
|
| 3016 |
export_base_prepared = prepare_dataframe_for_exposure_spread(st.session_state['export_base'], st.session_state['player_columns'])
|
| 3017 |
-
parsed_frame = exposure_spread(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3018 |
|
| 3019 |
# Use consolidated calculation function for export
|
| 3020 |
parsed_frame = calculate_lineup_metrics(
|
|
@@ -3231,9 +3277,13 @@ if selected_tab == 'Manage Portfolio':
|
|
| 3231 |
st.session_state['export_file'][col] = st.session_state['export_file'][col].map(position_dict)
|
| 3232 |
|
| 3233 |
if 'export_file' in st.session_state:
|
| 3234 |
-
download_port, merge_port, clear_export, add_rows_col, remove_rows_col, blank_export_col = st.columns([1, 1, 1, 2, 2,
|
| 3235 |
with download_port:
|
| 3236 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3237 |
|
| 3238 |
with merge_port:
|
| 3239 |
if st.button("Add all to Custom Export"):
|
|
@@ -3266,13 +3316,11 @@ if selected_tab == 'Manage Portfolio':
|
|
| 3266 |
|
| 3267 |
total_rows = len(st.session_state['display_frame'])
|
| 3268 |
rows_per_page = 500
|
| 3269 |
-
total_pages = (total_rows + rows_per_page - 1) // rows_per_page
|
| 3270 |
|
| 3271 |
-
# Initialize page number in session state if not exists
|
| 3272 |
if 'current_page' not in st.session_state:
|
| 3273 |
st.session_state.current_page = 1
|
| 3274 |
|
| 3275 |
-
# Display current page range info and pagination control in a single line
|
| 3276 |
st.write(
|
| 3277 |
f"Showing rows {(st.session_state.current_page - 1) * rows_per_page + 1} "
|
| 3278 |
f"to {min(st.session_state.current_page * rows_per_page, total_rows)} of {total_rows}"
|
|
|
|
| 42 |
|
| 43 |
pos_parse_options = list(pos_parse_mapping.keys())
|
| 44 |
|
| 45 |
+
## Should just do a loop with some f'strings here I think
|
| 46 |
showdown_selections = ['Showdown #1', 'Showdown #2', 'Showdown #3', 'Showdown #4', 'Showdown #5', 'Showdown #6', 'Showdown #7', 'Showdown #8', 'Showdown #9', 'Showdown #10', 'Showdown #11', 'Showdown #12', 'Showdown #13', 'Showdown #14', 'Showdown #15']
|
| 47 |
dk_db_nfl_showdown_selections = ['DK_NFL_SD_seed_frame_Showdown #1', 'DK_NFL_SD_seed_frame_Showdown #2', 'DK_NFL_SD_seed_frame_Showdown #3', 'DK_NFL_SD_seed_frame_Showdown #4', 'DK_NFL_SD_seed_frame_Showdown #5', 'DK_NFL_SD_seed_frame_Showdown #6',
|
| 48 |
'DK_NFL_SD_seed_frame_Showdown #7', 'DK_NFL_SD_seed_frame_Showdown #8', 'DK_NFL_SD_seed_frame_Showdown #9', 'DK_NFL_SD_seed_frame_Showdown #10', 'DK_NFL_SD_seed_frame_Showdown #11', 'DK_NFL_SD_seed_frame_Showdown #12', 'DK_NFL_SD_seed_frame_Showdown #13',
|
|
|
|
| 75 |
'FD_PGA_SD_seed_frame_Showdown #7', 'FD_PGA_SD_seed_frame_Showdown #8', 'FD_PGA_SD_seed_frame_Showdown #9', 'FD_PGA_SD_seed_frame_Showdown #10', 'FD_PGA_SD_seed_frame_Showdown #11', 'FD_PGA_SD_seed_frame_Showdown #12', 'FD_PGA_SD_seed_frame_Showdown #13',
|
| 76 |
'FD_PGA_SD_seed_frame_Showdown #14', 'FD_PGA_SD_seed_frame_Showdown #15']
|
| 77 |
|
| 78 |
+
## This feels like a bad way to do this?
|
| 79 |
dk_nfl_showdown_db_translation = dict(zip(showdown_selections, dk_db_nfl_showdown_selections))
|
| 80 |
fd_nfl_showdown_db_translation = dict(zip(showdown_selections, fd_db_nfl_showdown_selections))
|
| 81 |
dk_nba_showdown_db_translation = dict(zip(showdown_selections, dk_db_nba_showdown_selections))
|
|
|
|
| 1613 |
proj_options = st.selectbox("Select a projections source", options=['Paydirt DB', 'User Upload'])
|
| 1614 |
|
| 1615 |
upload_col, template_col = st.columns([3, 1])
|
| 1616 |
+
|
| 1617 |
with upload_col:
|
| 1618 |
if 'portfolio' not in st.session_state:
|
| 1619 |
st.session_state['projections_loaded'] = False
|
|
|
|
| 1622 |
st.session_state['db_projections_file'] = projections_file
|
| 1623 |
st.session_state['projections_loaded'] = True
|
| 1624 |
elif proj_options == 'Paydirt DB':
|
| 1625 |
+
## Should make it so this is dynamic instead of having hardcoded things here
|
| 1626 |
if st.button("Load from Database"):
|
| 1627 |
if sport_var == 'NBA':
|
| 1628 |
if site_var == 'Draftkings':
|
|
|
|
| 1661 |
if site_var == 'Draftkings':
|
| 1662 |
if type_var == 'Classic':
|
| 1663 |
projections_file = init_mma_baselines(type_var, site_var, slate_var3)[0]
|
| 1664 |
+
opp_map = init_mma_baselines(type_var, site_var, slate_var3)[8]
|
| 1665 |
elif type_var == 'Showdown':
|
| 1666 |
projections_file = init_mma_baselines(type_var, site_var, slate_var3)[2]
|
| 1667 |
elif site_var == 'Fanduel':
|
| 1668 |
if type_var == 'Classic':
|
| 1669 |
projections_file = init_mma_baselines(type_var, site_var, slate_var3)[1]
|
| 1670 |
+
opp_map = init_mma_baselines(type_var, site_var, slate_var3)[8]
|
| 1671 |
elif type_var == 'Showdown':
|
| 1672 |
projections_file = init_mma_baselines(type_var, site_var, slate_var3)[3]
|
| 1673 |
elif sport_var == 'GOLF':
|
|
|
|
| 1800 |
type_var,
|
| 1801 |
sport_var
|
| 1802 |
)
|
| 1803 |
+
|
| 1804 |
+
if sport_var == 'MMA':
|
| 1805 |
+
st.session_state['map_dict']['opp_map'] = opp_map
|
| 1806 |
+
else:
|
| 1807 |
+
st.session_state['map_dict']['opp_map'] = None
|
| 1808 |
|
| 1809 |
st.session_state['portfolio'] = st.session_state['portfolio'].astype(str)
|
| 1810 |
st.session_state['portfolio'] = st.session_state['portfolio'][~st.session_state['portfolio'].isin(['', 'nan', 'None', 'NaN']).any(axis=1)].reset_index(drop=True)
|
|
|
|
| 2889 |
sport_var,
|
| 2890 |
type_var,
|
| 2891 |
salary_max,
|
| 2892 |
+
stacking_sports,
|
| 2893 |
+
st.session_state['map_dict']['opp_map']
|
| 2894 |
)
|
| 2895 |
|
| 2896 |
# Update the original dataframe with the modified rows
|
|
|
|
| 2950 |
sport_var,
|
| 2951 |
type_var,
|
| 2952 |
salary_max,
|
| 2953 |
+
stacking_sports,
|
| 2954 |
+
st.session_state['map_dict']['opp_map']
|
| 2955 |
)
|
| 2956 |
|
| 2957 |
parsed_frame.loc[containing_mask] = modified_rows.values
|
|
|
|
| 2991 |
if reg_submitted:
|
| 2992 |
st.session_state['settings_base'] = False
|
| 2993 |
working_frame_prepared = prepare_dataframe_for_exposure_spread(st.session_state['working_frame'], st.session_state['player_columns'])
|
| 2994 |
+
parsed_frame = exposure_spread(
|
| 2995 |
+
working_frame_prepared,
|
| 2996 |
+
st.session_state['exposure_player'],
|
| 2997 |
+
exposure_target,
|
| 2998 |
+
comp_salary_below,
|
| 2999 |
+
comp_salary_above,
|
| 3000 |
+
ignore_stacks,
|
| 3001 |
+
remove_teams_exposure,
|
| 3002 |
+
specific_replacements,
|
| 3003 |
+
specific_exclusions,
|
| 3004 |
+
specific_columns,
|
| 3005 |
+
st.session_state['portfolio_inc_proj'],
|
| 3006 |
+
sport_var,
|
| 3007 |
+
type_var,
|
| 3008 |
+
salary_max,
|
| 3009 |
+
stacking_sports,
|
| 3010 |
+
st.session_state['map_dict']['opp_map']
|
| 3011 |
+
)
|
| 3012 |
|
| 3013 |
# Use consolidated calculation function
|
| 3014 |
parsed_frame = calculate_lineup_metrics(
|
|
|
|
| 3043 |
elif exp_submitted:
|
| 3044 |
st.session_state['settings_base'] = False
|
| 3045 |
export_base_prepared = prepare_dataframe_for_exposure_spread(st.session_state['export_base'], st.session_state['player_columns'])
|
| 3046 |
+
parsed_frame = exposure_spread(
|
| 3047 |
+
export_base_prepared,
|
| 3048 |
+
st.session_state['exposure_player'],
|
| 3049 |
+
exposure_target,
|
| 3050 |
+
comp_salary_below,
|
| 3051 |
+
comp_salary_above,
|
| 3052 |
+
ignore_stacks,
|
| 3053 |
+
remove_teams_exposure,
|
| 3054 |
+
specific_replacements,
|
| 3055 |
+
specific_exclusions,
|
| 3056 |
+
specific_columns,
|
| 3057 |
+
st.session_state['portfolio_inc_proj'],
|
| 3058 |
+
sport_var,
|
| 3059 |
+
type_var,
|
| 3060 |
+
salary_max,
|
| 3061 |
+
stacking_sports,
|
| 3062 |
+
st.session_state['map_dict']['opp_map']
|
| 3063 |
+
)
|
| 3064 |
|
| 3065 |
# Use consolidated calculation function for export
|
| 3066 |
parsed_frame = calculate_lineup_metrics(
|
|
|
|
| 3277 |
st.session_state['export_file'][col] = st.session_state['export_file'][col].map(position_dict)
|
| 3278 |
|
| 3279 |
if 'export_file' in st.session_state:
|
| 3280 |
+
download_port, merge_port, clear_export, add_rows_col, remove_rows_col, blank_export_col = st.columns([1, 1, 1, 2, 2, 4])
|
| 3281 |
with download_port:
|
| 3282 |
+
st.selectbox("Download as:", options=['Names+IDs', 'Names Only'], key='download_format')
|
| 3283 |
+
if st.session_state['download_format'] == 'Names+IDs':
|
| 3284 |
+
st.download_button(label="Download Portfolio", data=st.session_state['export_file'].to_csv(index=False), file_name="portfolio.csv", mime="text/csv")
|
| 3285 |
+
else:
|
| 3286 |
+
st.download_button(label="Download Portfolio", data=st.session_state['display_frame'].to_csv(index=False), file_name="portfolio.csv", mime="text/csv")
|
| 3287 |
|
| 3288 |
with merge_port:
|
| 3289 |
if st.button("Add all to Custom Export"):
|
|
|
|
| 3316 |
|
| 3317 |
total_rows = len(st.session_state['display_frame'])
|
| 3318 |
rows_per_page = 500
|
| 3319 |
+
total_pages = (total_rows + rows_per_page - 1) // rows_per_page
|
| 3320 |
|
|
|
|
| 3321 |
if 'current_page' not in st.session_state:
|
| 3322 |
st.session_state.current_page = 1
|
| 3323 |
|
|
|
|
| 3324 |
st.write(
|
| 3325 |
f"Showing rows {(st.session_state.current_page - 1) * rows_per_page + 1} "
|
| 3326 |
f"to {min(st.session_state.current_page * rows_per_page, total_rows)} of {total_rows}"
|
database_queries.py
CHANGED
|
@@ -466,7 +466,6 @@ def init_nba_baselines(type_var: str, site_var: str, slate_var: str):
|
|
| 466 |
fd_sd_id_map = dict(zip(fd_sd_roo_raw['Player'], fd_sd_roo_raw['player_ID']))
|
| 467 |
fd_sd_roo_raw['player_ID'] = fd_sd_roo_raw['player_ID'].astype(str)
|
| 468 |
fd_sd_roo_raw['player_ID'] = fd_sd_roo_raw['player_ID'].str.rsplit('-', n=1).str[0].astype(str)
|
| 469 |
-
dk_sd_roo_raw = dk_sd_roo_raw[dk_sd_roo_raw['slate'] == slate_var]
|
| 470 |
|
| 471 |
dk_sd_roo_raw = dk_sd_roo_raw.drop(columns=['player_ID', 'slate', 'version', 'timestamp', 'site'])
|
| 472 |
fd_sd_roo_raw = fd_sd_roo_raw.drop(columns=['player_ID', 'slate', 'version', 'timestamp', 'site'])
|
|
@@ -501,6 +500,15 @@ def init_nba_baselines(type_var: str, site_var: str, slate_var: str):
|
|
| 501 |
dk_roo_raw = dk_roo_raw.rename(columns={'Player': 'player_names', 'Position': 'position', 'Team': 'team', 'Salary': 'salary', 'Median': 'median', 'Own': 'ownership', 'CPT_Own': 'captain ownership'})
|
| 502 |
fd_roo_raw = fd_roo_raw.rename(columns={'Player': 'player_names', 'Position': 'position', 'Team': 'team', 'Salary': 'salary', 'Median': 'median', 'Own': 'ownership', 'CPT_Own': 'captain ownership'})
|
| 503 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
dk_sd_roo_raw = None
|
| 505 |
fd_sd_roo_raw = None
|
| 506 |
dk_sd_id_map = None
|
|
@@ -1267,6 +1275,7 @@ def init_mma_baselines(type_var: str, site_var: str, slate_var: str):
|
|
| 1267 |
|
| 1268 |
dk_roo_raw = None
|
| 1269 |
fd_roo_raw = None
|
|
|
|
| 1270 |
dk_id_map = None
|
| 1271 |
fd_id_map = None
|
| 1272 |
|
|
@@ -1275,7 +1284,7 @@ def init_mma_baselines(type_var: str, site_var: str, slate_var: str):
|
|
| 1275 |
cursor = collection.find()
|
| 1276 |
|
| 1277 |
raw_display = pd.DataFrame(list(cursor))
|
| 1278 |
-
raw_display = raw_display[['Player', 'Position', 'Salary', 'Median', 'Own', 'CPT_Own', 'player_id']]
|
| 1279 |
raw_display['Team'] = 'MMA'
|
| 1280 |
raw_display['Site'] = site_var
|
| 1281 |
raw_display = raw_display.rename(columns={"player_id": "player_ID"})
|
|
@@ -1285,10 +1294,11 @@ def init_mma_baselines(type_var: str, site_var: str, slate_var: str):
|
|
| 1285 |
fd_roo_raw = raw_display[raw_display['Site'] == 'Draftkings']
|
| 1286 |
dk_id_map = dict(zip(dk_roo_raw['Player'], dk_roo_raw['player_ID']))
|
| 1287 |
fd_id_map = dict(zip(fd_roo_raw['Player'], fd_roo_raw['player_ID']))
|
|
|
|
| 1288 |
raw_display = raw_display.apply(pd.to_numeric, errors='ignore')
|
| 1289 |
|
| 1290 |
-
dk_roo_raw = dk_roo_raw.drop(columns=['player_ID'])
|
| 1291 |
-
fd_roo_raw = fd_roo_raw.drop(columns=['player_ID'])
|
| 1292 |
|
| 1293 |
dk_roo_raw = dk_roo_raw.rename(columns={'Player': 'player_names', 'Position': 'position', 'Team': 'team', 'Salary': 'salary', 'Median': 'median', 'Own': 'ownership', 'CPT_Own': 'captain ownership'})
|
| 1294 |
fd_roo_raw = fd_roo_raw.rename(columns={'Player': 'player_names', 'Position': 'position', 'Team': 'team', 'Salary': 'salary', 'Median': 'median', 'Own': 'ownership', 'CPT_Own': 'captain ownership'})
|
|
@@ -1298,7 +1308,7 @@ def init_mma_baselines(type_var: str, site_var: str, slate_var: str):
|
|
| 1298 |
dk_sd_id_map = None
|
| 1299 |
fd_sd_id_map = None
|
| 1300 |
|
| 1301 |
-
return dk_roo_raw, fd_roo_raw, dk_sd_roo_raw, fd_sd_roo_raw, dk_id_map, fd_id_map, dk_sd_id_map, fd_sd_id_map
|
| 1302 |
|
| 1303 |
def init_DK_MMA_lineups(type_var, slate_var, prio_var, prio_mix, lineup_num, salary_min, salary_max, player_var2):
|
| 1304 |
|
|
|
|
| 466 |
fd_sd_id_map = dict(zip(fd_sd_roo_raw['Player'], fd_sd_roo_raw['player_ID']))
|
| 467 |
fd_sd_roo_raw['player_ID'] = fd_sd_roo_raw['player_ID'].astype(str)
|
| 468 |
fd_sd_roo_raw['player_ID'] = fd_sd_roo_raw['player_ID'].str.rsplit('-', n=1).str[0].astype(str)
|
|
|
|
| 469 |
|
| 470 |
dk_sd_roo_raw = dk_sd_roo_raw.drop(columns=['player_ID', 'slate', 'version', 'timestamp', 'site'])
|
| 471 |
fd_sd_roo_raw = fd_sd_roo_raw.drop(columns=['player_ID', 'slate', 'version', 'timestamp', 'site'])
|
|
|
|
| 500 |
dk_roo_raw = dk_roo_raw.rename(columns={'Player': 'player_names', 'Position': 'position', 'Team': 'team', 'Salary': 'salary', 'Median': 'median', 'Own': 'ownership', 'CPT_Own': 'captain ownership'})
|
| 501 |
fd_roo_raw = fd_roo_raw.rename(columns={'Player': 'player_names', 'Position': 'position', 'Team': 'team', 'Salary': 'salary', 'Median': 'median', 'Own': 'ownership', 'CPT_Own': 'captain ownership'})
|
| 502 |
|
| 503 |
+
collection = nba_db["Live_Projections"]
|
| 504 |
+
cursor = collection.find()
|
| 505 |
+
|
| 506 |
+
raw_display = pd.DataFrame(list(cursor))
|
| 507 |
+
live_dict = dict(zip(raw_display['Player'], raw_display['Live_Proj']))
|
| 508 |
+
|
| 509 |
+
dk_roo_raw['median'] = dk_roo_raw['player_names'].map(live_dict)
|
| 510 |
+
fd_roo_raw['median'] = fd_roo_raw['player_names'].map(live_dict)
|
| 511 |
+
|
| 512 |
dk_sd_roo_raw = None
|
| 513 |
fd_sd_roo_raw = None
|
| 514 |
dk_sd_id_map = None
|
|
|
|
| 1275 |
|
| 1276 |
dk_roo_raw = None
|
| 1277 |
fd_roo_raw = None
|
| 1278 |
+
opp_map = None
|
| 1279 |
dk_id_map = None
|
| 1280 |
fd_id_map = None
|
| 1281 |
|
|
|
|
| 1284 |
cursor = collection.find()
|
| 1285 |
|
| 1286 |
raw_display = pd.DataFrame(list(cursor))
|
| 1287 |
+
raw_display = raw_display[['Player', 'Opp', 'Position', 'Salary', 'Median', 'Own', 'CPT_Own', 'player_id']]
|
| 1288 |
raw_display['Team'] = 'MMA'
|
| 1289 |
raw_display['Site'] = site_var
|
| 1290 |
raw_display = raw_display.rename(columns={"player_id": "player_ID"})
|
|
|
|
| 1294 |
fd_roo_raw = raw_display[raw_display['Site'] == 'Draftkings']
|
| 1295 |
dk_id_map = dict(zip(dk_roo_raw['Player'], dk_roo_raw['player_ID']))
|
| 1296 |
fd_id_map = dict(zip(fd_roo_raw['Player'], fd_roo_raw['player_ID']))
|
| 1297 |
+
opp_map = dict(zip(dk_roo_raw['Player'], dk_roo_raw['Opp']))
|
| 1298 |
raw_display = raw_display.apply(pd.to_numeric, errors='ignore')
|
| 1299 |
|
| 1300 |
+
dk_roo_raw = dk_roo_raw.drop(columns=['player_ID', 'Opp'])
|
| 1301 |
+
fd_roo_raw = fd_roo_raw.drop(columns=['player_ID', 'Opp'])
|
| 1302 |
|
| 1303 |
dk_roo_raw = dk_roo_raw.rename(columns={'Player': 'player_names', 'Position': 'position', 'Team': 'team', 'Salary': 'salary', 'Median': 'median', 'Own': 'ownership', 'CPT_Own': 'captain ownership'})
|
| 1304 |
fd_roo_raw = fd_roo_raw.rename(columns={'Player': 'player_names', 'Position': 'position', 'Team': 'team', 'Salary': 'salary', 'Median': 'median', 'Own': 'ownership', 'CPT_Own': 'captain ownership'})
|
|
|
|
| 1308 |
dk_sd_id_map = None
|
| 1309 |
fd_sd_id_map = None
|
| 1310 |
|
| 1311 |
+
return dk_roo_raw, fd_roo_raw, dk_sd_roo_raw, fd_sd_roo_raw, dk_id_map, fd_id_map, dk_sd_id_map, fd_sd_id_map, opp_map
|
| 1312 |
|
| 1313 |
def init_DK_MMA_lineups(type_var, slate_var, prio_var, prio_mix, lineup_num, salary_min, salary_max, player_var2):
|
| 1314 |
|
global_func/exposure_spread.py
CHANGED
|
@@ -173,7 +173,12 @@ def check_salary_eligibility(current_lineup_salary, current_player, new_player,
|
|
| 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_exclusions, specific_columns, projections_df, sport_var, type_var, salary_max, stacking_sports):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
if specific_exclusions != []:
|
| 178 |
projections_df = projections_df[~projections_df['player_names'].isin(specific_exclusions)]
|
| 179 |
comparable_players = projections_df[projections_df['player_names'] == exposure_player]
|
|
@@ -191,8 +196,7 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 191 |
comp_projection_low = comparable_players['median'][0] - (comparable_players['median'][0] * .5)
|
| 192 |
else:
|
| 193 |
comp_projection_low = comparable_players['median'][0] - (comparable_players['median'][0] * .5)
|
| 194 |
-
|
| 195 |
-
# the position column can have positions designated as 1B/OF which means they are eligible at 1B and OF
|
| 196 |
comp_player_position = comparable_players['position'].tolist()
|
| 197 |
comp_team = comparable_players['team'].tolist()
|
| 198 |
try:
|
|
@@ -206,6 +210,23 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 206 |
player_pos_list = player_positions.split('/')
|
| 207 |
return any(pos in target_positions for pos in player_pos_list)
|
| 208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
# find the exposure rate of the player in the working frame
|
| 210 |
if specific_columns != []:
|
| 211 |
player_mask = working_frame[specific_columns].apply(
|
|
@@ -249,15 +270,9 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 249 |
random.shuffle(random_row_indices_insert)
|
| 250 |
random.shuffle(random_row_indices_replace)
|
| 251 |
|
| 252 |
-
#
|
| 253 |
-
|
| 254 |
-
# we will need to use two separate functions here, one for an exposure player who has a lineups to remove above 0 and one for below 0
|
| 255 |
-
# key concept here is if they have a lineups to remove above 0 it means that we are trying to replace them with comparable players
|
| 256 |
-
# if the lineups to remove is below zero it means we want to find comparable players and replace them with the exposure player
|
| 257 |
if lineups_to_remove > 0:
|
| 258 |
-
# Keep trying until we've made enough successful replacements
|
| 259 |
while change_counter < math.ceil(lineups_to_remove) and random_row_indices_insert:
|
| 260 |
-
# Get the next row to try
|
| 261 |
row = random_row_indices_insert.pop(0)
|
| 262 |
|
| 263 |
if specific_replacements != []:
|
|
@@ -296,7 +311,6 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 296 |
comparable_player_list = []
|
| 297 |
|
| 298 |
if comparable_player_list:
|
| 299 |
-
# Find which column contains the exposure_player
|
| 300 |
if specific_columns != []:
|
| 301 |
row_data = working_frame.iloc[row][specific_columns]
|
| 302 |
working_columns = specific_columns
|
|
@@ -304,25 +318,23 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 304 |
row_data = working_frame.iloc[row]
|
| 305 |
working_columns = working_frame.columns
|
| 306 |
|
| 307 |
-
# Track if we successfully made a replacement
|
| 308 |
replacement_made = False
|
| 309 |
|
| 310 |
-
# For exposure_target == 0, replace ALL occurrences of exposure_player in this row
|
| 311 |
if exposure_target == 0:
|
| 312 |
for col in working_columns:
|
| 313 |
if row_data[col] == exposure_player:
|
| 314 |
-
# Try to find a suitable replacement for this specific column
|
| 315 |
suitable_replacements = []
|
| 316 |
for candidate in comparable_player_list:
|
| 317 |
-
#
|
|
|
|
|
|
|
|
|
|
| 318 |
replacement_player_positions = projections_df[projections_df['player_names'] == candidate]['position'].iloc[0].split('/')
|
| 319 |
|
| 320 |
-
# Check if the replacement player is eligible for this column
|
| 321 |
if type_var == 'Classic':
|
| 322 |
if check_position_eligibility(sport_var, col, replacement_player_positions):
|
| 323 |
suitable_replacements.append(candidate)
|
| 324 |
else:
|
| 325 |
-
# For non-Classic types, check salary eligibility using helper function
|
| 326 |
current_lineup_salary = working_frame.iloc[row]['salary']
|
| 327 |
if check_salary_eligibility(current_lineup_salary, exposure_player, candidate, col, projections_df, type_var, salary_max):
|
| 328 |
suitable_replacements.append(candidate)
|
|
@@ -331,39 +343,55 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 331 |
insert_player = random.choice(suitable_replacements)
|
| 332 |
working_frame.at[row, col] = insert_player
|
| 333 |
replacement_made = True
|
| 334 |
-
# Remove this player from the list to avoid duplicates in the same row
|
| 335 |
comparable_player_list = [p for p in comparable_player_list if p != insert_player]
|
| 336 |
else:
|
| 337 |
for col in working_columns:
|
| 338 |
if row_data[col] == exposure_player:
|
| 339 |
-
|
| 340 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
replacement_player_positions = projections_df[projections_df['player_names'] == insert_player]['position'].iloc[0].split('/')
|
| 342 |
|
| 343 |
-
# Check if the replacement player is eligible for this column
|
| 344 |
if type_var == 'Classic':
|
| 345 |
if check_position_eligibility(sport_var, col, replacement_player_positions):
|
| 346 |
working_frame.at[row, col] = insert_player
|
| 347 |
replacement_made = True
|
| 348 |
break
|
| 349 |
else:
|
| 350 |
-
# For non-Classic types, check salary eligibility using helper function
|
| 351 |
current_lineup_salary = working_frame.iloc[row]['salary']
|
| 352 |
if check_salary_eligibility(current_lineup_salary, exposure_player, insert_player, col, projections_df, type_var, salary_max):
|
| 353 |
working_frame.at[row, col] = insert_player
|
| 354 |
replacement_made = True
|
| 355 |
break
|
| 356 |
|
| 357 |
-
# Only increment counter if we actually made a replacement
|
| 358 |
if replacement_made:
|
| 359 |
change_counter += 1
|
| 360 |
|
| 361 |
-
# If we've run out of rows to try, break to avoid infinite loop
|
| 362 |
if not random_row_indices_insert:
|
| 363 |
break
|
|
|
|
|
|
|
| 364 |
else:
|
| 365 |
while change_counter < math.ceil(lineups_to_add) and random_row_indices_replace:
|
| 366 |
row = random_row_indices_replace.pop(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
if specific_replacements != []:
|
| 368 |
comparable_players = projections_df[(projections_df['player_names'].isin(specific_replacements))
|
| 369 |
]
|
|
@@ -394,14 +422,12 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 394 |
|
| 395 |
comparable_players = comparable_players[comparable_players['player_names'] != exposure_player]
|
| 396 |
|
| 397 |
-
# Create a list of comparable players
|
| 398 |
comparable_player_list = comparable_players['player_names'].tolist()
|
| 399 |
|
| 400 |
if exposure_player in working_frame.iloc[row].values:
|
| 401 |
comparable_player_list = []
|
| 402 |
|
| 403 |
if comparable_player_list:
|
| 404 |
-
# Find which column contains the exposure_player
|
| 405 |
if specific_columns != []:
|
| 406 |
row_data = working_frame.iloc[row][specific_columns]
|
| 407 |
working_columns = specific_columns
|
|
@@ -414,19 +440,17 @@ def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary
|
|
| 414 |
current_lineup_salary = working_frame.iloc[row]['salary']
|
| 415 |
current_player = row_data[col]
|
| 416 |
|
| 417 |
-
# Check salary eligibility using helper function
|
| 418 |
if check_salary_eligibility(current_lineup_salary, current_player, exposure_player, col, projections_df, type_var, salary_max):
|
| 419 |
if type_var == 'Classic':
|
| 420 |
-
# For Classic types, also check position eligibility
|
| 421 |
exposure_player_positions = projections_df[projections_df['player_names'] == exposure_player]['position'].iloc[0].split('/')
|
| 422 |
if check_position_eligibility(sport_var, col, exposure_player_positions):
|
| 423 |
working_frame.at[row, col] = exposure_player
|
| 424 |
change_counter += 1
|
| 425 |
break
|
| 426 |
else:
|
| 427 |
-
# For non-Classic types, salary check is sufficient (position eligibility handled elsewhere)
|
| 428 |
working_frame.at[row, col] = exposure_player
|
| 429 |
change_counter += 1
|
| 430 |
break
|
|
|
|
| 431 |
return working_frame
|
| 432 |
|
|
|
|
| 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_exclusions, specific_columns, projections_df, sport_var, type_var, salary_max, stacking_sports, opp_map=None):
|
| 177 |
+
"""
|
| 178 |
+
Added parameter:
|
| 179 |
+
opp_map: Dictionary mapping player names to their opponents (optional)
|
| 180 |
+
"""
|
| 181 |
+
|
| 182 |
if specific_exclusions != []:
|
| 183 |
projections_df = projections_df[~projections_df['player_names'].isin(specific_exclusions)]
|
| 184 |
comparable_players = projections_df[projections_df['player_names'] == exposure_player]
|
|
|
|
| 196 |
comp_projection_low = comparable_players['median'][0] - (comparable_players['median'][0] * .5)
|
| 197 |
else:
|
| 198 |
comp_projection_low = comparable_players['median'][0] - (comparable_players['median'][0] * .5)
|
| 199 |
+
|
|
|
|
| 200 |
comp_player_position = comparable_players['position'].tolist()
|
| 201 |
comp_team = comparable_players['team'].tolist()
|
| 202 |
try:
|
|
|
|
| 210 |
player_pos_list = player_positions.split('/')
|
| 211 |
return any(pos in target_positions for pos in player_pos_list)
|
| 212 |
|
| 213 |
+
def check_opponent_conflict(candidate_player, current_row_data, opp_map):
|
| 214 |
+
"""Check if candidate player is an opponent of any player in the current row"""
|
| 215 |
+
if not opp_map:
|
| 216 |
+
return False # No conflicts if opp_map doesn't exist
|
| 217 |
+
|
| 218 |
+
# Get all players currently in the row
|
| 219 |
+
existing_players = set(current_row_data.values)
|
| 220 |
+
|
| 221 |
+
# Check if candidate is opponent of any existing player
|
| 222 |
+
for existing_player in existing_players:
|
| 223 |
+
if opp_map.get(existing_player) == candidate_player:
|
| 224 |
+
return True # Conflict found
|
| 225 |
+
if opp_map.get(candidate_player) == existing_player:
|
| 226 |
+
return True # Conflict found
|
| 227 |
+
|
| 228 |
+
return False # No conflicts
|
| 229 |
+
|
| 230 |
# find the exposure rate of the player in the working frame
|
| 231 |
if specific_columns != []:
|
| 232 |
player_mask = working_frame[specific_columns].apply(
|
|
|
|
| 270 |
random.shuffle(random_row_indices_insert)
|
| 271 |
random.shuffle(random_row_indices_replace)
|
| 272 |
|
| 273 |
+
# REMOVING EXPOSURE_PLAYER (lineups_to_remove > 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
if lineups_to_remove > 0:
|
|
|
|
| 275 |
while change_counter < math.ceil(lineups_to_remove) and random_row_indices_insert:
|
|
|
|
| 276 |
row = random_row_indices_insert.pop(0)
|
| 277 |
|
| 278 |
if specific_replacements != []:
|
|
|
|
| 311 |
comparable_player_list = []
|
| 312 |
|
| 313 |
if comparable_player_list:
|
|
|
|
| 314 |
if specific_columns != []:
|
| 315 |
row_data = working_frame.iloc[row][specific_columns]
|
| 316 |
working_columns = specific_columns
|
|
|
|
| 318 |
row_data = working_frame.iloc[row]
|
| 319 |
working_columns = working_frame.columns
|
| 320 |
|
|
|
|
| 321 |
replacement_made = False
|
| 322 |
|
|
|
|
| 323 |
if exposure_target == 0:
|
| 324 |
for col in working_columns:
|
| 325 |
if row_data[col] == exposure_player:
|
|
|
|
| 326 |
suitable_replacements = []
|
| 327 |
for candidate in comparable_player_list:
|
| 328 |
+
# NEW: Check for opponent conflicts
|
| 329 |
+
if check_opponent_conflict(candidate, current_row_data, opp_map):
|
| 330 |
+
continue # Skip this candidate
|
| 331 |
+
|
| 332 |
replacement_player_positions = projections_df[projections_df['player_names'] == candidate]['position'].iloc[0].split('/')
|
| 333 |
|
|
|
|
| 334 |
if type_var == 'Classic':
|
| 335 |
if check_position_eligibility(sport_var, col, replacement_player_positions):
|
| 336 |
suitable_replacements.append(candidate)
|
| 337 |
else:
|
|
|
|
| 338 |
current_lineup_salary = working_frame.iloc[row]['salary']
|
| 339 |
if check_salary_eligibility(current_lineup_salary, exposure_player, candidate, col, projections_df, type_var, salary_max):
|
| 340 |
suitable_replacements.append(candidate)
|
|
|
|
| 343 |
insert_player = random.choice(suitable_replacements)
|
| 344 |
working_frame.at[row, col] = insert_player
|
| 345 |
replacement_made = True
|
|
|
|
| 346 |
comparable_player_list = [p for p in comparable_player_list if p != insert_player]
|
| 347 |
else:
|
| 348 |
for col in working_columns:
|
| 349 |
if row_data[col] == exposure_player:
|
| 350 |
+
# Filter candidates to exclude opponent conflicts
|
| 351 |
+
valid_candidates = [
|
| 352 |
+
candidate for candidate in comparable_player_list
|
| 353 |
+
if not check_opponent_conflict(candidate, current_row_data, opp_map)
|
| 354 |
+
]
|
| 355 |
+
|
| 356 |
+
if not valid_candidates:
|
| 357 |
+
break # No valid replacements available
|
| 358 |
+
|
| 359 |
+
insert_player = random.choice(valid_candidates)
|
| 360 |
replacement_player_positions = projections_df[projections_df['player_names'] == insert_player]['position'].iloc[0].split('/')
|
| 361 |
|
|
|
|
| 362 |
if type_var == 'Classic':
|
| 363 |
if check_position_eligibility(sport_var, col, replacement_player_positions):
|
| 364 |
working_frame.at[row, col] = insert_player
|
| 365 |
replacement_made = True
|
| 366 |
break
|
| 367 |
else:
|
|
|
|
| 368 |
current_lineup_salary = working_frame.iloc[row]['salary']
|
| 369 |
if check_salary_eligibility(current_lineup_salary, exposure_player, insert_player, col, projections_df, type_var, salary_max):
|
| 370 |
working_frame.at[row, col] = insert_player
|
| 371 |
replacement_made = True
|
| 372 |
break
|
| 373 |
|
|
|
|
| 374 |
if replacement_made:
|
| 375 |
change_counter += 1
|
| 376 |
|
|
|
|
| 377 |
if not random_row_indices_insert:
|
| 378 |
break
|
| 379 |
+
|
| 380 |
+
# ADDING EXPOSURE_PLAYER (lineups_to_remove < 0)
|
| 381 |
else:
|
| 382 |
while change_counter < math.ceil(lineups_to_add) and random_row_indices_replace:
|
| 383 |
row = random_row_indices_replace.pop(0)
|
| 384 |
+
|
| 385 |
+
# Get current row data first to check for opponent conflicts
|
| 386 |
+
if specific_columns != []:
|
| 387 |
+
current_row_data = working_frame.iloc[row][specific_columns]
|
| 388 |
+
else:
|
| 389 |
+
current_row_data = working_frame.iloc[row]
|
| 390 |
+
|
| 391 |
+
# NEW: Check if exposure_player conflicts with existing players
|
| 392 |
+
if check_opponent_conflict(exposure_player, current_row_data, opp_map):
|
| 393 |
+
continue # Skip this row, can't add exposure_player here
|
| 394 |
+
|
| 395 |
if specific_replacements != []:
|
| 396 |
comparable_players = projections_df[(projections_df['player_names'].isin(specific_replacements))
|
| 397 |
]
|
|
|
|
| 422 |
|
| 423 |
comparable_players = comparable_players[comparable_players['player_names'] != exposure_player]
|
| 424 |
|
|
|
|
| 425 |
comparable_player_list = comparable_players['player_names'].tolist()
|
| 426 |
|
| 427 |
if exposure_player in working_frame.iloc[row].values:
|
| 428 |
comparable_player_list = []
|
| 429 |
|
| 430 |
if comparable_player_list:
|
|
|
|
| 431 |
if specific_columns != []:
|
| 432 |
row_data = working_frame.iloc[row][specific_columns]
|
| 433 |
working_columns = specific_columns
|
|
|
|
| 440 |
current_lineup_salary = working_frame.iloc[row]['salary']
|
| 441 |
current_player = row_data[col]
|
| 442 |
|
|
|
|
| 443 |
if check_salary_eligibility(current_lineup_salary, current_player, exposure_player, col, projections_df, type_var, salary_max):
|
| 444 |
if type_var == 'Classic':
|
|
|
|
| 445 |
exposure_player_positions = projections_df[projections_df['player_names'] == exposure_player]['position'].iloc[0].split('/')
|
| 446 |
if check_position_eligibility(sport_var, col, exposure_player_positions):
|
| 447 |
working_frame.at[row, col] = exposure_player
|
| 448 |
change_counter += 1
|
| 449 |
break
|
| 450 |
else:
|
|
|
|
| 451 |
working_frame.at[row, col] = exposure_player
|
| 452 |
change_counter += 1
|
| 453 |
break
|
| 454 |
+
|
| 455 |
return working_frame
|
| 456 |
|
global_func/optimize_lineup.py
CHANGED
|
@@ -146,12 +146,49 @@ def optimize_single_lineup(
|
|
| 146 |
for i in range(num_players):
|
| 147 |
solver.Add(sum(x[i, j] for j in range(num_open_cols)) <= 1)
|
| 148 |
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
|
| 156 |
# Constraint 4: Position eligibility
|
| 157 |
for i, player in enumerate(player_list):
|
|
|
|
| 146 |
for i in range(num_players):
|
| 147 |
solver.Add(sum(x[i, j] for j in range(num_open_cols)) <= 1)
|
| 148 |
|
| 149 |
+
# Constraint 3 & Opponent Exclusion: Handle locked players and opponents
|
| 150 |
+
opp_map = map_dict.get('opp_map', {})
|
| 151 |
+
|
| 152 |
+
if opp_map:
|
| 153 |
+
# When opp_map exists, use opponent-based constraints
|
| 154 |
+
|
| 155 |
+
# Build a mapping of locked opponents that cannot be selected
|
| 156 |
+
locked_opponents = set()
|
| 157 |
+
for locked_player in locked_player_names:
|
| 158 |
+
opponent = opp_map.get(locked_player)
|
| 159 |
+
if opponent:
|
| 160 |
+
locked_opponents.add(opponent)
|
| 161 |
+
|
| 162 |
+
# Prevent locked opponents from being selected
|
| 163 |
+
for i, player in enumerate(player_list):
|
| 164 |
+
player_name = player['player_names']
|
| 165 |
+
if player_name in locked_opponents:
|
| 166 |
+
for j in range(num_open_cols):
|
| 167 |
+
solver.Add(x[i, j] == 0)
|
| 168 |
+
|
| 169 |
+
# Prevent opponents from being selected together in open positions
|
| 170 |
+
player_name_to_idx = {player['player_names']: i for i, player in enumerate(player_list)}
|
| 171 |
+
|
| 172 |
+
for i, player in enumerate(player_list):
|
| 173 |
+
player_name = player['player_names']
|
| 174 |
+
opponent_name = opp_map.get(player_name)
|
| 175 |
+
|
| 176 |
+
if opponent_name and opponent_name in player_name_to_idx:
|
| 177 |
+
opponent_idx = player_name_to_idx[opponent_name]
|
| 178 |
+
|
| 179 |
+
# If player i is selected, opponent cannot be selected
|
| 180 |
+
solver.Add(
|
| 181 |
+
sum(x[i, j] for j in range(num_open_cols)) +
|
| 182 |
+
sum(x[opponent_idx, j] for j in range(num_open_cols)) <= 1
|
| 183 |
+
)
|
| 184 |
+
else:
|
| 185 |
+
# When opp_map doesn't exist, use standard locked player constraint
|
| 186 |
+
# Constraint 3: Players already LOCKED in the row cannot be selected again
|
| 187 |
+
for i, player in enumerate(player_list):
|
| 188 |
+
player_name = player['player_names']
|
| 189 |
+
if player_name in locked_player_names:
|
| 190 |
+
for j in range(num_open_cols):
|
| 191 |
+
solver.Add(x[i, j] == 0)
|
| 192 |
|
| 193 |
# Constraint 4: Position eligibility
|
| 194 |
for i, player in enumerate(player_list):
|