feat(ai-sdk): IACE warewashing hazard patterns + cross-domain gating
Add commercial-dishwasher hazard patterns (HP2200-HP2206): hot-water/steam scald on door opening, hot surfaces, hot ware, corrosive detergent/rinse-aid burn, respiratory irritation, door pinch and wet-floor slip — each gated by dom_warewashing so they never leak into other machine classes. Add the matching warewashing protective measures (M2200-M2208). Tighten capability-domain gating: emit dom_flame/dom_glue and add welding surface-form gate terms (schweissarbeitsplatz, schweissfunke, lichtbogenzone, ...) so the welding/flame/glue burn patterns stop leaking into thermal-capable machines such as a dishwasher. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package iace
|
||||
|
||||
import "testing"
|
||||
|
||||
// firedSet runs the engine for the given custom tags and returns the set of
|
||||
// fired pattern IDs.
|
||||
func firedSet(customTags []string) map[string]bool {
|
||||
engine := NewPatternEngine()
|
||||
out := engine.Match(MatchInput{CustomTags: customTags})
|
||||
fired := make(map[string]bool, len(out.MatchedPatterns))
|
||||
for _, m := range out.MatchedPatterns {
|
||||
fired[m.PatternID] = true
|
||||
}
|
||||
return fired
|
||||
}
|
||||
|
||||
// A warewashing narrative emits these capability + functional tags.
|
||||
var warewashingTags = []string{
|
||||
"dom_warewashing", "steam_emission", "hot_water", "high_temperature",
|
||||
"corrosive_chemical", "access_door", "rotating_part",
|
||||
}
|
||||
|
||||
func TestWarewashing_PatternsFireForDishwasher(t *testing.T) {
|
||||
fired := firedSet(warewashingTags)
|
||||
want := []string{"HP2200", "HP2201", "HP2202", "HP2203", "HP2204", "HP2205", "HP2206"}
|
||||
for _, id := range want {
|
||||
if !fired[id] {
|
||||
t.Errorf("expected warewashing pattern %s to fire for a dishwasher, but it did not", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarewashing_PatternsDoNotLeakIntoOtherMachines(t *testing.T) {
|
||||
// A machine with thermal + electrical + chemical capability but NOT a
|
||||
// dishwasher must never produce warewashing hazards (dom_warewashing gate).
|
||||
fired := firedSet([]string{"high_temperature", "electrical_part", "chemical_risk", "rotating_part", "moving_part"})
|
||||
for _, id := range []string{"HP2200", "HP2201", "HP2202", "HP2203", "HP2204", "HP2205", "HP2206"} {
|
||||
if fired[id] {
|
||||
t.Errorf("warewashing pattern %s leaked into a non-dishwasher machine", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarewashing_WeldingAndGlueDoNotLeakIntoDishwasher(t *testing.T) {
|
||||
// The gate-term additions must stop the welding/flame/glue burn patterns
|
||||
// from firing for a dishwasher (they previously leaked via high_temperature
|
||||
// / electrical_part). dom_welding/dom_flame/dom_glue are absent here.
|
||||
fired := firedSet(warewashingTags)
|
||||
leak := map[string]string{
|
||||
"HP530": "Lichtbogen-Verbrennung (Schweissen)",
|
||||
"HP532": "Schweissrauch",
|
||||
"HP533": "Brand durch Schweissfunken (Schweissen)",
|
||||
}
|
||||
for id, name := range leak {
|
||||
if fired[id] {
|
||||
t.Errorf("cross-domain pattern %s (%s) leaked into a dishwasher", id, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarewashing_MeasureIDsExist(t *testing.T) {
|
||||
lib := GetProtectiveMeasureLibrary()
|
||||
have := make(map[string]bool, len(lib))
|
||||
for _, m := range lib {
|
||||
have[m.ID] = true
|
||||
}
|
||||
for _, p := range GetWarewashingPatterns() {
|
||||
for _, mid := range p.SuggestedMeasureIDs {
|
||||
if !have[mid] {
|
||||
t.Errorf("pattern %s references measure %s which is not in the library", p.ID, mid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarewashing_NarrativeEmitsTags(t *testing.T) {
|
||||
// Closes the loop: a realistic dishwasher description must emit the tags
|
||||
// the warewashing patterns gate on (otherwise the patterns are dead).
|
||||
narrative := "Gewerbliche Untertisch-Geschirrspuelmaschine mit Heisswasser-Boiler " +
|
||||
"und Nachspuelung ca. 85 Grad C, Spuelpumpe mit rotierenden Spuelfeldern, " +
|
||||
"Dampf-/Wrasenabgabe beim Oeffnen, Reiniger und Klarspueler ueber Dosiergeraet, " +
|
||||
"Tuer mit Sicherheitsschalter, Eingreifen in die Spuelkammer."
|
||||
res := ParseNarrative(narrative, "Gewerbliche Geschirrspuelmaschine")
|
||||
got := make(map[string]bool, len(res.CustomTags))
|
||||
for _, tag := range res.CustomTags {
|
||||
got[tag] = true
|
||||
}
|
||||
for _, want := range []string{"dom_warewashing", "steam_emission", "hot_water", "corrosive_chemical", "access_door", "rotating_part"} {
|
||||
if !got[want] {
|
||||
t.Errorf("narrative did not emit expected tag %q (got %v)", want, res.CustomTags)
|
||||
}
|
||||
}
|
||||
// And it must NOT emit any welding/flame/glue domain that would re-open leaks.
|
||||
for _, bad := range []string{"dom_welding", "dom_flame", "dom_glue"} {
|
||||
if got[bad] {
|
||||
t.Errorf("dishwasher narrative unexpectedly emitted cross-domain tag %q", bad)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarewashing_NewMeasuresPresent(t *testing.T) {
|
||||
lib := GetProtectiveMeasureLibrary()
|
||||
have := make(map[string]bool, len(lib))
|
||||
for _, m := range lib {
|
||||
have[m.ID] = true
|
||||
}
|
||||
for _, mid := range []string{"M2200", "M2201", "M2202", "M2203", "M2204", "M2205", "M2206", "M2207", "M2208"} {
|
||||
if !have[mid] {
|
||||
t.Errorf("expected warewashing measure %s to be registered in the library", mid)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user