package iace import "sort" // NormSuggestion represents a single norm matched to a project context. type NormSuggestion struct { Norm NormReference `json:"norm"` Reason string `json:"reason"` // Why this norm was suggested Confidence float64 `json:"confidence"` // 0.0-1.0 relevance score Sources []string `json:"sources"` // What triggered the match } // NormSuggestionResult groups suggested norms by hierarchy type. type NormSuggestionResult struct { ANorms []NormSuggestion `json:"a_norms"` B1Norms []NormSuggestion `json:"b1_norms"` B2Norms []NormSuggestion `json:"b2_norms"` CNorms []NormSuggestion `json:"c_norms"` Total int `json:"total"` } // SuggestNorms matches relevant norms for a project based on its machine type, // identified hazard categories, and component/energy tags. // A-norms are always included (they apply universally). B/C norms are matched // by machine type (confidence 0.9), hazard category (0.8), or tag (0.7). func SuggestNorms(machineType string, hazardCategories []string, tags []string) *NormSuggestionResult { allNorms := GetNormsLibrary() allNorms = append(allNorms, GetExtendedB2Norms()...) allNorms = append(allNorms, GetCNormsLibrary()...) allNorms = append(allNorms, GetExtendedCNormsLibrary()...) allNorms = append(allNorms, GetWoodMetalCNorms()...) allNorms = append(allNorms, GetFoodPkgCNorms()...) allNorms = append(allNorms, GetLiftMiscCNorms()...) // Build lookup sets for efficient matching hazardSet := toSet(hazardCategories) tagSet := toSet(tags) seen := make(map[string]bool) var suggestions []NormSuggestion for _, norm := range allNorms { if seen[norm.ID] { continue } suggestion := matchNorm(norm, machineType, hazardSet, tagSet) if suggestion != nil { seen[norm.ID] = true suggestions = append(suggestions, *suggestion) } } return groupByType(suggestions) } // matchNorm checks a single norm against the project context and returns // a suggestion if it matches, or nil otherwise. func matchNorm(norm NormReference, machineType string, hazardSet, tagSet map[string]bool) *NormSuggestion { // A-norms always apply to all machines if norm.NormType == "A" { return &NormSuggestion{ Norm: norm, Reason: "Grundnorm — gilt fuer alle Maschinen", Confidence: 1.0, Sources: []string{"norm_type:A"}, } } var bestConfidence float64 var reasons []string var sources []string // Machine type match machineTypeMatched := false if machineType != "" && len(norm.MachineTypes) > 0 { for _, mt := range norm.MachineTypes { if mt == machineType { bestConfidence = 0.9 reasons = append(reasons, "Maschinentyp: "+machineType) sources = append(sources, "machine_type:"+machineType) machineTypeMatched = true break } } } // C-norms with machine_types ONLY match via machine type — skip tag/hazard matching // to avoid suggesting e.g. lathe norms for a press just because both have "rotating_part" if norm.NormType == "C" && len(norm.MachineTypes) > 0 && !machineTypeMatched { return nil } // Hazard category match (B-norms and C-norms without machine_types) for _, hc := range norm.HazardCats { if hazardSet[hc] { if 0.8 > bestConfidence { bestConfidence = 0.8 } reasons = append(reasons, "Gefaehrdung: "+hc) sources = append(sources, "hazard_cat:"+hc) } } // Tag match for _, t := range norm.Tags { if tagSet[t] { if 0.7 > bestConfidence { bestConfidence = 0.7 } reasons = append(reasons, "Tag: "+t) sources = append(sources, "tag:"+t) } } if bestConfidence == 0 { return nil } return &NormSuggestion{ Norm: norm, Reason: joinReasons(reasons), Confidence: bestConfidence, Sources: sources, } } // groupByType sorts suggestions by confidence and groups them by norm type. func groupByType(suggestions []NormSuggestion) *NormSuggestionResult { sort.Slice(suggestions, func(i, j int) bool { return suggestions[i].Confidence > suggestions[j].Confidence }) result := &NormSuggestionResult{ ANorms: []NormSuggestion{}, B1Norms: []NormSuggestion{}, B2Norms: []NormSuggestion{}, CNorms: []NormSuggestion{}, } for _, s := range suggestions { switch s.Norm.NormType { case "A": result.ANorms = append(result.ANorms, s) case "B1": result.B1Norms = append(result.B1Norms, s) case "B2": result.B2Norms = append(result.B2Norms, s) case "C": result.CNorms = append(result.CNorms, s) } } result.Total = len(suggestions) return result } // joinReasons combines multiple reason strings with "; ". func joinReasons(reasons []string) string { if len(reasons) == 0 { return "" } result := reasons[0] for i := 1; i < len(reasons); i++ { result += "; " + reasons[i] } return result }