import { useState } from 'react' import { useQuery } from '@tanstack/react-query' import clsx from 'clsx' import { Brain, TrendingUp, TrendingDown, RefreshCw, ShieldAlert, GitCompare, Layers, Zap, BookOpen, Clock, ChevronRight, CheckCircle2, AlertTriangle, XCircle, BarChart2, FlaskConical, } from 'lucide-react' const SCENARIO_META: Record = { goldilocks: { label: 'Goldilocks', color: '#22c55e', emoji: '🌟' }, desinflation: { label: 'Disinflation', color: '#06b6d4', emoji: '❄️' }, soft_landing: { label: 'Soft Landing', color: '#3b82f6', emoji: '🛬' }, reflation: { label: 'Reflation', color: '#f97316', emoji: '🔥' }, stagflation: { label: 'Stagflation', color: '#eab308', emoji: '⚡' }, inflation_shock: { label: 'Inflation Shock', color: '#ef4444', emoji: '💥' }, recession: { label: 'Recession', color: '#8b5cf6', emoji: '📉' }, crise_liquidite: { label: 'Liquidity Crisis', color: '#ec4899', emoji: '🚨' }, incertain: { label: 'Uncertain', color: '#64748b', emoji: '❓' }, } function RegimeBadge({ dominant }: { dominant?: string }) { if (!dominant) return null const m = SCENARIO_META[dominant] ?? SCENARIO_META.incertain return ( {m.emoji} {m.label} ) } function Section({ icon, title, children }: { icon: React.ReactNode; title: string; children: React.ReactNode }) { return (
{icon} {title}
{children}
) } const VERDICT_META = { OK: { color: 'text-emerald-400', bg: 'bg-emerald-900/10 border-emerald-700/30', icon: CheckCircle2 }, WARN: { color: 'text-yellow-400', bg: 'bg-yellow-900/10 border-yellow-700/30', icon: AlertTriangle }, ALERT: { color: 'text-red-400', bg: 'bg-red-900/10 border-red-700/30', icon: XCircle }, } function VerdictBadge({ verdict }: { verdict: 'OK' | 'WARN' | 'ALERT' }) { const m = VERDICT_META[verdict] ?? VERDICT_META.WARN const Icon = m.icon return ( {verdict} ) } function IVBar({ ivRank }: { ivRank?: number | null }) { if (ivRank == null) return IVR — const color = ivRank >= 60 ? 'bg-red-500' : ivRank >= 30 ? 'bg-yellow-500' : 'bg-emerald-500' return (
= 60 ? 'text-red-400' : ivRank >= 30 ? 'text-yellow-400' : 'text-emerald-400')}> IVR {ivRank.toFixed(0)}%
) } function DeltaRow({ emoji, label, count, items, colorClass }: { emoji: string; label: string; count: number; items: any[]; colorClass: string; }) { const [expanded, setExpanded] = useState(false) return (
{expanded && items.length > 0 && (
{items.map((item: any, i: number) => (
{item.name ?? item.underlying ?? '?'} {item.underlying && item.strategy ? ` · ${item.strategy}` : ''} {item.description && (
{item.description}
)} {item.triggers?.length > 0 && (
Triggers: {item.triggers.join(', ')}
)} {item.pnl_pct != null && ( = 0 ? 'text-emerald-400' : 'text-red-400')}> {item.pnl_pct >= 0 ? '+' : ''}{item.pnl_pct.toFixed(1)}% )} {item.macro_fit && (
{item.macro_fit}
)}
))}
)}
) } export default function RapportCycle() { const [selectedRunId, setSelectedRunId] = useState(null) const { data: listData, isLoading: listLoading } = useQuery({ queryKey: ['cycle-reports-list'], queryFn: () => fetch('/api/reports/cycle/list?limit=20').then(r => r.ok ? r.json() : { reports: [] }), staleTime: 30_000, }) const { data: latestReport, isLoading: latestLoading } = useQuery({ queryKey: ['cycle-report-latest'], queryFn: () => fetch('/api/reports/cycle/latest').then(r => r.ok ? r.json() : { report: null }), staleTime: 30_000, }) const { data: selectedReport, isLoading: selectedLoading } = useQuery({ queryKey: ['cycle-report', selectedRunId], queryFn: () => fetch(`/api/reports/cycle/${selectedRunId}`).then(r => r.ok ? r.json() : null), enabled: !!selectedRunId, staleTime: 60_000, }) const reports: any[] = listData?.reports ?? [] const report: any = selectedRunId ? selectedReport : latestReport?.report const isLoading = selectedRunId ? selectedLoading : latestLoading return (
{/* Left panel — history list */}
History
{listLoading ? (
Loading…
) : reports.length === 0 ? (
No reports. The first will be generated at the next cycle.
) : (
{reports.map((r: any) => { const dt = r.generated_at ? new Date(r.generated_at.endsWith('Z') ? r.generated_at : r.generated_at + 'Z') : null const dateStr = dt ? dt.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '—' const isActive = selectedRunId === r.run_id || (!selectedRunId && r === reports[0]) const m = SCENARIO_META[r.macro_dominant] ?? SCENARIO_META.incertain return ( ) })}
)}
{/* Right panel — report */}
{isLoading ? (
Loading…
) : !report ? (
No cycle report available
Reports are generated automatically at the end of each cycle.
) : ( <> {/* Header */}
Geo {report.geo_score ?? '—'}/100
Cycle of {report.generated_at ? new Date(report.generated_at.endsWith('Z') ? report.generated_at : report.generated_at + 'Z') .toLocaleString('en-GB') : '—'} {report.prev_cycle_at && ( · vs cycle of {new Date(report.prev_cycle_at.endsWith('Z') ? report.prev_cycle_at : report.prev_cycle_at + 'Z') .toLocaleDateString('en-GB')} )}
{/* Macro scores mini */} {report.macro_scores && (
{Object.entries(report.macro_scores as Record) .sort(([, a], [, b]) => b - a) .slice(0, 4) .map(([k, v]: [string, number]) => { const m = SCENARIO_META[k] ?? SCENARIO_META.incertain return ( {m.label} {v}% ) })}
)}
{/* PnL + VaR summary row */}
{report.pnl_summary && Object.keys(report.pnl_summary).length > 0 && (
PnL at cycle time
= 0 ? 'text-emerald-400' : 'text-red-400')}> {report.pnl_summary.total_pnl_pct != null ? `${report.pnl_summary.total_pnl_pct >= 0 ? '+' : ''}${report.pnl_summary.total_pnl_pct.toFixed(2)}%` : '—'} {report.pnl_summary.total_pnl_eur != null && ( {report.pnl_summary.total_pnl_eur >= 0 ? '+' : ''} {report.pnl_summary.total_pnl_eur.toFixed(0)}€ )}
{report.pnl_summary.n_open ?? 0} open · {report.pnl_summary.n_closed ?? 0} closed
)} {report.var_summary && Object.keys(report.var_summary).length > 0 && (
VaR 95% at cycle time
{[ { label: 'Hist.', val: report.var_summary.hist_var_1d_pct, color: 'text-blue-400' }, { label: 'CVaR', val: report.var_summary.hist_cvar_pct, color: 'text-orange-400' }, { label: 'MC', val: report.var_summary.mc_var_1d_pct, color: 'text-red-400' }, ].map(({ label, val, color }) => (
{label}
{val != null ? `${val >= 0 ? '+' : ''}${val.toFixed(2)}%` : '—'}
))}
)}
{/* Delta section */}
} title="Cycle changes"> ({ underlying: t.underlying, strategy: t.strategy, pnl_pct: t.pnl_pct, description: t.pattern_name }))} colorClass="text-emerald-400" /> ({ underlying: t.underlying, strategy: t.strategy, pnl_pct: t.pnl_pct, description: t.pattern_name }))} colorClass={report.trades_closed > 0 ? 'text-slate-300' : 'text-slate-600'} /> {/* Top scored */} {(report.top_scored ?? []).length > 0 && (
Top scored patterns this cycle
{(report.top_scored as any[]).map((p: any, i: number) => (
{i + 1}
{p.name ?? '—'} {p.key_catalyst && (
{p.key_catalyst}
)}
= 60 ? 'text-emerald-400' : (p.score ?? 0) >= 40 ? 'text-yellow-400' : 'text-slate-500')}> {p.score ?? '—'}
))}
)}
{/* Context narrative */} {report.context_narrative?.narrative && (
} title="AI reasoning — this cycle">

{report.context_narrative.narrative}

{(report.context_narrative.context_driven ?? []).length > 0 && (
📡 From transmitted context
    {(report.context_narrative.context_driven as string[]).map((s, i) => (
  • {s}
  • ))}
)} {(report.context_narrative.general_knowledge ?? []).length > 0 && (
🧠 General knowledge
    {(report.context_narrative.general_knowledge as string[]).map((s, i) => (
  • {s}
  • ))}
)}
{(report.context_narrative.key_signals ?? []).length > 0 && (
Determining signals
{(report.context_narrative.key_signals as string[]).map((s, i) => ( {s} ))}
)} {/* Context log */} {report.context_narrative.context_log && (
Context log — state transmitted to AI
{Object.entries(report.context_narrative.context_log.key_gauges ?? {}).map(([k, v]: [string, any]) => (
{k} {v}
))}
{(report.context_narrative.context_log.dominant_news ?? []).length > 0 && (
Transmitted news (top impact)
{(report.context_narrative.context_log.dominant_news as string[]).map((n, i) => (
· {n}
))}
)}
)}
)} {/* Cycle commentary */} {report.commentary?.commentary && (
} title="Cycle macro analysis">

{report.commentary.commentary}

{report.commentary.key_risk && (
{report.commentary.key_risk}
)} {report.commentary.top_pattern && (
Most relevant pattern: {report.commentary.top_pattern}
)}
)} {/* Options Technical Agent */} {report.options_technical && (report.options_technical.assessments?.length > 0 || report.options_technical.global_assessment) && (
} title="Options Technical Validation"> {/* Global score + summary */}
{report.options_technical.global_score != null && (
= 70 ? 'text-emerald-400' : report.options_technical.global_score >= 45 ? 'text-yellow-400' : 'text-red-400' )}> {report.options_technical.global_score}
Score vol
)}
{report.options_technical.global_assessment && (

{report.options_technical.global_assessment}

)}
{report.options_technical.n_ok > 0 && ( {report.options_technical.n_ok} OK )} {report.options_technical.n_warn > 0 && ( {report.options_technical.n_warn} WARN )} {report.options_technical.n_alert > 0 && ( {report.options_technical.n_alert} ALERT )}
{/* Per-trade assessments */}
{(report.options_technical.assessments as any[]).map((a: any, i: number) => (
{/* Trade header */}
{a.ticker} {a.strategy}
{/* IV data row */}
{a.skew_pct != null && ( Skew: 5 ? 'text-orange-400' : 'text-slate-400')}> {a.skew_pct > 0 ? '+' : ''}{a.skew_pct.toFixed(1)}pts )} {a.term_structure && ( Structure: {a.term_structure} )} {a.flow_bias && ( Flow: {a.flow_bias} )} Score: {a.fit_score}
{/* Issues + positives */} {((a.issues ?? []).length > 0 || (a.positives ?? []).length > 0) && (
{(a.issues as string[]).map((issue: string, j: number) => (
{issue}
))} {(a.positives as string[]).map((pos: string, j: number) => (
{pos}
))}
)} {/* GPT-4o analysis */} {a.analysis && (

{a.analysis}

)} {/* Optimal strategy + when to enter */}
{a.optimal_strategy && a.optimal_strategy !== a.strategy && ( Optimal strategy: {a.optimal_strategy} )} {a.when_to_enter && ( {a.when_to_enter} )}
))}
)} {/* Calibration report */} {report.calibration_report && (report.calibration_report as any).total_patterns > 0 && (() => { const cr = report.calibration_report as any const pct = cr.avg_calibration_weight_pct ?? 0 return (
} title="Calibration expected_move — IA vs observé"> {/* Global bar */}
IA pure ({cr.pure_ai_count} patterns) {pct.toFixed(1)}% poids observé moyen Data-driven ({cr.data_driven_count})
{[ { label: 'Pure AI', count: cr.pure_ai_count, cls: 'text-slate-400' }, { label: 'Early', count: cr.early_count, cls: 'text-sky-400' }, { label: 'Mixed', count: cr.mixed_count, cls: 'text-violet-400' }, { label: 'Data', count: cr.data_driven_count, cls: 'text-emerald-400' }, ].map(({ label, count, cls }) => (
{count}
{label}
))}
{/* Per-pattern detail */} {(cr.detail ?? []).length > 0 && (
Patterns avec données ({cr.patterns_with_data})
{(cr.detail as any[]).map((d: any, i: number) => (
{d.weight_pct?.toFixed(0)}% {d.name} {d.n_trades}t · {d.win_rate_pct}% WR AI:{d.ai_estimate?.toFixed(0)}% {d.calibrated != null && ( →{d.calibrated?.toFixed(0)}% )}
))}
)}
) })()} {/* Risk / portfolio monitor */} {report.portfolio_monitor && (
} title="Risk & Portfolio alerts">

{report.portfolio_monitor.assessment}

{(report.portfolio_monitor.actions ?? []).length > 0 && (
{(report.portfolio_monitor.actions as any[]).map((a: any, i: number) => (
{a.priority === 'high' ? '⚠️' : '👁'} {a.underlying && {a.underlying} }{a.reason}
))}
)} {report.portfolio_monitor.rebalance_suggestion && (
💡 {report.portfolio_monitor.rebalance_suggestion}
)}
)} )}
) }