80862e7073
HP013 (stored electrical energy) fires for dishwashers via the broad stored_energy tag but its zone is framed for Batteriefaecher/USV-Anlagen, which a dishwasher does not have. The precise residual-voltage pattern HP144 (Frequenzumrichter/Zwischenkreis, Priority 90) already fires and covers the same hazard. Add HP013 to the warewashing-scoped supersession set so the duplicate is dropped only when dom_warewashing is present. Warewashing recall stays 100% (25/25), precision 92.6% -> 96.2%. Kistenhub/Bremse keep HP013 (no dom_warewashing); 26 Bremse pins + benchmark unaffected. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
71 lines
3.1 KiB
Go
71 lines
3.1 KiB
Go
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
|
|
}
|
|
|
|
// Domain-specific supersession.
|
|
//
|
|
// A generic pattern that fires via a broad tag (e.g. high_temperature) can
|
|
// duplicate a domain-specific pattern that describes the same hazard more
|
|
// precisely. When the domain is present, the specific pattern wins and the
|
|
// generic duplicate is dropped. Scoped to the domain tag, so machines outside
|
|
// the domain keep the generic pattern — regression-safe by construction.
|
|
//
|
|
// HP016 (generic hot surfaces) -> HP2201 (Boiler/Tank/Spuelkammer)
|
|
// HP018 (actuator burn) -> HP2201 (same contact-burn hazard)
|
|
// HP013 (stored electrical NRG) -> HP144 (residual voltage; HP013's zone is
|
|
// framed for Batteriefaecher/USV-Anlagen a
|
|
// dishwasher does not have, HP144 is the
|
|
// Frequenzumrichter/Zwischenkreis variant)
|
|
var genericSupersededByWarewashing = map[string]bool{
|
|
"HP016": true,
|
|
"HP018": true,
|
|
"HP013": true,
|
|
}
|
|
|
|
// supersededByDomainSpecific reports whether a generic pattern is replaced by a
|
|
// more precise equivalent that the project's domain already provides.
|
|
func supersededByDomainSpecific(p HazardPattern, tagSet map[string]bool) bool {
|
|
return tagSet["dom_warewashing"] && genericSupersededByWarewashing[p.ID]
|
|
}
|