feat: IACE CE-Compliance Module — Normen, Risikobewertung, Production Lines
Major features: - 215 norms library with section references + Beuth URLs (A/B1/B2/C norms) - 173 hazard patterns with detail fields (scenario, trigger, harm, zone) - Deterministic pattern matching: Component × Lifecycle × Pattern cross-product - SIL/PL auto-calculation from S×E×P risk graph - Risk assessment table with editable S/E/P dropdowns - Production Line Dashboard with animated station flow (Running Dots) - IACE process flow + norms coverage on start page - Non-blocking cookie banner, ProcessFlow SSR fix - 104 Playwright E2E tests passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user