package iace import "strconv" // DeltaResult contains the impact of a proposed change to a project. // It compares Match(current) vs Match(current+proposed) and returns // the difference in matched patterns, hazards, measures, and evidence. type DeltaResult struct { // Patterns that would fire with the proposed change but not currently AddedPatterns []PatternMatch `json:"added_patterns"` // Patterns that currently fire but would stop with the proposed change RemovedPatterns []PatternMatch `json:"removed_patterns"` // Net change counts AddedHazardCount int `json:"added_hazard_count"` RemovedHazardCount int `json:"removed_hazard_count"` AddedMeasureCount int `json:"added_measure_count"` RemovedMeasureCount int `json:"removed_measure_count"` AddedEvidenceCount int `json:"added_evidence_count"` RemovedEvidenceCount int `json:"removed_evidence_count"` // Added/removed hazard categories AddedHazardCats []string `json:"added_hazard_categories,omitempty"` RemovedHazardCats []string `json:"removed_hazard_categories,omitempty"` // Added/removed measure IDs AddedMeasureIDs []string `json:"added_measure_ids,omitempty"` RemovedMeasureIDs []string `json:"removed_measure_ids,omitempty"` // Summary text (German) SummaryDE string `json:"summary_de"` } // DeltaMatch computes the impact of a proposed change by comparing // the current match output with the proposed match output. func (e *PatternEngine) DeltaMatch(current, proposed MatchInput) *DeltaResult { currentOutput := e.Match(current) proposedOutput := e.Match(proposed) // Build pattern ID sets currentPatterns := make(map[string]PatternMatch) for _, p := range currentOutput.MatchedPatterns { currentPatterns[p.PatternID] = p } proposedPatterns := make(map[string]PatternMatch) for _, p := range proposedOutput.MatchedPatterns { proposedPatterns[p.PatternID] = p } // Find added/removed patterns var added, removed []PatternMatch for id, p := range proposedPatterns { if _, exists := currentPatterns[id]; !exists { added = append(added, p) } } for id, p := range currentPatterns { if _, exists := proposedPatterns[id]; !exists { removed = append(removed, p) } } // Compute hazard category diff currentCats := collectCats(currentOutput.SuggestedHazards) proposedCats := collectCats(proposedOutput.SuggestedHazards) addedCats := diffStrings(proposedCats, currentCats) removedCats := diffStrings(currentCats, proposedCats) // Compute measure ID diff currentMIDs := collectMIDs(currentOutput.SuggestedMeasures) proposedMIDs := collectMIDs(proposedOutput.SuggestedMeasures) addedMIDs := diffStrings(proposedMIDs, currentMIDs) removedMIDs := diffStrings(currentMIDs, proposedMIDs) // Compute evidence ID diff currentEIDs := collectEIDs(currentOutput.SuggestedEvidence) proposedEIDs := collectEIDs(proposedOutput.SuggestedEvidence) result := &DeltaResult{ AddedPatterns: added, RemovedPatterns: removed, AddedHazardCount: len(addedCats), RemovedHazardCount: len(removedCats), AddedMeasureCount: len(addedMIDs), RemovedMeasureCount: len(removedMIDs), AddedEvidenceCount: len(diffStrings(proposedEIDs, currentEIDs)), RemovedEvidenceCount: len(diffStrings(currentEIDs, proposedEIDs)), AddedHazardCats: addedCats, RemovedHazardCats: removedCats, AddedMeasureIDs: addedMIDs, RemovedMeasureIDs: removedMIDs, } result.SummaryDE = buildDeltaSummary(result) return result } func collectCats(hazards []HazardSuggestion) []string { var cats []string for _, h := range hazards { cats = append(cats, h.Category) } return cats } func collectMIDs(measures []MeasureSuggestion) []string { var ids []string for _, m := range measures { ids = append(ids, m.MeasureID) } return ids } func collectEIDs(evidence []EvidenceSuggestion) []string { var ids []string for _, e := range evidence { ids = append(ids, e.EvidenceID) } return ids } func diffStrings(a, b []string) []string { bSet := make(map[string]bool) for _, s := range b { bSet[s] = true } var diff []string seen := make(map[string]bool) for _, s := range a { if !bSet[s] && !seen[s] { diff = append(diff, s) seen[s] = true } } return diff } func buildDeltaSummary(r *DeltaResult) string { if len(r.AddedPatterns) == 0 && len(r.RemovedPatterns) == 0 { return "Keine Auswirkung — die vorgeschlagene Aenderung veraendert die Risikobeurteilung nicht." } s := "" if len(r.AddedPatterns) > 0 { s += "+" + deltaItoa(len(r.AddedPatterns)) + " neue Gefaehrdungsmuster erkannt" } if len(r.RemovedPatterns) > 0 { if s != "" { s += ", " } s += "-" + deltaItoa(len(r.RemovedPatterns)) + " Gefaehrdungsmuster entfallen" } if r.AddedHazardCount > 0 { s += ". +" + deltaItoa(r.AddedHazardCount) + " neue Gefaehrdungskategorien" } if r.AddedMeasureCount > 0 { s += ". +" + deltaItoa(r.AddedMeasureCount) + " zusaetzliche Massnahmen empfohlen" } s += "." return s } func deltaItoa(i int) string { return strconv.Itoa(i) }