Files
breakpilot-compliance/admin-compliance/app/(sdk)/sdk/quality/page.tsx
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:28 +01:00

369 lines
13 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import { useSDK } from '@/lib/sdk'
// =============================================================================
// TYPES
// =============================================================================
interface QualityMetric {
id: string
name: string
category: 'accuracy' | 'fairness' | 'robustness' | 'explainability' | 'performance'
score: number
threshold: number
trend: 'up' | 'down' | 'stable'
lastMeasured: Date
aiSystem: string
}
interface QualityTest {
id: string
name: string
status: 'passed' | 'failed' | 'warning' | 'pending'
lastRun: Date
duration: string
aiSystem: string
details: string
}
// =============================================================================
// MOCK DATA
// =============================================================================
const mockMetrics: QualityMetric[] = [
{
id: 'm-1',
name: 'Accuracy Score',
category: 'accuracy',
score: 94.5,
threshold: 90,
trend: 'up',
lastMeasured: new Date('2024-01-22'),
aiSystem: 'Bewerber-Screening',
},
{
id: 'm-2',
name: 'Fairness Index (Gender)',
category: 'fairness',
score: 87.2,
threshold: 85,
trend: 'stable',
lastMeasured: new Date('2024-01-22'),
aiSystem: 'Bewerber-Screening',
},
{
id: 'm-3',
name: 'Fairness Index (Age)',
category: 'fairness',
score: 78.5,
threshold: 85,
trend: 'down',
lastMeasured: new Date('2024-01-22'),
aiSystem: 'Bewerber-Screening',
},
{
id: 'm-4',
name: 'Robustness Score',
category: 'robustness',
score: 91.0,
threshold: 85,
trend: 'up',
lastMeasured: new Date('2024-01-21'),
aiSystem: 'Kundenservice Chatbot',
},
{
id: 'm-5',
name: 'Explainability Index',
category: 'explainability',
score: 72.3,
threshold: 75,
trend: 'up',
lastMeasured: new Date('2024-01-22'),
aiSystem: 'Empfehlungsalgorithmus',
},
{
id: 'm-6',
name: 'Response Time (P95)',
category: 'performance',
score: 95.0,
threshold: 90,
trend: 'stable',
lastMeasured: new Date('2024-01-22'),
aiSystem: 'Kundenservice Chatbot',
},
]
const mockTests: QualityTest[] = [
{
id: 't-1',
name: 'Bias Detection Test',
status: 'warning',
lastRun: new Date('2024-01-22T10:30:00'),
duration: '45min',
aiSystem: 'Bewerber-Screening',
details: 'Leichte Verzerrung bei Altersgruppe 50+ erkannt',
},
{
id: 't-2',
name: 'Accuracy Benchmark',
status: 'passed',
lastRun: new Date('2024-01-22T08:00:00'),
duration: '2h 15min',
aiSystem: 'Bewerber-Screening',
details: 'Alle Schwellenwerte eingehalten',
},
{
id: 't-3',
name: 'Adversarial Testing',
status: 'passed',
lastRun: new Date('2024-01-21T14:00:00'),
duration: '1h 30min',
aiSystem: 'Kundenservice Chatbot',
details: 'System robust gegen Manipulation',
},
{
id: 't-4',
name: 'Explainability Test',
status: 'failed',
lastRun: new Date('2024-01-22T09:00:00'),
duration: '30min',
aiSystem: 'Empfehlungsalgorithmus',
details: 'SHAP-Werte unter Schwellenwert',
},
{
id: 't-5',
name: 'Performance Load Test',
status: 'passed',
lastRun: new Date('2024-01-22T06:00:00'),
duration: '3h',
aiSystem: 'Kundenservice Chatbot',
details: '10.000 gleichzeitige Anfragen verarbeitet',
},
]
// =============================================================================
// COMPONENTS
// =============================================================================
function MetricCard({ metric }: { metric: QualityMetric }) {
const isAboveThreshold = metric.score >= metric.threshold
const categoryColors = {
accuracy: 'bg-blue-100 text-blue-700',
fairness: 'bg-purple-100 text-purple-700',
robustness: 'bg-green-100 text-green-700',
explainability: 'bg-yellow-100 text-yellow-700',
performance: 'bg-orange-100 text-orange-700',
}
const categoryLabels = {
accuracy: 'Genauigkeit',
fairness: 'Fairness',
robustness: 'Robustheit',
explainability: 'Erklaerbarkeit',
performance: 'Performance',
}
return (
<div className={`bg-white rounded-xl border-2 p-6 ${
isAboveThreshold ? 'border-gray-200' : 'border-red-200'
}`}>
<div className="flex items-start justify-between mb-4">
<div>
<span className={`px-2 py-1 text-xs rounded-full ${categoryColors[metric.category]}`}>
{categoryLabels[metric.category]}
</span>
<h4 className="font-semibold text-gray-900 mt-2">{metric.name}</h4>
<p className="text-xs text-gray-500">{metric.aiSystem}</p>
</div>
<div className={`flex items-center gap-1 text-sm ${
metric.trend === 'up' ? 'text-green-600' :
metric.trend === 'down' ? 'text-red-600' : 'text-gray-500'
}`}>
{metric.trend === 'up' && (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 10l7-7m0 0l7 7m-7-7v18" />
</svg>
)}
{metric.trend === 'down' && (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg>
)}
{metric.trend === 'stable' && (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 12h14" />
</svg>
)}
</div>
</div>
<div className="flex items-end justify-between">
<div>
<div className={`text-3xl font-bold ${isAboveThreshold ? 'text-gray-900' : 'text-red-600'}`}>
{metric.score}%
</div>
<div className="text-sm text-gray-500">
Schwellenwert: {metric.threshold}%
</div>
</div>
<div className="w-24 h-2 bg-gray-100 rounded-full overflow-hidden">
<div
className={`h-full rounded-full ${isAboveThreshold ? 'bg-green-500' : 'bg-red-500'}`}
style={{ width: `${metric.score}%` }}
/>
</div>
</div>
</div>
)
}
function TestRow({ test }: { test: QualityTest }) {
const statusColors = {
passed: 'bg-green-100 text-green-700',
failed: 'bg-red-100 text-red-700',
warning: 'bg-yellow-100 text-yellow-700',
pending: 'bg-gray-100 text-gray-500',
}
const statusLabels = {
passed: 'Bestanden',
failed: 'Fehlgeschlagen',
warning: 'Warnung',
pending: 'Ausstehend',
}
return (
<tr className="hover:bg-gray-50">
<td className="px-6 py-4">
<div className="font-medium text-gray-900">{test.name}</div>
<div className="text-xs text-gray-500">{test.aiSystem}</div>
</td>
<td className="px-6 py-4">
<span className={`px-2 py-1 text-xs rounded-full ${statusColors[test.status]}`}>
{statusLabels[test.status]}
</span>
</td>
<td className="px-6 py-4 text-sm text-gray-500">
{test.lastRun.toLocaleString('de-DE')}
</td>
<td className="px-6 py-4 text-sm text-gray-500">{test.duration}</td>
<td className="px-6 py-4 text-sm text-gray-500 max-w-xs truncate">{test.details}</td>
<td className="px-6 py-4">
<button className="text-sm text-purple-600 hover:text-purple-700">Details</button>
</td>
</tr>
)
}
// =============================================================================
// MAIN PAGE
// =============================================================================
export default function QualityPage() {
const { state } = useSDK()
const [metrics] = useState<QualityMetric[]>(mockMetrics)
const [tests] = useState<QualityTest[]>(mockTests)
const passedTests = tests.filter(t => t.status === 'passed').length
const failedTests = tests.filter(t => t.status === 'failed').length
const metricsAboveThreshold = metrics.filter(m => m.score >= m.threshold).length
const avgScore = Math.round(metrics.reduce((sum, m) => sum + m.score, 0) / metrics.length)
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900">AI Quality Dashboard</h1>
<p className="mt-1 text-gray-500">
Ueberwachen Sie die Qualitaet und Fairness Ihrer KI-Systeme
</p>
</div>
<button className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Tests ausfuehren
</button>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-xl border border-gray-200 p-6">
<div className="text-sm text-gray-500">Durchschnittlicher Score</div>
<div className="text-3xl font-bold text-gray-900">{avgScore}%</div>
</div>
<div className="bg-white rounded-xl border border-green-200 p-6">
<div className="text-sm text-green-600">Metriken ueber Schwellenwert</div>
<div className="text-3xl font-bold text-green-600">{metricsAboveThreshold}/{metrics.length}</div>
</div>
<div className="bg-white rounded-xl border border-green-200 p-6">
<div className="text-sm text-green-600">Tests bestanden</div>
<div className="text-3xl font-bold text-green-600">{passedTests}</div>
</div>
<div className="bg-white rounded-xl border border-red-200 p-6">
<div className="text-sm text-red-600">Tests fehlgeschlagen</div>
<div className="text-3xl font-bold text-red-600">{failedTests}</div>
</div>
</div>
{/* Alert for failed metrics */}
{metrics.filter(m => m.score < m.threshold).length > 0 && (
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-4 flex items-center gap-4">
<div className="w-10 h-10 bg-yellow-100 rounded-full flex items-center justify-center">
<svg className="w-5 h-5 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div>
<h4 className="font-medium text-yellow-800">
{metrics.filter(m => m.score < m.threshold).length} Metrik(en) unter Schwellenwert
</h4>
<p className="text-sm text-yellow-600">
Ueberpruefen Sie die betroffenen KI-Systeme und ergreifen Sie Korrekturmassnahmen.
</p>
</div>
</div>
)}
{/* Metrics Grid */}
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Qualitaetsmetriken</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{metrics.map(metric => (
<MetricCard key={metric.id} metric={metric} />
))}
</div>
</div>
{/* Tests Table */}
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">Testergebnisse</h3>
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Test</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Letzter Lauf</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Dauer</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Details</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Aktion</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{tests.map(test => (
<TestRow key={test.id} test={test} />
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
)
}