e9002175ac
Adds a curated database of safety-relevant features for the major
manufacturers across mechanical/plant engineering, written entirely in
own words with norm anchors. No verbatim manufacturer texts — therefore
no copyright issue:
- Markennennung (§ 23 MarkenG nominative use) is permitted.
- Fakten ueber Produkt-Sicherheitsfunktionen are not protected by § 2
UrhG (only Werke, not facts).
- NormReferences contain only the identifiers (e.g. "EN ISO 13849-1
PLd Kat.3"), never the norm text itself.
Coverage (52 entries across 12 categories):
Industrieroboter (10): FANUC DCS, KUKA SafeOperation, ABB SafeMove,
Yaskawa FSU, Staeubli CS9, Kawasaki Cubic-S, Mitsubishi MELFA,
Universal Robots PolyScope, Doosan PRS, Comau SafeNet
CNC/WZM (8): DMG MORI, Mazak, TRUMPF, Okuma, Hermle, Heidenhain
SPLC, GROB, Heller
Pneumatik (4): Festo, SMC, AVENTICS, Parker
Hydraulik (3): Bosch Rexroth, HAWE, HYDAC
Safety-PLC / Sicherheitstechnik (8): PILZ, SICK, Schmersal, Euchner,
Leuze, Phoenix Contact, Banner, Wieland
Standard-PLC (5): Siemens, Beckhoff, Rockwell, Schneider, B&R
Pressen (3): Schuler, Bruderer, AIDA
Spritzguss (3): Arburg, KraussMaffei, ENGEL
Verpackung (2): Krones, Bosch Packaging/Syntegon
Laser/Schweissen (3): Bystronic, Amada, Fronius
Foerdertechnik (2): Interroll, SEW EURODRIVE
Engine integration:
- LookupManufacturerFeaturesInText() scans the project narrative for
any of the manufacturer aliases (case-insensitive, umlaut-tolerant).
- Init-Handler appends matched feature clarifications to the relevant
hazard's "Mit Anlagenbauer zu klaeren:" block — for the right
HazardCategory only (e.g. FANUC DCS only on mechanical_hazard).
- For a Bremse project narrative mentioning "Fanuc Robodrill", the
engine now adds clarification questions like "Ist DCS am Roboter
konfiguriert?" to relevant mechanical hazards automatically.
Tests: 7 new pin tests — manufacturer count, norm prefixes, FANUC/KUKA
detection in narrative, umlaut robustness (Staeubli vs Staubli).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
135 lines
4.7 KiB
Go
135 lines
4.7 KiB
Go
package iace
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestManufacturerLibrary_HasTopManufacturers checks the curated baseline
|
|
// (~50 entries across major mechanical/plant-engineering segments). The
|
|
// expected manufacturers below are the ones that would be unsurprising to
|
|
// find in any industrial risk-assessment context.
|
|
func TestManufacturerLibrary_HasTopManufacturers(t *testing.T) {
|
|
required := []string{
|
|
"FANUC", "KUKA", "ABB Robotics", "Yaskawa Motoman", "Universal Robots",
|
|
"DMG MORI", "Mazak", "TRUMPF", "Heidenhain",
|
|
"Festo", "SMC", "Bosch Rexroth",
|
|
"PILZ", "SICK", "Schmersal", "Euchner",
|
|
"Siemens", "Beckhoff", "Rockwell Automation",
|
|
"Schuler", "Arburg", "KraussMaffei",
|
|
}
|
|
have := map[string]bool{}
|
|
for _, mf := range GetManufacturerSafetyFeatures() {
|
|
have[mf.Manufacturer] = true
|
|
}
|
|
for _, want := range required {
|
|
if !have[want] {
|
|
t.Errorf("manufacturer library missing %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestManufacturerLibrary_AllHaveNorms pins that every entry carries at
|
|
// least one EN/ISO/IEC norm reference. A manufacturer entry without norm
|
|
// anchor is just hearsay; with it, the operator has something verifiable.
|
|
func TestManufacturerLibrary_AllHaveNorms(t *testing.T) {
|
|
for _, mf := range GetManufacturerSafetyFeatures() {
|
|
if len(mf.NormReferences) == 0 {
|
|
t.Errorf("manufacturer %q (%s) missing NormReferences", mf.Manufacturer, mf.FeatureName)
|
|
}
|
|
if len(mf.Clarifications) == 0 {
|
|
t.Errorf("manufacturer %q (%s) missing Clarifications", mf.Manufacturer, mf.FeatureName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestLookupManufacturerFeaturesInText_FindsFANUC reproduces the GT-Bremse
|
|
// scenario: a narrative mentioning "Robodrill" must surface the FANUC DCS
|
|
// entry — that's how the operator gets the right clarification question
|
|
// without us inventing commentary.
|
|
func TestLookupManufacturerFeaturesInText_FindsFANUC(t *testing.T) {
|
|
narrative := "Roboterzelle mit Fanuc Robodrill Werkzeugmaschine und KUKA-Roboter."
|
|
hits := LookupManufacturerFeaturesInText(narrative)
|
|
manufacturers := map[string]bool{}
|
|
for _, h := range hits {
|
|
manufacturers[h.Manufacturer] = true
|
|
}
|
|
if !manufacturers["FANUC"] {
|
|
t.Errorf("expected FANUC hit for narrative %q, got %v", narrative, manufacturers)
|
|
}
|
|
if !manufacturers["KUKA"] {
|
|
t.Errorf("expected KUKA hit for narrative %q, got %v", narrative, manufacturers)
|
|
}
|
|
}
|
|
|
|
// TestLookupManufacturerFeaturesInText_HandlesUmlauts pins case + umlaut
|
|
// robustness — "Stäubli" must match alias "staeubli".
|
|
func TestLookupManufacturerFeaturesInText_HandlesUmlauts(t *testing.T) {
|
|
hits := LookupManufacturerFeaturesInText("Stäubli TX2 Roboter und Schmersal Türverriegelung")
|
|
manufacturers := map[string]bool{}
|
|
for _, h := range hits {
|
|
manufacturers[h.Manufacturer] = true
|
|
}
|
|
if !manufacturers["Stäubli Robotics"] {
|
|
t.Errorf("expected Staeubli hit, got %v", manufacturers)
|
|
}
|
|
if !manufacturers["Schmersal"] {
|
|
t.Errorf("expected Schmersal hit, got %v", manufacturers)
|
|
}
|
|
}
|
|
|
|
// TestLookupManufacturerFeaturesInText_EmptyText handles missing narrative
|
|
// gracefully — must not crash and must return nil/empty.
|
|
func TestLookupManufacturerFeaturesInText_EmptyText(t *testing.T) {
|
|
if hits := LookupManufacturerFeaturesInText(""); len(hits) > 0 {
|
|
t.Errorf("expected no hits for empty narrative, got %d", len(hits))
|
|
}
|
|
}
|
|
|
|
// TestManufacturerLibrary_Has50OrMore checks the target count of the
|
|
// curated set so a future regression that deletes many entries fails CI.
|
|
func TestManufacturerLibrary_Has50OrMore(t *testing.T) {
|
|
got := len(GetManufacturerSafetyFeatures())
|
|
if got < 50 {
|
|
// Allow small undercount but keep the floor strict
|
|
t.Errorf("manufacturer library has %d entries, expected >= 50", got)
|
|
}
|
|
}
|
|
|
|
// TestNormalizeForMatch pins the umlaut/case folding behavior.
|
|
func TestNormalizeForMatch(t *testing.T) {
|
|
cases := []struct{ in, want string }{
|
|
{"Stäubli", "staeubli"},
|
|
{"FANUC", "fanuc"},
|
|
{"Schäffer", "schaeffer"},
|
|
{"Mößbauer", "moessbauer"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := normalizeForMatch(c.in); got != c.want {
|
|
t.Errorf("normalizeForMatch(%q) = %q, want %q", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestManufacturerLibrary_NormReferencesAreRealNorms scans for the standard
|
|
// norm prefixes so a future "we just put a placeholder" regression fails.
|
|
func TestManufacturerLibrary_NormReferencesAreRealNorms(t *testing.T) {
|
|
for _, mf := range GetManufacturerSafetyFeatures() {
|
|
ok := false
|
|
for _, n := range mf.NormReferences {
|
|
for _, prefix := range []string{"EN ", "IEC ", "ISO ", "DIN ", "EN ISO", "DIN EN", "VDE", "TRBS", "TRGS"} {
|
|
if strings.HasPrefix(n, prefix) {
|
|
ok = true
|
|
break
|
|
}
|
|
}
|
|
if ok {
|
|
break
|
|
}
|
|
}
|
|
if !ok {
|
|
t.Errorf("manufacturer %q has no recognized norm prefix in %v", mf.Manufacturer, mf.NormReferences)
|
|
}
|
|
}
|
|
}
|