docs+tests: Phase 2 RAG audit — missing tests, dev docs, SDK flow page
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 33s
CI / test-python-backend-compliance (push) Successful in 27s
CI / test-python-document-crawler (push) Successful in 20s
CI / test-python-dsms-gateway (push) Successful in 16s

- Add rag-query.test.ts (7 Jest tests for shared queryRAG utility)
- Add test_routes_legal_context.py (3 tests for ?include_legal_context param)
- Update ARCHITECTURE.md with multi-collection RAG section (3.3)
- Update DEVELOPER.md with RAG usage examples, collection table, error tolerance
- Add SDK flow page with updated requirements + DSFA RAG descriptions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-02 09:26:57 +01:00
parent 14a99322eb
commit d9f819e5be
6 changed files with 2090 additions and 41 deletions

View File

@@ -0,0 +1,837 @@
/**
* SDK Flow Visualization - Step-Definitionen mit Datenfluss
*
* Beschreibt alle SDK-Steps mit:
* - Inputs/Outputs (SDKState-Properties)
* - DB-Tabellen und Modus
* - RAG-Collections und Verwendungszweck
* - Checkpoint-Informationen
* - Detaillierte Beschreibungen
*/
export interface SDKFlowStep {
id: string
name: string
nameShort: string
package: 'vorbereitung' | 'analyse' | 'dokumentation' | 'rechtliche-texte' | 'betrieb'
seq: number
checkpointId?: string
checkpointType?: 'REQUIRED' | 'RECOMMENDED'
checkpointReviewer?: 'NONE' | 'DSB' | 'LEGAL'
// Beschreibung
description: string
descriptionLong: string
legalBasis?: string
// Datenfluss
inputs: string[]
outputs: string[]
prerequisiteSteps: string[]
// Infrastruktur
dbTables: string[]
dbMode: 'read' | 'write' | 'read/write' | 'none'
ragCollections: string[]
ragPurpose?: string
// Generierung
generates?: string[]
isOptional?: boolean
url: string
}
// =============================================================================
// PACKAGE METADATA
// =============================================================================
export const FLOW_PACKAGES = {
vorbereitung: {
name: 'Vorbereitung',
nameShort: 'Vorb.',
icon: '🎯',
color: { bg: '#dbeafe', border: '#3b82f6', text: '#1e40af' },
},
analyse: {
name: 'Analyse',
nameShort: 'Analyse',
icon: '🔍',
color: { bg: '#fef3c7', border: '#f59e0b', text: '#92400e' },
},
dokumentation: {
name: 'Dokumentation',
nameShort: 'Doku',
icon: '📋',
color: { bg: '#ede9fe', border: '#7c3aed', text: '#5b21b6' },
},
'rechtliche-texte': {
name: 'Rechtliche Texte',
nameShort: 'Legal',
icon: '📝',
color: { bg: '#d1fae5', border: '#10b981', text: '#065f46' },
},
betrieb: {
name: 'Betrieb',
nameShort: 'Betrieb',
icon: '⚙️',
color: { bg: '#f1f5f9', border: '#64748b', text: '#334155' },
},
} as const
// =============================================================================
// ALL SDK FLOW STEPS
// =============================================================================
export const SDK_FLOW_STEPS: SDKFlowStep[] = [
// =========================================================================
// PAKET 1: VORBEREITUNG (seq 100-700)
// =========================================================================
{
id: 'company-profile',
name: 'Unternehmensprofil',
nameShort: 'Profil',
package: 'vorbereitung',
seq: 100,
checkpointId: 'CP-PROF',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Erfassung aller Stammdaten des Unternehmens als Grundlage fuer die Compliance-Analyse.',
descriptionLong: 'Hier werden alle relevanten Unternehmensdaten erfasst: Firmenname, Rechtsform, Branche, Mitarbeiterzahl, Standorte, Datenschutzbeauftragter und Verantwortlicher. Diese Daten bilden die Basis fuer alle nachfolgenden Compliance-Schritte, da sie bestimmen, welche Regulierungen anwendbar sind (z.B. DSGVO, NIS2, AI Act). Ohne ein vollstaendiges Unternehmensprofil koennen keine weiteren Schritte durchgefuehrt werden.',
inputs: [],
outputs: ['companyProfile', 'complianceScope'],
prerequisiteSteps: [],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/company-profile',
},
{
id: 'compliance-scope',
name: 'Compliance Scope',
nameShort: 'Scope',
package: 'vorbereitung',
seq: 200,
checkpointId: 'CP-SCOPE',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Bestimmung der Compliance-Tiefe und des Umfangs basierend auf Unternehmensprofil.',
descriptionLong: 'Basierend auf dem Unternehmensprofil wird automatisch ermittelt, wie tiefgehend die Compliance-Analyse sein muss. Kleine Unternehmen mit wenig Datenverarbeitung erhalten eine "BASIS"-Tiefe, waehrend grosse Unternehmen mit sensiblen Daten oder KI-Systemen eine "ERWEITERT" oder "VOLLSTAENDIG"-Tiefe erhalten. Der Compliance-Scope bestimmt, welche Module aktiviert werden und wie detailliert die Dokumentation sein muss.',
inputs: ['companyProfile'],
outputs: ['complianceDepthLevel'],
prerequisiteSteps: ['company-profile'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/compliance-scope',
},
{
id: 'use-case-assessment',
name: 'Anwendungsfall-Erfassung',
nameShort: 'Anwendung',
package: 'vorbereitung',
seq: 300,
checkpointId: 'CP-UC',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Systematische Erfassung aller Datenverarbeitungs- und KI-Anwendungsfaelle.',
descriptionLong: 'In diesem Schritt werden alle konkreten Anwendungsfaelle erfasst, in denen das Unternehmen personenbezogene Daten verarbeitet oder KI-Systeme einsetzt. Fuer jeden Use Case wird ermittelt: Welche Daten werden verarbeitet? Welche Personen sind betroffen (Kunden, Mitarbeiter, Schueler)? Welche Technologien kommen zum Einsatz? Die RAG-Collection bp_compliance_ce wird verwendet, um relevante CE-Regulierungen automatisch den Use Cases zuzuordnen (UCCA - Use Case Compliance Assessment).',
legalBasis: 'Art. 30 DSGVO (Verzeichnis von Verarbeitungstaetigkeiten)',
inputs: ['companyProfile'],
outputs: ['useCases'],
prerequisiteSteps: ['company-profile'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_ce'],
ragPurpose: 'CE-Regulierungen fuer Use-Case Matching',
isOptional: false,
url: '/sdk/advisory-board',
},
{
id: 'import',
name: 'Dokument-Import',
nameShort: 'Import',
package: 'vorbereitung',
seq: 400,
description: 'Import bestehender Compliance-Dokumente (Datenschutzerklaerungen, TOMs, VVTs).',
descriptionLong: 'Optionaler Schritt zum Import bereits vorhandener Compliance-Dokumente. Der Document Crawler analysiert hochgeladene PDFs, Word-Dokumente oder bestehende Datenschutzerklaerungen und extrahiert automatisch relevante Informationen wie bestehende TOMs, Verarbeitungsverzeichnisse oder Risikoanalysen. Diese importierten Daten werden als Grundlage fuer die nachfolgenden Schritte verwendet, damit nicht alles von Null aufgebaut werden muss.',
inputs: ['useCases'],
outputs: ['importedDocuments'],
prerequisiteSteps: ['use-case-assessment'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: true,
url: '/sdk/import',
},
{
id: 'screening',
name: 'System Screening',
nameShort: 'Screening',
package: 'vorbereitung',
seq: 500,
checkpointId: 'CP-SCAN',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Technische Analyse der eingesetzten Software, Dienste und deren Abhaengigkeiten.',
descriptionLong: 'Das System Screening analysiert die technische Infrastruktur des Unternehmens. Es werden alle eingesetzten Software-Systeme, Cloud-Dienste, APIs und deren Abhaengigkeiten erfasst. Dabei wird auch eine SBOM (Software Bill of Materials) erstellt, die alle Drittanbieter-Komponenten und deren Lizenzen dokumentiert. Das Screening identifiziert potenzielle Compliance-Risiken wie Datentransfers in Drittlaender, unsichere Abhaengigkeiten oder fehlende Verschluesselung.',
inputs: ['useCases'],
outputs: ['screening', 'sbom'],
prerequisiteSteps: ['use-case-assessment'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/screening',
},
{
id: 'modules',
name: 'Compliance Modules',
nameShort: 'Module',
package: 'vorbereitung',
seq: 600,
checkpointId: 'CP-MOD',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Aktivierung der relevanten Compliance-Module basierend auf Screening-Ergebnissen.',
descriptionLong: 'Basierend auf dem Unternehmensprofil und den Screening-Ergebnissen werden die relevanten Compliance-Module aktiviert. Module umfassen z.B. DSGVO-Grundschutz, AI Act, NIS2, ePrivacy, Whistleblower-Richtlinie usw. Die RAG-Collection bp_compliance_gesetze wird verwendet, um aktuelle Gesetzestexte den Modulen zuzuordnen. Nur aktivierte Module erzeugen in den nachfolgenden Schritten Anforderungen, Controls und Dokumentation.',
inputs: ['companyProfile', 'screening'],
outputs: ['modules'],
prerequisiteSteps: ['screening'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_gesetze'],
ragPurpose: 'Regulierungen den Modulen zuordnen',
isOptional: false,
url: '/sdk/modules',
},
{
id: 'source-policy',
name: 'Source Policy',
nameShort: 'Quellen',
package: 'vorbereitung',
seq: 700,
checkpointId: 'CP-SPOL',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Festlegung der Rechtsquellen und Normen fuer die Compliance-Analyse.',
descriptionLong: 'Die Source Policy definiert, welche Rechtsquellen und Normen fuer die Compliance-Analyse herangezogen werden. Dies umfasst EU-Verordnungen (DSGVO, AI Act, NIS2), nationale Gesetze (BDSG, TTDSG), Branchenstandards (ISO 27001, BSI Grundschutz) und interne Richtlinien. Die Source Policy stellt sicher, dass alle nachfolgenden Schritte konsistent auf denselben Rechtsgrundlagen basieren.',
inputs: ['modules'],
outputs: ['sourcePolicy'],
prerequisiteSteps: ['modules'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/source-policy',
},
// =========================================================================
// PAKET 2: ANALYSE (seq 1000-1600)
// =========================================================================
{
id: 'requirements',
name: 'Requirements',
nameShort: 'Anforderungen',
package: 'analyse',
seq: 1000,
checkpointId: 'CP-REQ',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Ableitung konkreter Compliance-Anforderungen aus den aktivierten Modulen, mit RAG-Anreicherung.',
descriptionLong: 'Aus den aktivierten Modulen und der Source Policy werden konkrete, umsetzbare Compliance-Anforderungen abgeleitet. Die RAG-Collections bp_compliance_recht (DE-Gesetze) und bp_compliance_ce (EU-Verordnungen) liefern aktuelle Rechtstexte, aus denen spezifische Pflichten extrahiert werden. Die KI-gestuetzte Interpretation (interpret_requirement) und Control-Vorschlaege (suggest_controls) werden mit RAG-Kontext angereichert — die Collection wird automatisch anhand des Regulation-Codes gewaehlt (EU → bp_compliance_ce, DE → bp_compliance_recht). Der API-Endpunkt GET /requirements/{id}?include_legal_context=true liefert optional passende Rechtstexte als Kontext mit.',
legalBasis: 'Art. 5, 24, 25 DSGVO (Rechenschaftspflicht)',
inputs: ['modules', 'sourcePolicy'],
outputs: ['requirements'],
prerequisiteSteps: ['source-policy'],
dbTables: ['compliance_requirements'],
dbMode: 'read',
ragCollections: ['bp_compliance_recht', 'bp_compliance_ce'],
ragPurpose: 'Rechtliche Anforderungen ableiten + AI-Interpretation mit Rechtskontext anreichern (Collection-Routing: EU→ce, DE→recht)',
isOptional: false,
url: '/sdk/requirements',
},
{
id: 'controls',
name: 'Controls',
nameShort: 'Controls',
package: 'analyse',
seq: 1100,
checkpointId: 'CP-CTRL',
checkpointType: 'REQUIRED',
checkpointReviewer: 'DSB',
description: 'Definition technischer und organisatorischer Kontrollen zur Erfuellung der Anforderungen.',
descriptionLong: 'Fuer jede Compliance-Anforderung werden konkrete Controls (Kontrollmassnahmen) definiert. Controls sind technische oder organisatorische Massnahmen, die sicherstellen, dass eine Anforderung erfuellt wird. Beispiele: Zugriffskontrolle (RBAC), Verschluesselung (AES-256), Logging, Schulungspflichten. Der Datenschutzbeauftragte (DSB) muss diesen Schritt freigeben, da die Controls die Basis fuer die gesamte Sicherheitsarchitektur bilden.',
legalBasis: 'Art. 32 DSGVO (Sicherheit der Verarbeitung)',
inputs: ['requirements'],
outputs: ['controls'],
prerequisiteSteps: ['requirements'],
dbTables: ['compliance_controls'],
dbMode: 'read/write',
ragCollections: [],
isOptional: false,
url: '/sdk/controls',
},
{
id: 'evidence',
name: 'Evidence',
nameShort: 'Nachweise',
package: 'analyse',
seq: 1200,
checkpointId: 'CP-EVI',
checkpointType: 'RECOMMENDED',
checkpointReviewer: 'NONE',
description: 'Sammlung und Verwaltung von Nachweisen fuer die Umsetzung der Controls.',
descriptionLong: 'Fuer jeden Control wird dokumentiert, wie und wann er umgesetzt wurde. Evidence (Nachweise) koennen Screenshots, Konfigurationsdateien, Audit-Logs, Schulungszertifikate oder Testprotokolle sein. Die Evidence-Sammlung ist essentiell fuer Audits und Zertifizierungen, da sie die tatsaechliche Umsetzung der Compliance-Massnahmen belegt. Jeder Nachweis wird mit Zeitstempel, Verantwortlichem und Gueltigkeitsdauer versehen.',
legalBasis: 'Art. 5 Abs. 2 DSGVO (Rechenschaftspflicht)',
inputs: ['controls'],
outputs: ['evidence'],
prerequisiteSteps: ['controls'],
dbTables: ['compliance_evidence'],
dbMode: 'write',
ragCollections: [],
isOptional: false,
url: '/sdk/evidence',
},
{
id: 'risks',
name: 'Risk Matrix',
nameShort: 'Risiken',
package: 'analyse',
seq: 1300,
checkpointId: 'CP-RISK',
checkpointType: 'REQUIRED',
checkpointReviewer: 'DSB',
description: 'Bewertung aller Datenschutz- und Compliance-Risiken in einer Risikomatrix.',
descriptionLong: 'Die Risikomatrix bewertet jedes identifizierte Risiko nach Eintrittswahrscheinlichkeit und Schadenshoehe. Risiken werden aus den Controls und Modulen abgeleitet — z.B. "Datenverlust durch fehlende Verschluesselung" oder "Bussgeld durch unvollstaendiges VVT". Fuer jedes Risiko werden Massnahmen zur Risikominderung vorgeschlagen. Der DSB muss die Risikobewertung freigeben, da sie die Grundlage fuer die DSFA und weitere Schutzmassnahmen bildet.',
legalBasis: 'Art. 35 DSGVO (Datenschutz-Folgenabschaetzung)',
inputs: ['controls', 'modules'],
outputs: ['risks'],
prerequisiteSteps: ['evidence'],
dbTables: ['compliance_risks'],
dbMode: 'write',
ragCollections: [],
isOptional: false,
url: '/sdk/risks',
},
{
id: 'ai-act',
name: 'AI Act Klassifizierung',
nameShort: 'AI Act',
package: 'analyse',
seq: 1400,
checkpointId: 'CP-AI',
checkpointType: 'REQUIRED',
checkpointReviewer: 'LEGAL',
description: 'Klassifizierung aller KI-Systeme nach EU AI Act Risikokategorien.',
descriptionLong: 'Jeder Use Case, der KI-Systeme einsetzt, wird nach dem EU AI Act klassifiziert: Unakzeptabel (verboten), Hochrisiko (strenge Auflagen), Begrenzt (Transparenzpflicht) oder Minimal (keine Auflagen). Fuer Hochrisiko-KI-Systeme werden spezifische Pflichten abgeleitet: Risikomanagementsystem, Datenqualitaet, technische Dokumentation, Transparenz, menschliche Aufsicht, Genauigkeit und Robustheit. Die Rechtsabteilung (LEGAL) muss die Klassifizierung freigeben.',
legalBasis: 'EU AI Act Art. 6-9 (Risikokategorien)',
inputs: ['useCases', 'companyProfile'],
outputs: ['aiActClassification', 'obligations'],
prerequisiteSteps: ['risks'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_ce'],
ragPurpose: 'EU AI Act Regulierungstexte',
isOptional: false,
url: '/sdk/ai-act',
},
{
id: 'audit-checklist',
name: 'Audit Checklist',
nameShort: 'Checklist',
package: 'analyse',
seq: 1500,
checkpointId: 'CP-CHK',
checkpointType: 'RECOMMENDED',
checkpointReviewer: 'NONE',
description: 'Erstellung einer pruefbaren Checkliste fuer interne und externe Audits.',
descriptionLong: 'Aus den Requirements und Controls wird eine strukturierte Audit-Checkliste generiert. Jeder Checkpunkt verweist auf die zugrundeliegende Anforderung, den zugehoerigen Control und den erwarteten Nachweis. Die Checkliste kann fuer interne Self-Assessments oder als Vorbereitung auf externe Audits (z.B. ISO 27001, BSI Grundschutz) verwendet werden. Sie wird automatisch aktualisiert, wenn sich Requirements oder Controls aendern.',
inputs: ['requirements', 'controls'],
outputs: ['checklist'],
prerequisiteSteps: ['ai-act'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/audit-checklist',
},
{
id: 'audit-report',
name: 'Audit Report',
nameShort: 'Report',
package: 'analyse',
seq: 1600,
checkpointId: 'CP-AREP',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Generierung eines vollstaendigen Audit-Reports mit Findings und Empfehlungen.',
descriptionLong: 'Der Audit Report fasst alle Ergebnisse der Analyse-Phase zusammen: Erfuellte und offene Anforderungen, Control-Wirksamkeit, Evidence-Status und Risikobewertung. Er wird als PDF generiert und enthaelt Findings (Feststellungen), Empfehlungen und einen Massnahmenplan. Der Report dient als Nachweis gegenueber Aufsichtsbehoerden und als Grundlage fuer die Dokumentations-Phase.',
inputs: ['checklist', 'controls', 'evidence'],
outputs: ['auditReport'],
prerequisiteSteps: ['audit-checklist'],
dbTables: ['compliance_audit_sessions'],
dbMode: 'write',
ragCollections: [],
generates: ['Audit-Report (PDF)'],
isOptional: false,
url: '/sdk/audit-report',
},
// =========================================================================
// PAKET 3: DOKUMENTATION (seq 2000-2400)
// =========================================================================
{
id: 'obligations',
name: 'Pflichtenuebersicht',
nameShort: 'Pflichten',
package: 'dokumentation',
seq: 2000,
checkpointId: 'CP-OBL',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Zusammenfassung aller gesetzlichen Pflichten aus DSGVO, AI Act, NIS2.',
descriptionLong: 'Die Pflichtenuebersicht konsolidiert alle gesetzlichen Pflichten, die sich aus den Requirements, der AI-Act-Klassifizierung und den aktivierten Modulen ergeben. Fuer jede Pflicht wird angegeben: Welches Gesetz (DSGVO, AI Act, NIS2), welcher Artikel, welche Frist, wer verantwortlich ist und welche Massnahmen erforderlich sind. Die RAG-Collection bp_compliance_recht liefert aktuelle Pflichtentexte und Auslegungshinweise.',
legalBasis: 'Art. 5 Abs. 2 DSGVO, Art. 9 AI Act',
inputs: ['requirements', 'aiActClassification', 'modules'],
outputs: ['obligationsOverview'],
prerequisiteSteps: ['audit-report'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_recht'],
ragPurpose: 'NIS2, DSGVO, AI Act Pflichtentexte',
isOptional: false,
url: '/sdk/obligations',
},
{
id: 'dsfa',
name: 'DSFA',
nameShort: 'DSFA',
package: 'dokumentation',
seq: 2100,
checkpointId: 'CP-DSFA',
checkpointType: 'REQUIRED',
checkpointReviewer: 'DSB',
description: 'Datenschutz-Folgenabschaetzung fuer Hochrisiko-Verarbeitungen. Draft-Modus (v1 + v2) nutzt RAG fuer rechtliche Praezision.',
descriptionLong: 'Die Datenschutz-Folgenabschaetzung (DSFA) ist nach Art. 35 DSGVO fuer Verarbeitungen mit hohem Risiko vorgeschrieben. Sie bewertet systematisch die Notwendigkeit, Verhaeltnismaessigkeit und Risiken der Datenverarbeitung. Die DSFA enthaelt: Beschreibung der Verarbeitung, Bewertung der Notwendigkeit, Risikobewertung fuer die Betroffenen, geplante Abhilfemassnahmen. Der DSB muss die DSFA freigeben. Bei verbleibendem Hochrisiko muss die Aufsichtsbehoerde konsultiert werden. Sowohl der v1- als auch der v2-Draft-Modus werden mit rechtlichem Kontext aus dem RAG-Corpus (bp_dsfa_corpus) angereichert. Die shared queryRAG-Utility (rag-query.ts) wird von Chat- und Draft-Pipelines gemeinsam genutzt.',
legalBasis: 'Art. 35, 36 DSGVO (DSFA und vorherige Konsultation)',
inputs: ['risks', 'aiActClassification', 'modules'],
outputs: ['dsfa'],
prerequisiteSteps: ['obligations'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_dsfa_corpus'],
ragPurpose: 'DSFA-Vorlagen, Bewertungskriterien und Art. 35 DSGVO Rechtstexte. v1-Pipeline haengt RAG-Kontext an System-Prompt an, v2-Pipeline injiziert ihn pro Block als RECHTSKONTEXT-Referenz.',
generates: ['DSFA-Report'],
isOptional: true,
url: '/sdk/dsfa',
},
{
id: 'tom',
name: 'TOMs',
nameShort: 'TOMs',
package: 'dokumentation',
seq: 2200,
checkpointId: 'CP-TOM',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Dokumentation aller Technisch-Organisatorischen Massnahmen nach Art. 32 DSGVO.',
descriptionLong: 'Die Technisch-Organisatorischen Massnahmen (TOMs) dokumentieren alle Sicherheitsmassnahmen, die zum Schutz personenbezogener Daten implementiert sind. TOMs umfassen: Zutrittskontrolle, Zugangskontrolle, Zugriffskontrolle, Weitergabekontrolle, Eingabekontrolle, Auftragskontrolle, Verfuegbarkeitskontrolle und Trennungsgebot. Jede Massnahme wird mit Implementierungsstatus, Verantwortlichem und Pruefintervall dokumentiert. Die bestehenden Controls werden als Basis verwendet.',
legalBasis: 'Art. 32 DSGVO (Sicherheit der Verarbeitung)',
inputs: ['dsfa', 'controls', 'risks'],
outputs: ['toms'],
prerequisiteSteps: ['obligations'],
dbTables: ['compliance_controls'],
dbMode: 'read',
ragCollections: ['bp_compliance_datenschutz'],
ragPurpose: 'TOM-Massnahmenkataloge',
isOptional: false,
url: '/sdk/tom',
},
{
id: 'loeschfristen',
name: 'Loeschfristen',
nameShort: 'Loeschfristen',
package: 'dokumentation',
seq: 2300,
checkpointId: 'CP-RET',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Festlegung von Aufbewahrungs- und Loeschfristen fuer alle Datenkategorien.',
descriptionLong: 'Fuer jede Datenkategorie werden gesetzliche Aufbewahrungsfristen und Loeschzeitpunkte definiert. Dabei werden gesetzliche Mindestaufbewahrungsfristen (z.B. 10 Jahre Steuerrecht, 6 Jahre Handelsrecht, 3 Jahre Verjaehrung) mit dem Grundsatz der Datenminimierung abgewogen. Das Loeschkonzept definiert: Welche Daten, wann geloescht, wie geloescht (Anonymisierung vs. physische Loeschung) und wer verantwortlich ist.',
legalBasis: 'Art. 5 Abs. 1e DSGVO (Speicherbegrenzung), Art. 17 DSGVO (Recht auf Loeschung)',
inputs: ['vvt', 'dataMapping'],
outputs: ['retentionPolicies'],
prerequisiteSteps: ['tom'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_recht'],
ragPurpose: 'Aufbewahrungsfristen nach Gesetz',
isOptional: false,
url: '/sdk/loeschfristen',
},
{
id: 'vvt',
name: 'Verarbeitungsverzeichnis',
nameShort: 'VVT',
package: 'dokumentation',
seq: 2400,
checkpointId: 'CP-VVT',
checkpointType: 'REQUIRED',
checkpointReviewer: 'DSB',
description: 'Erstellung des Verzeichnisses aller Verarbeitungstaetigkeiten nach Art. 30 DSGVO.',
descriptionLong: 'Das VVT (Verzeichnis von Verarbeitungstaetigkeiten) ist eine gesetzliche Pflichtdokumentation nach Art. 30 DSGVO. Fuer jede Verarbeitungstaetigkeit wird dokumentiert: Zweck, Rechtsgrundlage, Kategorien betroffener Personen, Datenkategorien, Empfaenger, Drittlandtransfers, Loeschfristen und TOMs. Das VVT wird automatisch aus den vorherigen Schritten zusammengestellt (Module, TOMs, Data Mapping). Der DSB muss das VVT freigeben.',
legalBasis: 'Art. 30 DSGVO (Verzeichnis von Verarbeitungstaetigkeiten)',
inputs: ['modules', 'toms', 'dataMapping'],
outputs: ['vvt'],
prerequisiteSteps: ['loeschfristen'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_gesetze'],
ragPurpose: 'Art. 30 DSGVO Vorlage',
isOptional: false,
url: '/sdk/vvt',
},
// =========================================================================
// PAKET 4: RECHTLICHE TEXTE (seq 3000-3400)
// =========================================================================
{
id: 'einwilligungen',
name: 'Einwilligungen',
nameShort: 'Einwilligungen',
package: 'rechtliche-texte',
seq: 3000,
checkpointId: 'CP-CONS',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Definition aller erforderlichen Einwilligungserklaerungen fuer Datenverarbeitungen.',
descriptionLong: 'Basierend auf dem VVT und den aktivierten Modulen werden alle Verarbeitungen identifiziert, die eine Einwilligung erfordern (Art. 6 Abs. 1a DSGVO). Fuer jede Einwilligung wird ein rechtskonformer Text generiert, der: den Zweck klar benennt, freiwillig erteilt werden kann, informiert und spezifisch ist. Einwilligungen werden mit Widerrufsmechanismus, Versionierung und Nachweispflicht versehen.',
legalBasis: 'Art. 6 Abs. 1a, Art. 7 DSGVO (Einwilligung)',
inputs: ['vvt', 'modules'],
outputs: ['consents'],
prerequisiteSteps: ['vvt'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_datenschutz'],
ragPurpose: 'Einwilligungsvorlagen DSGVO',
isOptional: false,
url: '/sdk/einwilligungen',
},
{
id: 'consent',
name: 'Rechtliche Vorlagen',
nameShort: 'Vorlagen',
package: 'rechtliche-texte',
seq: 3100,
checkpointId: 'CP-DOC',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Generierung von Datenschutzerklaerung, AGB und Nutzungsbedingungen.',
descriptionLong: 'In diesem Schritt werden die zentralen rechtlichen Dokumente generiert: Datenschutzerklaerung (nach Art. 13/14 DSGVO), AGB, Nutzungsbedingungen und Informationspflichten. Die Dokumente werden aus dem Unternehmensprofil, VVT und den Einwilligungen automatisch zusammengestellt. Die RAG-Collection bp_legal_templates liefert branchenspezifische Vorlagen, die an die spezifischen Verarbeitungen des Unternehmens angepasst werden.',
legalBasis: 'Art. 13, 14 DSGVO (Informationspflichten)',
inputs: ['companyProfile', 'vvt', 'consents'],
outputs: ['documents'],
prerequisiteSteps: ['einwilligungen'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_legal_templates'],
ragPurpose: 'AGB, DSE, Nutzungsbedingungen Templates',
generates: ['Datenschutzerklaerung', 'AGB', 'Nutzungsbedingungen'],
isOptional: false,
url: '/sdk/consent',
},
{
id: 'cookie-banner',
name: 'Cookie Banner',
nameShort: 'Cookies',
package: 'rechtliche-texte',
seq: 3200,
checkpointId: 'CP-COOK',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Konfiguration eines rechtskonformen Cookie-Banners mit Consent-Management.',
descriptionLong: 'Der Cookie-Banner wird basierend auf den definierten Einwilligungen und dem Unternehmensprofil konfiguriert. Er unterscheidet zwischen technisch notwendigen Cookies (kein Consent noetig), funktionalen Cookies, Analyse-Cookies und Marketing-Cookies. Der Banner implementiert "Privacy by Default" — nur notwendige Cookies sind vorausgewaehlt. Die Konfiguration umfasst: Kategorien, Zweckbeschreibungen, Anbieter, Laufzeiten und Opt-in/Opt-out-Mechanismen.',
legalBasis: 'Art. 5 Abs. 3 ePrivacy-RL, TTDSG § 25',
inputs: ['consents', 'companyProfile'],
outputs: ['cookieBanner'],
prerequisiteSteps: ['consent'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_datenschutz'],
ragPurpose: 'Cookie-Consent Richtlinien',
isOptional: false,
url: '/sdk/cookie-banner',
},
{
id: 'document-generator',
name: 'Dokumentengenerator',
nameShort: 'Generator',
package: 'rechtliche-texte',
seq: 3300,
checkpointId: 'CP-DOCGEN',
checkpointType: 'RECOMMENDED',
checkpointReviewer: 'NONE',
description: 'Generierung weiterer rechtlicher Dokumente (Impressum, AVV, Auftragsverarbeitung).',
descriptionLong: 'Der Dokumentengenerator erstellt zusaetzliche rechtliche Dokumente, die ueber die Pflichtdokumente hinausgehen: Impressum (nach TMG/DDG), Auftragsverarbeitungsvertraege (AVV nach Art. 28 DSGVO), Vertraulichkeitsvereinbarungen, Betriebsvereinbarungen zum Datenschutz und Datenschutz-Folgenabschaetzungs-Berichte. Die Templates werden aus bp_legal_templates geladen und mit den unternehmensspezifischen Daten befuellt.',
legalBasis: 'Art. 28 DSGVO (Auftragsverarbeitung), DDG § 5 (Impressum)',
inputs: ['companyProfile', 'toms', 'vvt'],
outputs: ['generatedDocuments'],
prerequisiteSteps: ['cookie-banner'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_legal_templates'],
ragPurpose: 'Dokumenten-Templates',
generates: ['Impressum', 'Auftragsverarbeitungsvertrag'],
isOptional: true,
url: '/sdk/document-generator',
},
{
id: 'workflow',
name: 'Document Workflow',
nameShort: 'Workflow',
package: 'rechtliche-texte',
seq: 3400,
checkpointId: 'CP-WRKF',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Freigabe-Workflow fuer alle generierten rechtlichen Dokumente.',
descriptionLong: 'Der Document Workflow steuert den Freigabeprozess fuer alle generierten Dokumente. Jedes Dokument durchlaeuft definierte Phasen: Entwurf, Review, Freigabe, Veroeffentlichung. Je nach Dokumenttyp werden unterschiedliche Reviewer zugewiesen (DSB, Rechtsabteilung, Geschaeftsfuehrung). Der Workflow protokolliert alle Aenderungen, Kommentare und Freigaben fuer die Audit-Spur.',
inputs: ['documents', 'generatedDocuments'],
outputs: ['approvedDocuments'],
prerequisiteSteps: ['cookie-banner'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/workflow',
},
// =========================================================================
// PAKET 5: BETRIEB (seq 4000+)
// =========================================================================
{
id: 'dsr',
name: 'DSR Portal',
nameShort: 'DSR',
package: 'betrieb',
seq: 4000,
checkpointId: 'CP-DSR',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Einrichtung des Portals fuer Betroffenenrechte (Auskunft, Loeschung, Widerspruch).',
descriptionLong: 'Das DSR-Portal (Data Subject Rights) ermoeglicht es betroffenen Personen, ihre Rechte nach Art. 15-22 DSGVO auszuueben: Auskunftsrecht, Berichtigungsrecht, Loeschungsrecht ("Recht auf Vergessenwerden"), Einschraenkung der Verarbeitung, Datenportabilitaet und Widerspruchsrecht. Das Portal generiert automatisch Formulare, verwaltet Fristen (30 Tage) und dokumentiert die Bearbeitung jeder Anfrage fuer die Nachweispflicht.',
legalBasis: 'Art. 15-22 DSGVO (Betroffenenrechte)',
inputs: ['vvt', 'consents'],
outputs: ['dsrConfig'],
prerequisiteSteps: ['workflow'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_recht'],
ragPurpose: 'Art. 15-21 DSGVO Betroffenenrechte',
isOptional: false,
url: '/sdk/dsr',
},
{
id: 'escalations',
name: 'Escalations',
nameShort: 'Eskalationen',
package: 'betrieb',
seq: 4100,
checkpointId: 'CP-ESC',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Definition von Eskalationspfaden bei Compliance-Verstoessen und Datenpannen.',
descriptionLong: 'Das Eskalationsmanagement definiert klare Eskalationspfade fuer verschiedene Szenarien: Datenschutzverletzungen (Art. 33/34 DSGVO), Compliance-Verstoesse, Betroffenen-Beschwerden und Aufsichtsbehoerden-Anfragen. Fuer jedes Szenario werden Verantwortliche, Fristen (72h bei Datenpannen), Kommunikationswege und Massnahmen festgelegt. Die Eskalationspfade basieren auf der Risikomatrix und den definierten Controls.',
legalBasis: 'Art. 33, 34 DSGVO (Meldepflichten bei Datenpannen)',
inputs: ['risks', 'controls'],
outputs: ['escalationWorkflows'],
prerequisiteSteps: ['dsr'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/escalations',
},
{
id: 'vendor-compliance',
name: 'Vendor Compliance',
nameShort: 'Vendor',
package: 'betrieb',
seq: 4200,
checkpointId: 'CP-VEND',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Pruefung und Verwaltung aller Auftragsverarbeiter und Drittanbieter.',
descriptionLong: 'Vendor Compliance verwaltet alle externen Dienstleister, die im Auftrag personenbezogene Daten verarbeiten (Auftragsverarbeiter nach Art. 28 DSGVO). Fuer jeden Vendor wird geprueft: Gibt es einen AVV? Wo werden Daten gespeichert (EU/Drittland)? Welche TOMs hat der Vendor? Gibt es Subunternehmer? Die Pruefung umfasst auch regelmässige Re-Assessments und die Verwaltung von Standardvertragsklauseln (SCCs) fuer Drittlandtransfers.',
legalBasis: 'Art. 28 DSGVO (Auftragsverarbeiter), Art. 44-49 (Drittlandtransfer)',
inputs: ['modules', 'vvt'],
outputs: ['vendorAssessments'],
prerequisiteSteps: ['escalations'],
dbTables: [],
dbMode: 'none',
ragCollections: ['bp_compliance_recht'],
ragPurpose: 'AVV-Vorlagen und Pruefkataloge',
isOptional: false,
url: '/sdk/vendor-compliance',
},
{
id: 'consent-management',
name: 'Consent Verwaltung',
nameShort: 'Consent Mgmt',
package: 'betrieb',
seq: 4300,
checkpointId: 'CP-CMGMT',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Laufende Verwaltung aller erteilten und widerrufenen Einwilligungen.',
descriptionLong: 'Das Consent Management System verwaltet im laufenden Betrieb alle erteilten Einwilligungen: Wer hat wann welche Einwilligung erteilt? Wurde sie widerrufen? Welche Version der Einwilligungserklaerung wurde akzeptiert? Das System stellt sicher, dass Einwilligungen nachweisbar sind (Art. 7 Abs. 1 DSGVO), Widerrufe sofort wirksam werden und bei geaenderten Zwecken neue Einwilligungen eingeholt werden.',
legalBasis: 'Art. 7 DSGVO (Bedingungen fuer die Einwilligung)',
inputs: ['consents', 'documents'],
outputs: ['consentManagement'],
prerequisiteSteps: ['vendor-compliance'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/consent-management',
},
{
id: 'notfallplan',
name: 'Notfallplan & Breach Response',
nameShort: 'Notfallplan',
package: 'betrieb',
seq: 4400,
checkpointId: 'CP-NOTF',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Erstellung eines Notfallplans fuer Datenpannen und Sicherheitsvorfaelle.',
descriptionLong: 'Der Notfallplan definiert das Vorgehen bei Datenschutzverletzungen (Data Breaches). Er enthaelt: Sofortmassnahmen zur Schadensbegrenzung, Meldeprozess an die Aufsichtsbehoerde (innerhalb 72h nach Art. 33 DSGVO), Benachrichtigung betroffener Personen (Art. 34 DSGVO), Dokumentation des Vorfalls und Massnahmen zur Verhinderung kuenftiger Vorfaelle. Der Plan wird als PDF exportiert und allen relevanten Mitarbeitern zugaenglich gemacht.',
legalBasis: 'Art. 33, 34 DSGVO (Meldung von Datenpannen)',
inputs: ['risks', 'controls'],
outputs: ['incidentResponsePlan'],
prerequisiteSteps: ['consent-management'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
generates: ['Notfallplan (PDF)'],
isOptional: false,
url: '/sdk/notfallplan',
},
{
id: 'incidents',
name: 'Incident Management',
nameShort: 'Incidents',
package: 'betrieb',
seq: 4500,
checkpointId: 'CP-INC',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Verwaltung und Dokumentation von Datenschutz-Vorfaellen und Sicherheitsereignissen.',
descriptionLong: 'Das Incident Management System ermoeglicht die strukturierte Erfassung, Bewertung und Bearbeitung von Datenschutzvorfaellen. Jeder Vorfall wird klassifiziert (Schweregrad, betroffene Daten, Anzahl Betroffener), der Notfallplan wird aktiviert und alle Massnahmen werden protokolliert. Das System berechnet automatisch, ob eine Meldepflicht an die Aufsichtsbehoerde oder eine Benachrichtigung der Betroffenen erforderlich ist. Historische Vorfaelle werden im Incident Registry archiviert.',
legalBasis: 'Art. 33 DSGVO (Meldung an Aufsichtsbehoerde)',
inputs: ['incidentResponsePlan'],
outputs: ['incidentRegistry'],
prerequisiteSteps: ['notfallplan'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/incidents',
},
{
id: 'whistleblower',
name: 'Hinweisgebersystem',
nameShort: 'Whistleblower',
package: 'betrieb',
seq: 4600,
checkpointId: 'CP-WB',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Einrichtung eines anonymen Meldekanals nach HinSchG.',
descriptionLong: 'Das Hinweisgebersystem erfuellt die Anforderungen des Hinweisgeberschutzgesetzes (HinSchG). Es bietet Mitarbeitern und externen Personen einen sicheren, anonymen Kanal zur Meldung von Verstoessen gegen Compliance-Regeln, Datenschutzrecht oder andere Vorschriften. Das System schuetzt die Identitaet des Hinweisgebers, dokumentiert den Bearbeitungsprozess und stellt die Einhaltung der gesetzlichen Fristen (7 Tage Eingangsbestaetigung, 3 Monate Rueckmeldung) sicher.',
legalBasis: 'HinSchG (Hinweisgeberschutzgesetz)',
inputs: ['companyProfile'],
outputs: ['whistleblowerConfig'],
prerequisiteSteps: ['incidents'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/whistleblower',
},
{
id: 'academy',
name: 'Compliance Academy',
nameShort: 'Academy',
package: 'betrieb',
seq: 4700,
checkpointId: 'CP-ACAD',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Erstellung eines Schulungsplans fuer Mitarbeiter zu Datenschutz und Compliance.',
descriptionLong: 'Die Compliance Academy erstellt basierend auf dem Unternehmensprofil und den aktivierten Modulen einen massgeschneiderten Schulungsplan. Verschiedene Mitarbeitergruppen erhalten unterschiedliche Schulungsinhalte: Grundlagen-Datenschutz fuer alle, vertiefte DSGVO-Schulung fuer die IT, AI-Act-Schulung fuer KI-Entwickler, Fuehrungskraefte-Schulung fuer das Management. Der Plan definiert Schulungsintervalle, Pflicht- und Wahlmodule und Erfolgskontrolle.',
inputs: ['companyProfile', 'modules'],
outputs: ['trainingPlan'],
prerequisiteSteps: ['whistleblower'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/academy',
},
{
id: 'training',
name: 'Training Engine',
nameShort: 'Training',
package: 'betrieb',
seq: 4800,
checkpointId: 'CP-TRAIN',
checkpointType: 'REQUIRED',
checkpointReviewer: 'NONE',
description: 'Durchfuehrung und Tracking von Compliance-Schulungen mit Zertifikaten.',
descriptionLong: 'Die Training Engine setzt den Schulungsplan der Academy um. Sie bietet interaktive Schulungsmodule mit Quizzes, Fallbeispielen und Zertifikaten. Jede abgeschlossene Schulung wird dokumentiert (Teilnehmer, Datum, Ergebnis) und dient als Evidence fuer Audits. Die Engine ueberwacht Faelligkeiten, sendet Erinnerungen bei ausstehenden Pflichtschulungen und generiert Compliance-Reports ueber den Schulungsstand aller Mitarbeiter.',
inputs: ['trainingPlan', 'modules'],
outputs: ['trainingContent'],
prerequisiteSteps: ['academy'],
dbTables: [],
dbMode: 'none',
ragCollections: [],
isOptional: false,
url: '/sdk/training',
},
]
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/** Find which step produces a given SDKState property */
export function findProducerStep(property: string): SDKFlowStep | undefined {
return SDK_FLOW_STEPS.find(s => s.outputs.includes(property))
}
/** Get all unique DB tables used across all steps */
export function getAllDbTables(): string[] {
const tables = new Set<string>()
SDK_FLOW_STEPS.forEach(s => s.dbTables.forEach(t => tables.add(t)))
return Array.from(tables)
}
/** Get all unique RAG collections used across all steps */
export function getAllRagCollections(): string[] {
const collections = new Set<string>()
SDK_FLOW_STEPS.forEach(s => s.ragCollections.forEach(c => collections.add(c)))
return Array.from(collections)
}
/** Get steps that use a specific DB table */
export function getStepsUsingTable(table: string): SDKFlowStep[] {
return SDK_FLOW_STEPS.filter(s => s.dbTables.includes(table))
}
/** Get steps that use a specific RAG collection */
export function getStepsUsingRag(collection: string): SDKFlowStep[] {
return SDK_FLOW_STEPS.filter(s => s.ragCollections.includes(collection))
}

View File

@@ -0,0 +1,847 @@
'use client'
/**
* SDK Flow Visualization
*
* Interaktive React Flow Darstellung aller SDK-Steps:
* - Farblich nach Package gruppiert (5 Packages)
* - Prerequisite-Edges zeigen den sequenziellen Flow
* - DB-Tabellen und RAG-Collections als optionale Nodes
* - Detail-Panel bei Klick auf einen Step
*/
import { useCallback, useState, useMemo, useEffect } from 'react'
import ReactFlow, {
Node,
Edge,
Controls,
Background,
MiniMap,
useNodesState,
useEdgesState,
BackgroundVariant,
MarkerType,
Panel,
} from 'reactflow'
import 'reactflow/dist/style.css'
import {
SDK_FLOW_STEPS,
FLOW_PACKAGES,
findProducerStep,
getAllDbTables,
getAllRagCollections,
type SDKFlowStep,
} from './flow-data'
// =============================================================================
// TYPES
// =============================================================================
type PackageFilter = 'alle' | SDKFlowStep['package']
// =============================================================================
// LAYOUT
// =============================================================================
const PACKAGE_ORDER: SDKFlowStep['package'][] = [
'vorbereitung',
'analyse',
'dokumentation',
'rechtliche-texte',
'betrieb',
]
const PACKAGE_X_OFFSET: Record<string, number> = {
vorbereitung: 0,
analyse: 320,
dokumentation: 640,
'rechtliche-texte': 960,
betrieb: 1280,
}
const NODE_WIDTH = 200
const NODE_HEIGHT = 70
const STEP_Y_SPACING = 100
const STEP_Y_START = 100
function getStepPosition(step: SDKFlowStep): { x: number; y: number } {
const packageSteps = SDK_FLOW_STEPS
.filter(s => s.package === step.package)
.sort((a, b) => a.seq - b.seq)
const idx = packageSteps.findIndex(s => s.id === step.id)
return {
x: PACKAGE_X_OFFSET[step.package] + 40,
y: STEP_Y_START + idx * STEP_Y_SPACING,
}
}
// =============================================================================
// DETAIL PANEL
// =============================================================================
function DetailPanel({
step,
onClose,
}: {
step: SDKFlowStep
onClose: () => void
}) {
const pkg = FLOW_PACKAGES[step.package]
const baseUrl = 'https://macmini:3007'
return (
<div className="w-80 bg-white border-l border-slate-200 overflow-y-auto">
<div className="sticky top-0 bg-white z-10 border-b border-slate-200">
<div className="flex items-center justify-between p-4">
<h3 className="font-bold text-slate-900">{step.name}</h3>
<button
onClick={onClose}
className="text-slate-400 hover:text-slate-600 text-lg leading-none"
>
x
</button>
</div>
<div className="px-4 pb-3 flex items-center gap-2">
<span
className="px-2 py-0.5 rounded text-xs font-medium"
style={{ background: pkg.color.bg, color: pkg.color.text }}
>
{pkg.icon} {pkg.name}
</span>
<span className="text-xs text-slate-400">seq {step.seq}</span>
{step.isOptional && (
<span className="px-2 py-0.5 rounded text-xs bg-slate-100 text-slate-500">
Optional
</span>
)}
</div>
</div>
<div className="p-4 space-y-4">
{/* Beschreibung */}
<div>
<p className="text-sm text-slate-700 leading-relaxed">{step.description}</p>
<p className="text-xs text-slate-500 leading-relaxed mt-2">{step.descriptionLong}</p>
{step.legalBasis && (
<div className="mt-2 px-2 py-1.5 bg-blue-50 rounded text-xs text-blue-700">
<span className="font-medium">Rechtsgrundlage:</span> {step.legalBasis}
</div>
)}
</div>
{/* Inputs */}
{step.inputs.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-slate-500 uppercase mb-1.5">
Inputs (SDKState)
</h4>
<div className="space-y-1">
{step.inputs.map(input => {
const producer = findProducerStep(input)
return (
<div
key={input}
className="flex items-center justify-between text-sm bg-blue-50 rounded px-2 py-1"
>
<code className="text-blue-700 text-xs">{input}</code>
{producer && (
<span className="text-xs text-slate-400">
{producer.nameShort}
</span>
)}
</div>
)
})}
</div>
</div>
)}
{/* Outputs */}
{step.outputs.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-slate-500 uppercase mb-1.5">
Outputs (SDKState)
</h4>
<div className="space-y-1">
{step.outputs.map(output => (
<div
key={output}
className="text-sm bg-emerald-50 rounded px-2 py-1"
>
<code className="text-emerald-700 text-xs">{output}</code>
</div>
))}
</div>
</div>
)}
{/* DB Tables */}
{step.dbTables.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-slate-500 uppercase mb-1.5">
DB-Tabellen
</h4>
<div className="space-y-1">
{step.dbTables.map(table => (
<div
key={table}
className="flex items-center justify-between text-sm bg-slate-100 rounded px-2 py-1"
>
<code className="text-slate-700 text-xs">{table}</code>
<span className="text-xs text-slate-400">{step.dbMode}</span>
</div>
))}
</div>
</div>
)}
{/* RAG Collections */}
{step.ragCollections.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-slate-500 uppercase mb-1.5">
RAG-Collections
</h4>
<div className="space-y-1">
{step.ragCollections.map(rag => (
<div
key={rag}
className="text-sm bg-green-50 rounded px-2 py-1"
>
<code className="text-green-700 text-xs">{rag}</code>
</div>
))}
{step.ragPurpose && (
<p className="text-xs text-slate-500 mt-1">{step.ragPurpose}</p>
)}
</div>
</div>
)}
{/* Checkpoint */}
{step.checkpointId && (
<div>
<h4 className="text-xs font-semibold text-slate-500 uppercase mb-1.5">
Checkpoint
</h4>
<div className="bg-amber-50 rounded p-2 space-y-1">
<div className="flex items-center gap-2">
<code className="text-xs font-bold text-amber-800">
{step.checkpointId}
</code>
<span
className={`px-1.5 py-0.5 rounded text-xs font-medium ${
step.checkpointType === 'REQUIRED'
? 'bg-red-100 text-red-700'
: 'bg-yellow-100 text-yellow-700'
}`}
>
{step.checkpointType}
</span>
</div>
{step.checkpointReviewer && step.checkpointReviewer !== 'NONE' && (
<div className="text-xs text-slate-600">
Reviewer: <span className="font-medium">{step.checkpointReviewer}</span>
</div>
)}
</div>
</div>
)}
{/* Generated Docs */}
{step.generates && step.generates.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-slate-500 uppercase mb-1.5">
Generierte Dokumente
</h4>
<div className="space-y-1">
{step.generates.map(doc => (
<div
key={doc}
className="text-sm bg-violet-50 rounded px-2 py-1 text-violet-700"
>
{doc}
</div>
))}
</div>
</div>
)}
{/* Prerequisites */}
{step.prerequisiteSteps.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-slate-500 uppercase mb-1.5">
Voraussetzungen
</h4>
<div className="space-y-1">
{step.prerequisiteSteps.map(preId => {
const preStep = SDK_FLOW_STEPS.find(s => s.id === preId)
return (
<div
key={preId}
className="text-sm text-slate-600 bg-slate-50 rounded px-2 py-1"
>
{preStep?.name || preId}
</div>
)
})}
</div>
</div>
)}
{/* Open in SDK */}
<button
onClick={() => window.open(`${baseUrl}${step.url}`, '_blank')}
className="w-full mt-2 px-4 py-2 bg-purple-600 text-white rounded-lg text-sm font-medium hover:bg-purple-700 transition-colors"
>
Im SDK oeffnen
</button>
</div>
</div>
)
}
// =============================================================================
// MAIN COMPONENT
// =============================================================================
export default function SDKFlowPage() {
const [selectedStep, setSelectedStep] = useState<SDKFlowStep | null>(null)
const [packageFilter, setPackageFilter] = useState<PackageFilter>('alle')
const [showDb, setShowDb] = useState(false)
const [showRag, setShowRag] = useState(false)
const allDbTables = useMemo(() => getAllDbTables(), [])
const allRagCollections = useMemo(() => getAllRagCollections(), [])
// =========================================================================
// Build Nodes
// =========================================================================
const { nodes: initialNodes, edges: initialEdges } = useMemo(() => {
const nodes: Node[] = []
const edges: Edge[] = []
const visibleSteps =
packageFilter === 'alle'
? SDK_FLOW_STEPS
: SDK_FLOW_STEPS.filter(s => s.package === packageFilter)
const visibleStepIds = new Set(visibleSteps.map(s => s.id))
// Step Nodes
visibleSteps.forEach(step => {
const pkg = FLOW_PACKAGES[step.package]
const pos = getStepPosition(step)
const isSelected = selectedStep?.id === step.id
// Checkpoint badge text
let badge = ''
if (step.checkpointId) {
badge = step.checkpointType === 'REQUIRED' ? ' *' : ' ~'
if (step.checkpointReviewer && step.checkpointReviewer !== 'NONE') {
badge += ` [${step.checkpointReviewer}]`
}
}
nodes.push({
id: step.id,
type: 'default',
position: pos,
data: {
label: (
<div className="text-center px-1">
<div className="font-medium text-xs leading-tight">
{step.nameShort}
</div>
<div className="text-[10px] opacity-70 mt-0.5">
{step.checkpointId || ''}
{badge}
</div>
</div>
),
},
style: {
background: isSelected ? pkg.color.border : pkg.color.bg,
color: isSelected ? 'white' : pkg.color.text,
border: `2px solid ${pkg.color.border}`,
borderRadius: '10px',
padding: '8px 4px',
minWidth: `${NODE_WIDTH}px`,
maxWidth: `${NODE_WIDTH}px`,
cursor: 'pointer',
boxShadow: isSelected
? `0 0 16px ${pkg.color.border}`
: '0 1px 3px rgba(0,0,0,0.08)',
opacity: step.isOptional ? 0.85 : 1,
borderStyle: step.isOptional ? 'dashed' : 'solid',
},
})
})
// Prerequisite Edges
visibleSteps.forEach(step => {
step.prerequisiteSteps.forEach(preId => {
if (visibleStepIds.has(preId)) {
edges.push({
id: `e-${preId}-${step.id}`,
source: preId,
target: step.id,
type: 'smoothstep',
animated: selectedStep?.id === preId || selectedStep?.id === step.id,
style: {
stroke:
selectedStep?.id === preId || selectedStep?.id === step.id
? '#7c3aed'
: '#94a3b8',
strokeWidth:
selectedStep?.id === preId || selectedStep?.id === step.id
? 2.5
: 1.5,
},
markerEnd: {
type: MarkerType.ArrowClosed,
color:
selectedStep?.id === preId || selectedStep?.id === step.id
? '#7c3aed'
: '#94a3b8',
width: 14,
height: 14,
},
})
}
})
})
// DB Table Nodes
if (showDb) {
const dbTablesInUse = new Set<string>()
visibleSteps.forEach(s => s.dbTables.forEach(t => dbTablesInUse.add(t)))
let dbIdx = 0
dbTablesInUse.forEach(table => {
const nodeId = `db-${table}`
nodes.push({
id: nodeId,
type: 'default',
position: { x: -280, y: STEP_Y_START + dbIdx * 90 },
data: {
label: (
<div className="text-center">
<div className="text-sm mb-0.5">DB</div>
<div className="font-medium text-[10px] leading-tight">{table}</div>
</div>
),
},
style: {
background: '#f1f5f9',
color: '#475569',
border: '2px solid #94a3b8',
borderRadius: '50%',
width: '90px',
height: '90px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '4px',
},
})
// Connect steps to this DB table
visibleSteps
.filter(s => s.dbTables.includes(table))
.forEach(step => {
edges.push({
id: `e-db-${table}-${step.id}`,
source: nodeId,
target: step.id,
type: 'straight',
style: {
stroke: '#94a3b8',
strokeWidth: 1,
strokeDasharray: '6 3',
},
labelStyle: { fontSize: 9, fill: '#94a3b8' },
label: step.dbMode !== 'none' ? step.dbMode : undefined,
})
})
dbIdx++
})
}
// RAG Collection Nodes
if (showRag) {
const ragInUse = new Set<string>()
visibleSteps.forEach(s => s.ragCollections.forEach(r => ragInUse.add(r)))
let ragIdx = 0
ragInUse.forEach(collection => {
const nodeId = `rag-${collection}`
// Place RAG nodes to the right of all packages
const rightX = (packageFilter === 'alle' ? 1280 : PACKAGE_X_OFFSET[packageFilter] || 0) + NODE_WIDTH + 180
nodes.push({
id: nodeId,
type: 'default',
position: { x: rightX, y: STEP_Y_START + ragIdx * 90 },
data: {
label: (
<div className="text-center">
<div className="text-sm mb-0.5">RAG</div>
<div className="font-medium text-[10px] leading-tight">
{collection.replace('bp_', '')}
</div>
</div>
),
},
style: {
background: '#dcfce7',
color: '#166534',
border: '2px solid #22c55e',
borderRadius: '50%',
width: '90px',
height: '90px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '4px',
},
})
// Connect steps to this RAG collection
visibleSteps
.filter(s => s.ragCollections.includes(collection))
.forEach(step => {
edges.push({
id: `e-rag-${collection}-${step.id}`,
source: nodeId,
target: step.id,
type: 'straight',
style: {
stroke: '#22c55e',
strokeWidth: 1,
strokeDasharray: '6 3',
},
})
})
ragIdx++
})
}
return { nodes, edges }
}, [packageFilter, showDb, showRag, selectedStep])
// =========================================================================
// React Flow State
// =========================================================================
const [nodes, setNodes, onNodesChange] = useNodesState([])
const [edges, setEdges, onEdgesChange] = useEdgesState([])
useEffect(() => {
setNodes(initialNodes)
setEdges(initialEdges)
}, [initialNodes, initialEdges, setNodes, setEdges])
const onNodeClick = useCallback((_event: React.MouseEvent, node: Node) => {
const step = SDK_FLOW_STEPS.find(s => s.id === node.id)
if (step) {
setSelectedStep(prev => (prev?.id === step.id ? null : step))
}
}, [])
const onPaneClick = useCallback(() => {
setSelectedStep(null)
}, [])
// =========================================================================
// Stats
// =========================================================================
const stats = useMemo(() => {
const visible =
packageFilter === 'alle'
? SDK_FLOW_STEPS
: SDK_FLOW_STEPS.filter(s => s.package === packageFilter)
return {
total: SDK_FLOW_STEPS.length,
visible: visible.length,
checkpoints: visible.filter(s => s.checkpointId).length,
dbTables: allDbTables.length,
ragCollections: allRagCollections.length,
}
}, [packageFilter, allDbTables, allRagCollections])
// =========================================================================
// Render
// =========================================================================
return (
<div className="space-y-4">
{/* Header */}
<div className="bg-white rounded-xl border border-slate-200 p-5 shadow-sm">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl bg-purple-600 flex items-center justify-center text-xl text-white">
SDK
</div>
<div>
<h2 className="text-xl font-bold text-slate-900">
SDK Flow Visualization
</h2>
<p className="text-sm text-slate-500">
{stats.total} Steps in 5 Packages | {stats.checkpoints} Checkpoints |{' '}
{stats.dbTables} DB-Tabellen | {stats.ragCollections} RAG-Collections
</p>
</div>
</div>
</div>
{/* Toolbar */}
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="flex flex-wrap items-center gap-2">
{/* Package Filter */}
<button
onClick={() => {
setPackageFilter('alle')
setSelectedStep(null)
}}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
packageFilter === 'alle'
? 'bg-slate-800 text-white'
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
}`}
>
Alle ({SDK_FLOW_STEPS.length})
</button>
{PACKAGE_ORDER.map(pkgId => {
const pkg = FLOW_PACKAGES[pkgId]
const count = SDK_FLOW_STEPS.filter(s => s.package === pkgId).length
return (
<button
key={pkgId}
onClick={() => {
setPackageFilter(packageFilter === pkgId ? 'alle' : pkgId)
setSelectedStep(null)
}}
className="px-3 py-1.5 rounded-lg text-sm font-medium transition-all flex items-center gap-1.5"
style={{
background:
packageFilter === pkgId ? pkg.color.border : pkg.color.bg,
color: packageFilter === pkgId ? 'white' : pkg.color.text,
}}
>
<span
className="w-2.5 h-2.5 rounded-full"
style={{ background: pkg.color.border }}
/>
{pkg.nameShort} ({count})
</button>
)
})}
{/* Separator */}
<div className="w-px h-6 bg-slate-200 mx-1" />
{/* Toggles */}
<button
onClick={() => setShowDb(!showDb)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
showDb
? 'bg-slate-700 text-white'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
DB-Tabellen
</button>
<button
onClick={() => setShowRag(!showRag)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
showRag
? 'bg-green-600 text-white'
: 'bg-green-50 text-green-700 hover:bg-green-100'
}`}
>
RAG-Collections
</button>
</div>
</div>
{/* Flow Canvas + Detail Panel */}
<div className="flex bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden" style={{ height: '700px' }}>
{/* Canvas */}
<div className="flex-1 relative">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick}
onPaneClick={onPaneClick}
fitView
fitViewOptions={{ padding: 0.15 }}
attributionPosition="bottom-left"
>
<Controls />
<MiniMap
nodeColor={node => {
if (node.id.startsWith('db-')) return '#94a3b8'
if (node.id.startsWith('rag-')) return '#22c55e'
const step = SDK_FLOW_STEPS.find(s => s.id === node.id)
return step ? FLOW_PACKAGES[step.package].color.border : '#94a3b8'
}}
maskColor="rgba(0,0,0,0.08)"
/>
<Background variant={BackgroundVariant.Dots} gap={16} size={1} />
{/* Legende */}
<Panel
position="bottom-right"
className="bg-white/95 p-3 rounded-lg shadow-lg text-xs"
>
<div className="font-medium text-slate-700 mb-2">Legende</div>
<div className="space-y-1">
{PACKAGE_ORDER.map(pkgId => {
const pkg = FLOW_PACKAGES[pkgId]
return (
<div key={pkgId} className="flex items-center gap-2">
<span
className="w-3 h-3 rounded"
style={{
background: pkg.color.bg,
border: `1px solid ${pkg.color.border}`,
}}
/>
<span className="text-slate-600">{pkg.name}</span>
</div>
)
})}
<div className="border-t border-slate-200 my-1.5 pt-1.5">
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-slate-200 border border-slate-400" />
<span className="text-slate-500">DB-Tabelle</span>
</div>
<div className="flex items-center gap-2 mt-0.5">
<span className="w-3 h-3 rounded-full bg-green-100 border border-green-500" />
<span className="text-slate-500">RAG-Collection</span>
</div>
</div>
<div className="border-t border-slate-200 my-1.5 pt-1.5 text-slate-400">
<div>* = REQUIRED</div>
<div>~ = RECOMMENDED</div>
<div>--- = gestrichelte Border: Optional</div>
</div>
</div>
</Panel>
{/* Package Headers */}
{packageFilter === 'alle' && (
<Panel position="top-center" className="flex gap-2 pointer-events-none">
{PACKAGE_ORDER.map((pkgId) => {
const pkg = FLOW_PACKAGES[pkgId]
return (
<div
key={pkgId}
className="px-3 py-1 rounded-lg text-xs font-medium opacity-60"
style={{
background: pkg.color.bg,
color: pkg.color.text,
border: `1px solid ${pkg.color.border}`,
minWidth: '200px',
textAlign: 'center',
}}
>
{pkg.icon} {pkg.name}
</div>
)
})}
</Panel>
)}
</ReactFlow>
</div>
{/* Detail Panel */}
{selectedStep && (
<DetailPanel
step={selectedStep}
onClose={() => setSelectedStep(null)}
/>
)}
</div>
{/* Step Table (below flow) */}
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden">
<div className="px-4 py-3 bg-slate-50 border-b">
<h3 className="font-medium text-slate-700">
Alle Steps ({stats.visible}
{packageFilter !== 'alle' ? ` / ${stats.total}` : ''})
</h3>
</div>
<div className="divide-y max-h-96 overflow-y-auto">
{SDK_FLOW_STEPS.filter(
s => packageFilter === 'alle' || s.package === packageFilter
).map(step => {
const pkg = FLOW_PACKAGES[step.package]
return (
<button
key={step.id}
onClick={() => setSelectedStep(step)}
className={`w-full flex items-center gap-3 p-3 text-left transition-colors ${
selectedStep?.id === step.id
? 'bg-purple-50'
: 'hover:bg-slate-50'
}`}
>
<span
className="w-8 h-8 rounded-lg flex items-center justify-center text-xs font-bold shrink-0"
style={{ background: pkg.color.bg, color: pkg.color.text }}
>
{step.seq}
</span>
<div className="flex-1 min-w-0">
<div className="font-medium text-slate-800 text-sm">
{step.name}
</div>
<div className="text-xs text-slate-500 flex items-center gap-2 mt-0.5">
{step.checkpointId && (
<span
className={`px-1 py-0 rounded text-[10px] font-medium ${
step.checkpointType === 'REQUIRED'
? 'bg-red-100 text-red-600'
: 'bg-yellow-100 text-yellow-600'
}`}
>
{step.checkpointId}
</span>
)}
{step.dbTables.length > 0 && (
<span className="text-slate-400">
DB: {step.dbTables.join(', ')}
</span>
)}
{step.ragCollections.length > 0 && (
<span className="text-green-600">
RAG: {step.ragCollections.length}
</span>
)}
{step.isOptional && (
<span className="text-slate-400 italic">optional</span>
)}
</div>
</div>
<span
className="px-2 py-0.5 rounded text-[10px] font-medium shrink-0"
style={{ background: pkg.color.bg, color: pkg.color.text }}
>
{pkg.nameShort}
</span>
</button>
)
})}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,111 @@
/**
* Tests for the shared queryRAG utility.
*/
// Mock fetch globally before importing
const mockFetch = jest.fn()
global.fetch = mockFetch
// Reset modules to pick up our mock
jest.resetModules()
describe('queryRAG', () => {
let queryRAG: (query: string, topK?: number) => Promise<string>
beforeEach(async () => {
jest.resetModules()
mockFetch.mockReset()
// Dynamic import to pick up fresh env
const mod = await import('../rag-query')
queryRAG = mod.queryRAG
})
it('should return formatted results on success', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
results: [
{ source_name: 'DSGVO', content: 'Art. 35 regelt die DSFA...' },
{ source_code: 'EU_2016_679', content: 'Risikobewertung erforderlich' },
],
}),
})
const result = await queryRAG('DSFA Art. 35')
expect(result).toContain('[Quelle 1: DSGVO]')
expect(result).toContain('Art. 35 regelt die DSFA...')
expect(result).toContain('[Quelle 2: EU_2016_679]')
expect(mockFetch).toHaveBeenCalledTimes(1)
})
it('should return empty string on HTTP error', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 500,
})
const result = await queryRAG('test query')
expect(result).toBe('')
})
it('should return empty string on network error', async () => {
mockFetch.mockRejectedValueOnce(new Error('Connection refused'))
const result = await queryRAG('test query')
expect(result).toBe('')
})
it('should return empty string when no results', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ results: [] }),
})
const result = await queryRAG('test query')
expect(result).toBe('')
})
it('should pass topK parameter in URL', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ results: [] }),
})
await queryRAG('test query', 7)
const calledUrl = mockFetch.mock.calls[0][0] as string
expect(calledUrl).toContain('top_k=7')
})
it('should use default topK of 3', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ results: [] }),
})
await queryRAG('test query')
const calledUrl = mockFetch.mock.calls[0][0] as string
expect(calledUrl).toContain('top_k=3')
})
it('should handle results with missing fields gracefully', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
results: [
{ content: 'Some content without source' },
],
}),
})
const result = await queryRAG('test')
expect(result).toContain('[Quelle 1: Unbekannt]')
expect(result).toContain('Some content without source')
})
})

