diff --git a/admin-compliance/app/sdk/sdk-flow/flow-data.ts b/admin-compliance/app/sdk/sdk-flow/flow-data.ts index 4a7abc0..544507a 100644 --- a/admin-compliance/app/sdk/sdk-flow/flow-data.ts +++ b/admin-compliance/app/sdk/sdk-flow/flow-data.ts @@ -123,9 +123,9 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [ 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.', + 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. Zusaetzlich werden anwendbare Regulierungen (DSGVO, AI Act, NIS2 etc.) und zustaendige Aufsichtsbehoerden automatisch abgeleitet.', inputs: ['companyProfile'], - outputs: ['complianceDepthLevel'], + outputs: ['complianceDepthLevel', 'applicableRegulations', 'supervisoryAuthorities'], prerequisiteSteps: ['company-profile'], dbTables: ['sdk_states'], dbMode: 'read/write', @@ -399,9 +399,9 @@ export const SDK_FLOW_STEPS: SDKFlowStep[] = [ 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.', + descriptionLong: 'Die Pflichtenuebersicht konsolidiert alle gesetzlichen Pflichten, die sich aus den Requirements, der AI-Act-Klassifizierung und den aktivierten Modulen ergeben. Wenn applicableRegulations aus dem Scope-Profiling vorliegen, werden diese direkt als Vorfilter verwendet. 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'], + inputs: ['requirements', 'aiActClassification', 'modules', 'applicableRegulations'], outputs: ['obligationsOverview'], prerequisiteSteps: ['audit-report'], dbTables: ['compliance_obligations'], diff --git a/admin-compliance/components/sdk/ProjectSelector/ProjectSelector.tsx b/admin-compliance/components/sdk/ProjectSelector/ProjectSelector.tsx index 6ac35d1..d7b4ffa 100644 --- a/admin-compliance/components/sdk/ProjectSelector/ProjectSelector.tsx +++ b/admin-compliance/components/sdk/ProjectSelector/ProjectSelector.tsx @@ -482,7 +482,6 @@ export function ProjectSelector() { } const handleProjectClick = (project: ProjectInfo) => { - if (project.status === 'archived') return // archived projects are read-only in list router.push(`/sdk?project=${project.id}`) } @@ -598,26 +597,6 @@ export function ProjectSelector() { )} - {/* No active but has archived */} - {!loading && projects.length === 0 && archivedProjects.length > 0 && ( -
-

Keine aktiven Projekte

-

- Sie haben {archivedProjects.length} archivierte{archivedProjects.length === 1 ? 's' : ''} Projekt{archivedProjects.length === 1 ? '' : 'e'}. - Stellen Sie ein Projekt wieder her oder erstellen Sie ein neues. -

- -
- )} - {/* Archived Projects Section */} {!loading && archivedProjects.length > 0 && (
diff --git a/admin-compliance/lib/sdk/__tests__/scope-to-facts.test.ts b/admin-compliance/lib/sdk/__tests__/scope-to-facts.test.ts new file mode 100644 index 0000000..faa7fad --- /dev/null +++ b/admin-compliance/lib/sdk/__tests__/scope-to-facts.test.ts @@ -0,0 +1,193 @@ +import { describe, it, expect } from 'vitest' +import { + parseEmployeeRange, + parseRevenueRange, + buildAssessmentPayload, +} from '../scope-to-facts' +import type { CompanyProfile } from '../types' +import type { ScopeProfilingAnswer, ScopeDecision } from '../compliance-scope-types' + +// ============================================================================= +// parseEmployeeRange +// ============================================================================= + +describe('parseEmployeeRange', () => { + it('returns 5 for "1-9"', () => { + expect(parseEmployeeRange('1-9')).toBe(5) + }) + + it('returns 30 for "10-49"', () => { + expect(parseEmployeeRange('10-49')).toBe(30) + }) + + it('returns 150 for "50-249"', () => { + expect(parseEmployeeRange('50-249')).toBe(150) + }) + + it('returns 625 for "250-999"', () => { + expect(parseEmployeeRange('250-999')).toBe(625) + }) + + it('returns 1500 for "1000+"', () => { + expect(parseEmployeeRange('1000+')).toBe(1500) + }) + + it('returns 10 for null', () => { + expect(parseEmployeeRange(null)).toBe(10) + }) + + it('returns 10 for undefined', () => { + expect(parseEmployeeRange(undefined)).toBe(10) + }) +}) + +// ============================================================================= +// parseRevenueRange +// ============================================================================= + +describe('parseRevenueRange', () => { + it('returns 1000000 for "< 2 Mio"', () => { + expect(parseRevenueRange('< 2 Mio')).toBe(1000000) + }) + + it('returns 6000000 for "2-10 Mio"', () => { + expect(parseRevenueRange('2-10 Mio')).toBe(6000000) + }) + + it('returns 30000000 for "10-50 Mio"', () => { + expect(parseRevenueRange('10-50 Mio')).toBe(30000000) + }) + + it('returns 75000000 for "> 50 Mio"', () => { + expect(parseRevenueRange('> 50 Mio')).toBe(75000000) + }) + + it('returns 1000000 for null', () => { + expect(parseRevenueRange(null)).toBe(1000000) + }) + + it('returns 1000000 for undefined', () => { + expect(parseRevenueRange(undefined)).toBe(1000000) + }) +}) + +// ============================================================================= +// buildAssessmentPayload +// ============================================================================= + +describe('buildAssessmentPayload', () => { + const baseProfile: CompanyProfile = { + companyName: 'Test GmbH', + legalForm: 'GmbH', + industry: ['IT', 'Software'], + employeeCount: '50-249', + annualRevenue: '10-50 Mio', + headquartersCountry: 'DE', + headquartersState: 'BW', + isDataController: true, + isDataProcessor: false, + offerings: ['software_saas'], + } + + const baseAnswers: ScopeProfilingAnswer[] = [ + { questionId: 'data_art9', value: false, blockId: 'data' }, + { questionId: 'data_minors', value: false, blockId: 'data' }, + { questionId: 'data_hr', value: true, blockId: 'data' }, + { questionId: 'data_financial', value: false, blockId: 'data' }, + { questionId: 'tech_third_country', value: true, blockId: 'tech' }, + { questionId: 'tech_subprocessors', value: true, blockId: 'tech' }, + { questionId: 'proc_adm_scoring', value: false, blockId: 'processing' }, + { questionId: 'proc_employee_monitoring', value: false, blockId: 'processing' }, + { questionId: 'proc_video_surveillance', value: false, blockId: 'processing' }, + { questionId: 'proc_tracking', value: false, blockId: 'processing' }, + { questionId: 'prod_cookies_consent', value: true, blockId: 'product' }, + { questionId: 'data_volume', value: false, blockId: 'data' }, + { questionId: 'ai_uses_ai', value: true, blockId: 'ai' }, + { questionId: 'ai_categories', value: ['ai_provider'], blockId: 'ai' }, + { questionId: 'ai_risk_assessment', value: 'limited', blockId: 'ai' }, + { questionId: 'org_cert_target', value: 'iso27001', blockId: 'organisation' }, + ] + + it('maps a full profile correctly', () => { + const payload = buildAssessmentPayload(baseProfile, baseAnswers, null) + + expect(payload.employee_count).toBe(150) + expect(payload.annual_revenue).toBe(30000000) + expect(payload.country).toBe('DE') + expect(payload.industry).toBe('IT, Software') + expect(payload.legal_form).toBe('GmbH') + expect(payload.is_controller).toBe(true) + expect(payload.is_processor).toBe(false) + expect(payload.cross_border_transfer).toBe(true) + expect(payload.uses_processors).toBe(true) + expect(payload.uses_cookies).toBe(true) + expect(payload.processes_employee_data).toBe(true) + expect(payload.operates_platform).toBe(true) + expect(payload.proc_ai_usage).toBe(true) + expect(payload.cert_target).toBe('iso27001') + }) + + it('uses defaults for null/undefined profile fields', () => { + const emptyProfile: CompanyProfile = { + companyName: 'Minimal', + } + const payload = buildAssessmentPayload(emptyProfile, [], null) + + expect(payload.employee_count).toBe(10) // parseEmployeeRange(undefined) + expect(payload.annual_revenue).toBe(1000000) + expect(payload.country).toBe('DE') // default + expect(payload.industry).toBe('') + expect(payload.legal_form).toBe('') + expect(payload.is_controller).toBe(true) // default + expect(payload.is_processor).toBe(false) // default + expect(payload.determined_level).toBe('L2') // default + }) + + it('detects AI provider from ai_categories', () => { + const payload = buildAssessmentPayload(baseProfile, baseAnswers, null) + + expect(payload.is_ai_provider).toBe(true) + expect(payload.is_ai_deployer).toBe(false) + expect(payload.limited_risk_ai).toBe(true) + expect(payload.high_risk_ai).toBe(false) + }) + + it('detects AI deployer from ai_categories', () => { + const deployerAnswers = baseAnswers.map(a => + a.questionId === 'ai_categories' + ? { ...a, value: ['ai_deployer'] } + : a + ) + const payload = buildAssessmentPayload(baseProfile, deployerAnswers, null) + + expect(payload.is_ai_provider).toBe(false) + expect(payload.is_ai_deployer).toBe(true) + }) + + it('detects financial institution from industry', () => { + const finProfile: CompanyProfile = { + ...baseProfile, + industry: ['Finanzdienstleistungen', 'Banking'], + } + const payload = buildAssessmentPayload(finProfile, baseAnswers, null) + + expect(payload.is_financial_institution).toBe(true) + }) + + it('includes decision data when provided', () => { + const decision: ScopeDecision = { + determinedLevel: 'L3', + triggeredHardTriggers: [ + { rule: { id: 'rule-1', name: 'Test Rule', description: '', targetLevel: 'L3', trigger: { field: '', op: 'eq', value: true } }, factValue: true }, + ], + requiredDocuments: [ + { documentType: 'dsfa', reason: 'test', regulation: 'dsgvo' }, + ], + } as any + const payload = buildAssessmentPayload(baseProfile, baseAnswers, decision) + + expect(payload.determined_level).toBe('L3') + expect(payload.triggered_rules).toEqual(['rule-1']) + expect(payload.required_documents).toEqual(['dsfa']) + }) +}) diff --git a/admin-compliance/lib/sdk/__tests__/supervisory-authority-resolver.test.ts b/admin-compliance/lib/sdk/__tests__/supervisory-authority-resolver.test.ts new file mode 100644 index 0000000..6afdfb6 --- /dev/null +++ b/admin-compliance/lib/sdk/__tests__/supervisory-authority-resolver.test.ts @@ -0,0 +1,105 @@ +import { describe, it, expect } from 'vitest' +import { resolveAuthorities } from '../supervisory-authority-resolver' + +// ============================================================================= +// Datenschutz-Aufsichtsbehoerde (DSGVO) +// ============================================================================= + +describe('resolveAuthorities — Datenschutz', () => { + it('resolves DE + BW to LfDI BW', () => { + const results = resolveAuthorities('BW', 'DE', ['dsgvo']) + const dp = results.find(r => r.domain === 'Datenschutz') + expect(dp).toBeDefined() + expect(dp!.authority.abbreviation).toBe('LfDI BW') + }) + + it('resolves DE + BY to BayLDA', () => { + const results = resolveAuthorities('BY', 'DE', ['dsgvo']) + const dp = results.find(r => r.domain === 'Datenschutz') + expect(dp).toBeDefined() + expect(dp!.authority.abbreviation).toBe('BayLDA') + }) + + it('resolves DE without state to BfDI', () => { + const results = resolveAuthorities(undefined, 'DE', ['dsgvo']) + const dp = results.find(r => r.domain === 'Datenschutz') + expect(dp).toBeDefined() + expect(dp!.authority.abbreviation).toBe('BfDI') + }) + + it('resolves AT to DSB AT', () => { + const results = resolveAuthorities(undefined, 'AT', ['dsgvo']) + const dp = results.find(r => r.domain === 'Datenschutz') + expect(dp).toBeDefined() + expect(dp!.authority.abbreviation).toBe('DSB AT') + }) + + it('resolves CH to EDOEB', () => { + const results = resolveAuthorities(undefined, 'CH', ['dsgvo']) + const dp = results.find(r => r.domain === 'Datenschutz') + expect(dp).toBeDefined() + expect(dp!.authority.abbreviation).toBe('EDOEB') + }) +}) + +// ============================================================================= +// Regulierungs-spezifische Behoerden +// ============================================================================= + +describe('resolveAuthorities — regulation-specific', () => { + it('includes BSI for NIS2 in DE', () => { + const results = resolveAuthorities('BW', 'DE', ['dsgvo', 'nis2']) + const nis2 = results.find(r => r.domain.includes('NIS2')) + expect(nis2).toBeDefined() + expect(nis2!.authority.abbreviation).toBe('BSI') + }) + + it('includes BaFin for financial_policy in DE', () => { + const results = resolveAuthorities('BW', 'DE', ['dsgvo', 'financial_policy']) + const fin = results.find(r => r.domain.includes('Finanzaufsicht')) + expect(fin).toBeDefined() + expect(fin!.authority.abbreviation).toBe('BaFin') + }) + + it('includes BNetzA for ai_act in DE', () => { + const results = resolveAuthorities('BW', 'DE', ['dsgvo', 'ai_act']) + const ai = results.find(r => r.domain.includes('KI-Aufsicht')) + expect(ai).toBeDefined() + expect(ai!.authority.abbreviation).toBe('BNetzA') + }) + + it('includes NCSA for NIS2 outside DE', () => { + const results = resolveAuthorities(undefined, 'AT', ['dsgvo', 'nis2']) + const nis2 = results.find(r => r.domain.includes('NIS2')) + expect(nis2).toBeDefined() + expect(nis2!.authority.abbreviation).toBe('NCSA') + }) +}) + +// ============================================================================= +// Edge Cases +// ============================================================================= + +describe('resolveAuthorities — edge cases', () => { + it('returns empty array when no regulations', () => { + const results = resolveAuthorities('BW', 'DE', []) + expect(results).toEqual([]) + }) + + it('returns multiple authorities for multiple regulations', () => { + const results = resolveAuthorities('BW', 'DE', ['dsgvo', 'nis2', 'ai_act', 'financial_policy']) + expect(results.length).toBe(4) + }) + + it('does not include BaFin for non-DE financial_policy', () => { + const results = resolveAuthorities(undefined, 'AT', ['financial_policy']) + const fin = results.find(r => r.domain.includes('Finanzaufsicht')) + expect(fin).toBeUndefined() + }) + + it('does not include BNetzA for non-DE ai_act', () => { + const results = resolveAuthorities(undefined, 'AT', ['ai_act']) + const ai = results.find(r => r.domain.includes('KI-Aufsicht')) + expect(ai).toBeUndefined() + }) +}) diff --git a/developer-portal/app/page.tsx b/developer-portal/app/page.tsx index 2dc5099..f5f6793 100644 --- a/developer-portal/app/page.tsx +++ b/developer-portal/app/page.tsx @@ -228,6 +228,12 @@ function ComplianceDashboard() { PDF, JSON, ZIP-Export fuer Audits und Dokumentation

+
+

Automatische Regulierungs-Ableitung

+

+ Aus CompanyProfile und Scope-Profiling werden anwendbare Gesetze (DSGVO, AI Act, NIS2, DORA etc.) und zustaendige Aufsichtsbehoerden automatisch ermittelt +

+
{/* Next Steps */} diff --git a/docs-src/services/sdk-modules/obligations.md b/docs-src/services/sdk-modules/obligations.md index 88c58a6..27d0cbe 100644 --- a/docs-src/services/sdk-modules/obligations.md +++ b/docs-src/services/sdk-modules/obligations.md @@ -144,7 +144,7 @@ Obligation DSGVO-ART-32 → Controls [TOM-001, TOM-042, TOM-097] | `GET` | `/sdk/v1/ucca/obligations/regulations` | Alle verfügbaren Regulierungen auflisten | | `GET` | `/sdk/v1/ucca/obligations/regulations/:id/decision-tree` | Entscheidungsbaum für eine Regulierung | -### Schnellprüfung +### Schnellprüfung & Scope-Assessment | Methode | Pfad | Beschreibung | |---------|------|--------------| @@ -205,6 +205,60 @@ Obligation DSGVO-ART-32 → Controls [TOM-001, TOM-042, TOM-097] --- +## Automatische Regulierungs-Ableitung + +Seit v2 kann das Obligations-Framework anwendbare Regulierungen und Aufsichtsbehörden direkt aus dem Scope-Profiling ableiten. + +### Datenfluss + +``` +CompanyProfile + ScopeProfilingAnswers + │ + ▼ + scope-to-facts.ts ← Konvertiert Profil + Scope-Antworten in ScopeDecisionPayload + │ + ▼ + POST /assess-from-scope ← Go AI SDK bewertet Payload gegen Condition Engine + │ + ▼ + ApplicableRegulations ← Liste anwendbarer Gesetze (DSGVO, AI Act, NIS2, etc.) + + + supervisory-authority- ← Zuständige Aufsichtsbehörden (LfDI, BSI, BaFin, BNetzA) + resolver.ts +``` + +### Neue Dateien + +| Datei | Beschreibung | +|-------|--------------| +| `admin-compliance/lib/sdk/scope-to-facts.ts` | Mapper: CompanyProfile + ScopeAnswers → `ScopeDecisionPayload` für Go SDK | +| `admin-compliance/lib/sdk/supervisory-authority-resolver.ts` | Ermittelt Aufsichtsbehörden aus Bundesland/Land + Regulierungen | + +### scope-to-facts.ts + +Exportierte Funktionen: + +- `buildAssessmentPayload(profile, scopeAnswers, decision)` → `ScopeDecisionPayload` +- `parseEmployeeRange(range)` → Mittelwert als Zahl (z.B. "50-249" → 150) +- `parseRevenueRange(range)` → Umsatz als Zahl (z.B. "10-50 Mio" → 30.000.000) + +### supervisory-authority-resolver.ts + +Exportierte Funktion: + +- `resolveAuthorities(state, country, regulationIds)` → `SupervisoryAuthorityResult[]` + +Abgedeckte Regulierungen → Behörden: + +| Regulierung | Behörde (DE) | Behörde (Andere) | +|-------------|-------------|-----------------| +| `dsgvo` | Landes-Datenschutzbehörde (16 Bundesländer) | Nationale DSB (AT, CH, FR, NL, etc.) | +| `nis2` | BSI | NCSA | +| `financial_policy` | BaFin | — | +| `ai_act` | BNetzA | — | + +--- + ## Gap-Analyse Die Gap-Analyse vergleicht die **geforderten TOM-Controls** (aus Obligations) mit den **implementierten Controls** (aus `compliance_controls`):