Files
breakpilot-compliance/ai-compliance-sdk/internal/iace/measures_library_test.go
T
Benjamin Admin bf9d8a5ed3 fix(iace): resolve M-ID collisions for electrical/pressure patterns
6 supplementary measures (M410-M420) were silently overwritten by
metalworking duplicates in measureByID lookups, so robot-cell electrical
patterns resolved to chip-extraction/cleaning fallbacks instead of
equipotential bonding, creepage, EMC, or hose-burst protection. Rename
supplementary IDs to M475-M480 and rewire 13 affected pattern references
in robot_cell + robot_cell_ext.

HP1640 (direct contact with live parts, GT 2.2): priority 98->99, drop
RequiredEnergyTags gate so it fires in robot cells without an electrical
tag, expand mitigations to 5 concrete TRBS 2131 / IEC 60204-1 / EN 61140
measures (basic protection, double insulation, earthing, insulation
monitoring, equipotential bonding) — was previously losing to HP1688
even though HP1688 describes a different scenario.

HP1688 (touch voltage from potential differences): priority 98->96 so it
no longer outranks HP1640 for the direct-contact case; mitigations
expanded from M410-only to 4 concrete electrical measures.

Add regression tests pinning HP1640 contact-protection resolution and
M475 = Potentialausgleich. Existing TestGetProtectiveMeasureLibrary_-
UniqueIDs now actually enforces uniqueness (previously masked by
last-wins map override).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:12:55 +02:00

85 lines
2.4 KiB
Go

package iace
import "testing"
// TestHP1640_ResolvesToContactProtection pins the GT-2.2 fix: the "direct
// contact with live parts" pattern must resolve to electrical-contact-protection
// measures (basic protection, double insulation, earthing, equipotential
// bonding), not to mechanical fallbacks like chip extraction.
func TestHP1640_ResolvesToContactProtection(t *testing.T) {
measureByID := make(map[string]ProtectiveMeasureEntry)
for _, m := range GetProtectiveMeasureLibrary() {
measureByID[m.ID] = m
}
patterns := GetRobotCellPatterns()
var hp1640 *HazardPattern
for i := range patterns {
if patterns[i].ID == "HP1640" {
hp1640 = &patterns[i]
break
}
}
if hp1640 == nil {
t.Fatal("HP1640 not found in robot cell patterns")
}
if len(hp1640.SuggestedMeasureIDs) < 3 {
t.Errorf("HP1640 should suggest at least 3 measures, got %d", len(hp1640.SuggestedMeasureIDs))
}
for _, mid := range hp1640.SuggestedMeasureIDs {
m, ok := measureByID[mid]
if !ok {
t.Errorf("HP1640 references non-existent measure %s", mid)
continue
}
if m.HazardCategory != "electrical" {
t.Errorf("HP1640 measure %s (%q) has HazardCategory=%s, expected electrical",
mid, m.Name, m.HazardCategory)
}
}
}
// TestHP1688_M475IsPotentialausgleich pins the M475 rename: HP1688 (touch
// voltage from potential differences) must resolve M475 to the equipotential
// bonding measure, not to the metalworking chip extraction that previously
// occupied M410 and overwrote the electrical definition.
func TestHP1688_M475IsPotentialausgleich(t *testing.T) {
measureByID := make(map[string]ProtectiveMeasureEntry)
for _, m := range GetProtectiveMeasureLibrary() {
measureByID[m.ID] = m
}
m, ok := measureByID["M475"]
if !ok {
t.Fatal("M475 not defined — supplementary rename did not land")
}
if m.HazardCategory != "electrical" {
t.Errorf("M475 must be HazardCategory=electrical, got %s (%q)", m.HazardCategory, m.Name)
}
patterns := GetRobotCellPatternsExt()
var hp1688 *HazardPattern
for i := range patterns {
if patterns[i].ID == "HP1688" {
hp1688 = &patterns[i]
break
}
}
if hp1688 == nil {
t.Fatal("HP1688 not found in robot cell ext patterns")
}
found := false
for _, mid := range hp1688.SuggestedMeasureIDs {
if mid == "M475" {
found = true
break
}
}
if !found {
t.Errorf("HP1688 must reference M475 (Potentialausgleich), got %v", hp1688.SuggestedMeasureIDs)
}
}