Britzzy commited on
Commit
1a1d0e5
·
verified ·
1 Parent(s): 23f9676

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -41
app.py CHANGED
@@ -18,48 +18,46 @@ def load_data_and_model():
18
  return None, None
19
  df = pd.read_csv(df_path)
20
 
21
- # Standardize market value column name EARLY and DEDUPLICATE
22
- for col in ['market_value_in_eur_x', 'market_value_in_eur_y', 'market_value_x', 'market_value_y']:
23
- if col in df.columns:
24
- df.rename(columns={col: 'market_value_in_eur'}, inplace=True)
 
 
 
 
25
 
26
- # CRITICAL: Drop any duplicate columns created by renaming
27
  df = df.loc[:, ~df.columns.duplicated()].copy()
28
 
29
  # Load model
30
  model = xgb.XGBRegressor()
31
  try:
32
- if os.path.exists("fairvalue_xgboost.json"):
33
- model.load_model("fairvalue_xgboost.json")
34
- elif os.path.exists("FairValue_xgboost.json"):
35
- model.load_model("FairValue_xgboost.json")
 
 
36
  else:
37
- return df, "MODEL_NOT_FOUND"
38
  except Exception as e:
39
- return df, f"ERROR: {str(e)}"
40
 
41
  return df, model
42
 
43
  df, model = load_data_and_model()
44
 
45
  if isinstance(model, str):
46
- st.error(f"Model Error: {model}. Please ensure 'fairvalue_xgboost.json' is uploaded to the root of your Hugging Face space.")
 
47
  st.stop()
48
  if df is None:
49
- st.error("Data file 'data/processed/app_features.csv' not found. Please upload the 'data/' folder.")
50
  st.stop()
51
 
52
- # === Data Sanity Gate ===
53
- if 'Age' in df.columns:
54
- df['Age'] = df['Age'].clip(15, 45)
55
- if 'Contract_Years_Left' in df.columns:
56
- df['Contract_Years_Left'] = df['Contract_Years_Left'].clip(0.5, 6.0)
57
- if 'market_value_in_eur' in df.columns:
58
- df['market_value_in_eur'] = df['market_value_in_eur'].clip(0, 300_000_000)
59
-
60
  # ======= Sidebar =======
61
  st.sidebar.header("Player Transfer Profile")
62
-
63
  input_mode = st.sidebar.radio("Input Mode", ["Select Existing Player", "Create Custom Player"])
64
 
65
  name_col = next((c for c in ['name', 'name_x', 'Player_Name', 'Name'] if c in df.columns), None)
@@ -68,21 +66,19 @@ if input_mode == "Select Existing Player":
68
  if name_col:
69
  player_list = sorted(df[name_col].unique().tolist())
70
  selected_name = st.sidebar.selectbox("Target Database Player", player_list)
71
- # Using [0:1] to keep it as a DataFrame
72
  player_data = df[df[name_col] == selected_name].iloc[0:1].copy()
73
  else:
74
- st.sidebar.error("Player names column not found in CSV.")
75
  st.stop()
76
 
77
  st.sidebar.markdown("---")
78
- st.sidebar.markdown("**Simulation Parameters** (Overrides)")
79
 
80
- # Use .iloc[0] to avoid KeyError: 0 when the original index is preserved
81
  contract_default = max(0.5, min(6.0, float(player_data['Contract_Years_Left'].iloc[0])))
82
  age_default = max(16, min(40, int(player_data['Age'].iloc[0])))
83
 
84
  injury_col = next((c for c in ['Injury_Days_Total_24m', 'Injury_Days'] if c in player_data.columns), None)
85
- injury_default = 10
86
  if injury_col:
87
  injury_default = max(0, min(500, int(player_data[injury_col].iloc[0])))
88
 
@@ -93,34 +89,41 @@ if input_mode == "Select Existing Player":
93
  else:
94
  st.sidebar.markdown("---")
95
  st.sidebar.markdown("**Custom Player Attributes**")
 
96
  player_data = df.median(numeric_only=True).to_frame().T
97
 
