feat: market event
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -805,14 +805,26 @@ function EventDetail({
|
||||
)}
|
||||
</div>
|
||||
{Object.keys(instInputs).length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<div className="space-y-1">
|
||||
{Object.entries(instInputs).map(([k, v]) => (
|
||||
<span key={k} className={clsx(
|
||||
'text-xs font-mono px-1.5 py-0.5 rounded border',
|
||||
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)}
|
||||
</span>
|
||||
<div key={k} className="flex items-center gap-1.5">
|
||||
<span className="text-xs font-mono text-slate-500 flex-1 truncate" title={k}>{k}</span>
|
||||
<input
|
||||
type="number"
|
||||
step="any"
|
||||
value={v}
|
||||
onChange={e => {
|
||||
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'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user