Files
breakpilot-compliance/ai-compliance-sdk/internal/ucca/json_regulation_module.go
Benjamin Admin 38e278ee3c
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 32s
CI / test-python-backend-compliance (push) Successful in 29s
CI / test-python-document-crawler (push) Successful in 20s
CI / test-python-dsms-gateway (push) Successful in 18s
feat(ucca): Pflichtendatenbank v2 (325 Obligations), Trigger-Engine, TOM-Control-Mapping
- 9 Regulation-JSON-Dateien (DSGVO 80, AI Act 60, NIS2 40, BDSG 30, TTDSG 20, DSA 35, Data Act 25, EU-Maschinen 15, DORA 20)
- Condition-Tree-Engine fuer automatische Pflichtenselektion (all_of/any_of, 80+ Field-Paths)
- Generischer JSONRegulationModule-Loader mit YAML-Fallback
- Bidirektionales TOM-Control-Mapping (291 Obligation→Control, 92 Control→Obligation)
- Gap-Analyse-Engine (Compliance-%, Priority Actions, Domain Breakdown)
- ScopeDecision→UnifiedFacts Bridge fuer Auto-Profiling
- 4 neue API-Endpoints (assess-from-scope, tom-controls, gap-analysis, reverse-lookup)
- Frontend: Auto-Profiling Button, Regulation-Filter Chips, TOM-Panel, Gap-Analyse-View
- 18 Unit Tests (Condition Engine, v2 Loader, TOM Mapper)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:51:44 +01:00

302 lines
9.3 KiB
Go

