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
- 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>
150 lines
6.4 KiB
TypeScript
150 lines
6.4 KiB
TypeScript
'use client'
|
||
|
||
import React from 'react'
|
||
import { Art22Assessment, Art22ExceptionType } from '@/lib/sdk/dsfa/ai-use-case-types'
|
||
|
||
interface Art22AssessmentPanelProps {
|
||
assessment: Art22Assessment
|
||
onChange: (updated: Art22Assessment) => void
|
||
readonly?: boolean
|
||
}
|
||
|
||
const EXCEPTION_OPTIONS: { value: Art22ExceptionType; label: string; description: string }[] = [
|
||
{
|
||
value: 'contract',
|
||
label: 'Art. 22 Abs. 2 lit. a – Vertragserfüllung',
|
||
description: 'Die Entscheidung ist für den Abschluss oder die Erfüllung eines Vertrags erforderlich',
|
||
},
|
||
{
|
||
value: 'legal',
|
||
label: 'Art. 22 Abs. 2 lit. b – Rechtliche Verpflichtung',
|
||
description: 'Die Entscheidung ist durch Unionsrecht oder mitgliedstaatliches Recht zugelassen',
|
||
},
|
||
{
|
||
value: 'consent',
|
||
label: 'Art. 22 Abs. 2 lit. c – Ausdrückliche Einwilligung',
|
||
description: 'Die betroffene Person hat ausdrücklich eingewilligt',
|
||
},
|
||
]
|
||
|
||
export function Art22AssessmentPanel({ assessment, onChange, readonly }: Art22AssessmentPanelProps) {
|
||
const missingRequiredSafeguards = assessment.applies &&
|
||
!assessment.safeguards.some(s => s.id === 'human_review' && s.implemented)
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* Art. 22 Toggle */}
|
||
<div className="flex items-start gap-3 p-4 rounded-xl border border-gray-200 bg-gray-50">
|
||
<input
|
||
type="checkbox"
|
||
id="art22_applies"
|
||
checked={assessment.applies}
|
||
onChange={e => onChange({ ...assessment, applies: e.target.checked })}
|
||
disabled={readonly}
|
||
className="mt-1 h-4 w-4 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
|
||
/>
|
||
<label htmlFor="art22_applies" className="flex-1">
|
||
<div className="font-medium text-gray-900">
|
||
Automatisierte Einzelentscheidung mit Rechtswirkung (Art. 22 DSGVO)
|
||
</div>
|
||
<p className="text-xs text-gray-500 mt-0.5">
|
||
Das KI-System trifft Entscheidungen, die rechtliche Wirkung oder ähnlich erhebliche
|
||
Auswirkungen auf Personen haben, ohne maßgebliche menschliche Beteiligung.
|
||
</p>
|
||
</label>
|
||
</div>
|
||
|
||
{/* Warning if applies without safeguards */}
|
||
{missingRequiredSafeguards && (
|
||
<div className="flex items-start gap-2 p-3 bg-red-50 border border-red-200 rounded-lg">
|
||
<svg className="w-4 h-4 text-red-500 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>
|
||
<p className="text-sm text-red-700">
|
||
Art. 22 gilt als anwendbar, aber die erforderliche Schutzmaßnahme „Recht auf menschliche Überprüfung" ist nicht implementiert.
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{assessment.applies && (
|
||
<>
|
||
{/* Justification */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||
Begründung der Ausnahme (Art. 22 Abs. 2)
|
||
</label>
|
||
<textarea
|
||
value={assessment.justification || ''}
|
||
onChange={e => onChange({ ...assessment, justification: e.target.value })}
|
||
disabled={readonly}
|
||
rows={3}
|
||
placeholder="Begründen Sie, auf welcher Rechtsgrundlage die automatisierte Entscheidung zulässig ist..."
|
||
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 resize-none"
|
||
/>
|
||
</div>
|
||
|
||
{/* Exception Type */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">Ausnahmetatbestand</label>
|
||
<div className="space-y-2">
|
||
{EXCEPTION_OPTIONS.map(opt => (
|
||
<label key={opt.value} className="flex items-start gap-3 p-3 rounded-lg border border-gray-200 hover:bg-gray-50 cursor-pointer">
|
||
<input
|
||
type="radio"
|
||
name="art22_exception"
|
||
value={opt.value}
|
||
checked={assessment.exception_type === opt.value}
|
||
onChange={() => onChange({ ...assessment, exception_type: opt.value })}
|
||
disabled={readonly}
|
||
className="mt-0.5 h-4 w-4 text-purple-600"
|
||
/>
|
||
<div>
|
||
<div className="text-sm font-medium text-gray-900">{opt.label}</div>
|
||
<p className="text-xs text-gray-500 mt-0.5">{opt.description}</p>
|
||
</div>
|
||
</label>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Safeguards */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
Schutzmaßnahmen (Art. 22 Abs. 3 DSGVO)
|
||
</label>
|
||
<div className="space-y-2">
|
||
{assessment.safeguards.map((safeguard, idx) => (
|
||
<label key={safeguard.id} className="flex items-start gap-3 p-3 rounded-lg border border-gray-200 hover:bg-gray-50 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={safeguard.implemented}
|
||
onChange={e => {
|
||
const updated = assessment.safeguards.map((s, i) =>
|
||
i === idx ? { ...s, implemented: e.target.checked } : s
|
||
)
|
||
onChange({ ...assessment, safeguards: updated })
|
||
}}
|
||
disabled={readonly}
|
||
className="mt-0.5 h-4 w-4 rounded border-gray-300 text-purple-600"
|
||
/>
|
||
<div>
|
||
<div className={`text-sm font-medium ${safeguard.id === 'human_review' && !safeguard.implemented ? 'text-red-700' : 'text-gray-900'}`}>
|
||
{safeguard.label}
|
||
{safeguard.id === 'human_review' && (
|
||
<span className="ml-1 text-xs text-red-500">*Pflicht</span>
|
||
)}
|
||
</div>
|
||
{safeguard.description && (
|
||
<p className="text-xs text-gray-500 mt-0.5">{safeguard.description}</p>
|
||
)}
|
||
</div>
|
||
</label>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|