Spaces:
Running
Running
File size: 9,676 Bytes
21b054e f7cecf3 21b054e f7cecf3 cbb573e f7cecf3 cbb573e f7cecf3 cbb573e f7cecf3 4c4af79 f7cecf3 6e30e73 f7cecf3 21b054e f7cecf3 c3e906f f7cecf3 21b054e f7cecf3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | import requests
from typing import Any
_FIXTURE_CACHE = []
def _norm_id_list(raw) -> list[int]:
if not raw:
return []
out = []
for x in raw:
# THE FIX: Safely extract the ID from React's dictionary payload
if isinstance(x, dict):
pid = x.get("ID") or x.get("id", 0)
out.append(int(pid))
else:
out.append(int(x))
return out
def _norm_gw_list(raw) -> list[int]:
"""Normalise a chip GW list to a list of ints, handling None gracefully."""
if not raw:
return []
return [int(g) for g in raw if g is not None]
def prep_solver_data(payload_data: dict):
"""
Translates the React JSON payload into the exact mathematical dictionaries
the MILP engine requires, including chips and advanced constraints.
"""
print("Prepping data for the MILP Engine...")
horizon_gws = [int(g) for g in payload_data["horizon_gws"]]
raw_squad = payload_data["current_squad_ids"]
current_squad_ids = [
int(pid)
for pid in raw_squad
if isinstance(pid, (int, float)) or str(pid).isdigit()
]
settings_payload: dict[str, Any] = dict(payload_data.get("settings") or {})
comp_payload = dict(payload_data.get("comprehensive_settings") or {})
# Start with an empty dictionary.
# Python no longer guesses defaults. It completely trusts the React payload.
settings = {}
# 1. Apply basic settings
settings.update({k: v for k, v in settings_payload.items() if v not in (None, "")})
# 2. Apply comprehensive settings (Filters out None AND empty strings from UI)
settings.update({k: v for k, v in comp_payload.items() if v not in (None, "")})
# --- BAN / LOCK ---
banned_ids = _norm_id_list(settings.get("banned") or settings.get("banned_ids"))
locked_ids = _norm_id_list(settings.get("locked") or settings.get("locked_ids"))
settings["banned_ids"] = banned_ids
settings["locked_ids"] = locked_ids
# --- SCALAR SETTINGS ---
settings["iterations"] = max(
1,
min(5, int(settings.get("iterations", settings.get("num_iterations", 1)))),
)
settings["iteration_diff"] = int(settings.get("iteration_diff", 1))
settings["iteration_criteria"] = settings.get(
"iteration_criteria", "this_gw_transfer_in_out"
)
settings["hit_cost"] = int(settings.get("hit_cost", 4))
settings["max_ft"] = int(settings.get("max_ft", 5))
settings["itb_value"] = float(settings.get("itb_value", 0.08))
settings["time_limit_sec"] = int(settings.get("time_limit_sec", 3000))
settings["vice_weight"] = float(settings.get("vice_weight", 0.05))
settings["max_per_team"] = int(settings.get("max_per_team", 3))
settings["no_transfer_last_gws"] = int(settings.get("no_transfer_last_gws", 0))
settings["itb_loss_per_transfer"] = float(settings.get("itb_loss_per_transfer", 0))
settings["ft_value"] = float(
settings.get("ft_value_base", settings.get("ft_value", 0)) or 0
)
settings["ft_use_penalty"] = float(settings.get("ft_use_penalty", 0) or 0)
settings["future_transfer_limit"] = settings.get("future_transfer_limit")
if settings["future_transfer_limit"] is not None:
settings["future_transfer_limit"] = int(settings["future_transfer_limit"])
settings["hit_limit"] = settings.get("hit_limit")
if settings["hit_limit"] is not None:
settings["hit_limit"] = int(settings["hit_limit"])
settings["weekly_hit_limit"] = settings.get("weekly_hit_limit")
if settings["weekly_hit_limit"] is not None:
settings["weekly_hit_limit"] = int(settings["weekly_hit_limit"])
settings["no_transfer_gws"] = _norm_gw_list(settings.get("no_transfer_gws"))
settings["no_transfer_by_position"] = settings.get("no_transfer_by_position") or []
settings["no_trs_except_wc"] = bool(settings.get("no_trs_except_wc", False))
settings["max_defenders_per_team"] = int(settings.get("max_defenders_per_team", 3))
settings["double_defense_pick"] = bool(settings.get("double_defense_pick", False))
settings["transfer_itb_buffer"] = settings.get("transfer_itb_buffer")
if settings["transfer_itb_buffer"] is not None:
settings["transfer_itb_buffer"] = float(settings["transfer_itb_buffer"])
settings["no_gk_rotation_after"] = settings.get("no_gk_rotation_after")
settings["no_opposing_play"] = settings.get("no_opposing_play", False)
settings["opposing_play_group"] = settings.get("opposing_play_group", "all")
settings["opposing_play_penalty"] = float(
settings.get("opposing_play_penalty", 0.5)
)
settings["force_ft_state_lb"] = settings.get("force_ft_state_lb") or []
settings["force_ft_state_ub"] = settings.get("force_ft_state_ub") or []
settings["no_chip_gws"] = _norm_gw_list(settings.get("no_chip_gws"))
# Parse gw-specific lock/bans
def norm_temporal_list(raw):
if not raw:
return []
out = []
for x in raw:
if isinstance(x, list) and len(x) == 2:
out.append((int(x[0]), int(x[1])))
elif isinstance(x, dict):
# THE FIX: Extract the ID and strictly bind it to the FIRST gameweek of the horizon!
pid = x.get("ID") or x.get("id", 0)
out.append((int(pid), horizon_gws[0]))
else:
out.append((int(x), horizon_gws[0]))
return out
settings["banned_next_gw"] = norm_temporal_list(
settings.get("ban_this_gw") or settings.get("banned_next_gw")
)
settings["locked_next_gw"] = norm_temporal_list(
settings.get("lock_this_gw") or settings.get("locked_next_gw")
)
settings["booked_transfers"] = settings.get("booked_transfers") or []
settings["only_booked_transfers"] = bool(
settings.get("only_booked_transfers", False)
)
settings["no_future_transfer"] = bool(settings.get("no_future_transfer", False))
settings["num_transfers"] = settings.get("num_transfers")
settings["pick_prices"] = settings.get("pick_prices", {})
# --- CHIP GW LISTS ---
settings["use_wc"] = _norm_gw_list(settings.get("use_wc"))
settings["use_fh"] = _norm_gw_list(settings.get("use_fh"))
settings["use_bb"] = _norm_gw_list(settings.get("use_bb"))
settings["use_tc"] = _norm_gw_list(settings.get("use_tc"))
# --- BENCH WEIGHTS (dict with string keys "0"-"3") ---
raw_bw = settings.get("bench_weights")
if raw_bw and isinstance(raw_bw, dict):
settings["bench_weights"] = {str(k): float(v) for k, v in raw_bw.items()}
else:
settings["bench_weights"] = {"0": 0.03, "1": 0.18, "2": 0.06, "3": 0.002}
# --- FT VALUE LIST (dict with string keys "2"-"5") ---
raw_ftvl = settings.get("ft_value_list")
if raw_ftvl and isinstance(raw_ftvl, dict):
settings["ft_value_list"] = {str(k): float(v) for k, v in raw_ftvl.items()}
else:
settings["ft_value_list"] = {}
# --- DECAY ---
decay = float(settings.get("decay_base", settings.get("decay", 1.0)) or 1.0)
# --- BUILD PLAYER DICTS ---
buy_prices: dict[int, float] = {}
sell_prices: dict[int, float] = {}
positions: dict[int, str] = {}
teams: dict[int, str] = {}
ev_matrix: dict[int, dict[int, float]] = {}
raw_ev_matrix: dict[int, dict[int, float]] = {}
for idx, gw in enumerate(horizon_gws):
decay_factor = decay**idx if decay != 1.0 else 1.0
for p in payload_data["market_players"]:
pid = int(p["id"])
if pid not in buy_prices:
positions[pid] = p["pos"]
teams[pid] = p["team"]
buy_prices[pid] = float(p["now_cost"])
sell_prices[pid] = float(p.get("sell_price", p["now_cost"]))
ev_matrix[pid] = {}
raw_ev_matrix[pid] = {}
raw_ev = p["evs"].get(gw, p["evs"].get(str(gw), 0))
ev_matrix[pid][gw] = float(raw_ev) * decay_factor
raw_ev_matrix[pid][gw] = float(raw_ev)
fh_sell_price = {
pid: sell_prices[pid] if pid in current_squad_ids else buy_prices[pid]
for pid in buy_prices
}
# --- CROSS-PLAY FIXTURE PREP ---
global _FIXTURE_CACHE
gw_opp_teams = {w: [] for w in horizon_gws}
if settings.get("no_opposing_play"):
if not _FIXTURE_CACHE:
try:
# Same fast cache logic as solver_the_real_one
_FIXTURE_CACHE = requests.get(
"https://fantasy.premierleague.com/api/fixtures/"
).json()
except: # noqa: E722
pass
# Map the opponent matchups for the solver
team_mapping = {v: k for k, v in teams.items()} # Reverse lookup
for f in _FIXTURE_CACHE:
w = f.get("event")
if w in horizon_gws:
home_team = team_mapping.get(f.get("team_h"))
away_team = team_mapping.get(f.get("team_a"))
if home_team and away_team:
gw_opp_teams[w].extend(
[(home_team, away_team), (away_team, home_team)]
)
return {
"players": list(buy_prices.keys()),
"gws": horizon_gws,
"buy_prices": buy_prices,
"sell_prices": sell_prices,
"fh_sell_price": fh_sell_price,
"positions": positions,
"teams": teams,
"ev_matrix": ev_matrix,
"current_squad": current_squad_ids,
"itb": float(payload_data["in_the_bank"]),
"ft": int(payload_data["free_transfers"]),
"settings": settings,
"gw_opp_teams": gw_opp_teams,
}
|