/* SINAIS FIFA — Análise H2H modal (idle → loading → analysis) */ const { useState: useS_M, useEffect: useE_M, useRef: useR_M } = React; const LOADING_STEPS = [ 'Conectando ao motor estatístico…', 'Lendo padrões ELO + λ Poisson dos jogadores…', 'Decifrando histórico de confronto direto…', 'Calculando edge e odds justas…', 'Validando análise para entrega…', ]; const SAMPLE_SIZES = [5, 10, 15, 20]; const FormChip = ({ result }) => ( {result} ); const SignalModal = ({ match, onClose }) => { const [phase, setPhase] = useS_M('idle'); // idle | loading | analysis | error const [step, setStep] = useS_M(0); const [sample, setSample] = useS_M(10); const [analysis, setAnalysis] = useS_M(null); const [errMsg, setErrMsg] = useS_M(''); const stepRef = useR_M(null); useE_M(() => () => { if (stepRef.current) clearInterval(stepRef.current); }, []); const generate = async () => { setPhase('loading'); setStep(0); setErrMsg(''); setAnalysis(null); // animação dos passos roda enquanto a API responde stepRef.current = setInterval(() => { setStep(s => Math.min(s + 1, LOADING_STEPS.length - 1)); }, 380); try { const res = await fetch(`/api/analysis/${match.id}?sample=${sample}`); if (!res.ok) { const detail = await res.json().catch(() => ({})); throw new Error(detail.detail || `HTTP ${res.status}`); } const data = await res.json(); // garante o tempo mínimo da animação (≈1.5s) pra não piscar const elapsed = (LOADING_STEPS.length - 1) * 380; const wait = Math.max(0, 1500 - elapsed); setTimeout(() => { clearInterval(stepRef.current); setStep(LOADING_STEPS.length - 1); setAnalysis(data); setPhase('analysis'); }, wait); } catch (e) { clearInterval(stepRef.current); setErrMsg(e.message || 'falha ao gerar análise'); setPhase('error'); } }; // URL real do jogo (do banco) > fallback do AFFIL const gameUrl = (analysis && analysis.url) || match.url || `${AFFIL.gameUrlBase}${match.id}`; // streak: a API retorna side='home'|'away'; resolvemos pro objeto match const streakObj = analysis && analysis.streak ? (analysis.streak.side === 'home' ? match.home : match.away) : null; return (
e.stopPropagation()}>

Análise H2H · {match.home.handle} vs {match.away.handle}

{match.league} · kickoff {match.t} BRT

{phase === 'idle' && (
{match.home.handle.slice(0,2).toUpperCase()}
{match.home.handle}
{match.home.team}
VS
{match.away.handle.slice(0,2).toUpperCase()}
{match.away.handle}
{match.away.team}
Base de análise · últimos N jogos
{SAMPLE_SIZES.map(n => ( ))}
EV estimado +{(match.ev || 0).toFixed(1)}% · Conf {match.conf}%
)} {phase === 'loading' && (
{LOADING_STEPS.map((s, i) => (
{s}
))}
)} {phase === 'error' && (
⚠️
Falha ao gerar análise
{errMsg}
)} {phase === 'analysis' && analysis && ( <> {/* Sample size pill */}
base · últimos {sample} jogos · conf {analysis.confidence}%
{/* Recent form — both players */}
{match.home.handle} · forma recente
{analysis.recentHome.length === 0 && sem histórico} {analysis.recentHome.map((r, i) => )}
{match.away.handle} · forma recente
{analysis.recentAway.length === 0 && sem histórico} {analysis.recentAway.map((r, i) => )}
{/* H2H direct + ELO */}
H2H direto · {analysis.h2h.n} confrontos
{analysis.h2h.n === 0 ? (
sem confronto direto registrado
) : ( <>
{analysis.h2h.w}V {analysis.h2h.d}E {analysis.h2h.l}D
)}
ELO · momentum
{analysis.eloHome}
{match.home.handle}
= 0 ? 'up' : 'down'}`}> {analysis.momentum >= 0 ? '↑' : '↓'} {Math.abs(analysis.momentum)}
{analysis.eloAway}
{match.away.handle}
{/* Stats grid */}
Gols esperados (λ)
{analysis.avgGoals}
BTTS prob
{analysis.btts}%
Over 2.5 hits
{analysis.overHits}/{analysis.overTotal}
WR casa · fora
{analysis.homeWR}% · {analysis.awayWR}%
Streak ativa
{analysis.streak.n > 0 ? ( <> {analysis.streak.n}{analysis.streak.type} {streakObj?.handle} ) : }
{/* Recommended picks (ranked) */}
Picks recomendados ranqueados por edge — motor estatístico real
{analysis.picks.length === 0 && (
nenhum pick com edge positivo
)} {analysis.picks.map((p, i) => (
#{i+1}
{p.type}
odd mín. {p.minOdd} · justa {p.fairOdd} {p.marketOdd && <> · GoldeBet {p.marketOdd}} {' '}· prob {p.prob}%
= 6 ? 'hot' : ''}`}> +{p.edge}%
))}
Ir para o jogo → )}
); }; window.SignalModal = SignalModal;