Files
breakpilot-compliance/ai-compliance-sdk/internal/ucca/tom_gap_analysis.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

260 lines
7.4 KiB
Go

package ucca
import (
"fmt"
"sort"
)
// ControlStatus represents the implementation status of a control
type ControlStatus string
const (
ControlImplemented ControlStatus = "IMPLEMENTED"
ControlPartial ControlStatus = "PARTIAL"
ControlNotImplemented ControlStatus = "NOT_IMPLEMENTED"
ControlNotApplicable ControlStatus = "NOT_APPLICABLE"
)
// GapAnalysisRequest is the input for gap analysis
type GapAnalysisRequest struct {
// Obligations that apply (from assessment)
Obligations []Obligation `json:"obligations"`
// Current implementation status of controls
ControlStatusMap map[string]ControlStatus `json:"control_status_map"` // control_id -> status
}
// GapAnalysisResult is the output of gap analysis
type GapAnalysisResult struct {
CompliancePercent float64 `json:"compliance_percent"` // 0-100
TotalControls int `json:"total_controls"`
ImplementedControls int `json:"implemented_controls"`
PartialControls int `json:"partial_controls"`
MissingControls int `json:"missing_controls"`
Gaps []GapItem `json:"gaps"`
PriorityActions []PriorityAction `json:"priority_actions"`
ByDomain map[string]DomainGap `json:"by_domain"`
}
// GapItem represents a single compliance gap
type GapItem struct {
ControlID string `json:"control_id"`
ControlTitle string `json:"control_title"`
ControlDomain string `json:"control_domain"`
Status ControlStatus `json:"status"`
Priority string `json:"priority"`
ObligationIDs []string `json:"obligation_ids"`
RequiredByCount int `json:"required_by_count"`
Impact string `json:"impact"` // "critical", "high", "medium", "low"
}
// PriorityAction is a recommended action to close gaps
type PriorityAction struct {
Rank int `json:"rank"`
Action string `json:"action"`
ControlIDs []string `json:"control_ids"`
Impact string `json:"impact"`
Effort string `json:"effort"` // "low", "medium", "high"
}
// DomainGap summarizes gaps per TOM domain
type DomainGap struct {
DomainID string `json:"domain_id"`
DomainName string `json:"domain_name"`
TotalControls int `json:"total_controls"`
ImplementedControls int `json:"implemented_controls"`
CompliancePercent float64 `json:"compliance_percent"`
}
// TOMGapAnalyzer performs gap analysis between obligations and control implementation
type TOMGapAnalyzer struct {
mapper *TOMObligationMapper
tomIndex *TOMControlIndex
}
// NewTOMGapAnalyzer creates a new gap analyzer
func NewTOMGapAnalyzer(mapper *TOMObligationMapper, tomIndex *TOMControlIndex) *TOMGapAnalyzer {
return &TOMGapAnalyzer{
mapper: mapper,
tomIndex: tomIndex,
}
}
// Analyze performs gap analysis
func (g *TOMGapAnalyzer) Analyze(req *GapAnalysisRequest) *GapAnalysisResult {
result := &GapAnalysisResult{
Gaps: []GapItem{},
PriorityActions: []PriorityAction{},
ByDomain: make(map[string]DomainGap),
}
// Derive required controls from obligations
requirements := g.mapper.DeriveControlsFromObligations(req.Obligations)
result.TotalControls = len(requirements)
// Track domain stats
domainTotal := make(map[string]int)
domainImplemented := make(map[string]int)
for _, ctrl := range requirements {
controlID := ctrl.Control.ID
domain := ctrl.Control.DomainID
domainTotal[domain]++
status, hasStatus := req.ControlStatusMap[controlID]
if !hasStatus {
status = ControlNotImplemented
}
switch status {
case ControlImplemented:
result.ImplementedControls++
domainImplemented[domain]++
case ControlPartial:
result.PartialControls++
// Count partial as 0.5 for domain
domainImplemented[domain]++ // simplified
result.Gaps = append(result.Gaps, GapItem{
ControlID: controlID,
ControlTitle: ctrl.Control.Title,
ControlDomain: domain,
Status: ControlPartial,
Priority: ctrl.Priority,
ObligationIDs: ctrl.ObligationIDs,
RequiredByCount: ctrl.RequiredByCount,
Impact: ctrl.Priority,
})
case ControlNotImplemented:
result.MissingControls++
result.Gaps = append(result.Gaps, GapItem{
ControlID: controlID,
ControlTitle: ctrl.Control.Title,
ControlDomain: domain,
Status: ControlNotImplemented,
Priority: ctrl.Priority,
ObligationIDs: ctrl.ObligationIDs,
RequiredByCount: ctrl.RequiredByCount,
Impact: ctrl.Priority,
})
case ControlNotApplicable:
result.TotalControls-- // Don't count N/A
domainTotal[domain]--
}
}
// Calculate compliance percent
if result.TotalControls > 0 {
implemented := float64(result.ImplementedControls) + float64(result.PartialControls)*0.5
result.CompliancePercent = (implemented / float64(result.TotalControls)) * 100
} else {
result.CompliancePercent = 100
}
// Sort gaps by priority
priorityRank := map[string]int{"critical": 0, "high": 1, "medium": 2, "low": 3}
sort.Slice(result.Gaps, func(i, j int) bool {
ri := priorityRank[result.Gaps[i].Priority]
rj := priorityRank[result.Gaps[j].Priority]
if ri != rj {
return ri < rj
}
return result.Gaps[i].RequiredByCount > result.Gaps[j].RequiredByCount
})
// Build domain gaps
for domain, total := range domainTotal {
if total <= 0 {
continue
}
impl := domainImplemented[domain]
pct := float64(impl) / float64(total) * 100
domainName := domain
if ctrls := g.tomIndex.GetControlsByDomain(domain); len(ctrls) > 0 {
domainName = domain // Use domain ID as name
}
result.ByDomain[domain] = DomainGap{
DomainID: domain,
DomainName: domainName,
TotalControls: total,
ImplementedControls: impl,
CompliancePercent: pct,
}
}
// Generate priority actions
result.PriorityActions = g.generatePriorityActions(result.Gaps)
return result
}
func (g *TOMGapAnalyzer) generatePriorityActions(gaps []GapItem) []PriorityAction {
var actions []PriorityAction
rank := 1
// Group critical gaps by domain
domainGaps := make(map[string][]GapItem)
for _, gap := range gaps {
if gap.Status == ControlNotImplemented {
domainGaps[gap.ControlDomain] = append(domainGaps[gap.ControlDomain], gap)
}
}
// Generate actions for domains with most critical gaps
type domainPriority struct {
domain string
gaps []GapItem
critCount int
}
var dp []domainPriority
for domain, gs := range domainGaps {
crit := 0
for _, g := range gs {
if g.Priority == "critical" {
crit++
}
}
dp = append(dp, domainPriority{domain, gs, crit})
}
sort.Slice(dp, func(i, j int) bool {
if dp[i].critCount != dp[j].critCount {
return dp[i].critCount > dp[j].critCount
}
return len(dp[i].gaps) > len(dp[j].gaps)
})
for _, d := range dp {
if rank > 10 {
break
}
var controlIDs []string
for _, g := range d.gaps {
controlIDs = append(controlIDs, g.ControlID)
}
impact := "medium"
if d.critCount > 0 {
impact = "critical"
} else if len(d.gaps) > 3 {
impact = "high"
}
effort := "medium"
if len(d.gaps) > 5 {
effort = "high"
} else if len(d.gaps) <= 2 {
effort = "low"
}
actions = append(actions, PriorityAction{
Rank: rank,
Action: fmt.Sprintf("Domain %s: %d fehlende Controls implementieren", d.domain, len(d.gaps)),
ControlIDs: controlIDs,
Impact: impact,
Effort: effort,
})
rank++
}
return actions
}