98
  contract_years = st.sidebar.slider("Contract Years Remaining", 0.5, 6.0, 2.0, 0.5)
99
  age = st.sidebar.slider("Age", 16, 40, 24)
100
  injuries = st.sidebar.number_input("Injury missed days (24m)", 0, 500, 10)
101
 
102
- if 'market_value_in_eur' in player_data.columns:
103
- m_val = st.sidebar.number_input("Current Market Value Estimation (£m)", 1.0, 150.0, 20.0)
104
- player_data['market_value_in_eur'] = (m_val * 1_000_000) / 0.85
 
 
105
 
106
- asking_price = st.sidebar.number_input("Selling Club Asking Price (£m)", 1.0, 200.0, 45.0)
107
 
108
  # Apply overrides
109
  player_data['Contract_Years_Left'] = contract_years
110
  player_data['Age'] = age
111
- if 'Injury_Days_Total_24m' in player_data.columns:
112
- player_data['Injury_Days_Total_24m'] = injuries
113
 
114
  # Prepare inference payload
115
  expected_cols = model.feature_names_in_
116
  X_infer = player_data.reindex(columns=expected_cols, fill_value=0)
117
 
118
  if st.button("Calculate Prediction", type="primary"):
119
- # Predict
 
 
 
 
120
  log_pv = model.predict(X_infer)[0]
121
  baseline_pv = np.expm1(log_pv)
122
 
123
- # Apply Hard Cap logic
124
  conservative_bound = baseline_pv * 0.85
125
  risk_discount_pct = 0.0
126
  if contract_years < 1.5: risk_discount_pct += 0.20
@@ -142,11 +145,10 @@ if st.button("Calculate Prediction", type="primary"):
142
  delta={'reference': hard_cap_m, 'increasing': {'color': "red"}, 'decreasing': {'color': "green"}},
143
  title={'text': "Asking Price vs Calculated Hard Cap (£m)"},
144
  gauge={
145
- 'axis': {'range': [None, max(asking_price, hard_cap_m) * 1.5]},
146
  'steps': [
147
  {'range': [0, hard_cap_m], 'color': "rgba(0, 255, 0, 0.3)"},
148
  {'range': [hard_cap_m, hard_cap_m*1.2], 'color': "rgba(255, 255, 0, 0.3)"},
149
- {'range': [hard_cap_m*1.2, max(asking_price, hard_cap_m) * 1.5], 'color': "rgba(255, 0, 0, 0.3)"}
150
  ],
151
  'threshold': {'line': {'color': "white", 'width': 4}, 'thickness': 0.75, 'value': hard_cap_m}
152
  }
@@ -157,16 +159,20 @@ if st.button("Calculate Prediction", type="primary"):
157
  st.subheader("Metrics Breakdown")
158
  st.metric("Predicted Market Value (ML Baseline)", f"£{baseline_pv_m:.1f}m")
159
  if risk_discount_pct > 0:
160
- st.metric("Risk Penalty Applied (Busquets Factor)", f"-£{discount_val/1_000_000:.1f}m", f"-{risk_discount_pct*100:.0f}% Risk penalty!")
161
  else:
162
  st.metric("Risk Penalty Applied", f"£0m", "0% Risk")
163
  st.metric("Recommended Hard Cap", f"£{hard_cap_m:.1f}m")
164
 
165
  if asking_price > hard_cap_m:
166
- st.error(f"**Winner's Curse Risk:** The asking price is £{asking_price - hard_cap_m:.1f}m OVER the Cap.")
167
  else:
168
- st.success("The asking price is within acceptable statistical margins.")
169
 
 
 
 
 
170
  st.subheader("Algorithmic Interpretability (SHAP)")
171
  try:
172
  explainer = shap.TreeExplainer(model)
@@ -181,6 +187,6 @@ if st.button("Calculate Prediction", type="primary"):
181
  st.markdown("---")
182
  st.subheader("Model Performance Assessment")
183
  st.markdown(f"**Training Set Size:** `{len(df):,}` verified transfers")
