Files
breakpilot-compliance/ai-compliance-sdk/internal/iace/norms_engine.go
T
Benjamin Admin 2e1e18d853 feat: Normen-Bibliothek auf 617 erweitert (Ziel: 700)
Wave 3: +161 Normen (456 → 617)
- Serien-Lücken geschlossen (EN 1870, EN 474, EN 1034, EN 81, ISO 4254)
- Glas, Leder, Backwaren, Tabak, Medizin (IEC 60601), Labor, Feuerwehr
- Spielplatz, Fitness, Schwimmbad, HVAC, Kältetechnik
- PSA (Schuhe, Handschuhe, Augenschutz, Gehörschutz, Atemschutz)
- Leitern, Gerüste, Drahtseile, Gasgeräte, Messtechnik

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 13:04:22 +02:00

181 lines
5.3 KiB
Go

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()...)
allNorms = append(allNorms, GetMachiningCNorms()...)
allNorms = append(allNorms, GetConveyorAutoCNorms()...)
allNorms = append(allNorms, GetProcessCNorms()...)
allNorms = append(allNorms, GetConstructionCNorms()...)
allNorms = append(allNorms, GetNiche1CNorms()...)
allNorms = append(allNorms, GetNiche2CNorms()...)
allNorms = append(allNorms, GetNiche3CNorms()...)
allNorms = append(allNorms, GetExtendedB2Norms2()...)
allNorms = append(allNorms, GetWave3aCNorms()...)
allNorms = append(allNorms, GetWave3bCNorms()...)
allNorms = append(allNorms, GetWave3cCNorms()...)
allNorms = append(allNorms, GetWave3dCNorms()...)
// 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
}