import { useState, useEffect } from 'react' import { Play, CheckSquare, Clock, AlertCircle, ChevronDown, ChevronUp, Loader2, Database } from 'lucide-react' const API = '' interface ActionDef { id: string label: string description: string status: 'idle' | 'running' implemented: boolean group: string } interface CreatedEvent { name: string category: string date: string source: string } interface ActionResult { action: string status: string started_at: string finished_at: string duration_s: number total_created: number detail: { news: CreatedEvent[] eco: CreatedEvent[] technical: CreatedEvent[] reports: CreatedEvent[] } } const SOURCE_LABELS: Record = { news: 'News', eco: 'Éco. Calendar', technical: 'Technique', reports: 'Reports', } const CAT_COLORS: Record = { event_calendar: 'text-amber-400', geopolitical: 'text-red-400', fundamental: 'text-emerald-400', report: 'text-blue-400', sentiment: 'text-purple-400', technical: 'text-cyan-400', } // ── Simple bootstrap action button ─────────────────────────────────────────── function BootstrapAction({ action }: { action: ActionDef }) { const [running, setRunning] = useState(false) const [result, setResult] = useState<{ inserted?: number; updated?: number; skipped?: number } | null>(null) const [error, setError] = useState(null) const [force, setForce] = useState(false) const run = async () => { setRunning(true) setResult(null) setError(null) try { const res = await fetch(`${API}/api/actions/${action.id}?force=${force}`, { method: 'POST' }) if (!res.ok) { const err = await res.json().catch(() => ({ detail: res.statusText })) throw new Error(err.detail || res.statusText) } const data = await res.json() setResult(data) } catch (e: any) { setError(e.message) } finally { setRunning(false) } } return (
{action.label}
{action.description}
{error && (
{error}
)} {result && !error && (
✓ Terminé — {result.inserted != null && ` ${result.inserted} insérés`} {result.updated != null && ` · ${result.updated} mis à jour`} {result.skipped != null && ` · ${result.skipped} ignorés`}
)}
) } // ── Check Market Events config panel ──────────────────────────────────────── interface CheckEventsConfig { sources: string[] news_impact_min: number eco_z_threshold: number technical_lookback_days: number report_days: number report_min_importance: number date_from: string date_to: string } const SCAN_PRESETS = [ { label: '6h', hours: 6 }, { label: '12h', hours: 12 }, { label: '24h', hours: 24 }, { label: '48h', hours: 48 }, { label: '72h', hours: 72 }, { label: '7j', hours: 168 }, ] function presetDates(hours: number): { date_from: string; date_to: string } { const now = new Date() const from = new Date(now.getTime() - hours * 3_600_000) const fmt = (d: Date) => d.toISOString().slice(0, 10) return { date_from: fmt(from), date_to: fmt(now) } } function makeDefaultConfig(): CheckEventsConfig { return { sources: ['news', 'eco', 'technical', 'reports'], news_impact_min: 0.55, eco_z_threshold: 1.5, technical_lookback_days: 7, report_days: 7, report_min_importance: 3, ...presetDates(48), } } function CheckEventsPanel() { const [config, setConfig] = useState(makeDefaultConfig) const [running, setRunning] = useState(false) const [result, setResult] = useState(null) const [error, setError] = useState(null) const [expanded, setExpanded] = useState(false) const [showConfig, setShowConfig] = useState(false) const toggleSource = (src: string) => { setConfig(c => ({ ...c, sources: c.sources.includes(src) ? c.sources.filter(s => s !== src) : [...c.sources, src], })) } const run = async () => { setRunning(true) setResult(null) setError(null) try { const res = await fetch(`${API}/api/actions/check-market-events`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config), }) if (!res.ok) { const err = await res.json().catch(() => ({ detail: res.statusText })) throw new Error(err.detail || res.statusText) } const data: ActionResult = await res.json() setResult(data) setExpanded(true) } catch (e: any) { setError(e.message) } finally { setRunning(false) } } const allEvents = result ? [...(result.detail.news || []), ...(result.detail.eco || []), ...(result.detail.technical || []), ...(result.detail.reports || [])] : [] return (
Check New Market Events Scans news · FRED · MA crossovers · rapports institutionnels
{showConfig && (
{/* Fenêtre de scan */}
Fenêtre de scan
{SCAN_PRESETS.map(p => { const { date_from, date_to } = presetDates(p.hours) const active = config.date_from === date_from && config.date_to === date_to return ( ) })}
setConfig(c => ({ ...c, date_from: e.target.value }))} className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-xs flex-1" /> setConfig(c => ({ ...c, date_to: e.target.value }))} className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-xs flex-1" />
{/* Sources */}
Sources actives
{['news', 'eco', 'technical', 'reports'].map(s => ( ))}
)} {error && (
{error}
)} {result && (
setExpanded(x => !x)} > {result.total_created} événement{result.total_created !== 1 ? 's' : ''} créé{result.total_created !== 1 ? 's' : ''}
{(['news', 'eco', 'technical', 'reports'] as const).map(src => { const count = result.detail[src]?.length ?? 0 return count > 0 ? ( {SOURCE_LABELS[src]}: {count} ) : null })}
{result.duration_s}s {expanded ? : }
{expanded && allEvents.length > 0 && (
{allEvents.map((ev, i) => (
{ev.date} {ev.category?.replace('_', ' ')} {ev.name} {SOURCE_LABELS[ev.source] ?? ev.source}
))}
)} {expanded && allEvents.length === 0 && (
Aucun nouvel événement — tout était déjà enregistré ou sous le seuil.
)}
)}
) } // ── Planned action card ─────────────────────────────────────────────────────── function PlannedActionCard({ action }: { action: ActionDef }) { return (
{action.label}
{action.description}
Planifié
) } // ── Main page ───────────────────────────────────────────────────────────────── const GROUP_LABELS: Record = { detection: 'Détection', bootstrap: 'Bootstrap (données initiales)', data: 'Données & Indicateurs', ai: 'Analyse IA', portfolio: 'Portfolio & Rapport', } export default function CycleActions() { const [actions, setActions] = useState([]) useEffect(() => { fetch(`${API}/api/actions`) .then(r => r.json()) .then(d => setActions(d.actions || [])) .catch(() => {}) }, []) const groups = ['detection', 'bootstrap', 'data', 'ai', 'portfolio'] return (

Actions Cycle

Décomposition du cycle en actions isolées — lancez-les indépendamment pour tester ou rejouer une étape. Les bootstraps auto au démarrage ont été désactivés.

{groups.map(group => { const groupActions = actions.filter(a => a.group === group) if (groupActions.length === 0) return null const hasAny = groupActions.some(a => a.implemented) return (
{GROUP_LABELS[group] ?? group}
{groupActions.map(action => { if (!action.implemented) return if (action.id === 'check-market-events') return return })}
) })}
) }