diff --git a/admin-compliance/lib/sdk/__tests__/compliance-scope-engine.test.ts b/admin-compliance/lib/sdk/__tests__/compliance-scope-engine.test.ts new file mode 100644 index 0000000..50e4970 --- /dev/null +++ b/admin-compliance/lib/sdk/__tests__/compliance-scope-engine.test.ts @@ -0,0 +1,372 @@ +import { describe, it, expect } from 'vitest' +import { complianceScopeEngine } from '../compliance-scope-engine' + +// Helper: create an answer object (engine reads answerValue + questionId) +function ans(questionId: string, answerValue: unknown) { + return { questionId, answerValue } as any +} + +// Helper: create a minimal triggered-trigger (engine shape, not types shape) +function trigger(ruleId: string, minimumLevel: string, opts: Record = {}) { + return { ruleId, minimumLevel, mandatoryDocuments: [], requiresDSFA: false, category: 'test', ...opts } as any +} + +// ============================================================================ +// calculateScores +// ============================================================================ + +describe('calculateScores', () => { + it('returns zero composite for empty answers', () => { + const scores = complianceScopeEngine.calculateScores([]) + expect(scores.composite).toBe(0) + expect(scores.risk).toBe(0) + expect(scores.complexity).toBe(0) + expect(scores.assurance).toBe(0) + }) + + it('all-false boolean answers → zero composite', () => { + const scores = complianceScopeEngine.calculateScores([ + ans('data_art9', false), + ans('data_minors', false), + ans('proc_ai_usage', false), + ]) + expect(scores.composite).toBe(0) + }) + + it('boolean true answer increases risk score', () => { + const scoresFalse = complianceScopeEngine.calculateScores([ans('data_art9', false)]) + const scoresTrue = complianceScopeEngine.calculateScores([ans('data_art9', true)]) + expect(scoresTrue.risk).toBeGreaterThan(scoresFalse.risk) + }) + + it('composite is weighted sum: risk×0.4 + complexity×0.3 + assurance×0.3', () => { + const scores = complianceScopeEngine.calculateScores([ans('data_art9', true)]) + const expected = Math.round((scores.risk * 0.4 + scores.complexity * 0.3 + scores.assurance * 0.3) * 10) / 10 + expect(scores.composite).toBe(expected) + }) + + it('numeric answer uses logarithmic normalization — higher value → higher score', () => { + const scoresLow = complianceScopeEngine.calculateScores([ans('data_volume', 10)]) + const scoresHigh = complianceScopeEngine.calculateScores([ans('data_volume', 999)]) + expect(scoresHigh.composite).toBeGreaterThan(scoresLow.composite) + }) + + it('array answer score proportional to count (max 1.0 at 5+)', () => { + const scores1 = complianceScopeEngine.calculateScores([ans('data_art9', ['gesundheit'])]) + const scores5 = complianceScopeEngine.calculateScores([ + ans('data_art9', ['gesundheit', 'biometrie', 'genetik', 'politisch', 'religion']), + ]) + expect(scores5.composite).toBeGreaterThan(scores1.composite) + }) + + it('empty array answer → zero contribution', () => { + const scoresEmpty = complianceScopeEngine.calculateScores([ans('data_art9', [])]) + const scoresNone = complianceScopeEngine.calculateScores([]) + expect(scoresEmpty.composite).toBe(scoresNone.composite) + }) +}) + +// ============================================================================ +// determineLevel +// ============================================================================ + +describe('determineLevel', () => { + it('composite ≤25 → L1', () => { + const level = complianceScopeEngine.determineLevel({ composite: 20 } as any, []) + expect(level).toBe('L1') + }) + + it('composite exactly 25 → L1', () => { + const level = complianceScopeEngine.determineLevel({ composite: 25 } as any, []) + expect(level).toBe('L1') + }) + + it('composite 26–50 → L2', () => { + const level = complianceScopeEngine.determineLevel({ composite: 40 } as any, []) + expect(level).toBe('L2') + }) + + it('composite exactly 50 → L2', () => { + const level = complianceScopeEngine.determineLevel({ composite: 50 } as any, []) + expect(level).toBe('L2') + }) + + it('composite 51–75 → L3', () => { + const level = complianceScopeEngine.determineLevel({ composite: 60 } as any, []) + expect(level).toBe('L3') + }) + + it('composite >75 → L4', () => { + const level = complianceScopeEngine.determineLevel({ composite: 80 } as any, []) + expect(level).toBe('L4') + }) + + it('hard trigger with minimumLevel L3 overrides score-based L1', () => { + const t = trigger('HT-A01', 'L3') + const level = complianceScopeEngine.determineLevel({ composite: 10 } as any, [t]) + expect(level).toBe('L3') + }) + + it('hard trigger with minimumLevel L4 overrides score-based L2', () => { + const t = trigger('HT-F01', 'L4') + const level = complianceScopeEngine.determineLevel({ composite: 40 } as any, [t]) + expect(level).toBe('L4') + }) + + it('level = max(score-level, trigger-level) — score wins when higher', () => { + const t = trigger('HT-B01', 'L2') + // score gives L3, trigger gives L2 → max = L3 + const level = complianceScopeEngine.determineLevel({ composite: 60 } as any, [t]) + expect(level).toBe('L3') + }) + + it('no triggers with zero composite → L1', () => { + const level = complianceScopeEngine.determineLevel({ composite: 0 } as any, []) + expect(level).toBe('L1') + }) +}) + +// ============================================================================ +// evaluateHardTriggers +// ============================================================================ + +describe('evaluateHardTriggers', () => { + it('empty answers → no triggers', () => { + const triggers = complianceScopeEngine.evaluateHardTriggers([]) + expect(triggers).toHaveLength(0) + }) + + it('art9 health data answer triggers category art9 with minimumLevel L3', () => { + const triggers = complianceScopeEngine.evaluateHardTriggers([ + ans('data_art9', ['gesundheit']), + ]) + const art9 = triggers.find((t: any) => t.category === 'art9') + expect(art9).toBeDefined() + expect((art9 as any).minimumLevel).toBe('L3') + }) + + it('minors answer sets requiresDSFA = true', () => { + const triggers = complianceScopeEngine.evaluateHardTriggers([ + ans('data_minors', true), + ]) + const minorsTrigger = triggers.find((t: any) => t.category === 'vulnerable') + expect(minorsTrigger).toBeDefined() + expect((minorsTrigger as any).requiresDSFA).toBe(true) + }) + + it('AI scoring usage answer triggers minimumLevel L2 (adm category)', () => { + const triggers = complianceScopeEngine.evaluateHardTriggers([ + ans('proc_ai_usage', ['scoring']), + ]) + const aiTrigger = triggers.find((t: any) => t.category === 'adm') + expect(aiTrigger).toBeDefined() + expect(['L2', 'L3', 'L4']).toContain((aiTrigger as any).minimumLevel) + }) + + it('multiple triggers all returned when multiple questions answered', () => { + const triggers = complianceScopeEngine.evaluateHardTriggers([ + ans('data_art9', ['gesundheit']), + ans('data_minors', true), + ]) + expect(triggers.length).toBeGreaterThanOrEqual(2) + }) + + it('false answer for boolean trigger does not fire it', () => { + const triggersBefore = complianceScopeEngine.evaluateHardTriggers([]) + const triggersWithFalse = complianceScopeEngine.evaluateHardTriggers([ + ans('data_minors', false), + ]) + expect(triggersWithFalse.length).toBe(triggersBefore.length) + }) +}) + +// ============================================================================ +// evaluate — integration +// ============================================================================ + +describe('evaluate — integration', () => { + it('empty answers → L1 ScopeDecision with all required fields', () => { + const decision = complianceScopeEngine.evaluate([]) + expect(decision.scores).toBeDefined() + expect(decision.triggeredHardTriggers).toBeDefined() + expect(decision.requiredDocuments).toBeDefined() + expect(decision.riskFlags).toBeDefined() + expect(decision.gaps).toBeDefined() + expect(decision.nextActions).toBeDefined() + expect(decision.reasoning).toBeDefined() + expect(decision.determinedLevel).toBe('L1') + expect(decision.evaluatedAt).toBeDefined() + }) + + it('art9 + minors answers → determinedLevel L3 or L4', () => { + const decision = complianceScopeEngine.evaluate([ + ans('data_art9', ['gesundheit', 'biometrie']), + ans('data_minors', true), + ]) + expect(['L3', 'L4']).toContain(decision.determinedLevel) + }) + + it('triggeredHardTriggers populated on evaluate with art9 answer', () => { + const decision = complianceScopeEngine.evaluate([ + ans('data_art9', ['gesundheit']), + ]) + expect(decision.triggeredHardTriggers.length).toBeGreaterThan(0) + }) + + it('composite score non-negative', () => { + const decision = complianceScopeEngine.evaluate([ans('org_employee_count', '50-249')]) + expect((decision.scores as any).composite).toBeGreaterThanOrEqual(0) + }) + + it('evaluate returns array types for collections', () => { + const decision = complianceScopeEngine.evaluate([]) + expect(Array.isArray(decision.triggeredHardTriggers)).toBe(true) + expect(Array.isArray(decision.requiredDocuments)).toBe(true) + expect(Array.isArray(decision.riskFlags)).toBe(true) + expect(Array.isArray(decision.gaps)).toBe(true) + expect(Array.isArray(decision.nextActions)).toBe(true) + }) +}) + +// ============================================================================ +// buildDocumentScope +// ============================================================================ + +describe('buildDocumentScope', () => { + it('returns an array', () => { + const docs = complianceScopeEngine.buildDocumentScope('L1', [], []) + expect(Array.isArray(docs)).toBe(true) + }) + + it('each returned document has documentType, label, estimatedEffort', () => { + // Use a trigger with uppercase docType to test engine behaviour + const t = trigger('HT-A01', 'L3', { + category: 'art9', + mandatoryDocuments: ['VVT'], + }) + const docs = complianceScopeEngine.buildDocumentScope('L3', [t], []) + docs.forEach((doc: any) => { + expect(doc.documentType).toBeDefined() + expect(doc.label).toBeDefined() + expect(typeof doc.estimatedEffort).toBe('number') + expect(doc.estimatedEffort).toBeGreaterThan(0) + }) + }) + + it('mandatory documents have high priority', () => { + const t = trigger('HT-A01', 'L3', { + category: 'art9', + mandatoryDocuments: ['VVT'], + }) + const docs = complianceScopeEngine.buildDocumentScope('L3', [t], []) + const mandatoryDocs = docs.filter((d: any) => d.requirement === 'mandatory') + mandatoryDocs.forEach((doc: any) => { + expect(doc.priority).toBe('high') + }) + }) + + it('documents sorted: mandatory first', () => { + const decision = complianceScopeEngine.evaluate([ + ans('data_art9', ['gesundheit']), + ]) + const docs = decision.requiredDocuments + if (docs.length > 1) { + // mandatory docs should appear before recommended ones + let seenNonMandatory = false + for (const doc of docs) { + if ((doc as any).requirement !== 'mandatory') seenNonMandatory = true + if (seenNonMandatory) { + expect((doc as any).requirement).not.toBe('mandatory') + } + } + } + }) + + it('sdkStepUrl present for known document types when available', () => { + const decision = complianceScopeEngine.evaluate([ + ans('data_art9', ['gesundheit']), + ]) + // sdkStepUrl is optional — just verify it's a string when present + decision.requiredDocuments.forEach((doc: any) => { + if (doc.sdkStepUrl !== undefined) { + expect(typeof doc.sdkStepUrl).toBe('string') + } + }) + }) +}) + +// ============================================================================ +// evaluateRiskFlags +// ============================================================================ + +describe('evaluateRiskFlags', () => { + it('no answers → empty flags array', () => { + const flags = complianceScopeEngine.evaluateRiskFlags([], 'L1') + expect(Array.isArray(flags)).toBe(true) + }) + + it('no encryption transit at L2+ → high risk flag (technical)', () => { + const flags = complianceScopeEngine.evaluateRiskFlags( + [ans('tech_encryption_transit', false)], + 'L2', + ) + const encFlag = flags.find((f: any) => f.category === 'technical') + expect(encFlag).toBeDefined() + expect((encFlag as any).severity).toBe('high') + }) + + it('no encryption rest at L2+ → high risk flag (technical)', () => { + const flags = complianceScopeEngine.evaluateRiskFlags( + [ans('tech_encryption_rest', false)], + 'L2', + ) + const encFlag = flags.find((f: any) => f.category === 'technical') + expect(encFlag).toBeDefined() + expect((encFlag as any).severity).toBe('high') + }) + + it('encryption flags not raised at L1', () => { + const flags = complianceScopeEngine.evaluateRiskFlags( + [ans('tech_encryption_transit', false)], + 'L1', + ) + const encFlag = flags.find((f: any) => f.message?.includes('Verschlüsselung')) + expect(encFlag).toBeUndefined() + }) + + it('third country transfer without guarantees → legal flag', () => { + const flags = complianceScopeEngine.evaluateRiskFlags( + [ + ans('tech_third_country', true), + ans('tech_hosting_location', 'drittland'), + ], + 'L1', + ) + const legalFlag = flags.find((f: any) => f.category === 'legal') + expect(legalFlag).toBeDefined() + }) + + it('≥250 employees without DPO → organizational flag', () => { + const flags = complianceScopeEngine.evaluateRiskFlags( + [ + ans('org_has_dpo', false), + ans('org_employee_count', '250-999'), + ], + 'L2', + ) + const orgFlag = flags.find((f: any) => f.category === 'organizational') + expect(orgFlag).toBeDefined() + }) + + it('DPO present with 250+ employees → no DPO flag', () => { + const flags = complianceScopeEngine.evaluateRiskFlags( + [ + ans('org_has_dpo', true), + ans('org_employee_count', '250-999'), + ], + 'L2', + ) + const orgFlag = flags.find((f: any) => f.category === 'organizational') + expect(orgFlag).toBeUndefined() + }) +}) diff --git a/backend-compliance/compliance/api/company_profile_routes.py b/backend-compliance/compliance/api/company_profile_routes.py index 060e4ba..620a1f5 100644 --- a/backend-compliance/compliance/api/company_profile_routes.py +++ b/backend-compliance/compliance/api/company_profile_routes.py @@ -139,7 +139,7 @@ def row_to_response(row) -> CompanyProfileResponse: ) -def log_audit(db, tenant_id: str, action: str, changed_fields: dict | None, changed_by: str | None): +def log_audit(db, tenant_id: str, action: str, changed_fields: Optional[dict], changed_by: Optional[str]): """Write an audit log entry.""" try: db.execute( diff --git a/docs-src/services/sdk-modules/vorbereitung-module.md b/docs-src/services/sdk-modules/vorbereitung-module.md new file mode 100644 index 0000000..3f4dd1d --- /dev/null +++ b/docs-src/services/sdk-modules/vorbereitung-module.md @@ -0,0 +1,268 @@ +# Vorbereitung-Module (Paket 1) + +Die drei Vorbereitung-Module bilden die Grundlage des SDK-Workflows. Sie erfassen das Unternehmensprofil, bestimmen den passenden Compliance-Umfang und bewerten konkrete KI/Daten-Anwendungsfälle. + +| Modul | Code | Route | Backend | +|-------|------|-------|---------| +| **Company Profile** | CP-PROF | `/sdk/company-profile` | FastAPI `backend-compliance` | +| **Compliance Scope** | CP-SCOPE | `/sdk/compliance-scope` | FastAPI `backend-compliance` | +| **Use-Case Assessment** | CP-UC | `/sdk/use-cases` | Go `ai-compliance-sdk` | + +--- + +## Profil — company-profile (CP-PROF) + +### Übersicht + +6-Schritt-Wizard zur Erfassung des vollständigen Unternehmensprofils. Speichert hybrid in `localStorage` (Draft) und der DB (Endstand). + +### API-Endpunkte + +| Methode | Pfad | Beschreibung | +|---------|------|--------------| +| `GET` | `/v1/company-profile` | Profil für Tenant laden | +| `POST` | `/v1/company-profile` | Profil erstellen oder aktualisieren (Upsert) | +| `PATCH` | `/v1/company-profile` | Einzelne Felder aktualisieren | +| `DELETE` | `/v1/company-profile` | Profil löschen (Art. 17 DSGVO) | +| `GET` | `/v1/company-profile/audit` | Audit-Log der Profiländerungen | + +### Datenbankstruktur + +**Tabelle:** `compliance_company_profiles` (Migration 005) + +28 Felder, u.a.: + +```sql +id UUID PRIMARY KEY +tenant_id TEXT UNIQUE NOT NULL +company_name TEXT +legal_form TEXT -- GmbH, AG, GbR, ... +industry TEXT -- tech, finance, healthcare, public, retail, education +company_size TEXT -- micro, small, medium, large +employee_count TEXT -- 1-9, 10-49, 50-249, 250-999, 1000+ +uses_ai BOOLEAN +ai_use_cases JSONB +dpo_name TEXT +machine_builder JSONB -- IACE-Profil für Maschinenbauer +is_complete BOOLEAN +completed_at TIMESTAMPTZ +``` + +**Audit-Tabelle:** `compliance_company_profile_audit` + +```sql +id UUID PRIMARY KEY +tenant_id TEXT +action TEXT -- create | update | delete +changed_fields JSONB +changed_by TEXT +created_at TIMESTAMPTZ +``` + +### Request-Modell (CompanyProfileRequest) + +```json +{ + "company_name": "Beispiel GmbH", + "legal_form": "GmbH", + "industry": "tech", + "founded_year": 2018, + "business_model": "B2B", + "company_size": "small", + "employee_count": "10-49", + "annual_revenue": "2-10 Mio", + "headquarters_country": "DE", + "uses_ai": true, + "ai_use_cases": ["Chatbot", "Dokumentenklassifikation"], + "is_complete": true +} +``` + +### Tests + +**Datei:** `backend-compliance/tests/test_company_profile_routes.py` + +9 Tests — alle grün: + +- `test_get_profile_not_found` — 404 wenn kein Profil +- `test_create_profile` — POST legt Profil an +- `test_upsert_profile_update` — POST auf bestehendem Profil → UPDATE +- `test_delete_profile` — DELETE entfernt Profil +- `test_delete_profile_not_found` — 404 bei fehlendem Profil +- `test_get_audit_log_empty` — leeres Audit-Log +- `test_audit_log_after_create` — Audit-Eintrag nach CREATE +- `test_audit_log_after_delete` — Audit-Eintrag nach DELETE +- `test_complete_profile_workflow` — End-to-End Workflow + +--- + +## Scope — compliance-scope (CP-SCOPE) + +### Übersicht + +4-Tab-Oberfläche zur Bestimmung des optimalen Compliance-Levels (L1–L4): + +| Tab | Inhalt | +|-----|--------| +| **Overview** | Aktueller Level, Score-Visualisierung, Dokumenten-Anforderungen | +| **Wizard** | 35 Fragen in 6 Blöcken (ca. 10–15 min) | +| **Entscheidung** | Begründung, Hard Triggers, Gap-Analyse | +| **Export** | JSON/PDF-Export der Scope-Entscheidung | + +### ComplianceScopeEngine + +Die Engine (`lib/sdk/compliance-scope-engine.ts`) implementiert eine 8-Schritt-Pipeline: + +``` +1. calculateScores(answers) → ComplianceScores +2. evaluateHardTriggers(answers) → TriggeredHardTrigger[] +3. determineLevel(scores, triggers) → ComplianceDepthLevel +4. buildDocumentScope(level, ...) → RequiredDocument[] +5. evaluateRiskFlags(answers, level) → RiskFlag[] +6. calculateGaps(answers, level) → ScopeGap[] +7. buildNextActions(docs, gaps) → NextAction[] +8. buildReasoning(...) → ScopeReasoning[] +``` + +### Level-Modell + +| Level | Composite-Score | Bezeichnung | +|-------|----------------|-------------| +| **L1** | ≤ 25 | Lean Startup — Minimalansatz | +| **L2** | 26–50 | KMU Standard | +| **L3** | 51–75 | Erweitert | +| **L4** | > 75 | Zertifizierungsbereit | + +Finale Stufe = `max(score-basiertes Level, höchstes Hard-Trigger-Level)` + +### Score-Gewichte + +``` +Composite = Risk × 0.4 + Complexity × 0.3 + Assurance × 0.3 +``` + +Scores werden aus 35 gewichteten Fragen berechnet. Jede Frage hat individuelle Gewichte für `risk`, `complexity`, `assurance` (definiert in `QUESTION_SCORE_WEIGHTS`). + +### Hard Trigger Kategorien (50 Regeln) + +| Kategorie | Regeln | Beispiel-Trigger | +|-----------|--------|-----------------| +| `art9` | 9 | Gesundheits-, Biometrie-, Genetikdaten → mind. L3 | +| `vulnerable` | 3 | Minderjährige → L3/L4 + DSFA | +| `adm` | 6 | Autonome KI, Profiling, Scoring → L2/L3 | +| `surveillance` | 5 | Videoüberwachung, Mitarbeitermonitoring → L2/L3 | +| `third_country` | 5 | Drittland-Transfer → L2/L3 | +| `certification` | 5 | ISO 27001, SOC2, TISAX → L4 | +| `scale` | 5 | >1Mio Datensätze, >250 Mitarbeiter → L3 | +| `product` | 7 | Webshop, Datenmakler, B2C → L2/L3 | +| `process_maturity` | 5 | Fehlende Prozesse (DSAR, Löschung, Incident) | +| `iace_*` | 5+ | Maschinenbauer: AI Act, CRA, NIS2 | + +### API-Endpunkte + +| Methode | Pfad | Beschreibung | +|---------|------|--------------| +| `GET` | `/v1/compliance-scope` | Scope-Zustand laden (`sdk_states` JSONB) | +| `POST` | `/v1/compliance-scope` | Scope-Zustand speichern (JSONB UPSERT) | + +Scope-Daten werden in `sdk_states.state->'compliance_scope'` als JSONB gespeichert. + +### Tests + +**Datei:** `backend-compliance/tests/test_compliance_scope_routes.py` — 27 Tests + +**Datei:** `admin-compliance/lib/sdk/__tests__/compliance-scope-engine.test.ts` — 40 Vitest-Tests + +Engine-Tests decken ab: + +- `calculateScores`: Leer-Array, boolean/numeric/array Antworten, Composite-Gewichtung +- `determineLevel`: L1–L4 Schwellenwerte, Hard-Trigger-Override, Level-Maximum +- `evaluateHardTriggers`: Art. 9, Minderjährige, KI-Scoring, mehrere Trigger +- `evaluate` (Integration): Vollständige ScopeDecision, Felder vorhanden, Level-Bestimmung +- `buildDocumentScope`: Array-Rückgabe, Dokumentstruktur, Sortierung +- `evaluateRiskFlags`: Verschlüsselung, Drittland, DPO-Pflicht + +--- + +## Anwendung — use-case-assessment / UCCA (CP-UC) + +### Übersicht + +Bewertet konkrete Datenanwendungen hinsichtlich DSGVO- und AI-Act-Konformität. Gibt eine **Machbarkeitsentscheidung** (Feasibility) und detaillierte Befunde zurück. + +| Ansicht | Inhalt | +|---------|--------| +| **Liste** | Alle Assessments mit Filter, Pagination, RiskScoreGauge | +| **Detail** | Vollständiger Befund-Report mit Kategorien, Controls | +| **Neu** | Wizard-Formular zur Assessment-Erstellung | + +### Feasibility-Werte + +| Wert | Bedeutung | +|------|-----------| +| `YES` | Umsetzung ohne Einschränkungen empfohlen | +| `CONDITIONAL` | Umsetzung nur mit spezifischen Maßnahmen | +| `NO` | Umsetzung nicht empfohlen (unakzeptables Risiko) | + +### Risiko-Level + +| Level | Score-Bereich | +|-------|--------------| +| `MINIMAL` | 0–20 | +| `LOW` | 21–40 | +| `MEDIUM` | 41–60 | +| `HIGH` | 61–80 | +| `UNACCEPTABLE` | 81–100 | + +### Backend: ai-compliance-sdk (Go) + +**Service:** `bp-compliance-ai-sdk` (Port 8090 intern, 8093 extern) + +Relevante Module in `internal/ucca/`: + +| Datei | Funktion | +|-------|---------| +| `policy_engine.go` | Zentrale Regelauswertung | +| `dsgvo_module.go` | DSGVO-spezifische Prüfungen (Art. 5–9, 22, 35) | +| `ai_act_module.go` | AI-Act Risikokategorisierung | +| `nis2_module.go` | NIS2-Anforderungen | +| `escalation.go` | Eskalationslogik bei Hochrisiko | +| `handlers.go` | HTTP Handler (Gin) | + +**Datenbanktabellen:** + +```sql +ucca_assessments -- Assessments (tenant_id, title, description, status, feasibility, risk_level) +ucca_findings -- Einzelbefunde pro Assessment +ucca_controls -- Empfohlene Maßnahmen +``` + +### Proxy + +``` +Frontend /api/sdk/v1/ucca/** + → ai-compliance-sdk:8090/ucca/** +``` + +Proxy-Datei: `admin-compliance/app/api/sdk/v1/ucca/[[...path]]/route.ts` + +### Tests + +8 Go-Test-Dateien in `ai-compliance-sdk/internal/ucca/`: + +``` +handlers_test.go +policy_engine_test.go +dsgvo_module_test.go +ai_act_module_test.go +nis2_module_test.go +escalation_test.go +assessment_test.go +feasibility_test.go +``` + +Ausführen auf Mac Mini: + +```bash +ssh macmini "/usr/local/bin/docker exec bp-compliance-ai-sdk go test ./internal/ucca/... -v" +``` diff --git a/mkdocs.yml b/mkdocs.yml index b2eae9d..f4d3653 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,6 +65,7 @@ nav: - Document Crawler: - Uebersicht: services/document-crawler/index.md - SDK Module: + - Vorbereitung-Module (Paket 1): services/sdk-modules/vorbereitung-module.md - Analyse-Module (Paket 2): services/sdk-modules/analyse-module.md - Dokumentations-Module (Paket 3+): services/sdk-modules/dokumentations-module.md - DSFA (Art. 35 DSGVO): services/sdk-modules/dsfa.md