amendolajine commited on
Commit
2089a47
·
verified ·
1 Parent(s): d85766c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -44
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
- Roles are taken from the union of columns "role1", "role2", and "role3".
17
- A blank option is added.
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 # Prepend blank option.
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 column.
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 # allow blank
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 (as a string) for the given player and selected role.
78
- The search is performed on rows where the player's name matches and the role is present.
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, pref_role):
92
  """
93
- For each selected player (role/player pair), compute:
94
  - Actual value: sum of selected players’ xB90.
95
  - Target: actual * 1.05.
96
- - Candidate replacements: for each selection, candidates are chosen from rows
97
- where the selected role appears and the candidate has the preferred proposal role,
98
- excluding any player already selected.
99
- Generates candidate combinations and selects up to three proposals ensuring that
100
- no candidate appears in more than one proposal.
 
 
101
  """
102
  global df
103
  logging.debug("compute_exchange: Starting computation")
104
  selected = []
105
- for i, (r, p) in enumerate([(role1, player1), (role2, player2), (role3, player3)], start=1):
 
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
- logging.debug("Gathering candidates for role: %s", r)
132
- candidates_df = df[
133
- (((df["role1"] == r) | (df["role2"] == r) | (df["role3"] == r))) &
134
- (((df["role1"] == pref_role) | (df["role2"] == pref_role) | (df["role3"] == pref_role))) &
135
- (~df["player"].isin(selected_names))
136
- ]
137
- candidates = candidates_df[["player", "xb90"]].dropna().to_dict(orient="records")
138
- logging.debug("Candidates for role %s with preference %s: %s", r, pref_role, candidates)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  if not candidates:
140
- return f"No alternative candidates found for role '{r}' (excluding selected players) matching preference '{pref_role}'."
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 appears in more than one.
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 a soft theme and custom CSS for mobile.
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
- pref_role = gr.Dropdown(label="Preferred Proposal Role", choices=[], interactive=True)
 
221
 
222
- file_input.change(fn=load_excel, inputs=file_input, outputs=[role1, role2, role3, pref_role])
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, pref_role],
236
  outputs=output_box,
237
  )
238
 
239
- # Launch the app with debug=True (remove demo.render() to avoid DuplicateBlockError)
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)