diff --git a/frontend/src/pages/InstrumentDashboard.tsx b/frontend/src/pages/InstrumentDashboard.tsx index 1b45e73..0d57704 100644 --- a/frontend/src/pages/InstrumentDashboard.tsx +++ b/frontend/src/pages/InstrumentDashboard.tsx @@ -762,17 +762,49 @@ function CausalFrise({ } | null>(null) const [popupScore, setPopupScore] = useState('loading') - // Fetch activation_score live when popup opens for a specific event + // Fetch analysis live when popup opens — compute same precision as MarketEvents expanded view useEffect(() => { const evId = activeChip?.ev.id if (!evId) { setPopupScore(null); return } setPopupScore('loading') api.get(`/causal-lab/analyses?market_event_id=${evId}&limit=1`) .then(r => { - const score = r.data?.[0]?.activation_score - setPopupScore(score != null ? Math.round(score * 100) : null) + const analysis = r.data?.[0] + console.log('[CausalFrise] analysis for event', evId, analysis) + if (!analysis) { setPopupScore(null); return } + + // Compute magnitude-weighted precision (same formula as MarketEvents expanded panel) + const preds: Record = analysis.prediction_json || {} + const actuals: Record = analysis.actual_json || {} + const graph = analysis.graph_json || {} + const outputNodes: { id: string; instrument?: string }[] = (graph.nodes || []) + .filter((n: { type?: string }) => n.type === 'market_asset' || n.type === 'output') + + console.log('[CausalFrise] preds:', preds, 'actuals:', actuals, 'outputNodes:', outputNodes) + + const nodeScores = outputNodes.map((n: { id: string; instrument?: string }) => { + const pred = preds[n.id] + const act = actuals[n.instrument || n.id] ?? actuals[n.id] + if (pred == null || act == null) return null + if (pred === 0 && act === 0) return 100 + if (pred === 0) return 40 + const sameDir = pred * act > 0 + if (!sameDir) return 0 + const ratio = Math.min(Math.abs(act), Math.abs(pred)) / Math.max(Math.abs(act), Math.abs(pred)) + return Math.round(50 + ratio * 50) + }).filter((s): s is number => s != null) + + console.log('[CausalFrise] nodeScores:', nodeScores) + + if (nodeScores.length > 0) { + setPopupScore(Math.round(nodeScores.reduce((a, b) => a + b, 0) / nodeScores.length)) + } else { + // Fallback: use stored activation_score if no prediction data + const stored = analysis.activation_score + setPopupScore(stored != null ? Math.round(stored * 100) : null) + } }) - .catch(() => setPopupScore(null)) + .catch(err => { console.error('[CausalFrise] fetch error:', err); setPopupScore(null) }) }, [activeChip?.ev.id]) useEffect(() => {