diff --git a/admin-compliance/app/api/sdk/v1/ai-registration/[id]/route.ts b/admin-compliance/app/api/sdk/v1/ai-registration/[id]/route.ts new file mode 100644 index 0000000..cc82c76 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/ai-registration/[id]/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from 'next/server' + +const SDK_URL = process.env.SDK_URL || 'http://ai-compliance-sdk:8090' + +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration/${id}`) + const data = await resp.json() + return NextResponse.json(data) + } catch (err) { + return NextResponse.json({ error: 'Failed to fetch registration' }, { status: 500 }) + } +} + +export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + const body = await request.json() + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId }, + body: JSON.stringify(body), + }) + const data = await resp.json() + return NextResponse.json(data, { status: resp.status }) + } catch (err) { + return NextResponse.json({ error: 'Failed to update registration' }, { status: 500 }) + } +} + +export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params + const body = await request.json() + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration/${id}/status`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) + const data = await resp.json() + return NextResponse.json(data, { status: resp.status }) + } catch (err) { + return NextResponse.json({ error: 'Failed to update status' }, { status: 500 }) + } +} diff --git a/admin-compliance/app/api/sdk/v1/ai-registration/route.ts b/admin-compliance/app/api/sdk/v1/ai-registration/route.ts new file mode 100644 index 0000000..785cff3 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/ai-registration/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from 'next/server' + +const SDK_URL = process.env.SDK_URL || 'http://ai-compliance-sdk:8090' + +export async function GET(request: NextRequest) { + try { + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration`, { + headers: { 'X-Tenant-ID': tenantId }, + }) + const data = await resp.json() + return NextResponse.json(data) + } catch (err) { + return NextResponse.json({ error: 'Failed to fetch registrations' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + try { + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + const body = await request.json() + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId }, + body: JSON.stringify(body), + }) + const data = await resp.json() + return NextResponse.json(data, { status: resp.status }) + } catch (err) { + return NextResponse.json({ error: 'Failed to create registration' }, { status: 500 }) + } +} diff --git a/admin-compliance/app/api/sdk/v1/payment-compliance/route.ts b/admin-compliance/app/api/sdk/v1/payment-compliance/route.ts new file mode 100644 index 0000000..872bd93 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/payment-compliance/route.ts @@ -0,0 +1,48 @@ +import { NextRequest, NextResponse } from 'next/server' + +const SDK_URL = process.env.SDK_URL || 'http://ai-compliance-sdk:8090' + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const endpoint = searchParams.get('endpoint') || 'controls' + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + + let path: string + switch (endpoint) { + case 'controls': + const domain = searchParams.get('domain') || '' + path = `/sdk/v1/payment-compliance/controls${domain ? `?domain=${domain}` : ''}` + break + case 'assessments': + path = '/sdk/v1/payment-compliance/assessments' + break + default: + path = '/sdk/v1/payment-compliance/controls' + } + + const resp = await fetch(`${SDK_URL}${path}`, { + headers: { 'X-Tenant-ID': tenantId }, + }) + const data = await resp.json() + return NextResponse.json(data) + } catch (err) { + return NextResponse.json({ error: 'Failed to fetch' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + try { + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + const body = await request.json() + const resp = await fetch(`${SDK_URL}/sdk/v1/payment-compliance/assessments`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId }, + body: JSON.stringify(body), + }) + const data = await resp.json() + return NextResponse.json(data, { status: resp.status }) + } catch (err) { + return NextResponse.json({ error: 'Failed to create' }, { status: 500 }) + } +} diff --git a/admin-compliance/app/api/sdk/v1/payment-compliance/tender/[id]/route.ts b/admin-compliance/app/api/sdk/v1/payment-compliance/tender/[id]/route.ts new file mode 100644 index 0000000..34562ad --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/payment-compliance/tender/[id]/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from 'next/server' + +const SDK_URL = process.env.SDK_URL || 'http://ai-compliance-sdk:8090' + +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params + const resp = await fetch(`${SDK_URL}/sdk/v1/payment-compliance/tender/${id}`) + return NextResponse.json(await resp.json()) + } catch { + return NextResponse.json({ error: 'Failed' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params + const { searchParams } = new URL(request.url) + const action = searchParams.get('action') || 'extract' + const resp = await fetch(`${SDK_URL}/sdk/v1/payment-compliance/tender/${id}/${action}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + }) + return NextResponse.json(await resp.json(), { status: resp.status }) + } catch { + return NextResponse.json({ error: 'Failed' }, { status: 500 }) + } +} diff --git a/admin-compliance/app/api/sdk/v1/payment-compliance/tender/route.ts b/admin-compliance/app/api/sdk/v1/payment-compliance/tender/route.ts new file mode 100644 index 0000000..c62f417 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/payment-compliance/tender/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from 'next/server' + +const SDK_URL = process.env.SDK_URL || 'http://ai-compliance-sdk:8090' + +export async function GET(request: NextRequest) { + try { + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + const resp = await fetch(`${SDK_URL}/sdk/v1/payment-compliance/tender`, { + headers: { 'X-Tenant-ID': tenantId }, + }) + return NextResponse.json(await resp.json()) + } catch { + return NextResponse.json({ error: 'Failed' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + try { + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + const formData = await request.formData() + const resp = await fetch(`${SDK_URL}/sdk/v1/payment-compliance/tender/upload`, { + method: 'POST', + headers: { 'X-Tenant-ID': tenantId }, + body: formData, + }) + return NextResponse.json(await resp.json(), { status: resp.status }) + } catch { + return NextResponse.json({ error: 'Upload failed' }, { status: 500 }) + } +} diff --git a/admin-compliance/app/api/sdk/v1/ucca/decision-tree/[[...path]]/route.ts b/admin-compliance/app/api/sdk/v1/ucca/decision-tree/[[...path]]/route.ts new file mode 100644 index 0000000..ddbb733 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/ucca/decision-tree/[[...path]]/route.ts @@ -0,0 +1,57 @@ +import { NextRequest, NextResponse } from 'next/server' + +const SDK_URL = process.env.SDK_URL || 'http://ai-compliance-sdk:8090' +const DEFAULT_TENANT = process.env.DEFAULT_TENANT_ID || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + +/** + * Proxy: /api/sdk/v1/ucca/decision-tree/... → Go Backend /sdk/v1/ucca/decision-tree/... + */ +async function proxyRequest(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { + const { path } = await params + const subPath = path ? path.join('/') : '' + const search = request.nextUrl.search || '' + const targetUrl = `${SDK_URL}/sdk/v1/ucca/decision-tree/${subPath}${search}` + + const tenantID = request.headers.get('X-Tenant-ID') || DEFAULT_TENANT + + try { + const headers: Record = { + 'X-Tenant-ID': tenantID, + } + + const fetchOptions: RequestInit = { + method: request.method, + headers, + } + + if (request.method === 'POST' || request.method === 'PUT' || request.method === 'PATCH') { + const body = await request.json() + headers['Content-Type'] = 'application/json' + fetchOptions.body = JSON.stringify(body) + } + + const response = await fetch(targetUrl, fetchOptions) + + if (!response.ok) { + const errorText = await response.text() + console.error(`Decision tree proxy error [${request.method} ${subPath}]:`, errorText) + return NextResponse.json( + { error: 'Backend error', details: errorText }, + { status: response.status } + ) + } + + const data = await response.json() + return NextResponse.json(data, { status: response.status }) + } catch (error) { + console.error('Decision tree proxy connection error:', error) + return NextResponse.json( + { error: 'Failed to connect to AI compliance backend' }, + { status: 503 } + ) + } +} + +export const GET = proxyRequest +export const POST = proxyRequest +export const DELETE = proxyRequest diff --git a/admin-compliance/app/api/sdk/v1/ucca/decision-tree/route.ts b/admin-compliance/app/api/sdk/v1/ucca/decision-tree/route.ts new file mode 100644 index 0000000..d8b1c33 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/ucca/decision-tree/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from 'next/server' + +const SDK_URL = process.env.SDK_URL || 'http://ai-compliance-sdk:8090' +const DEFAULT_TENANT = process.env.DEFAULT_TENANT_ID || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + +/** + * Proxy: GET /api/sdk/v1/ucca/decision-tree → Go Backend GET /sdk/v1/ucca/decision-tree + * Returns the decision tree definition (questions, structure) + */ +export async function GET(request: NextRequest) { + const tenantID = request.headers.get('X-Tenant-ID') || DEFAULT_TENANT + + try { + const response = await fetch(`${SDK_URL}/sdk/v1/ucca/decision-tree`, { + headers: { 'X-Tenant-ID': tenantID }, + }) + + if (!response.ok) { + const errorText = await response.text() + console.error('Decision tree GET error:', errorText) + return NextResponse.json( + { error: 'Backend error', details: errorText }, + { status: response.status } + ) + } + + const data = await response.json() + return NextResponse.json(data) + } catch (error) { + console.error('Decision tree proxy error:', error) + return NextResponse.json( + { error: 'Failed to connect to AI compliance backend' }, + { status: 503 } + ) + } +} diff --git a/admin-compliance/app/sdk/advisory-board/_types.ts b/admin-compliance/app/sdk/advisory-board/_types.ts index 0d037ed..3a24935 100644 --- a/admin-compliance/app/sdk/advisory-board/_types.ts +++ b/admin-compliance/app/sdk/advisory-board/_types.ts @@ -7,6 +7,116 @@ export interface AdvisoryForm { custom_data_types: string[] purposes: string[] automation: string + // BetrVG / works council + employee_monitoring: boolean + hr_decision_support: boolean + works_council_consulted: boolean + // Domain-specific contexts (Annex III) + hr_automated_screening: boolean + hr_automated_rejection: boolean + hr_candidate_ranking: boolean + hr_bias_audits: boolean + hr_agg_visible: boolean + hr_human_review: boolean + hr_performance_eval: boolean + edu_grade_influence: boolean + edu_exam_evaluation: boolean + edu_student_selection: boolean + edu_minors: boolean + edu_teacher_review: boolean + hc_diagnosis: boolean + hc_treatment: boolean + hc_triage: boolean + hc_patient_data: boolean + hc_medical_device: boolean + hc_clinical_validation: boolean + // Legal + leg_legal_advice: boolean + leg_court_prediction: boolean + leg_client_confidential: boolean + // Public Sector + pub_admin_decision: boolean + pub_benefit_allocation: boolean + pub_transparency: boolean + // Critical Infrastructure + crit_grid_control: boolean + crit_safety_critical: boolean + crit_redundancy: boolean + // Automotive + auto_autonomous: boolean + auto_safety: boolean + auto_functional_safety: boolean + // Retail + ret_pricing: boolean + ret_profiling: boolean + ret_credit_scoring: boolean + ret_dark_patterns: boolean + // IT Security + its_surveillance: boolean + its_threat_detection: boolean + its_data_retention: boolean + // Logistics + log_driver_tracking: boolean + log_workload_scoring: boolean + // Construction + con_tenant_screening: boolean + con_worker_safety: boolean + // Marketing + mkt_deepfake: boolean + mkt_minors: boolean + mkt_targeting: boolean + mkt_labeled: boolean + // Manufacturing + mfg_machine_safety: boolean + mfg_ce_required: boolean + mfg_validated: boolean + // Agriculture + agr_pesticide: boolean + agr_animal_welfare: boolean + agr_environmental: boolean + // Social Services + soc_vulnerable: boolean + soc_benefit: boolean + soc_case_mgmt: boolean + // Hospitality + hos_guest_profiling: boolean + hos_dynamic_pricing: boolean + hos_review_manipulation: boolean + // Insurance + ins_risk_class: boolean + ins_claims: boolean + ins_premium: boolean + ins_fraud: boolean + // Investment + inv_algo_trading: boolean + inv_advice: boolean + inv_robo: boolean + // Defense + def_dual_use: boolean + def_export: boolean + def_classified: boolean + // Supply Chain + sch_supplier: boolean + sch_human_rights: boolean + sch_environmental: boolean + // Facility + fac_access: boolean + fac_occupancy: boolean + fac_energy: boolean + // Sports + spo_athlete: boolean + spo_fan: boolean + spo_doping: boolean + // Finance / Banking + fin_credit_scoring: boolean + fin_aml_kyc: boolean + fin_algo_decisions: boolean + fin_customer_profiling: boolean + // General + gen_affects_people: boolean + gen_automated_decisions: boolean + gen_sensitive_data: boolean + // Hosting hosting_provider: string hosting_region: string model_usage: string[] diff --git a/admin-compliance/app/sdk/advisory-board/page.tsx b/admin-compliance/app/sdk/advisory-board/page.tsx index a6359ee..0ee1cdd 100644 --- a/admin-compliance/app/sdk/advisory-board/page.tsx +++ b/admin-compliance/app/sdk/advisory-board/page.tsx @@ -51,6 +51,71 @@ function AdvisoryBoardPageInner() { custom_data_types: [], purposes: [], automation: '', + // BetrVG / works council + employee_monitoring: false, + hr_decision_support: false, + works_council_consulted: false, + // Domain-specific contexts (Annex III) + hr_automated_screening: false, + hr_automated_rejection: false, + hr_candidate_ranking: false, + hr_bias_audits: false, + hr_agg_visible: false, + hr_human_review: false, + hr_performance_eval: false, + edu_grade_influence: false, + edu_exam_evaluation: false, + edu_student_selection: false, + edu_minors: false, + edu_teacher_review: false, + hc_diagnosis: false, + hc_treatment: false, + hc_triage: false, + hc_patient_data: false, + hc_medical_device: false, + hc_clinical_validation: false, + // Legal + leg_legal_advice: false, leg_court_prediction: false, leg_client_confidential: false, + // Public Sector + pub_admin_decision: false, pub_benefit_allocation: false, pub_transparency: false, + // Critical Infrastructure + crit_grid_control: false, crit_safety_critical: false, crit_redundancy: false, + // Automotive + auto_autonomous: false, auto_safety: false, auto_functional_safety: false, + // Retail + ret_pricing: false, ret_profiling: false, ret_credit_scoring: false, ret_dark_patterns: false, + // IT Security + its_surveillance: false, its_threat_detection: false, its_data_retention: false, + // Logistics + log_driver_tracking: false, log_workload_scoring: false, + // Construction + con_tenant_screening: false, con_worker_safety: false, + // Marketing + mkt_deepfake: false, mkt_minors: false, mkt_targeting: false, mkt_labeled: false, + // Manufacturing + mfg_machine_safety: false, mfg_ce_required: false, mfg_validated: false, + // Agriculture + agr_pesticide: false, agr_animal_welfare: false, agr_environmental: false, + // Social Services + soc_vulnerable: false, soc_benefit: false, soc_case_mgmt: false, + // Hospitality + hos_guest_profiling: false, hos_dynamic_pricing: false, hos_review_manipulation: false, + // Insurance + ins_risk_class: false, ins_claims: false, ins_premium: false, ins_fraud: false, + // Investment + inv_algo_trading: false, inv_advice: false, inv_robo: false, + // Defense + def_dual_use: false, def_export: false, def_classified: false, + // Supply Chain + sch_supplier: false, sch_human_rights: false, sch_environmental: false, + // Facility + fac_access: false, fac_occupancy: false, fac_energy: false, + // Sports + spo_athlete: false, spo_fan: false, spo_doping: false, + // Finance / Banking + fin_credit_scoring: false, fin_aml_kyc: false, fin_algo_decisions: false, fin_customer_profiling: false, + // General + gen_affects_people: false, gen_automated_decisions: false, gen_sensitive_data: false, hosting_provider: '', hosting_region: '', model_usage: [], @@ -133,7 +198,131 @@ function AdvisoryBoardPageInner() { retention_purpose: form.retention_purpose, contracts_list: form.contracts, subprocessors: form.subprocessors, + employee_monitoring: form.employee_monitoring, + hr_decision_support: form.hr_decision_support, + works_council_consulted: form.works_council_consulted, + // Domain-specific contexts + hr_context: ['hr', 'recruiting'].includes(form.domain) ? { + automated_screening: form.hr_automated_screening, + automated_rejection: form.hr_automated_rejection, + candidate_ranking: form.hr_candidate_ranking, + bias_audits_done: form.hr_bias_audits, + agg_categories_visible: form.hr_agg_visible, + human_review_enforced: form.hr_human_review, + performance_evaluation: form.hr_performance_eval, + } : undefined, + education_context: ['education', 'higher_education', 'vocational_training', 'research'].includes(form.domain) ? { + grade_influence: form.edu_grade_influence, + exam_evaluation: form.edu_exam_evaluation, + student_selection: form.edu_student_selection, + minors_involved: form.edu_minors, + teacher_review_required: form.edu_teacher_review, + } : undefined, + healthcare_context: ['healthcare', 'medical_devices', 'pharma', 'elderly_care'].includes(form.domain) ? { + diagnosis_support: form.hc_diagnosis, + treatment_recommendation: form.hc_treatment, + triage_decision: form.hc_triage, + patient_data_processed: form.hc_patient_data, + medical_device: form.hc_medical_device, + clinical_validation: form.hc_clinical_validation, + } : undefined, + legal_context: ['legal', 'consulting', 'tax_advisory'].includes(form.domain) ? { + legal_advice: form.leg_legal_advice, + court_prediction: form.leg_court_prediction, + client_confidential: form.leg_client_confidential, + } : undefined, + public_sector_context: ['public_sector', 'defense', 'justice'].includes(form.domain) ? { + admin_decision: form.pub_admin_decision, + benefit_allocation: form.pub_benefit_allocation, + transparency_ensured: form.pub_transparency, + } : undefined, + critical_infra_context: ['energy', 'utilities', 'oil_gas'].includes(form.domain) ? { + grid_control: form.crit_grid_control, + safety_critical: form.crit_safety_critical, + redundancy_exists: form.crit_redundancy, + } : undefined, + automotive_context: ['automotive', 'aerospace'].includes(form.domain) ? { + autonomous_driving: form.auto_autonomous, + safety_relevant: form.auto_safety, + functional_safety: form.auto_functional_safety, + } : undefined, + retail_context: ['retail', 'ecommerce', 'wholesale'].includes(form.domain) ? { + pricing_personalized: form.ret_pricing, + credit_scoring: form.ret_credit_scoring, + dark_patterns: form.ret_dark_patterns, + } : undefined, + it_security_context: ['it_services', 'cybersecurity', 'telecom'].includes(form.domain) ? { + employee_surveillance: form.its_surveillance, + threat_detection: form.its_threat_detection, + data_retention_logs: form.its_data_retention, + } : undefined, + logistics_context: ['logistics'].includes(form.domain) ? { + driver_tracking: form.log_driver_tracking, + workload_scoring: form.log_workload_scoring, + } : undefined, + construction_context: ['construction', 'real_estate', 'facility_management'].includes(form.domain) ? { + tenant_screening: form.con_tenant_screening, + worker_safety: form.con_worker_safety, + } : undefined, + marketing_context: ['marketing', 'media', 'entertainment'].includes(form.domain) ? { + deepfake_content: form.mkt_deepfake, + behavioral_targeting: form.mkt_targeting, + minors_targeted: form.mkt_minors, + ai_content_labeled: form.mkt_labeled, + } : undefined, + manufacturing_context: ['mechanical_engineering', 'electrical_engineering', 'plant_engineering', 'chemicals', 'food_beverage'].includes(form.domain) ? { + machine_safety: form.mfg_machine_safety, + ce_marking_required: form.mfg_ce_required, + safety_validated: form.mfg_validated, + } : undefined, + agriculture_context: ['agriculture', 'forestry', 'fishing'].includes(form.domain) ? { + pesticide_ai: form.agr_pesticide, + animal_welfare: form.agr_animal_welfare, + environmental_data: form.agr_environmental, + } : undefined, + social_services_context: ['social_services', 'nonprofit'].includes(form.domain) ? { + vulnerable_groups: form.soc_vulnerable, + benefit_decision: form.soc_benefit, + case_management: form.soc_case_mgmt, + } : undefined, + hospitality_context: ['hospitality', 'tourism'].includes(form.domain) ? { + guest_profiling: form.hos_guest_profiling, + dynamic_pricing: form.hos_dynamic_pricing, + review_manipulation: form.hos_review_manipulation, + } : undefined, + insurance_context: ['insurance'].includes(form.domain) ? { + risk_classification: form.ins_risk_class, + claims_automation: form.ins_claims, + premium_calculation: form.ins_premium, + fraud_detection: form.ins_fraud, + } : undefined, + investment_context: ['investment'].includes(form.domain) ? { + algo_trading: form.inv_algo_trading, + investment_advice: form.inv_advice, + robo_advisor: form.inv_robo, + } : undefined, + defense_context: ['defense'].includes(form.domain) ? { + dual_use: form.def_dual_use, + export_controlled: form.def_export, + classified_data: form.def_classified, + } : undefined, + supply_chain_context: ['textiles', 'packaging'].includes(form.domain) ? { + supplier_monitoring: form.sch_supplier, + human_rights_check: form.sch_human_rights, + environmental_impact: form.sch_environmental, + } : undefined, + facility_context: ['facility_management'].includes(form.domain) ? { + access_control_ai: form.fac_access, + occupancy_tracking: form.fac_occupancy, + energy_optimization: form.fac_energy, + } : undefined, + sports_context: ['sports'].includes(form.domain) ? { + athlete_tracking: form.spo_athlete, + fan_profiling: form.spo_fan, + } : undefined, store_raw_text: true, + // Finance/Banking and General don't need separate context structs — + // their fields are evaluated via existing FinancialContext or generic rules } const url = isEditMode diff --git a/admin-compliance/app/sdk/ai-act/page.tsx b/admin-compliance/app/sdk/ai-act/page.tsx index 1243c12..96522f9 100644 --- a/admin-compliance/app/sdk/ai-act/page.tsx +++ b/admin-compliance/app/sdk/ai-act/page.tsx @@ -8,9 +8,178 @@ import { LoadingSkeleton } from './_components/LoadingSkeleton' import { RiskPyramid } from './_components/RiskPyramid' import { AddSystemForm } from './_components/AddSystemForm' import { AISystemCard } from './_components/AISystemCard' +import DecisionTreeWizard from '@/components/sdk/ai-act/DecisionTreeWizard' + +type TabId = 'overview' | 'decision-tree' | 'results' + +// SAVED RESULTS TAB +// ============================================================================= + +interface SavedResult { + id: string + system_name: string + system_description?: string + high_risk_result: string + gpai_result: { gpai_category: string; is_systemic_risk: boolean } + combined_obligations: string[] + created_at: string +} + +function SavedResultsTab() { + const [results, setResults] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const load = async () => { + try { + const res = await fetch('/api/sdk/v1/ucca/decision-tree/results') + if (res.ok) { + const data = await res.json() + setResults(data.results || []) + } + } catch { + // Ignore + } finally { + setLoading(false) + } + } + load() + }, []) + + const handleDelete = async (id: string) => { + if (!confirm('Ergebnis wirklich löschen?')) return + try { + const res = await fetch(`/api/sdk/v1/ucca/decision-tree/results/${id}`, { method: 'DELETE' }) + if (res.ok) { + setResults(prev => prev.filter(r => r.id !== id)) + } + } catch { + // Ignore + } + } + + const riskLabels: Record = { + unacceptable: 'Unzulässig', + high_risk: 'Hochrisiko', + limited_risk: 'Begrenztes Risiko', + minimal_risk: 'Minimales Risiko', + not_applicable: 'Nicht anwendbar', + } + + const riskColors: Record = { + unacceptable: 'bg-red-100 text-red-700', + high_risk: 'bg-orange-100 text-orange-700', + limited_risk: 'bg-yellow-100 text-yellow-700', + minimal_risk: 'bg-green-100 text-green-700', + not_applicable: 'bg-gray-100 text-gray-500', + } + + const gpaiLabels: Record = { + none: 'Kein GPAI', + standard: 'GPAI Standard', + systemic: 'GPAI Systemisch', + } + + const gpaiColors: Record = { + none: 'bg-gray-100 text-gray-500', + standard: 'bg-blue-100 text-blue-700', + systemic: 'bg-purple-100 text-purple-700', + } + + if (loading) { + return + } + + if (results.length === 0) { + return ( +
+
+ + + +
+

Keine Ergebnisse vorhanden

+

Nutzen Sie den Entscheidungsbaum, um KI-Systeme zu klassifizieren.

+
+ ) + } + + return ( +
+ {results.map(r => ( +
+
+
+

{r.system_name}

+ {r.system_description && ( +

{r.system_description}

+ )} +
+ + {riskLabels[r.high_risk_result] || r.high_risk_result} + + + {gpaiLabels[r.gpai_result?.gpai_category] || 'Kein GPAI'} + + {r.gpai_result?.is_systemic_risk && ( + Systemisch + )} +
+
+ {r.combined_obligations?.length || 0} Pflichten · {new Date(r.created_at).toLocaleDateString('de-DE')} +
+
+ +
+
+ ))} +
+ ) +} + +// TABS +// ============================================================================= + +const TABS: { id: TabId; label: string; icon: React.ReactNode }[] = [ + { + id: 'overview', + label: 'Übersicht', + icon: ( + + + + ), + }, + { + id: 'decision-tree', + label: 'Entscheidungsbaum', + icon: ( + + + + ), + }, + { + id: 'results', + label: 'Ergebnisse', + icon: ( + + + + ), + }, +] + +// MAIN PAGE export default function AIActPage() { const { state } = useSDK() + const [activeTab, setActiveTab] = useState('overview') const [systems, setSystems] = useState([]) const [filter, setFilter] = useState('all') const [showAddForm, setShowAddForm] = useState(false) @@ -178,17 +347,38 @@ export default function AIActPage() { explanation={stepInfo.explanation} tips={stepInfo.tips} > - + {activeTab === 'overview' && ( + + )} + {/* Tabs */} +
+ {TABS.map(tab => ( + + ))} +
+ + {/* Error Banner */} {error && (
{error} @@ -196,82 +386,105 @@ export default function AIActPage() {
)} - {showAddForm && ( - { setShowAddForm(false); setEditingSystem(null) }} - initialData={editingSystem} - /> - )} - -
-
-
KI-Systeme gesamt
-
{systems.length}
-
-
-
Hochrisiko
-
{highRiskCount}
-
-
-
Konform
-
{compliantCount}
-
-
-
Nicht klassifiziert
-
{unclassifiedCount}
-
-
- - - -
- Filter: - {['all', 'high-risk', 'limited-risk', 'minimal-risk', 'unclassified', 'compliant', 'non-compliant'].map(f => ( - - ))} -
- - {loading && } - - {!loading && ( -
- {filteredSystems.map(system => ( - handleAssess(system.id)} - onEdit={() => handleEdit(system)} - onDelete={() => handleDelete(system.id)} - assessing={assessingId === system.id} + {/* Tab: Overview */} + {activeTab === 'overview' && ( + <> + {/* Add/Edit System Form */} + {showAddForm && ( + { setShowAddForm(false); setEditingSystem(null) }} + initialData={editingSystem} /> - ))} -
+ )} + + {/* Stats */} +
+
+
KI-Systeme gesamt
+
{systems.length}
+
+
+
Hochrisiko
+
{highRiskCount}
+
+
+
Konform
+
{compliantCount}
+
+
+
Nicht klassifiziert
+
{unclassifiedCount}
+
+
+ + {/* Risk Pyramid */} + + + {/* Filter */} +
+ Filter: + {['all', 'high-risk', 'limited-risk', 'minimal-risk', 'unclassified', 'compliant', 'non-compliant'].map(f => ( + + ))} +
+ + {/* Loading */} + {loading && } + + {/* AI Systems List */} + {!loading && ( +
+ {filteredSystems.map(system => ( + handleAssess(system.id)} + onEdit={() => handleEdit(system)} + onDelete={() => handleDelete(system.id)} + assessing={assessingId === system.id} + /> + ))} +
+ )} + + {!loading && filteredSystems.length === 0 && ( +
+
+ + + +
+

Keine KI-Systeme gefunden

+

Passen Sie den Filter an oder registrieren Sie ein neues KI-System.

+
+ )} + )} - {!loading && filteredSystems.length === 0 && ( -
-
- - - -
-

Keine KI-Systeme gefunden

-

Passen Sie den Filter an oder registrieren Sie ein neues KI-System.

-
+ {/* Tab: Decision Tree */} + {activeTab === 'decision-tree' && ( + + )} + + {/* Tab: Results */} + {activeTab === 'results' && ( + )} ) diff --git a/admin-compliance/app/sdk/ai-registration/page.tsx b/admin-compliance/app/sdk/ai-registration/page.tsx new file mode 100644 index 0000000..19c4971 --- /dev/null +++ b/admin-compliance/app/sdk/ai-registration/page.tsx @@ -0,0 +1,491 @@ +'use client' + +import React, { useState, useEffect } from 'react' + +interface Registration { + id: string + system_name: string + system_version: string + risk_classification: string + gpai_classification: string + registration_status: string + eu_database_id: string + provider_name: string + created_at: string +} + +const STATUS_STYLES: Record = { + draft: { bg: 'bg-gray-100', text: 'text-gray-700', label: 'Entwurf' }, + ready: { bg: 'bg-blue-100', text: 'text-blue-700', label: 'Bereit' }, + submitted: { bg: 'bg-yellow-100', text: 'text-yellow-700', label: 'Eingereicht' }, + registered: { bg: 'bg-green-100', text: 'text-green-700', label: 'Registriert' }, + update_required: { bg: 'bg-orange-100', text: 'text-orange-700', label: 'Update noetig' }, + withdrawn: { bg: 'bg-red-100', text: 'text-red-700', label: 'Zurueckgezogen' }, +} + +const RISK_STYLES: Record = { + high_risk: { bg: 'bg-red-100', text: 'text-red-700' }, + limited_risk: { bg: 'bg-yellow-100', text: 'text-yellow-700' }, + minimal_risk: { bg: 'bg-green-100', text: 'text-green-700' }, + not_classified: { bg: 'bg-gray-100', text: 'text-gray-500' }, +} + +const INITIAL_FORM = { + system_name: '', + system_version: '1.0', + system_description: '', + intended_purpose: '', + provider_name: '', + provider_legal_form: '', + provider_address: '', + provider_country: 'DE', + eu_representative_name: '', + eu_representative_contact: '', + risk_classification: 'not_classified', + annex_iii_category: '', + gpai_classification: 'none', + conformity_assessment_type: 'internal', + notified_body_name: '', + notified_body_id: '', + ce_marking: false, + training_data_summary: '', +} + +export default function AIRegistrationPage() { + const [registrations, setRegistrations] = useState([]) + const [loading, setLoading] = useState(true) + const [showWizard, setShowWizard] = useState(false) + const [wizardStep, setWizardStep] = useState(1) + const [form, setForm] = useState({ ...INITIAL_FORM }) + const [submitting, setSubmitting] = useState(false) + const [error, setError] = useState(null) + + useEffect(() => { loadRegistrations() }, []) + + async function loadRegistrations() { + try { + setLoading(true) + const resp = await fetch('/api/sdk/v1/ai-registration') + if (resp.ok) { + const data = await resp.json() + setRegistrations(data.registrations || []) + } + } catch { + setError('Fehler beim Laden') + } finally { + setLoading(false) + } + } + + async function handleSubmit() { + setSubmitting(true) + try { + const resp = await fetch('/api/sdk/v1/ai-registration', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(form), + }) + if (resp.ok) { + setShowWizard(false) + setForm({ ...INITIAL_FORM }) + setWizardStep(1) + loadRegistrations() + } else { + const data = await resp.json() + setError(data.error || 'Fehler beim Erstellen') + } + } catch { + setError('Netzwerkfehler') + } finally { + setSubmitting(false) + } + } + + async function handleExport(id: string) { + try { + const resp = await fetch(`/api/sdk/v1/ai-registration/${id}`) + if (resp.ok) { + const reg = await resp.json() + // Build export JSON client-side + const exportData = { + schema_version: '1.0', + submission_type: 'ai_system_registration', + regulation: 'EU AI Act (EU) 2024/1689', + article: 'Art. 49', + provider: { name: reg.provider_name, address: reg.provider_address, country: reg.provider_country }, + system: { name: reg.system_name, version: reg.system_version, description: reg.system_description, purpose: reg.intended_purpose }, + classification: { risk_level: reg.risk_classification, annex_iii: reg.annex_iii_category, gpai: reg.gpai_classification }, + conformity: { type: reg.conformity_assessment_type, ce_marking: reg.ce_marking }, + } + const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `eu_ai_registration_${reg.system_name.replace(/\s+/g, '_')}.json` + a.click() + URL.revokeObjectURL(url) + } + } catch { + setError('Export fehlgeschlagen') + } + } + + async function handleStatusChange(id: string, status: string) { + try { + await fetch(`/api/sdk/v1/ai-registration/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ status }), + }) + loadRegistrations() + } catch { + setError('Status-Aenderung fehlgeschlagen') + } + } + + const updateForm = (updates: Partial) => setForm(prev => ({ ...prev, ...updates })) + + const STEPS = [ + { id: 1, title: 'Anbieter', desc: 'Unternehmensangaben' }, + { id: 2, title: 'System', desc: 'KI-System Details' }, + { id: 3, title: 'Klassifikation', desc: 'Risikoeinstufung' }, + { id: 4, title: 'Konformitaet', desc: 'CE & Notified Body' }, + { id: 5, title: 'Trainingsdaten', desc: 'Datenzusammenfassung' }, + { id: 6, title: 'Pruefung', desc: 'Zusammenfassung & Export' }, + ] + + return ( +
+ {/* Header */} +
+
+

EU AI Database Registrierung

+

Art. 49 KI-Verordnung (EU) 2024/1689 — Registrierung von Hochrisiko-KI-Systemen

+
+ +
+ + {error && ( +
+ {error} + +
+ )} + + {/* Stats */} +
+ {['draft', 'ready', 'submitted', 'registered'].map(status => { + const count = registrations.filter(r => r.registration_status === status).length + const style = STATUS_STYLES[status] + return ( +
+
{count}
+
{style.label}
+
+ ) + })} +
+ + {/* Registrations List */} + {loading ? ( +
Lade...
+ ) : registrations.length === 0 ? ( +
+

Noch keine Registrierungen

+

Erstelle eine neue Registrierung fuer dein Hochrisiko-KI-System.

+
+ ) : ( +
+ {registrations.map(reg => { + const status = STATUS_STYLES[reg.registration_status] || STATUS_STYLES.draft + const risk = RISK_STYLES[reg.risk_classification] || RISK_STYLES.not_classified + return ( +
+
+
+
+

{reg.system_name}

+ v{reg.system_version} + {status.label} + {reg.risk_classification.replace('_', ' ')} + {reg.gpai_classification !== 'none' && ( + GPAI: {reg.gpai_classification} + )} +
+
+ {reg.provider_name && {reg.provider_name} · } + {reg.eu_database_id && EU-ID: {reg.eu_database_id} · } + {new Date(reg.created_at).toLocaleDateString('de-DE')} +
+
+
+ + {reg.registration_status === 'draft' && ( + + )} + {reg.registration_status === 'ready' && ( + + )} +
+
+
+ ) + })} +
+ )} + + {/* Wizard Modal */} + {showWizard && ( +
+
+
+
+

Neue EU AI Registrierung

+ +
+ {/* Step Indicator */} +
+ {STEPS.map(step => ( + + ))} +
+
+ +
+ {/* Step 1: Provider */} + {wizardStep === 1 && ( + <> +

Anbieter-Informationen

+

Angaben zum Anbieter des KI-Systems gemaess Art. 49 KI-VO.

+
+
+ + updateForm({ provider_name: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="Acme GmbH" /> +
+
+ + updateForm({ provider_legal_form: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="GmbH" /> +
+
+
+ + updateForm({ provider_address: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="Musterstr. 1, 20095 Hamburg" /> +
+
+
+ + +
+
+ + updateForm({ eu_representative_name: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="Optional" /> +
+
+ + )} + + {/* Step 2: System */} + {wizardStep === 2 && ( + <> +

KI-System Details

+
+
+ + updateForm({ system_name: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="z.B. HR Copilot" /> +
+
+ + updateForm({ system_version: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="1.0" /> +
+
+
+ +