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

@@ -1028,7 +1028,7 @@ function ExplanationScore({
// For each linked event, compute the comprehension score and collect scored ones
const scored: number[] = []
const totalLinked: number[] = []
const debugRows: {id?: number; name: string; pred: string; actual: string; score: number | null; reason: string}[] = []
const debugRows: {id?: number; name: string; pred: string; actual: string; score: number | null; reason: string; surprisePct?: number | null}[] = []
for (const ev of events) {
if (!ev.template_id) continue
@@ -1062,6 +1062,7 @@ function ExplanationScore({
pred: hasPred ? '✓' : '✗',
actual: hasActual ? '✓' : '✗',
score: null, reason,
surprisePct: ev.surprise_pct ?? null,
})
const s = comprehensionScore(ev, tmpl, inst)
@@ -1136,9 +1137,12 @@ function ExplanationScore({
<div key={i} className="flex gap-2 py-0.5 border-b border-slate-800/40 last:border-0">
<span className={clsx('shrink-0 w-4', r.pred === '✓' ? 'text-emerald-600' : 'text-red-600')}>{r.pred}</span>
<span className={clsx('shrink-0 w-4', r.actual === '✓' ? 'text-emerald-600' : 'text-red-600')}>{r.actual}</span>
<span className="text-slate-400 w-36 shrink-0 truncate" title={r.name}>{r.name}</span>
<span className="text-slate-400 w-32 shrink-0 truncate" title={r.name}>{r.name}</span>
<span className={clsx('flex-1', r.reason === 'ok' ? 'text-emerald-600' : 'text-amber-600')}>{r.reason}</span>
{r.score != null && <span className="text-violet-400">{r.score}%</span>}
{r.surprisePct != null
? <span className="text-cyan-600 shrink-0">{r.surprisePct > 0 ? '+' : ''}{r.surprisePct.toFixed(1)}%</span>
: <span className="text-red-800 shrink-0">surp?</span>}
{r.score != null && <span className="text-violet-400 shrink-0">{r.score}%</span>}
</div>
))}
{debugInfo && (
@@ -1149,8 +1153,12 @@ function ExplanationScore({
}
{debugInfo.details?.map((d: any, i: number) => (
<div key={i} className="mt-0.5">
#{d.event_id} {(d.event_name ?? '').slice(0, 25)} <span className={d.result === 'ok' ? 'text-emerald-600' : 'text-red-500'}>{d.result}</span>
{d.surprise_pct != null && <span className="text-slate-500 ml-1">surp:{d.surprise_pct}</span>}
#{d.event_id} {(d.event_name ?? '').slice(0, 22)} <span className={d.result === 'ok' ? 'text-emerald-600' : 'text-red-500'}>{d.result}</span>
{d.surprise_pct != null
? <span className="text-cyan-700 ml-1">surp:{Number(d.surprise_pct).toFixed(1)}%</span>
: d.actual_value != null
? <span className="text-amber-700 ml-1">act:{d.actual_value} exp:{d.expected_value ?? '?'}</span>
: <span className="text-red-800 ml-1">surp=null</span>}
</div>
))}
</div>