Files
breakpilot-compliance/admin-compliance/components/sdk/dsfa/AIRiskCriteriaChecklist.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

130 lines
5.1 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 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>
)
}