From 9f90c97461af33c0726939866f4198d5912b2ff6 Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Mon, 29 Jun 2026 11:05:25 +0200 Subject: [PATCH] feat: market event --- backend/routers/causal_lab.py | 22 ++++++++++++++++++---- frontend/src/pages/MarketEvents.tsx | 26 +++++++++++++++++++------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/backend/routers/causal_lab.py b/backend/routers/causal_lab.py index b0dbedb..24bec22 100644 --- a/backend/routers/causal_lab.py +++ b/backend/routers/causal_lab.py @@ -701,9 +701,12 @@ Nœuds à configurer (entrées manuelles) : Règles : - Surprise NÉGATIVE → valeurs négatives pour les nœuds de surprise - Surprise POSITIVE → valeurs positives -- Utilise les plages "range" si fournies (ex: [-3, 3] pour z-score) -- Magnitude proportionnelle à l'ampleur de la surprise -- Si surprise_pct disponible, utilise-la pour calibrer (ex: -29.1% → nœud_surprise ≈ -2.5) +- Si le nœud a un champ "range" (ex: [-3, 3]) : normalise OBLIGATOIREMENT la valeur dans ce range. + Formule : valeur = clamp(surprise_pct / 100 * abs(range_max), range_min, range_max) + Exemples : surprise_pct=-33.3%, range=[-3,3] → -0.333*3 = -1.0 | surprise_pct=-29.1%, range=[-3,3] → -0.873 + NE JAMAIS retourner la valeur brute surprise_pct (-33.3, -29.1…) — c'est une erreur de calibration. +- Si pas de "range" défini : retourne surprise_pct / 100 (forme fractionnelle, ex: -33.3% → -0.333) +- Magnitude proportionnelle à l'ampleur de la surprise dans la plage autorisée Retourne UNIQUEMENT un JSON : {{ "node_id": valeur_numerique, ... }} Inclure uniquement les nœuds listés ci-dessus.""" @@ -857,7 +860,18 @@ def analyze_event(body: AnalyzeRequest): # Sources legacy elif src == "surprise" and event.get("surprise_pct") is not None: - inputs[input_id] = float(event["surprise_pct"]) + 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: + # Plage type z-score : normalise surprise_pct → [-bound, +bound] + 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) # fractional par défaut elif src == "impact_score_scaled" and event.get("impact_score") is not None: inputs[input_id] = float(event["impact_score"]) / 10.0 elif src == "impact_score_pct" and event.get("impact_score") is not None: diff --git a/frontend/src/pages/MarketEvents.tsx b/frontend/src/pages/MarketEvents.tsx index 3fdd8bd..3dc8251 100644 --- a/frontend/src/pages/MarketEvents.tsx +++ b/frontend/src/pages/MarketEvents.tsx @@ -805,14 +805,26 @@ function EventDetail({ )} {Object.keys(instInputs).length > 0 && ( -
+
{Object.entries(instInputs).map(([k, v]) => ( - 0 ? 'text-emerald-400 border-emerald-800/50 bg-emerald-900/20' : v < 0 ? 'text-red-400 border-red-800/50 bg-red-900/20' : 'text-slate-500 border-slate-700/50' - )}> - {k}: {v > 0 ? '+' : ''}{v.toFixed(2)} - +
+ {k} + { + const n = parseFloat(e.target.value) + if (!isNaN(n)) setInstInputs(prev => ({ ...prev, [k]: n })) + }} + className={clsx( + 'w-20 text-xs font-mono text-right rounded px-1.5 py-0.5 border bg-dark-900 focus:outline-none focus:ring-1 focus:ring-blue-500/50', + v > 0 ? 'text-emerald-400 border-emerald-800/60' + : v < 0 ? 'text-red-400 border-red-800/60' + : 'text-slate-400 border-slate-700/60' + )} + /> +
))}
)}