package ucca
import (
"fmt"
"time"
)
// JSONRegulationModule implements RegulationModule from a v2 JSON file
type JSONRegulationModule struct {
regFile *V2RegulationFile
conditionEngine *ObligationConditionEngine
applicability func(*UnifiedFacts) bool
}
// NewJSONRegulationModule creates a RegulationModule from a v2 JSON regulation file
func NewJSONRegulationModule(regFile *V2RegulationFile) *JSONRegulationModule {
m := &JSONRegulationModule{
regFile: regFile,
conditionEngine: NewObligationConditionEngine(),
}
// Set regulation-level applicability check
m.applicability = m.defaultApplicability
return m
}
func (m *JSONRegulationModule) ID() string { return m.regFile.Regulation }
func (m *JSONRegulationModule) Name() string { return m.regFile.Name }
func (m *JSONRegulationModule) Description() string { return m.regFile.Description }
// IsApplicable checks regulation-level applicability
func (m *JSONRegulationModule) IsApplicable(facts *UnifiedFacts) bool {
return m.applicability(facts)
}
// defaultApplicability determines if the regulation applies based on regulation ID
func (m *JSONRegulationModule) defaultApplicability(facts *UnifiedFacts) bool {
switch m.regFile.Regulation {
case "dsgvo":
return facts.DataProtection.ProcessesPersonalData &&
(facts.Organization.EUMember || facts.DataProtection.OffersToEU || facts.DataProtection.MonitorsEUIndividuals)
case "ai_act":
return facts.AIUsage.UsesAI
case "nis2":
return facts.Sector.PrimarySector != "" && facts.Sector.PrimarySector != "other" &&
(facts.Organization.MeetsNIS2SizeThreshold() || len(facts.Sector.SpecialServices) > 0)
case "bdsg":
return facts.DataProtection.ProcessesPersonalData && facts.Organization.Country == "DE"
case "ttdsg":
return (facts.DataProtection.UsesCookies || facts.DataProtection.UsesTracking) &&
facts.Organization.Country == "DE"
case "dsa":
return facts.DataProtection.OperatesPlatform && facts.Organization.EUMember
case "data_act":
return facts.Organization.EUMember
case "eu_machinery":
return facts.Organization.EUMember && facts.AIUsage.UsesAI
case "dora":
return facts.Financial.DORAApplies || facts.Financial.IsRegulated
default:
return true
}
}
// DeriveObligations derives applicable obligations from the JSON data
func (m *JSONRegulationModule) DeriveObligations(facts *UnifiedFacts) []Obligation {
var result []Obligation
for _, v2Obl := range m.regFile.Obligations {
// Check condition
if v2Obl.AppliesWhenCondition != nil {
if !m.conditionEngine.Evaluate(v2Obl.AppliesWhenCondition, facts) {
continue
}
} else {
// Fall back to legacy applies_when string matching
if !m.evaluateLegacyCondition(v2Obl.AppliesWhen, facts) {
continue
}
}
result = append(result, m.convertObligation(v2Obl))
}
return result
}
// DeriveControls derives applicable controls
func (m *JSONRegulationModule) DeriveControls(facts *UnifiedFacts) []ObligationControl {
var result []ObligationControl
for _, ctrl := range m.regFile.Controls {
result = append(result, ObligationControl{
ID: ctrl.ID,
RegulationID: m.regFile.Regulation,
Name: ctrl.Name,
Description: ctrl.Description,
Category: ctrl.Category,
WhatToDo: ctrl.WhatToDo,
ISO27001Mapping: ctrl.ISO27001Mapping,
Priority: ObligationPriority(mapPriority(ctrl.Priority)),
})
}
return result
}
func (m *JSONRegulationModule) GetDecisionTree() *DecisionTree { return nil }
func (m *JSONRegulationModule) GetIncidentDeadlines(facts *UnifiedFacts) []IncidentDeadline {
var result []IncidentDeadline
for _, dl := range m.regFile.IncidentDL {
var legalBasis []LegalReference
for _, lb := range dl.LegalBasis {
legalBasis = append(legalBasis, LegalReference{
Norm: lb.Norm,
Article: lb.Article,
Title: lb.Title,
})
}
result = append(result, IncidentDeadline{
RegulationID: m.regFile.Regulation,
Phase: dl.Phase,
Deadline: dl.Deadline,
Content: dl.Content,
Recipient: dl.Recipient,
LegalBasis: legalBasis,
})
}
return result
}
func (m *JSONRegulationModule) GetClassification(facts *UnifiedFacts) string {
switch m.regFile.Regulation {
case "nis2":
if facts.Organization.MeetsNIS2LargeThreshold() || facts.Sector.IsKRITIS {
return string(NIS2EssentialEntity)
}
if facts.Organization.MeetsNIS2SizeThreshold() {
return string(NIS2ImportantEntity)
}
if len(facts.Sector.SpecialServices) > 0 {
return string(NIS2EssentialEntity)
}
return string(NIS2NotAffected)
case "ai_act":
if facts.AIUsage.HasHighRiskAI {
return "hochrisiko"
}
if facts.AIUsage.HasLimitedRiskAI {
return "begrenztes_risiko"
}
return "minimales_risiko"
case "dsgvo":
if facts.DataProtection.IsController {
if facts.DataProtection.LargeScaleProcessing || facts.DataProtection.ProcessesSpecialCategories {
return "verantwortlicher_hochrisiko"
}
return "verantwortlicher"
}
return "auftragsverarbeiter"
default:
return "anwendbar"
}
}
// convertObligation converts a V2Obligation to the framework Obligation struct
func (m *JSONRegulationModule) convertObligation(v2 V2Obligation) Obligation {
obl := Obligation{
ID: v2.ID,
RegulationID: m.regFile.Regulation,
Title: v2.Title,
Description: v2.Description,
Category: ObligationCategory(v2.Category),
Responsible: ResponsibleRole(v2.Responsible),
Priority: ObligationPriority(mapPriority(v2.Priority)),
AppliesWhen: v2.AppliesWhen,
ISO27001Mapping: v2.ISO27001Mapping,
HowToImplement: v2.HowToImplement,
BreakpilotFeature: v2.BreakpilotFeature,
}
// Legal basis
for _, lb := range v2.LegalBasis {
obl.LegalBasis = append(obl.LegalBasis, LegalReference{
Norm: lb.Norm,
Article: lb.Article,
Title: lb.Title,
})
}
// Sanctions
if v2.Sanctions != nil {
obl.Sanctions = &SanctionInfo{
MaxFine: v2.Sanctions.MaxFine,
PersonalLiability: v2.Sanctions.PersonalLiability,
CriminalLiability: v2.Sanctions.CriminalLiability,
Description: v2.Sanctions.Description,
}
}
// Deadline
if v2.Deadline != nil {
obl.Deadline = m.convertDeadline(v2.Deadline)
}
// Evidence
for _, ev := range v2.Evidence {
switch e := ev.(type) {
case string:
obl.Evidence = append(obl.Evidence, EvidenceItem{Name: e, Required: true})
case map[string]interface{}:
name, _ := e["name"].(string)
required, _ := e["required"].(bool)
format, _ := e["format"].(string)
obl.Evidence = append(obl.Evidence, EvidenceItem{
Name: name,
Required: required,
Format: format,
})
}
}
// Store TOM control IDs in ExternalResources for now
obl.ExternalResources = v2.TOMControlIDs
return obl
}
func (m *JSONRegulationModule) convertDeadline(dl *V2Deadline) *Deadline {
d := &Deadline{
Type: DeadlineType(dl.Type),
Duration: dl.Duration,
Interval: dl.Interval,
Event: dl.Event,
}
if dl.Date != "" {
t, err := time.Parse("2006-01-02", dl.Date)
if err == nil {
d.Date = &t
}
}
return d
}
// evaluateLegacyCondition evaluates the legacy applies_when string
func (m *JSONRegulationModule) evaluateLegacyCondition(condition string, facts *UnifiedFacts) bool {
switch condition {
case "always":
return true
case "controller":
return facts.DataProtection.IsController
case "processor":
return facts.DataProtection.IsProcessor
case "high_risk":
return facts.DataProtection.LargeScaleProcessing || facts.DataProtection.SystematicMonitoring || facts.DataProtection.ProcessesSpecialCategories
case "needs_dpo":
return facts.DataProtection.RequiresDSBByLaw || facts.Organization.IsPublicAuthority || facts.Organization.EmployeeCount >= 20
case "uses_processors":
return facts.DataProtection.UsesExternalProcessor
case "cross_border":
return facts.DataProtection.TransfersToThirdCountries || facts.DataProtection.CrossBorderProcessing
case "uses_ai":
return facts.AIUsage.UsesAI
case "high_risk_provider":
return facts.AIUsage.HasHighRiskAI && facts.AIUsage.IsAIProvider
case "high_risk_deployer":
return facts.AIUsage.HasHighRiskAI && facts.AIUsage.IsAIDeployer
case "high_risk_deployer_fria":
return facts.AIUsage.HasHighRiskAI && facts.AIUsage.IsAIDeployer && (facts.Organization.IsPublicAuthority || facts.AIUsage.EducationAccess || facts.AIUsage.EmploymentDecisions)
case "limited_risk":
return facts.AIUsage.HasLimitedRiskAI || facts.AIUsage.AIInteractsWithNaturalPersons
case "gpai_provider":
return facts.AIUsage.UsesGPAI && facts.AIUsage.IsAIProvider
case "gpai_systemic_risk":
return facts.AIUsage.GPAIWithSystemicRisk
default:
// For NIS2-style conditions with classification
if condition == "classification != 'nicht_betroffen'" {
return true // if module is applicable, classification is not "nicht_betroffen"
}
if condition == "classification == 'besonders_wichtige_einrichtung'" {
return facts.Organization.MeetsNIS2LargeThreshold() || facts.Sector.IsKRITIS
}
fmt.Printf("Warning: unknown legacy condition: %s\n", condition)
return true
}
}
// mapPriority normalizes priority strings to standard enum values
func mapPriority(p string) string {
switch p {
case "kritisch", "critical":
return "critical"
case "hoch", "high":
return "high"
case "mittel", "medium":
return "medium"
case "niedrig", "low":
return "low"
default:
return p
}
}