Initial commit
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
Chip,
|
||||
Typography,
|
||||
LinearProgress,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Alert,
|
||||
CircularProgress,
|
||||
} from '@mui/material'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { propertyService } from '../../services/propertyService'
|
||||
|
||||
interface MetricCard {
|
||||
label: string
|
||||
value: string
|
||||
color: string
|
||||
note: string
|
||||
}
|
||||
|
||||
const HEALTH_METRICS: MetricCard[] = [
|
||||
{ label: 'Extraktionsgenauigkeit', value: '94%', color: '#1a7a4a', note: 'Ø letzte 30 Tage' },
|
||||
{ label: 'Konfidenz-Ø', value: '73%', color: '#d97706', note: 'Alle Objekte' },
|
||||
{ label: 'Validierungsrate', value: '88%', color: '#1a7a4a', note: 'Menschliche Bestätigung' },
|
||||
{ label: 'Fehlerrate', value: '2.1%', color: '#1a7a4a', note: 'Kritische Fehler' },
|
||||
]
|
||||
|
||||
interface AIDecision {
|
||||
timestamp: string
|
||||
type: string
|
||||
confidence: string
|
||||
result: string
|
||||
impact: string
|
||||
}
|
||||
|
||||
const RECENT_DECISIONS: AIDecision[] = [
|
||||
{
|
||||
timestamp: '15.05.2025 14:32',
|
||||
type: 'Bedarfsextraktion',
|
||||
confidence: '91%',
|
||||
result: 'Kriterien extrahiert',
|
||||
impact: 'Suche ausgelöst',
|
||||
},
|
||||
{
|
||||
timestamp: '15.05.2025 11:15',
|
||||
type: 'Match-Scoring',
|
||||
confidence: '88%',
|
||||
result: '3 Matches berechnet',
|
||||
impact: 'Review ausgelöst',
|
||||
},
|
||||
{
|
||||
timestamp: '14.05.2025 16:40',
|
||||
type: 'Signal-Erkennung',
|
||||
confidence: '72%',
|
||||
result: 'Expansion erkannt',
|
||||
impact: 'Signal erstellt',
|
||||
},
|
||||
{
|
||||
timestamp: '14.05.2025 09:00',
|
||||
type: 'Datenqualitätsprüfung',
|
||||
confidence: '95%',
|
||||
result: '2 Warnungen erkannt',
|
||||
impact: 'Meldung erstellt',
|
||||
},
|
||||
{
|
||||
timestamp: '13.05.2025 15:22',
|
||||
type: 'Match-Scoring',
|
||||
confidence: '84%',
|
||||
result: '2 Matches berechnet',
|
||||
impact: 'Review ausgelöst',
|
||||
},
|
||||
{
|
||||
timestamp: '12.05.2025 10:11',
|
||||
type: 'Signal-Erkennung',
|
||||
confidence: '65%',
|
||||
result: 'Möglicher Auszug erkannt',
|
||||
impact: 'Signal erstellt',
|
||||
},
|
||||
]
|
||||
|
||||
function getConfidenceBadge(pct: string) {
|
||||
const n = parseInt(pct)
|
||||
const color = n >= 85 ? '#1a7a4a' : n >= 70 ? '#d97706' : '#c0392b'
|
||||
return (
|
||||
<Chip
|
||||
label={pct}
|
||||
size="small"
|
||||
sx={{ bgcolor: color, color: 'white', fontWeight: 700, fontSize: 11 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function AIMonitoring() {
|
||||
const { data: propResp, isLoading } = useQuery({
|
||||
queryKey: ['properties'],
|
||||
queryFn: () => propertyService.getAll(),
|
||||
})
|
||||
|
||||
const properties = propResp?.data ?? []
|
||||
|
||||
const highConf = properties.filter(p => p.confidenceScore > 0.85).length
|
||||
const midConf = properties.filter(p => p.confidenceScore >= 0.65 && p.confidenceScore <= 0.85).length
|
||||
const lowConf = properties.filter(p => p.confidenceScore < 0.65).length
|
||||
const total = properties.length || 1
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* Page Header */}
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: 'white',
|
||||
borderBottom: '1px solid #e2e8f0',
|
||||
px: 3,
|
||||
py: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
<Typography variant="h5" fontWeight={700} color="text.primary">
|
||||
AI Monitoring
|
||||
</Typography>
|
||||
<Chip
|
||||
label="Live"
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: '#1a7a4a',
|
||||
color: 'white',
|
||||
fontWeight: 700,
|
||||
fontSize: 11,
|
||||
animation: 'pulse 2s ease-in-out infinite',
|
||||
'@keyframes pulse': {
|
||||
'0%, 100%': { opacity: 1 },
|
||||
'50%': { opacity: 0.6 },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
AI-Layer Gesundheit und Entscheidungsqualität
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ px: 3, py: 3 }}>
|
||||
{/* Health Metrics */}
|
||||
<Box className="grid grid-cols-4 gap-4" sx={{ mb: 3 }}>
|
||||
{HEALTH_METRICS.map(m => (
|
||||
<Card key={m.label} sx={{ p: 2.5 }}>
|
||||
<Typography variant="overline" color="text.secondary" lineHeight={1.4} display="block">
|
||||
{m.label}
|
||||
</Typography>
|
||||
<Typography variant="h3" fontWeight={800} sx={{ color: m.color }}>
|
||||
{m.value}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{m.note}
|
||||
</Typography>
|
||||
</Card>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Box className="grid grid-cols-2 gap-4" sx={{ mb: 3 }}>
|
||||
{/* Confidence Distribution */}
|
||||
<Card sx={{ p: 2.5 }}>
|
||||
<Typography variant="subtitle1" fontWeight={600} mb={2}>
|
||||
Konfidenzverteilung
|
||||
</Typography>
|
||||
|
||||
{isLoading ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||
<CircularProgress size={32} />
|
||||
</Box>
|
||||
) : (
|
||||
<Stack spacing={2}>
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
||||
<Typography variant="body2" fontWeight={500}>Hoch (>85%)</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{highConf} Objekte</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={(highConf / total) * 100}
|
||||
color="success"
|
||||
sx={{ height: 10, borderRadius: 5 }}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
||||
<Typography variant="body2" fontWeight={500}>Mittel (65–85%)</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{midConf} Objekte</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={(midConf / total) * 100}
|
||||
color="warning"
|
||||
sx={{ height: 10, borderRadius: 5 }}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
||||
<Typography variant="body2" fontWeight={500}>Niedrig (<65%)</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{lowConf} Objekte</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={(lowConf / total) * 100}
|
||||
color="error"
|
||||
sx={{ height: 10, borderRadius: 5 }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Anomaly Alerts */}
|
||||
<Card sx={{ p: 2.5 }}>
|
||||
<Typography variant="subtitle1" fontWeight={600} mb={2}>
|
||||
Anomalien
|
||||
</Typography>
|
||||
<Stack spacing={1.5}>
|
||||
<Alert severity="warning">
|
||||
Mietpreisangaben für prop-004 weichen von Marktdurchschnitt ab (±31%). Manuelle Prüfung empfohlen.
|
||||
</Alert>
|
||||
<Alert severity="success">
|
||||
Keine kritischen Anomalien erkannt. System läuft stabil.
|
||||
</Alert>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
{/* Recent AI Decisions */}
|
||||
<Card>
|
||||
<Box sx={{ px: 2.5, py: 2, borderBottom: '1px solid #f1f5f9' }}>
|
||||
<Typography variant="subtitle1" fontWeight={600}>
|
||||
Letzte KI-Entscheidungen
|
||||
</Typography>
|
||||
</Box>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow sx={{ bgcolor: '#f8fafc' }}>
|
||||
<TableCell sx={{ fontSize: 12, fontWeight: 600, color: '#64748b' }}>Zeitpunkt</TableCell>
|
||||
<TableCell sx={{ fontSize: 12, fontWeight: 600, color: '#64748b' }}>Entscheidungstyp</TableCell>
|
||||
<TableCell sx={{ fontSize: 12, fontWeight: 600, color: '#64748b' }}>Konfidenz</TableCell>
|
||||
<TableCell sx={{ fontSize: 12, fontWeight: 600, color: '#64748b' }}>Ergebnis</TableCell>
|
||||
<TableCell sx={{ fontSize: 12, fontWeight: 600, color: '#64748b' }}>Einfluss</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{RECENT_DECISIONS.map((d, i) => (
|
||||
<TableRow key={i} hover>
|
||||
<TableCell>
|
||||
<Typography variant="caption" color="text.secondary">{d.timestamp}</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" fontWeight={500}>{d.type}</Typography>
|
||||
</TableCell>
|
||||
<TableCell>{getConfidenceBadge(d.confidence)}</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2">{d.result}</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" color="text.secondary">{d.impact}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user