import { useState } from 'react' import { motion } from 'framer-motion' import { useUsageLimiter } from '../hooks/useUsageLimiter' import AccessModal from '../components/AccessModal' const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' interface ScoutResult { player: string durability: number recency: number agent: number logs: string[] from_cache: boolean nlp_found?: boolean } function ScoreCard({ label, value, icon, desc, found }: { label: string; value: number; icon: string; desc: string; found?: boolean }) { const norm = (value + 1) / 2 // –1..+1 → 0..1 const pct = Math.round(norm * 100) const color = value > 0.1 ? 'var(--profit-color)' : value < -0.1 ? 'var(--loss-color)' : 'var(--accent-blue)' const label2 = value > 0.15 ? 'Positive' : value < -0.15 ? 'Negative' : 'Neutral' const badge = value > 0.15 ? 'badge-green' : value < -0.15 ? 'badge-red' : 'badge-blue' return (
{icon}

{label}

{desc}

{value === 0 ? (found ? '0.00' : 'N/A') : (value > 0 ? '+' : '') + value.toFixed(2)}
{value === 0 ? (found ? 'Neutral' : 'No Data') : label2}
{/* Bar */}
{/* Neutral marker at 50% */}
= 0 ? '50%' : `${pct}%`, width: `${Math.abs(value) * 50}%`, height:'100%', borderRadius:4, background: color, transition:'all 0.5s cubic-bezier(0.4,0,0.2,1)', }}/>
−1.0 Negative +1.0 Positive
) } function HypeFactorDisplay({ durability, recency, agent }: { durability: number; recency: number; agent: number }) { // Mirror the tier logic from api/main.py but use a generic baseline const dur_adj = Math.min(0.0, durability) * 0.15 const rec_adj = Math.max(0.0, recency) * 0.25 // Elite ceiling assumed const agt_adj = Math.min(0.0, agent) * 0.05 const mult = 1.0 + rec_adj + dur_adj + agt_adj const pct = ((mult - 1) * 100).toFixed(1) const isPos = mult >= 1 return (
🔥 Derived Hype Factor

NLP multiplier applied to the ML baseline in the Transfer Estimator. Computed from weighted sentiment across all three axes.

×{mult.toFixed(3)}
{isPos ? `+${pct}% sentiment premium` : `${pct}% sentiment discount`}
) } export default function Intel() { const { isLocked, incrementUsage } = useUsageLimiter() const [player, setPlayer] = useState('') const [club, setClub] = useState('') const [result, setResult] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [showLog, setShowLog] = useState(false) const handleFetch = async () => { if (!incrementUsage()) return; if (!player.trim()) { setError('Enter a player name first.'); return } setLoading(true); setError(null); setResult(null) try { const params = new URLSearchParams({ player: player.trim(), club: club.trim() }) const res = await fetch(`${API_URL}/api/scout?${params}`) if (!res.ok) { const err = await res.json().catch(() => ({})) throw new Error(err.detail || `API error ${res.status}`) } setResult(await res.json()) } catch (e) { setError(e instanceof Error ? e.message : 'Failed to fetch intel.') } finally { setLoading(false) } } return (
{isLocked && } {/* Header */}
Live Intelligence

🔍 Live Player Intel

Scrape real-time news across three NLP axes to compute the market sentiment multiplier before bidding.

{/* Search card */}
setPlayer(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleFetch()}/>
setClub(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleFetch()}/>
{error &&
{error}
}

ℹ️ First request per player takes 15–30 seconds (live DDGS scrape). Subsequent requests within 1 hour are instant (cached).

{/* Loading */} {loading && (

Scraping live transfer news, injury reports, and agent whispers…

)} {/* Results */} {result && !loading && (
{/* Cache badge */}

{result.player}

{result.from_cache ? '⚡ Cached (< 1hr)' : '🔴 Live Scraped'}
{/* Hype Factor */} {/* Three score cards */}
{/* Methodology note */}

Methodology

Sentiment is derived from TextBlob polarity analysis applied to DDGS (DuckDuckGo) search snippets scraped in real time. Scores range from −1.0 (very negative) to +1.0 (very positive). The Hype Factor multiplier is then applied to the XGBoost ML baseline in the Transfer Estimator to compute the PSR hard cap.

{/* Recon log */}
{showLog && (
{result.logs.length === 0 ? No log entries. : result.logs.map((l, i) => (
{l}
))}
)}
)} {/* Empty state */} {!result && !loading && !error && (
🔍

Enter a player name above and click Fetch Intel.

)}
) }