fix: compute surprise_pct from actual/expected when NULL for auto-analysis scoring

Events with surprise_pct=NULL (bootstrap or older events) got inputs={} → evaluate_graph
returned {} → prediction_json='{}' → all scores showed 'En attente'.

Three-step fallback in _run_auto_analysis:
1. Use event.surprise_pct if set (existing behaviour)
2. Compute from actual_value / expected_value stored in market_events
3. Look up most recent ff_calendar release by currency + date

Frontend diagnostic now shows surprise_pct per event and exposes actual/expected
in the refresh result panel to make the source visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-29 22:54:57 +02:00
parent 959c2825df
commit c93eba7a7f
2 changed files with 68 additions and 28 deletions

View File

@@ -1182,27 +1182,56 @@ def _run_auto_analysis(event: dict, template_id: int) -> bool:
except (ValueError, TypeError):
pass
elif src == "surprise_bps" and event.get("surprise_pct") is not None:
raw = float(event["surprise_pct"])
node_range = cfg.get("range")
if node_range and len(node_range) == 2:
lo, hi = float(node_range[0]), float(node_range[1])
inputs[input_id] = round(max(lo, min(hi, raw)), 4)
else:
inputs[input_id] = round(raw, 4)
elif src == "surprise" and event.get("surprise_pct") is not None:
raw = float(event["surprise_pct"])
node_range = cfg.get("range")
if node_range and len(node_range) == 2:
lo, hi = float(node_range[0]), float(node_range[1])
bound = max(abs(lo), abs(hi))
if bound <= 10:
inputs[input_id] = round(max(lo, min(hi, raw / 100 * bound)), 4)
elif src in ("surprise_bps", "surprise"):
# Step 1: use stored surprise_pct
sp = event.get("surprise_pct")
# Step 2: compute from actual_value / expected_value
if sp is None:
try:
act_s = str(event.get("actual_value") or "").replace(",", ".").strip()
exp_s = str(event.get("expected_value") or "").replace(",", ".").strip()
if act_s and exp_s:
act_f = float(act_s)
exp_f = float(exp_s)
sp = (act_f - exp_f) / abs(exp_f) * 100 if abs(exp_f) > 1e-9 else (act_f - exp_f)
except (ValueError, TypeError):
pass
# Step 3: look up ff_calendar by currency + date
if sp is None:
try:
currency = (event.get("sub_type") or "").upper()
if currency:
row_ff = conn.execute("""
SELECT actual_value, forecast_value FROM ff_calendar
WHERE currency = ? AND event_date <= ? AND actual_value IS NOT NULL
AND forecast_value IS NOT NULL
ORDER BY event_date DESC LIMIT 1
""", (currency, edate_str)).fetchone()
if row_ff:
a_f = float(row_ff["actual_value"])
e_f = float(row_ff["forecast_value"])
sp = (a_f - e_f) / abs(e_f) * 100 if abs(e_f) > 1e-9 else (a_f - e_f)
except (ValueError, TypeError):
pass
if sp is not None:
raw = float(sp)
node_range = cfg.get("range")
if src == "surprise_bps":
if node_range and len(node_range) == 2:
lo, hi = float(node_range[0]), float(node_range[1])
inputs[input_id] = round(max(lo, min(hi, raw)), 4)
else:
inputs[input_id] = round(raw, 4)
else:
inputs[input_id] = round(max(lo, min(hi, raw)), 4)
else:
inputs[input_id] = round(raw / 100, 4)
if node_range and len(node_range) == 2:
lo, hi = float(node_range[0]), float(node_range[1])
bound = max(abs(lo), abs(hi))
if bound <= 10:
inputs[input_id] = round(max(lo, min(hi, raw / 100 * bound)), 4)
else:
inputs[input_id] = round(max(lo, min(hi, raw)), 4)
else:
inputs[input_id] = round(raw / 100, 4)
elif src == "impact_score_scaled" and event.get("impact_score") is not None:
inputs[input_id] = float(event["impact_score"]) / 10.0
@@ -1240,7 +1269,8 @@ def _run_auto_analysis(event: dict, template_id: int) -> bool:
logger.info(
f"[auto_analysis] event #{event_id} → tmpl #{template_id} | "
f"inputs={inputs} | instruments={all_instruments_csv} | actual_moves={actual_moves}"
f"surprise_pct={event.get('surprise_pct')} inputs={inputs} | "
f"nodes={len(node_values)} | instruments={all_instruments_csv} | actual_moves={actual_moves}"
)
existing = conn.execute(
@@ -1443,8 +1473,10 @@ def refresh_auto_analyses():
details.append(detail)
continue
ev = dict(ev_row)
detail["surprise_pct"] = ev.get("surprise_pct")
detail["start_date"] = ev.get("start_date")
detail["surprise_pct"] = ev.get("surprise_pct")
detail["actual_value"] = ev.get("actual_value")
detail["expected_value"] = ev.get("expected_value")
detail["start_date"] = ev.get("start_date")
ok = _run_auto_analysis(ev, row["template_id"])
detail["result"] = "ok" if ok else "failed"
if ok: