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>
369 lines
13 KiB
TypeScript
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>
|
|
)
|
|
}
|