Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
480
ai-compliance-sdk/internal/ucca/obligations_registry.go
Normal file
480
ai-compliance-sdk/internal/ucca/obligations_registry.go
Normal file
@@ -0,0 +1,480 @@
|
||||
package ucca
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Obligations Registry
|
||||
// ============================================================================
|
||||
//
|
||||
// The registry manages all regulation modules and provides methods to evaluate
|
||||
// facts against all registered regulations, aggregating the results.
|
||||
//
|
||||
// ============================================================================
|
||||
|
||||
// ObligationsRegistry manages all regulation modules
|
||||
type ObligationsRegistry struct {
|
||||
modules map[string]RegulationModule
|
||||
}
|
||||
|
||||
// NewObligationsRegistry creates a new registry and registers all default modules
|
||||
func NewObligationsRegistry() *ObligationsRegistry {
|
||||
r := &ObligationsRegistry{
|
||||
modules: make(map[string]RegulationModule),
|
||||
}
|
||||
|
||||
// Register default modules
|
||||
// NIS2 module
|
||||
nis2Module, err := NewNIS2Module()
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Could not load NIS2 module: %v\n", err)
|
||||
} else {
|
||||
r.Register(nis2Module)
|
||||
}
|
||||
|
||||
// DSGVO module
|
||||
dsgvoModule, err := NewDSGVOModule()
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Could not load DSGVO module: %v\n", err)
|
||||
} else {
|
||||
r.Register(dsgvoModule)
|
||||
}
|
||||
|
||||
// AI Act module
|
||||
aiActModule, err := NewAIActModule()
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Could not load AI Act module: %v\n", err)
|
||||
} else {
|
||||
r.Register(aiActModule)
|
||||
}
|
||||
|
||||
// Future modules will be registered here:
|
||||
// r.Register(NewDORAModule())
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// NewObligationsRegistryWithModules creates a registry with specific modules
|
||||
func NewObligationsRegistryWithModules(modules ...RegulationModule) *ObligationsRegistry {
|
||||
r := &ObligationsRegistry{
|
||||
modules: make(map[string]RegulationModule),
|
||||
}
|
||||
for _, m := range modules {
|
||||
r.Register(m)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Register adds a regulation module to the registry
|
||||
func (r *ObligationsRegistry) Register(module RegulationModule) {
|
||||
r.modules[module.ID()] = module
|
||||
}
|
||||
|
||||
// Unregister removes a regulation module from the registry
|
||||
func (r *ObligationsRegistry) Unregister(moduleID string) {
|
||||
delete(r.modules, moduleID)
|
||||
}
|
||||
|
||||
// GetModule returns a specific module by ID
|
||||
func (r *ObligationsRegistry) GetModule(moduleID string) (RegulationModule, bool) {
|
||||
m, ok := r.modules[moduleID]
|
||||
return m, ok
|
||||
}
|
||||
|
||||
// ListModules returns info about all registered modules
|
||||
func (r *ObligationsRegistry) ListModules() []RegulationInfo {
|
||||
var result []RegulationInfo
|
||||
for _, m := range r.modules {
|
||||
result = append(result, RegulationInfo{
|
||||
ID: m.ID(),
|
||||
Name: m.Name(),
|
||||
Description: m.Description(),
|
||||
})
|
||||
}
|
||||
// Sort by ID for consistent output
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].ID < result[j].ID
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// EvaluateAll evaluates all registered modules against the given facts
|
||||
func (r *ObligationsRegistry) EvaluateAll(tenantID uuid.UUID, facts *UnifiedFacts, orgName string) *ManagementObligationsOverview {
|
||||
overview := &ManagementObligationsOverview{
|
||||
ID: uuid.New(),
|
||||
TenantID: tenantID,
|
||||
OrganizationName: orgName,
|
||||
AssessmentDate: time.Now(),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
ApplicableRegulations: []ApplicableRegulation{},
|
||||
Obligations: []Obligation{},
|
||||
RequiredControls: []ObligationControl{},
|
||||
IncidentDeadlines: []IncidentDeadline{},
|
||||
}
|
||||
|
||||
// Track aggregated sanctions
|
||||
var maxFine string
|
||||
var personalLiability, criminalLiability bool
|
||||
var affectedRegulations []string
|
||||
|
||||
// Evaluate each module
|
||||
for _, module := range r.modules {
|
||||
if module.IsApplicable(facts) {
|
||||
// Get classification
|
||||
classification := module.GetClassification(facts)
|
||||
|
||||
// Derive obligations
|
||||
obligations := module.DeriveObligations(facts)
|
||||
|
||||
// Derive controls
|
||||
controls := module.DeriveControls(facts)
|
||||
|
||||
// Get incident deadlines
|
||||
incidentDeadlines := module.GetIncidentDeadlines(facts)
|
||||
|
||||
// Add to applicable regulations
|
||||
overview.ApplicableRegulations = append(overview.ApplicableRegulations, ApplicableRegulation{
|
||||
ID: module.ID(),
|
||||
Name: module.Name(),
|
||||
Classification: classification,
|
||||
Reason: r.getApplicabilityReason(module, facts, classification),
|
||||
ObligationCount: len(obligations),
|
||||
ControlCount: len(controls),
|
||||
})
|
||||
|
||||
// Aggregate obligations
|
||||
overview.Obligations = append(overview.Obligations, obligations...)
|
||||
|
||||
// Aggregate controls
|
||||
overview.RequiredControls = append(overview.RequiredControls, controls...)
|
||||
|
||||
// Aggregate incident deadlines
|
||||
overview.IncidentDeadlines = append(overview.IncidentDeadlines, incidentDeadlines...)
|
||||
|
||||
// Track sanctions
|
||||
for _, obl := range obligations {
|
||||
if obl.Sanctions != nil {
|
||||
if obl.Sanctions.MaxFine != "" && (maxFine == "" || len(obl.Sanctions.MaxFine) > len(maxFine)) {
|
||||
maxFine = obl.Sanctions.MaxFine
|
||||
}
|
||||
if obl.Sanctions.PersonalLiability {
|
||||
personalLiability = true
|
||||
}
|
||||
if obl.Sanctions.CriminalLiability {
|
||||
criminalLiability = true
|
||||
}
|
||||
if !containsString(affectedRegulations, module.ID()) {
|
||||
affectedRegulations = append(affectedRegulations, module.ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort obligations by priority and deadline
|
||||
r.sortObligations(overview)
|
||||
|
||||
// Build sanctions summary
|
||||
overview.SanctionsSummary = r.buildSanctionsSummary(maxFine, personalLiability, criminalLiability, affectedRegulations)
|
||||
|
||||
// Generate executive summary
|
||||
overview.ExecutiveSummary = r.generateExecutiveSummary(overview)
|
||||
|
||||
return overview
|
||||
}
|
||||
|
||||
// EvaluateSingle evaluates a single module against the given facts
|
||||
func (r *ObligationsRegistry) EvaluateSingle(moduleID string, facts *UnifiedFacts) (*ManagementObligationsOverview, error) {
|
||||
module, ok := r.modules[moduleID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("module not found: %s", moduleID)
|
||||
}
|
||||
|
||||
overview := &ManagementObligationsOverview{
|
||||
ID: uuid.New(),
|
||||
AssessmentDate: time.Now(),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
ApplicableRegulations: []ApplicableRegulation{},
|
||||
Obligations: []Obligation{},
|
||||
RequiredControls: []ObligationControl{},
|
||||
IncidentDeadlines: []IncidentDeadline{},
|
||||
}
|
||||
|
||||
if !module.IsApplicable(facts) {
|
||||
return overview, nil
|
||||
}
|
||||
|
||||
classification := module.GetClassification(facts)
|
||||
obligations := module.DeriveObligations(facts)
|
||||
controls := module.DeriveControls(facts)
|
||||
incidentDeadlines := module.GetIncidentDeadlines(facts)
|
||||
|
||||
overview.ApplicableRegulations = append(overview.ApplicableRegulations, ApplicableRegulation{
|
||||
ID: module.ID(),
|
||||
Name: module.Name(),
|
||||
Classification: classification,
|
||||
Reason: r.getApplicabilityReason(module, facts, classification),
|
||||
ObligationCount: len(obligations),
|
||||
ControlCount: len(controls),
|
||||
})
|
||||
|
||||
overview.Obligations = obligations
|
||||
overview.RequiredControls = controls
|
||||
overview.IncidentDeadlines = incidentDeadlines
|
||||
|
||||
r.sortObligations(overview)
|
||||
overview.ExecutiveSummary = r.generateExecutiveSummary(overview)
|
||||
|
||||
return overview, nil
|
||||
}
|
||||
|
||||
// GetDecisionTree returns the decision tree for a specific module
|
||||
func (r *ObligationsRegistry) GetDecisionTree(moduleID string) (*DecisionTree, error) {
|
||||
module, ok := r.modules[moduleID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("module not found: %s", moduleID)
|
||||
}
|
||||
tree := module.GetDecisionTree()
|
||||
if tree == nil {
|
||||
return nil, fmt.Errorf("module %s does not have a decision tree", moduleID)
|
||||
}
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Methods
|
||||
// ============================================================================
|
||||
|
||||
func (r *ObligationsRegistry) getApplicabilityReason(module RegulationModule, facts *UnifiedFacts, classification string) string {
|
||||
switch module.ID() {
|
||||
case "nis2":
|
||||
if classification == string(NIS2EssentialEntity) {
|
||||
return "Besonders wichtige Einrichtung aufgrund von Sektor und Größe"
|
||||
} else if classification == string(NIS2ImportantEntity) {
|
||||
return "Wichtige Einrichtung aufgrund von Sektor und Größe"
|
||||
}
|
||||
return "NIS2-Richtlinie anwendbar"
|
||||
case "dsgvo":
|
||||
return "Verarbeitung personenbezogener Daten"
|
||||
case "ai_act":
|
||||
return "Einsatz von KI-Systemen"
|
||||
case "dora":
|
||||
return "Reguliertes Finanzunternehmen"
|
||||
default:
|
||||
return "Regulierung anwendbar"
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ObligationsRegistry) sortObligations(overview *ManagementObligationsOverview) {
|
||||
// Sort by priority (critical first), then by deadline
|
||||
priorityOrder := map[ObligationPriority]int{
|
||||
PriorityCritical: 0,
|
||||
PriorityHigh: 1,
|
||||
PriorityMedium: 2,
|
||||
PriorityLow: 3,
|
||||
}
|
||||
|
||||
sort.Slice(overview.Obligations, func(i, j int) bool {
|
||||
// First by priority
|
||||
pi := priorityOrder[overview.Obligations[i].Priority]
|
||||
pj := priorityOrder[overview.Obligations[j].Priority]
|
||||
if pi != pj {
|
||||
return pi < pj
|
||||
}
|
||||
|
||||
// Then by deadline (earlier first, nil last)
|
||||
di := overview.Obligations[i].Deadline
|
||||
dj := overview.Obligations[j].Deadline
|
||||
|
||||
if di == nil && dj == nil {
|
||||
return false
|
||||
}
|
||||
if di == nil {
|
||||
return false
|
||||
}
|
||||
if dj == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// For absolute deadlines, compare dates
|
||||
if di.Type == DeadlineAbsolute && dj.Type == DeadlineAbsolute {
|
||||
if di.Date != nil && dj.Date != nil {
|
||||
return di.Date.Before(*dj.Date)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func (r *ObligationsRegistry) buildSanctionsSummary(maxFine string, personal, criminal bool, affected []string) SanctionsSummary {
|
||||
var summary string
|
||||
if personal && criminal {
|
||||
summary = "Hohe Bußgelder möglich. Persönliche Haftung der Geschäftsführung sowie strafrechtliche Konsequenzen bei Verstößen."
|
||||
} else if personal {
|
||||
summary = "Hohe Bußgelder möglich. Persönliche Haftung der Geschäftsführung bei Verstößen."
|
||||
} else if maxFine != "" {
|
||||
summary = fmt.Sprintf("Bußgelder bis zu %s bei Verstößen möglich.", maxFine)
|
||||
} else {
|
||||
summary = "Keine spezifischen Sanktionen identifiziert."
|
||||
}
|
||||
|
||||
return SanctionsSummary{
|
||||
MaxFinancialRisk: maxFine,
|
||||
PersonalLiabilityRisk: personal,
|
||||
CriminalLiabilityRisk: criminal,
|
||||
AffectedRegulations: affected,
|
||||
Summary: summary,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ObligationsRegistry) generateExecutiveSummary(overview *ManagementObligationsOverview) ExecutiveSummary {
|
||||
summary := ExecutiveSummary{
|
||||
TotalRegulations: len(overview.ApplicableRegulations),
|
||||
TotalObligations: len(overview.Obligations),
|
||||
CriticalObligations: 0,
|
||||
UpcomingDeadlines: 0,
|
||||
OverdueObligations: 0,
|
||||
KeyRisks: []string{},
|
||||
RecommendedActions: []string{},
|
||||
ComplianceScore: 100, // Start at 100, deduct for gaps
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
thirtyDaysFromNow := now.AddDate(0, 0, 30)
|
||||
|
||||
for _, obl := range overview.Obligations {
|
||||
// Count critical
|
||||
if obl.Priority == PriorityCritical {
|
||||
summary.CriticalObligations++
|
||||
summary.ComplianceScore -= 10
|
||||
}
|
||||
|
||||
// Count deadlines
|
||||
if obl.Deadline != nil && obl.Deadline.Type == DeadlineAbsolute && obl.Deadline.Date != nil {
|
||||
if obl.Deadline.Date.Before(now) {
|
||||
summary.OverdueObligations++
|
||||
summary.ComplianceScore -= 15
|
||||
} else if obl.Deadline.Date.Before(thirtyDaysFromNow) {
|
||||
summary.UpcomingDeadlines++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure score doesn't go below 0
|
||||
if summary.ComplianceScore < 0 {
|
||||
summary.ComplianceScore = 0
|
||||
}
|
||||
|
||||
// Add key risks
|
||||
if summary.CriticalObligations > 0 {
|
||||
summary.KeyRisks = append(summary.KeyRisks, fmt.Sprintf("%d kritische Pflichten erfordern sofortige Aufmerksamkeit", summary.CriticalObligations))
|
||||
}
|
||||
if summary.OverdueObligations > 0 {
|
||||
summary.KeyRisks = append(summary.KeyRisks, fmt.Sprintf("%d Pflichten haben überfällige Fristen", summary.OverdueObligations))
|
||||
}
|
||||
if overview.SanctionsSummary.PersonalLiabilityRisk {
|
||||
summary.KeyRisks = append(summary.KeyRisks, "Persönliche Haftungsrisiken für die Geschäftsführung bestehen")
|
||||
}
|
||||
|
||||
// Add recommended actions
|
||||
if summary.OverdueObligations > 0 {
|
||||
summary.RecommendedActions = append(summary.RecommendedActions, "Überfällige Pflichten priorisieren und umgehend adressieren")
|
||||
}
|
||||
if summary.CriticalObligations > 0 {
|
||||
summary.RecommendedActions = append(summary.RecommendedActions, "Kritische Pflichten in der nächsten Vorstandssitzung besprechen")
|
||||
}
|
||||
if len(overview.IncidentDeadlines) > 0 {
|
||||
summary.RecommendedActions = append(summary.RecommendedActions, "Incident-Response-Prozesse gemäß Meldefristen etablieren")
|
||||
}
|
||||
|
||||
// Default action if no specific risks
|
||||
if len(summary.RecommendedActions) == 0 {
|
||||
summary.RecommendedActions = append(summary.RecommendedActions, "Regelmäßige Compliance-Reviews durchführen")
|
||||
}
|
||||
|
||||
// Set next review date (3 months from now)
|
||||
nextReview := now.AddDate(0, 3, 0)
|
||||
summary.NextReviewDate = &nextReview
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
// containsString checks if a slice contains a string
|
||||
func containsString(slice []string, s string) bool {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Grouping Methods
|
||||
// ============================================================================
|
||||
|
||||
// GroupByRegulation groups obligations by their regulation ID
|
||||
func (r *ObligationsRegistry) GroupByRegulation(obligations []Obligation) map[string][]Obligation {
|
||||
result := make(map[string][]Obligation)
|
||||
for _, obl := range obligations {
|
||||
result[obl.RegulationID] = append(result[obl.RegulationID], obl)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GroupByDeadline groups obligations by deadline timeframe
|
||||
func (r *ObligationsRegistry) GroupByDeadline(obligations []Obligation) ObligationsByDeadlineResponse {
|
||||
result := ObligationsByDeadlineResponse{
|
||||
Overdue: []Obligation{},
|
||||
ThisWeek: []Obligation{},
|
||||
ThisMonth: []Obligation{},
|
||||
NextQuarter: []Obligation{},
|
||||
Later: []Obligation{},
|
||||
NoDeadline: []Obligation{},
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
oneWeek := now.AddDate(0, 0, 7)
|
||||
oneMonth := now.AddDate(0, 1, 0)
|
||||
threeMonths := now.AddDate(0, 3, 0)
|
||||
|
||||
for _, obl := range obligations {
|
||||
if obl.Deadline == nil || obl.Deadline.Type != DeadlineAbsolute || obl.Deadline.Date == nil {
|
||||
result.NoDeadline = append(result.NoDeadline, obl)
|
||||
continue
|
||||
}
|
||||
|
||||
deadline := *obl.Deadline.Date
|
||||
switch {
|
||||
case deadline.Before(now):
|
||||
result.Overdue = append(result.Overdue, obl)
|
||||
case deadline.Before(oneWeek):
|
||||
result.ThisWeek = append(result.ThisWeek, obl)
|
||||
case deadline.Before(oneMonth):
|
||||
result.ThisMonth = append(result.ThisMonth, obl)
|
||||
case deadline.Before(threeMonths):
|
||||
result.NextQuarter = append(result.NextQuarter, obl)
|
||||
default:
|
||||
result.Later = append(result.Later, obl)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GroupByResponsible groups obligations by responsible role
|
||||
func (r *ObligationsRegistry) GroupByResponsible(obligations []Obligation) map[ResponsibleRole][]Obligation {
|
||||
result := make(map[ResponsibleRole][]Obligation)
|
||||
for _, obl := range obligations {
|
||||
result[obl.Responsible] = append(result[obl.Responsible], obl)
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user