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 }