"""Page 3 — Auction War Room. Budget waterfall, value scatter, grid-auction heatmap, budget slider simulator. """ import numpy as np import pandas as pd import streamlit as st import plotly.graph_objects as go from dashboard.warehouse import load_players, load_predictions from dashboard.viz.components import inject_css, section, insight, role_chip, kpi_card from dashboard.viz.charts import budget_waterfall, value_scatter from dashboard.viz.template import ( PITCH_GREEN, GOLD, RED, SKY, BG, CARD_BG, BORDER, TEXT_SECONDARY, WHITE, FANTABETO_TEMPLATE, HEATMAP_COLORS, ROLE_COLORS, ) @st.cache_data(ttl=3600) def _get_data(): players = load_players() preds = load_predictions() return players, preds def _compute_auction(players, budget, gk, df, mf, fw): """Greedy knapsack auction solver.""" quotas = {"P": gk, "D": df, "C": mf, "A": fw} filled = {"P": 0, "D": 0, "C": 0, "A": 0} remaining = budget df = players.copy() df["value_ratio"] = df["fv_avg"] / df["qi"].clip(lower=1) df["estimated_price"] = df["qi"] * np.clip(np.random.RandomState(42).normal(2.5, 0.8, len(df)), 0.8, 6) scored = [] for _, p in df.iterrows(): role = p["role"] if role not in quotas: continue scored.append((p["value_ratio"] * p["fv_avg"], p)) scored.sort(key=lambda x: -x[0]) selected = [] for _, p in scored: role = p["role"] if filled[role] >= quotas[role]: continue price = p["estimated_price"] if price > remaining: continue selected.append({ "player": p["player"], "role": role, "team": p["team"], "fv_avg": p["fv_avg"], "qi": p["qi"], "estimated_price": price, "games_season": p.get("games_season", 30), }) remaining -= price filled[role] += 1 total = budget - remaining total_fv = sum(s["fv_avg"] for s in selected) return selected, total, total_fv, remaining def _grid_heatmap(players): """Simplified grid auction heatmap: top players × bid levels.""" top = players.nlargest(10, "fv_avg")[ ["player", "role", "fv_avg", "qi"] ].copy() bid_multipliers = [1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0] matrix = [] labels = [] for _, p in top.iterrows(): row = [] for mult in bid_multipliers: bid = p["qi"] * mult surplus = p["fv_avg"] * 3 - bid # rough value row.append(max(0, surplus)) matrix.append(row) labels.append(p["player"]) fig = go.Figure(data=go.Heatmap( z=matrix, x=[f"{m}x QI" for m in bid_multipliers], y=labels, colorscale=HEATMAP_COLORS, hovertemplate="%{y}
Bid: %{x}
Surplus: %{z:.0f}", )) fig.update_layout( template=FANTABETO_TEMPLATE, height=350, xaxis=dict(side="top"), yaxis=dict(autorange="reversed"), ) return fig def run(): st.set_page_config(page_title="Auction — Fantabeto", page_icon="💰", layout="wide") inject_css() players, preds = _get_data() st.markdown("## 💰 Auction War Room") st.caption("Project Al-Cihred — Draft Strategy for 2026/27 Classic Auction") # ── Budget controls ── c_budget, c_gk, c_def, c_mid, c_fwd = st.columns(5) with c_budget: budget = st.slider("Budget (cr)", 300, 700, 500, 10) with c_gk: n_gk = st.number_input("GK", 1, 5, 3) with c_def: n_def = st.number_input("DEF", 3, 12, 8) with c_mid: n_mid = st.number_input("MID", 3, 12, 8) with c_fwd: n_fwd = st.number_input("FWD", 1, 8, 6) selected, total_cost, total_fv, remaining = _compute_auction( players, budget, n_gk, n_def, n_mid, n_fwd ) # ── KPI Row ── k1, k2, k3, k4 = st.columns(4) with k1: st.markdown(kpi_card("PLAYERS DRAFTED", str(len(selected)), f"{n_gk+n_def+n_mid+n_fwd} target", SKY), unsafe_allow_html=True) with k2: st.markdown(kpi_card("TOTAL SPENT", f"{total_cost:.0f} cr", f"{remaining:.0f} cr remaining", PITCH_GREEN), unsafe_allow_html=True) with k3: st.markdown(kpi_card("PROJECTED FV", f"{total_fv:.1f}", f"{total_fv / max(total_cost, 1):.2f} cr/FV", GOLD), unsafe_allow_html=True) with k4: st.markdown(kpi_card("AVG PRICE", f"{total_cost / max(len(selected), 1):.0f} cr", "per player", SKY), unsafe_allow_html=True) st.divider() # ── Budget Waterfall + Value Scatter ── c1, c2 = st.columns([2, 3]) with c1: section("💧 Budget Allocation") allocations = {} for r in ["P", "D", "C", "A"]: allocations[r] = sum(s["estimated_price"] for s in selected if s["role"] == r) fig = budget_waterfall(allocations) st.plotly_chart(fig, use_container_width=True) insight("How your budget maps across roles. Aim for ~15% GK, ~35% DEF, ~30% MID, ~20% FWD.") with c2: section("📈 Value Scatter") fig = value_scatter(players) st.plotly_chart(fig, use_container_width=True) insight("Top-right: high FV, high price. Bottom-right: value steals. " "Bubble size = games played. Dashed lines = cost-per-FV-point isolines.") st.divider() # ── Target Squad ── section("🎯 Recommended Squad") if selected: squad_df = pd.DataFrame(selected) for role in ["P", "D", "C", "A"]: rdf = squad_df[squad_df["role"] == role] if rdf.empty: continue role_name = {"P": "Goalkeepers", "D": "Defenders", "C": "Midfielders", "A": "Forwards"}[role] st.markdown(f"**{role_name}** {role_chip(role)}") for _, p in rdf.iterrows(): st.markdown( f"- **{p['player']}** ({p['team']}) — " f"FV: {p['fv_avg']:.2f} | " f"Max bid: {p['estimated_price']:.0f} cr | " f"Games: {p['games_season']:.0f}", ) st.divider() # ── Grid Auction Heatmap ── section("🔢 Grid Auction Simulator") fig = _grid_heatmap(players) st.plotly_chart(fig, use_container_width=True) insight("Green = good value at that bid multiplier. Red = overpaying. " "Bid at the 'green' multiplier for each player.") if __name__ == "__main__": run()