Initial commit

This commit is contained in:
Benjamin Sutter
2026-05-15 00:48:18 +02:00
commit 9e827c50f9
72 changed files with 10477 additions and 0 deletions
+280
View File
@@ -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 (&gt;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 (6585%)</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 (&lt;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>
)
}