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

- 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:
Benjamin Admin
2026-03-05 09:20:27 +01:00
parent 274dc68e24
commit 308d559c85
17 changed files with 2541 additions and 3 deletions

View 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>
)
}