Files
breakpilot-compliance/ai-compliance-sdk/internal/ucca/tom_obligation_mapper.go
Benjamin Admin 38e278ee3c
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 29s
CI / test-python-document-crawler (push) Successful in 20s
CI / test-python-dsms-gateway (push) Successful in 18s
feat(ucca): Pflichtendatenbank v2 (325 Obligations), Trigger-Engine, TOM-Control-Mapping
- 9 Regulation-JSON-Dateien (DSGVO 80, AI Act 60, NIS2 40, BDSG 30, TTDSG 20, DSA 35, Data Act 25, EU-Maschinen 15, DORA 20)
- Condition-Tree-Engine fuer automatische Pflichtenselektion (all_of/any_of, 80+ Field-Paths)
- Generischer JSONRegulationModule-Loader mit YAML-Fallback
- Bidirektionales TOM-Control-Mapping (291 Obligation→Control, 92 Control→Obligation)
- Gap-Analyse-Engine (Compliance-%, Priority Actions, Domain Breakdown)
- ScopeDecision→UnifiedFacts Bridge fuer Auto-Profiling
- 4 neue API-Endpoints (assess-from-scope, tom-controls, gap-analysis, reverse-lookup)
- Frontend: Auto-Profiling Button, Regulation-Filter Chips, TOM-Panel, Gap-Analyse-View
- 18 Unit Tests (Condition Engine, v2 Loader, TOM Mapper)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:51:44 +01:00

151 lines
4.9 KiB
Go

package ucca
import (
"sort"
)
// TOMObligationMapper provides bidirectional mapping between obligations and TOM controls
type TOMObligationMapper struct {
tomIndex *TOMControlIndex
obligationToControl map[string][]string // obligation_id -> []control_id
controlToObligation map[string][]string // control_id -> []obligation_id
}
// TOMControlRequirement represents a required TOM control with context
type TOMControlRequirement struct {
Control *TOMControl `json:"control"`
ObligationIDs []string `json:"obligation_ids"`
Priority string `json:"priority"` // highest priority from linked obligations
RequiredByCount int `json:"required_by_count"` // number of obligations requiring this
}
// NewTOMObligationMapper creates a new mapper from TOM index and v2 mapping
func NewTOMObligationMapper(tomIndex *TOMControlIndex, mapping *V2TOMMapping) *TOMObligationMapper {
m := &TOMObligationMapper{
tomIndex: tomIndex,
obligationToControl: make(map[string][]string),
controlToObligation: make(map[string][]string),
}
if mapping != nil {
m.obligationToControl = mapping.ObligationToControl
m.controlToObligation = mapping.ControlToObligation
}
return m
}
// NewTOMObligationMapperFromObligations builds the mapper from obligations' tom_control_ids
func NewTOMObligationMapperFromObligations(tomIndex *TOMControlIndex, obligations []V2Obligation) *TOMObligationMapper {
m := &TOMObligationMapper{
tomIndex: tomIndex,
obligationToControl: make(map[string][]string),
controlToObligation: make(map[string][]string),
}
for _, obl := range obligations {
for _, controlID := range obl.TOMControlIDs {
m.obligationToControl[obl.ID] = append(m.obligationToControl[obl.ID], controlID)
m.controlToObligation[controlID] = append(m.controlToObligation[controlID], obl.ID)
}
}
return m
}
// GetControlsForObligation returns TOM controls linked to an obligation
func (m *TOMObligationMapper) GetControlsForObligation(obligationID string) []*TOMControl {
controlIDs := m.obligationToControl[obligationID]
var result []*TOMControl
for _, id := range controlIDs {
if ctrl, ok := m.tomIndex.GetControl(id); ok {
result = append(result, ctrl)
}
}
return result
}
// GetControlIDsForObligation returns control IDs for an obligation
func (m *TOMObligationMapper) GetControlIDsForObligation(obligationID string) []string {
return m.obligationToControl[obligationID]
}
// GetObligationsForControl returns obligation IDs linked to a control
func (m *TOMObligationMapper) GetObligationsForControl(controlID string) []string {
return m.controlToObligation[controlID]
}
// DeriveControlsFromObligations takes a list of applicable obligations and returns
// deduplicated, priority-sorted TOM control requirements
func (m *TOMObligationMapper) DeriveControlsFromObligations(obligations []Obligation) []TOMControlRequirement {
// Collect all required controls with their linking obligations
controlMap := make(map[string]*TOMControlRequirement)
priorityRank := map[string]int{"critical": 0, "high": 1, "medium": 2, "low": 3}
for _, obl := range obligations {
// Get control IDs from ExternalResources (where tom_control_ids are stored)
controlIDs := obl.ExternalResources
if len(controlIDs) == 0 {
controlIDs = m.obligationToControl[obl.ID]
}
for _, controlID := range controlIDs {
ctrl, ok := m.tomIndex.GetControl(controlID)
if !ok {
continue
}
if existing, found := controlMap[controlID]; found {
existing.ObligationIDs = append(existing.ObligationIDs, obl.ID)
existing.RequiredByCount++
// Keep highest priority
if rank, ok := priorityRank[string(obl.Priority)]; ok {
if existingRank, ok2 := priorityRank[existing.Priority]; ok2 && rank < existingRank {
existing.Priority = string(obl.Priority)
}
}
} else {
controlMap[controlID] = &TOMControlRequirement{
Control: ctrl,
ObligationIDs: []string{obl.ID},
Priority: string(obl.Priority),
RequiredByCount: 1,
}
}
}
}
// Convert to slice and sort by priority then required_by_count
var result []TOMControlRequirement
for _, req := range controlMap {
result = append(result, *req)
}
sort.Slice(result, func(i, j int) bool {
ri := priorityRank[result[i].Priority]
rj := priorityRank[result[j].Priority]
if ri != rj {
return ri < rj
}
return result[i].RequiredByCount > result[j].RequiredByCount
})
return result
}
// AddMapping adds a single obligation->control mapping
func (m *TOMObligationMapper) AddMapping(obligationID, controlID string) {
m.obligationToControl[obligationID] = appendUnique(m.obligationToControl[obligationID], controlID)
m.controlToObligation[controlID] = appendUnique(m.controlToObligation[controlID], obligationID)
}
func appendUnique(slice []string, item string) []string {
for _, s := range slice {
if s == item {
return slice
}
}
return append(slice, item)
}