feat: instrument analysis

This commit is contained in:
OpenSquared
2026-07-02 14:21:57 +02:00
parent 0d5ad88b1c
commit 82739e8140

View File

@@ -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 (
<div
@@ -958,13 +965,13 @@ function CausalFrise({
: clsx('text-slate-500 border-slate-700/40 bg-slate-800/25',
'hover:text-slate-300 hover:border-slate-600/60 hover:bg-slate-800/50'),
)}
style={{ left: x1, top: chipY, width: w, height: FRISE_CHIP_H }}
style={{ left: x1, top: chipY, width: w, height: chipH }}
onMouseDown={e => 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`}`}
>
<span className={clsx('w-1.5 h-1.5 rounded-full bg-current shrink-0', !active && 'opacity-40')} />
{label && <span className="truncate">{label}</span>}