Britzzy commited on
Commit
331b1cb
·
verified ·
1 Parent(s): a8addfb

Upload 2 files

Browse files
Files changed (1) hide show
  1. app.py +40 -46
app.py CHANGED
@@ -18,15 +18,14 @@ def load_data_and_model():
18
  return None, None
19
  df = pd.read_csv(df_path)
20
 
21
- # Standardize ALL possible market value names
22
- mv_rename_map = {}
23
- for col in df.columns:
24
- if 'market_value' in col.lower():
25
- mv_rename_map[col] = 'market_value_in_eur'
26
- if mv_rename_map:
27
- df.rename(columns=mv_rename_map, inplace=True)
28
 
29
- # Deduplicate
30
  df = df.loc[:, ~df.columns.duplicated()].copy()
31
 
32
  # Load model
@@ -37,13 +36,9 @@ def load_data_and_model():
37
  path = "FairValue_xgboost.json"
38
 
39
  if os.path.exists(path):
40
- # Check file size to catch corrupted uploads
41
- filesize = os.path.getsize(path)
42
- if filesize < 100:
43
- return df, f"CORRUPTED_MODEL (Size: {filesize} bytes. Is too small!)"
44
  model.load_model(path)
45
  else:
46
- return df, f"MODEL_FILE_NOT_FOUND (Checked root for {path})"
47
  except Exception as e:
48
  return df, f"LOAD_ERROR: {str(e)}"
49
 
@@ -70,10 +65,16 @@ if input_mode == "Select Existing Player":
70
  selected_name = st.sidebar.selectbox("Target Database Player", player_list)
71
  player_data = df[df[name_col] == selected_name].iloc[0:1].copy()
72
  else:
 
73
  st.stop()
 
 
 
74
 
 
75
  contract_default = max(0.5, min(6.0, float(player_data['Contract_Years_Left'].iloc[0])))
76
  age_default = max(16, min(40, int(player_data['Age'].iloc[0])))
 
77
  injury_col = next((c for c in ['Injury_Days_Total_24m', 'Injury_Days'] if c in player_data.columns), None)
78
  injury_default = 0
79
  if injury_col:
@@ -84,7 +85,10 @@ if input_mode == "Select Existing Player":
84
  injuries = st.sidebar.number_input("Injury missed days (24m)", 0, 500, injury_default)
85
 
86
  else:
 
 
87
  player_data = df.median(numeric_only=True).to_frame().T
 
88
  contract_years = st.sidebar.slider("Contract Years Remaining", 0.5, 6.0, 2.0, 0.5)
89
  age = st.sidebar.slider("Age", 16, 40, 24)
90
  injuries = st.sidebar.number_input("Injury missed days (24m)", 0, 500, 10)
@@ -95,32 +99,37 @@ else:
95
 
96
  asking_price = st.sidebar.number_input("Selling Club Asking Price (£m)", 1.0, 300.0, 45.0)
97
 
98
- # Build X_infer
99
- expected_cols = model.feature_names_in_
100
  player_data['Contract_Years_Left'] = contract_years
101
  player_data['Age'] = age
102
  if 'Injury_Days_Total_24m' in player_data.columns:
103
  player_data['Injury_Days_Total_24m'] = injuries
104
 
 
105
  X_infer = player_data.reindex(columns=expected_cols, fill_value=0)
106
 
107
  if st.button("Calculate Prediction", type="primary"):
108
- # Debugging Visibility
109
- raw_preds = model.predict(X_infer)
110
- log_pv = raw_preds[0]
111
- baseline_pv = np.expm1(log_pv)
112
-
113
- # UI logic
114
- conservative_bound = baseline_pv * 0.85
115
- risk_discount_pct = 0.0
116
- if contract_years < 1.5: risk_discount_pct += 0.20
117
- if age > 30: risk_discount_pct += 0.15
118
- if injuries > 60: risk_discount_pct += 0.10
119
 
120
- discount_val = conservative_bound * risk_discount_pct
121
- hard_cap = conservative_bound - discount_val
122
 
 
123
  baseline_pv_m = baseline_pv / 1_000_000
 
 
 
 
 
 
 
 
124
  hard_cap_m = hard_cap / 1_000_000
125
 
126
  col1, col2 = st.columns([2, 1])
@@ -137,27 +146,12 @@ if st.button("Calculate Prediction", type="primary"):
137
 
138
  with col2:
139
  st.subheader("Metrics Breakdown")
140
- st.metric("Predicted Market Value (ML Baseline)", f"£{baseline_pv_m:.1f}m")
141
  st.metric("Recommended Hard Cap", f"£{hard_cap_m:.1f}m")
142
  if asking_price > hard_cap_m:
143
- st.error(f"Winner's Curse Risk: £{asking_price - hard_cap_m:.1f}m Overpay.")
144
-
145
- with st.expander("🔬 Technical Diagnostics (FOR DEBUGGING)"):
146
- st.write("Raw Log-Prediction from Model:", log_pv)
147
- st.write("Input Feature Vector (X_infer):", X_infer)
148
- if log_pv == 0 and X_infer['market_value_in_eur'].iloc[0] > 0:
149
- st.error("CRITICAL: The model is receiving data but returning exactly 0.0. This means the uploaded model file is likely empty or corrupted.")
150
-
151
- st.subheader("Algorithmic Interpretability (SHAP)")
152
- try:
153
- explainer = shap.TreeExplainer(model)
154
- shap_values = explainer(X_infer)
155
- shap_df = pd.DataFrame({'Feature': X_infer.columns, 'Impact': shap_values.values[0]}).sort_values(by='Impact', ascending=False)
156
- st.dataframe(shap_df.head(10), use_container_width=True)
157
- except:
158
- st.warning("SHAP Error. Model file/version mismatch.")
159
 
