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:
Benjamin Admin
2026-05-10 08:22:55 +02:00
parent f201c01a06
commit f07c4db164
8 changed files with 183 additions and 25 deletions
@@ -14,6 +14,9 @@ type MatchInput struct {
// StateTransitions are active state transitions (format: "from→to").
// Used to detect transition-specific hazards like unexpected restart.
StateTransitions []string `json:"state_transitions,omitempty"`
// HumanRoles are the human roles interacting with the machine in this project.
// Used to filter patterns that only apply to specific roles (e.g. programmer, maintenance_tech).
HumanRoles []string `json:"human_roles,omitempty"`
}
// MatchOutput contains the results of pattern matching.
@@ -53,6 +56,7 @@ type PatternMatch struct {
RequiresExpert bool `json:"requires_expert,omitempty"`
OperationalStates []string `json:"operational_states,omitempty"`
StateTransitions []string `json:"state_transitions,omitempty"`
HumanRoles []string `json:"human_roles,omitempty"`
}
// HazardSuggestion is a suggested hazard from pattern matching.
@@ -195,6 +199,12 @@ func (e *PatternEngine) Match(input MatchInput) *MatchOutput {
reasons = append(reasons, MatchReason{Type: "state_transition", Tag: t, Met: transSet[t]})
}
}
if len(p.HumanRoles) > 0 {
roleSet := toSet(input.HumanRoles)
for _, r := range p.HumanRoles {
reasons = append(reasons, MatchReason{Type: "human_role", Tag: r, Met: roleSet[r]})
}
}
matchedPatterns = append(matchedPatterns, PatternMatch{
PatternID: p.ID,
@@ -214,6 +224,7 @@ func (e *PatternEngine) Match(input MatchInput) *MatchOutput {
RequiresExpert: p.RequiresExpertCalculation,
OperationalStates: p.OperationalStates,
StateTransitions: p.StateTransitions,
HumanRoles: p.HumanRoles,
})
for _, cat := range p.GeneratedHazardCats {
@@ -351,6 +362,21 @@ func patternMatches(p HazardPattern, tagSet map[string]bool, input MatchInput) b
}
}
// If pattern requires specific human roles, at least one must match.
if len(p.HumanRoles) > 0 && len(input.HumanRoles) > 0 {
found := false
roleSet := toSet(input.HumanRoles)
for _, r := range p.HumanRoles {
if roleSet[r] {
found = true
break
}
}
if !found {
return false
}
}
return true
}