View File

@@ -0,0 +1,111 @@
"""Tests for the include_legal_context parameter on GET /requirements/{id}."""
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from compliance.services.rag_client import RAGSearchResult
class TestRequirementLegalContext:
"""Tests for the ?include_legal_context=true query parameter."""
def _make_mock_requirement(self):
req = MagicMock()
req.id = "req-001"
req.regulation_id = "reg-001"
req.article = "Art. 35"
req.paragraph = "Abs. 1"
req.title = "Datenschutz-Folgenabschaetzung"
req.description = "Beschreibung"
req.requirement_text = "Text"
req.breakpilot_interpretation = None
req.implementation_status = "not_started"
req.implementation_details = None
req.code_references = None
req.documentation_links = None
req.evidence_description = None
req.evidence_artifacts = None
req.auditor_notes = None
req.audit_status = "pending"
req.last_audit_date = None
req.last_auditor = None
req.is_applicable = True
req.applicability_reason = None
req.priority = 3
req.source_page = None
req.source_section = None
return req
def _make_mock_regulation(self):
reg = MagicMock()
reg.code = "DSGVO"
return reg
@pytest.mark.asyncio
async def test_legal_context_returns_results(self):
"""When include_legal_context=true, response should contain legal_context array."""
mock_results = [
RAGSearchResult(
text="Art. 35 regelt die DSFA...",
regulation_code="eu_2016_679",
regulation_name="DSGVO",
regulation_short="DSGVO",
category="regulation",
article="Art. 35",
paragraph="",
source_url="https://example.com",
score=0.92,
)
]
with patch("compliance.services.rag_client.get_rag_client") as mock_get_client:
client = AsyncMock()
client.search.return_value = mock_results
mock_get_client.return_value = client
# Simulate what the route handler does
rag = mock_get_client()
results = await rag.search("Datenschutz-Folgenabschaetzung Art. 35", collection="bp_compliance_ce", top_k=3)
legal_context = [
{
"text": r.text,
"regulation_code": r.regulation_code,
"regulation_short": r.regulation_short,
"article": r.article,
"score": r.score,
"source_url": r.source_url,
}
for r in results
]
assert len(legal_context) == 1
assert legal_context[0]["regulation_code"] == "eu_2016_679"
assert legal_context[0]["article"] == "Art. 35"
assert legal_context[0]["score"] == 0.92
@pytest.mark.asyncio
async def test_legal_context_error_returns_empty(self):
"""When RAG search fails, legal_context should be empty array."""
with patch("compliance.services.rag_client.get_rag_client") as mock_get_client:
client = AsyncMock()
client.search.side_effect = Exception("Connection refused")
mock_get_client.return_value = client
rag = mock_get_client()
try:
await rag.search("test", collection="bp_compliance_ce", top_k=3)
legal_context = [] # Should not reach here
except Exception:
legal_context = []
assert legal_context == []
def test_legal_context_not_included_by_default(self):
"""When include_legal_context is not set, no legal_context key should be present."""
result = {
"id": "req-001",
"title": "Test",
}
# Default behavior: no legal_context key
assert "legal_context" not in result

