Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -13,9 +13,8 @@ def load_excel(file):
|
|
| 13 |
"""
|
| 14 |
Reads the Excel file and normalizes header names.
|
| 15 |
Expected headers (row 1): "Player", "Role 1", "Role 2", "Role 3", "xB90".
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
Returns new Dropdown components for the three role selectors and the preferred role.
|
| 19 |
"""
|
| 20 |
global df
|
| 21 |
logging.debug("load_excel: Received file %s", file.name)
|
|
@@ -25,7 +24,7 @@ def load_excel(file):
|
|
| 25 |
except Exception as e:
|
| 26 |
logging.error("Error reading Excel file: %s", e)
|
| 27 |
empty = gr.Dropdown(choices=[], interactive=True)
|
| 28 |
-
return empty, empty, empty, empty
|
| 29 |
# Normalize headers.
|
| 30 |
df.columns = [col.strip().lower().replace(" ", "") for col in df.columns]
|
| 31 |
required_cols = ["player", "role1", "role2", "role3", "xb90"]
|
|
@@ -33,29 +32,32 @@ def load_excel(file):
|
|
| 33 |
if col not in df.columns:
|
| 34 |
logging.error("Missing required column: %s", col)
|
| 35 |
empty = gr.Dropdown(choices=[], interactive=True)
|
| 36 |
-
return empty, empty, empty, empty
|
| 37 |
-
# Clean data.
|
| 38 |
df["player"] = df["player"].astype(str).str.strip()
|
| 39 |
df["role1"] = df["role1"].astype(str).str.strip()
|
| 40 |
df["role2"] = df["role2"].astype(str).str.strip()
|
| 41 |
df["role3"] = df["role3"].astype(str).str.strip()
|
| 42 |
df["xb90"] = pd.to_numeric(df["xb90"], errors="coerce")
|
| 43 |
-
# Get union of roles.
|
| 44 |
roles = pd.concat([df["role1"], df["role2"], df["role3"]]).dropna().unique().tolist()
|
| 45 |
roles = sorted(roles)
|
| 46 |
-
roles = [""] + roles #
|
| 47 |
logging.debug("Roles found: %s", roles)
|
|
|
|
| 48 |
return (
|
| 49 |
-
gr.Dropdown(choices=roles, interactive=True),
|
| 50 |
-
gr.Dropdown(choices=roles, interactive=True),
|
| 51 |
-
gr.Dropdown(choices=roles, interactive=True),
|
| 52 |
-
gr.Dropdown(choices=roles, interactive=True)
|
|
|
|
|
|
|
| 53 |
)
|
| 54 |
|
| 55 |
def update_players(selected_role):
|
| 56 |
"""
|
| 57 |
-
Given a selected role, returns a new Dropdown with players whose name appears
|
| 58 |
-
in any role
|
| 59 |
A blank option is added.
|
| 60 |
"""
|
| 61 |
global df
|
|
@@ -63,19 +65,19 @@ def update_players(selected_role):
|
|
| 63 |
if df is None or not selected_role:
|
| 64 |
return gr.Dropdown(choices=[], interactive=True)
|
| 65 |
players = df[
|
| 66 |
-
(df["role1"] == selected_role) |
|
| 67 |
-
(df["role2"] == selected_role) |
|
| 68 |
(df["role3"] == selected_role)
|
| 69 |
]["player"].dropna().unique().tolist()
|
| 70 |
players = sorted(players)
|
| 71 |
-
players = [""] + players
|
| 72 |
logging.debug("Players for role %s: %s", selected_role, players)
|
| 73 |
return gr.Dropdown(choices=players, interactive=True)
|
| 74 |
|
| 75 |
def get_xb90_value(role, player):
|
| 76 |
"""
|
| 77 |
-
Returns the xB90 value
|
| 78 |
-
|
| 79 |
"""
|
| 80 |
global df
|
| 81 |
logging.debug("get_xb90_value: role=%s, player=%s", role, player)
|
|
@@ -88,21 +90,24 @@ def get_xb90_value(role, player):
|
|
| 88 |
return value
|
| 89 |
return ""
|
| 90 |
|
| 91 |
-
def compute_exchange(role1, player1, role2, player2, role3, player3,
|
| 92 |
"""
|
| 93 |
-
|
| 94 |
- Actual value: sum of selected players’ xB90.
|
| 95 |
- Target: actual * 1.05.
|
| 96 |
-
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
| 101 |
"""
|
| 102 |
global df
|
| 103 |
logging.debug("compute_exchange: Starting computation")
|
| 104 |
selected = []
|
| 105 |
-
|
|
|
|
| 106 |
if r and p:
|
| 107 |
xb90_val = get_xb90_value(r, p)
|
| 108 |
if xb90_val == "":
|
|
@@ -115,8 +120,8 @@ def compute_exchange(role1, player1, role2, player2, role3, player3, pref_role):
|
|
| 115 |
msg = f"Error: Invalid xB90 value for Player {i}."
|
| 116 |
logging.error(msg)
|
| 117 |
return msg
|
| 118 |
-
selected.append({"role": r, "player": p, "xb90": xb90_val})
|
| 119 |
-
logging.debug("Selected Player %d: %s, xB90: %s", i, p, xb90_val)
|
| 120 |
if len(selected) == 0:
|
| 121 |
return "Please select at least one player."
|
| 122 |
|
|
@@ -128,16 +133,33 @@ def compute_exchange(role1, player1, role2, player2, role3, player3, pref_role):
|
|
| 128 |
candidate_lists = []
|
| 129 |
for sel in selected:
|
| 130 |
r = sel["role"]
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
if not candidates:
|
| 140 |
-
return f"No alternative candidates found for role '{r}' (excluding selected players)
|
| 141 |
candidate_lists.append(candidates)
|
| 142 |
|
| 143 |
all_combos = list(itertools.product(*candidate_lists))
|
|
@@ -158,7 +180,7 @@ def compute_exchange(role1, player1, role2, player2, role3, player3, pref_role):
|
|
| 158 |
combo_diffs.append((combo, combo_value, diff))
|
| 159 |
combo_diffs.sort(key=lambda x: x[2])
|
| 160 |
|
| 161 |
-
# Choose up to three proposals ensuring no candidate
|
| 162 |
selected_options = []
|
| 163 |
used_candidates = set()
|
| 164 |
for combo, combo_value, diff in combo_diffs:
|
|
@@ -189,7 +211,7 @@ def compute_exchange(role1, player1, role2, player2, role3, player3, pref_role):
|
|
| 189 |
logging.debug("Exchange proposals computed successfully.")
|
| 190 |
return output
|
| 191 |
|
| 192 |
-
# Build the Gradio Blocks interface with
|
| 193 |
with gr.Blocks(theme=gr.themes.Soft(), css="""
|
| 194 |
.gradio-container { max-width: 800px; margin: auto; }
|
| 195 |
.gradio-interface { padding: 20px; }
|
|
@@ -208,21 +230,27 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 208 |
role1 = gr.Dropdown(label="Role for Player 1", choices=[], interactive=True)
|
| 209 |
player1 = gr.Dropdown(label="Player 1", choices=[], interactive=True)
|
| 210 |
xb90_1 = gr.Textbox(label="xB90 for Player 1", interactive=False)
|
|
|
|
| 211 |
with gr.Column():
|
| 212 |
role2 = gr.Dropdown(label="Role for Player 2 (Optional)", choices=[], interactive=True)
|
| 213 |
player2 = gr.Dropdown(label="Player 2 (Optional)", choices=[], interactive=True)
|
| 214 |
xb90_2 = gr.Textbox(label="xB90 for Player 2", interactive=False)
|
|
|
|
| 215 |
with gr.Column():
|
| 216 |
role3 = gr.Dropdown(label="Role for Player 3 (Optional)", choices=[], interactive=True)
|
| 217 |
player3 = gr.Dropdown(label="Player 3 (Optional)", choices=[], interactive=True)
|
| 218 |
xb90_3 = gr.Textbox(label="xB90 for Player 3", interactive=False)
|
|
|
|
| 219 |
|
| 220 |
-
|
|
|
|
| 221 |
|
| 222 |
-
|
| 223 |
role1.change(fn=update_players, inputs=role1, outputs=player1)
|
| 224 |
role2.change(fn=update_players, inputs=role2, outputs=player2)
|
| 225 |
role3.change(fn=update_players, inputs=role3, outputs=player3)
|
|
|
|
|
|
|
| 226 |
player1.change(fn=get_xb90_value, inputs=[role1, player1], outputs=xb90_1)
|
| 227 |
player2.change(fn=get_xb90_value, inputs=[role2, player2], outputs=xb90_2)
|
| 228 |
player3.change(fn=get_xb90_value, inputs=[role3, player3], outputs=xb90_3)
|
|
@@ -232,9 +260,9 @@ with gr.Blocks(theme=gr.themes.Soft(), css="""
|
|
| 232 |
|
| 233 |
exchange_button.click(
|
| 234 |
fn=compute_exchange,
|
| 235 |
-
inputs=[role1, player1, role2, player2, role3, player3,
|
| 236 |
outputs=output_box,
|
| 237 |
)
|
| 238 |
|
| 239 |
-
# Launch the app with debug=True
|
| 240 |
demo.launch(debug=True)
|
|
|
|
| 13 |
"""
|
| 14 |
Reads the Excel file and normalizes header names.
|
| 15 |
Expected headers (row 1): "Player", "Role 1", "Role 2", "Role 3", "xB90".
|
| 16 |
+
Returns six Dropdown components (three for exchange roles and three for preferred proposal roles)
|
| 17 |
+
populated with the union of roles from the three role columns (blank option prepended).
|
|
|
|
| 18 |
"""
|
| 19 |
global df
|
| 20 |
logging.debug("load_excel: Received file %s", file.name)
|
|
|
|
| 24 |
except Exception as e:
|
| 25 |
logging.error("Error reading Excel file: %s", e)
|
| 26 |
empty = gr.Dropdown(choices=[], interactive=True)
|
| 27 |
+
return empty, empty, empty, empty, empty, empty
|
| 28 |
# Normalize headers.
|
| 29 |
df.columns = [col.strip().lower().replace(" ", "") for col in df.columns]
|
| 30 |
required_cols = ["player", "role1", "role2", "role3", "xb90"]
|
|
|
|
| 32 |
if col not in df.columns:
|
| 33 |
logging.error("Missing required column: %s", col)
|
| 34 |
empty = gr.Dropdown(choices=[], interactive=True)
|
| 35 |
+
return empty, empty, empty, empty, empty, empty
|
| 36 |
+
# Clean up data.
|
| 37 |
df["player"] = df["player"].astype(str).str.strip()
|
| 38 |
df["role1"] = df["role1"].astype(str).str.strip()
|
| 39 |
df["role2"] = df["role2"].astype(str).str.strip()
|
| 40 |
df["role3"] = df["role3"].astype(str).str.strip()
|
| 41 |
df["xb90"] = pd.to_numeric(df["xb90"], errors="coerce")
|
| 42 |
+
# Get union of roles from all three columns.
|
| 43 |
roles = pd.concat([df["role1"], df["role2"], df["role3"]]).dropna().unique().tolist()
|
| 44 |
roles = sorted(roles)
|
| 45 |
+
roles = [""] + roles # Blank option.
|
| 46 |
logging.debug("Roles found: %s", roles)
|
| 47 |
+
# Return six dropdown components.
|
| 48 |
return (
|
| 49 |
+
gr.Dropdown(choices=roles, interactive=True), # role1 for exchange
|
| 50 |
+
gr.Dropdown(choices=roles, interactive=True), # role2 for exchange
|
| 51 |
+
gr.Dropdown(choices=roles, interactive=True), # role3 for exchange
|
| 52 |
+
gr.Dropdown(choices=roles, interactive=True), # preferred role for player1
|
| 53 |
+
gr.Dropdown(choices=roles, interactive=True), # preferred role for player2
|
| 54 |
+
gr.Dropdown(choices=roles, interactive=True) # preferred role for player3
|
| 55 |
)
|
| 56 |
|
| 57 |
def update_players(selected_role):
|
| 58 |
"""
|
| 59 |
+
Given a selected role, returns a new Dropdown with players whose name appears
|
| 60 |
+
in any of the role columns.
|
| 61 |
A blank option is added.
|
| 62 |
"""
|
| 63 |
global df
|
|
|
|
| 65 |
if df is None or not selected_role:
|
| 66 |
return gr.Dropdown(choices=[], interactive=True)
|
| 67 |
players = df[
|
| 68 |
+
(df["role1"] == selected_role) |
|
| 69 |
+
(df["role2"] == selected_role) |
|
| 70 |
(df["role3"] == selected_role)
|
| 71 |
]["player"].dropna().unique().tolist()
|
| 72 |
players = sorted(players)
|
| 73 |
+
players = [""] + players
|
| 74 |
logging.debug("Players for role %s: %s", selected_role, players)
|
| 75 |
return gr.Dropdown(choices=players, interactive=True)
|
| 76 |
|
| 77 |
def get_xb90_value(role, player):
|
| 78 |
"""
|
| 79 |
+
Returns the xB90 value for the given player and selected role.
|
| 80 |
+
Searches rows where the player's name matches and the role appears in any role column.
|
| 81 |
"""
|
| 82 |
global df
|
| 83 |
logging.debug("get_xb90_value: role=%s, player=%s", role, player)
|
|
|
|
| 90 |
return value
|
| 91 |
return ""
|
| 92 |
|
| 93 |
+
def compute_exchange(role1, player1, pref1, role2, player2, pref2, role3, player3, pref3):
|
| 94 |
"""
|
| 95 |
+
Computes exchange proposals:
|
| 96 |
- Actual value: sum of selected players’ xB90.
|
| 97 |
- Target: actual * 1.05.
|
| 98 |
+
- For each selected player, gather candidate replacements from rows where:
|
| 99 |
+
(a) the candidate has the selected role (in any role column), and
|
| 100 |
+
(b) if a preferred role is provided for that player, then candidate must have it in some role column.
|
| 101 |
+
If no candidate meets the preferred criterion, the preference is relaxed.
|
| 102 |
+
- Excludes any candidate that is among the selected players.
|
| 103 |
+
- Generates candidate combinations (Cartesian product) and selects up to three proposals
|
| 104 |
+
ensuring that no candidate appears in more than one proposal.
|
| 105 |
"""
|
| 106 |
global df
|
| 107 |
logging.debug("compute_exchange: Starting computation")
|
| 108 |
selected = []
|
| 109 |
+
# Collect selected players.
|
| 110 |
+
for i, (r, p, pref) in enumerate([(role1, player1, pref1), (role2, player2, pref2), (role3, player3, pref3)], start=1):
|
| 111 |
if r and p:
|
| 112 |
xb90_val = get_xb90_value(r, p)
|
| 113 |
if xb90_val == "":
|
|
|
|
| 120 |
msg = f"Error: Invalid xB90 value for Player {i}."
|
| 121 |
logging.error(msg)
|
| 122 |
return msg
|
| 123 |
+
selected.append({"role": r, "player": p, "xb90": xb90_val, "pref": pref})
|
| 124 |
+
logging.debug("Selected Player %d: %s, Role: %s, Preferred: %s, xB90: %s", i, p, r, pref, xb90_val)
|
| 125 |
if len(selected) == 0:
|
| 126 |
return "Please select at least one player."
|
| 127 |
|
|
|
|
| 133 |
candidate_lists = []
|
| 134 |
for sel in selected:
|
| 135 |
r = sel["role"]
|
| 136 |
+
pref = sel["pref"]
|
| 137 |
+
logging.debug("Gathering candidates for selected role %s with preference %s", r, pref)
|
| 138 |
+
# First, try filtering by both role and preferred role if provided.
|
| 139 |
+
if pref:
|
| 140 |
+
candidates_df = df[
|
| 141 |
+
(((df["role1"] == r) | (df["role2"] == r) | (df["role3"] == r))) &
|
| 142 |
+
(((df["role1"] == pref) | (df["role2"] == pref) | (df["role3"] == pref))) &
|
| 143 |
+
(~df["player"].isin(selected_names))
|
| 144 |
+
]
|
| 145 |
+
candidates = candidates_df[["player", "xb90"]].dropna().to_dict(orient="records")
|
| 146 |
+
if not candidates:
|
| 147 |
+
logging.debug("No candidates found matching preference %s; relaxing preference", pref)
|
| 148 |
+
# Relax the preference constraint.
|
| 149 |
+
candidates_df = df[
|
| 150 |
+
(((df["role1"] == r) | (df["role2"] == r) | (df["role3"] == r))) &
|
| 151 |
+
(~df["player"].isin(selected_names))
|
| 152 |
+
]
|
| 153 |
+
candidates = candidates_df[["player", "xb90"]].dropna().to_dict(orient="records")
|
| 154 |
+
else:
|
| 155 |
+
candidates_df = df[
|
| 156 |
+
(((df["role1"] == r) | (df["role2"] == r) | (df["role3"] == r))) &
|
| 157 |
+
(~df["player"].isin(selected_names))
|
| 158 |
+
]
|
| 159 |
+
candidates = candidates_df[["player", "xb90"]].dropna().to_dict(orient="records")
|
| 160 |
+
logging.debug("Candidates for %s: %s", r, candidates)
|
| 161 |
if not candidates:
|
| 162 |
+
return f"No alternative candidates found for role '{r}' (excluding selected players)."
|
| 163 |
candidate_lists.append(candidates)
|
| 164 |
|
| 165 |
all_combos = list(itertools.product(*candidate_lists))
|
|
|
|
| 180 |
combo_diffs.append((combo, combo_value, diff))
|
| 181 |
combo_diffs.sort(key=lambda x: x[2])
|
| 182 |
|
| 183 |
+
# Choose up to three proposals ensuring no candidate is repeated across proposals.
|
| 184 |
selected_options = []
|
| 185 |
used_candidates = set()
|
| 186 |
for combo, combo_value, diff in combo_diffs:
|
|
|
|
| 211 |
logging.debug("Exchange proposals computed successfully.")
|
| 212 |
return output
|
| 213 |
|
| 214 |
+
# Build the Gradio Blocks interface with Soft theme and custom CSS for mobile.
|
| 215 |
with gr.Blocks(theme=gr.themes.Soft(), css="""
|
| 216 |
.gradio-container { max-width: 800px; margin: auto; }
|
| 217 |
.gradio-interface { padding: 20px; }
|
|
|
|
| 230 |
role1 = gr.Dropdown(label="Role for Player 1", choices=[], interactive=True)
|
| 231 |
player1 = gr.Dropdown(label="Player 1", choices=[], interactive=True)
|
| 232 |
xb90_1 = gr.Textbox(label="xB90 for Player 1", interactive=False)
|
| 233 |
+
pref1 = gr.Dropdown(label="Preferred Proposal Role for Player 1", choices=[], interactive=True)
|
| 234 |
with gr.Column():
|
| 235 |
role2 = gr.Dropdown(label="Role for Player 2 (Optional)", choices=[], interactive=True)
|
| 236 |
player2 = gr.Dropdown(label="Player 2 (Optional)", choices=[], interactive=True)
|
| 237 |
xb90_2 = gr.Textbox(label="xB90 for Player 2", interactive=False)
|
| 238 |
+
pref2 = gr.Dropdown(label="Preferred Proposal Role for Player 2", choices=[], interactive=True)
|
| 239 |
with gr.Column():
|
| 240 |
role3 = gr.Dropdown(label="Role for Player 3 (Optional)", choices=[], interactive=True)
|
| 241 |
player3 = gr.Dropdown(label="Player 3 (Optional)", choices=[], interactive=True)
|
| 242 |
xb90_3 = gr.Textbox(label="xB90 for Player 3", interactive=False)
|
| 243 |
+
pref3 = gr.Dropdown(label="Preferred Proposal Role for Player 3", choices=[], interactive=True)
|
| 244 |
|
| 245 |
+
# When file is uploaded, update all six dropdowns with the union of roles.
|
| 246 |
+
file_input.change(fn=load_excel, inputs=file_input, outputs=[role1, role2, role3, pref1, pref2, pref3])
|
| 247 |
|
| 248 |
+
# When a role is selected, update the corresponding player dropdown.
|
| 249 |
role1.change(fn=update_players, inputs=role1, outputs=player1)
|
| 250 |
role2.change(fn=update_players, inputs=role2, outputs=player2)
|
| 251 |
role3.change(fn=update_players, inputs=role3, outputs=player3)
|
| 252 |
+
|
| 253 |
+
# When a player is selected, display the xB90 value.
|
| 254 |
player1.change(fn=get_xb90_value, inputs=[role1, player1], outputs=xb90_1)
|
| 255 |
player2.change(fn=get_xb90_value, inputs=[role2, player2], outputs=xb90_2)
|
| 256 |
player3.change(fn=get_xb90_value, inputs=[role3, player3], outputs=xb90_3)
|
|
|
|
| 260 |
|
| 261 |
exchange_button.click(
|
| 262 |
fn=compute_exchange,
|
| 263 |
+
inputs=[role1, player1, pref1, role2, player2, pref2, role3, player3, pref3],
|
| 264 |
outputs=output_box,
|
| 265 |
)
|
| 266 |
|
| 267 |
+
# Launch the app with debug=True.
|
| 268 |
demo.launch(debug=True)
|