Files
breakpilot-compliance/admin-compliance/components/sdk/dsfa/AIUseCaseSection.tsx
Benjamin Admin 308d559c85
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 38s
CI / test-python-backend-compliance (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 19s
feat: DSFA Section 8 KI-Anwendungsfälle + Bundesland RAG-Ingest
- Migration 028: ai_use_case_modules JSONB + section_8_complete auf compliance_dsfas
- Neues ai-use-case-types.ts: AIUseCaseModule Interface, 8 Typen, Art22Assessment,
  AI Act Risikoklassen, WP248-Kriterien, Privacy by Design, createEmptyModule() Helper
- types.ts: Section 8 in DSFA_SECTIONS, ai_use_case_modules im DSFA Interface,
  section_8_complete in DSFASectionProgress
- api.ts: addAIUseCaseModule, updateAIUseCaseModule, removeAIUseCaseModule
- 5 neue UI-Komponenten: AIUseCaseTypeSelector, Art22AssessmentPanel,
  AIRiskCriteriaChecklist, AIUseCaseModuleEditor (7 Tabs), AIUseCaseSection
- DSFASidebar: Section 8 Eintrag + calculateSectionProgress case 8
- ReviewScheduleSection: ai_use_case_module Trigger-Typ ergänzt
- page.tsx: Section 8 Rendering + Weiter-Button auf activeSection < 8 + KI-Module Counter
- scripts/ingest-dsfa-bundesland.sh: WP248 + alle 17 Behörden → bp_dsfa_corpus
- Docs: dsfa.md Section 8 + RAG-Corpus, Developer Portal DSFA mit AI-Modul-Code-Beispielen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 09:20:27 +01:00

265 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import React, { useState } from 'react'
import { DSFA } from '@/lib/sdk/dsfa/types'
import {
AIUseCaseModule,
AIUseCaseType,
AI_USE_CASE_TYPES,
AI_ACT_RISK_CLASSES,
createEmptyModule,
calculateModuleRiskLevel,
getModuleCompletionPercentage,
} from '@/lib/sdk/dsfa/ai-use-case-types'
import { AIUseCaseTypeSelector } from './AIUseCaseTypeSelector'
import { AIUseCaseModuleEditor } from './AIUseCaseModuleEditor'
interface AIUseCaseSectionProps {
dsfa: DSFA
onUpdate: (data: Record<string, unknown>) => Promise<void>
isSubmitting: boolean
}
const RISK_LEVEL_LABELS: Record<string, string> = {
low: 'Niedrig',
medium: 'Mittel',
high: 'Hoch',
very_high: 'Sehr hoch',
}
const RISK_LEVEL_COLORS: Record<string, string> = {
low: 'bg-green-100 text-green-700 border-green-200',
medium: 'bg-yellow-100 text-yellow-700 border-yellow-200',
high: 'bg-orange-100 text-orange-700 border-orange-200',
very_high: 'bg-red-100 text-red-700 border-red-200',
}
export function AIUseCaseSection({ dsfa, onUpdate, isSubmitting }: AIUseCaseSectionProps) {
const [showTypeSelector, setShowTypeSelector] = useState(false)
const [editingModule, setEditingModule] = useState<AIUseCaseModule | null>(null)
const [isSavingModule, setIsSavingModule] = useState(false)
const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null)
const modules = dsfa.ai_use_case_modules || []
// Aggregate AI risk overview
const highRiskCount = modules.filter(m => {
const level = calculateModuleRiskLevel(m)
return level === 'high' || level === 'very_high'
}).length
const art22Count = modules.filter(m => m.art22_assessment?.applies).length
const handleSelectType = (type: AIUseCaseType) => {
setShowTypeSelector(false)
const newModule = createEmptyModule(type)
setEditingModule(newModule)
}
const handleSaveModule = async (savedModule: AIUseCaseModule) => {
setIsSavingModule(true)
try {
const existing = dsfa.ai_use_case_modules || []
const idx = existing.findIndex(m => m.id === savedModule.id)
const updated = idx >= 0
? existing.map(m => m.id === savedModule.id ? savedModule : m)
: [...existing, savedModule]
await onUpdate({ ai_use_case_modules: updated })
setEditingModule(null)
} finally {
setIsSavingModule(false)
}
}
const handleDeleteModule = async (moduleId: string) => {
const updated = modules.filter(m => m.id !== moduleId)
await onUpdate({ ai_use_case_modules: updated })
setConfirmDeleteId(null)
}
return (
<div className="space-y-6">
{/* Header & Info */}
<div className="p-4 bg-purple-50 border border-purple-200 rounded-xl">
<div className="flex items-start gap-3">
<span className="text-2xl">🤖</span>
<div className="flex-1">
<h3 className="font-semibold text-purple-900">KI-Anwendungsfälle (Section 8)</h3>
<p className="text-sm text-purple-700 mt-1">
Dokumentieren Sie KI-spezifische Verarbeitungen mit modularen Anhängen nach Art. 35 DSGVO,
Art. 22 DSGVO und den Transparenzanforderungen des EU AI Act.
</p>
</div>
</div>
</div>
{/* Warning: involves_ai but no modules */}
{dsfa.involves_ai && modules.length === 0 && (
<div className="flex items-start gap-3 p-4 bg-yellow-50 border border-yellow-300 rounded-xl">
<svg className="w-5 h-5 text-yellow-600 flex-shrink-0 mt-0.5" 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>
<p className="text-sm font-medium text-yellow-800">KI-Einsatz dokumentiert, aber keine Module vorhanden</p>
<p className="text-xs text-yellow-700 mt-0.5">
In Section 3 wurde KI-Einsatz festgestellt. Fügen Sie mindestens ein KI-Modul hinzu.
</p>
</div>
</div>
)}
{/* Aggregated Risk Overview (only when modules exist) */}
{modules.length > 0 && (
<div className="grid grid-cols-3 gap-3">
<div className="p-3 bg-white border border-gray-200 rounded-xl text-center">
<div className="text-2xl font-bold text-purple-700">{modules.length}</div>
<div className="text-xs text-gray-500 mt-0.5">KI-Module</div>
</div>
<div className={`p-3 rounded-xl text-center border ${highRiskCount > 0 ? 'bg-red-50 border-red-200' : 'bg-green-50 border-green-200'}`}>
<div className={`text-2xl font-bold ${highRiskCount > 0 ? 'text-red-700' : 'text-green-700'}`}>{highRiskCount}</div>
<div className="text-xs text-gray-500 mt-0.5">Hoch-Risiko-Module</div>
</div>
<div className={`p-3 rounded-xl text-center border ${art22Count > 0 ? 'bg-orange-50 border-orange-200' : 'bg-gray-50 border-gray-200'}`}>
<div className={`text-2xl font-bold ${art22Count > 0 ? 'text-orange-700' : 'text-gray-400'}`}>{art22Count}</div>
<div className="text-xs text-gray-500 mt-0.5">Art. 22 DSGVO</div>
</div>
</div>
)}
{/* Module Cards */}
<div className="space-y-3">
{modules.map(module => {
const riskLevel = calculateModuleRiskLevel(module)
const completion = getModuleCompletionPercentage(module)
const typeInfo = AI_USE_CASE_TYPES[module.use_case_type]
const aiActInfo = AI_ACT_RISK_CLASSES[module.ai_act_risk_class]
return (
<div
key={module.id}
className="bg-white border border-gray-200 rounded-xl p-4 hover:border-purple-300 transition-colors"
>
<div className="flex items-start gap-3">
<span className="text-2xl flex-shrink-0">{typeInfo.icon}</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-medium text-gray-900">{module.name}</span>
<span className={`text-xs px-1.5 py-0.5 rounded border ${RISK_LEVEL_COLORS[riskLevel]}`}>
{RISK_LEVEL_LABELS[riskLevel]}
</span>
<span className="text-xs px-1.5 py-0.5 bg-gray-100 text-gray-500 rounded">
{typeInfo.label}
</span>
{module.art22_assessment?.applies && (
<span className="text-xs px-1.5 py-0.5 bg-orange-100 text-orange-700 rounded border border-orange-200">
Art. 22
</span>
)}
{module.ai_act_risk_class !== 'minimal' && (
<span className={`text-xs px-1.5 py-0.5 rounded border ${
module.ai_act_risk_class === 'unacceptable' ? 'bg-red-100 text-red-700 border-red-200' :
module.ai_act_risk_class === 'high_risk' ? 'bg-orange-100 text-orange-700 border-orange-200' :
'bg-yellow-100 text-yellow-700 border-yellow-200'
}`}>
AI Act: {aiActInfo.labelDE}
</span>
)}
</div>
{module.model_description && (
<p className="text-xs text-gray-500 mt-1 line-clamp-1">{module.model_description}</p>
)}
{/* Progress Bar */}
<div className="flex items-center gap-2 mt-2">
<div className="flex-1 h-1.5 bg-gray-100 rounded-full overflow-hidden">
<div
className="h-full bg-purple-500 transition-all"
style={{ width: `${completion}%` }}
/>
</div>
<span className="text-xs text-gray-400">{completion}%</span>
</div>
</div>
{/* Actions */}
<div className="flex items-center gap-1 flex-shrink-0">
<button
onClick={() => setEditingModule(module)}
className="px-3 py-1.5 text-xs bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition-colors"
>
Bearbeiten
</button>
<button
onClick={() => setConfirmDeleteId(module.id)}
className="px-2 py-1.5 text-xs text-red-500 hover:bg-red-50 rounded-lg transition-colors"
>
×
</button>
</div>
</div>
{/* Delete confirmation */}
{confirmDeleteId === module.id && (
<div className="mt-3 p-3 bg-red-50 border border-red-200 rounded-lg">
<p className="text-sm text-red-700 mb-2">Modul wirklich löschen?</p>
<div className="flex gap-2">
<button
onClick={() => handleDeleteModule(module.id)}
disabled={isSubmitting}
className="px-3 py-1 text-xs bg-red-600 text-white rounded hover:bg-red-700"
>
Löschen
</button>
<button
onClick={() => setConfirmDeleteId(null)}
className="px-3 py-1 text-xs bg-white border border-gray-300 rounded hover:bg-gray-50"
>
Abbrechen
</button>
</div>
</div>
)}
</div>
)
})}
{/* Empty State */}
{modules.length === 0 && !dsfa.involves_ai && (
<div className="text-center py-8 text-gray-400">
<div className="text-4xl mb-2">🤖</div>
<p className="text-sm">Keine KI-Anwendungsfälle dokumentiert</p>
<p className="text-xs mt-1">Optional: Fügen Sie KI-Module hinzu, wenn dieses DSFA KI-Einsatz umfasst</p>
</div>
)}
</div>
{/* Add Module Button */}
<button
onClick={() => setShowTypeSelector(true)}
disabled={isSubmitting}
className="w-full py-3 border-2 border-dashed border-purple-300 rounded-xl text-sm text-purple-600 hover:border-purple-500 hover:bg-purple-50 transition-all font-medium"
>
+ KI-Anwendungsfall-Modul hinzufügen
</button>
{/* Type Selector Modal */}
{showTypeSelector && (
<AIUseCaseTypeSelector
onSelect={handleSelectType}
onCancel={() => setShowTypeSelector(false)}
/>
)}
{/* Module Editor Modal */}
{editingModule && (
<AIUseCaseModuleEditor
module={editingModule}
onSave={handleSaveModule}
onCancel={() => setEditingModule(null)}
isSaving={isSavingModule}
/>
)}
</div>
)
}