package ucca import ( "fmt" "os" "path/filepath" "strings" "gopkg.in/yaml.v3" ) // ============================================================================ // Financial Regulations Policy Engine // ============================================================================ // // Evaluates financial use-cases against DORA, MaRisk, and BAIT rules. // // Split into: // - financial_policy_types.go — all struct/type definitions and result types // - financial_policy.go — engine implementation (this file) // // ============================================================================ // FinancialPolicyEngine evaluates intakes against financial regulations type FinancialPolicyEngine struct { config *FinancialPolicyConfig } // NewFinancialPolicyEngine creates a new financial policy engine func NewFinancialPolicyEngine() (*FinancialPolicyEngine, error) { searchPaths := []string{ DefaultFinancialPolicyPath, filepath.Join(".", "policies", "financial_regulations_policy.yaml"), filepath.Join("..", "policies", "financial_regulations_policy.yaml"), filepath.Join("..", "..", "policies", "financial_regulations_policy.yaml"), "/app/policies/financial_regulations_policy.yaml", } var data []byte var err error for _, path := range searchPaths { data, err = os.ReadFile(path) if err == nil { break } } if err != nil { return nil, fmt.Errorf("failed to load financial policy from any known location: %w", err) } var config FinancialPolicyConfig if err := yaml.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("failed to parse financial policy YAML: %w", err) } return &FinancialPolicyEngine{config: &config}, nil } // NewFinancialPolicyEngineFromPath loads policy from a specific file path func NewFinancialPolicyEngineFromPath(path string) (*FinancialPolicyEngine, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read financial policy file: %w", err) } var config FinancialPolicyConfig if err := yaml.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("failed to parse financial policy YAML: %w", err) } return &FinancialPolicyEngine{config: &config}, nil } // GetPolicyVersion returns the financial policy version func (e *FinancialPolicyEngine) GetPolicyVersion() string { return e.config.Metadata.Version } // IsApplicable checks if the financial policy applies to the given intake func (e *FinancialPolicyEngine) IsApplicable(intake *UseCaseIntake) bool { domain := strings.ToLower(string(intake.Domain)) for _, d := range e.config.ApplicableDomains { if domain == d { return true } } return false } // Evaluate runs financial regulation rules against the intake func (e *FinancialPolicyEngine) Evaluate(intake *UseCaseIntake) *FinancialAssessmentResult { result := &FinancialAssessmentResult{ IsApplicable: e.IsApplicable(intake), Feasibility: FeasibilityYES, RiskScore: 0, TriggeredRules: []FinancialTriggeredRule{}, RequiredControls: []FinancialRequiredControl{}, IdentifiedGaps: []FinancialIdentifiedGap{}, StopLinesHit: []FinancialStopLineHit{}, EscalationLevel: "", PolicyVersion: e.config.Metadata.Version, } if !result.IsApplicable { return result } if intake.FinancialContext == nil { result.MissingContext = true return result } hasBlock := false controlSet := make(map[string]bool) needsEscalation := "" for _, rule := range e.config.Rules { if e.evaluateCondition(&rule.Condition, intake) { triggered := FinancialTriggeredRule{ Code: rule.ID, Category: rule.Category, Title: rule.Title, Description: rule.Description, Severity: parseSeverity(rule.Severity), ScoreDelta: rule.Effect.RiskAdd, Rationale: rule.Rationale, DORARef: rule.DORARef, MaRiskRef: rule.MaRiskRef, BAITRef: rule.BAITRef, MiFIDRef: rule.MiFIDRef, } result.TriggeredRules = append(result.TriggeredRules, triggered) result.RiskScore += rule.Effect.RiskAdd if parseSeverity(rule.Severity) == SeverityBLOCK { hasBlock = true } if rule.Effect.Feasibility != "" { switch rule.Effect.Feasibility { case "NO": result.Feasibility = FeasibilityNO case "CONDITIONAL": if result.Feasibility != FeasibilityNO { result.Feasibility = FeasibilityCONDITIONAL } } } 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, FinancialRequiredControl{ ID: ctrl.ID, Title: ctrl.Title, Category: ctrl.Category, Description: ctrl.Description, WhatToDo: ctrl.WhatToDo, EvidenceNeeded: ctrl.EvidenceNeeded, Effort: ctrl.Effort, DORARef: ctrl.DORARef, MaRiskRef: ctrl.MaRiskRef, BAITRef: ctrl.BAITRef, }) } } } if rule.Effect.Escalation { needsEscalation = e.determineEscalationLevel(intake) } } } for _, stopLine := range e.config.StopLines { if e.evaluateStopLineConditions(stopLine.When, intake) { result.StopLinesHit = append(result.StopLinesHit, FinancialStopLineHit{ ID: stopLine.ID, Title: stopLine.Title, Message: stopLine.Message, Outcome: stopLine.Outcome, }) result.Feasibility = FeasibilityNO hasBlock = true } } for _, gap := range e.config.Gaps { if e.evaluateGapConditions(gap.When, intake) { result.IdentifiedGaps = append(result.IdentifiedGaps, FinancialIdentifiedGap{ ID: gap.ID, Title: gap.Title, Description: gap.Description, Severity: parseSeverity(gap.Severity), Controls: gap.Controls, LegalRefs: gap.LegalRefs, }) if gap.Escalation != "" && needsEscalation == "" { needsEscalation = gap.Escalation } } } if hasBlock { result.Feasibility = FeasibilityNO } result.EscalationLevel = needsEscalation result.Summary = e.generateSummary(result) return result } func (e *FinancialPolicyEngine) evaluateCondition(cond *FinancialConditionDef, 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 } func (e *FinancialPolicyEngine) 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) default: return false } } func (e *FinancialPolicyEngine) getFieldValue(field string, intake *UseCaseIntake) interface{} { parts := strings.Split(field, ".") if len(parts) == 0 { return nil } switch parts[0] { case "domain": return strings.ToLower(string(intake.Domain)) case "financial_entity": if len(parts) < 2 || intake.FinancialContext == nil { return nil } return e.getFinancialEntityValue(parts[1], intake.FinancialContext) case "ict_service": if len(parts) < 2 || intake.FinancialContext == nil { return nil } return e.getICTServiceValue(parts[1], intake.FinancialContext) case "ai_application": if len(parts) < 2 || intake.FinancialContext == nil { return nil } return e.getAIApplicationValue(parts[1], intake.FinancialContext) case "model_usage": if len(parts) < 2 { return nil } switch parts[1] { case "training": return intake.ModelUsage.Training case "finetune": return intake.ModelUsage.Finetune case "rag": return intake.ModelUsage.RAG } } return nil } func (e *FinancialPolicyEngine) getFinancialEntityValue(field string, ctx *FinancialContext) interface{} { switch field { case "type": return string(ctx.FinancialEntity.Type) case "regulated": return ctx.FinancialEntity.Regulated case "size_category": return string(ctx.FinancialEntity.SizeCategory) } return nil } func (e *FinancialPolicyEngine) getICTServiceValue(field string, ctx *FinancialContext) interface{} { switch field { case "is_critical": return ctx.ICTService.IsCritical case "is_outsourced": return ctx.ICTService.IsOutsourced case "provider_location": return string(ctx.ICTService.ProviderLocation) case "concentration_risk": return ctx.ICTService.ConcentrationRisk } return nil } func (e *FinancialPolicyEngine) getAIApplicationValue(field string, ctx *FinancialContext) interface{} { switch field { case "affects_customer_decisions": return ctx.AIApplication.AffectsCustomerDecisions case "algorithmic_trading": return ctx.AIApplication.AlgorithmicTrading case "risk_assessment": return ctx.AIApplication.RiskAssessment case "aml_kyc": return ctx.AIApplication.AMLKYC case "model_validation_done": return ctx.AIApplication.ModelValidationDone } return nil } func (e *FinancialPolicyEngine) 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 strings.EqualFold(sv, es) } } return false } func (e *FinancialPolicyEngine) 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 && strings.EqualFold(is, sv) { return true } } return false } func (e *FinancialPolicyEngine) evaluateStopLineConditions(conditions []string, intake *UseCaseIntake) bool { if intake.FinancialContext == nil { return false } for _, cond := range conditions { if !e.parseAndEvaluateSimpleCondition(cond, intake) { return false } } return len(conditions) > 0 } func (e *FinancialPolicyEngine) evaluateGapConditions(conditions []string, intake *UseCaseIntake) bool { if intake.FinancialContext == nil { return false } for _, cond := range conditions { if !e.parseAndEvaluateSimpleCondition(cond, intake) { return false } } return len(conditions) > 0 } func (e *FinancialPolicyEngine) parseAndEvaluateSimpleCondition(condition string, intake *UseCaseIntake) bool { if strings.Contains(condition, "==") { parts := strings.SplitN(condition, "==", 2) if len(parts) != 2 { return false } field := strings.TrimSpace(parts[0]) value := strings.TrimSpace(parts[1]) fieldVal := e.getFieldValue(field, intake) if fieldVal == nil { return false } if value == "true" { if bv, ok := fieldVal.(bool); ok { return bv } } else if value == "false" { if bv, ok := fieldVal.(bool); ok { return !bv } } if sv, ok := fieldVal.(string); ok { return strings.EqualFold(sv, value) } } return false } func (e *FinancialPolicyEngine) determineEscalationLevel(intake *UseCaseIntake) string { if intake.FinancialContext == nil { return "" } ctx := intake.FinancialContext if ctx.AIApplication.AlgorithmicTrading { return "E3" } if ctx.ICTService.IsCritical && ctx.ICTService.IsOutsourced { return "E3" } if ctx.AIApplication.RiskAssessment || ctx.AIApplication.AffectsCustomerDecisions { return "E2" } return "E1" } func (e *FinancialPolicyEngine) generateSummary(result *FinancialAssessmentResult) string { var parts []string switch result.Feasibility { case FeasibilityYES: parts = append(parts, "Der Use Case ist aus regulatorischer Sicht (DORA/MaRisk/BAIT) grundsätzlich umsetzbar.") case FeasibilityCONDITIONAL: parts = append(parts, "Der Use Case ist unter Einhaltung der Finanzregulierungen bedingt umsetzbar.") case FeasibilityNO: parts = append(parts, "Der Use Case ist ohne weitere Maßnahmen regulatorisch nicht zulässig.") } if len(result.StopLinesHit) > 0 { parts = append(parts, fmt.Sprintf("%d kritische Stop-Lines wurden ausgelöst.", len(result.StopLinesHit))) } if len(result.IdentifiedGaps) > 0 { parts = append(parts, fmt.Sprintf("%d Compliance-Lücken wurden identifiziert.", len(result.IdentifiedGaps))) } if len(result.RequiredControls) > 0 { parts = append(parts, fmt.Sprintf("%d regulatorische Kontrollen sind erforderlich.", len(result.RequiredControls))) } if result.EscalationLevel != "" { parts = append(parts, fmt.Sprintf("Eskalation auf Stufe %s empfohlen.", result.EscalationLevel)) } return strings.Join(parts, " ") } // GetAllControls returns all controls in the financial policy func (e *FinancialPolicyEngine) GetAllControls() map[string]FinancialControlDef { return e.config.Controls } // GetAllGaps returns all gaps in the financial policy func (e *FinancialPolicyEngine) GetAllGaps() map[string]FinancialGapDef { return e.config.Gaps } // GetAllStopLines returns all stop lines in the financial policy func (e *FinancialPolicyEngine) GetAllStopLines() map[string]FinancialStopLine { return e.config.StopLines } // GetApplicableDomains returns domains where financial regulations apply func (e *FinancialPolicyEngine) GetApplicableDomains() []string { return e.config.ApplicableDomains }