This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/admin-v2/app/(admin)/ai/rag/page.tsx
BreakPilot Dev f927c0c205 feat(rag): Add DACH legal corpus ingestion (DE/AT/CH laws)
Add 29 new regulations (7 DE + 7 AT + 4 CH + 11 P2/P3) with country
metadata, legal corpus text excerpts, and updated RAG admin UI with
AT/CH type colors and labels. Fix module path in deploy script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 09:24:33 +01:00

3273 lines
159 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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'
/**
* RAG & Legal Corpus Management
*
* Verwaltet das Legal Corpus RAG System fuer Compliance.
* Zeigt Status aller 19 Regulierungen, Chunks und ermoeglicht Suche.
*/
import React, { useState, useEffect, useCallback } from 'react'
import Link from 'next/link'
import { PagePurpose } from '@/components/common/PagePurpose'
import { AIModuleSidebarResponsive } from '@/components/ai/AIModuleSidebar'
// API uses local proxy route to klausur-service
const API_PROXY = '/api/legal-corpus'
const DSFA_API_PROXY = '/api/dsfa-corpus'
// Types
interface RegulationStatus {
code: string
name: string
fullName: string
type: string
chunkCount: number
expectedRequirements: number
sourceUrl: string
status: 'ready' | 'empty' | 'error'
}
interface CollectionStatus {
collection: string
totalPoints: number
vectorSize: number
status: string
regulations: Record<string, number>
}
interface SearchResult {
text: string
regulation_code: string
regulation_name: string
article: string | null
paragraph: string | null
source_url: string
score: number
}
// DSFA source type (from /api/dsfa-corpus)
interface DsfaSource {
source_code: string
name: string
full_name?: string
organization?: string
source_url?: string
license_code: string
attribution_text: string
document_type: string
language: string
chunk_count?: number
}
interface DsfaCorpusStatus {
qdrant_collection: string
total_sources: number
total_documents: number
total_chunks: number
qdrant_points_count: number
qdrant_status: string
}
// RAG category filter for Regulations tab
type RegulationCategory = 'regulations' | 'dsfa' | 'nibis' | 'templates'
// Tab definitions
type TabId = 'overview' | 'regulations' | 'map' | 'search' | 'data' | 'ingestion' | 'pipeline'
// Custom document type
interface CustomDocument {
id: string
code: string
title: string
filename?: string
url?: string
document_type: string
uploaded_at: string
status: 'uploaded' | 'queued' | 'fetching' | 'processing' | 'indexed' | 'error'
chunk_count: number
error?: string
}
// Pipeline types
interface Validation {
name: string
status: 'passed' | 'warning' | 'failed' | 'not_run'
expected: any
actual: any
message: string
}
interface PipelineCheckpoint {
phase: string
name: string
status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped'
started_at: string | null
completed_at: string | null
duration_seconds: number | null
metrics: Record<string, any>
validations: Validation[]
error: string | null
}
interface PipelineState {
status: string
pipeline_id: string | null
started_at: string | null
completed_at: string | null
current_phase: string | null
checkpoints: PipelineCheckpoint[]
summary: Record<string, any>
validation_summary?: {
passed: number
warning: number
failed: number
total: number
}
}
// All regulations with descriptions
const REGULATIONS = [
{
code: 'GDPR',
name: 'DSGVO',
fullName: 'Datenschutz-Grundverordnung (GDPR)',
type: 'eu_regulation',
expected: 99,
description: 'Die DSGVO ist das zentrale Datenschutzgesetz der EU. Sie regelt die Verarbeitung personenbezogener Daten und gibt Betroffenen umfangreiche Rechte (Auskunft, Loeschung, Datenportabilitaet). Gilt fuer alle Unternehmen, die Daten von EU-Buergern verarbeiten.',
relevantFor: ['Alle Unternehmen mit EU-Kunden', 'Datenverarbeiter', 'Auftragsverarbeiter'],
keyTopics: ['Einwilligung', 'Betroffenenrechte', 'Datenschutz-Folgenabschaetzung', 'Auftragsverarbeitung', 'Bussgelder bis 4% Umsatz'],
effectiveDate: '25. Mai 2018'
},
{
code: 'EPRIVACY',
name: 'ePrivacy-Richtlinie',
fullName: 'Richtlinie 2002/58/EG (ePrivacy)',
type: 'eu_directive',
expected: 25,
description: 'Ergaenzt die DSGVO speziell fuer elektronische Kommunikation. Regelt Cookies, Tracking, Direktmarketing und Vertraulichkeit der Kommunikation. Wird durch nationale Gesetze wie das TDDDG umgesetzt.',
relevantFor: ['Website-Betreiber', 'App-Entwickler', 'Marketing-Abteilungen', 'Telekommunikationsanbieter'],
keyTopics: ['Cookie-Consent', 'Tracking', 'E-Mail-Marketing', 'Vertraulichkeit'],
effectiveDate: '31. Juli 2002'
},
{
code: 'TDDDG',
name: 'TDDDG',
fullName: 'Telekommunikation-Digitale-Dienste-Datenschutz-Gesetz',
type: 'de_law',
expected: 30,
description: 'Deutsches Umsetzungsgesetz der ePrivacy-Richtlinie. Regelt den Datenschutz bei Telemedien und Telekommunikation. Enthaelt die strengen deutschen Cookie-Consent-Anforderungen.',
relevantFor: ['Deutsche Unternehmen', 'Website-Betreiber mit deutschen Nutzern', 'App-Anbieter'],
keyTopics: ['Cookie-Banner', 'Einwilligung fuer Endgeraetezugriff', 'Tracking-Consent', 'Bestandsdaten'],
effectiveDate: '1. Dezember 2021'
},
{
code: 'SCC',
name: 'Standardvertragsklauseln',
fullName: 'EU-Standardvertragsklauseln (2021/914/EU)',
type: 'eu_regulation',
expected: 18,
description: 'Vorgefertigte Vertragsklauseln fuer den internationalen Datentransfer. Erforderlich, wenn personenbezogene Daten in Drittlaender ohne Angemessenheitsbeschluss uebermittelt werden (z.B. USA vor DPF).',
relevantFor: ['Unternehmen mit Drittland-Transfers', 'Cloud-Nutzer', 'Internationale Konzerne'],
keyTopics: ['Drittlandtransfer', 'Controller-to-Controller', 'Controller-to-Processor', 'TIA (Transfer Impact Assessment)'],
effectiveDate: '27. Juni 2021'
},
{
code: 'DPF',
name: 'EU-US Data Privacy Framework',
fullName: 'EU-US Data Privacy Framework',
type: 'eu_regulation',
expected: 12,
description: 'Angemessenheitsbeschluss fuer Datentransfers in die USA. Nachfolger des gekippten Privacy Shield. Ermoeglicht Datenuebermittlungen an zertifizierte US-Unternehmen ohne zusaetzliche Garantien.',
relevantFor: ['Unternehmen mit US-Dienstleistern', 'Cloud-Nutzer (AWS, Google, Microsoft)', 'SaaS-Anwender'],
keyTopics: ['US-Datentransfer', 'Zertifizierung', 'Redress-Mechanismus', 'Privacy Shield Nachfolger'],
effectiveDate: '10. Juli 2023'
},
{
code: 'AIACT',
name: 'EU AI Act',
fullName: 'Verordnung (EU) 2024/1689 - KI-Verordnung',
type: 'eu_regulation',
expected: 85,
description: 'Weltweit erste umfassende KI-Regulierung. Klassifiziert KI-Systeme nach Risiko (verboten, hoch, begrenzt, minimal) und stellt entsprechende Anforderungen. Hochrisiko-KI benoetigt CE-Kennzeichnung.',
relevantFor: ['KI-Entwickler', 'KI-Anwender', 'Hersteller von KI-Produkten', 'Importeure'],
keyTopics: ['Risikoklassifizierung', 'Hochrisiko-KI', 'Verbotene KI', 'Transparenzpflichten', 'CE-Kennzeichnung'],
effectiveDate: '1. August 2024 (gestaffelt bis 2027)'
},
{
code: 'CRA',
name: 'Cyber Resilience Act',
fullName: 'Verordnung (EU) 2024/2847 - Cyber Resilience Act',
type: 'eu_regulation',
expected: 45,
description: 'Cybersicherheitsanforderungen fuer Produkte mit digitalen Elementen. Verpflichtet Hersteller zu Security-by-Design, Schwachstellenmanagement und Security-Updates ueber den Lebenszyklus.',
relevantFor: ['IoT-Hersteller', 'Software-Entwickler', 'Hardware-Hersteller', 'Importeure'],
keyTopics: ['Security by Design', 'Schwachstellenmanagement', 'SBOM', 'CE-Kennzeichnung', 'Security Updates'],
effectiveDate: '2024 (Uebergangsfristen bis 2027)'
},
{
code: 'NIS2',
name: 'NIS2-Richtlinie',
fullName: 'Richtlinie (EU) 2022/2555 - Network and Information Security',
type: 'eu_directive',
expected: 46,
description: 'Cybersicherheitsrichtlinie fuer kritische und wichtige Einrichtungen. Erweitert NIS1 erheblich auf mehr Sektoren. Fordert Risikomanagement, Incident Reporting und Lieferkettensicherheit.',
relevantFor: ['Kritische Infrastrukturen (KRITIS)', 'Gesundheitswesen', 'Energie', 'Transport', 'Digitale Dienste', 'Oeffentliche Verwaltung'],
keyTopics: ['Cybersicherheits-Risikomanagement', 'Incident Reporting (24h)', 'Lieferkettensicherheit', 'Geschaeftsfuehrer-Haftung'],
effectiveDate: '17. Oktober 2024 (nationale Umsetzung)'
},
{
code: 'EUCSA',
name: 'EU Cybersecurity Act',
fullName: 'Verordnung (EU) 2019/881 - EU Cybersecurity Act',
type: 'eu_regulation',
expected: 35,
description: 'Schafft den EU-Rahmen fuer Cybersicherheitszertifizierung und staerkt die ENISA. Ermoeglicht EU-weite Zertifizierungsschemata fuer IT-Produkte, -Dienste und -Prozesse.',
relevantFor: ['IT-Produkthersteller', 'Cloud-Anbieter', 'Zertifizierungsstellen', 'Oeffentliche Auftraggeber'],
keyTopics: ['Cybersicherheitszertifizierung', 'ENISA', 'Vertrauensstufen (basic/substantial/high)', 'EUCC Schema'],
effectiveDate: '27. Juni 2019'
},
{
code: 'DATAACT',
name: 'Data Act',
fullName: 'Verordnung (EU) 2023/2854 - Data Act',
type: 'eu_regulation',
expected: 42,
description: 'Regelt den Zugang zu und die Nutzung von Daten. Gibt Nutzern Rechte an Daten, die durch vernetzte Produkte erzeugt werden. Ermoeglicht Datenweitergabe und Cloud-Wechsel.',
relevantFor: ['IoT-Hersteller', 'Cloud-Anbieter', 'Dateninhaber', 'Datennutzer', 'Oeffentliche Stellen'],
keyTopics: ['Datenzugang', 'Datenportabilitaet', 'Cloud-Switching', 'B2B-Datenteilung', 'IoT-Daten'],
effectiveDate: '12. September 2025'
},
{
code: 'DGA',
name: 'Data Governance Act',
fullName: 'Verordnung (EU) 2022/868 - Data Governance Act',
type: 'eu_regulation',
expected: 35,
description: 'Schafft Rahmenbedingungen fuer Datenmaerkte und Datenaltruismus. Regelt Datenvermittlungsdienste und die Weiterverwendung geschuetzter oeffentlicher Daten.',
relevantFor: ['Datenvermittler', 'Oeffentliche Stellen', 'Forschungseinrichtungen', 'Datenaltruismus-Organisationen'],
keyTopics: ['Datenvermittlung', 'Datenaltruismus', 'Oeffentliche Daten', 'Datentreuhaender'],
effectiveDate: '24. September 2023'
},
{
code: 'DSA',
name: 'Digital Services Act',
fullName: 'Verordnung (EU) 2022/2065 - Digital Services Act',
type: 'eu_regulation',
expected: 93,
description: 'Reguliert digitale Dienste und Plattformen. Schafft Pflichten fuer Online-Vermittler, Hosting-Dienste und Plattformen. Sehr grosse Plattformen (VLOPs) haben erweiterte Pflichten.',
relevantFor: ['Online-Plattformen', 'Marktplaetze', 'Social Media', 'Hosting-Anbieter', 'Suchmaschinen'],
keyTopics: ['Notice-and-Action', 'Transparenz', 'Illegale Inhalte', 'VLOP-Pflichten', 'Algorithmen-Transparenz'],
effectiveDate: '17. Februar 2024'
},
{
code: 'EAA',
name: 'European Accessibility Act',
fullName: 'Richtlinie (EU) 2019/882 - Barrierefreiheitsanforderungen',
type: 'eu_directive',
expected: 25,
description: 'Barrierefreiheitsanforderungen fuer Produkte und Dienstleistungen. Betrifft Computer, Smartphones, Bankdienstleistungen, E-Commerce, E-Books und mehr.',
relevantFor: ['E-Commerce', 'Banken', 'Telekommunikation', 'Verkehrsdienste', 'Medienanbieter'],
keyTopics: ['Barrierefreiheit', 'WCAG', 'Assistive Technologien', 'Ausnahmen fuer KMU'],
effectiveDate: '28. Juni 2025'
},
{
code: 'DSM',
name: 'DSM-Urheberrechtsrichtlinie',
fullName: 'Richtlinie (EU) 2019/790 - Digital Single Market Copyright',
type: 'eu_directive',
expected: 22,
description: 'Modernisiert das EU-Urheberrecht fuer das digitale Zeitalter. Enthaelt kontroverse Artikel zu Uploadfiltern (Art. 17) und Leistungsschutzrecht fuer Presseverleger (Art. 15).',
relevantFor: ['Content-Plattformen', 'Nachrichtenaggregatoren', 'Bildungseinrichtungen', 'Kulturerbe-Einrichtungen'],
keyTopics: ['Upload-Filter (Art. 17)', 'Leistungsschutzrecht', 'Text-und-Data-Mining', 'Verguetungsansprueche'],
effectiveDate: '7. Juni 2021'
},
{
code: 'PLD',
name: 'Produkthaftungsrichtlinie',
fullName: 'Richtlinie 85/374/EWG (aktualisiert) - Produkthaftung',
type: 'eu_directive',
expected: 18,
description: 'Regelt die Haftung fuer fehlerhafte Produkte. Aktualisierte Version umfasst auch Software und KI. Hersteller haften verschuldensunabhaengig fuer Produktfehler.',
relevantFor: ['Produkthersteller', 'Software-Entwickler', 'KI-Anbieter', 'Importeure'],
keyTopics: ['Verschuldensunabhaengige Haftung', 'Software als Produkt', 'KI-Haftung', 'Beweislast'],
effectiveDate: 'Ueberarbeitung 2024'
},
{
code: 'GPSR',
name: 'General Product Safety',
fullName: 'Verordnung (EU) 2023/988 - Allgemeine Produktsicherheit',
type: 'eu_regulation',
expected: 30,
description: 'Ersetzt die alte Produktsicherheitsrichtlinie. Stellt sicher, dass nur sichere Verbraucherprodukte auf den EU-Markt gelangen. Gilt auch fuer Online-Marktplaetze.',
relevantFor: ['Produkthersteller', 'Importeure', 'Online-Marktplaetze', 'Haendler'],
keyTopics: ['Produktsicherheit', 'Marktplatzhaftung', 'Rueckrufe', 'Safety Gate'],
effectiveDate: '13. Dezember 2024'
},
{
code: 'BSI-TR-03161-1',
name: 'BSI-TR Teil 1',
fullName: 'BSI TR-03161 Teil 1 - Sicherheitsanforderungen an Digitale Gesundheitsanwendungen (DiGA) - Mobile Anwendungen',
type: 'bsi_standard',
expected: 45,
description: 'Deutsche Technische Richtlinie fuer die Sicherheit mobiler Gesundheits-Apps (DiGA). Definiert Pruefverfahren und Sicherheitsanforderungen fuer die DiGA-Zulassung.',
relevantFor: ['DiGA-Hersteller', 'Mobile-App-Entwickler im Gesundheitswesen', 'Pruefstellen'],
keyTopics: ['Mobile App Security', 'Authentifizierung', 'Datenverschluesselung', 'Secure Coding'],
effectiveDate: 'Version 1.0: 2020'
},
{
code: 'BSI-TR-03161-2',
name: 'BSI-TR Teil 2',
fullName: 'BSI TR-03161 Teil 2 - Sicherheitsanforderungen an Digitale Gesundheitsanwendungen (DiGA) - Web-Anwendungen',
type: 'bsi_standard',
expected: 40,
description: 'Technische Richtlinie fuer die Sicherheit von Web-Anwendungen im Gesundheitswesen. Ergaenzt Teil 1 um spezifische Anforderungen fuer Web-Frontends.',
relevantFor: ['DiGA-Hersteller mit Web-Apps', 'Web-Entwickler im Gesundheitswesen', 'Pruefstellen'],
keyTopics: ['Web Application Security', 'OWASP Top 10', 'Session Management', 'TLS'],
effectiveDate: 'Version 1.0: 2020'
},
{
code: 'BSI-TR-03161-3',
name: 'BSI-TR Teil 3',
fullName: 'BSI TR-03161 Teil 3 - Sicherheitsanforderungen an Digitale Gesundheitsanwendungen (DiGA) - Hintergrundsysteme',
type: 'bsi_standard',
expected: 35,
description: 'Technische Richtlinie fuer Backend-Systeme von Gesundheitsanwendungen. Deckt Server, APIs, Datenbanken und Cloud-Infrastruktur ab.',
relevantFor: ['DiGA-Backend-Entwickler', 'Cloud-Architekten im Gesundheitswesen', 'DevOps-Teams'],
keyTopics: ['Backend Security', 'API Security', 'Datenbanksicherheit', 'Cloud Security', 'Logging'],
effectiveDate: 'Version 1.0: 2020'
},
// Financial Sector Regulations
{
code: 'DORA',
name: 'DORA',
fullName: 'Verordnung (EU) 2022/2554 - Digital Operational Resilience Act',
type: 'eu_regulation',
expected: 64,
description: 'Digitale operationale Resilienz fuer den Finanzsektor. Verpflichtet Finanzunternehmen zu umfassendem IKT-Risikomanagement, Vorfallmeldung, Resilienz-Tests und Drittanbieter-Management.',
relevantFor: ['Banken', 'Versicherungen', 'Wertpapierfirmen', 'Zahlungsdienstleister', 'Krypto-Anbieter', 'IKT-Drittanbieter'],
keyTopics: ['IKT-Risikomanagement', 'Incident Reporting', 'Resilience Testing', 'Third-Party Risk', 'Threat-Led Penetration Testing'],
effectiveDate: '17. Januar 2025'
},
{
code: 'PSD2',
name: 'PSD2',
fullName: 'Richtlinie (EU) 2015/2366 - Zahlungsdiensterichtlinie',
type: 'eu_directive',
expected: 117,
description: 'Reguliert Zahlungsdienste im EU-Binnenmarkt. Fuehrt Open Banking ein, verpflichtet zu starker Kundenauthentifizierung (SCA) und ermoeglicht Drittanbieterzugang zu Bankkonten.',
relevantFor: ['Banken', 'Zahlungsdienstleister', 'FinTechs', 'E-Commerce-Anbieter', 'Kontoinformationsdienste'],
keyTopics: ['Strong Customer Authentication (SCA)', 'Open Banking APIs', 'PSD2-Schnittstellen', 'Kontoinformationsdienste', 'Zahlungsauslösedienste'],
effectiveDate: '13. Januar 2018'
},
{
code: 'AMLR',
name: 'AML-Verordnung',
fullName: 'Verordnung (EU) 2024/1624 - Anti-Money Laundering Regulation',
type: 'eu_regulation',
expected: 89,
description: 'EU-weite Verordnung zur Bekaempfung von Geldwaesche und Terrorismusfinanzierung. Ersetzt die bisherigen Richtlinien durch direkt anwendbare Vorschriften. Schafft einheitliche Sorgfaltspflichten.',
relevantFor: ['Banken', 'Finanzdienstleister', 'Krypto-Anbieter', 'Immobilienmakler', 'Wirtschaftspruefer', 'Notare'],
keyTopics: ['Sorgfaltspflichten (KYC)', 'Verdachtsmeldungen', 'Wirtschaftlich Berechtigte', 'Risikobewertung', 'AMLA (neue EU-Behoerde)'],
effectiveDate: '2027 (gestaffelt)'
},
{
code: 'MiCA',
name: 'MiCA',
fullName: 'Verordnung (EU) 2023/1114 - Markets in Crypto-Assets',
type: 'eu_regulation',
expected: 149,
description: 'Umfassende Regulierung fuer Kryptowerte, Stablecoins und Crypto-Asset-Dienstleister. Schafft EU-weiten Rechtsrahmen fuer Krypto-Maerkte mit Zulassungspflichten und Verbraucherschutz.',
relevantFor: ['Krypto-Boersen', 'Wallet-Anbieter', 'Stablecoin-Emittenten', 'Token-Herausgeber', 'Krypto-Verwahrer', 'FinTechs'],
keyTopics: ['Krypto-Zulassung', 'Stablecoin-Regulierung', 'Whitepaper-Pflicht', 'Marktmissbrauch', 'Verwahrung'],
effectiveDate: '30. Dezember 2024'
},
{
code: 'EHDS',
name: 'EHDS',
fullName: 'Verordnung (EU) 2025/327 - Europaeischer Gesundheitsdatenraum',
type: 'eu_regulation',
expected: 95,
description: 'Schafft den Europaeischen Raum fuer Gesundheitsdaten. Ermoeglicht Primaernutzung (Patientenrechte) und Sekundaernutzung (Forschung, KI-Training) von Gesundheitsdaten unter strengen Auflagen.',
relevantFor: ['Krankenhaeuser', 'Aerzte', 'Gesundheits-Apps', 'Pharma', 'Forschungseinrichtungen', 'Versicherungen'],
keyTopics: ['Patientenakte (MyHealth@EU)', 'Sekundaernutzung', 'Datenzugangsorgane', 'Gesundheitsdatenstandards', 'Forschungszugang'],
effectiveDate: '2025 (gestaffelt bis 2029)'
},
// National Data Protection Laws (migrated from bp_dsfa_corpus)
{
code: 'AT_DSG',
name: 'DSG Oesterreich',
fullName: 'Datenschutzgesetz Oesterreich (DSG)',
type: 'national_law',
expected: 50,
description: 'Oesterreichisches Datenschutzgesetz zur Ergaenzung der DSGVO. Regelt nationale Besonderheiten wie Bildverarbeitung, Datenschutzbehoerde und Strafbestimmungen.',
relevantFor: ['Unternehmen in Oesterreich', 'DACH-Unternehmen', 'Auftragsverarbeiter'],
keyTopics: ['Nationale DSGVO-Ergaenzung', 'Bildverarbeitung', 'Datenschutzbehoerde', 'Strafbestimmungen'],
effectiveDate: '25. Mai 2018'
},
{
code: 'BDSG_FULL',
name: 'BDSG',
fullName: 'Bundesdatenschutzgesetz (BDSG) - Volltext',
type: 'de_law',
expected: 9,
description: 'Deutsches Bundesdatenschutzgesetz als nationale Ergaenzung zur DSGVO. Regelt Beschaeftigtendatenschutz, Videoueberachung, Scoring und Datenschutzbeauftragte.',
relevantFor: ['Deutsche Unternehmen', 'Arbeitgeber', 'Auskunfteien', 'Oeffentliche Stellen'],
keyTopics: ['Beschaeftigtendatenschutz', 'Videoueberachung', 'Scoring', 'Datenschutzbeauftragter'],
effectiveDate: '25. Mai 2018'
},
{
code: 'CH_DSG',
name: 'DSG Schweiz',
fullName: 'Datenschutzgesetz Schweiz (revDSG 2023)',
type: 'national_law',
expected: 2,
description: 'Revidiertes Schweizer Datenschutzgesetz mit DSGVO-nahen Anforderungen. Gilt fuer Schweizer Unternehmen und solche, die Schweizer Daten verarbeiten.',
relevantFor: ['Schweizer Unternehmen', 'DACH-Unternehmen', 'Internationale Dienstleister'],
keyTopics: ['Datenschutz-Folgenabschaetzung', 'Meldepflichten', 'Profiling', 'Strafbestimmungen'],
effectiveDate: '1. September 2023'
},
{
code: 'LI_DSG',
name: 'DSG Liechtenstein',
fullName: 'Datenschutzgesetz Liechtenstein',
type: 'national_law',
expected: 1,
description: 'Liechtensteinisches Datenschutzgesetz als EWR-Umsetzung der DSGVO.',
relevantFor: ['Unternehmen in Liechtenstein', 'EWR-Dienstleister'],
keyTopics: ['EWR-Datenschutz', 'Nationale Ergaenzung', 'Datenschutzstelle'],
effectiveDate: '2018'
},
{
code: 'BE_DPA_LAW',
name: 'Datenschutzgesetz Belgien',
fullName: 'Loi relative a la protection des donnees (Belgien)',
type: 'national_law',
expected: 153,
description: 'Belgisches Datenschutzgesetz zur nationalen Umsetzung der DSGVO. Regelt die Autorite de protection des donnees (APD).',
relevantFor: ['Unternehmen in Belgien', 'EU-Hauptsitz Bruessel'],
keyTopics: ['APD', 'Nationale DSGVO-Umsetzung', 'Strafbestimmungen', 'Sektorale Regeln'],
effectiveDate: '2018'
},
{
code: 'NL_UAVG',
name: 'UAVG Niederlande',
fullName: 'Uitvoeringswet AVG (UAVG) Niederlande',
type: 'national_law',
expected: 138,
description: 'Niederlaendisches Ausfuehrungsgesetz zur DSGVO. Regelt nationale Besonderheiten wie BSN-Verarbeitung und Gesundheitsdaten.',
relevantFor: ['Unternehmen in den Niederlanden', 'Gesundheitssektor NL'],
keyTopics: ['BSN-Nummer', 'Gesundheitsdaten', 'Autoriteit Persoonsgegevens', 'Nationale Ergaenzung'],
effectiveDate: '25. Mai 2018'
},
{
code: 'FR_CNIL_GUIDE',
name: 'CNIL Guide RGPD',
fullName: 'Guide pratique RGPD (CNIL Frankreich)',
type: 'national_law',
expected: 14,
description: 'Praktischer DSGVO-Leitfaden der franzoesischen Datenschutzbehoerde CNIL. Wichtig fuer alle Unternehmen mit franzoesischen Kunden.',
relevantFor: ['Unternehmen in Frankreich', 'Franzoesischsprachige Maerkte'],
keyTopics: ['CNIL-Guidance', 'Cookies', 'Einwilligung', 'Sanktionen'],
effectiveDate: '2018'
},
{
code: 'ES_LOPDGDD',
name: 'LOPDGDD Spanien',
fullName: 'Ley Organica de Proteccion de Datos (LOPDGDD) Spanien',
type: 'national_law',
expected: 154,
description: 'Spanisches organisches Datenschutzgesetz mit Garantien digitaler Rechte. Umfassende DSGVO-Umsetzung mit digitalen Grundrechten.',
relevantFor: ['Unternehmen in Spanien', 'Spanischsprachige Maerkte'],
keyTopics: ['Digitale Rechte', 'AEPD', 'Recht auf Vergessenwerden', 'Beschaeftigtendatenschutz'],
effectiveDate: '7. Dezember 2018'
},
{
code: 'IT_CODICE_PRIVACY',
name: 'Codice Privacy Italien',
fullName: 'Codice in materia di protezione dei dati personali (Italien)',
type: 'national_law',
expected: 3,
description: 'Italienisches Datenschutzgesetzbuch, aktualisiert gemaess DSGVO. Umfassende nationale Regelung durch den Garante.',
relevantFor: ['Unternehmen in Italien', 'Garante-regulierte Sektoren'],
keyTopics: ['Garante Privacy', 'Codice Privacy', 'Gesundheitsdaten', 'Strafrecht'],
effectiveDate: '2018 (aktualisiert)'
},
{
code: 'IE_DPA_2018',
name: 'DPA 2018 Ireland',
fullName: 'Data Protection Act 2018 (Ireland)',
type: 'national_law',
expected: 28,
description: 'Irisches Datenschutzgesetz. Besonders relevant da viele Tech-Konzerne (Google, Meta, Apple) ihren EU-Hauptsitz in Irland haben.',
relevantFor: ['Tech-Konzerne mit EU-Sitz Irland', 'Irische Unternehmen', 'DPC-reguliert'],
keyTopics: ['DPC Ireland', 'Big Tech Aufsicht', 'Nationale Ergaenzung', 'Strafbestimmungen'],
effectiveDate: '24. Mai 2018'
},
{
code: 'UK_DPA_2018',
name: 'DPA 2018 UK',
fullName: 'Data Protection Act 2018 (United Kingdom)',
type: 'national_law',
expected: 94,
description: 'Britisches Datenschutzgesetz nach dem Brexit. Ergaenzt die UK GDPR mit nationalen Bestimmungen, reguliert durch das ICO.',
relevantFor: ['Unternehmen mit UK-Kunden', 'UK-Datentransfers', 'ICO-regulierte Unternehmen'],
keyTopics: ['ICO', 'UK Adequacy', 'Post-Brexit Datenschutz', 'Law Enforcement'],
effectiveDate: '23. Mai 2018'
},
{
code: 'UK_GDPR',
name: 'UK GDPR',
fullName: 'UK General Data Protection Regulation (retained EU law)',
type: 'national_law',
expected: 24,
description: 'In UK-Recht ueberfuehrte DSGVO nach dem Brexit. Weitgehend identisch mit EU-DSGVO, aber unter britischer Aufsicht (ICO).',
relevantFor: ['UK-Unternehmen', 'EU-UK Datentransfers', 'Internationale Konzerne'],
keyTopics: ['Retained EU Law', 'UK-EU Adequacy', 'ICO Enforcement', 'UK-spezifische Anpassungen'],
effectiveDate: '1. Januar 2021'
},
{
code: 'NO_PERSONOPPLYSNINGSLOVEN',
name: 'Personopplysningsloven',
fullName: 'Personopplysningsloven (Norwegen)',
type: 'national_law',
expected: 18,
description: 'Norwegisches Datenschutzgesetz als EWR-Umsetzung der DSGVO. Reguliert durch Datatilsynet.',
relevantFor: ['Unternehmen in Norwegen', 'EWR-Dienstleister', 'Skandinavische Maerkte'],
keyTopics: ['Datatilsynet', 'EWR-Datenschutz', 'Nationale Ergaenzung', 'Kameras'],
effectiveDate: '20. Juli 2018'
},
{
code: 'SE_DATASKYDDSLAG',
name: 'Dataskyddslag Schweden',
fullName: 'Dataskyddslag (2018:218) Schweden',
type: 'national_law',
expected: 30,
description: 'Schwedisches Datenschutzgesetz als ergaenzende Bestimmungen zur DSGVO. Reguliert durch IMY.',
relevantFor: ['Unternehmen in Schweden', 'Skandinavische Maerkte'],
keyTopics: ['IMY', 'Personnummer', 'Forschungsdaten', 'Pressefreiheit'],
effectiveDate: '25. Mai 2018'
},
{
code: 'FI_TIETOSUOJALAKI',
name: 'Tietosuojalaki Finnland',
fullName: 'Tietosuojalaki (1050/2018) Finnland',
type: 'national_law',
expected: 1,
description: 'Finnisches Datenschutzgesetz als nationale Ergaenzung zur DSGVO.',
relevantFor: ['Unternehmen in Finnland', 'Nordische Maerkte'],
keyTopics: ['Nationale Ergaenzung', 'Tietosuojavaltuutettu', 'Forschungsdaten'],
effectiveDate: '1. Januar 2019'
},
{
code: 'PL_UODO',
name: 'UODO Polen',
fullName: 'Ustawa o ochronie danych osobowych (Polen)',
type: 'national_law',
expected: 1,
description: 'Polnisches Datenschutzgesetz als DSGVO-Umsetzung. Reguliert durch den UODO (Praesident des Amtes fuer den Schutz personenbezogener Daten).',
relevantFor: ['Unternehmen in Polen', 'Osteuropaeische Maerkte'],
keyTopics: ['UODO', 'Nationale Ergaenzung', 'Strafbestimmungen', 'Oeffentlicher Sektor'],
effectiveDate: '25. Mai 2018'
},
{
code: 'CZ_ZOU',
name: 'Zakon Tschechien',
fullName: 'Zakon o zpracovani osobnich udaju (Tschechien)',
type: 'national_law',
expected: 135,
description: 'Tschechisches Datenschutzgesetz zur DSGVO-Umsetzung. Reguliert durch das UOOU.',
relevantFor: ['Unternehmen in Tschechien', 'Mitteleuropaeische Maerkte'],
keyTopics: ['UOOU', 'Nationale Ergaenzung', 'Kamerasysteme', 'Strafbestimmungen'],
effectiveDate: '24. April 2019'
},
{
code: 'HU_INFOTV',
name: 'Infotv. Ungarn',
fullName: 'Informacios torvenye (Infotv.) Ungarn',
type: 'national_law',
expected: 156,
description: 'Ungarisches Informationsgesetz ueber Selbstbestimmung und Informationsfreiheit als DSGVO-Ergaenzung. Reguliert durch NAIH.',
relevantFor: ['Unternehmen in Ungarn', 'Mitteleuropaeische Maerkte'],
keyTopics: ['NAIH', 'Informationsfreiheit', 'Nationale Ergaenzung', 'Datensicherheit'],
effectiveDate: '2018 (aktualisiert)'
},
{
code: 'SCC_FULL_TEXT',
name: 'SCC Volltext',
fullName: 'Standardvertragsklauseln Volltext (2021/914/EU)',
type: 'eu_regulation',
expected: 154,
description: 'Vollstaendiger Text der EU-Standardvertragsklauseln fuer internationale Datentransfers. Alle Module (C2C, C2P, P2C, P2P) mit Annexen.',
relevantFor: ['Alle mit Drittlandtransfers', 'Cloud-Nutzer', 'Auftragsverarbeiter'],
keyTopics: ['Module 1-4', 'TIA', 'Annexe', 'Technische Massnahmen'],
effectiveDate: '27. Juni 2021'
},
{
code: 'EDPB_GUIDELINES_2_2019',
name: 'EDPB GL Art. 6(1)(b)',
fullName: 'EDPB Leitlinien 2/2019 zu Art. 6(1)(b) DSGVO',
type: 'eu_guideline',
expected: 3,
description: 'EDPB-Leitlinien zur Verarbeitung personenbezogener Daten auf Grundlage der Vertragserfullung gemaess Art. 6 Abs. 1 lit. b DSGVO.',
relevantFor: ['Alle Verantwortlichen', 'Vertragsdatenverarbeitung', 'Online-Dienste'],
keyTopics: ['Vertragserfullung', 'Art. 6(1)(b)', 'Erforderlichkeit', 'Online-Dienste'],
effectiveDate: '2019'
},
{
code: 'EDPB_GUIDELINES_3_2019',
name: 'EDPB GL Videoueberwachung',
fullName: 'EDPB Leitlinien 3/2019 Videoueberwachung',
type: 'eu_guideline',
expected: 3,
description: 'EDPB-Leitlinien zur Verarbeitung personenbezogener Daten durch Videoueberwachungsgeraete.',
relevantFor: ['Videoueberwachung', 'Sicherheitsdienste', 'Einzelhandel', 'Oeffentliche Stellen'],
keyTopics: ['Videoueberwachung', 'Kameras', 'Speicherfristen', 'Hinweisschilder'],
effectiveDate: '2020'
},
{
code: 'EDPB_GUIDELINES_5_2020',
name: 'EDPB GL Einwilligung',
fullName: 'EDPB Leitlinien 5/2020 zur Einwilligung',
type: 'eu_guideline',
expected: 2,
description: 'EDPB-Leitlinien zur Einwilligung gemaess DSGVO. Klaert Anforderungen an gueltige Einwilligungen, Widerruf und Cookie-Consent.',
relevantFor: ['Website-Betreiber', 'Marketing', 'App-Entwickler', 'Consent-Management'],
keyTopics: ['Einwilligung', 'Cookie-Consent', 'Widerruf', 'Freiwilligkeit'],
effectiveDate: '2020'
},
{
code: 'EDPB_GUIDELINES_7_2020',
name: 'EDPB GL Controller/Processor',
fullName: 'EDPB Leitlinien 7/2020 Controller und Processor',
type: 'eu_guideline',
expected: 2,
description: 'EDPB-Leitlinien zu den Begriffen Verantwortlicher und Auftragsverarbeiter. Klaert Rollen, Pflichten und Joint Controllership.',
relevantFor: ['Alle Verantwortlichen', 'Auftragsverarbeiter', 'Joint Controller'],
keyTopics: ['Verantwortlicher', 'Auftragsverarbeiter', 'Joint Controller', 'AVV'],
effectiveDate: '2021'
},
// =====================================================================
// DACH National Laws — Deutschland
// =====================================================================
{
code: 'DE_DDG',
name: 'Digitale-Dienste-Gesetz',
fullName: 'Digitale-Dienste-Gesetz (DDG)',
type: 'de_law',
expected: 30,
description: 'Deutsches Umsetzungsgesetz zum DSA. Regelt Impressumspflicht (§5 DDG), Informationspflichten fuer digitale Dienste und Cookie-Consent.',
relevantFor: ['Website-Betreiber', 'Online-Dienste', 'Plattformen'],
keyTopics: ['Impressumspflicht §5', 'Informationspflichten', 'Digitale Dienste'],
effectiveDate: '14. Mai 2024'
},
{
code: 'DE_BGB_AGB',
name: 'BGB AGB-Recht',
fullName: 'BGB §§305-310, 312-312k — AGB und Fernabsatz',
type: 'de_law',
expected: 40,
description: 'Deutsches AGB-Recht: Einbeziehungskontrolle (§305), Inhaltskontrolle (§307), Klauselverbote (§§308-309). Fernabsatz: Widerrufsrecht, Button-Loesung.',
relevantFor: ['Alle Unternehmen mit AGB', 'Online-Shops', 'SaaS-Anbieter', 'Dienstleister'],
keyTopics: ['AGB-Kontrolle', 'Klauselverbote', 'Widerrufsrecht', 'Button-Loesung', 'Fernabsatz'],
effectiveDate: 'Dauerhaft gueltig'
},
{
code: 'DE_EGBGB',
name: 'EGBGB Art. 246-248',
fullName: 'EGBGB — Informationspflichten bei Verbrauchervertraegen',
type: 'de_law',
expected: 20,
description: 'Detaillierte Informationspflichten bei Verbrauchervertraegen (Art. 246), Fernabsatz (Art. 246a) und E-Commerce (Art. 246c).',
relevantFor: ['Online-Shops', 'E-Commerce', 'Dienstleister', 'App-Anbieter'],
keyTopics: ['Vorvertragliche Information', 'Widerrufsbelehrung', 'E-Commerce-Pflichten'],
effectiveDate: 'Dauerhaft gueltig'
},
{
code: 'DE_UWG',
name: 'UWG Deutschland',
fullName: 'Gesetz gegen den unlauteren Wettbewerb (UWG)',
type: 'de_law',
expected: 25,
description: 'Schutz vor unlauterem Wettbewerb: irrefuehrende Werbung, Spam-Verbot, Preisangaben, Online-Marketing-Regeln.',
relevantFor: ['Marketing', 'Vertrieb', 'Online-Shops', 'Werbetreibende'],
keyTopics: ['Irrefuehrende Werbung', 'Spam-Verbot', 'Dark Patterns', 'Preisangaben'],
effectiveDate: '2004 (laufend aktualisiert)'
},
{
code: 'DE_HGB_RET',
name: 'HGB Aufbewahrung',
fullName: 'HGB §§238-261, 257 — Handelsbuecher und Aufbewahrungsfristen',
type: 'de_law',
expected: 15,
description: 'Buchfuehrungspflicht und handelsrechtliche Aufbewahrungsfristen: 6 Jahre (Handelsbriefe) und 10 Jahre (Buchungsbelege, Jahresabschluesse).',
relevantFor: ['Alle Kaufleute', 'Kapitalgesellschaften', 'Buchhaltung'],
keyTopics: ['Aufbewahrung 6/10 Jahre', 'Buchfuehrungspflicht', 'Elektronische Aufbewahrung'],
effectiveDate: 'Dauerhaft gueltig'
},
{
code: 'DE_AO_RET',
name: 'AO Aufbewahrung',
fullName: 'Abgabenordnung §§140-148 — Steuerliche Aufbewahrungspflichten',
type: 'de_law',
expected: 12,
description: 'Steuerliche Buchfuehrungs- und Aufbewahrungspflichten. 6/10 Jahre Fristen, Datenzugriff durch Finanzbehoerden (§147 Abs. 6).',
relevantFor: ['Alle Steuerpflichtigen', 'Gewerbetreibende', 'Buchhaltung'],
keyTopics: ['Steuerliche Aufbewahrung', 'Datenzugriff Finanzamt', 'GoBD'],
effectiveDate: 'Dauerhaft gueltig'
},
{
code: 'DE_TKG',
name: 'TKG 2021',
fullName: 'Telekommunikationsgesetz 2021',
type: 'de_law',
expected: 45,
description: 'Telekommunikationsregulierung: Kundenschutz, Datenschutz, Vertragslaufzeiten max. 24 Monate, Netzinfrastruktur.',
relevantFor: ['Telekommunikationsanbieter', 'VoIP-Dienste', 'ISPs'],
keyTopics: ['Kundenschutz', 'Vertragslaufzeiten', 'Fernmeldegeheimnis', 'Netzneutralitaet'],
effectiveDate: '1. Dezember 2021'
},
{
code: 'DE_PANGV',
name: 'PAngV',
fullName: 'Preisangabenverordnung (PAngV 2022)',
type: 'de_law',
expected: 15,
description: 'Preisangaben: Gesamtpreis, Grundpreis, Streichpreise (§11 — 30-Tage-Regel), Online-Preisauszeichnung.',
relevantFor: ['Online-Shops', 'Einzelhandel', 'Marktplaetze'],
keyTopics: ['Gesamtpreis', 'Grundpreis', 'Streichpreis-Regel', 'Online-Preise'],
effectiveDate: '28. Mai 2022'
},
{
code: 'DE_DLINFOV',
name: 'DL-InfoV',
fullName: 'Dienstleistungs-Informationspflichten-Verordnung',
type: 'de_law',
expected: 10,
description: 'Informationspflichten fuer Dienstleister: Identitaet, Kontakt, Berufshaftpflicht, AGB-Zugang.',
relevantFor: ['Dienstleister', 'Freiberufler', 'Handwerker'],
keyTopics: ['Dienstleister-Impressum', 'Kontaktdaten', 'Berufshaftpflicht'],
effectiveDate: '17. Mai 2010'
},
{
code: 'DE_BETRVG',
name: 'BetrVG §87',
fullName: 'Betriebsverfassungsgesetz §87 Abs.1 Nr.6',
type: 'de_law',
expected: 5,
description: 'Mitbestimmung des Betriebsrats bei technischer Ueberwachung: IT-Systeme die Arbeitnehmerverhalten ueberwachen koennen.',
relevantFor: ['Arbeitgeber mit Betriebsrat', 'IT-Abteilungen', 'HR'],
keyTopics: ['Mitbestimmung', 'Technische Ueberwachung', 'IT-Systeme', 'DSFA-Pflicht'],
effectiveDate: '1972 (laufend aktualisiert)'
},
{
code: 'DE_GESCHGEHG',
name: 'GeschGehG',
fullName: 'Gesetz zum Schutz von Geschaeftsgeheimnissen',
type: 'de_law',
expected: 10,
description: 'Schutz von Geschaeftsgeheimnissen: Definition, angemessene Geheimhaltungsmassnahmen erforderlich, Reverse Engineering erlaubt.',
relevantFor: ['Alle Unternehmen', 'IT-Sicherheit', 'F&E-Abteilungen'],
keyTopics: ['Geschaeftsgeheimnis-Definition', 'Geheimhaltungsmassnahmen', 'Reverse Engineering'],
effectiveDate: '26. April 2019'
},
{
code: 'DE_BSIG',
name: 'BSI-Gesetz',
fullName: 'Gesetz ueber das Bundesamt fuer Sicherheit in der Informationstechnik',
type: 'de_law',
expected: 20,
description: 'BSI-Aufgaben, KRITIS-Meldepflichten, IT-Sicherheitsstandards, Zertifizierung, Warn- und Empfehlungsbefugnis.',
relevantFor: ['KRITIS-Betreiber', 'IT-Sicherheit', 'Cloud-Anbieter'],
keyTopics: ['KRITIS-Meldepflicht', 'IT-Sicherheitsstandards', 'BSI-Zertifizierung'],
effectiveDate: '2009 (laufend aktualisiert)'
},
{
code: 'DE_USTG_RET',
name: 'UStG §14b',
fullName: 'Umsatzsteuergesetz §14b — Aufbewahrung von Rechnungen',
type: 'de_law',
expected: 5,
description: 'Aufbewahrungspflicht fuer Rechnungen: 10 Jahre, Grundstuecke 20 Jahre, elektronische Aufbewahrung.',
relevantFor: ['Alle Unternehmer', 'Buchhaltung', 'Steuerberater'],
keyTopics: ['Rechnungsaufbewahrung', '10/20 Jahre Frist', 'Elektronische Rechnungen'],
effectiveDate: 'Dauerhaft gueltig'
},
// =====================================================================
// DACH National Laws — Oesterreich
// =====================================================================
{
code: 'AT_ECG',
name: 'E-Commerce-Gesetz AT',
fullName: 'E-Commerce-Gesetz (ECG) Oesterreich',
type: 'at_law',
expected: 30,
description: 'Oesterreichisches E-Commerce-Gesetz: Impressum/Offenlegungspflicht (§5), Informationspflichten, Haftung von Diensteanbietern.',
relevantFor: ['Oesterreichische Online-Dienste', 'E-Commerce AT', 'Website-Betreiber'],
keyTopics: ['Impressum §5 ECG', 'Offenlegungspflicht', 'Diensteanbieter-Haftung'],
effectiveDate: '1. Januar 2002'
},
{
code: 'AT_TKG',
name: 'TKG 2021 AT',
fullName: 'Telekommunikationsgesetz 2021 Oesterreich',
type: 'at_law',
expected: 40,
description: 'Oesterreichisches TKG: Cookie-Bestimmungen (§165), Kommunikationsgeheimnis, Endgeraetezugriff, Spam-Verbot.',
relevantFor: ['Oesterreichische Websites', 'Telekommunikation AT', 'App-Anbieter'],
keyTopics: ['Cookies §165', 'Kommunikationsgeheimnis', 'Endgeraetezugriff', 'Spam-Verbot'],
effectiveDate: '1. November 2021'
},
{
code: 'AT_KSCHG',
name: 'KSchG Oesterreich',
fullName: 'Konsumentenschutzgesetz (KSchG) Oesterreich',
type: 'at_law',
expected: 35,
description: 'Konsumentenschutz: AGB-Kontrolle (§6 Klauselverbote, §9 Verbandsklage), Ruecktrittsrecht bei Haustuergeschaeften.',
relevantFor: ['Unternehmen mit oesterreichischen Verbrauchern', 'E-Commerce AT'],
keyTopics: ['AGB-Klauselverbote §6', 'Verbandsklage §9', 'Ruecktrittsrecht', 'Transparenzkontrolle'],
effectiveDate: '1. Oktober 1979 (laufend aktualisiert)'
},
{
code: 'AT_FAGG',
name: 'FAGG Oesterreich',
fullName: 'Fern- und Auswaertsgeschaefte-Gesetz (FAGG) Oesterreich',
type: 'at_law',
expected: 20,
description: 'Fernabsatzrecht: Informationspflichten, Widerrufsrecht 14 Tage, Button-Loesung, Ausnahmen.',
relevantFor: ['Oesterreichische Online-Shops', 'Fernabsatz AT', 'Versandhandel'],
keyTopics: ['Widerrufsrecht 14 Tage', 'Informationspflichten', 'Button-Loesung', 'Kostenfolgen'],
effectiveDate: '13. Juni 2014'
},
{
code: 'AT_UGB_RET',
name: 'UGB Aufbewahrung AT',
fullName: 'UGB §§189-216, 212 — Rechnungslegung und Aufbewahrung Oesterreich',
type: 'at_law',
expected: 15,
description: 'Oesterreichische Rechnungslegungspflicht und Aufbewahrungsfristen (7 Jahre). Buchfuehrung, Jahresabschluss.',
relevantFor: ['Oesterreichische Kapitalgesellschaften', 'Unternehmen >700k EUR Umsatz'],
keyTopics: ['Aufbewahrung 7 Jahre', 'Rechnungslegung', 'Buchfuehrung'],
effectiveDate: 'Dauerhaft gueltig'
},
{
code: 'AT_BAO_RET',
name: 'BAO §132 AT',
fullName: 'Bundesabgabenordnung §132 — Aufbewahrung Oesterreich',
type: 'at_law',
expected: 5,
description: 'Steuerliche Aufbewahrungspflicht 7 Jahre fuer Buecher, Aufzeichnungen und Belege. Grundstuecke 22 Jahre.',
relevantFor: ['Oesterreichische Steuerpflichtige', 'Buchhaltung AT'],
keyTopics: ['Aufbewahrung 7 Jahre', 'Grundstuecke 22 Jahre', 'Steuerliche Belege'],
effectiveDate: 'Dauerhaft gueltig'
},
{
code: 'AT_MEDIENG',
name: 'MedienG §§24-25 AT',
fullName: 'Mediengesetz §§24-25 Oesterreich — Impressum und Offenlegung',
type: 'at_law',
expected: 10,
description: 'Impressum/Offenlegungspflicht fuer periodische Medien und Websites in Oesterreich.',
relevantFor: ['Medienunternehmen AT', 'Website-Betreiber AT', 'Blogger AT'],
keyTopics: ['Impressum', 'Offenlegung', 'Medieninhaber', 'Periodische Medien'],
effectiveDate: '1. Januar 1982 (laufend aktualisiert)'
},
{
code: 'AT_ABGB_AGB',
name: 'ABGB AGB-Recht AT',
fullName: 'ABGB §§861-879, 864a — AGB-Kontrolle Oesterreich',
type: 'at_law',
expected: 10,
description: 'Geltungskontrolle (§864a — ueberraschende Klauseln), Sittenwidrigkeitskontrolle (§879 Abs.3 — groebliche Benachteiligung).',
relevantFor: ['Unternehmen mit oesterreichischen Kunden', 'AGB-Ersteller'],
keyTopics: ['Geltungskontrolle §864a', 'Inhaltskontrolle §879', 'Groebliche Benachteiligung'],
effectiveDate: '1. Juni 1811 (laufend aktualisiert)'
},
{
code: 'AT_UWG',
name: 'UWG Oesterreich',
fullName: 'Bundesgesetz gegen den unlauteren Wettbewerb Oesterreich',
type: 'at_law',
expected: 15,
description: 'Lauterkeitsrecht AT: irrefuehrende Geschaeftspraktiken, aggressive Praktiken, Preisauszeichnung.',
relevantFor: ['Marketing AT', 'Vertrieb AT', 'Werbetreibende AT'],
keyTopics: ['Irrefuehrung', 'Aggressive Praktiken', 'Preisauszeichnung', 'Unterlassungsklagen'],
effectiveDate: '1984 (laufend aktualisiert)'
},
// =====================================================================
// DACH National Laws — Schweiz
// =====================================================================
{
code: 'CH_DSV',
name: 'DSV Schweiz',
fullName: 'Datenschutzverordnung (DSV) Schweiz — SR 235.11',
type: 'ch_law',
expected: 30,
description: 'Ausfuehrungsverordnung zum revDSG: Meldepflichten, DSFA-Verfahren, Auslandtransfers, technische Massnahmen.',
relevantFor: ['Schweizer Unternehmen', 'DACH-Unternehmen', 'Datenexporteure CH'],
keyTopics: ['Meldepflicht', 'DSFA-Verfahren', 'Datentransfer', 'Technische Massnahmen'],
effectiveDate: '1. September 2023'
},
{
code: 'CH_OR_AGB',
name: 'OR AGB/Aufbewahrung CH',
fullName: 'Obligationenrecht — AGB-Kontrolle und Aufbewahrung Schweiz (SR 220)',
type: 'ch_law',
expected: 20,
description: 'Art. 8 OR (AGB-Inhaltskontrolle), Art. 19/20 (Vertragsfreiheit), Art. 957-958f (Buchfuehrung, 10 Jahre Aufbewahrung).',
relevantFor: ['Schweizer Unternehmen', 'AGB-Ersteller CH', 'Buchhaltung CH'],
keyTopics: ['AGB-Kontrolle Art. 8', 'Aufbewahrung 10 Jahre', 'Buchfuehrungspflicht'],
effectiveDate: '1. Januar 2023 (AGB-Revision)'
},
{
code: 'CH_UWG',
name: 'UWG Schweiz',
fullName: 'Bundesgesetz gegen den unlauteren Wettbewerb Schweiz (SR 241)',
type: 'ch_law',
expected: 20,
description: 'Lauterkeitsrecht: Impressumspflicht, irrefuehrende Werbung, aggressive Verkaufsmethoden, AGB-Transparenz.',
relevantFor: ['Schweizer Unternehmen', 'Marketing CH', 'Online-Shops CH'],
keyTopics: ['Impressumspflicht', 'Irrefuehrende Werbung', 'AGB-Transparenz'],
effectiveDate: '1. Maerz 1988 (laufend aktualisiert)'
},
{
code: 'CH_FMG',
name: 'FMG Schweiz',
fullName: 'Fernmeldegesetz Schweiz (SR 784.10)',
type: 'ch_law',
expected: 25,
description: 'Telekommunikationsregulierung: Fernmeldegeheimnis, Cookies/Tracking (Art. 45c), Spam-Verbot, Datenschutz.',
relevantFor: ['Schweizer Websites', 'Telekommunikation CH', 'App-Anbieter CH'],
keyTopics: ['Cookies Art. 45c', 'Fernmeldegeheimnis', 'Spam-Verbot', 'Tracking'],
effectiveDate: '1. April 2007 (laufend aktualisiert)'
},
{
code: 'CH_GEBUV',
name: 'GeBuV Schweiz',
fullName: 'Geschaeftsbuecher-Verordnung Schweiz (SR 221.431)',
type: 'ch_law',
expected: 10,
description: 'Ausfuehrungsvorschriften zur Buchfuehrung: elektronische Aufbewahrung, Integritaet, Datentraeger.',
relevantFor: ['Schweizer Unternehmen', 'Buchhaltung CH', 'IT-Archivierung'],
keyTopics: ['Elektronische Aufbewahrung', 'Integritaet', 'Unveraenderbarkeit'],
effectiveDate: '1. Juni 2002'
},
{
code: 'CH_ZERTES',
name: 'ZertES Schweiz',
fullName: 'Bundesgesetz ueber die elektronische Signatur (SR 943.03)',
type: 'ch_law',
expected: 10,
description: 'Elektronische Signatur und Zertifizierung: Qualifizierte Signaturen, Zertifizierungsdiensteanbieter.',
relevantFor: ['Vertragsmanagement CH', 'AVV-Erstellung', 'E-Government CH'],
keyTopics: ['Qualifizierte Signatur', 'Zertifizierungsdienste', 'Rechtswirkung'],
effectiveDate: '1. Januar 2017'
},
{
code: 'CH_ZGB_PERS',
name: 'ZGB Persoenlichkeitsschutz CH',
fullName: 'Zivilgesetzbuch Art. 28-28l — Persoenlichkeitsschutz Schweiz (SR 210)',
type: 'ch_law',
expected: 8,
description: 'Persoenlichkeitsschutz: Recht am eigenen Bild, Schutz der Privatsphaere, Gegendarstellungsrecht.',
relevantFor: ['Medien CH', 'Social Media', 'Datenschutz CH'],
keyTopics: ['Persoenlichkeitsschutz', 'Recht am Bild', 'Gegendarstellung'],
effectiveDate: '1. Juli 1985 (laufend aktualisiert)'
},
// =====================================================================
// 3 fehlgeschlagene Quellen mit korrigierten URLs
// =====================================================================
{
code: 'LU_DPA_LAW',
name: 'Datenschutzgesetz Luxemburg',
fullName: 'Loi du 1er aout 2018 — Datenschutzgesetz Luxemburg',
type: 'national_law',
expected: 40,
description: 'Luxemburgisches Datenschutzgesetz: Organisation der CNPD, nationale DSGVO-Ergaenzung.',
relevantFor: ['Unternehmen in Luxemburg', 'EU-Finanzplatz', 'CNPD-reguliert'],
keyTopics: ['CNPD', 'Nationale DSGVO-Umsetzung', 'Strafbestimmungen'],
effectiveDate: '1. August 2018'
},
{
code: 'DK_DATABESKYTTELSESLOVEN',
name: 'Databeskyttelsesloven DK',
fullName: 'Databeskyttelsesloven — Datenschutzgesetz Daenemark',
type: 'national_law',
expected: 30,
description: 'Daenisches Datenschutzgesetz als ergaenzende Bestimmungen zur DSGVO. Reguliert durch Datatilsynet.',
relevantFor: ['Unternehmen in Daenemark', 'Skandinavische Maerkte'],
keyTopics: ['Datatilsynet', 'Nationale DSGVO-Ergaenzung', 'Strafbestimmungen'],
effectiveDate: '25. Mai 2018'
},
{
code: 'EDPB_GUIDELINES_1_2022',
name: 'EDPB GL Bussgelder',
fullName: 'EDPB Leitlinien 04/2022 zur Berechnung von Bussgeldern nach der DSGVO',
type: 'eu_guideline',
expected: 15,
description: 'EDPB-Leitlinien zur Berechnung von Verwaltungsbussgeldern unter der DSGVO. Systematik, Schwere, Milderungsgruende.',
relevantFor: ['Datenschutzbeauftragte', 'Compliance-Abteilungen', 'Rechtsabteilungen'],
keyTopics: ['Bussgeldberechnung', 'Schweregrad', 'Milderungsgruende', 'Bussgeldrahmen'],
effectiveDate: '2022'
},
]
// License info for each regulation
const REGULATION_LICENSES: Record<string, { license: string; licenseNote: string }> = {
GDPR: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk der EU — frei verwendbar' },
EPRIVACY: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Richtlinie — amtliches Werk' },
TDDDG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
SCC: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Durchfuehrungsbeschluss — amtliches Werk' },
DPF: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Angemessenheitsbeschluss — amtliches Werk' },
AIACT: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
CRA: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
NIS2: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Richtlinie — amtliches Werk' },
EUCSA: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
DATAACT: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
DGA: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
DSA: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
EAA: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Richtlinie — amtliches Werk' },
DSM: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Richtlinie — amtliches Werk' },
PLD: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Richtlinie — amtliches Werk' },
GPSR: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
'BSI-TR-03161-1': { license: 'DL-DE-BY-2.0', licenseNote: 'Datenlizenz Deutschland — Namensnennung 2.0' },
'BSI-TR-03161-2': { license: 'DL-DE-BY-2.0', licenseNote: 'Datenlizenz Deutschland — Namensnennung 2.0' },
'BSI-TR-03161-3': { license: 'DL-DE-BY-2.0', licenseNote: 'Datenlizenz Deutschland — Namensnennung 2.0' },
DORA: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
PSD2: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Richtlinie — amtliches Werk' },
AMLR: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
MiCA: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
EHDS: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Verordnung — amtliches Werk' },
// National Data Protection Laws
AT_DSG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
BDSG_FULL: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
CH_DSG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Schweiz — frei verwendbar' },
LI_DSG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Liechtenstein — frei verwendbar' },
BE_DPA_LAW: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Belgien — frei verwendbar' },
NL_UAVG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Niederlande — frei verwendbar' },
FR_CNIL_GUIDE: { license: 'PUBLIC_DOMAIN', licenseNote: 'CNIL — oeffentliches Dokument' },
ES_LOPDGDD: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Spanien (BOE) — frei verwendbar' },
IT_CODICE_PRIVACY: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Italien — frei verwendbar' },
IE_DPA_2018: { license: 'OGL-3.0', licenseNote: 'Open Government Licence v3.0 — Ireland' },
UK_DPA_2018: { license: 'OGL-3.0', licenseNote: 'Open Government Licence v3.0 — UK' },
UK_GDPR: { license: 'OGL-3.0', licenseNote: 'Open Government Licence v3.0 — UK' },
NO_PERSONOPPLYSNINGSLOVEN: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Norwegen — frei verwendbar' },
SE_DATASKYDDSLAG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Schweden — frei verwendbar' },
FI_TIETOSUOJALAKI: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Finnland — frei verwendbar' },
PL_UODO: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Polen — frei verwendbar' },
CZ_ZOU: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Tschechien — frei verwendbar' },
HU_INFOTV: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Ungarn — frei verwendbar' },
SCC_FULL_TEXT: { license: 'PUBLIC_DOMAIN', licenseNote: 'EU-Durchfuehrungsbeschluss — amtliches Werk' },
EDPB_GUIDELINES_2_2019: { license: 'EDPB-LICENSE', licenseNote: 'EDPB Document License' },
EDPB_GUIDELINES_3_2019: { license: 'EDPB-LICENSE', licenseNote: 'EDPB Document License' },
EDPB_GUIDELINES_5_2020: { license: 'EDPB-LICENSE', licenseNote: 'EDPB Document License' },
EDPB_GUIDELINES_7_2020: { license: 'EDPB-LICENSE', licenseNote: 'EDPB Document License' },
// DACH National Laws — Deutschland
DE_DDG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_BGB_AGB: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_EGBGB: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_UWG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_HGB_RET: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_AO_RET: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_TKG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_PANGV: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsche Verordnung — amtliches Werk (§5 UrhG)' },
DE_DLINFOV: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsche Verordnung — amtliches Werk (§5 UrhG)' },
DE_BETRVG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_GESCHGEHG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_BSIG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
DE_USTG_RET: { license: 'PUBLIC_DOMAIN', licenseNote: 'Deutsches Bundesgesetz — amtliches Werk (§5 UrhG)' },
// DACH National Laws — Oesterreich
AT_ECG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
AT_TKG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
AT_KSCHG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
AT_FAGG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
AT_UGB_RET: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
AT_BAO_RET: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
AT_MEDIENG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
AT_ABGB_AGB: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
AT_UWG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Oesterreich — frei verwendbar' },
// DACH National Laws — Schweiz
CH_DSV: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Schweiz — frei verwendbar' },
CH_OR_AGB: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Schweiz — frei verwendbar' },
CH_UWG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Schweiz — frei verwendbar' },
CH_FMG: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Schweiz — frei verwendbar' },
CH_GEBUV: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Schweiz — frei verwendbar' },
CH_ZERTES: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Schweiz — frei verwendbar' },
CH_ZGB_PERS: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Schweiz — frei verwendbar' },
// 3 fehlgeschlagene Quellen (korrigiert)
LU_DPA_LAW: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Luxemburg — frei verwendbar' },
DK_DATABESKYTTELSESLOVEN: { license: 'PUBLIC_DOMAIN', licenseNote: 'Amtliches Werk Daenemark — frei verwendbar' },
EDPB_GUIDELINES_1_2022: { license: 'EDPB-LICENSE', licenseNote: 'EDPB Document License' },
}
// License display labels
const LICENSE_LABELS: Record<string, string> = {
PUBLIC_DOMAIN: 'Public Domain',
'DL-DE-BY-2.0': 'DL-DE-BY 2.0',
'CC-BY-4.0': 'CC BY 4.0',
'EDPB-LICENSE': 'EDPB License',
'OGL-3.0': 'OGL v3.0',
PROPRIETARY: 'Proprietaer',
}
const TYPE_COLORS: Record<string, string> = {
eu_regulation: 'bg-blue-100 text-blue-700',
eu_directive: 'bg-purple-100 text-purple-700',
de_law: 'bg-yellow-100 text-yellow-700',
at_law: 'bg-red-100 text-red-700',
ch_law: 'bg-rose-100 text-rose-700',
bsi_standard: 'bg-green-100 text-green-700',
national_law: 'bg-orange-100 text-orange-700',
eu_guideline: 'bg-teal-100 text-teal-700',
}
const TYPE_LABELS: Record<string, string> = {
eu_regulation: 'EU-VO',
eu_directive: 'EU-RL',
de_law: 'DE-Gesetz',
at_law: 'AT-Gesetz',
ch_law: 'CH-Gesetz',
bsi_standard: 'BSI',
national_law: 'Nat. Gesetz',
eu_guideline: 'EDPB-GL',
}
// Industry/Sector definitions for the regulation map
const INDUSTRIES = [
{ id: 'all', name: 'Alle Unternehmen', icon: '🏢', description: 'Grundlegende Anforderungen fuer alle' },
{ id: 'health', name: 'Gesundheitswesen', icon: '🏥', description: 'Krankenhaeuser, DiGA, Medizintechnik' },
{ id: 'finance', name: 'Finanzsektor', icon: '🏦', description: 'Banken, Versicherungen, Zahlungsdienstleister' },
{ id: 'ecommerce', name: 'E-Commerce', icon: '🛒', description: 'Online-Shops, Marktplaetze' },
{ id: 'tech', name: 'Technologie/Software', icon: '💻', description: 'Software-Entwickler, SaaS, Cloud' },
{ id: 'iot', name: 'IoT/Hardware', icon: '📱', description: 'Geraetehersteller, Smart Devices' },
{ id: 'ai', name: 'KI-Anbieter', icon: '🤖', description: 'KI-Entwickler und -Anwender' },
{ id: 'kritis', name: 'Kritische Infrastruktur', icon: '⚡', description: 'Energie, Wasser, Transport' },
{ id: 'media', name: 'Medien/Plattformen', icon: '📺', description: 'Social Media, Content-Plattformen' },
{ id: 'public', name: 'Oeffentlicher Sektor', icon: '🏛️', description: 'Behoerden, Verwaltung' },
]
// Mapping: Which regulations apply to which industries
const INDUSTRY_REGULATION_MAP: Record<string, string[]> = {
all: ['GDPR', 'EPRIVACY', 'TDDDG'],
health: ['GDPR', 'TDDDG', 'BSI-TR-03161-1', 'BSI-TR-03161-2', 'BSI-TR-03161-3', 'NIS2', 'AIACT', 'PLD', 'EHDS'],
finance: ['GDPR', 'TDDDG', 'NIS2', 'EUCSA', 'DSA', 'AIACT', 'DPF', 'DORA', 'PSD2', 'AMLR', 'MiCA'],
ecommerce: ['GDPR', 'TDDDG', 'DSA', 'GPSR', 'EAA', 'PLD', 'DPF', 'PSD2'],
tech: ['GDPR', 'TDDDG', 'CRA', 'AIACT', 'DPF', 'SCC', 'DATAACT', 'DSM', 'MiCA'],
iot: ['GDPR', 'CRA', 'GPSR', 'PLD', 'DATAACT', 'AIACT'],
ai: ['GDPR', 'AIACT', 'PLD', 'DSM', 'DATAACT'],
kritis: ['GDPR', 'NIS2', 'EUCSA', 'CRA', 'AIACT', 'DORA'],
media: ['GDPR', 'TDDDG', 'DSA', 'DSM', 'EAA', 'AIACT'],
public: ['GDPR', 'NIS2', 'EUCSA', 'EAA', 'DGA', 'AIACT', 'EHDS'],
}
// Thematic groupings showing overlaps
const THEMATIC_GROUPS = [
{
id: 'datenschutz',
name: 'Datenschutz & Privacy',
color: 'bg-blue-500',
regulations: ['GDPR', 'EPRIVACY', 'TDDDG', 'SCC', 'DPF'],
description: 'Schutz personenbezogener Daten, Einwilligung, Betroffenenrechte'
},
{
id: 'cybersecurity',
name: 'Cybersicherheit',
color: 'bg-red-500',
regulations: ['NIS2', 'EUCSA', 'CRA', 'BSI-TR-03161-1', 'BSI-TR-03161-2', 'BSI-TR-03161-3', 'DORA'],
description: 'IT-Sicherheit, Risikomanagement, Incident Response'
},
{
id: 'ai',
name: 'Kuenstliche Intelligenz',
color: 'bg-purple-500',
regulations: ['AIACT', 'PLD', 'GPSR'],
description: 'KI-Regulierung, Hochrisiko-Systeme, Haftung'
},
{
id: 'digital-markets',
name: 'Digitale Maerkte & Plattformen',
color: 'bg-green-500',
regulations: ['DSA', 'DGA', 'DATAACT', 'DSM'],
description: 'Plattformregulierung, Datenzugang, Urheberrecht'
},
{
id: 'product-safety',
name: 'Produktsicherheit & Haftung',
color: 'bg-orange-500',
regulations: ['CRA', 'PLD', 'GPSR', 'EAA'],
description: 'Sicherheitsanforderungen, CE-Kennzeichnung, Barrierefreiheit'
},
{
id: 'finance',
name: 'Finanzmarktregulierung',
color: 'bg-emerald-500',
regulations: ['DORA', 'PSD2', 'AMLR', 'MiCA'],
description: 'Zahlungsdienste, Krypto-Assets, Geldwaeschebekaempfung, digitale Resilienz'
},
{
id: 'health',
name: 'Gesundheitsdaten',
color: 'bg-pink-500',
regulations: ['EHDS', 'BSI-TR-03161-1', 'BSI-TR-03161-2', 'BSI-TR-03161-3'],
description: 'Gesundheitsdatenraum, DiGA-Sicherheit, Patientenrechte'
},
]
// Key overlaps and intersections
const KEY_INTERSECTIONS = [
{
regulations: ['GDPR', 'AIACT'],
topic: 'KI und personenbezogene Daten',
description: 'Automatisierte Entscheidungen, Profiling, Erklaerbarkeit'
},
{
regulations: ['NIS2', 'CRA'],
topic: 'Cybersicherheit von Produkten',
description: 'Sicherheitsanforderungen ueber den gesamten Lebenszyklus'
},
{
regulations: ['AIACT', 'PLD'],
topic: 'KI-Haftung',
description: 'Wer haftet, wenn KI Schaeden verursacht?'
},
{
regulations: ['DSA', 'GDPR'],
topic: 'Plattform-Transparenz',
description: 'Inhaltsmoderation und Datenschutz'
},
{
regulations: ['DATAACT', 'GDPR'],
topic: 'Datenzugang vs. Datenschutz',
description: 'Balance zwischen Datenteilung und Privacy'
},
{
regulations: ['CRA', 'GPSR'],
topic: 'Digitale Produktsicherheit',
description: 'Hardware mit Software-Komponenten'
},
]
// Future outlook - proposed and discussed regulations
const FUTURE_OUTLOOK = [
{
id: 'digital-omnibus',
name: 'EU Digital Omnibus',
status: 'proposed',
statusLabel: 'Vorgeschlagen Nov 2025',
expectedDate: '2026/2027',
description: 'Umfassendes Vereinfachungspaket fuer AI Act, DSGVO und Cybersicherheit. Ziel: 5 Mrd. EUR Einsparung bei Verwaltungskosten.',
keyChanges: [
'AI Act: Verschiebung Hochrisiko-Pflichten um bis zu 16 Monate (bis Dez 2027)',
'AI Act: Vereinfachte Dokumentation fuer KMU und Small Midcaps',
'AI Act: EU-weite regulatorische Sandbox fuer KI-Tests',
'DSGVO: Cookie-Banner-Reform - Berechtigtes Interesse statt nur Einwilligung',
'DSGVO: Automatische Privacy-Signale via Browser statt Pop-ups',
'Cybersecurity: Single Entry Point fuer Meldepflichten'
],
affectedRegulations: ['AIACT', 'GDPR', 'NIS2', 'CRA', 'EUCSA'],
source: 'https://digital-strategy.ec.europa.eu/en/library/digital-omnibus-ai-regulation-proposal'
},
{
id: 'sustainability-omnibus',
name: 'EU Nachhaltigkeits-Omnibus',
status: 'agreed',
statusLabel: 'Einigung Dez 2025',
expectedDate: 'Q1 2026',
description: 'Drastische Reduzierung der Nachhaltigkeits-Berichtspflichten. Anwendungsbereich wird stark eingeschraenkt.',
keyChanges: [
'CSRD: Nur noch Unternehmen >1.000 MA und >450 Mio EUR Umsatz berichtspflichtig',
'CSRD: Betroffene Unternehmen sinken von 50.000 auf ca. 5.000 in der EU',
'CSRD: Verschiebung Welle 2+3 um 2 Jahre (auf Geschaeftsjahr 2027)',
'CSDDD: Nur noch Unternehmen >5.000 MA und >1,5 Mrd EUR Umsatz',
'CSDDD: Sorgfaltspflichten nur noch fuer Tier-1-Lieferanten',
'CSDDD: Pruefung nur noch alle 5 Jahre statt jaehrlich'
],
affectedRegulations: ['CSRD', 'CSDDD', 'EU-Taxonomie'],
source: 'https://kpmg-law.de/erste-omnibus-verordnung-soll-die-pflichten-der-csddd-csrd-und-eu-taxonomie-lockern/'
},
{
id: 'eprivacy-withdrawal',
name: 'ePrivacy-Verordnung',
status: 'withdrawn',
statusLabel: 'Zurueckgezogen Feb 2025',
expectedDate: 'Unbekannt',
description: 'Nach 9 Jahren Verhandlung hat die EU-Kommission den Vorschlag zurueckgezogen. Die ePrivacy-Richtlinie bleibt in Kraft, Cookie-Reform kommt via DSGVO/Digital Omnibus.',
keyChanges: [
'Urspruenglicher Vorschlag: Einheitliche EU-Cookie-Regeln',
'Urspruenglicher Vorschlag: Strikte Tracking-Einwilligung',
'Status: ePrivacy-Richtlinie + TDDDG bleiben gueltig',
'Zukunft: Cookie-Reform wird Teil der DSGVO-Aenderungen'
],
affectedRegulations: ['EPRIVACY', 'TDDDG', 'GDPR'],
source: 'https://netzpolitik.org/2025/cookie-banner-und-online-tracking-eu-kommission-beerdigt-plaene-fuer-eprivacy-verordnung/'
},
{
id: 'ai-liability',
name: 'KI-Haftungsrichtlinie',
status: 'pending',
statusLabel: 'In Verhandlung',
expectedDate: '2026',
description: 'Ergaenzt den AI Act um zivilrechtliche Haftungsregeln. Erleichtert Geschaedigten die Beweisfuehrung bei KI-Schaeden.',
keyChanges: [
'Beweislasterleichterung bei KI-verursachten Schaeden',
'Offenlegungspflichten fuer KI-Anbieter im Schadensfall',
'Verknuepfung mit Produkthaftungsrichtlinie'
],
affectedRegulations: ['AIACT', 'PLD'],
source: 'https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:52022PC0496'
},
]
// Additional regulations that could be added to RAG (publicly available, commercial use permitted)
// Regulations now integrated in main RAG (previously listed as candidates)
const INTEGRATED_REGULATIONS = [
{
code: 'DORA',
name: 'Digital Operational Resilience Act',
status: 'integrated',
addedDate: 'Januar 2025',
description: 'Jetzt im RAG verfuegbar - Finanzsektor IT-Resilienz'
},
{
code: 'MiCA',
name: 'Markets in Crypto-Assets',
status: 'integrated',
addedDate: 'Januar 2025',
description: 'Jetzt im RAG verfuegbar - Krypto-Regulierung'
},
{
code: 'PSD2',
name: 'Payment Services Directive 2',
status: 'integrated',
addedDate: 'Januar 2025',
description: 'Jetzt im RAG verfuegbar - Zahlungsdienste'
},
{
code: 'AMLR',
name: 'AML-Verordnung',
status: 'integrated',
addedDate: 'Januar 2025',
description: 'Jetzt im RAG verfuegbar - Geldwaeschebekaempfung'
},
{
code: 'EHDS',
name: 'European Health Data Space',
status: 'integrated',
addedDate: 'Januar 2025',
description: 'Jetzt im RAG verfuegbar - Gesundheitsdatenraum'
},
]
// Potential future regulations (not yet integrated)
const ADDITIONAL_REGULATIONS = [
{
code: 'PSD3',
name: 'Payment Services Directive 3',
fullName: 'Richtlinie zur dritten Zahlungsdiensterichtlinie (Entwurf)',
type: 'eu_directive',
status: 'proposed',
effectiveDate: 'Voraussichtlich 2026',
description: 'Modernisierung der Zahlungsdienste-Regulierung. Staerkerer Verbraucherschutz, Open Banking 2.0, Betrugsbekaempfung. Ersetzt dann PSD2.',
relevantFor: ['Banken', 'Zahlungsdienstleister', 'Fintechs', 'E-Commerce'],
celex: '52023PC0366',
priority: 'medium'
},
{
code: 'AMLD6',
name: 'AML-Richtlinie 6',
fullName: 'Richtlinie (EU) 2024/1640 - 6. Geldwaescherichtlinie',
type: 'eu_directive',
status: 'active',
effectiveDate: '10. Juli 2027 (Umsetzung)',
description: 'Ergaenzt die AML-Verordnung. Nationale Umsetzungsvorschriften, strafrechtliche Sanktionen, AMLA-Behoerde.',
relevantFor: ['Banken', 'Krypto-Anbieter', 'Immobilienmakler', 'Gluecksspielanbieter'],
celex: '32024L1640',
priority: 'medium'
},
{
code: 'FIDA',
name: 'Financial Data Access',
fullName: 'Verordnung zum Zugang zu Finanzdaten (Entwurf)',
type: 'eu_regulation',
status: 'proposed',
effectiveDate: 'Voraussichtlich 2027',
description: 'Open Finance Framework - erweitert PSD2-Open-Banking auf Versicherungen, Investitionen, Kredite.',
relevantFor: ['Banken', 'Versicherungen', 'Fintechs', 'Datenaggregatoren'],
celex: '52023PC0360',
priority: 'medium'
},
]
// Legal basis for using EUR-Lex content
const LEGAL_BASIS_INFO = {
title: 'Rechtliche Grundlage fuer RAG-Nutzung',
summary: 'EU-Rechtstexte auf EUR-Lex sind oeffentliche amtliche Dokumente und duerfen frei verwendet werden.',
details: [
{
aspect: 'EUR-Lex Dokumente',
status: 'Erlaubt',
explanation: 'Offizielle EU-Gesetzestexte, Richtlinien und Verordnungen sind gemeinfrei (Public Domain) und duerfen frei reproduziert und kommerziell genutzt werden.'
},
{
aspect: 'Text-und-Data-Mining (TDM)',
status: 'Erlaubt',
explanation: 'Art. 4 der DSM-Richtlinie (2019/790) erlaubt TDM fuer kommerzielle Zwecke, sofern kein Opt-out des Rechteinhabers vorliegt. Fuer amtliche Texte gilt kein Opt-out.'
},
{
aspect: 'AI Act Anforderungen',
status: 'Beachten',
explanation: 'Art. 53 AI Act verlangt von GPAI-Anbietern die Einhaltung des Urheberrechts. Fuer oeffentliche Rechtstexte unproblematisch.'
},
{
aspect: 'BSI-Richtlinien',
status: 'Erlaubt',
explanation: 'BSI-Publikationen sind oeffentlich zugaenglich und duerfen fuer Compliance-Zwecke verwendet werden.'
},
]
}
export default function RAGPage() {
const [activeTab, setActiveTab] = useState<TabId>('overview')
const [collectionStatus, setCollectionStatus] = useState<CollectionStatus | null>(null)
const [loading, setLoading] = useState(true)
const [searchQuery, setSearchQuery] = useState('')
const [searchResults, setSearchResults] = useState<SearchResult[]>([])
const [searching, setSearching] = useState(false)
const [selectedRegulations, setSelectedRegulations] = useState<string[]>([])
const [ingestionRunning, setIngestionRunning] = useState(false)
const [ingestionLog, setIngestionLog] = useState<string[]>([])
const [pipelineState, setPipelineState] = useState<PipelineState | null>(null)
const [pipelineLoading, setPipelineLoading] = useState(false)
const [pipelineStarting, setPipelineStarting] = useState(false)
const [expandedRegulation, setExpandedRegulation] = useState<string | null>(null)
const [autoRefresh, setAutoRefresh] = useState(true)
const [elapsedTime, setElapsedTime] = useState<string>('')
// DSFA corpus state
const [dsfaSources, setDsfaSources] = useState<DsfaSource[]>([])
const [dsfaStatus, setDsfaStatus] = useState<DsfaCorpusStatus | null>(null)
const [dsfaLoading, setDsfaLoading] = useState(false)
const [regulationCategory, setRegulationCategory] = useState<RegulationCategory>('regulations')
const [expandedDsfaSource, setExpandedDsfaSource] = useState<string | null>(null)
// Data tab state
const [customDocuments, setCustomDocuments] = useState<CustomDocument[]>([])
const [uploadFile, setUploadFile] = useState<File | null>(null)
const [uploadTitle, setUploadTitle] = useState('')
const [uploadCode, setUploadCode] = useState('')
const [uploading, setUploading] = useState(false)
const [linkUrl, setLinkUrl] = useState('')
const [linkTitle, setLinkTitle] = useState('')
const [linkCode, setLinkCode] = useState('')
const [addingLink, setAddingLink] = useState(false)
const fetchStatus = useCallback(async () => {
setLoading(true)
try {
const res = await fetch(`${API_PROXY}?action=status`)
if (res.ok) {
const data = await res.json()
setCollectionStatus(data)
}
} catch (error) {
console.error('Failed to fetch status:', error)
} finally {
setLoading(false)
}
}, [])
const fetchPipeline = useCallback(async () => {
setPipelineLoading(true)
try {
const res = await fetch(`${API_PROXY}?action=pipeline-checkpoints`)
if (res.ok) {
const data = await res.json()
setPipelineState(data)
}
} catch (error) {
console.error('Failed to fetch pipeline:', error)
} finally {
setPipelineLoading(false)
}
}, [])
const fetchDsfaStatus = useCallback(async () => {
setDsfaLoading(true)
try {
const [statusRes, sourcesRes] = await Promise.all([
fetch(`${DSFA_API_PROXY}?action=status`),
fetch(`${DSFA_API_PROXY}?action=sources`),
])
if (statusRes.ok) {
const data = await statusRes.json()
setDsfaStatus(data)
}
if (sourcesRes.ok) {
const data = await sourcesRes.json()
setDsfaSources(data.sources || data || [])
}
} catch (error) {
console.error('Failed to fetch DSFA status:', error)
} finally {
setDsfaLoading(false)
}
}, [])
const fetchCustomDocuments = useCallback(async () => {
try {
const res = await fetch(`${API_PROXY}?action=custom-documents`)
if (res.ok) {
const data = await res.json()
setCustomDocuments(data.documents || [])
}
} catch (error) {
console.error('Failed to fetch custom documents:', error)
}
}, [])
const handleUpload = async () => {
if (!uploadFile || !uploadTitle || !uploadCode) return
setUploading(true)
try {
const formData = new FormData()
formData.append('file', uploadFile)
formData.append('title', uploadTitle)
formData.append('code', uploadCode)
formData.append('document_type', 'custom')
const res = await fetch(`${API_PROXY}?action=upload`, {
method: 'POST',
body: formData,
})
if (res.ok) {
setUploadFile(null)
setUploadTitle('')
setUploadCode('')
fetchCustomDocuments()
fetchStatus()
}
} catch (error) {
console.error('Upload failed:', error)
} finally {
setUploading(false)
}
}
const handleAddLink = async () => {
if (!linkUrl || !linkTitle || !linkCode) return
setAddingLink(true)
try {
const res = await fetch(`${API_PROXY}?action=add-link`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: linkUrl,
title: linkTitle,
code: linkCode,
document_type: 'custom',
}),
})
if (res.ok) {
setLinkUrl('')
setLinkTitle('')
setLinkCode('')
fetchCustomDocuments()
}
} catch (error) {
console.error('Add link failed:', error)
} finally {
setAddingLink(false)
}
}
const handleDeleteDocument = async (docId: string) => {
try {
const res = await fetch(`${API_PROXY}?action=delete-document&docId=${docId}`, {
method: 'DELETE',
})
if (res.ok) {
fetchCustomDocuments()
fetchStatus()
}
} catch (error) {
console.error('Delete failed:', error)
}
}
const handleStartPipeline = async (skipIngestion: boolean = false) => {
setPipelineStarting(true)
try {
const res = await fetch(`${API_PROXY}?action=start-pipeline`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
force_reindex: false,
skip_ingestion: skipIngestion,
}),
})
if (res.ok) {
// Wait a moment then refresh pipeline status
setTimeout(() => {
fetchPipeline()
setPipelineStarting(false)
}, 2000)
} else {
setPipelineStarting(false)
}
} catch (error) {
console.error('Failed to start pipeline:', error)
setPipelineStarting(false)
}
}
useEffect(() => {
fetchStatus()
fetchDsfaStatus()
}, [fetchStatus, fetchDsfaStatus])
useEffect(() => {
if (activeTab === 'pipeline') {
fetchPipeline()
}
}, [activeTab, fetchPipeline])
useEffect(() => {
if (activeTab === 'data') {
fetchCustomDocuments()
}
}, [activeTab, fetchCustomDocuments])
// Auto-refresh pipeline status when running
useEffect(() => {
if (activeTab !== 'pipeline' || !autoRefresh) return
const isRunning = pipelineState?.status === 'running'
if (isRunning) {
const interval = setInterval(() => {
fetchPipeline()
fetchStatus() // Also refresh chunk counts
}, 5000) // Every 5 seconds
return () => clearInterval(interval)
}
}, [activeTab, autoRefresh, pipelineState?.status, fetchPipeline, fetchStatus])
// Update elapsed time
useEffect(() => {
if (!pipelineState?.started_at || pipelineState?.status !== 'running') {
setElapsedTime('')
return
}
const updateElapsed = () => {
const start = new Date(pipelineState.started_at!).getTime()
const now = Date.now()
const diff = Math.floor((now - start) / 1000)
const hours = Math.floor(diff / 3600)
const minutes = Math.floor((diff % 3600) / 60)
const seconds = diff % 60
if (hours > 0) {
setElapsedTime(`${hours}h ${minutes}m ${seconds}s`)
} else if (minutes > 0) {
setElapsedTime(`${minutes}m ${seconds}s`)
} else {
setElapsedTime(`${seconds}s`)
}
}
updateElapsed()
const interval = setInterval(updateElapsed, 1000)
return () => clearInterval(interval)
}, [pipelineState?.started_at, pipelineState?.status])
const handleSearch = async () => {
if (!searchQuery.trim()) return
setSearching(true)
try {
const params = new URLSearchParams({
action: 'search',
query: searchQuery,
top_k: '5',
})
if (selectedRegulations.length > 0) {
params.append('regulations', selectedRegulations.join(','))
}
const res = await fetch(`${API_PROXY}?${params}`)
if (res.ok) {
const data = await res.json()
setSearchResults(data.results || [])
}
} catch (error) {
console.error('Search failed:', error)
} finally {
setSearching(false)
}
}
const triggerIngestion = async () => {
setIngestionRunning(true)
setIngestionLog(['Starte Re-Ingestion aller 19 Regulierungen...'])
try {
const res = await fetch(`${API_PROXY}?action=ingest`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ force: true }),
})
if (res.ok) {
const data = await res.json()
setIngestionLog((prev) => [...prev, 'Ingestion gestartet. Job-ID: ' + (data.job_id || 'N/A')])
// Poll for status
const checkStatus = setInterval(async () => {
try {
const statusRes = await fetch(`${API_PROXY}?action=ingestion-status`)
if (statusRes.ok) {
const statusData = await statusRes.json()
if (statusData.completed) {
clearInterval(checkStatus)
setIngestionRunning(false)
setIngestionLog((prev) => [...prev, 'Ingestion abgeschlossen!'])
fetchStatus()
} else if (statusData.current_regulation) {
setIngestionLog((prev) => [
...prev,
`Verarbeite: ${statusData.current_regulation} (${statusData.processed}/${statusData.total})`,
])
}
}
} catch {
// Ignore polling errors
}
}, 5000)
} else {
setIngestionLog((prev) => [...prev, 'Fehler: ' + res.statusText])
setIngestionRunning(false)
}
} catch (error) {
setIngestionLog((prev) => [...prev, 'Fehler: ' + String(error)])
setIngestionRunning(false)
}
}
const getRegulationChunks = (code: string): number => {
return collectionStatus?.regulations?.[code] || 0
}
const getTotalChunks = (): number => {
return collectionStatus?.totalPoints || 0
}
const tabs = [
{ id: 'overview' as TabId, name: 'Uebersicht', icon: '📊' },
{ id: 'regulations' as TabId, name: 'Regulierungen', icon: '📜' },
{ id: 'map' as TabId, name: 'Landkarte', icon: '🗺️' },
{ id: 'search' as TabId, name: 'Suche', icon: '🔍' },
{ id: 'data' as TabId, name: 'Daten', icon: '📁' },
{ id: 'ingestion' as TabId, name: 'Ingestion', icon: '⚙️' },
{ id: 'pipeline' as TabId, name: 'Pipeline', icon: '🔄' },
]
return (
<div className="min-h-screen bg-slate-50">
{/* Header */}
<div className="bg-white border-b px-6 py-4">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-slate-900">Daten & RAG</h1>
<p className="text-slate-600">Legal Corpus Management fuer Compliance</p>
</div>
<Link
href="/ai"
className="flex items-center gap-2 text-slate-600 hover:text-slate-800"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
KI & Automatisierung
</Link>
</div>
</div>
<div className="p-6">
{/* Page Purpose */}
<PagePurpose
title="Daten & RAG"
purpose="Verwalten und durchsuchen Sie 4 RAG-Collections: Legal Corpus (24 Regulierungen), DSFA Corpus (70+ Quellen inkl. internationaler Datenschutzgesetze), NiBiS EH (Bildungsinhalte) und Legal Templates (Dokumentvorlagen). Teil der KI-Daten-Pipeline fuer Compliance und Klausur-Korrektur."
audience={['DSB', 'Compliance Officer', 'Entwickler']}
gdprArticles={['§5 UrhG (Amtliche Werke)', 'Art. 5 DSGVO (Rechenschaftspflicht)']}
architecture={{
services: ['klausur-service (Python)', 'embedding-service (BGE-M3)', 'Qdrant (Vector DB)'],
databases: ['Qdrant: bp_legal_corpus, bp_dsfa_corpus, bp_nibis_eh, bp_legal_templates'],
}}
relatedPages={[
{ name: 'RAG Pipeline', href: '/ai/rag-pipeline', description: 'Neue Dokumente indexieren' },
{ name: 'Klausur-Korrektur', href: '/ai/klausur-korrektur', description: 'RAG-Suche nutzen' },
{ name: 'OCR-Labeling', href: '/ai/ocr-labeling', description: 'Ground Truth erstellen' },
{ name: 'Compliance Hub', href: '/sdk/compliance-hub', description: 'Compliance-Dashboard' },
]}
/>
{/* AI Module Sidebar - Desktop: Fixed, Mobile: FAB + Drawer */}
<AIModuleSidebarResponsive currentModule="rag" />
{/* RAG Collections Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div className="bg-white rounded-xl p-4 border border-slate-200">
<p className="text-xs font-medium text-blue-600 uppercase mb-1">Legal Corpus</p>
<p className="text-2xl font-bold text-slate-900">{loading ? '-' : getTotalChunks().toLocaleString()}</p>
<p className="text-xs text-slate-500">Chunks &middot; {REGULATIONS.length} Regulierungen</p>
</div>
<div className="bg-white rounded-xl p-4 border border-slate-200">
<p className="text-xs font-medium text-purple-600 uppercase mb-1">DSFA Corpus</p>
<p className="text-2xl font-bold text-slate-900">{dsfaLoading ? '-' : (dsfaStatus?.total_chunks || 0).toLocaleString()}</p>
<p className="text-xs text-slate-500">Chunks &middot; {dsfaSources.length || '~70'} Quellen</p>
</div>
<div className="bg-white rounded-xl p-4 border border-slate-200">
<p className="text-xs font-medium text-emerald-600 uppercase mb-1">NiBiS EH</p>
<p className="text-2xl font-bold text-slate-900">28.662</p>
<p className="text-xs text-slate-500">Chunks &middot; Bildungs-Erwartungshorizonte</p>
</div>
<div className="bg-white rounded-xl p-4 border border-slate-200">
<p className="text-xs font-medium text-orange-600 uppercase mb-1">Legal Templates</p>
<p className="text-2xl font-bold text-slate-900">824</p>
<p className="text-xs text-slate-500">Chunks &middot; Dokumentvorlagen</p>
</div>
</div>
{/* Tabs */}
<div className="flex gap-1 mb-6 border-b border-slate-200">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors ${
activeTab === tab.id
? 'border-teal-600 text-teal-600'
: 'border-transparent text-slate-500 hover:text-slate-700'
}`}
>
<span className="mr-2">{tab.icon}</span>
{tab.name}
</button>
))}
</div>
{/* Tab Content */}
{activeTab === 'overview' && (
<div className="space-y-6">
{/* RAG Categories Overview */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h3 className="font-semibold text-slate-900 mb-4">RAG-Kategorien</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<button
onClick={() => { setRegulationCategory('regulations'); setActiveTab('regulations') }}
className="p-4 rounded-lg border border-blue-200 bg-blue-50 hover:bg-blue-100 transition-colors text-left"
>
<p className="text-xs font-medium text-blue-600 uppercase">Gesetze & Regulierungen</p>
<p className="text-2xl font-bold text-slate-900 mt-1">{loading ? '-' : getTotalChunks().toLocaleString()}</p>
<p className="text-xs text-slate-500 mt-1">{REGULATIONS.length} Regulierungen (EU, DE, BSI)</p>
</button>
<button
onClick={() => { setRegulationCategory('dsfa'); setActiveTab('regulations') }}
className="p-4 rounded-lg border border-purple-200 bg-purple-50 hover:bg-purple-100 transition-colors text-left"
>
<p className="text-xs font-medium text-purple-600 uppercase">DSFA Corpus</p>
<p className="text-2xl font-bold text-slate-900 mt-1">{dsfaLoading ? '-' : (dsfaStatus?.total_chunks || 0).toLocaleString()}</p>
<p className="text-xs text-slate-500 mt-1">{dsfaSources.length || '~70'} Quellen (WP248, DSK, Gesetze)</p>
</button>
<div className="p-4 rounded-lg border border-emerald-200 bg-emerald-50 text-left">
<p className="text-xs font-medium text-emerald-600 uppercase">NiBiS EH</p>
<p className="text-2xl font-bold text-slate-900 mt-1">28.662</p>
<p className="text-xs text-slate-500 mt-1">Chunks &middot; Bildungs-Erwartungshorizonte</p>
</div>
<div className="p-4 rounded-lg border border-orange-200 bg-orange-50 text-left">
<p className="text-xs font-medium text-orange-600 uppercase">Legal Templates</p>
<p className="text-2xl font-bold text-slate-900 mt-1">824</p>
<p className="text-xs text-slate-500 mt-1">Chunks &middot; Dokumentvorlagen (VVT, TOM, DSFA)</p>
</div>
</div>
</div>
{/* Quick Stats per Type */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{Object.entries(TYPE_LABELS).map(([type, label]) => {
const regs = REGULATIONS.filter((r) => r.type === type)
const totalChunks = regs.reduce((sum, r) => sum + getRegulationChunks(r.code), 0)
return (
<div key={type} className="bg-white rounded-xl p-4 border border-slate-200">
<div className="flex items-center gap-2 mb-2">
<span className={`px-2 py-0.5 text-xs rounded ${TYPE_COLORS[type]}`}>{label}</span>
<span className="text-slate-500 text-sm">{regs.length} Dok.</span>
</div>
<p className="text-xl font-bold text-slate-900">{totalChunks.toLocaleString()} Chunks</p>
</div>
)
})}
</div>
{/* Top Regulations */}
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<div className="px-4 py-3 border-b bg-slate-50">
<h3 className="font-semibold text-slate-900">Top Regulierungen (nach Chunks)</h3>
</div>
<div className="divide-y">
{REGULATIONS.sort((a, b) => getRegulationChunks(b.code) - getRegulationChunks(a.code))
.slice(0, 5)
.map((reg) => {
const chunks = getRegulationChunks(reg.code)
return (
<div key={reg.code} className="px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-3">
<span className={`px-2 py-0.5 text-xs rounded ${TYPE_COLORS[reg.type]}`}>
{TYPE_LABELS[reg.type]}
</span>
<span className="font-medium text-slate-900">{reg.name}</span>
<span className="text-slate-500 text-sm">({reg.code})</span>
</div>
<span className="font-bold text-teal-600">{chunks.toLocaleString()} Chunks</span>
</div>
)
})}
</div>
</div>
</div>
)}
{activeTab === 'regulations' && (
<div className="space-y-4">
{/* Category Filter */}
<div className="flex items-center gap-2 flex-wrap">
<button
onClick={() => setRegulationCategory('regulations')}
className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${
regulationCategory === 'regulations'
? 'bg-blue-100 text-blue-700 ring-2 ring-blue-300'
: 'bg-white text-slate-600 border border-slate-200 hover:bg-slate-50'
}`}
>
Gesetze & Regulierungen ({REGULATIONS.length})
</button>
<button
onClick={() => setRegulationCategory('dsfa')}
className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${
regulationCategory === 'dsfa'
? 'bg-purple-100 text-purple-700 ring-2 ring-purple-300'
: 'bg-white text-slate-600 border border-slate-200 hover:bg-slate-50'
}`}
>
DSFA Quellen ({dsfaSources.length || '~70'})
</button>
<button
onClick={() => setRegulationCategory('nibis')}
className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${
regulationCategory === 'nibis'
? 'bg-emerald-100 text-emerald-700 ring-2 ring-emerald-300'
: 'bg-white text-slate-600 border border-slate-200 hover:bg-slate-50'
}`}
>
NiBiS Dokumente
</button>
<button
onClick={() => setRegulationCategory('templates')}
className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${
regulationCategory === 'templates'
? 'bg-orange-100 text-orange-700 ring-2 ring-orange-300'
: 'bg-white text-slate-600 border border-slate-200 hover:bg-slate-50'
}`}
>
Templates & Vorlagen
</button>
</div>
{/* Regulations Table (existing) */}
{regulationCategory === 'regulations' && (
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<div className="px-4 py-3 border-b bg-slate-50 flex items-center justify-between">
<h3 className="font-semibold text-slate-900">Alle {REGULATIONS.length} Regulierungen</h3>
<button
onClick={fetchStatus}
className="text-sm text-teal-600 hover:text-teal-700"
>
Aktualisieren
</button>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-slate-50 border-b">
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Code</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Typ</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Name</th>
<th className="px-4 py-3 text-right text-xs font-medium text-slate-500 uppercase">Chunks</th>
<th className="px-4 py-3 text-right text-xs font-medium text-slate-500 uppercase">Erwartet</th>
<th className="px-4 py-3 text-center text-xs font-medium text-slate-500 uppercase">Status</th>
</tr>
</thead>
<tbody className="divide-y">
{REGULATIONS.map((reg) => {
const chunks = getRegulationChunks(reg.code)
const ratio = chunks / (reg.expected * 10) // Rough estimate: 10 chunks per requirement
let statusColor = 'text-red-500'
let statusIcon = '❌'
if (ratio > 0.5) {
statusColor = 'text-green-500'
statusIcon = '✓'
} else if (ratio > 0.1) {
statusColor = 'text-yellow-500'
statusIcon = '⚠'
}
const isExpanded = expandedRegulation === reg.code
return (
<React.Fragment key={reg.code}>
<tr
onClick={() => setExpandedRegulation(isExpanded ? null : reg.code)}
className="hover:bg-slate-50 cursor-pointer transition-colors"
>
<td className="px-4 py-3 font-mono font-medium text-teal-600">
<span className="inline-flex items-center gap-2">
<span className={`transform transition-transform ${isExpanded ? 'rotate-90' : ''}`}></span>
{reg.code}
</span>
</td>
<td className="px-4 py-3">
<span className={`px-2 py-0.5 text-xs rounded ${TYPE_COLORS[reg.type]}`}>
{TYPE_LABELS[reg.type]}
</span>
</td>
<td className="px-4 py-3 text-slate-900">{reg.name}</td>
<td className="px-4 py-3 text-right font-bold">{chunks.toLocaleString()}</td>
<td className="px-4 py-3 text-right text-slate-500">{reg.expected}</td>
<td className={`px-4 py-3 text-center ${statusColor}`}>{statusIcon}</td>
</tr>
{isExpanded && (
<tr key={`${reg.code}-detail`} className="bg-slate-50">
<td colSpan={6} className="px-4 py-4">
<div className="bg-white rounded-lg border border-slate-200 p-4 space-y-3">
<div>
<h4 className="font-semibold text-slate-900 mb-1">{reg.fullName}</h4>
<p className="text-sm text-slate-600">{reg.description}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2 border-t border-slate-100">
<div>
<p className="text-xs font-medium text-slate-500 uppercase mb-1">Relevant fuer</p>
<div className="flex flex-wrap gap-1">
{reg.relevantFor.map((item, idx) => (
<span key={idx} className="px-2 py-0.5 text-xs bg-slate-100 text-slate-600 rounded">
{item}
</span>
))}
</div>
</div>
<div>
<p className="text-xs font-medium text-slate-500 uppercase mb-1">Kernthemen</p>
<div className="flex flex-wrap gap-1">
{reg.keyTopics.map((topic, idx) => (
<span key={idx} className="px-2 py-0.5 text-xs bg-teal-50 text-teal-700 rounded">
{topic}
</span>
))}
</div>
</div>
</div>
<div className="flex items-center justify-between pt-2 border-t border-slate-100 text-xs text-slate-500">
<div className="flex items-center gap-4">
<span>In Kraft seit: {reg.effectiveDate}</span>
{REGULATION_LICENSES[reg.code] && (
<span className="flex items-center gap-1">
<span className="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded text-[10px] font-medium">
{LICENSE_LABELS[REGULATION_LICENSES[reg.code].license] || REGULATION_LICENSES[reg.code].license}
</span>
<span className="text-slate-400">{REGULATION_LICENSES[reg.code].licenseNote}</span>
</span>
)}
</div>
<button
onClick={(e) => {
e.stopPropagation()
setSearchQuery(reg.name)
setActiveTab('search')
}}
className="text-teal-600 hover:text-teal-700 font-medium"
>
In Chunks suchen
</button>
</div>
</div>
</td>
</tr>
)}
</React.Fragment>
)
})}
</tbody>
</table>
</div>
</div>
)}
{/* DSFA Sources */}
{regulationCategory === 'dsfa' && (
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<div className="px-4 py-3 border-b bg-slate-50 flex items-center justify-between">
<div>
<h3 className="font-semibold text-slate-900">DSFA Quellen ({dsfaSources.length || '~70'})</h3>
<p className="text-xs text-slate-500">WP248, DSK Kurzpapiere, Muss-Listen, nationale Datenschutzgesetze</p>
</div>
<button
onClick={fetchDsfaStatus}
className="text-sm text-teal-600 hover:text-teal-700"
>
Aktualisieren
</button>
</div>
{dsfaLoading ? (
<div className="p-8 text-center text-slate-500">Lade DSFA-Quellen...</div>
) : dsfaSources.length === 0 ? (
<div className="p-8 text-center text-slate-500">
<p className="mb-2">Keine DSFA-Quellen vom Backend geladen.</p>
<p className="text-xs">Endpunkt: <code className="bg-slate-100 px-1 rounded">/api/dsfa-corpus?action=sources</code></p>
</div>
) : (
<div className="divide-y">
{dsfaSources.map((source) => {
const isExpanded = expandedDsfaSource === source.source_code
const typeColors: Record<string, string> = {
regulation: 'bg-blue-100 text-blue-700',
legislation: 'bg-indigo-100 text-indigo-700',
guideline: 'bg-teal-100 text-teal-700',
checklist: 'bg-yellow-100 text-yellow-700',
standard: 'bg-green-100 text-green-700',
methodology: 'bg-purple-100 text-purple-700',
specification: 'bg-orange-100 text-orange-700',
catalog: 'bg-pink-100 text-pink-700',
guidance: 'bg-cyan-100 text-cyan-700',
}
return (
<React.Fragment key={source.source_code}>
<div
onClick={() => setExpandedDsfaSource(isExpanded ? null : source.source_code)}
className="px-4 py-3 hover:bg-slate-50 cursor-pointer transition-colors flex items-center justify-between"
>
<div className="flex items-center gap-3">
<span className={`transform transition-transform text-xs ${isExpanded ? 'rotate-90' : ''}`}></span>
<span className="font-mono text-sm text-purple-600 font-medium">{source.source_code}</span>
<span className={`px-2 py-0.5 text-xs rounded ${typeColors[source.document_type] || 'bg-slate-100 text-slate-600'}`}>
{source.document_type}
</span>
<span className="text-sm text-slate-900">{source.name}</span>
</div>
<div className="flex items-center gap-3">
<span className="px-1.5 py-0.5 text-[10px] font-medium bg-slate-100 text-slate-500 rounded uppercase">
{source.language}
</span>
{source.chunk_count != null && (
<span className="text-sm font-bold text-purple-600">{source.chunk_count} Chunks</span>
)}
</div>
</div>
{isExpanded && (
<div className="px-4 pb-4 bg-slate-50">
<div className="bg-white rounded-lg border border-slate-200 p-4 space-y-3">
<div>
<h4 className="font-semibold text-slate-900 mb-1">{source.full_name || source.name}</h4>
{source.organization && (
<p className="text-sm text-slate-600">Organisation: {source.organization}</p>
)}
</div>
<div className="flex items-center gap-4 pt-2 border-t border-slate-100 text-xs text-slate-500">
<span className="flex items-center gap-1">
<span className="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded text-[10px] font-medium">
{LICENSE_LABELS[source.license_code] || source.license_code}
</span>
<span className="text-slate-400">{source.attribution_text}</span>
</span>
</div>
{source.source_url && (
<div className="text-xs">
<a
href={source.source_url}
target="_blank"
rel="noopener noreferrer"
className="text-teal-600 hover:underline"
onClick={(e) => e.stopPropagation()}
>
Quelle: {source.source_url}
</a>
</div>
)}
</div>
</div>
)}
</React.Fragment>
)
})}
</div>
)}
</div>
)}
{/* NiBiS Dokumente (info only) */}
{regulationCategory === 'nibis' && (
<div className="bg-white rounded-xl border border-slate-200 p-6">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-lg bg-emerald-100 flex items-center justify-center text-xl">📚</div>
<div>
<h3 className="font-semibold text-slate-900">NiBiS Erwartungshorizonte</h3>
<p className="text-sm text-slate-500">Collection: <code className="bg-slate-100 px-1 rounded">bp_nibis_eh</code></p>
</div>
</div>
<div className="grid grid-cols-3 gap-4 mb-4">
<div className="bg-emerald-50 rounded-lg p-4 border border-emerald-200">
<p className="text-sm text-emerald-600 font-medium">Chunks</p>
<p className="text-2xl font-bold text-slate-900">28.662</p>
</div>
<div className="bg-emerald-50 rounded-lg p-4 border border-emerald-200">
<p className="text-sm text-emerald-600 font-medium">Vector Size</p>
<p className="text-2xl font-bold text-slate-900">1024</p>
</div>
<div className="bg-emerald-50 rounded-lg p-4 border border-emerald-200">
<p className="text-sm text-emerald-600 font-medium">Typ</p>
<p className="text-2xl font-bold text-slate-900">BGE-M3</p>
</div>
</div>
<p className="text-sm text-slate-600">
Bildungsinhalte aus dem Niedersaechsischen Bildungsserver (NiBiS). Enthaelt Erwartungshorizonte fuer
verschiedene Faecher und Schulformen. Wird ueber die Klausur-Korrektur fuer EH-Matching genutzt.
Diese Daten sind nicht direkt compliance-relevant.
</p>
</div>
)}
{/* Templates (info only) */}
{regulationCategory === 'templates' && (
<div className="bg-white rounded-xl border border-slate-200 p-6">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center text-xl">📋</div>
<div>
<h3 className="font-semibold text-slate-900">Legal Templates & Vorlagen</h3>
<p className="text-sm text-slate-500">Collection: <code className="bg-slate-100 px-1 rounded">bp_legal_templates</code></p>
</div>
</div>
<div className="grid grid-cols-3 gap-4 mb-4">
<div className="bg-orange-50 rounded-lg p-4 border border-orange-200">
<p className="text-sm text-orange-600 font-medium">Chunks</p>
<p className="text-2xl font-bold text-slate-900">824</p>
</div>
<div className="bg-orange-50 rounded-lg p-4 border border-orange-200">
<p className="text-sm text-orange-600 font-medium">Vector Size</p>
<p className="text-2xl font-bold text-slate-900">1024</p>
</div>
<div className="bg-orange-50 rounded-lg p-4 border border-orange-200">
<p className="text-sm text-orange-600 font-medium">Typ</p>
<p className="text-2xl font-bold text-slate-900">BGE-M3</p>
</div>
</div>
<p className="text-sm text-slate-600">
Vorlagen fuer VVT (Verzeichnis von Verarbeitungstaetigkeiten), TOM (Technisch-Organisatorische Massnahmen),
DSFA-Berichte und weitere Compliance-Dokumente. Werden vom AI Compliance SDK fuer die Dokumentgenerierung genutzt.
</p>
</div>
)}
</div>
)}
{activeTab === 'map' && (
<div className="space-y-6">
{/* Industry Filter */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h3 className="font-semibold text-slate-900 mb-4">Regulierungen nach Branche</h3>
<p className="text-sm text-slate-500 mb-4">
Waehlen Sie Ihre Branche, um relevante Regulierungen zu sehen.
</p>
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
{INDUSTRIES.map((industry) => {
const regs = INDUSTRY_REGULATION_MAP[industry.id] || []
return (
<button
key={industry.id}
onClick={() => {
setExpandedRegulation(industry.id === expandedRegulation ? null : industry.id)
}}
className={`p-4 rounded-lg border text-left transition-all ${
expandedRegulation === industry.id
? 'border-teal-500 bg-teal-50 ring-2 ring-teal-200'
: 'border-slate-200 hover:border-slate-300 hover:bg-slate-50'
}`}
>
<div className="text-2xl mb-2">{industry.icon}</div>
<div className="font-medium text-slate-900 text-sm">{industry.name}</div>
<div className="text-xs text-slate-500 mt-1">{regs.length} Regulierungen</div>
</button>
)
})}
</div>
{/* Selected Industry Details */}
{expandedRegulation && INDUSTRIES.find(i => i.id === expandedRegulation) && (
<div className="mt-6 p-4 bg-slate-50 rounded-lg">
{(() => {
const industry = INDUSTRIES.find(i => i.id === expandedRegulation)!
const regCodes = INDUSTRY_REGULATION_MAP[industry.id] || []
const regs = REGULATIONS.filter(r => regCodes.includes(r.code))
return (
<>
<div className="flex items-center gap-3 mb-4">
<span className="text-3xl">{industry.icon}</span>
<div>
<h4 className="font-semibold text-slate-900">{industry.name}</h4>
<p className="text-sm text-slate-500">{industry.description}</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{regs.map((reg) => (
<div
key={reg.code}
className="bg-white p-3 rounded-lg border border-slate-200"
>
<div className="flex items-center gap-2 mb-1">
<span className={`px-2 py-0.5 text-xs rounded ${TYPE_COLORS[reg.type]}`}>
{reg.code}
</span>
</div>
<div className="font-medium text-sm text-slate-900">{reg.name}</div>
<div className="text-xs text-slate-500 mt-1 line-clamp-2">{reg.description}</div>
</div>
))}
</div>
</>
)
})()}
</div>
)}
</div>
{/* Thematic Groups */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h3 className="font-semibold text-slate-900 mb-4">Thematische Cluster</h3>
<p className="text-sm text-slate-500 mb-4">
Regulierungen gruppiert nach Themenbereichen - zeigt Ueberschneidungen.
</p>
<div className="space-y-4">
{THEMATIC_GROUPS.map((group) => (
<div key={group.id} className="border border-slate-200 rounded-lg overflow-hidden">
<div className={`${group.color} px-4 py-2 text-white font-medium flex items-center justify-between`}>
<span>{group.name}</span>
<span className="text-sm opacity-80">{group.regulations.length} Regulierungen</span>
</div>
<div className="p-4">
<p className="text-sm text-slate-600 mb-3">{group.description}</p>
<div className="flex flex-wrap gap-2">
{group.regulations.map((code) => {
const reg = REGULATIONS.find(r => r.code === code)
return (
<span
key={code}
className="px-3 py-1.5 bg-slate-100 rounded-full text-sm font-medium text-slate-700 hover:bg-slate-200 cursor-pointer"
onClick={() => {
setActiveTab('regulations')
setExpandedRegulation(code)
}}
title={reg?.fullName || code}
>
{code}
</span>
)
})}
</div>
</div>
</div>
))}
</div>
</div>
{/* Key Intersections */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h3 className="font-semibold text-slate-900 mb-4">Wichtige Schnittstellen</h3>
<p className="text-sm text-slate-500 mb-4">
Bereiche, in denen sich mehrere Regulierungen ueberschneiden und zusammenwirken.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{KEY_INTERSECTIONS.map((intersection, idx) => (
<div key={idx} className="bg-gradient-to-br from-slate-50 to-slate-100 rounded-lg p-4 border border-slate-200">
<div className="flex flex-wrap gap-1 mb-2">
{intersection.regulations.map((code) => (
<span
key={code}
className="px-2 py-0.5 text-xs font-medium bg-teal-100 text-teal-700 rounded"
>
{code}
</span>
))}
</div>
<div className="font-medium text-slate-900 text-sm mb-1">{intersection.topic}</div>
<div className="text-xs text-slate-500">{intersection.description}</div>
</div>
))}
</div>
</div>
{/* Regulation Matrix */}
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<div className="px-4 py-3 border-b bg-slate-50">
<h3 className="font-semibold text-slate-900">Branchen-Regulierungs-Matrix</h3>
<p className="text-sm text-slate-500">Welche Regulierungen in welchen Branchen gelten</p>
</div>
<div className="overflow-x-auto">
<table className="w-full text-xs">
<thead className="bg-slate-50 border-b">
<tr>
<th className="px-2 py-2 text-left font-medium text-slate-500 sticky left-0 bg-slate-50">Regulierung</th>
{INDUSTRIES.filter(i => i.id !== 'all').map((industry) => (
<th key={industry.id} className="px-2 py-2 text-center font-medium text-slate-500 min-w-[60px]">
<div className="flex flex-col items-center">
<span className="text-lg">{industry.icon}</span>
<span className="text-[10px] leading-tight">{industry.name.split('/')[0]}</span>
</div>
</th>
))}
</tr>
</thead>
<tbody className="divide-y">
{REGULATIONS.map((reg) => (
<tr key={reg.code} className="hover:bg-slate-50">
<td className="px-2 py-2 font-medium text-teal-600 sticky left-0 bg-white">
{reg.code}
</td>
{INDUSTRIES.filter(i => i.id !== 'all').map((industry) => {
const applies = INDUSTRY_REGULATION_MAP[industry.id]?.includes(reg.code)
return (
<td key={industry.id} className="px-2 py-2 text-center">
{applies ? (
<span className="inline-flex items-center justify-center w-5 h-5 bg-teal-100 text-teal-600 rounded-full"></span>
) : (
<span className="inline-flex items-center justify-center w-5 h-5 text-slate-300"></span>
)}
</td>
)
})}
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Future Outlook Section */}
<div className="bg-gradient-to-r from-indigo-50 to-purple-50 rounded-xl border border-indigo-200 p-6">
<div className="flex items-center gap-3 mb-4">
<span className="text-2xl">🔮</span>
<div>
<h3 className="font-semibold text-slate-900">Zukunftsaussicht</h3>
<p className="text-sm text-slate-500">Geplante Aenderungen und neue Regulierungen</p>
</div>
</div>
<div className="space-y-4">
{FUTURE_OUTLOOK.map((item) => (
<div key={item.id} className="bg-white rounded-lg border border-slate-200 overflow-hidden">
<div className="px-4 py-3 flex items-center justify-between bg-slate-50 border-b">
<div className="flex items-center gap-3">
<span className={`px-2 py-1 text-xs font-medium rounded ${
item.status === 'proposed' ? 'bg-yellow-100 text-yellow-700' :
item.status === 'agreed' ? 'bg-green-100 text-green-700' :
item.status === 'withdrawn' ? 'bg-red-100 text-red-700' :
'bg-blue-100 text-blue-700'
}`}>
{item.statusLabel}
</span>
<h4 className="font-semibold text-slate-900">{item.name}</h4>
</div>
<span className="text-sm text-slate-500">Erwartet: {item.expectedDate}</span>
</div>
<div className="p-4">
<p className="text-sm text-slate-600 mb-3">{item.description}</p>
<div className="mb-3">
<p className="text-xs font-medium text-slate-500 uppercase mb-2">Wichtige Aenderungen:</p>
<ul className="text-sm text-slate-600 space-y-1">
{item.keyChanges.slice(0, 4).map((change, idx) => (
<li key={idx} className="flex items-start gap-2">
<span className="text-teal-500 mt-1"></span>
<span>{change}</span>
</li>
))}
{item.keyChanges.length > 4 && (
<li className="text-slate-400 text-xs">+ {item.keyChanges.length - 4} weitere...</li>
)}
</ul>
</div>
<div className="flex items-center justify-between">
<div className="flex flex-wrap gap-1">
{item.affectedRegulations.map((code) => (
<span key={code} className="px-2 py-0.5 text-xs bg-slate-100 text-slate-600 rounded">
{code}
</span>
))}
</div>
<a
href={item.source}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-teal-600 hover:underline"
>
Quelle
</a>
</div>
</div>
</div>
))}
</div>
</div>
{/* Integrated Regulations */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<div className="flex items-center gap-3 mb-4">
<span className="text-2xl"></span>
<div>
<h3 className="font-semibold text-slate-900">Neu integrierte Regulierungen</h3>
<p className="text-sm text-slate-500">Jetzt im RAG-System verfuegbar (Stand: Januar 2025)</p>
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
{INTEGRATED_REGULATIONS.map((reg) => (
<div key={reg.code} className="rounded-lg border border-green-200 bg-green-50 p-3 text-center">
<span className="px-2 py-1 text-sm font-bold bg-green-100 text-green-700 rounded">
{reg.code}
</span>
<p className="text-xs text-slate-600 mt-2">{reg.name}</p>
<p className="text-xs text-green-600 mt-1">Im RAG</p>
</div>
))}
</div>
</div>
{/* Potential Future Regulations */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<div className="flex items-center gap-3 mb-4">
<span className="text-2xl">🔮</span>
<div>
<h3 className="font-semibold text-slate-900">Zukuenftige Regulierungen</h3>
<p className="text-sm text-slate-500">Noch nicht verabschiedet oder zur Erweiterung vorgesehen</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{ADDITIONAL_REGULATIONS.map((reg) => (
<div key={reg.code} className={`rounded-lg border p-4 ${
reg.status === 'active' ? 'border-green-200 bg-green-50' : 'border-yellow-200 bg-yellow-50'
}`}>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className={`px-2 py-0.5 text-xs font-bold rounded ${
reg.type === 'eu_regulation' ? 'bg-blue-100 text-blue-700' : 'bg-purple-100 text-purple-700'
}`}>
{reg.code}
</span>
<span className={`px-2 py-0.5 text-xs rounded ${
reg.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'
}`}>
{reg.status === 'active' ? 'In Kraft' : 'Vorgeschlagen'}
</span>
</div>
<span className={`px-2 py-0.5 text-xs rounded ${
reg.priority === 'high' ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-600'
}`}>
{reg.priority === 'high' ? 'Hohe Prioritaet' : 'Mittel'}
</span>
</div>
<h4 className="font-medium text-slate-900 text-sm mb-1">{reg.name}</h4>
<p className="text-xs text-slate-600 mb-2">{reg.description}</p>
<div className="flex items-center justify-between text-xs">
<span className="text-slate-500">Ab: {reg.effectiveDate}</span>
{reg.celex && (
<a
href={`https://eur-lex.europa.eu/legal-content/DE/TXT/?uri=CELEX:${reg.celex}`}
target="_blank"
rel="noopener noreferrer"
className="text-teal-600 hover:underline"
>
EUR-Lex
</a>
)}
</div>
</div>
))}
</div>
</div>
{/* Legal Basis Info */}
<div className="bg-emerald-50 rounded-xl border border-emerald-200 p-6">
<div className="flex items-center gap-3 mb-4">
<span className="text-2xl"></span>
<div>
<h3 className="font-semibold text-slate-900">{LEGAL_BASIS_INFO.title}</h3>
<p className="text-sm text-emerald-700">{LEGAL_BASIS_INFO.summary}</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{LEGAL_BASIS_INFO.details.map((detail, idx) => (
<div key={idx} className="bg-white rounded-lg border border-emerald-100 p-3">
<div className="flex items-center gap-2 mb-1">
<span className={`px-2 py-0.5 text-xs font-medium rounded ${
detail.status === 'Erlaubt' ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'
}`}>
{detail.status}
</span>
<span className="font-medium text-sm text-slate-900">{detail.aspect}</span>
</div>
<p className="text-xs text-slate-600">{detail.explanation}</p>
</div>
))}
</div>
</div>
</div>
)}
{activeTab === 'search' && (
<div className="space-y-6">
{/* Search Box */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h3 className="font-semibold text-slate-900 mb-4">Semantische Suche</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Suchanfrage</label>
<textarea
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="z.B. 'Welche Anforderungen gibt es fuer KI-Systeme mit hohem Risiko?'"
rows={3}
className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Filter (optional)</label>
<div className="flex flex-wrap gap-2">
{['GDPR', 'AIACT', 'CRA', 'NIS2', 'BSI-TR-03161-1'].map((code) => (
<button
key={code}
onClick={() => {
setSelectedRegulations((prev) =>
prev.includes(code) ? prev.filter((c) => c !== code) : [...prev, code]
)
}}
className={`px-3 py-1 text-sm rounded-full border transition-colors ${
selectedRegulations.includes(code)
? 'bg-teal-100 border-teal-300 text-teal-700'
: 'bg-white border-slate-200 text-slate-600 hover:border-slate-300'
}`}
>
{code}
</button>
))}
</div>
</div>
<button
onClick={handleSearch}
disabled={searching || !searchQuery.trim()}
className="px-6 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 disabled:opacity-50"
>
{searching ? 'Suche...' : 'Suchen'}
</button>
</div>
</div>
{/* Search Results */}
{searchResults.length > 0 && (
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<div className="px-4 py-3 border-b bg-slate-50">
<h3 className="font-semibold text-slate-900">{searchResults.length} Ergebnisse</h3>
</div>
<div className="divide-y">
{searchResults.map((result, i) => (
<div key={i} className="p-4">
<div className="flex items-center gap-2 mb-2">
<span className="px-2 py-0.5 text-xs rounded bg-teal-100 text-teal-700">
{result.regulation_code}
</span>
{result.article && (
<span className="text-sm text-slate-500">Art. {result.article}</span>
)}
<span className="ml-auto text-sm text-slate-400">
Score: {(result.score * 100).toFixed(1)}%
</span>
</div>
<p className="text-slate-700 text-sm">{result.text}</p>
</div>
))}
</div>
</div>
)}
</div>
)}
{activeTab === 'data' && (
<div className="space-y-6">
{/* Upload Document */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h3 className="font-semibold text-slate-900 mb-4">Dokument hochladen (PDF)</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">PDF-Datei</label>
<input
type="file"
accept=".pdf"
onChange={(e) => setUploadFile(e.target.files?.[0] || null)}
className="w-full px-3 py-2 border rounded-lg text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Titel</label>
<input
type="text"
value={uploadTitle}
onChange={(e) => setUploadTitle(e.target.value)}
placeholder="z.B. Firmen-Datenschutzrichtlinie"
className="w-full px-3 py-2 border rounded-lg"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Code (eindeutig)</label>
<input
type="text"
value={uploadCode}
onChange={(e) => setUploadCode(e.target.value.toUpperCase())}
placeholder="z.B. CUSTOM-DSR-01"
className="w-full px-3 py-2 border rounded-lg font-mono"
/>
</div>
</div>
<button
onClick={handleUpload}
disabled={uploading || !uploadFile || !uploadTitle || !uploadCode}
className="mt-4 px-6 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 disabled:opacity-50"
>
{uploading ? 'Wird hochgeladen...' : 'Hochladen & Indexieren'}
</button>
</div>
{/* Add Link */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h3 className="font-semibold text-slate-900 mb-4">Link hinzufuegen (Webseite/PDF)</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">URL</label>
<input
type="url"
value={linkUrl}
onChange={(e) => setLinkUrl(e.target.value)}
placeholder="https://example.com/document.pdf"
className="w-full px-3 py-2 border rounded-lg"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Titel</label>
<input
type="text"
value={linkTitle}
onChange={(e) => setLinkTitle(e.target.value)}
placeholder="z.B. BSI IT-Grundschutz"
className="w-full px-3 py-2 border rounded-lg"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">Code (eindeutig)</label>
<input
type="text"
value={linkCode}
onChange={(e) => setLinkCode(e.target.value.toUpperCase())}
placeholder="z.B. BSI-GRUNDSCHUTZ"
className="w-full px-3 py-2 border rounded-lg font-mono"
/>
</div>
</div>
<button
onClick={handleAddLink}
disabled={addingLink || !linkUrl || !linkTitle || !linkCode}
className="mt-4 px-6 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 disabled:opacity-50"
>
{addingLink ? 'Wird hinzugefuegt...' : 'Link hinzufuegen & Indexieren'}
</button>
</div>
{/* Custom Documents List */}
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<div className="px-4 py-3 border-b bg-slate-50 flex items-center justify-between">
<h3 className="font-semibold text-slate-900">Eigene Dokumente ({customDocuments.length})</h3>
<button
onClick={fetchCustomDocuments}
className="text-sm text-teal-600 hover:text-teal-700"
>
Aktualisieren
</button>
</div>
{customDocuments.length === 0 ? (
<div className="p-8 text-center text-slate-500">
Noch keine eigenen Dokumente hinzugefuegt.
</div>
) : (
<div className="divide-y">
{customDocuments.map((doc) => (
<div key={doc.id} className="px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="w-8 h-8 rounded-lg bg-slate-100 flex items-center justify-center text-lg">
{doc.url ? '🔗' : '📄'}
</span>
<div>
<p className="font-medium text-slate-900">{doc.title}</p>
<p className="text-sm text-slate-500">
<span className="font-mono text-teal-600">{doc.code}</span>
{' • '}
{doc.filename || doc.url}
</p>
</div>
</div>
<div className="flex items-center gap-4">
<span className={`px-2 py-1 rounded text-xs font-medium ${
doc.status === 'indexed' ? 'bg-green-100 text-green-700' :
doc.status === 'error' ? 'bg-red-100 text-red-700' :
doc.status === 'processing' || doc.status === 'fetching' ? 'bg-blue-100 text-blue-700' :
'bg-slate-100 text-slate-700'
}`}>
{doc.status === 'indexed' ? `${doc.chunk_count} Chunks` :
doc.status === 'error' ? 'Fehler' :
doc.status === 'processing' ? 'Verarbeitung...' :
doc.status === 'fetching' ? 'Abruf...' :
doc.status}
</span>
<button
onClick={() => handleDeleteDocument(doc.id)}
className="text-red-500 hover:text-red-700 text-sm"
>
Loeschen
</button>
</div>
</div>
))}
</div>
)}
</div>
{/* Info Box */}
<div className="bg-teal-50 border border-teal-200 rounded-xl p-6">
<h4 className="font-semibold text-teal-800 flex items-center gap-2">
<span></span>
Hinweis zur Verwendung
</h4>
<p className="text-sm text-teal-700 mt-2">
Laden Sie eigene Dokumente (z.B. interne Datenschutzrichtlinien, Vertraege) oder
externe Links hoch. Diese werden automatisch in Chunks aufgeteilt und indexiert.
Nach dem Hinzufuegen koennen Sie im <strong>Pipeline</strong>-Tab die vollstaendige
Compliance-Analyse starten.
</p>
</div>
</div>
)}
{activeTab === 'ingestion' && (
<div className="space-y-6">
{/* Ingestion Control */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h3 className="font-semibold text-slate-900 mb-4">Legal Corpus Re-Ingestion</h3>
<p className="text-slate-600 mb-4">
Startet die Neuindexierung aller 19 Regulierungen. Die Dokumente werden von EUR-Lex,
gesetze-im-internet.de und BSI heruntergeladen, in semantische Chunks aufgeteilt und
mit BGE-M3 Embeddings in Qdrant indexiert.
</p>
<div className="flex items-center gap-4">
<button
onClick={triggerIngestion}
disabled={ingestionRunning}
className="px-6 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 disabled:opacity-50"
>
{ingestionRunning ? 'Laeuft...' : 'Re-Ingestion starten'}
</button>
{ingestionRunning && (
<span className="flex items-center gap-2 text-teal-600">
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Ingestion laeuft...
</span>
)}
</div>
</div>
{/* Ingestion Log */}
{ingestionLog.length > 0 && (
<div className="bg-slate-900 rounded-xl p-4">
<h4 className="text-slate-400 text-sm mb-2">Log</h4>
<div className="font-mono text-sm text-green-400 space-y-1 max-h-64 overflow-y-auto">
{ingestionLog.map((line, i) => (
<div key={i}>{line}</div>
))}
</div>
</div>
)}
{/* Info Box */}
<div className="bg-teal-50 border border-teal-200 rounded-xl p-6">
<h4 className="font-semibold text-teal-800 flex items-center gap-2">
<span>💡</span>
Hinweis zur Datenquelle
</h4>
<p className="text-sm text-teal-700 mt-2">
Alle indexierten Dokumente sind amtliche Werke (§5 UrhG) und damit urheberrechtsfrei.
Sie werden nur fuer RAG/Retrieval verwendet, nicht fuer Modell-Training.
Die Daten werden lokal auf dem Mac Mini verarbeitet und nicht an externe Dienste gesendet.
</p>
</div>
</div>
)}
{activeTab === 'pipeline' && (
<div className="space-y-6">
{/* Pipeline Header */}
<div className="flex items-center justify-between flex-wrap gap-4">
<div className="flex items-center gap-4">
<h3 className="text-lg font-semibold text-slate-900">Compliance Pipeline Status</h3>
{/* Running Time Indicator */}
{pipelineState?.status === 'running' && elapsedTime && (
<div className="flex items-center gap-2 px-3 py-1.5 bg-blue-50 border border-blue-200 rounded-full">
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
<span className="text-sm font-medium text-blue-700">Laufzeit: {elapsedTime}</span>
</div>
)}
</div>
<div className="flex items-center gap-3">
{/* Auto-Refresh Toggle */}
<label className="flex items-center gap-2 text-sm text-slate-600 cursor-pointer">
<input
type="checkbox"
checked={autoRefresh}
onChange={(e) => setAutoRefresh(e.target.checked)}
className="w-4 h-4 text-teal-600 rounded border-slate-300 focus:ring-teal-500"
/>
Auto-Refresh
</label>
{/* Start Pipeline Button */}
{(!pipelineState || pipelineState.status !== 'running') && (
<button
onClick={() => handleStartPipeline(false)}
disabled={pipelineStarting}
className="flex items-center gap-2 px-4 py-2 text-sm bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50"
>
{pipelineStarting ? (
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
) : (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)}
Pipeline starten
</button>
)}
<button
onClick={fetchPipeline}
disabled={pipelineLoading}
className="flex items-center gap-2 px-4 py-2 text-sm bg-teal-600 text-white rounded-lg hover:bg-teal-700 disabled:opacity-50"
>
{pipelineLoading ? (
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
) : (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
)}
Aktualisieren
</button>
</div>
</div>
{/* No Data */}
{(!pipelineState || pipelineState.status === 'no_data') && !pipelineLoading && (
<div className="bg-white rounded-xl border border-slate-200 p-8 text-center">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-slate-100 flex items-center justify-center">
<svg className="w-8 h-8 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<h4 className="text-lg font-semibold text-slate-900 mb-2">Keine Pipeline-Daten</h4>
<p className="text-slate-600 mb-4">
Es wurde noch keine Pipeline ausgefuehrt. Starten Sie die Compliance-Pipeline um Checkpoint-Daten zu sehen.
</p>
<button
onClick={() => handleStartPipeline(false)}
disabled={pipelineStarting}
className="inline-flex items-center gap-2 px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50"
>
{pipelineStarting ? (
<>
<svg className="animate-spin h-5 w-5" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Startet...
</>
) : (
<>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Pipeline jetzt starten
</>
)}
</button>
</div>
)}
{/* Pipeline Status */}
{pipelineState && pipelineState.status !== 'no_data' && (
<>
{/* Status Card */}
<div className="bg-white rounded-xl border border-slate-200 p-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
pipelineState.status === 'completed' ? 'bg-green-100' :
pipelineState.status === 'running' ? 'bg-blue-100' :
pipelineState.status === 'failed' ? 'bg-red-100' : 'bg-slate-100'
}`}>
{pipelineState.status === 'completed' && (
<svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
)}
{pipelineState.status === 'running' && (
<svg className="w-6 h-6 text-blue-600 animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
)}
{pipelineState.status === 'failed' && (
<svg className="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
)}
</div>
<div>
<h4 className="font-semibold text-slate-900">Pipeline {pipelineState.pipeline_id}</h4>
<p className="text-sm text-slate-500">
Gestartet: {pipelineState.started_at ? new Date(pipelineState.started_at).toLocaleString('de-DE') : '-'}
{pipelineState.completed_at && ` | Beendet: ${new Date(pipelineState.completed_at).toLocaleString('de-DE')}`}
</p>
</div>
</div>
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
pipelineState.status === 'completed' ? 'bg-green-100 text-green-700' :
pipelineState.status === 'running' ? 'bg-blue-100 text-blue-700' :
pipelineState.status === 'failed' ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-700'
}`}>
{pipelineState.status === 'completed' ? 'Abgeschlossen' :
pipelineState.status === 'running' ? 'Laeuft' :
pipelineState.status === 'failed' ? 'Fehlgeschlagen' : pipelineState.status}
</span>
</div>
</div>
{/* Current Progress - only when running */}
{pipelineState.status === 'running' && pipelineState.current_phase && (
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl border border-blue-200 p-6">
<div className="flex items-center justify-between mb-4">
<h4 className="font-semibold text-blue-900 flex items-center gap-2">
<svg className="w-5 h-5 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
Aktuelle Verarbeitung
</h4>
<span className="text-sm text-blue-600">Phase: {pipelineState.current_phase}</span>
</div>
{/* Phase Progress Indicator */}
<div className="flex items-center gap-2 mb-4">
{['ingestion', 'extraction', 'controls', 'measures'].map((phase, idx) => (
<div key={phase} className="flex-1 flex items-center">
<div className={`flex-1 h-2 rounded-full ${
pipelineState.current_phase === phase ? 'bg-blue-500 animate-pulse' :
pipelineState.checkpoints?.some((c: PipelineCheckpoint) => c.phase === phase && c.status === 'completed') ? 'bg-green-500' :
'bg-slate-200'
}`} />
{idx < 3 && <div className="w-2" />}
</div>
))}
</div>
<div className="flex justify-between text-xs text-slate-500 mb-4">
<span>Ingestion</span>
<span>Extraktion</span>
<span>Controls</span>
<span>Massnahmen</span>
</div>
{/* Current checkpoint details */}
{pipelineState.checkpoints?.filter((c: PipelineCheckpoint) => c.status === 'running').map((checkpoint: PipelineCheckpoint, idx: number) => (
<div key={idx} className="bg-white/60 rounded-lg p-4 mt-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-3 h-3 bg-blue-500 rounded-full animate-pulse" />
<span className="font-medium text-slate-900">{checkpoint.name}</span>
</div>
{checkpoint.metrics && Object.keys(checkpoint.metrics).length > 0 && (
<div className="flex gap-2">
{Object.entries(checkpoint.metrics).slice(0, 3).map(([key, value]) => (
<span key={key} className="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">
{key.replace(/_/g, ' ')}: {typeof value === 'number' ? value.toLocaleString() : String(value)}
</span>
))}
</div>
)}
</div>
</div>
))}
{/* Live chunk count */}
<div className="mt-4 flex items-center justify-between text-sm">
<span className="text-slate-600">Chunks in Qdrant:</span>
<span className="font-bold text-blue-700">{collectionStatus?.totalPoints?.toLocaleString() || '-'}</span>
</div>
</div>
)}
{/* Validation Summary */}
{pipelineState.validation_summary && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-white rounded-xl border border-green-200 p-4">
<p className="text-sm text-slate-500">Bestanden</p>
<p className="text-2xl font-bold text-green-600">{pipelineState.validation_summary.passed}</p>
</div>
<div className="bg-white rounded-xl border border-yellow-200 p-4">
<p className="text-sm text-slate-500">Warnungen</p>
<p className="text-2xl font-bold text-yellow-600">{pipelineState.validation_summary.warning}</p>
</div>
<div className="bg-white rounded-xl border border-red-200 p-4">
<p className="text-sm text-slate-500">Fehlgeschlagen</p>
<p className="text-2xl font-bold text-red-600">{pipelineState.validation_summary.failed}</p>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-4">
<p className="text-sm text-slate-500">Gesamt</p>
<p className="text-2xl font-bold text-slate-700">{pipelineState.validation_summary.total}</p>
</div>
</div>
)}
{/* Checkpoints */}
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
<div className="px-4 py-3 border-b bg-slate-50">
<h3 className="font-semibold text-slate-900">Checkpoints ({pipelineState.checkpoints?.length || 0})</h3>
</div>
<div className="divide-y">
{pipelineState.checkpoints?.map((checkpoint, idx) => (
<div key={idx} className="p-4">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3">
<span className={`w-3 h-3 rounded-full ${
checkpoint.phase === 'ingestion' ? 'bg-blue-500' :
checkpoint.phase === 'extraction' ? 'bg-purple-500' :
checkpoint.phase === 'controls' ? 'bg-green-500' : 'bg-orange-500'
}`} />
<span className="font-medium text-slate-900">{checkpoint.name}</span>
<span className="text-sm text-slate-500">
({checkpoint.phase}) |
{checkpoint.duration_seconds ? ` ${checkpoint.duration_seconds.toFixed(1)}s` : ' -'}
</span>
</div>
<span className={`px-2 py-0.5 rounded text-xs font-medium ${
checkpoint.status === 'completed' ? 'bg-green-100 text-green-700' :
checkpoint.status === 'running' ? 'bg-blue-100 text-blue-700' :
checkpoint.status === 'failed' ? 'bg-red-100 text-red-700' : 'bg-slate-100 text-slate-700'
}`}>
{checkpoint.status}
</span>
</div>
{/* Metrics */}
{Object.keys(checkpoint.metrics || {}).length > 0 && (
<div className="flex flex-wrap gap-2 mt-2">
{Object.entries(checkpoint.metrics).map(([key, value]) => (
<span key={key} className="px-2 py-1 bg-slate-100 rounded text-xs text-slate-600">
{key.replace(/_/g, ' ')}: <strong>{typeof value === 'number' ? value.toLocaleString() : String(value)}</strong>
</span>
))}
</div>
)}
{/* Validations */}
{checkpoint.validations?.length > 0 && (
<div className="mt-3 space-y-1">
{checkpoint.validations.map((v, vIdx) => (
<div key={vIdx} className="flex items-center gap-2 text-sm">
<span className={`w-4 h-4 flex items-center justify-center ${
v.status === 'passed' ? 'text-green-500' :
v.status === 'warning' ? 'text-yellow-500' : 'text-red-500'
}`}>
{v.status === 'passed' ? '✓' : v.status === 'warning' ? '⚠' : '✗'}
</span>
<span className="text-slate-700">{v.name}:</span>
<span className="text-slate-500">{v.message}</span>
</div>
))}
</div>
)}
{/* Error */}
{checkpoint.error && (
<div className="mt-2 p-2 bg-red-50 border border-red-200 rounded text-sm text-red-700">
{checkpoint.error}
</div>
)}
</div>
))}
{(!pipelineState.checkpoints || pipelineState.checkpoints.length === 0) && (
<div className="p-4 text-center text-slate-500">
Noch keine Checkpoints vorhanden.
</div>
)}
</div>
</div>
{/* Summary */}
{Object.keys(pipelineState.summary || {}).length > 0 && (
<div className="bg-white rounded-xl border border-slate-200 p-4">
<h4 className="font-semibold text-slate-900 mb-3">Zusammenfassung</h4>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{Object.entries(pipelineState.summary).map(([key, value]) => (
<div key={key}>
<p className="text-sm text-slate-500">{key.replace(/_/g, ' ')}</p>
<p className="font-bold text-slate-900">
{typeof value === 'number' ? value.toLocaleString() : String(value)}
</p>
</div>
))}
</div>
</div>
)}
</>
)}
</div>
)}
</div>
</div>
)
}