From 82739e8140f6715f9454712bfd00c3b478b57efe Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Thu, 2 Jul 2026 14:21:57 +0200 Subject: [PATCH] feat: instrument analysis --- frontend/src/pages/InstrumentDashboard.tsx | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/frontend/src/pages/InstrumentDashboard.tsx b/frontend/src/pages/InstrumentDashboard.tsx index 96e728e..307da86 100644 --- a/frontend/src/pages/InstrumentDashboard.tsx +++ b/frontend/src/pages/InstrumentDashboard.tsx @@ -22,6 +22,9 @@ interface CausalTemplate { nodes: { id: string; type: string; label: string; instrument?: string }[] edges: { from: string; to: string; lag_days?: number; lag_min?: number; sign?: string }[] } + calibration_json: { + absorption_days?: number; lag_days?: number; half_life_days?: number; decay_type?: string + } } interface InstrumentConfig { @@ -699,6 +702,8 @@ function makeTdToX(priceData: PriceCandle[], width: number): (d: string) => numb // ── Comprehension scoring ───────────────────────────────────────────────────── function templateAbsorptionDays(tmpl: CausalTemplate): number { + const fromCalib = tmpl.calibration_json?.absorption_days + if (fromCalib && fromCalib > 0) return fromCalib const maxLag = Math.max(0, ...tmpl.graph_json.edges.map(e => e.lag_days || 0)) return maxLag > 0 ? maxLag : 30 } @@ -863,7 +868,7 @@ function CausalFrise({ // Build chips (one per event × template) type Chip = { ev: SnapshotEvent; tmpl: CausalTemplate - x1: number; x2: number; w: number; active: boolean + x1: number; x2: number; w: number; active: boolean; absorptionDays: number } const chips: Chip[] = linked .filter(ev => ev.date <= maxDate) @@ -878,7 +883,7 @@ function CausalFrise({ const x1 = tdToX(ev.date) const raw = tdToX(endDate) - x1 const w = Math.max(raw, FRISE_MIN_W) - return { ev, tmpl, x1, x2: x1 + w, w, active: isActiveAt(ev, selectedDate) } + return { ev, tmpl, x1, x2: x1 + w, w, active: isActiveAt(ev, selectedDate), absorptionDays } }) .filter((c): c is Chip => c !== null) .sort((a, b) => a.x1 - b.x1) @@ -939,13 +944,15 @@ function CausalFrise({ )} {/* Chips */} - {placed.map(({ ev, tmpl, x1, w, lane, active }) => { + {placed.map(({ ev, tmpl, x1, w, lane, active, absorptionDays }) => { const catTw = EV_CAT_TW[ev.category] ?? 'text-slate-400 border-slate-700/30 bg-slate-800/40' - const chipY = lane * FRISE_LANE_H + FRISE_CHIP_PAD + // Height scales with absorption: 13px (short) → 20px (30+ days), centred in lane + const chipH = Math.max(13, Math.min(20, Math.round(13 + Math.min(absorptionDays, 30) / 30 * 7))) + const chipY = lane * FRISE_LANE_H + Math.floor((FRISE_LANE_H - chipH) / 2) const isOpen = activeChip?.ev.id === ev.id && activeChip?.tmpl.id === tmpl.id const charsFit = Math.floor((w - 18) / 5.5) - const label = charsFit < 3 ? '' : tmpl.name.length > charsFit - ? tmpl.name.slice(0, charsFit - 1) + '…' : tmpl.name + const label = charsFit < 3 ? '' : ev.title.length > charsFit + ? ev.title.slice(0, charsFit - 1) + '…' : ev.title return (
e.stopPropagation()} onClick={e => { e.stopPropagation() setActiveChip(isOpen ? null : { tmpl, ev, chipX: x1, chipY }) }} - title={`${tmpl.name} · ${ev.title}\n${fmtDateFR(ev.date)} → ${ev.end_date ? fmtDateFR(ev.end_date) : '+30j'}`} + title={`${ev.title} [${tmpl.name}]\n${fmtDateFR(ev.date)} → ${ev.end_date ? fmtDateFR(ev.end_date) : `+${absorptionDays}j`}`} > {label && {label}}