184
- st.markdown(f"**Features Engined:** `{len(model.feature_names_in_)}` columns")
185
  st.markdown("**Validation MAE (Mean Absolute Error):** `~£23,980,000` *(Statistical Range)*")
186
  st.caption("Training occurred utilizing a 5-Folds RandomizedSearchCV across 40 hyperparameter iterations.")
 
18
  return None, None
19
  df = pd.read_csv(df_path)
20
 
21
+ # Standardize ALL possible market value names to match model
22
+ # The common ones are market_value_in_eur, market_value_in_eur_x, market_value_in_eur_y
23
+ mv_rename_map = {}
24
+ for col in df.columns:
25
+ if 'market_value' in col.lower() and 'eur' in col.lower():
26
+ mv_rename_map[col] = 'market_value_in_eur'
27
+ if mv_rename_map:
28
+ df.rename(columns=mv_rename_map, inplace=True)
29
 
30
+ # Deduplicate early
31
  df = df.loc[:, ~df.columns.duplicated()].copy()
32
 
33
  # Load model
34
  model = xgb.XGBRegressor()
35
  try:
36
+ path = "fairvalue_xgboost.json"
37
+ if not os.path.exists(path):
38
+ path = "FairValue_xgboost.json"
39
+
40
+ if os.path.exists(path):
41
+ model.load_model(path)
42
  else:
43
+ return df, f"MODEL_FILE_NOT_FOUND (Checked: {os.getcwd()})"
44
  except Exception as e:
45
+ return df, f"LOAD_ERROR: {str(e)}"
46
 
47
  return df, model
48
 
49
  df, model = load_data_and_model()
50
 
51
  if isinstance(model, str):
52
+ st.error(f"⚠️ {model}")
53
+ st.info("Ensure the model file is uploaded to the root of your Space.")
54
  st.stop()
55
  if df is None:
56
+ st.error("⚠️ Data file not found at 'data/processed/app_features.csv'")
57
  st.stop()
58
 
 
 
 
 
 
 
 
 
59
  # ======= Sidebar =======
60
  st.sidebar.header("Player Transfer Profile")
 
61
  input_mode = st.sidebar.radio("Input Mode", ["Select Existing Player", "Create Custom Player"])
62
 
63
  name_col = next((c for c in ['name', 'name_x', 'Player_Name', 'Name'] if c in df.columns), None)
 
66
  if name_col:
67
  player_list = sorted(df[name_col].unique().tolist())
68
  selected_name = st.sidebar.selectbox("Target Database Player", player_list)
 
69
  player_data = df[df[name_col] == selected_name].iloc[0:1].copy()
70
  else:
71
+ st.sidebar.error("Player names column not found.")
72
  st.stop()
73
 
74
  st.sidebar.markdown("---")
75
+ st.sidebar.markdown("**Simulation Parameters**")
76
 
 
77
  contract_default = max(0.5, min(6.0, float(player_data['Contract_Years_Left'].iloc[0])))
78
  age_default = max(16, min(40, int(player_data['Age'].iloc[0])))
79
 
80
  injury_col = next((c for c in ['Injury_Days_Total_24m', 'Injury_Days'] if c in player_data.columns), None)
81
+ injury_default = 0
82
  if injury_col:
83
  injury_default = max(0, min(500, int(player_data[injury_col].iloc[0])))
84
 
 
89
  else:
90
  st.sidebar.markdown("---")
91
  st.sidebar.markdown("**Custom Player Attributes**")
92
+ # MEDIAN fallback to ensure all 20+ columns are filled
93
  player_data = df.median(numeric_only=True).to_frame().T
94
 
95
  contract_years = st.sidebar.slider("Contract Years Remaining", 0.5, 6.0, 2.0, 0.5)
96
  age = st.sidebar.slider("Age", 16, 40, 24)
97
  injuries = st.sidebar.number_input("Injury missed days (24m)", 0, 500, 10)
98
 
