mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 10:31:12 -05:00
Introduce statistical anomaly detection for metrics via edge function, hooks, and UI components. Adds detection algorithms (z-score, moving average, rate of change), anomaly storage, auto-alerts, and dashboard rendering of detected anomalies with run-once trigger and scheduling guidance.
170 lines
7.1 KiB
TypeScript
170 lines
7.1 KiB
TypeScript
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { Brain, TrendingUp, TrendingDown, Activity, AlertTriangle, Play, Sparkles } from 'lucide-react';
|
||
import { formatDistanceToNow } from 'date-fns';
|
||
import type { AnomalyDetection } from '@/hooks/admin/useAnomalyDetection';
|
||
import { useRunAnomalyDetection } from '@/hooks/admin/useAnomalyDetection';
|
||
|
||
interface AnomalyDetectionPanelProps {
|
||
anomalies?: AnomalyDetection[];
|
||
isLoading: boolean;
|
||
}
|
||
|
||
const ANOMALY_TYPE_CONFIG = {
|
||
spike: { icon: TrendingUp, label: 'Spike', color: 'text-orange-500' },
|
||
drop: { icon: TrendingDown, label: 'Drop', color: 'text-blue-500' },
|
||
trend_change: { icon: Activity, label: 'Trend Change', color: 'text-purple-500' },
|
||
outlier: { icon: AlertTriangle, label: 'Outlier', color: 'text-yellow-500' },
|
||
pattern_break: { icon: Activity, label: 'Pattern Break', color: 'text-red-500' },
|
||
};
|
||
|
||
const SEVERITY_CONFIG = {
|
||
critical: { badge: 'destructive', label: 'Critical' },
|
||
high: { badge: 'default', label: 'High' },
|
||
medium: { badge: 'secondary', label: 'Medium' },
|
||
low: { badge: 'outline', label: 'Low' },
|
||
};
|
||
|
||
export function AnomalyDetectionPanel({ anomalies, isLoading }: AnomalyDetectionPanelProps) {
|
||
const runDetection = useRunAnomalyDetection();
|
||
|
||
const handleRunDetection = () => {
|
||
runDetection.mutate();
|
||
};
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Brain className="h-5 w-5" />
|
||
ML Anomaly Detection
|
||
</CardTitle>
|
||
<CardDescription>Loading anomaly data...</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="flex items-center justify-center py-8">
|
||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
const recentAnomalies = anomalies?.slice(0, 5) || [];
|
||
|
||
return (
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center justify-between">
|
||
<span className="flex items-center gap-2">
|
||
<Brain className="h-5 w-5" />
|
||
ML Anomaly Detection
|
||
</span>
|
||
<div className="flex items-center gap-2">
|
||
{anomalies && anomalies.length > 0 && (
|
||
<span className="text-sm font-normal text-muted-foreground">
|
||
{anomalies.length} detected (24h)
|
||
</span>
|
||
)}
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={handleRunDetection}
|
||
disabled={runDetection.isPending}
|
||
>
|
||
<Play className="h-4 w-4 mr-1" />
|
||
Run Detection
|
||
</Button>
|
||
</div>
|
||
</CardTitle>
|
||
<CardDescription>
|
||
Statistical ML algorithms detecting unusual patterns in metrics
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
{recentAnomalies.length === 0 ? (
|
||
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
|
||
<Sparkles className="h-12 w-12 mb-2 opacity-50" />
|
||
<p>No anomalies detected in last 24 hours</p>
|
||
<p className="text-sm">ML models are monitoring metrics continuously</p>
|
||
</div>
|
||
) : (
|
||
<>
|
||
{recentAnomalies.map((anomaly) => {
|
||
const typeConfig = ANOMALY_TYPE_CONFIG[anomaly.anomaly_type];
|
||
const severityConfig = SEVERITY_CONFIG[anomaly.severity];
|
||
const TypeIcon = typeConfig.icon;
|
||
|
||
return (
|
||
<div
|
||
key={anomaly.id}
|
||
className="border rounded-lg p-4 space-y-2 bg-card hover:bg-accent/5 transition-colors"
|
||
>
|
||
<div className="flex items-start justify-between gap-4">
|
||
<div className="flex items-start gap-3 flex-1">
|
||
<TypeIcon className={`h-5 w-5 mt-0.5 ${typeConfig.color}`} />
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex items-center gap-2 flex-wrap mb-1">
|
||
<Badge variant={severityConfig.badge as any} className="text-xs">
|
||
{severityConfig.label}
|
||
</Badge>
|
||
<span className="text-xs px-2 py-0.5 rounded bg-purple-500/10 text-purple-600">
|
||
{typeConfig.label}
|
||
</span>
|
||
<span className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground">
|
||
{anomaly.metric_name.replace(/_/g, ' ')}
|
||
</span>
|
||
{anomaly.alert_created && (
|
||
<span className="text-xs px-2 py-0.5 rounded bg-green-500/10 text-green-600">
|
||
Alert Created
|
||
</span>
|
||
)}
|
||
</div>
|
||
<div className="text-sm space-y-1">
|
||
<div className="flex items-center gap-4 text-muted-foreground">
|
||
<span>
|
||
Baseline: <span className="font-medium text-foreground">{anomaly.baseline_value.toFixed(2)}</span>
|
||
</span>
|
||
<span>→</span>
|
||
<span>
|
||
Detected: <span className="font-medium text-foreground">{anomaly.anomaly_value.toFixed(2)}</span>
|
||
</span>
|
||
<span className="ml-2 px-2 py-0.5 rounded bg-orange-500/10 text-orange-600 text-xs font-medium">
|
||
{anomaly.deviation_score.toFixed(2)}σ
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||
<span className="flex items-center gap-1">
|
||
<Brain className="h-3 w-3" />
|
||
Algorithm: {anomaly.detection_algorithm.replace(/_/g, ' ')}
|
||
</span>
|
||
<span>
|
||
Confidence: {(anomaly.confidence_score * 100).toFixed(0)}%
|
||
</span>
|
||
<span>
|
||
Detected {formatDistanceToNow(new Date(anomaly.detected_at), { addSuffix: true })}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
{anomalies && anomalies.length > 5 && (
|
||
<div className="text-center pt-2">
|
||
<span className="text-sm text-muted-foreground">
|
||
+ {anomalies.length - 5} more anomalies
|
||
</span>
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|