package gci import ( "fmt" "math" "time" ) // Engine calculates the GCI score type Engine struct{} // NewEngine creates a new GCI calculation engine func NewEngine() *Engine { return &Engine{} } // Calculate computes the full GCI result for a tenant func (e *Engine) Calculate(tenantID string, profileID string) *GCIResult { now := time.Now() profile := GetProfile(profileID) auditTrail := []AuditEntry{} // Step 1: Get module data (mock for now) modules := MockModuleData(tenantID) certDates := MockCertificateData() // Step 2: Calculate Level 1 - Module Scores with validity for i := range modules { m := &modules[i] if m.Assigned > 0 { m.RawScore = float64(m.Completed) / float64(m.Assigned) * 100.0 } // Apply validity factor if validUntil, ok := certDates[m.ModuleID]; ok { m.ValidityFactor = CalculateValidityFactor(validUntil, now) } else { m.ValidityFactor = 1.0 // No certificate tracking = assume valid } m.FinalScore = m.RawScore * m.ValidityFactor if m.ValidityFactor < 1.0 { auditTrail = append(auditTrail, AuditEntry{ Timestamp: now, Factor: "validity_decay", Description: fmt.Sprintf("Modul '%s': Gueltigkeitsfaktor %.2f (Zertifikat laeuft ab/abgelaufen)", m.ModuleName, m.ValidityFactor), Value: m.ValidityFactor, Impact: "negative", }) } } // Step 3: Calculate Level 2 - Risk-Weighted Scores per area areaModules := map[string][]ModuleScore{ "dsgvo": {}, "nis2": {}, "iso27001": {}, "ai_act": {}, } for _, m := range modules { if _, ok := areaModules[m.Category]; ok { areaModules[m.Category] = append(areaModules[m.Category], m) } } level2Areas := []RiskWeightedScore{} areaNames := map[string]string{ "dsgvo": "DSGVO", "nis2": "NIS2", "iso27001": "ISO 27001", "ai_act": "EU AI Act", } for areaID, mods := range areaModules { rws := RiskWeightedScore{ AreaID: areaID, AreaName: areaNames[areaID], Modules: mods, } for _, m := range mods { rws.WeightedSum += m.FinalScore * m.RiskWeight rws.TotalWeight += m.RiskWeight } if rws.TotalWeight > 0 { rws.AreaScore = rws.WeightedSum / rws.TotalWeight } level2Areas = append(level2Areas, rws) } // Step 4: Calculate Level 3 - Regulation Area Scores areaScores := []RegulationAreaScore{} for _, rws := range level2Areas { weight := profile.Weights[rws.AreaID] completedCount := 0 for _, m := range rws.Modules { if m.Completed >= m.Assigned && m.Assigned > 0 { completedCount++ } } ras := RegulationAreaScore{ RegulationID: rws.AreaID, RegulationName: rws.AreaName, Score: math.Round(rws.AreaScore*100) / 100, Weight: weight, WeightedScore: rws.AreaScore * weight, ModuleCount: len(rws.Modules), CompletedCount: completedCount, } areaScores = append(areaScores, ras) auditTrail = append(auditTrail, AuditEntry{ Timestamp: now, Factor: "area_score", Description: fmt.Sprintf("Bereich '%s': Score %.1f, Gewicht %.0f%%", rws.AreaName, rws.AreaScore, weight*100), Value: rws.AreaScore, Impact: "neutral", }) } // Step 5: Calculate raw GCI rawGCI := 0.0 totalWeight := 0.0 for _, ras := range areaScores { rawGCI += ras.WeightedScore totalWeight += ras.Weight } if totalWeight > 0 { rawGCI = rawGCI / totalWeight } // Step 6: Apply Criticality Multiplier criticalityMult := calculateCriticalityMultiplier(modules) auditTrail = append(auditTrail, AuditEntry{ Timestamp: now, Factor: "criticality_multiplier", Description: fmt.Sprintf("Kritikalitaetsmultiplikator: %.3f", criticalityMult), Value: criticalityMult, Impact: func() string { if criticalityMult < 1.0 { return "negative" } return "neutral" }(), }) // Step 7: Apply Incident Adjustment openInc, critInc := MockIncidentData() incidentAdj := calculateIncidentAdjustment(openInc, critInc) auditTrail = append(auditTrail, AuditEntry{ Timestamp: now, Factor: "incident_adjustment", Description: fmt.Sprintf("Vorfallsanpassung: %.3f (%d offen, %d kritisch)", incidentAdj, openInc, critInc), Value: incidentAdj, Impact: "negative", }) // Step 8: Final GCI finalGCI := rawGCI * criticalityMult * incidentAdj finalGCI = math.Max(0, math.Min(100, math.Round(finalGCI*10)/10)) // Step 9: Determine Maturity Level maturity := determineMaturityLevel(finalGCI) auditTrail = append(auditTrail, AuditEntry{ Timestamp: now, Factor: "final_gci", Description: fmt.Sprintf("GCI-Endergebnis: %.1f → Reifegrad: %s", finalGCI, MaturityLabels[maturity]), Value: finalGCI, Impact: "neutral", }) return &GCIResult{ TenantID: tenantID, GCIScore: finalGCI, MaturityLevel: maturity, MaturityLabel: MaturityLabels[maturity], CalculatedAt: now, Profile: profileID, AreaScores: areaScores, CriticalityMult: criticalityMult, IncidentAdj: incidentAdj, AuditTrail: auditTrail, } } // CalculateBreakdown returns the full 4-level breakdown func (e *Engine) CalculateBreakdown(tenantID string, profileID string) *GCIBreakdown { result := e.Calculate(tenantID, profileID) modules := MockModuleData(tenantID) certDates := MockCertificateData() now := time.Now() // Recalculate module scores for the breakdown for i := range modules { m := &modules[i] if m.Assigned > 0 { m.RawScore = float64(m.Completed) / float64(m.Assigned) * 100.0 } if validUntil, ok := certDates[m.ModuleID]; ok { m.ValidityFactor = CalculateValidityFactor(validUntil, now) } else { m.ValidityFactor = 1.0 } m.FinalScore = m.RawScore * m.ValidityFactor } // Build Level 2 areas areaModules := map[string][]ModuleScore{} for _, m := range modules { areaModules[m.Category] = append(areaModules[m.Category], m) } areaNames := map[string]string{"dsgvo": "DSGVO", "nis2": "NIS2", "iso27001": "ISO 27001", "ai_act": "EU AI Act"} level2 := []RiskWeightedScore{} for areaID, mods := range areaModules { rws := RiskWeightedScore{AreaID: areaID, AreaName: areaNames[areaID], Modules: mods} for _, m := range mods { rws.WeightedSum += m.FinalScore * m.RiskWeight rws.TotalWeight += m.RiskWeight } if rws.TotalWeight > 0 { rws.AreaScore = rws.WeightedSum / rws.TotalWeight } level2 = append(level2, rws) } return &GCIBreakdown{ GCIResult: *result, Level1Modules: modules, Level2Areas: level2, } } // GetHistory returns historical GCI snapshots func (e *Engine) GetHistory(tenantID string) []GCISnapshot { // Add current score to history result := e.Calculate(tenantID, "default") history := MockGCIHistory(tenantID) current := GCISnapshot{ TenantID: tenantID, Score: result.GCIScore, MaturityLevel: result.MaturityLevel, AreaScores: make(map[string]float64), CalculatedAt: result.CalculatedAt, } for _, as := range result.AreaScores { current.AreaScores[as.RegulationID] = as.Score } history = append(history, current) return history } // GetMatrix returns the compliance matrix (roles x regulations) func (e *Engine) GetMatrix(tenantID string) []ComplianceMatrixEntry { modules := MockModuleData(tenantID) roles := []struct { ID string Name string }{ {"management", "Geschaeftsfuehrung"}, {"it_security", "IT-Sicherheit / CISO"}, {"data_protection", "Datenschutz / DSB"}, {"hr", "Personalwesen"}, {"general", "Allgemeine Mitarbeiter"}, } // Define which modules are relevant per role roleModules := map[string][]string{ "management": {"dsgvo-grundlagen", "nis2-management", "ai-governance", "iso-isms"}, "it_security": {"nis2-risikomanagement", "nis2-incident-response", "iso-zugangssteuerung", "iso-kryptografie", "ai-hochrisiko"}, "data_protection": {"dsgvo-grundlagen", "dsgvo-betroffenenrechte", "dsgvo-tom", "dsgvo-dsfa", "dsgvo-auftragsverarbeitung"}, "hr": {"dsgvo-grundlagen", "dsgvo-betroffenenrechte", "nis2-management"}, "general": {"dsgvo-grundlagen", "nis2-risikomanagement", "ai-risikokategorien", "ai-transparenz"}, } moduleMap := map[string]ModuleScore{} for _, m := range modules { moduleMap[m.ModuleID] = m } entries := []ComplianceMatrixEntry{} for _, role := range roles { entry := ComplianceMatrixEntry{ Role: role.ID, RoleName: role.Name, Regulations: map[string]float64{}, } regScores := map[string][]float64{} requiredModuleIDs := roleModules[role.ID] entry.RequiredModules = len(requiredModuleIDs) for _, modID := range requiredModuleIDs { if m, ok := moduleMap[modID]; ok { score := 0.0 if m.Assigned > 0 { score = float64(m.Completed) / float64(m.Assigned) * 100 } regScores[m.Category] = append(regScores[m.Category], score) if m.Completed >= m.Assigned && m.Assigned > 0 { entry.CompletedModules++ } } } totalScore := 0.0 count := 0 for reg, scores := range regScores { sum := 0.0 for _, s := range scores { sum += s } avg := sum / float64(len(scores)) entry.Regulations[reg] = math.Round(avg*10) / 10 totalScore += avg count++ } if count > 0 { entry.OverallScore = math.Round(totalScore/float64(count)*10) / 10 } entries = append(entries, entry) } return entries } // Helper functions func calculateCriticalityMultiplier(modules []ModuleScore) float64 { criticalModules := 0 criticalLow := 0 for _, m := range modules { if m.RiskWeight >= 2.5 { criticalModules++ if m.FinalScore < 50 { criticalLow++ } } } if criticalModules == 0 { return 1.0 } // Reduce score if critical modules have low completion ratio := float64(criticalLow) / float64(criticalModules) return 1.0 - (ratio * 0.15) // max 15% reduction } func calculateIncidentAdjustment(openIncidents, criticalIncidents int) float64 { adj := 1.0 // Each open incident reduces by 1% adj -= float64(openIncidents) * 0.01 // Each critical incident reduces by additional 3% adj -= float64(criticalIncidents) * 0.03 return math.Max(0.8, adj) // minimum 80% (max 20% reduction) } func determineMaturityLevel(score float64) string { switch { case score >= 90: return MaturityOptimized case score >= 75: return MaturityManaged case score >= 60: return MaturityDefined case score >= 40: return MaturityReactive default: return MaturityHighRisk } }