View File

@@ -144,53 +144,88 @@ Das Eskalationssystem routet kritische Assessments zur menschlichen Prüfung.
└─────────────────────────────────────────────────────────────────┘
```
### 3.3 Legal RAG (`internal/llm/legal_rag.go`)
### 3.3 Legal RAG (`internal/ucca/legal_rag.go`)
Semantische Suche in 19 EU-Regulierungen für kontextbasierte Erklärungen.
Semantische Suche in 19 EU-Regulierungen fuer kontextbasierte Erklaerungen.
Unterstuetzt Multi-Collection-Suche fuer unterschiedliche Rechtsgebiete.
```
┌─────────────────────────────────────────────────────────────────┐
│ Legal RAG System │
├─────────────────────────────────────────────────────────────────┤
│ │
Explain Request ──────────────────────────────────────────────>
Search Request (Query + Collection) ────────────────────────── │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Qdrant Vector DB │ │
│ │ Collection: bp_legal_corpus │ │
│ │ 2,274 Chunks, 1024-dim BGE-M3 │ │
│ │ 6 Collections, 1024-dim BGE-M3 │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ EU-Verordnungen: │ │
│ │ ├── DSGVO (128) ├── AI Act (96) ├── NIS2 (128) │ │
│ │ ── CRA (256) ├── Data Act (256) ├── DSA (256) │ │
│ │ ├── DGA (32) ├── EUCSA (32) ├── DPF (714) │ │
│ │ └── ... │ │
│ │ bp_compliance_ce (Default) │ │
│ │ ├── DSGVO, AI Act, NIS2, CRA, Data Act, DSA, DPF, ... │ │
│ │ ── EU-Verordnungen & CE-Regulierungen │ │
│ │ │ │
│ │ Deutsche Gesetze: │ │
│ │ ├── TDDDG (1) ├── SCC (32) ├── ... │ │
│ │ bp_compliance_recht │ │
│ │ ├── BDSG, TDDDG, DDG, UrhG, TMG, TKG │ │
│ │ └── Deutsche Gesetze │ │
│ │ │ │
│ │ BSI-Standards: │ │
│ │ ├── TR-03161-1 (6) ├── TR-03161-2 (6) ├── TR-03161-3 │ │
│ │ bp_compliance_gesetze — Regulierungstexte (Modul-Matching) │ │
│ │ bp_compliance_datenschutz — DSGVO Datenschutzmassnahmen │ │
│ │ bp_dsfa_corpus — DSFA Templates & Bewertungskriterien │ │
│ │ bp_legal_templates — Rechtsdokument-Vorlagen │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Hybrid Search (Dense + Sparse)
│ │ Re-Ranking (Cross-Encoder) │
│ │ Dense Search (BGE-M3 Embedding)
│ ▼ │
│ Top-K Relevant Passages ─────────────────────────────────────> │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ LLM Explanation │ │
│ │ Provider: Ollama (local) / Anthropic (fallback) │ │
│ │ Prompt: Assessment + Legal Context → Erklärung │ │
│ │ Consumer Modules │ │
│ │ ├── UCCA Explain (LLM-Erklaerung fuer Assessments) │ │
│ │ ├── Requirements AI (interpret + suggest_controls) │ │
│ │ ├── DSFA Drafting v1/v2 (Rechtskontext im Draft) │ │
│ │ └── Requirements API (?include_legal_context=true) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
#### 3.3.1 Multi-Collection Search API
```
POST /sdk/v1/rag/search
{
"query": "DSGVO Art. 35 Datenschutz-Folgenabschaetzung",
"collection": "bp_compliance_recht", // optional, default: bp_compliance_ce
"regulations": ["eu_2016_679"], // optional Filter
"top_k": 5 // optional, default: 5, max: 20
}
Erlaubte Collections (Whitelist):
bp_compliance_ce, bp_compliance_recht, bp_compliance_gesetze,
bp_compliance_datenschutz, bp_dsfa_corpus, bp_legal_templates
```
#### 3.3.2 Collection-Routing (Requirements AI)
Das Python-Backend waehlt automatisch die passende Collection:
| Regulation | Collection |
|------------|-----------|
| DSGVO, GDPR, AI_ACT, NIS2, CRA | `bp_compliance_ce` |
| BDSG, TDDDG, DDG, UrhG, TMG, TKG | `bp_compliance_recht` |
| Unbekannt / Fallback | `bp_compliance_ce` |
#### 3.3.3 Fehlertoleranz
RAG-Ausfaelle brechen **nie** die Hauptfunktion:
- Go SDK: `SearchCollection()` gibt leere Liste bei Fehler
- Python: `ComplianceRAGClient.search()` gibt `[]` zurueck, wirft nie
- TypeScript: `queryRAG()` gibt `''` zurueck, wirft nie
---
## 4. Datenfluss
@@ -943,5 +978,5 @@ policies/obligations/
---
*Dokumentation erstellt: 2026-01-29*
*Version: 2.1.0*
*Dokumentation erstellt: 2026-01-29, aktualisiert: 2026-03-02*
*Version: 2.2.0 (Phase 2: Multi-Collection RAG)*

