Spaces:
Sleeping
Sleeping
FairValue commited on
Commit ·
54ac3b6
1
Parent(s): 778ab74
style: polish SHAP labels and optimize NLP cache retry logic
Browse files- api/main.py +29 -3
api/main.py
CHANGED
|
@@ -160,6 +160,24 @@ def startup_event():
|
|
| 160 |
model_global.load_model(MODEL_PATH)
|
| 161 |
expected_cols_global = model_global.feature_names_in_
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
# ── NLP Intelligence Cache (TTL = 1 hour) ────────────────────────────────────
|
| 165 |
# Fixed: previously ran 3 live DDGS searches on every API call — caused
|
|
@@ -178,8 +196,16 @@ def _fetch_nlp_intelligence(
|
|
| 178 |
"""
|
| 179 |
cache_key = f"{player_name.lower()}|{current_club.lower()}"
|
| 180 |
cached = _nlp_cache.get(cache_key)
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
ddgs = DDGS()
|
| 185 |
axes = {
|
|
@@ -327,7 +353,7 @@ async def evaluate_player(req: PlayerEvaluateRequest):
|
|
| 327 |
# ── SHAP Feature Contribution Table ──────────────────────────────────────
|
| 328 |
shap_data = sorted(
|
| 329 |
[
|
| 330 |
-
{"feature":
|
| 331 |
for f, s in zip(expected_cols_global, feature_shaps)
|
| 332 |
],
|
| 333 |
key=lambda x: abs(x['impact']),
|
|
|
|
| 160 |
model_global.load_model(MODEL_PATH)
|
| 161 |
expected_cols_global = model_global.feature_names_in_
|
| 162 |
|
| 163 |
+
def _format_feature_label(f: str) -> str:
|
| 164 |
+
"""Converts raw model feature names to boardroom-ready English."""
|
| 165 |
+
mapping = {
|
| 166 |
+
'Highest_Market_Value_In_Eur': 'Peak Historical Valuation',
|
| 167 |
+
'Highest MarketValue In Eur': 'Peak Historical Valuation',
|
| 168 |
+
'Contract_Years_Left': 'Contractual Duration',
|
| 169 |
+
'Contract YearsLeft': 'Contractual Duration',
|
| 170 |
+
'Injury_Days_Total_24m': 'Physical Availability Risk',
|
| 171 |
+
'Injury Days Total 24M': 'Physical Availability Risk',
|
| 172 |
+
'League_Index': 'League Quality Index',
|
| 173 |
+
'height_in_cm': 'Aerial/Physical Profile',
|
| 174 |
+
'Height In Cm': 'Aerial/Physical Profile',
|
| 175 |
+
'international_caps': 'International Experience',
|
| 176 |
+
'market_value_in_eur': 'Baseline Market Valuation',
|
| 177 |
+
}
|
| 178 |
+
# Fallback to Title Case with underscores replaced by spaces
|
| 179 |
+
return mapping.get(f, f.replace('_', ' ').title())
|
| 180 |
+
|
| 181 |
|
| 182 |
# ── NLP Intelligence Cache (TTL = 1 hour) ────────────────────────────────────
|
| 183 |
# Fixed: previously ran 3 live DDGS searches on every API call — caused
|
|
|
|
| 196 |
"""
|
| 197 |
cache_key = f"{player_name.lower()}|{current_club.lower()}"
|
| 198 |
cached = _nlp_cache.get(cache_key)
|
| 199 |
+
|
| 200 |
+
# Logic: If we have a cached result with real data, keep it for 1 hour.
|
| 201 |
+
# If the cached result was "Empty" (no news found), allow a retry after 5 mins.
|
| 202 |
+
if cached:
|
| 203 |
+
age = time.time() - cached.get('_ts', 0)
|
| 204 |
+
has_signals = cached.get('_found_any', False)
|
| 205 |
+
|
| 206 |
+
if age < _NLP_CACHE_TTL:
|
| 207 |
+
if has_signals or age < 300: # 300s = 5 mins
|
| 208 |
+
return {**cached, '_from_cache': True}
|
| 209 |
|
| 210 |
ddgs = DDGS()
|
| 211 |
axes = {
|
|
|
|
| 353 |
# ── SHAP Feature Contribution Table ──────────────────────────────────────
|
| 354 |
shap_data = sorted(
|
| 355 |
[
|
| 356 |
+
{"feature": _format_feature_label(f), "impact": float(s)}
|
| 357 |
for f, s in zip(expected_cols_global, feature_shaps)
|
| 358 |
],
|
| 359 |
key=lambda x: abs(x['impact']),
|