package ucca import ( "fmt" "strings" ) // ============================================================================ // Rule Engine - Deterministic rule evaluation // ============================================================================ // Rule represents a single evaluation rule type Rule struct { Code string Category string Title string TitleDE string Description string DescriptionDE string Severity Severity ScoreDelta int // Points added to risk score GDPRRef string // GDPR article reference Controls []string // Required control IDs Patterns []string // Recommended pattern IDs Condition func(intake *UseCaseIntake) bool Rationale func(intake *UseCaseIntake) string } // RuleEngine holds all rules and performs evaluation type RuleEngine struct { rules []Rule } // NewRuleEngine creates a new rule engine with all rules func NewRuleEngine() *RuleEngine { return &RuleEngine{ rules: AllRules, } } // Evaluate runs all rules against the intake and returns the assessment result func (e *RuleEngine) Evaluate(intake *UseCaseIntake) *AssessmentResult { result := &AssessmentResult{ Feasibility: FeasibilityYES, RiskLevel: RiskLevelMINIMAL, Complexity: ComplexityLOW, RiskScore: 0, TriggeredRules: []TriggeredRule{}, RequiredControls: []RequiredControl{}, RecommendedArchitecture: []PatternRecommendation{}, ForbiddenPatterns: []ForbiddenPattern{}, ExampleMatches: []ExampleMatch{}, DSFARecommended: false, Art22Risk: false, TrainingAllowed: TrainingYES, } // Track triggered severities hasBlock := false hasWarn := false controlMap := make(map[string]bool) patternMap := make(map[string]int) // pattern -> priority // Evaluate each rule for _, rule := range e.rules { if rule.Condition(intake) { // Add triggered rule triggered := TriggeredRule{ Code: rule.Code, Category: rule.Category, Title: rule.TitleDE, Description: rule.DescriptionDE, Severity: rule.Severity, ScoreDelta: rule.ScoreDelta, GDPRRef: rule.GDPRRef, Rationale: rule.Rationale(intake), } result.TriggeredRules = append(result.TriggeredRules, triggered) // Update risk score result.RiskScore += rule.ScoreDelta // Track severity switch rule.Severity { case SeverityBLOCK: hasBlock = true case SeverityWARN: hasWarn = true } // Collect required controls for _, controlID := range rule.Controls { if !controlMap[controlID] { controlMap[controlID] = true if ctrl := GetControlByID(controlID); ctrl != nil { result.RequiredControls = append(result.RequiredControls, *ctrl) } } } // Collect recommended patterns for i, patternID := range rule.Patterns { if _, exists := patternMap[patternID]; !exists { patternMap[patternID] = i + 1 // priority } } } } // Determine feasibility based on aggregation rules (R-090 to R-092) if hasBlock { result.Feasibility = FeasibilityNO } else if hasWarn { result.Feasibility = FeasibilityCONDITIONAL } else { result.Feasibility = FeasibilityYES } // Determine risk level based on score if result.RiskScore >= 80 { result.RiskLevel = RiskLevelUNACCEPTABLE } else if result.RiskScore >= 60 { result.RiskLevel = RiskLevelHIGH } else if result.RiskScore >= 40 { result.RiskLevel = RiskLevelMEDIUM } else if result.RiskScore >= 20 { result.RiskLevel = RiskLevelLOW } else { result.RiskLevel = RiskLevelMINIMAL } // Determine complexity if len(result.RequiredControls) >= 5 || result.RiskScore >= 50 { result.Complexity = ComplexityHIGH } else if len(result.RequiredControls) >= 3 || result.RiskScore >= 25 { result.Complexity = ComplexityMEDIUM } else { result.Complexity = ComplexityLOW } // Check DSFA recommendation if result.RiskLevel == RiskLevelHIGH || result.RiskLevel == RiskLevelUNACCEPTABLE || intake.DataTypes.Article9Data || intake.DataTypes.BiometricData || (intake.Purpose.Profiling && intake.DataTypes.PersonalData) { result.DSFARecommended = true } // Check Art. 22 risk if intake.Automation == AutomationFullyAutomated && (intake.Outputs.LegalEffects || intake.Outputs.RankingsOrScores || intake.Purpose.EvaluationScoring) { result.Art22Risk = true } // Determine training allowed if intake.ModelUsage.Training && intake.DataTypes.PersonalData { result.TrainingAllowed = TrainingNO } else if intake.ModelUsage.Finetune && intake.DataTypes.PersonalData { result.TrainingAllowed = TrainingCONDITIONAL } // Add recommended architecture patterns for patternID, priority := range patternMap { if p := GetPatternByID(patternID); p != nil { result.RecommendedArchitecture = append(result.RecommendedArchitecture, PatternToRecommendation(*p, "Empfohlen basierend auf ausgelösten Regeln", priority)) } } // Add applicable patterns not yet recommended applicable := GetApplicablePatterns(intake) for _, p := range applicable { found := false for _, rec := range result.RecommendedArchitecture { if rec.PatternID == p.ID { found = true break } } if !found { result.RecommendedArchitecture = append(result.RecommendedArchitecture, PatternToRecommendation(p, "Anwendbar für Ihren Use Case", len(result.RecommendedArchitecture)+1)) } } // Add forbidden patterns result.ForbiddenPatterns = GetForbiddenPatterns(intake) // Add matching examples result.ExampleMatches = MatchExamples(intake) // Generate summary result.Summary = generateSummary(result, intake) result.Recommendation = generateRecommendation(result, intake) if result.Feasibility == FeasibilityNO { result.AlternativeApproach = generateAlternative(result, intake) } return result } // generateSummary creates a human-readable summary func generateSummary(result *AssessmentResult, intake *UseCaseIntake) string { var parts []string switch result.Feasibility { case FeasibilityYES: parts = append(parts, "Der Use Case ist aus DSGVO-Sicht grundsätzlich umsetzbar.") case FeasibilityCONDITIONAL: parts = append(parts, "Der Use Case ist unter Auflagen umsetzbar.") case FeasibilityNO: parts = append(parts, "Der Use Case ist in der aktuellen Form nicht DSGVO-konform umsetzbar.") } if len(result.TriggeredRules) > 0 { blockCount := 0 warnCount := 0 for _, r := range result.TriggeredRules { if r.Severity == SeverityBLOCK { blockCount++ } else if r.Severity == SeverityWARN { warnCount++ } } if blockCount > 0 { parts = append(parts, fmt.Sprintf("%d kritische Regelverletzung(en) identifiziert.", blockCount)) } if warnCount > 0 { parts = append(parts, fmt.Sprintf("%d Warnungen erfordern Aufmerksamkeit.", warnCount)) } } if result.DSFARecommended { parts = append(parts, "Eine Datenschutz-Folgenabschätzung (DSFA) wird empfohlen.") } return strings.Join(parts, " ") } // generateRecommendation creates actionable recommendations func generateRecommendation(result *AssessmentResult, intake *UseCaseIntake) string { if result.Feasibility == FeasibilityYES { return "Fahren Sie mit der Implementierung fort. Beachten Sie die empfohlenen Architektur-Patterns für optimale DSGVO-Konformität." } if result.Feasibility == FeasibilityCONDITIONAL { if len(result.RequiredControls) > 0 { return fmt.Sprintf("Implementieren Sie die %d erforderlichen Kontrollen vor dem Go-Live. Dokumentieren Sie alle Maßnahmen für den Nachweis der Rechenschaftspflicht (Art. 5 DSGVO).", len(result.RequiredControls)) } return "Prüfen Sie die ausgelösten Warnungen und implementieren Sie entsprechende Schutzmaßnahmen." } // FeasibilityNO return "Der Use Case erfordert grundlegende Änderungen. Prüfen Sie die Alternative-Ansatz-Empfehlung." } // generateAlternative creates alternative approach suggestions func generateAlternative(result *AssessmentResult, intake *UseCaseIntake) string { var suggestions []string // Check specific blocking reasons if intake.ModelUsage.Training && intake.DataTypes.PersonalData { suggestions = append(suggestions, "Nutzen Sie nur RAG statt Training mit personenbezogenen Daten") } if intake.Automation == AutomationFullyAutomated && intake.Outputs.LegalEffects { suggestions = append(suggestions, "Implementieren Sie Human-in-the-Loop für Entscheidungen mit rechtlichen Auswirkungen") } if intake.DataTypes.MinorData && intake.Purpose.EvaluationScoring { suggestions = append(suggestions, "Verzichten Sie auf automatisches Scoring von Minderjährigen - nutzen Sie KI nur zur Unterstützung menschlicher Entscheidungsträger") } if intake.Hosting.Region == "third_country" && intake.DataTypes.PersonalData { suggestions = append(suggestions, "Hosten Sie innerhalb der EU oder implementieren Sie Standardvertragsklauseln (SCCs)") } if len(suggestions) == 0 { return "Überarbeiten Sie den Use Case unter Berücksichtigung der ausgelösten Regeln." } return strings.Join(suggestions, ". ") + "." } // GetRules returns all rules func (e *RuleEngine) GetRules() []Rule { return e.rules }