Update app.py
Browse files
app.py
CHANGED
|
@@ -105,14 +105,6 @@ def dataset_nations():
|
|
| 105 |
vals = [n for n in DF["Nation"].dropna().unique().tolist() if str(n).strip()]
|
| 106 |
return sorted(set(vals))
|
| 107 |
|
| 108 |
-
def all_countries_minus_excluded():
|
| 109 |
-
vals = []
|
| 110 |
-
for c in pycountry.countries:
|
| 111 |
-
nm = c.name
|
| 112 |
-
if re.sub(r"\s+"," ", nm).upper() not in EXCLUDED_NATIONS:
|
| 113 |
-
vals.append(nm)
|
| 114 |
-
return sorted(set(vals))
|
| 115 |
-
|
| 116 |
def clubs_list():
|
| 117 |
if "Club" not in DF.columns: return []
|
| 118 |
vals = [c for c in DF["Club"].dropna().unique().tolist() if str(c).strip()]
|
|
@@ -151,39 +143,31 @@ def to_csv_bytes(df):
|
|
| 151 |
return buf.getvalue().encode("utf-8")
|
| 152 |
|
| 153 |
# =======================
|
| 154 |
-
# UI (
|
| 155 |
# =======================
|
| 156 |
THEME = gr.themes.Soft(primary_hue="blue")
|
| 157 |
CSS = """
|
| 158 |
#title { text-align:center; }
|
| 159 |
-
.bubble { background:#fff; border-radius:16px; padding:12px; box-shadow:0 2px 10px rgba(0,0,0,.06); display:flex; flex-direction:column; justify-content:space-between; min-height:
|
| 160 |
.bubble.tall { min-height: 220px; }
|
| 161 |
.grid { gap:12px; }
|
| 162 |
.stat { font-weight:600; font-size:14px; }
|
| 163 |
.banner { background:#f6f7ff; border:1px solid #e3e6ff; padding:10px 12px; border-radius:12px; }
|
|
|
|
|
|
|
| 164 |
"""
|
| 165 |
|
| 166 |
with gr.Blocks(theme=THEME, css=CSS) as demo:
|
| 167 |
-
#
|
| 168 |
if DF.empty:
|
| 169 |
-
gr.Markdown("<div class='banner'><b>
|
| 170 |
else:
|
| 171 |
-
|
| 172 |
-
gr.Markdown(f"<div class='banner'>Loaded <b>{len(DF)}</b> players β’ Columns: <code>{cols}</code></div>")
|
| 173 |
|
| 174 |
gr.Markdown("<h1 id='title'>ProScout β Player Finder</h1>")
|
| 175 |
|
| 176 |
-
# Toggle for nation list source (dataset vs all countries)
|
| 177 |
-
with gr.Row():
|
| 178 |
-
with gr.Column(scale=3):
|
| 179 |
-
pass
|
| 180 |
-
with gr.Column(scale=1):
|
| 181 |
-
with gr.Group(elem_classes="bubble"):
|
| 182 |
-
show_all_countries = gr.Checkbox(label="Show all countries", value=False, info="Default: only nations present in your dataset")
|
| 183 |
-
|
| 184 |
-
# Filters + results
|
| 185 |
with gr.Row():
|
| 186 |
-
# Column 1 β categorical + search
|
| 187 |
with gr.Column(scale=1, elem_classes="grid"):
|
| 188 |
with gr.Group(elem_classes="bubble tall"):
|
| 189 |
pos = gr.CheckboxGroup(positions_list(), label="Positions", value=[], info="Pick one or more")
|
|
@@ -218,28 +202,16 @@ with gr.Blocks(theme=THEME, css=CSS) as demo:
|
|
| 218 |
with gr.Group(elem_classes="bubble"):
|
| 219 |
max_wage = gr.Number(value=np.nan, label="Max Wage (EUR)")
|
| 220 |
|
| 221 |
-
# Results panel
|
| 222 |
with gr.Column(scale=2):
|
| 223 |
with gr.Group(elem_classes="bubble tall"):
|
|
|
|
| 224 |
results = gr.Dataframe(row_count=(12,"dynamic"), wrap=True, interactive=False, label="Results")
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
avg_age_box = gr.Markdown("", elem_classes="stat")
|
| 229 |
-
dl = gr.DownloadButton("Download CSV")
|
| 230 |
-
|
| 231 |
-
# Full-width Search button
|
| 232 |
-
with gr.Row():
|
| 233 |
-
with gr.Column(scale=3):
|
| 234 |
-
with gr.Group(elem_classes="bubble"):
|
| 235 |
btn = gr.Button("Search", variant="primary")
|
| 236 |
|
| 237 |
-
# Dynamic: switch nations list source
|
| 238 |
-
def update_nations_list(show_all):
|
| 239 |
-
return gr.update(choices=all_countries_minus_excluded() if show_all else dataset_nations(), value=[])
|
| 240 |
-
|
| 241 |
-
show_all_countries.change(update_nations_list, inputs=show_all_countries, outputs=nat)
|
| 242 |
-
|
| 243 |
# Main callback
|
| 244 |
def _run(positions, nations, clubs, min_overall, min_potential,
|
| 245 |
max_age, min_h, max_h, min_w, max_w, max_val, max_wage, query):
|
|
@@ -247,21 +219,4 @@ with gr.Blocks(theme=THEME, css=CSS) as demo:
|
|
| 247 |
max_age, min_h, max_h, min_w, max_w, max_val, max_wage, query)
|
| 248 |
count = len(df)
|
| 249 |
avg_ovr = round(df["Overall"].mean(), 2) if "Overall" in df and not df["Overall"].isna().all() else "-"
|
| 250 |
-
avg_age = round(df["Age"].mean(), 2) if "Age" in df and not df["Age"].isna(
|
| 251 |
-
csv_bytes = to_csv_bytes(df)
|
| 252 |
-
return df, f"**Players:** {count}", f"**Avg OVR:** {avg_ovr}", f"**Avg Age:** {avg_age}", csv_bytes
|
| 253 |
-
|
| 254 |
-
# Load initial results automatically
|
| 255 |
-
demo.load(
|
| 256 |
-
_run,
|
| 257 |
-
inputs=[pos,nat,clu,min_ovr,min_pot,max_age,min_h,max_h,min_w,max_w,max_val,max_wage,query],
|
| 258 |
-
outputs=[results,count_box,avg_ovr_box,avg_age_box,dl]
|
| 259 |
-
)
|
| 260 |
-
btn.click(
|
| 261 |
-
_run,
|
| 262 |
-
inputs=[pos,nat,clu,min_ovr,min_pot,max_age,min_h,max_h,min_w,max_w,max_val,max_wage,query],
|
| 263 |
-
outputs=[results,count_box,avg_ovr_box,avg_age_box,dl]
|
| 264 |
-
)
|
| 265 |
-
|
| 266 |
-
# Launch for Spaces
|
| 267 |
-
demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|
|
|
|
| 105 |
vals = [n for n in DF["Nation"].dropna().unique().tolist() if str(n).strip()]
|
| 106 |
return sorted(set(vals))
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
def clubs_list():
|
| 109 |
if "Club" not in DF.columns: return []
|
| 110 |
vals = [c for c in DF["Club"].dropna().unique().tolist() if str(c).strip()]
|
|
|
|
| 143 |
return buf.getvalue().encode("utf-8")
|
| 144 |
|
| 145 |
# =======================
|
| 146 |
+
# UI (clean aesthetic; equal bubble heights; Search in Results header)
|
| 147 |
# =======================
|
| 148 |
THEME = gr.themes.Soft(primary_hue="blue")
|
| 149 |
CSS = """
|
| 150 |
#title { text-align:center; }
|
| 151 |
+
.bubble { background:#fff; border-radius:16px; padding:12px; box-shadow:0 2px 10px rgba(0,0,0,.06); display:flex; flex-direction:column; justify-content:space-between; min-height:120px; }
|
| 152 |
.bubble.tall { min-height: 220px; }
|
| 153 |
.grid { gap:12px; }
|
| 154 |
.stat { font-weight:600; font-size:14px; }
|
| 155 |
.banner { background:#f6f7ff; border:1px solid #e3e6ff; padding:10px 12px; border-radius:12px; }
|
| 156 |
+
.header-row { display:flex; align-items:center; justify-content:space-between; gap:12px; }
|
| 157 |
+
.header-row .stats { display:flex; gap:16px; }
|
| 158 |
"""
|
| 159 |
|
| 160 |
with gr.Blocks(theme=THEME, css=CSS) as demo:
|
| 161 |
+
# Top banner confirms linkage
|
| 162 |
if DF.empty:
|
| 163 |
+
gr.Markdown("<div class='banner'><b>No players loaded.</b> Make sure <code>players.dataset.xlsx</code> is in the root.</div>")
|
| 164 |
else:
|
| 165 |
+
gr.Markdown(f"<div class='banner'>Loaded <b>{len(DF)}</b> players.</div>")
|
|
|
|
| 166 |
|
| 167 |
gr.Markdown("<h1 id='title'>ProScout β Player Finder</h1>")
|
| 168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
with gr.Row():
|
| 170 |
+
# Column 1 β categorical + search (each in its own bubble)
|
| 171 |
with gr.Column(scale=1, elem_classes="grid"):
|
| 172 |
with gr.Group(elem_classes="bubble tall"):
|
| 173 |
pos = gr.CheckboxGroup(positions_list(), label="Positions", value=[], info="Pick one or more")
|
|
|
|
| 202 |
with gr.Group(elem_classes="bubble"):
|
| 203 |
max_wage = gr.Number(value=np.nan, label="Max Wage (EUR)")
|
| 204 |
|
| 205 |
+
# Results panel β Search button in header next to stats
|
| 206 |
with gr.Column(scale=2):
|
| 207 |
with gr.Group(elem_classes="bubble tall"):
|
| 208 |
+
header = gr.HTML("<div class='header-row'><div class='stats'><span id='stat-count'></span><span id='stat-ovr'></span><span id='stat-age'></span></div></div>")
|
| 209 |
results = gr.Dataframe(row_count=(12,"dynamic"), wrap=True, interactive=False, label="Results")
|
| 210 |
+
count_box = gr.Markdown("", elem_id="stat-count")
|
| 211 |
+
avg_ovr_box = gr.Markdown("", elem_id="stat-ovr")
|
| 212 |
+
avg_age_box = gr.Markdown("", elem_id="stat-age")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
btn = gr.Button("Search", variant="primary")
|
| 214 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
# Main callback
|
| 216 |
def _run(positions, nations, clubs, min_overall, min_potential,
|
| 217 |
max_age, min_h, max_h, min_w, max_w, max_val, max_wage, query):
|
|
|
|
| 219 |
max_age, min_h, max_h, min_w, max_w, max_val, max_wage, query)
|
| 220 |
count = len(df)
|
| 221 |
avg_ovr = round(df["Overall"].mean(), 2) if "Overall" in df and not df["Overall"].isna().all() else "-"
|
| 222 |
+
avg_age = round(df["Age"].mean(), 2) if "Age" in df and not df["Age"].isna(_
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|