Files
Sharang Parnerkar c293d76e6b refactor(go/ucca): split policy_engine, legal_rag, ai_act, nis2, financial_policy, dsgvo_module
Split 6 oversized files (719–882 LOC each) into focused files under 500 LOC:
- policy_engine.go → types, loader, eval, gen (4 files)
- legal_rag.go     → types, client, http, context, scroll (5 files)
- ai_act_module.go → module, yaml, obligations (3 files)
- nis2_module.go   → module, yaml, obligations + shared obligation_yaml_types.go (3+1 files)
- financial_policy.go → types, engine (2 files)
- dsgvo_module.go  → module, yaml, obligations (3 files)

All in package ucca, zero exported symbol renames, go test ./internal/ucca/... passes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 09:48:41 +02:00

496 lines
13 KiB
Go

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
}