amendolajine's picture
Update app.py
a633476 verified
import gradio as gr
import pandas as pd
import itertools
import logging
# Set up logging for debugging.
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s")
# Global DataFrame variable.
df = None
def load_excel(file):
"""
Reads the Excel file and normalizes header names.
Expected headers (row 1): "Player", "Role 1", "Role 2", "Role 3", "xB90".
Returns six Dropdown components: three for selected roles and three for preferred proposal roles,
populated with the union of roles from all three columns (with a blank option prepended).
"""
global df
logging.debug("load_excel: Received file %s", file.name)
try:
df = pd.read_excel(file.name, header=0)
logging.debug("Excel file read successfully")
except Exception as e:
logging.error("Error reading Excel file: %s", e)
empty = gr.Dropdown(choices=[], interactive=True)
return empty, empty, empty, empty, empty, empty
# Normalize headers.
df.columns = [col.strip().lower().replace(" ", "") for col in df.columns]
required_cols = ["player", "role1", "role2", "role3", "xb90"]
for col in required_cols:
if col not in df.columns:
logging.error("Missing required column: %s", col)
empty = gr.Dropdown(choices=[], interactive=True)
return empty, empty, empty, empty, empty, empty
# Clean up data.
df["player"] = df["player"].astype(str).str.strip()
df["role1"] = df["role1"].astype(str).str.strip()
df["role2"] = df["role2"].astype(str).str.strip()
df["role3"] = df["role3"].astype(str).str.strip()
df["xb90"] = pd.to_numeric(df["xb90"], errors="coerce")
# Get union of roles from all three columns.
roles = pd.concat([df["role1"], df["role2"], df["role3"]]).dropna().unique().tolist()
roles = sorted(roles)
roles = [""] + roles # Prepend blank.
logging.debug("Roles found: %s", roles)
return (
gr.Dropdown(choices=roles, interactive=True),
gr.Dropdown(choices=roles, interactive=True),
gr.Dropdown(choices=roles, interactive=True),
gr.Dropdown(choices=roles, interactive=True),
gr.Dropdown(choices=roles, interactive=True),
gr.Dropdown(choices=roles, interactive=True)
)
def update_players(selected_role):
"""
Given a selected role, returns a new Dropdown with players whose name appears
in any of the role columns.
A blank option is added.
"""
global df
logging.debug("update_players: Selected role: %s", selected_role)
if df is None or not selected_role:
return gr.Dropdown(choices=[], interactive=True)
players = df[
(df["role1"] == selected_role) |
(df["role2"] == selected_role) |
(df["role3"] == selected_role)
]["player"].dropna().unique().tolist()
players = sorted(players)
players = [""] + players
logging.debug("Players for role %s: %s", selected_role, players)
return gr.Dropdown(choices=players, interactive=True)
def sync_pref(selected_role):
"""
If the selected role is "Por", returns "Por" for the corresponding preferred proposal role.
Otherwise, returns an empty string.
"""
if selected_role == "Por":
return "Por"
return ""
def get_xb90_value(role, player):
"""
Returns the xB90 value (as a string) for the given player where the selected role appears in any role column.
"""
global df
logging.debug("get_xb90_value: role=%s, player=%s", role, player)
if df is None or not role or not player:
return ""
row = df[(df["player"] == player) & (((df["role1"] == role) | (df["role2"] == role) | (df["role3"] == role)))]
if not row.empty:
value = str(row.iloc[0]["xb90"])
logging.debug("xB90 for %s: %s", player, value)
return value
return ""
def get_candidate_roles(candidate_name):
"""
Returns a comma-separated string of roles for the candidate.
Omits any role that is 'nan' (case-insensitive).
"""
global df
if df is None or not candidate_name:
return ""
row = df[df["player"] == candidate_name]
if not row.empty:
roles = set()
for col in ["role1", "role2", "role3"]:
val = row.iloc[0][col]
val_str = str(val).strip()
if pd.notna(val) and val_str and val_str.lower() != "nan":
roles.add(val_str)
return ", ".join(sorted(roles))
return ""
def compute_exchange(role1, player1, pref1, role2, player2, pref2, role3, player3, pref3, multiplier):
"""
Computes exchange proposals:
- Actual value: sum of selected players’ xB90.
- Target: actual * multiplier.
- For each selected player, gathers candidate replacements from rows where:
(a) the candidate has the selected role (in any column), and
(b) if a preferred proposal role is provided, candidate must have it.
If no candidate meets the preferred criterion, the preference constraint is relaxed.
- Excludes any candidate that is among the selected players.
- Generates candidate combinations and selects up to three proposals ensuring no candidate
is repeated across proposals.
- Enforces that if a player's role is "Por", then the preferred must be "Por"; if not, returns an error.
"""
global df
logging.debug("compute_exchange: Starting computation")
selected = []
for i, (r, p, pref) in enumerate([(role1, player1, pref1), (role2, player2, pref2), (role3, player3, pref3)], start=1):
if r and p:
if r == "Por":
if pref != "Por":
msg = f"Puoi scambiare un portiere solo per un altro portiere. (Giocatore {i})"
logging.error(msg)
return msg
pref = "Por"
xb90_val = get_xb90_value(r, p)
if xb90_val == "":
msg = f"Error: Could not find xB90 for Player {i}."
logging.error(msg)
return msg
try:
xb90_val = float(xb90_val)
except:
msg = f"Error: Invalid xB90 value for Player {i}."
logging.error(msg)
return msg
selected.append({"role": r, "player": p, "xb90": xb90_val, "pref": pref})
logging.debug("Selected Player %d: %s, Role: %s, Preferred: %s, xB90: %s", i, p, r, pref, xb90_val)
if len(selected) == 0:
return "Please select at least one player."
actual_value = sum(item["xb90"] for item in selected)
target = actual_value * multiplier
logging.debug("Actual value: %s, Target: %s (multiplier=%s)", actual_value, target, multiplier)
selected_names = {sel["player"] for sel in selected}
candidate_lists = []
for sel in selected:
r = sel["role"]
pref = sel["pref"]
logging.debug("Gathering candidates for role %s with preference %s", r, pref)
if pref:
candidates_df = df[
(((df["role1"] == r) | (df["role2"] == r) | (df["role3"] == r))) &
(((df["role1"] == pref) | (df["role2"] == pref) | (df["role3"] == pref))) &
(~df["player"].isin(selected_names))
]
candidates = candidates_df[["player", "xb90"]].dropna().to_dict(orient="records")
if not candidates:
logging.debug("No candidates found matching preference %s; relaxing preference", pref)
candidates_df = df[
(((df["role1"] == r) | (df["role2"] == r) | (df["role3"] == r))) &
(~df["player"].isin(selected_names))
]
candidates = candidates_df[["player", "xb90"]].dropna().to_dict(orient="records")
else:
candidates_df = df[
(((df["role1"] == r) | (df["role2"] == r) | (df["role3"] == r))) &
(~df["player"].isin(selected_names))
]
candidates = candidates_df[["player", "xb90"]].dropna().to_dict(orient="records")
logging.debug("Candidates for role %s: %s", r, candidates)
if not candidates:
return f"No alternative candidates found for role '{r}' (excluding selected players) matching preference '{pref}'."
candidate_lists.append(candidates)
all_combos = list(itertools.product(*candidate_lists))
valid_combos = []
for combo in all_combos:
candidate_names = [candidate["player"] for candidate in combo]
if len(set(candidate_names)) == len(candidate_names):
valid_combos.append(combo)
logging.debug("Found %d valid candidate combinations", len(valid_combos))
if not valid_combos:
return "No valid candidate combinations found."
combo_diffs = []
for combo in valid_combos:
combo_value = sum(candidate["xb90"] for candidate in combo)
diff = abs(combo_value - target)
combo_diffs.append((combo, combo_value, diff))
combo_diffs.sort(key=lambda x: x[2])
# Choose up to three proposals ensuring no candidate is repeated across proposals.
selected_options = []
used_candidates = set()
for combo, combo_value, diff in combo_diffs:
candidate_set = set(candidate["player"] for candidate in combo)
if candidate_set.isdisjoint(used_candidates):
selected_options.append((combo, combo_value, diff, candidate_set))
used_candidates.update(candidate_set)
if len(selected_options) == 3:
break
logging.debug("Selected %d proposals", len(selected_options))
if not selected_options:
return "No valid, disjoint candidate combinations found."
output = (
f"Actual value: {actual_value:.2f}\n"
f"Value for equal exchange (target): {target:.2f}\n\n"
"Top alternative exchange options:\n"
)
for idx, (combo, combo_value, diff, _) in enumerate(selected_options, start=1):
option_str = f"Option {idx} (Total xB90: {combo_value:.2f}, Diff: {diff:.2f}):\n"
for i, candidate in enumerate(combo):
cand_roles = get_candidate_roles(candidate["player"])
option_str += (
f" - Replacement for '{selected[i]['player']}' (Role: {selected[i]['role']}): "
f"{candidate['player']} (Role: {cand_roles}, xB90: {candidate['xb90']})\n"
)
output += option_str + "\n"
logging.debug("Exchange proposals computed successfully.")
return output
# Build the Gradio Blocks interface with advanced custom CSS.
with gr.Blocks(theme=gr.themes.Soft(), css="""
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap');
body, .gradio-container {
font-family: 'Montserrat', sans-serif;
}
.gradio-container {
background: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url('https://source.unsplash.com/1600x900/?football,stadium') no-repeat center center fixed;
background-size: cover;
}
.gradio-interface {
background-color: rgba(255, 255, 255, 0.95) !important;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}
h2, h3, h4, p {
color: #FFD700;
text-shadow: 1px 1px 4px rgba(0,0,0,0.8);
}
.gr-button {
background-color: #006400 !important;
color: #FFF !important;
border: none !important;
border-radius: 5px !important;
padding: 10px 20px !important;
font-size: 1.1em !important;
cursor: pointer !important;
transition: background-color 0.3s ease !important;
}
.gr-button:hover {
background-color: #228B22 !important;
}
@media (max-width: 600px) {
.gradio-container { padding: 10px; }
.gradio-interface { padding: 10px; }
}
""") as demo:
gr.Markdown("<h2 style='text-align:center;'>Fantastico Calcio Andria - il foglietto dell'uomo algoritmo</h2>")
gr.Markdown("<p style='text-align:center;'>Carica il file Excel con i dati dei giocatori. La prima riga deve contenere le intestazioni: 'Player', 'Role 1', 'Role 2', 'Role 3', 'xB90'.</p>")
file_input = gr.File(label="Carica file Excel (.xlsx)", file_types=[".xlsx"])
with gr.Row():
with gr.Column():
role1 = gr.Dropdown(label="Ruolo per Giocatore 1", choices=[], interactive=True)
player1 = gr.Dropdown(label="Giocatore 1", choices=[], interactive=True)
xb90_1 = gr.Textbox(label="xB90 per Giocatore 1", interactive=False)
pref1 = gr.Dropdown(label="Ruolo Preferito per Proposta (1)", choices=[], interactive=True)
with gr.Column():
role2 = gr.Dropdown(label="Ruolo per Giocatore 2 (Opzionale)", choices=[], interactive=True)
player2 = gr.Dropdown(label="Giocatore 2 (Opzionale)", choices=[], interactive=True)
xb90_2 = gr.Textbox(label="xB90 per Giocatore 2", interactive=False)
pref2 = gr.Dropdown(label="Ruolo Preferito per Proposta (2)", choices=[], interactive=True)
with gr.Column():
role3 = gr.Dropdown(label="Ruolo per Giocatore 3 (Opzionale)", choices=[], interactive=True)
player3 = gr.Dropdown(label="Giocatore 3 (Opzionale)", choices=[], interactive=True)
xb90_3 = gr.Textbox(label="xB90 per Giocatore 3", interactive=False)
pref3 = gr.Dropdown(label="Ruolo Preferito per Proposta (3)", choices=[], interactive=True)
multiplier_slider = gr.Slider(minimum=0.5, maximum=1.5, value=1.05, step=0.01, label="Moltiplicatore di Scambio", interactive=True)
file_input.change(fn=load_excel, inputs=file_input, outputs=[role1, role2, role3, pref1, pref2, pref3])
role1.change(fn=update_players, inputs=role1, outputs=player1)
role2.change(fn=update_players, inputs=role2, outputs=player2)
role3.change(fn=update_players, inputs=role3, outputs=player3)
# Sync preferred role if "Por" is selected.
role1.change(fn=sync_pref, inputs=role1, outputs=pref1)
role2.change(fn=sync_pref, inputs=role2, outputs=pref2)
role3.change(fn=sync_pref, inputs=role3, outputs=pref3)
player1.change(fn=get_xb90_value, inputs=[role1, player1], outputs=xb90_1)
player2.change(fn=get_xb90_value, inputs=[role2, player2], outputs=xb90_2)
player3.change(fn=get_xb90_value, inputs=[role3, player3], outputs=xb90_3)
exchange_button = gr.Button("Calcola Proposte di Scambio")
output_box = gr.Textbox(label="Proposte di Scambio", lines=14)
exchange_button.click(
fn=compute_exchange,
inputs=[role1, player1, pref1, role2, player2, pref2, role3, player3, pref3, multiplier_slider],
outputs=output_box,
)
# Launch the app with debug enabled.
demo.launch(debug=True)