diff --git a/dashboard/pages/01_matchday.py b/dashboard/pages/01_matchday.py index f7c51f0..86efb72 100644 --- a/dashboard/pages/01_matchday.py +++ b/dashboard/pages/01_matchday.py @@ -1,17 +1,24 @@ import sys; from pathlib import Path; _p = Path(__file__).resolve().parent.parent.parent; str(_p) not in sys.path and sys.path.insert(0, str(_p)) """Page 1 — Matchday Control Room. -KPI row, fixture difficulty heatmap, start/sit table, bump chart. +KPI row, fixture difficulty heatmap, start/sit table, season-long projections. """ import numpy as np import pandas as pd import streamlit as st +import plotly.graph_objects as go +from plotly.subplots import make_subplots from dashboard.warehouse import load_predictions, load_fixtures, load_players, load_lineups, load_votes from dashboard.viz.components import inject_css, kpi_card, section, insight, role_chip from dashboard.viz.charts import fixture_heatmap, kpi_sparkline, error_violins -from dashboard.viz.template import PITCH_GREEN, GOLD, RED, SKY, VIOLET, WHITE, TEXT_SECONDARY, ROLE_COLORS, ROLE_ICONS, FANTABETO_TEMPLATE +from dashboard.viz.template import ( + PITCH_GREEN, GOLD, RED, SKY, VIOLET, WHITE, TEXT_SECONDARY, + ROLE_COLORS, ROLE_ICONS, FANTABETO_TEMPLATE +) + +SEASON_PROJECTIONS = Path(__file__).resolve().parent.parent.parent / "data" / "season_projections_26_27.xlsx" @st.cache_data(ttl=3600) @@ -168,6 +175,47 @@ def run(): ) st.plotly_chart(fig, width="stretch") + # ── Season Projections ───────────────────────────────────────────── + st.divider() + section("📅 Season-Long Projections", "4-3-3 starting XI + full season FV rankings.") + + if SEASON_PROJECTIONS.exists(): + season = pd.read_excel(SEASON_PROJECTIONS) + + # 4-3-3 Best XI + s_gk = season[season['role'] == 'P'].nlargest(1, 'total_season_fv') + s_def = season[season['role'] == 'D'].nlargest(4, 'total_season_fv') + s_mid = season[season['role'] == 'C'].nlargest(3, 'total_season_fv') + s_fwd = season[season['role'] == 'A'].nlargest(3, 'total_season_fv') + stotal = s_gk['total_season_fv'].sum() + s_def['total_season_fv'].sum() + \ + s_mid['total_season_fv'].sum() + s_fwd['total_season_fv'].sum() + captain_iron = s_fwd.iloc[0] + + c_season, c_table = st.columns([1, 2]) + with c_season: + st.markdown("**4-3-3 Projected XI (Season)**", unsafe_allow_html=False) + st.markdown(f"🧤 {s_gk.iloc[0]['player']} ({s_gk.iloc[0]['team']}) — {s_gk.iloc[0]['total_season_fv']:.0f} FV") + for _, d in s_def.iterrows(): + st.markdown(f"🛡 {d['player']} ({d['team']}) — {d['total_season_fv']:.0f} FV") + for _, m in s_mid.iterrows(): + st.markdown(f"⚙ {m['player']} ({m['team']}) — {m['total_season_fv']:.0f} FV") + for _, f in s_fwd.iterrows(): + st.markdown(f"⚡ {f['player']} ({f['team']}) — {f['total_season_fv']:.0f} FV") + st.markdown(f"⭐ Captain: **{captain_iron['player']}** → {stotal + captain_iron['total_season_fv']:.0f} FV") + insight(f"Projected season total: {stotal:.0f} FV ({stotal + captain_iron['total_season_fv']:.0f} with captain)") + + with c_table: + show = season.nlargest(25, 'total_season_fv')[ + ['player', 'role', 'team', 'projected_games', 'avg_fv_per_game', 'total_season_fv'] + ].copy() + show.columns = ['Player', 'Role', 'Team', 'Games', 'Avg FV/g', 'Season FV'] + show['Season FV'] = show['Season FV'].astype(float).round(0).astype(int) + show['Avg FV/g'] = show['Avg FV/g'].astype(float).round(2) + st.dataframe(show, use_container_width=True, hide_index=True) + insight("Season-long FV projections based on 25/26 performance, opponent difficulty, home/away, and fatigue.") + else: + st.info("Season projections not yet generated. Run the season projector to populate.") + if __name__ == "__main__": run() diff --git a/dashboard/pages/04_lineup.py b/dashboard/pages/04_lineup.py index 2759135..3881518 100644 --- a/dashboard/pages/04_lineup.py +++ b/dashboard/pages/04_lineup.py @@ -48,10 +48,10 @@ def _optimize_lineup(pool, captain_override=None, force_in=None, force_out=None) # Sort by FV mean sorted_pool = sorted(pool, key=lambda x: x.get("fv_mean", 0), reverse=True) - # Build starting XI: 1 GK + format 4-4-2 + # Build starting XI: 1 GK + 4 DEF + 3 MID + 3 FWD = 4-3-3 lineup = [] roles_filled = {"P": 0, "D": 0, "C": 0, "A": 0} - limits = {"P": 1, "D": 4, "C": 4, "A": 2} + limits = {"P": 1, "D": 4, "C": 3, "A": 3} for p in sorted_pool: r = p.get("role", "C") @@ -104,7 +104,7 @@ def run(): help="Override: bench a player") force_out = None if force_out == "None" else force_out with c3: - formation = st.selectbox("Formation", ["4-4-2", "4-3-3", "3-5-2", "4-2-3-1", "3-4-3"], index=0) + formation = st.selectbox("Formation", ["4-3-3", "4-4-2", "3-5-2", "4-2-3-1", "3-4-3"], index=0) lineup, bench, captain, expected = _optimize_lineup(squad, force_in=force_in, force_out=force_out) @@ -194,3 +194,5 @@ def run(): if __name__ == "__main__": run() + +# EOF marker — actually let me rewrite the full file diff --git a/data/auction_plan_al_cihred.xlsx b/data/auction_plan_al_cihred.xlsx index 39d97c7..7b6b099 100644 Binary files a/data/auction_plan_al_cihred.xlsx and b/data/auction_plan_al_cihred.xlsx differ diff --git a/data/matchday_projections.xlsx b/data/matchday_projections.xlsx new file mode 100644 index 0000000..891742c Binary files /dev/null and b/data/matchday_projections.xlsx differ diff --git a/data/season_projections_26_27.xlsx b/data/season_projections_26_27.xlsx new file mode 100644 index 0000000..2a82d4e Binary files /dev/null and b/data/season_projections_26_27.xlsx differ