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 (
{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 */}
{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 */}
setShowLog(!showLog)}>
{showLog ? '▲ Hide' : '▼ Show'} Raw Recon Log ({result.logs.length} entries)
{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.
)}
)
}