View File

@@ -273,36 +273,112 @@ if facts.Publisher == "DIN_MEDIA" && facts.AIUsePermitted != "YES" {
## 5. Legal RAG Integration
### Übersicht
### Uebersicht
Das Legal RAG System (`internal/ucca/legal_rag.go`) generiert Erklärungen mit rechtlichem Kontext.
Das Legal RAG System (`internal/ucca/legal_rag.go`) stellt semantische Suche in Qdrant-Collections bereit. Es wird von mehreren Modulen konsumiert:
### Verwendung
- **UCCA Explain**: LLM-Erklaerungen fuer Assessments
- **Requirements AI** (Python): `interpret_requirement()` und `suggest_controls()`
- **DSFA Drafting** (TypeScript): v1 + v2 Draft-Pipelines
- **Requirements API** (Python): `?include_legal_context=true` Endpunkt
### Go SDK — SearchCollection
```go
import "ai-compliance-sdk/internal/ucca"
rag := ucca.NewLegalRAGService(qdrantClient, llmClient, "bp_legal_corpus")
client := ucca.NewLegalRAGClient()
// Erklärung generieren
explanation, err := rag.Explain(ctx, result, intake)
if err != nil {
log.Error(err)
}
// Standard-Suche (bp_compliance_ce)
results, err := client.Search(ctx, "DSGVO Art. 35", nil, 5)
fmt.Println("Erklärung:", explanation.Text)
fmt.Println("Rechtsquellen:", explanation.Sources)
// Suche in spezifischer Collection
results, err := client.SearchCollection(ctx, "bp_compliance_recht", "BDSG §26", nil, 3)
// Leere Collection → Fallback auf Default (bp_compliance_ce)
results, err := client.SearchCollection(ctx, "", "DSGVO Art. 35", nil, 5)
```
### Rechtsquellen im RAG
### Python RAG Client (Proxy)
| Quelle | Chunks | Beschreibung |
|--------|--------|--------------|
| DSGVO | 128 | EU Datenschutz-Grundverordnung |
| AI Act | 96 | EU AI-Verordnung |
| NIS2 | 128 | Netzwerk-Informationssicherheit |
| SCC | 32 | Standardvertragsklauseln |
| DPF | 714 | Data Privacy Framework |
```python
from compliance.services.rag_client import get_rag_client
rag = get_rag_client()
# Async-Suche via Go SDK
results = await rag.search(
"DSGVO Art. 35 Risikobewertung",
collection="bp_compliance_recht",
top_k=3
)
# Fuer LLM-Prompt formatieren
context_str = rag.format_for_prompt(results)
# → "## Relevanter Rechtskontext\n1. **DSGVO** (eu_2016_679) — Art. 35\n..."
```
### TypeScript Shared Utility (Drafting Engine)
```typescript
import { queryRAG } from '@/lib/sdk/drafting-engine/rag-query'
// Sucht via klausur-service DSFA-RAG
const ragContext = await queryRAG('DSFA Art. 35 DSGVO', 3)
// → "[Quelle 1: DSGVO]\nArt. 35 regelt die DSFA..."
```
### RAG Search API
```bash
# Suche in spezifischer Collection
curl -X POST http://localhost:8090/sdk/v1/rag/search \
-H "Content-Type: application/json" \
-d '{
"query": "Datenschutz-Folgenabschaetzung Art. 35",
"collection": "bp_compliance_recht",
"top_k": 3
}'
# Antwort
{
"query": "Datenschutz-Folgenabschaetzung Art. 35",
"results": [
{
"text": "...",
"regulation_code": "eu_2016_679",
"regulation_name": "DSGVO",
"regulation_short": "DSGVO",
"score": 0.92
}
],
"count": 1
}
```
### Erlaubte Collections (Whitelist)
| Collection | Inhalt |
|------------|--------|
| `bp_compliance_ce` | EU-Verordnungen (DSGVO, AI Act, NIS2, CRA, ...) |
| `bp_compliance_recht` | Deutsche Gesetze (BDSG, TDDDG, DDG, ...) |
| `bp_compliance_gesetze` | Regulierungstexte fuer Modul-Matching |
| `bp_compliance_datenschutz` | DSGVO Datenschutzmassnahmen |
| `bp_dsfa_corpus` | DSFA Templates & Bewertungskriterien |
| `bp_legal_templates` | Rechtsdokument-Vorlagen (DSE, AGB, AVV) |
Unbekannte Collections → `400 Bad Request`.
### Fehlertoleranz
RAG-Ausfaelle brechen **nie** die Hauptfunktion:
| Schicht | Verhalten bei Fehler |
|---------|---------------------|
| Go SDK (`SearchCollection`) | Gibt `error` zurueck (Caller entscheidet) |
| Python (`ComplianceRAGClient`) | Gibt `[]` zurueck, loggt WARNING |
| TypeScript (`queryRAG`) | Gibt `''` zurueck, kein throw |
| Requirements API | `legal_context: []` statt HTTP 500 |
---
@@ -391,6 +467,15 @@ go monitor.Start(ctx)
| GET | `/sdk/v1/ucca/wizard/schema` | Wizard-Schema abrufen |
| POST | `/sdk/v1/ucca/wizard/ask` | Legal Assistant fragen |
### RAG Endpoints
| Method | Endpoint | Beschreibung |
|--------|----------|--------------|
| POST | `/sdk/v1/rag/search` | Multi-Collection RAG-Suche |
| GET | `/sdk/v1/rag/regulations` | Verfuegbare Regulierungen |
| GET | `/sdk/v1/rag/corpus-status` | Corpus-Versions-Status |
| GET | `/sdk/v1/rag/corpus-versions/:collection` | Versionshistorie |
### License Endpoints
| Method | Endpoint | Beschreibung |
@@ -732,6 +817,10 @@ func TestAIActModule_HighRiskEmploymentAI(t *testing.T) {
|-------|--------------|
| `internal/ucca/policy_engine.go` | Haupt-Policy-Engine |
| `internal/ucca/license_policy.go` | License Policy Engine |
| `internal/ucca/legal_rag.go` | Legal RAG Client (Multi-Collection Search) |
| `internal/ucca/legal_rag_test.go` | Tests fuer SearchCollection, Fallback |
| `internal/api/handlers/rag_handlers.go` | RAG Search API (Collection-Whitelist) |
| `internal/api/handlers/rag_handlers_test.go` | Tests fuer RAG Handler |
| `internal/ucca/obligations_framework.go` | Obligations Interfaces & Typen |
| `internal/ucca/obligations_registry.go` | Modul-Registry |
| `internal/ucca/nis2_module.go` | NIS2 Decision Tree |
@@ -741,6 +830,25 @@ func TestAIActModule_HighRiskEmploymentAI(t *testing.T) {
| `internal/api/handlers/obligations_handlers.go` | Obligations API |
| `policies/obligations/*.yaml` | Pflichten-Kataloge |
### Python Backend (RAG-Integration)
| Datei | Beschreibung |
|-------|--------------|
| `backend-compliance/compliance/services/rag_client.py` | ComplianceRAGClient (Proxy zum Go SDK) |
| `backend-compliance/compliance/services/ai_compliance_assistant.py` | AI Assistant mit RAG-Anreicherung |
| `backend-compliance/compliance/api/routes.py` | Requirements API mit `?include_legal_context` |
| `backend-compliance/tests/test_rag_client.py` | Tests fuer RAG Client + Collection Mapping |
| `backend-compliance/tests/test_routes_legal_context.py` | Tests fuer Legal Context API |
### TypeScript Frontend (Drafting Engine RAG)
| Datei | Beschreibung |
|-------|--------------|
| `admin-compliance/lib/sdk/drafting-engine/rag-query.ts` | Shared `queryRAG()` Utility |
| `admin-compliance/lib/sdk/drafting-engine/__tests__/rag-query.test.ts` | Tests fuer queryRAG |
| `admin-compliance/app/api/sdk/drafting-engine/draft/route.ts` | Draft v1/v2 mit RAG-Kontext |
| `admin-compliance/app/api/sdk/drafting-engine/chat/route.ts` | Chat mit shared queryRAG Import |
---
*Dokumentationsstand: 2026-01-29*
*Dokumentationsstand: 2026-03-02*