160
  st.markdown("---")
161
  st.subheader("Model Performance Assessment")
162
- st.markdown(f"**Training Set Size:** `{len(df):,}` transfers | **Features:** `{len(expected_cols)}` cols")
163
  st.markdown("**Validation MAE:** `~£23,980,000` | **Engine:** XGBoost + SHAP")
 
18
  return None, None
19
  df = pd.read_csv(df_path)
20
 
21
+ # Standardize ALL possible market value names to match model
22
+ # We look for ANY column containing 'market_value' and 'eur'
23
+ mv_cols = [c for c in df.columns if 'market_value' in c.lower()]
24
+ if mv_cols:
25
+ target_col = next((c for c in mv_cols if 'eur' in c.lower()), mv_cols[0])
26
+ df.rename(columns={target_col: 'market_value_in_eur'}, inplace=True)
 
27
 
28
+ # Deduplicate early
29
  df = df.loc[:, ~df.columns.duplicated()].copy()
30
 
31
  # Load model
 
36
  path = "FairValue_xgboost.json"
37
 
38
  if os.path.exists(path):
 
 
 
 
39
  model.load_model(path)
40
  else:
41
+ return df, "MODEL_FILE_NOT_FOUND"
42
  except Exception as e:
43
  return df, f"LOAD_ERROR: {str(e)}"
44
 
 
65
  selected_name = st.sidebar.selectbox("Target Database Player", player_list)
66
  player_data = df[df[name_col] == selected_name].iloc[0:1].copy()
67
  else:
68
+ st.sidebar.error("Player name column missing.")
69
  st.stop()
70
+
71
+ st.sidebar.markdown("---")
72
+ st.sidebar.markdown("**Simulation Parameters**")
73
 
74
+ # Use position-based access (iloc) for everything to avoid KeyError
75
  contract_default = max(0.5, min(6.0, float(player_data['Contract_Years_Left'].iloc[0])))
76
  age_default = max(16, min(40, int(player_data['Age'].iloc[0])))
77
+
78
  injury_col = next((c for c in ['Injury_Days_Total_24m', 'Injury_Days'] if c in player_data.columns), None)
79
  injury_default = 0
80
  if injury_col:
 
85
  injuries = st.sidebar.number_input("Injury missed days (24m)", 0, 500, injury_default)
86
 
87
  else:
88
+ st.sidebar.markdown("---")
89
+ st.sidebar.markdown("**Custom Player Attributes**")
90
  player_data = df.median(numeric_only=True).to_frame().T
91
+
92
  contract_years = st.sidebar.slider("Contract Years Remaining", 0.5, 6.0, 2.0, 0.5)
93
  age = st.sidebar.slider("Age", 16, 40, 24)
94
  injuries = st.sidebar.number_input("Injury missed days (24m)", 0, 500, 10)
 
99
 
100
  asking_price = st.sidebar.number_input("Selling Club Asking Price (£m)", 1.0, 300.0, 45.0)
101
 
102
+ # Build X_infer with safety
 
103
  player_data['Contract_Years_Left'] = contract_years
104
  player_data['Age'] = age
105
  if 'Injury_Days_Total_24m' in player_data.columns:
106
  player_data['Injury_Days_Total_24m'] = injuries
107
 
108
+ expected_cols = model.feature_names_in_
109
  X_infer = player_data.reindex(columns=expected_cols, fill_value=0)
110
 
111
  if st.button("Calculate Prediction", type="primary"):
112
+ # SAFE CHECK FOR ANY KEYERRORS
113
+ mv_v = 0.0
114
+ if 'market_value_in_eur' in X_infer.columns:
115
+ mv_v = X_infer['market_value_in_eur'].iloc[0]
116
+
117
+ if mv_v == 0:
118
+ st.warning("⚠️ Market Value is 0.0. Prediction may be inaccurate.")
 
 
 
 
119
 
120
+ log_pv = model.predict(X_infer)[0]
121
+ baseline_pv = np.expm1(log_pv)
122
 
123
+ # UI Logic
124
  baseline_pv_m = baseline_pv / 1_000_000
125
+ conservative_bound = baseline_pv * 0.85
126
+
127
+ risk_discount = 0.0
128
+ if contract_years < 1.5: risk_discount += 0.20
129
+ if age > 30: risk_discount += 0.15
130
+ if injuries > 60: risk_discount += 0.10
131
+
132
+ hard_cap = conservative_bound * (1 - risk_discount)
133
  hard_cap_m = hard_cap / 1_000_000
134
 
135
  col1, col2 = st.columns([2, 1])
 
146
 
147
  with col2:
148
  st.subheader("Metrics Breakdown")
149
+ st.metric("Predicted Market Value (Baseline)", f"£{baseline_pv_m:.1f}m")
150
  st.metric("Recommended Hard Cap", f"£{hard_cap_m:.1f}m")
151
  if asking_price > hard_cap_m:
152
+ st.error(f"Winner's Curse Risk: £{asking_price-hard_cap_m:.1f}m Overpay")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
  st.markdown("---")
155
  st.subheader("Model Performance Assessment")
156
+ st.markdown(f"**Training Set:** `{len(df):,}` transfers | **Features:** `{len(expected_cols)}` cols")
157
  st.markdown("**Validation MAE:** `~£23,980,000` | **Engine:** XGBoost + SHAP")