feat(ucca): Pflichtendatenbank v2 (325 Obligations), Trigger-Engine, TOM-Control-Mapping
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
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
- 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>
This commit is contained in:
259
ai-compliance-sdk/internal/ucca/tom_gap_analysis.go
Normal file
259
ai-compliance-sdk/internal/ucca/tom_gap_analysis.go
Normal file
@@ -0,0 +1,259 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user