fix(freigabe): Vorbereitung-Module release prep — Python 3.9 fix, Scope Engine tests, MkDocs
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 36s
CI / test-python-backend-compliance (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 20s
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 36s
CI / test-python-backend-compliance (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 20s
- fix: company_profile_routes.py — dict|None → Optional[dict] for Python 3.9 compat (9/9 tests grün) - test: 40 Vitest-Tests für ComplianceScopeEngine (calculateScores, determineLevel, evaluateHardTriggers, evaluate integration, buildDocumentScope, evaluateRiskFlags) - docs: vorbereitung-module.md — Profil, Scope, UCCA vollständig dokumentiert - docs: mkdocs.yml — Nav-Eintrag "Vorbereitung-Module (Paket 1)" vor Analyse-Module Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<string, unknown> = {}) {
|
||||
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()
|
||||
})
|
||||
})
|
||||
@@ -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(
|
||||
|
||||
268
docs-src/services/sdk-modules/vorbereitung-module.md
Normal file
268
docs-src/services/sdk-modules/vorbereitung-module.md
Normal file
@@ -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"
|
||||
```
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user