99
+ # Try to find the market value column to let user edit it
100
+ mv_col = 'market_value_in_eur'
101
+ if mv_col in player_data.columns:
102
+ m_val = st.sidebar.number_input("Current Market Value Estimation (£m)", 1.0, 200.0, 20.0)
103
+ player_data[mv_col] = (m_val * 1_000_000) / 0.85
104
 
105
+ asking_price = st.sidebar.number_input("Selling Club Asking Price (£m)", 1.0, 300.0, 45.0)
106
 
107
  # Apply overrides
108
  player_data['Contract_Years_Left'] = contract_years
109
  player_data['Age'] = age
110
+ inj_col_to_set = next((c for c in ['Injury_Days_Total_24m', 'Injury_Days'] if c in player_data.columns), 'Injury_Days_Total_24m')
111
+ player_data[inj_col_to_set] = injuries
112
 
113
  # Prepare inference payload
114
  expected_cols = model.feature_names_in_
115
  X_infer = player_data.reindex(columns=expected_cols, fill_value=0)
116
 
117
  if st.button("Calculate Prediction", type="primary"):
118
+ # Debug: Check if the market value is zero
119
+ if X_infer['market_value_in_eur'].iloc[0] == 0:
120
+ st.warning("⚠️ Market Value feature is 0.0! The model will underestimate. Checking input alignment...")
121
+ st.write("Columns available in player_data:", player_data.columns.tolist())
122
+
123
  log_pv = model.predict(X_infer)[0]
124
  baseline_pv = np.expm1(log_pv)
125
 
126
+ # Hard Cap logic
127
  conservative_bound = baseline_pv * 0.85
128
  risk_discount_pct = 0.0
129
  if contract_years < 1.5: risk_discount_pct += 0.20
 
145
  delta={'reference': hard_cap_m, 'increasing': {'color': "red"}, 'decreasing': {'color': "green"}},
146
  title={'text': "Asking Price vs Calculated Hard Cap (£m)"},
147
  gauge={
148
+ 'axis': {'range': [0, max(asking_price, hard_cap_m) * 1.5]},
149
  'steps': [
150
  {'range': [0, hard_cap_m], 'color': "rgba(0, 255, 0, 0.3)"},
151
  {'range': [hard_cap_m, hard_cap_m*1.2], 'color': "rgba(255, 255, 0, 0.3)"},
 
152
  ],
153
  'threshold': {'line': {'color': "white", 'width': 4}, 'thickness': 0.75, 'value': hard_cap_m}
154
  }
 
159
  st.subheader("Metrics Breakdown")
160
  st.metric("Predicted Market Value (ML Baseline)", f"£{baseline_pv_m:.1f}m")
161
  if risk_discount_pct > 0:
162
+ st.metric("Risk Penalty Applied", f"-£{discount_val/1_000_000:.1f}m", f"-{risk_discount_pct*100:.0f}% Risk penalty!")
163
  else:
164
  st.metric("Risk Penalty Applied", f"£0m", "0% Risk")
165
  st.metric("Recommended Hard Cap", f"£{hard_cap_m:.1f}m")
166
 
167
  if asking_price > hard_cap_m:
168
+ st.error(f"Winner's Curse Risk: The asking price is £{asking_price - hard_cap_m:.1f}m OVER the Cap.")
169
  else:
170
+ st.success("Asking price is within acceptable margins.")
171
 
172
+ # SHAP / Diagnostics
173
+ with st.expander("Show Model Input Diagnostics"):
174
+ st.write("X_infer Payload:", X_infer)
175
+
176
  st.subheader("Algorithmic Interpretability (SHAP)")
177
  try:
178
  explainer = shap.TreeExplainer(model)
 
187
  st.markdown("---")
188
  st.subheader("Model Performance Assessment")
189
  st.markdown(f"**Training Set Size:** `{len(df):,}` verified transfers")
190
+ st.markdown(f"**Features Engined:** `{len(expected_cols)}` columns")
191
  st.markdown("**Validation MAE (Mean Absolute Error):** `~£23,980,000` *(Statistical Range)*")
192
  st.caption("Training occurred utilizing a 5-Folds RandomizedSearchCV across 40 hyperparameter iterations.")