Major additions: - Community Next.js app (port 18187) for browsing claims with API docs - stemedb-chaos crate: Fault injection, chaos testing, CRDT properties - Latent ingestion system: Reddit/FDA ingesters with ADK-Go agents - Disputed claims handling: Manual review workflows and validation - Aphoria security scanner: New extractors (SQL injection, command injection, weak crypto, TLS version), policy-based ignores, UAT reports - Docker infrastructure: Dockerfile, docker-compose.yml for full stack - VulnBank demo: Intentionally vulnerable multi-language test corpus SDK & API enhancements: - Source registry handlers for tracking data provenance - Metrics endpoint - Skeptic filtering improvements Code quality: - Split 14 large files (>500 lines) into focused modules - All files now under 500-line limit per project guidelines Documentation: - Chaos testing guide, circuit breakers, observability docs - Phase 7 UAT documentation updates - Martin Kleppmann technical writer agent Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
160 lines
7.1 KiB
TypeScript
160 lines
7.1 KiB
TypeScript
import { Badge } from "@/components/ui/badge"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { AlertTriangle, CheckCircle, Activity, ArrowUpRight } from "lucide-react"
|
|
|
|
async function getData() {
|
|
// In a real app, this would be an API call or DB query
|
|
// For the prototype, we load the JSON generated by the python engine
|
|
// Simulating fetch delay
|
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
|
|
// Use environment variable with localhost fallback for development
|
|
const apiUrl = process.env.LATENT_API_URL || 'http://localhost:3000'
|
|
const res = await fetch(`${apiUrl}/data.json`, { cache: 'no-store' })
|
|
if (!res.ok) throw new Error('Failed to fetch data')
|
|
return res.json()
|
|
}
|
|
|
|
export default async function Dashboard() {
|
|
const signals = await getData()
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-950 text-slate-100 p-8 font-mono">
|
|
{/* Header */}
|
|
<div className="max-w-6xl mx-auto mb-12 border-b border-slate-800 pb-6">
|
|
<div className="flex justify-between items-end">
|
|
<div>
|
|
<h1 className="text-4xl font-bold tracking-tight text-white mb-2">LATENT <span className="text-emerald-500 text-sm align-top">v1.0</span></h1>
|
|
<p className="text-slate-400">Epistemic Divergence Monitor // Pharma Sector</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<div className="text-xs text-slate-500 uppercase tracking-widest mb-1">Status</div>
|
|
<div className="flex items-center gap-2 text-emerald-400">
|
|
<span className="relative flex h-3 w-3">
|
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
|
<span className="relative inline-flex rounded-full h-3 w-3 bg-emerald-500"></span>
|
|
</span>
|
|
LIVE MONITORING
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<div className="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
|
|
{/* Left Col: High Level Stats */}
|
|
<div className="space-y-6">
|
|
<Card className="bg-slate-900 border-slate-800">
|
|
<CardHeader>
|
|
<CardTitle className="text-slate-400 text-sm uppercase tracking-wider">Active Molecules</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-4xl font-bold text-white">3</div>
|
|
<p className="text-xs text-slate-500 mt-2">Semaglutide, Tirzepatide, Liraglutide</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="bg-slate-900 border-slate-800">
|
|
<CardHeader>
|
|
<CardTitle className="text-slate-400 text-sm uppercase tracking-wider">Social Volume</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-4xl font-bold text-white">8,492</div>
|
|
<p className="text-xs text-slate-500 mt-2">Posts analyzed (Last 30d)</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="p-4 bg-amber-950/30 border border-amber-900/50 rounded-lg">
|
|
<h3 className="text-amber-500 font-bold flex items-center gap-2 mb-2">
|
|
<AlertTriangle className="h-4 w-4" />
|
|
Alpha Signal Detected
|
|
</h3>
|
|
<p className="text-sm text-amber-200/80">
|
|
Semaglutide shows a <strong>0.88 divergence</strong> on "Gastroparesis". Signal is absent from FDA label but high volume in Tier 5 sources.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Col: The Heatmap */}
|
|
<div className="lg:col-span-2 space-y-6">
|
|
<h2 className="text-xl font-bold text-white mb-4 flex items-center gap-2">
|
|
<Activity className="h-5 w-5 text-indigo-400" />
|
|
Signal Divergence Feed
|
|
</h2>
|
|
|
|
<div className="grid gap-4">
|
|
{signals.map((signal: any, idx: number) => (
|
|
<div key={idx} className="group relative bg-slate-900 hover:bg-slate-800 border border-slate-800 p-6 rounded-xl transition-all duration-200">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div>
|
|
<div className="flex items-center gap-3 mb-1">
|
|
<h3 className="text-lg font-bold text-white capitalize">{signal.molecule}</h3>
|
|
<span className="text-slate-600 text-sm">//</span>
|
|
<span className="text-indigo-300 font-medium">{signal.signal}</span>
|
|
</div>
|
|
<div className="flex gap-2 mt-2">
|
|
<Badge variant={signal.status === 'LATENT_SIGNAL' ? 'destructive' : 'secondary'}>
|
|
{signal.status.replace('_', ' ')}
|
|
</Badge>
|
|
<Badge variant="outline" className="text-slate-400 border-slate-700">
|
|
Vol: {signal.volume}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-right">
|
|
<div className="text-xs text-slate-500 uppercase tracking-wider mb-1">Divergence Score</div>
|
|
<div className={`text-3xl font-bold ${getScoreColor(signal.divergence_score)}`}>
|
|
{signal.divergence_score.toFixed(2)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress Bar Visual */}
|
|
<div className="w-full bg-slate-950 h-2 rounded-full overflow-hidden mb-4">
|
|
<div
|
|
className={`h-full ${getBarColor(signal.divergence_score)}`}
|
|
style={{ width: `${signal.divergence_score * 100}%` }}
|
|
></div>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center text-sm text-slate-400 border-t border-slate-800 pt-4 mt-4">
|
|
<div className="flex gap-4">
|
|
<div>
|
|
<span className="block text-xs text-slate-600 uppercase">FDA Status</span>
|
|
<span className={signal.regulatory_status.includes('Absent') ? 'text-red-400' : 'text-emerald-400'}>
|
|
{signal.regulatory_status}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span className="block text-xs text-slate-600 uppercase">Social Trend</span>
|
|
<span className="text-white">Rising (+12%)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button className="text-indigo-400 hover:text-indigo-300 flex items-center gap-1 text-xs uppercase tracking-widest font-bold">
|
|
Investigate <ArrowUpRight className="h-3 w-3" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function getScoreColor(score: number) {
|
|
if (score > 0.7) return "text-red-500"
|
|
if (score > 0.4) return "text-amber-500"
|
|
return "text-emerald-500"
|
|
}
|
|
|
|
function getBarColor(score: number) {
|
|
if (score > 0.7) return "bg-red-500"
|
|
if (score > 0.4) return "bg-amber-500"
|
|
return "bg-emerald-500"
|
|
}
|