feat: DSFA Section 8 KI-Anwendungsfälle + Bundesland RAG-Ingest
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
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>
This commit is contained in:
129
admin-compliance/components/sdk/dsfa/AIRiskCriteriaChecklist.tsx
Normal file
129
admin-compliance/components/sdk/dsfa/AIRiskCriteriaChecklist.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { AI_RISK_CRITERIA, AIUseCaseRiskCriterion } from '@/lib/sdk/dsfa/ai-use-case-types'
|
||||
|
||||
interface AIRiskCriteriaChecklistProps {
|
||||
criteria: AIUseCaseRiskCriterion[]
|
||||
onChange: (updated: AIUseCaseRiskCriterion[]) => void
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
const SEVERITY_LABELS: Record<string, string> = {
|
||||
low: 'Niedrig',
|
||||
medium: 'Mittel',
|
||||
high: 'Hoch',
|
||||
}
|
||||
|
||||
const SEVERITY_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-red-100 text-red-700 border-red-200',
|
||||
}
|
||||
|
||||
export function AIRiskCriteriaChecklist({ criteria, onChange, readonly }: AIRiskCriteriaChecklistProps) {
|
||||
const appliedCount = criteria.filter(c => c.applies).length
|
||||
|
||||
const updateCriterion = (id: string, updates: Partial<AIUseCaseRiskCriterion>) => {
|
||||
onChange(criteria.map(c => c.id === id ? { ...c, ...updates } : c))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{/* Summary Banner */}
|
||||
{appliedCount > 0 && (
|
||||
<div className={`p-3 rounded-lg border text-sm ${
|
||||
appliedCount >= 3
|
||||
? 'bg-red-50 border-red-200 text-red-700'
|
||||
: appliedCount >= 2
|
||||
? 'bg-orange-50 border-orange-200 text-orange-700'
|
||||
: 'bg-yellow-50 border-yellow-200 text-yellow-700'
|
||||
}`}>
|
||||
{appliedCount} von 6 Risikokriterien erfüllt
|
||||
{appliedCount >= 2 && ' – DSFA ist erforderlich'}
|
||||
{appliedCount >= 4 && ' – behördliche Konsultation prüfen'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Criteria Cards */}
|
||||
<div className="space-y-2">
|
||||
{AI_RISK_CRITERIA.map(critDef => {
|
||||
const criterion = criteria.find(c => c.id === critDef.id) || {
|
||||
id: critDef.id,
|
||||
applies: false,
|
||||
severity: critDef.default_severity,
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={critDef.id}
|
||||
className={`rounded-xl border p-4 transition-all ${
|
||||
criterion.applies
|
||||
? 'border-red-300 bg-red-50'
|
||||
: 'border-gray-200 bg-white'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Checkbox */}
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={criterion.applies}
|
||||
onChange={e => updateCriterion(critDef.id, { applies: e.target.checked })}
|
||||
disabled={readonly}
|
||||
className="mt-1 h-4 w-4 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
|
||||
/>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className={`font-medium text-sm ${criterion.applies ? 'text-red-800' : 'text-gray-900'}`}>
|
||||
{critDef.label}
|
||||
</span>
|
||||
<span className={`text-[10px] px-1.5 py-0.5 rounded border ${SEVERITY_COLORS[criterion.severity]}`}>
|
||||
{SEVERITY_LABELS[criterion.severity]}
|
||||
</span>
|
||||
<span className="text-[10px] px-1.5 py-0.5 bg-gray-100 text-gray-500 rounded font-mono">
|
||||
{critDef.gdpr_ref.split(',')[0]}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-0.5">{critDef.description}</p>
|
||||
|
||||
{/* Justification (shown when applies) */}
|
||||
{criterion.applies && (
|
||||
<div className="mt-3 space-y-2">
|
||||
<textarea
|
||||
value={criterion.justification || ''}
|
||||
onChange={e => updateCriterion(critDef.id, { justification: e.target.value })}
|
||||
disabled={readonly}
|
||||
rows={2}
|
||||
placeholder="Begründung, warum dieses Kriterium zutrifft..."
|
||||
className="w-full px-3 py-2 text-xs border border-red-200 rounded-lg bg-white focus:ring-2 focus:ring-red-400 focus:border-red-400 resize-none"
|
||||
/>
|
||||
|
||||
{/* Severity Override */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-gray-500">Schwere:</span>
|
||||
{(['low', 'medium', 'high'] as const).map(sev => (
|
||||
<button
|
||||
key={sev}
|
||||
onClick={() => !readonly && updateCriterion(critDef.id, { severity: sev })}
|
||||
className={`text-xs px-2 py-0.5 rounded border transition-colors ${
|
||||
criterion.severity === sev
|
||||
? SEVERITY_COLORS[sev]
|
||||
: 'bg-white text-gray-500 border-gray-200 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{SEVERITY_LABELS[sev]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user