feat(iace): Sprint 3B — Human Interaction Model
- 6 Standard-Rollen: operator, maintenance_tech, programmer, cleaning_staff, bystander, supervisor - HumanRoles []string Feld in HazardPattern, MatchInput, PatternMatch - patternMatches() filtert Patterns nach Rolle (nil = feuert fuer alle Rollen) - MatchReason um human_role Typ erweitert (Explainability) - 25 bestehende Patterns mit Rollen annotiert: - Cobot HP059/062/064 → operator/programmer - Maintenance HP700-714 → maintenance_tech/programmer - Operational HP070/073-078/080 → operator/maintenance_tech/programmer - Init + Parser Handler reichen Roles an MatchInput durch - 4 neue Tests: NilFiresAlways, MaintenanceTechFilter, ProgrammerTeachMode, RoleCount Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -419,6 +419,114 @@ func TestStandardStateTransitions_Valid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Human Interaction Model tests ──────────────────────────────────
|
||||
|
||||
func TestPatternEngine_HumanRole_NilFiresAlways(t *testing.T) {
|
||||
engine := NewPatternEngine()
|
||||
result1 := engine.Match(MatchInput{
|
||||
ComponentLibraryIDs: []string{"C001"},
|
||||
EnergySourceIDs: []string{"EN01"},
|
||||
})
|
||||
result2 := engine.Match(MatchInput{
|
||||
ComponentLibraryIDs: []string{"C001"},
|
||||
EnergySourceIDs: []string{"EN01"},
|
||||
HumanRoles: []string{"operator"},
|
||||
})
|
||||
if len(result1.MatchedPatterns) == 0 {
|
||||
t.Fatal("expected patterns without roles")
|
||||
}
|
||||
if len(result2.MatchedPatterns) == 0 {
|
||||
t.Fatal("expected patterns with operator role")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatternEngine_HumanRole_MaintenanceTechFilter(t *testing.T) {
|
||||
// HP073 has HumanRoles: ["maintenance_tech"]
|
||||
engine := NewPatternEngine()
|
||||
|
||||
// With maintenance_tech → HP073 should fire
|
||||
resultMT := engine.Match(MatchInput{
|
||||
ComponentLibraryIDs: []string{"C001"},
|
||||
EnergySourceIDs: []string{"EN01"},
|
||||
LifecyclePhases: []string{"maintenance"},
|
||||
OperationalStates: []string{"maintenance"},
|
||||
HumanRoles: []string{"maintenance_tech"},
|
||||
})
|
||||
|
||||
// With only operator → HP073 should NOT fire
|
||||
resultOp := engine.Match(MatchInput{
|
||||
ComponentLibraryIDs: []string{"C001"},
|
||||
EnergySourceIDs: []string{"EN01"},
|
||||
LifecyclePhases: []string{"maintenance"},
|
||||
OperationalStates: []string{"maintenance"},
|
||||
HumanRoles: []string{"operator"},
|
||||
})
|
||||
|
||||
hasHP073MT := false
|
||||
for _, p := range resultMT.MatchedPatterns {
|
||||
if p.PatternID == "HP073" {
|
||||
hasHP073MT = true
|
||||
break
|
||||
}
|
||||
}
|
||||
hasHP073Op := false
|
||||
for _, p := range resultOp.MatchedPatterns {
|
||||
if p.PatternID == "HP073" {
|
||||
hasHP073Op = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasHP073MT {
|
||||
t.Error("HP073 should fire for maintenance_tech role")
|
||||
}
|
||||
if hasHP073Op {
|
||||
t.Error("HP073 should NOT fire for operator role")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatternEngine_HumanRole_ProgrammerTeachMode(t *testing.T) {
|
||||
// HP062 has OperationalStates: ["teach_mode"], HumanRoles: ["programmer"]
|
||||
engine := NewPatternEngine()
|
||||
|
||||
// Programmer in teach mode with cobot components
|
||||
result := engine.Match(MatchInput{
|
||||
ComponentLibraryIDs: []string{"C139"}, // Cobot: moving_part, programmable, collaborative_operation
|
||||
EnergySourceIDs: []string{"EN01"},
|
||||
OperationalStates: []string{"teach_mode"},
|
||||
HumanRoles: []string{"programmer"},
|
||||
})
|
||||
|
||||
hasHP062 := false
|
||||
for _, p := range result.MatchedPatterns {
|
||||
if p.PatternID == "HP062" {
|
||||
hasHP062 = true
|
||||
// Verify explainability
|
||||
hasRoleReason := false
|
||||
for _, r := range p.MatchReasons {
|
||||
if r.Type == "human_role" && r.Tag == "programmer" && r.Met {
|
||||
hasRoleReason = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasRoleReason {
|
||||
t.Error("HP062 should include human_role:programmer reason")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasHP062 {
|
||||
t.Error("HP062 should fire for programmer in teach_mode with cobot")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllHumanRoles_Count(t *testing.T) {
|
||||
roles := AllHumanRoles()
|
||||
if len(roles) != 6 {
|
||||
t.Errorf("expected 6 human roles, got %d", len(roles))
|
||||
}
|
||||
}
|
||||
|
||||
func splitTransition(tr string) []string {
|
||||
// Split on → (UTF-8: 0xE2 0x86 0x92)
|
||||
idx := 0
|
||||
|
||||
Reference in New Issue
Block a user