package ucca import ( "sort" "strings" ) // Evaluate runs all YAML rules against the intake func (e *PolicyEngine) 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, } hasBlock := false hasWarn := false controlSet := make(map[string]bool) patternPriority := make(map[string]int) triggeredRuleIDs := make(map[string]bool) needsEscalation := false priority := 1 for _, rule := range e.config.Rules { if rule.Condition.Aggregate != "" { continue } if e.evaluateCondition(&rule.Condition, intake) { triggeredRuleIDs[rule.ID] = true triggered := TriggeredRule{ Code: rule.ID, Category: rule.Category, Title: rule.Title, Description: rule.Description, Severity: parseSeverity(rule.Severity), ScoreDelta: rule.Effect.RiskAdd, GDPRRef: rule.GDPRRef, Rationale: rule.Rationale, } result.TriggeredRules = append(result.TriggeredRules, triggered) result.RiskScore += rule.Effect.RiskAdd switch parseSeverity(rule.Severity) { case SeverityBLOCK: hasBlock = true case SeverityWARN: hasWarn = true } if rule.Effect.Feasibility != "" { switch rule.Effect.Feasibility { case "NO": result.Feasibility = FeasibilityNO case "CONDITIONAL": if result.Feasibility != FeasibilityNO { result.Feasibility = FeasibilityCONDITIONAL } case "YES": if result.Feasibility != FeasibilityNO && result.Feasibility != FeasibilityCONDITIONAL { result.Feasibility = FeasibilityYES } } } for _, ctrlID := range rule.Effect.ControlsAdd { if !controlSet[ctrlID] { controlSet[ctrlID] = true if ctrl, ok := e.config.Controls[ctrlID]; ok { result.RequiredControls = append(result.RequiredControls, RequiredControl{ ID: ctrl.ID, Title: ctrl.Title, Description: ctrl.Description, Severity: parseSeverity(rule.Severity), Category: categorizeControl(ctrl.ID), GDPRRef: ctrl.GDPRRef, }) } } } for _, patternID := range rule.Effect.SuggestedPatterns { if _, exists := patternPriority[patternID]; !exists { patternPriority[patternID] = priority priority++ } } if rule.Effect.Escalation { needsEscalation = true } if rule.Effect.Art22Risk { result.Art22Risk = true } } } if hasBlock { result.Feasibility = FeasibilityNO } else if hasWarn && result.Feasibility != FeasibilityNO { result.Feasibility = FeasibilityCONDITIONAL } result.RiskLevel = e.calculateRiskLevel(result.RiskScore) result.Complexity = e.calculateComplexity(result) result.DSFARecommended = e.shouldRecommendDSFA(intake, result) result.TrainingAllowed = e.determineTrainingAllowed(intake) result.RecommendedArchitecture = e.buildPatternRecommendations(patternPriority) result.ExampleMatches = MatchExamples(intake) result.Summary = e.generateSummary(result, intake) result.Recommendation = e.generateRecommendation(result, intake) if result.Feasibility == FeasibilityNO { result.AlternativeApproach = e.generateAlternative(result, intake, triggeredRuleIDs) } _ = needsEscalation return result } // evaluateCondition recursively evaluates a condition against the intake func (e *PolicyEngine) evaluateCondition(cond *ConditionDef, intake *UseCaseIntake) bool { if len(cond.AllOf) > 0 { for _, subCond := range cond.AllOf { if !e.evaluateCondition(&subCond, intake) { return false } } return true } if len(cond.AnyOf) > 0 { for _, subCond := range cond.AnyOf { if e.evaluateCondition(&subCond, intake) { return true } } return false } if cond.Field != "" { return e.evaluateFieldCondition(cond.Field, cond.Operator, cond.Value, intake) } return false } // evaluateFieldCondition evaluates a single field comparison func (e *PolicyEngine) evaluateFieldCondition(field, operator string, value interface{}, intake *UseCaseIntake) bool { fieldValue := e.getFieldValue(field, intake) if fieldValue == nil { return false } switch operator { case "equals": return e.compareEquals(fieldValue, value) case "not_equals": return !e.compareEquals(fieldValue, value) case "in": return e.compareIn(fieldValue, value) case "contains": return e.compareContains(fieldValue, value) default: return false } } // getFieldValue extracts a field value from the intake using dot notation func (e *PolicyEngine) getFieldValue(field string, intake *UseCaseIntake) interface{} { parts := strings.Split(field, ".") if len(parts) == 0 { return nil } switch parts[0] { case "data_types": if len(parts) < 2 { return nil } return e.getDataTypeValue(parts[1], intake) case "purpose": if len(parts) < 2 { return nil } return e.getPurposeValue(parts[1], intake) case "automation": return string(intake.Automation) case "outputs": if len(parts) < 2 { return nil } return e.getOutputsValue(parts[1], intake) case "hosting": if len(parts) < 2 { return nil } return e.getHostingValue(parts[1], intake) case "model_usage": if len(parts) < 2 { return nil } return e.getModelUsageValue(parts[1], intake) case "domain": return string(intake.Domain) case "retention": if len(parts) < 2 { return nil } return e.getRetentionValue(parts[1], intake) } return nil } func (e *PolicyEngine) getDataTypeValue(field string, intake *UseCaseIntake) interface{} { switch field { case "personal_data": return intake.DataTypes.PersonalData case "article_9_data": return intake.DataTypes.Article9Data case "minor_data": return intake.DataTypes.MinorData case "license_plates": return intake.DataTypes.LicensePlates case "images": return intake.DataTypes.Images case "audio": return intake.DataTypes.Audio case "location_data": return intake.DataTypes.LocationData case "biometric_data": return intake.DataTypes.BiometricData case "financial_data": return intake.DataTypes.FinancialData case "employee_data": return intake.DataTypes.EmployeeData case "customer_data": return intake.DataTypes.CustomerData case "public_data": return intake.DataTypes.PublicData } return nil } func (e *PolicyEngine) getPurposeValue(field string, intake *UseCaseIntake) interface{} { switch field { case "customer_support": return intake.Purpose.CustomerSupport case "marketing": return intake.Purpose.Marketing case "analytics": return intake.Purpose.Analytics case "automation": return intake.Purpose.Automation case "evaluation_scoring": return intake.Purpose.EvaluationScoring case "decision_making": return intake.Purpose.DecisionMaking case "profiling": return intake.Purpose.Profiling case "research": return intake.Purpose.Research case "internal_tools": return intake.Purpose.InternalTools case "public_service": return intake.Purpose.PublicService } return nil } func (e *PolicyEngine) getOutputsValue(field string, intake *UseCaseIntake) interface{} { switch field { case "recommendations_to_users": return intake.Outputs.RecommendationsToUsers case "rankings_or_scores": return intake.Outputs.RankingsOrScores case "legal_effects": return intake.Outputs.LegalEffects case "access_decisions": return intake.Outputs.AccessDecisions case "content_generation": return intake.Outputs.ContentGeneration case "data_export": return intake.Outputs.DataExport } return nil } func (e *PolicyEngine) getHostingValue(field string, intake *UseCaseIntake) interface{} { switch field { case "provider": return intake.Hosting.Provider case "region": return intake.Hosting.Region case "data_residency": return intake.Hosting.DataResidency } return nil } func (e *PolicyEngine) getModelUsageValue(field string, intake *UseCaseIntake) interface{} { switch field { case "rag": return intake.ModelUsage.RAG case "finetune": return intake.ModelUsage.Finetune case "training": return intake.ModelUsage.Training case "inference": return intake.ModelUsage.Inference } return nil } func (e *PolicyEngine) getRetentionValue(field string, intake *UseCaseIntake) interface{} { switch field { case "store_prompts": return intake.Retention.StorePrompts case "store_responses": return intake.Retention.StoreResponses case "retention_days": return intake.Retention.RetentionDays case "anonymize_after_use": return intake.Retention.AnonymizeAfterUse } return nil } func (e *PolicyEngine) compareEquals(fieldValue, expected interface{}) bool { if bv, ok := fieldValue.(bool); ok { if eb, ok := expected.(bool); ok { return bv == eb } } if sv, ok := fieldValue.(string); ok { if es, ok := expected.(string); ok { return sv == es } } if iv, ok := fieldValue.(int); ok { switch ev := expected.(type) { case int: return iv == ev case float64: return iv == int(ev) } } return false } func (e *PolicyEngine) compareIn(fieldValue, expected interface{}) bool { list, ok := expected.([]interface{}) if !ok { return false } sv, ok := fieldValue.(string) if !ok { return false } for _, item := range list { if is, ok := item.(string); ok && is == sv { return true } } return false } func (e *PolicyEngine) compareContains(fieldValue, expected interface{}) bool { sv, ok := fieldValue.(string) if !ok { return false } es, ok := expected.(string) if !ok { return false } return strings.Contains(strings.ToLower(sv), strings.ToLower(es)) } func (e *PolicyEngine) calculateRiskLevel(score int) RiskLevel { t := e.config.Thresholds.Risk if score >= t.Unacceptable { return RiskLevelUNACCEPTABLE } if score >= t.High { return RiskLevelHIGH } if score >= t.Medium { return RiskLevelMEDIUM } if score >= t.Low { return RiskLevelLOW } return RiskLevelMINIMAL } func (e *PolicyEngine) calculateComplexity(result *AssessmentResult) Complexity { controlCount := len(result.RequiredControls) if controlCount >= 5 || result.RiskScore >= 50 { return ComplexityHIGH } if controlCount >= 3 || result.RiskScore >= 25 { return ComplexityMEDIUM } return ComplexityLOW } func (e *PolicyEngine) shouldRecommendDSFA(intake *UseCaseIntake, result *AssessmentResult) bool { if result.RiskLevel == RiskLevelHIGH || result.RiskLevel == RiskLevelUNACCEPTABLE { return true } if intake.DataTypes.Article9Data || intake.DataTypes.BiometricData { return true } if intake.Purpose.Profiling && intake.DataTypes.PersonalData { return true } for _, ctrl := range result.RequiredControls { if ctrl.ID == "C_DSFA" { return true } } return false } func (e *PolicyEngine) determineTrainingAllowed(intake *UseCaseIntake) TrainingAllowed { if intake.ModelUsage.Training && intake.DataTypes.PersonalData { return TrainingNO } if intake.ModelUsage.Finetune && intake.DataTypes.PersonalData { return TrainingCONDITIONAL } if intake.DataTypes.MinorData && (intake.ModelUsage.Training || intake.ModelUsage.Finetune) { return TrainingNO } return TrainingYES } func (e *PolicyEngine) buildPatternRecommendations(patternPriority map[string]int) []PatternRecommendation { type priorityPair struct { id string priority int } pairs := make([]priorityPair, 0, len(patternPriority)) for id, p := range patternPriority { pairs = append(pairs, priorityPair{id, p}) } sort.Slice(pairs, func(i, j int) bool { return pairs[i].priority < pairs[j].priority }) recommendations := make([]PatternRecommendation, 0, len(pairs)) for _, p := range pairs { if pattern, ok := e.config.Patterns[p.id]; ok { recommendations = append(recommendations, PatternRecommendation{ PatternID: pattern.ID, Title: pattern.Title, Description: pattern.Description, Rationale: pattern.Benefit, Priority: p.priority, }) } } return recommendations }