feat(ai-sdk): interlocked-enclosure model — guard-open re-scoping of contact hazards
Class C (phase-aware, generic EN ISO 14120). A contact/entanglement hazard from a moving part is removed during NORMAL operation when the part is behind an interlocked guard; it remains only when the guard is open (maintenance/cleaning). - New HazardPattern.GuardableByEnclosure flag; set on HP096 (friction at rotating surfaces) and HP101 (entanglement of hair/clothing). - Narrative emits interlocked_enclosure for an interlocked door/hood. - pattern_enclosure.go: suppressedByEnclosure (drop in normal-op-only contexts) + guardedLifecycles (re-scope to maintenance/cleaning). - GT #3 gains the maintenance-phase entanglement/friction rows. Generic + regression-safe: machines that do not emit interlocked_enclosure are unaffected. GT #3 recall 80% -> 82.4%, one false positive removed (Aufwickeln). Kistenhub 97.1% and all 26 Bremse pinned mappings unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,13 @@ type HazardPattern struct {
|
||||
// "hazard" = source only, "hazardous_situation" = person exposed, "harm" = injury.
|
||||
// Empty = default (hazardous_situation).
|
||||
GeneratedHazardType string `json:"generated_hazard_type,omitempty"`
|
||||
// GuardableByEnclosure marks a contact/entanglement hazard that an interlocked
|
||||
// enclosure removes during normal operation. When the project emits the
|
||||
// "interlocked_enclosure" tag, such a pattern is re-scoped to maintenance/
|
||||
// cleaning (guard open) and does NOT fire as a normal-operation hazard.
|
||||
// Generic EN ISO 14120 logic — surfaced by the warewashing GT (the spray
|
||||
// arm rotates behind the interlocked door).
|
||||
GuardableByEnclosure bool `json:"guardable_by_enclosure,omitempty"`
|
||||
// RequiredFailureModes restricts this pattern to fire only when at least one
|
||||
// of the listed failure modes is relevant (by ComponentType match against project components).
|
||||
// Empty/nil = fires regardless of failure modes (backwards compatible).
|
||||
|
||||
@@ -37,6 +37,7 @@ func GetDGUVExtendedPatterns() []HazardPattern {
|
||||
},
|
||||
{
|
||||
ID: "HP096", NameDE: "Reibung/Abrieb durch rotierende Oberflaechen", NameEN: "Friction/abrasion by rotating surfaces",
|
||||
GuardableByEnclosure: true,
|
||||
RequiredComponentTags: []string{"rotating_part"},
|
||||
RequiredEnergyTags: []string{},
|
||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||
@@ -88,6 +89,7 @@ func GetDGUVExtendedPatterns() []HazardPattern {
|
||||
},
|
||||
{
|
||||
ID: "HP101", NameDE: "Aufwickeln von Kleidung/Haaren", NameEN: "Winding up of clothing/hair",
|
||||
GuardableByEnclosure: true,
|
||||
RequiredComponentTags: []string{"rotating_part"},
|
||||
RequiredEnergyTags: []string{"rotational"},
|
||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||
|
||||
@@ -201,6 +201,12 @@ func GetKeywordDictionary() []KeywordEntry {
|
||||
{Keywords: []string{"lichtgitter", "lichtvorhang", "light curtain", "light grid"}, ComponentIDs: []string{"C102"}, ExtraTags: []string{"safety_device"}},
|
||||
{Keywords: []string{"sicherheitsschalter", "safety switch"}, ComponentIDs: []string{"C104"}, ExtraTags: []string{"safety_device", "interlocked"}},
|
||||
{Keywords: []string{"zuhaltung", "guard locking", "interlock"}, ComponentIDs: []string{"C105"}, ExtraTags: []string{"safety_device", "interlocked"}},
|
||||
// interlocked_enclosure signals that moving parts are inaccessible behind a
|
||||
// guard that is monitored/locked — feeds the GuardableByEnclosure re-scoping
|
||||
// (contact/entanglement becomes a maintenance/guard-open hazard, not a
|
||||
// normal-operation one). Emitted only by explicit "interlocked door/guard"
|
||||
// vocabulary so it does not trigger for machines with exposed motion.
|
||||
{Keywords: []string{"tuer mit sicherheitsschalter", "verriegelte tuer", "verriegelte haube", "verriegelte einhausung", "sicherheitstuer", "tuerverriegelung", "haube mit sicherheitsschalter"}, ExtraTags: []string{"interlocked_enclosure"}},
|
||||
{Keywords: []string{"zweihand", "two-hand", "zweihandschaltung"}, ComponentIDs: []string{"C106"}, ExtraTags: []string{"safety_device", "two_hand_control_required"}},
|
||||
{Keywords: []string{"schaltmatte", "safety mat"}, ComponentIDs: []string{"C108"}, ExtraTags: []string{"safety_device"}},
|
||||
{Keywords: []string{"seilzug", "pull wire"}, ComponentIDs: []string{"C109"}, ExtraTags: []string{"safety_device"}},
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package iace
|
||||
|
||||
// Interlocked-enclosure model (EN ISO 14120 / EN ISO 12100).
|
||||
//
|
||||
// A contact or entanglement hazard from a moving part is removed during NORMAL
|
||||
// operation when that part is inaccessible behind an interlocked guard. The
|
||||
// hazard then remains only when the guard is open — maintenance, cleaning or
|
||||
// fault clearing. Patterns flagged GuardableByEnclosure express this; a project
|
||||
// emits the "interlocked_enclosure" tag (interlocked door/hood, see
|
||||
// keyword_dictionary.go) to declare the guard.
|
||||
//
|
||||
// This is GENERIC: it applies to every enclosed machine (dishwasher spray arm,
|
||||
// enclosed mixer, centrifuge ...) and is regression-safe — machines that do not
|
||||
// emit interlocked_enclosure are unaffected.
|
||||
|
||||
const (
|
||||
phaseMaintenance = "maintenance"
|
||||
phaseCleaning = "cleaning"
|
||||
phaseFaultClearing = "fault_clearing"
|
||||
)
|
||||
|
||||
// suppressedByEnclosure reports whether a guardable hazard must be dropped: the
|
||||
// part is enclosed AND none of the project's lifecycle phases opens the guard.
|
||||
func suppressedByEnclosure(p HazardPattern, tagSet map[string]bool, lifecycles []string) bool {
|
||||
if !p.GuardableByEnclosure || !tagSet["interlocked_enclosure"] || len(lifecycles) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, lc := range lifecycles {
|
||||
if lc == phaseMaintenance || lc == phaseCleaning || lc == phaseFaultClearing {
|
||||
return false // guard is open in some phase → hazard remains there
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// guardedLifecycles re-scopes a guardable hazard to the guard-open phases when
|
||||
// the project declares an interlocked enclosure, so it is documented as a
|
||||
// maintenance/cleaning hazard rather than a normal-operation one.
|
||||
func guardedLifecycles(p HazardPattern, tagSet map[string]bool) []string {
|
||||
if p.GuardableByEnclosure && tagSet["interlocked_enclosure"] {
|
||||
return []string{phaseMaintenance, phaseCleaning}
|
||||
}
|
||||
return p.ApplicableLifecycles
|
||||
}
|
||||
@@ -223,7 +223,7 @@ func (e *PatternEngine) Match(input MatchInput) *MatchOutput {
|
||||
HumanRoles: p.HumanRoles,
|
||||
GeneratedHazardType: p.GeneratedHazardType,
|
||||
MatchedFailureModes: matchedFMs,
|
||||
ApplicableLifecycles: p.ApplicableLifecycles,
|
||||
ApplicableLifecycles: guardedLifecycles(p, tagSet),
|
||||
SuggestedMeasureIDs: p.SuggestedMeasureIDs,
|
||||
ClarificationQuestionsDE: p.ClarificationQuestionsDE,
|
||||
ISO12100Section: p.ISO12100Section,
|
||||
@@ -411,6 +411,11 @@ func patternMatches(p HazardPattern, tagSet map[string]bool, input MatchInput) b
|
||||
}
|
||||
}
|
||||
|
||||
// Interlocked-enclosure gate (guardable contact/entanglement). See pattern_enclosure.go.
|
||||
if suppressedByEnclosure(p, tagSet, input.LifecyclePhases) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -228,6 +228,36 @@
|
||||
"risk_out": {"f": 1, "w": 1, "p": 1, "s": 3, "r": 9},
|
||||
"norm_references": ["EN ISO 13849-1"],
|
||||
"sufficient": true
|
||||
},
|
||||
{
|
||||
"nr": "4.4",
|
||||
"hazard_group": "Mechanische Gefährdungen",
|
||||
"hazard_group_applicable": true,
|
||||
"hazard_type": "Erfassen/Aufwickeln an rotierenden Teilen bei geöffneter Schutztür",
|
||||
"hazard_cause": "Bei geöffneter Tür im Wartungs- oder Reinigungsfall können lose Kleidung oder Haare an noch zugänglichen rotierenden Wellen erfasst und aufgewickelt werden",
|
||||
"lifecycle_phases": ["Instandhaltung", "Reinigung"],
|
||||
"component_zone": "Rotierende Wellen, Spülarm bei geöffneter Schutztür",
|
||||
"risk_in": {"f": 1, "w": 1, "p": 2, "s": 3, "r": 12},
|
||||
"measures": ["Rotation stoppt bei geöffneter Tür durch Verriegelung", "Warnhinweis"],
|
||||
"measure_type": "KM",
|
||||
"risk_out": {"f": 1, "w": 1, "p": 1, "s": 3, "r": 6},
|
||||
"norm_references": ["EN ISO 14120"],
|
||||
"sufficient": true
|
||||
},
|
||||
{
|
||||
"nr": "4.5",
|
||||
"hazard_group": "Mechanische Gefährdungen",
|
||||
"hazard_group_applicable": true,
|
||||
"hazard_type": "Reibung/Hautabschürfung an rotierenden Teilen bei geöffneter Schutztür",
|
||||
"hazard_cause": "Berührung rotierender Wellen oder Oberflächen bei geöffneter Tür im Wartungsfall führt zu Hautabschürfungen durch Reibung",
|
||||
"lifecycle_phases": ["Instandhaltung"],
|
||||
"component_zone": "Rotierende Welle bei geöffneter Schutztür",
|
||||
"risk_in": {"f": 1, "w": 1, "p": 2, "s": 2, "r": 8},
|
||||
"measures": ["Rotation stoppt bei geöffneter Tür durch Verriegelung"],
|
||||
"measure_type": "KM",
|
||||
"risk_out": {"f": 1, "w": 1, "p": 1, "s": 2, "r": 4},
|
||||
"norm_references": ["EN ISO 14120"],
|
||||
"sufficient": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user