97a52533a8
Build + Deploy / build-admin-compliance (push) Successful in 2m29s
Build + Deploy / build-backend-compliance (push) Successful in 3m23s
Build + Deploy / build-ai-sdk (push) Failing after 47s
Build + Deploy / build-developer-portal (push) Successful in 1m19s
Build + Deploy / build-tts (push) Failing after 1m29s
Build + Deploy / build-document-crawler (push) Successful in 43s
Build + Deploy / build-dsms-gateway (push) Successful in 25s
Build + Deploy / build-dsms-node (push) Successful in 11s
CI / branch-name (push) Has been skipped
Build + Deploy / trigger-orca (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 18s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m17s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Failing after 48s
CI / test-python-backend (push) Successful in 42s
CI / test-python-document-crawler (push) Successful in 31s
CI / test-python-dsms-gateway (push) Successful in 26s
CI / validate-canonical-controls (push) Successful in 18s
Local origin is 20+ commits ahead of remote gitea. All conflicts resolved by keeping HEAD (our version) which includes the full 56→138 check expansion and doc_checks package split. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
173 lines
4.9 KiB
Go
173 lines
4.9 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()...)
|
|
|
|
// 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
|
|
}
|