From 2c35775b44d6be3cdd1d08eb40fea0691bb122e6 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Tue, 10 Mar 2026 07:56:01 +0100 Subject: [PATCH] feat(use-case-workshop): 8-Schritt-Wizard mit UCCA-API-Integration Workshop von 5 auf 8 Schritte erweitert: Datenkategorien (Art.9, Sonstige), Verarbeitungszweck (Rechtsgrundlage), Technologie (Glossar, Modell-Nutzung), Automatisierung (Beispiele, Art.22), Hosting/Transfer, Datenhaltung/Vertraege, Zusammenfassung mit automatischer Risikobewertung und UCCA-API-Aufruf. Co-Authored-By: Claude Opus 4.6 --- .../app/sdk/advisory-board/page.tsx | 1075 ++++++++++++++--- .../lib/sdk/demo-data/use-cases.ts | 6 +- admin-compliance/lib/sdk/types.ts | 49 + 3 files changed, 958 insertions(+), 172 deletions(-) diff --git a/admin-compliance/app/sdk/advisory-board/page.tsx b/admin-compliance/app/sdk/advisory-board/page.tsx index 1276a80..f88f4ca 100644 --- a/admin-compliance/app/sdk/advisory-board/page.tsx +++ b/admin-compliance/app/sdk/advisory-board/page.tsx @@ -2,20 +2,71 @@ import React, { useState } from 'react' import Link from 'next/link' -import { useSDK, UseCaseAssessment } from '@/lib/sdk' +import { useSDK, UseCaseAssessment, UseCaseIntake } from '@/lib/sdk' // ============================================================================= // WIZARD STEPS // ============================================================================= const WIZARD_STEPS = [ - { id: 1, name: 'Grunddaten', description: 'Name und Beschreibung des Use Cases' }, + { id: 1, name: 'Grunddaten', description: 'Name, Beschreibung, Kategorie und Branche' }, { id: 2, name: 'Datenkategorien', description: 'Welche Daten werden verarbeitet?' }, - { id: 3, name: 'Technologie', description: 'Eingesetzte KI-Technologien' }, - { id: 4, name: 'Risikobewertung', description: 'Erste Risikoeinschätzung' }, - { id: 5, name: 'Zusammenfassung', description: 'Überprüfung und Abschluss' }, + { id: 3, name: 'Verarbeitungszweck', description: 'Rechtsgrundlage und Zweck' }, + { id: 4, name: 'Technologie & Modell', description: 'KI-Technologien und Modell-Nutzung' }, + { id: 5, name: 'Automatisierung', description: 'Grad der Automatisierung' }, + { id: 6, name: 'Hosting & Transfer', description: 'Hosting und Datentransfer' }, + { id: 7, name: 'Datenhaltung', description: 'Aufbewahrung und Vertraege' }, + { id: 8, name: 'Zusammenfassung', description: 'Ueberpruefung und Abschluss' }, ] +const TOTAL_STEPS = WIZARD_STEPS.length + +const DOMAINS = [ + { value: 'healthcare', label: 'Gesundheit' }, + { value: 'finance', label: 'Finanzen' }, + { value: 'education', label: 'Bildung' }, + { value: 'retail', label: 'Handel' }, + { value: 'it_services', label: 'IT-Dienstleistungen' }, + { value: 'consulting', label: 'Beratung' }, + { value: 'manufacturing', label: 'Produktion' }, + { value: 'hr', label: 'Personalwesen' }, + { value: 'marketing', label: 'Marketing' }, + { value: 'legal', label: 'Recht' }, + { value: 'public', label: 'Oeffentlicher Sektor' }, + { value: 'general', label: 'Allgemein' }, +] + +// ============================================================================= +// RISK CALCULATION (client-side) +// ============================================================================= + +function calculateRiskScore(form: WizardFormData): { score: number; level: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; dsfaRequired: boolean } { + let score = 0 + if (form.processesPersonalData) score += 10 + if (form.specialCategories) score += 25 + if (form.biometricData) score += 20 + if (form.healthData) score += 15 + if (form.minorsData) score += 15 + if (form.financialData) score += 5 + if (form.purposeProfiling) score += 15 + if (form.purposeAutomatedDecision) score += 20 + if (form.automation === 'fully_automated') score += 25 + else if (form.automation === 'semi_automated') score += 10 + if (form.internationalTransfer) score += 10 + if (form.modelTraining) score += 10 + if (form.modelFinetune) score += 5 + + let level: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' + if (score >= 60) level = 'CRITICAL' + else if (score >= 40) level = 'HIGH' + else if (score >= 20) level = 'MEDIUM' + else level = 'LOW' + + const dsfaRequired = score >= 40 || form.specialCategories || (form.purposeAutomatedDecision && form.automation === 'fully_automated') + + return { score, level, dsfaRequired } +} + // ============================================================================= // USE CASE CARD // ============================================================================= @@ -31,7 +82,7 @@ function UseCaseCard({ onSelect: () => void onDelete: () => void }) { - const completionPercent = Math.round((useCase.stepsCompleted / 5) * 100) + const completionPercent = Math.round((useCase.stepsCompleted / TOTAL_STEPS) * 100) return (
void }) { const [currentStep, setCurrentStep] = useState(1) + const [isSubmitting, setIsSubmitting] = useState(false) + const [uccaError, setUccaError] = useState(null) const [formData, setFormData] = useState({ name: '', description: '', category: '', + domain: 'general', dataCategories: [], processesPersonalData: false, specialCategories: false, + healthData: false, + biometricData: false, + minorsData: false, + financialData: false, + customDataTypes: [], + legalBasis: 'consent', + purposeProfiling: false, + purposeAutomatedDecision: false, + purposeMarketing: false, + purposeAnalytics: false, + purposeServiceDelivery: false, aiTechnologies: [], - dataVolume: 'medium', - riskLevel: 'medium', + modelInference: true, + modelRag: false, + modelFinetune: false, + modelTraining: false, + automation: 'assistive', + hostingProvider: 'self_hosted', + hostingRegion: 'eu', + internationalTransfer: false, + transferCountries: [], + transferMechanism: 'none', + retentionDays: 90, + retentionPurpose: '', + hasDpa: false, + hasAiaDocumentation: false, + hasRiskAssessment: false, + subprocessors: '', notes: '', }) @@ -158,43 +275,174 @@ function UseCaseWizard({ setFormData(prev => ({ ...prev, ...updates })) } - const handleNext = () => { - if (currentStep < 5) { + const buildIntake = (): UseCaseIntake => ({ + domain: formData.domain, + dataCategories: formData.dataCategories, + processesPersonalData: formData.processesPersonalData, + specialCategories: formData.specialCategories, + healthData: formData.healthData, + biometricData: formData.biometricData, + minorsData: formData.minorsData, + financialData: formData.financialData, + customDataTypes: formData.customDataTypes.filter(s => s.trim()), + legalBasis: formData.legalBasis, + purposes: { + profiling: formData.purposeProfiling, + automatedDecision: formData.purposeAutomatedDecision, + marketing: formData.purposeMarketing, + analytics: formData.purposeAnalytics, + serviceDelivery: formData.purposeServiceDelivery, + }, + automation: formData.automation, + hosting: { + provider: formData.hostingProvider, + region: formData.hostingRegion, + }, + modelUsage: { + inference: formData.modelInference, + rag: formData.modelRag, + finetune: formData.modelFinetune, + training: formData.modelTraining, + }, + aiTechnologies: formData.aiTechnologies, + internationalTransfer: { + enabled: formData.internationalTransfer, + countries: formData.transferCountries, + mechanism: formData.transferMechanism, + }, + retention: { + days: formData.retentionDays, + purpose: formData.retentionPurpose, + }, + contracts: { + hasDpa: formData.hasDpa, + hasAiaDocumentation: formData.hasAiaDocumentation, + hasRiskAssessment: formData.hasRiskAssessment, + subprocessors: formData.subprocessors, + }, + }) + + const handleNext = async () => { + if (currentStep < TOTAL_STEPS) { setCurrentStep(prev => prev + 1) - } else { - // Create use case - const newUseCase: UseCaseAssessment = { - id: `uc-${Date.now()}`, - name: formData.name, - description: formData.description, - category: formData.category, - stepsCompleted: 5, - steps: WIZARD_STEPS.map(s => ({ - id: `step-${s.id}`, - name: s.name, - completed: true, - data: {}, - })), - assessmentResult: { - riskLevel: formData.riskLevel as 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL', - applicableRegulations: ['DSGVO', 'AI Act'], - recommendedControls: ['Datenschutz-Folgenabschätzung', 'Technische Maßnahmen'], - dsfaRequired: formData.specialCategories || formData.riskLevel === 'HIGH', - aiActClassification: formData.aiTechnologies.length > 0 ? 'LIMITED' : 'MINIMAL', - }, - createdAt: new Date(), - updatedAt: new Date(), - } - onComplete(newUseCase) + return } + + // Final step — create use case + call UCCA API + setIsSubmitting(true) + setUccaError(null) + + const risk = calculateRiskScore(formData) + const intake = buildIntake() + + const newUseCase: UseCaseAssessment = { + id: `uc-${Date.now()}`, + name: formData.name, + description: formData.description, + category: formData.category, + stepsCompleted: TOTAL_STEPS, + steps: WIZARD_STEPS.map(s => ({ + id: `step-${s.id}`, + name: s.name, + completed: true, + data: {}, + })), + assessmentResult: { + riskLevel: risk.level, + applicableRegulations: ['DSGVO', 'AI Act'], + recommendedControls: buildRecommendations(formData, risk), + dsfaRequired: risk.dsfaRequired, + aiActClassification: formData.aiTechnologies.length > 0 + ? (risk.level === 'CRITICAL' || risk.level === 'HIGH' ? 'HIGH' : 'LIMITED') + : 'MINIMAL', + }, + intake, + createdAt: new Date(), + updatedAt: new Date(), + } + + // Call UCCA API in background + try { + const uccaIntake = { + title: formData.name, + use_case_text: formData.description, + domain: formData.domain, + data_types: { + personal_data: formData.processesPersonalData, + special_categories: formData.specialCategories, + health_data: formData.healthData, + biometric_data: formData.biometricData, + minors_data: formData.minorsData, + financial_data: formData.financialData, + custom_data_types: formData.customDataTypes.filter(s => s.trim()), + }, + purpose: { + profiling: formData.purposeProfiling, + automated_decision: formData.purposeAutomatedDecision, + marketing: formData.purposeMarketing, + analytics: formData.purposeAnalytics, + service_delivery: formData.purposeServiceDelivery, + }, + automation: formData.automation, + hosting: { provider: formData.hostingProvider, region: formData.hostingRegion }, + model_usage: { + inference: formData.modelInference, + rag: formData.modelRag, + finetune: formData.modelFinetune, + training: formData.modelTraining, + }, + legal_basis: formData.legalBasis, + international_transfer: { + enabled: formData.internationalTransfer, + countries: formData.transferCountries, + mechanism: formData.transferMechanism, + }, + retention: { days: formData.retentionDays, purpose: formData.retentionPurpose }, + contracts: { + has_dpa: formData.hasDpa, + has_aia_documentation: formData.hasAiaDocumentation, + has_risk_assessment: formData.hasRiskAssessment, + subprocessors: formData.subprocessors, + }, + store_raw_text: true, + } + + const resp = await fetch('/api/sdk/v1/ucca/assess', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(uccaIntake), + }) + + if (resp.ok) { + const data = await resp.json() + // Merge UCCA result if available + if (data.assessment?.id) { + newUseCase.uccaAssessmentId = data.assessment.id + } + if (data.result) { + newUseCase.assessmentResult = { + ...newUseCase.assessmentResult!, + riskLevel: data.result.risk_level || newUseCase.assessmentResult!.riskLevel, + dsfaRequired: data.result.dsfa_required ?? newUseCase.assessmentResult!.dsfaRequired, + } + } + } else { + setUccaError('UCCA-API nicht erreichbar — lokale Risikobewertung wird verwendet.') + } + } catch { + setUccaError('UCCA-API nicht erreichbar — lokale Risikobewertung wird verwendet.') + } + + setIsSubmitting(false) + onComplete(newUseCase) } const handleBack = () => { - if (currentStep > 1) { - setCurrentStep(prev => prev - 1) - } + if (currentStep > 1) setCurrentStep(prev => prev - 1) } + const risk = calculateRiskScore(formData) + return (
{/* Header */} @@ -208,20 +456,21 @@ function UseCaseWizard({
{/* Progress */} -
+
{WIZARD_STEPS.map((step, index) => (
{step.id < currentStep ? ( - + ) : ( @@ -239,12 +488,15 @@ function UseCaseWizard({ ))}

- Schritt {currentStep}: {WIZARD_STEPS[currentStep - 1].description} + Schritt {currentStep} von {TOTAL_STEPS}: {WIZARD_STEPS[currentStep - 1].description}

{/* Content */}
+ {/* ============================================ */} + {/* STEP 1: Grunddaten */} + {/* ============================================ */} {currentStep === 1 && (
@@ -253,7 +505,7 @@ function UseCaseWizard({ type="text" value={formData.name} onChange={e => updateFormData({ name: e.target.value })} - placeholder="z.B. Marketing-KI für Kundensegmentierung" + placeholder="z.B. Marketing-KI fuer Kundensegmentierung" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
@@ -262,7 +514,7 @@ function UseCaseWizard({