fairvalue-api / src /models /predict_cap.py
FairValue
feat: production web app — React/Vite frontend + FastAPI backend with Render/Vercel deployment
b72652e
import numpy as np
import xgboost as xgb
class FairValueCapEstimator:
def __init__(self, model_path="fairvalue_xgboost.json"):
self.model = xgb.XGBRegressor()
try:
self.model.load_model(model_path)
# Default validation error margin (Confidence Interval) roughly 15%
self.model_error_margin = 0.15
self.is_loaded = True
except:
print("Warning: Model file not found. Estimations will be unavailable.")
self.model_error_margin = 0.15
self.is_loaded = False
def predict_fair_value(self, player_features_df):
"""
Calculates the Predicted Value (PV) and the computationally strict Hard Cap.
"""
if not self.is_loaded:
return {"error": "Model not loaded."}
# 1. Predicted Value (PV) (Reverse the log1p transform)
log_pv = self.model.predict(player_features_df)
pv = np.expm1(log_pv)[0]
# 2. Confidence Interval (CI) bounds
conservative_value = pv * (1 - self.model_error_margin)
optimistic_value = pv * (1 + self.model_error_margin)
# 3. Calculate Risk Discount (RD)
risk_discount_percentage = 0.0
# Using feature engineered columns from build_features.py logic
if 'Risk_Contract' in player_features_df.columns and player_features_df['Risk_Contract'].iloc[0] == 1:
risk_discount_percentage += 0.20 # 20% discount for expiring contracts
if 'Risk_Age' in player_features_df.columns and player_features_df['Risk_Age'].iloc[0] == 1:
risk_discount_percentage += 0.15 # 15% discount for age curve decay
if 'Risk_Injury' in player_features_df.columns and player_features_df['Risk_Injury'].iloc[0] == 1:
risk_discount_percentage += 0.10 # 10% discount for recurring injuries
risk_discount_monetary = conservative_value * risk_discount_percentage
# 4. Hard Cap
hard_cap = conservative_value - risk_discount_monetary
return {
'predicted_value': pv,
'conservative': conservative_value,
'optimistic': optimistic_value,
'risk_discount': risk_discount_monetary,
'risk_percentage': risk_discount_percentage,
'hard_cap': hard_cap
}