diff --git a/ai-compliance-sdk/internal/api/handlers/iace_handler_init.go b/ai-compliance-sdk/internal/api/handlers/iace_handler_init.go index 99c6a2f..a6cda39 100644 --- a/ai-compliance-sdk/internal/api/handlers/iace_handler_init.go +++ b/ai-compliance-sdk/internal/api/handlers/iace_handler_init.go @@ -104,6 +104,7 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) { CustomTags: parseResult.CustomTags, OperationalStates: parseResult.OperationalStates, StateTransitions: parseResult.StateTransitions, + HumanRoles: parseResult.Roles, }) steps = append(steps, InitStep{ Name: "Patterns abgeglichen", diff --git a/ai-compliance-sdk/internal/api/handlers/iace_handler_parser.go b/ai-compliance-sdk/internal/api/handlers/iace_handler_parser.go index aea1bda..1a507fe 100644 --- a/ai-compliance-sdk/internal/api/handlers/iace_handler_parser.go +++ b/ai-compliance-sdk/internal/api/handlers/iace_handler_parser.go @@ -75,6 +75,7 @@ func (h *IACEHandler) ParseNarrative(c *gin.Context) { CustomTags: parseResult.CustomTags, OperationalStates: parseResult.OperationalStates, StateTransitions: parseResult.StateTransitions, + HumanRoles: parseResult.Roles, } matchOutput := engine.Match(matchInput) diff --git a/ai-compliance-sdk/internal/iace/hazard_pattern_types.go b/ai-compliance-sdk/internal/iace/hazard_pattern_types.go index 5c25c85..58b825e 100644 --- a/ai-compliance-sdk/internal/iace/hazard_pattern_types.go +++ b/ai-compliance-sdk/internal/iace/hazard_pattern_types.go @@ -42,4 +42,26 @@ type HazardPattern struct { // Hazards at transitions are critical — e.g. "maintenance→automatic_operation" // is where "unexpected restart" fatalities occur. Empty = no transition filter. StateTransitions []string `json:"state_transitions,omitempty"` + // HumanRoles restricts this pattern to projects where specific human roles + // interact with the machine. Empty/nil = fires for all roles (backwards compatible). + // Standard roles: operator, maintenance_tech, programmer, cleaning_staff, bystander, supervisor. + HumanRoles []string `json:"human_roles,omitempty"` +} + +// Standard human roles for machinery interaction (ISO 12100 + BetrSichV). +const ( + RoleOperator = "operator" + RoleMaintenanceTech = "maintenance_tech" + RoleProgrammer = "programmer" + RoleCleaningStaff = "cleaning_staff" + RoleBystander = "bystander" + RoleSupervisor = "supervisor" +) + +// AllHumanRoles returns the 6 standard human roles. +func AllHumanRoles() []string { + return []string{ + RoleOperator, RoleMaintenanceTech, RoleProgrammer, + RoleCleaningStaff, RoleBystander, RoleSupervisor, + } } diff --git a/ai-compliance-sdk/internal/iace/hazard_patterns_cobot.go b/ai-compliance-sdk/internal/iace/hazard_patterns_cobot.go index 318f966..5f9c32b 100644 --- a/ai-compliance-sdk/internal/iace/hazard_patterns_cobot.go +++ b/ai-compliance-sdk/internal/iace/hazard_patterns_cobot.go @@ -9,7 +9,7 @@ func GetCobotHazardPatterns() []HazardPattern { // Collaborative Robot (Cobot) Patterns (HP059-HP065) // ================================================================ { - ID: "HP059", NameDE: "Kollision Mensch-Roboter (Kraft/Geschwindigkeit)", NameEN: "Human-robot collision (force/speed)", + ID: "HP059", HumanRoles: []string{"operator"}, NameDE: "Kollision Mensch-Roboter (Kraft/Geschwindigkeit)", NameEN: "Human-robot collision (force/speed)", RequiredComponentTags: []string{"collaborative_operation", "moving_part"}, RequiredEnergyTags: []string{}, GeneratedHazardCats: []string{"mechanical_hazard"}, @@ -63,7 +63,7 @@ func GetCobotHazardPatterns() []HazardPattern { DefaultSeverity: 4, DefaultExposure: 3, }, { - ID: "HP062", OperationalStates: []string{"teach_mode"}, NameDE: "Fehlprogrammierung Kraft-/Geschwindigkeitsgrenzwerte", NameEN: "Misprogramming of force/speed limits", + ID: "HP062", OperationalStates: []string{"teach_mode"}, HumanRoles: []string{"programmer"}, NameDE: "Fehlprogrammierung Kraft-/Geschwindigkeitsgrenzwerte", NameEN: "Misprogramming of force/speed limits", RequiredComponentTags: []string{"programmable", "force_limited"}, RequiredEnergyTags: []string{}, GeneratedHazardCats: []string{"safety_function_failure"}, @@ -94,7 +94,7 @@ func GetCobotHazardPatterns() []HazardPattern { DefaultSeverity: 3, DefaultExposure: 3, }, { - ID: "HP064", OperationalStates: []string{"automatic_operation"}, NameDE: "Quetschen im Roboter-Arbeitsraum (nicht-kollaborierend)", NameEN: "Crushing in robot workspace (non-collaborative)", + ID: "HP064", OperationalStates: []string{"automatic_operation"}, HumanRoles: []string{"operator", "maintenance_tech"}, NameDE: "Quetschen im Roboter-Arbeitsraum (nicht-kollaborierend)", NameEN: "Crushing in robot workspace (non-collaborative)", RequiredComponentTags: []string{"moving_part", "high_force", "sensor_part"}, RequiredEnergyTags: []string{}, ExcludedComponentTags: []string{"collaborative_operation"}, diff --git a/ai-compliance-sdk/internal/iace/hazard_patterns_maintenance_ext.go b/ai-compliance-sdk/internal/iace/hazard_patterns_maintenance_ext.go index 6304665..32c019c 100644 --- a/ai-compliance-sdk/internal/iace/hazard_patterns_maintenance_ext.go +++ b/ai-compliance-sdk/internal/iace/hazard_patterns_maintenance_ext.go @@ -5,70 +5,70 @@ package iace func GetMaintenanceExtPatterns() []HazardPattern { return []HazardPattern{ // — Wartung allgemein (HP700-HP709) — - {ID: "HP700", OperationalStates: []string{"maintenance"}, NameDE: "LOTO-Fehler: Maschine nicht freigeschaltet", NameEN: "LOTO failure: not locked out", + {ID: "HP700", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "LOTO-Fehler: Maschine nicht freigeschaltet", NameEN: "LOTO failure: not locked out", RequiredComponentTags: []string{"moving_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"mechanical_hazard", "safety_function_failure"}, SuggestedMeasureIDs: []string{"M054", "M082"}, SuggestedEvidenceIDs: []string{"E08", "E20"}, Priority: 95, ScenarioDE: "Arbeit ohne Freischaltung der Maschine", TriggerDE: "Fehlende LOTO-Prozedur", HarmDE: "Erfassen durch anlaufende Teile, Tod", AffectedDE: "Instandhalter", ZoneDE: "Gesamte Maschine", DefaultSeverity: 5, DefaultExposure: 3}, - {ID: "HP701", OperationalStates: []string{"maintenance"}, NameDE: "Restenergie trotz Abschaltung", NameEN: "Residual energy after shutdown", + {ID: "HP701", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Restenergie trotz Abschaltung", NameEN: "Residual energy after shutdown", RequiredComponentTags: []string{"moving_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"mechanical_hazard", "pneumatic_hydraulic"}, SuggestedMeasureIDs: []string{"M054", "M082"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 90, ScenarioDE: "Gespeicherte Energie entlaedt sich bei Wartung", TriggerDE: "Nicht abgelassener Druckspeicher", HarmDE: "Unkontrollierte Bewegung, Quetschung", AffectedDE: "Instandhalter", ZoneDE: "Antriebe, Speicher", DefaultSeverity: 5, DefaultExposure: 3}, - {ID: "HP702", OperationalStates: []string{"maintenance"}, NameDE: "Falsches Werkzeug bei Wartung", NameEN: "Wrong tool during maintenance", + {ID: "HP702", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Falsches Werkzeug bei Wartung", NameEN: "Wrong tool during maintenance", RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"mechanical_hazard"}, SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 50, ScenarioDE: "Ungeeignetes oder defektes Werkzeug", TriggerDE: "Falscher Schraubenschluessel", HarmDE: "Abrutschen, Quetschung", AffectedDE: "Instandhalter", ZoneDE: "Wartungszugang", DefaultSeverity: 3, DefaultExposure: 4}, - {ID: "HP703", OperationalStates: []string{"maintenance"}, NameDE: "Fehlende Qualifikation des Instandhalters", NameEN: "Insufficient maintainer qualification", + {ID: "HP703", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Fehlende Qualifikation des Instandhalters", NameEN: "Insufficient maintainer qualification", RequiredComponentTags: []string{"electrical_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"electrical_hazard", "safety_function_failure"}, SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 70, ScenarioDE: "Unqualifiziertes Personal an Elektrik", TriggerDE: "Keine Elektrofachkraft", HarmDE: "Stromschlag, Fehlverdrahtung", AffectedDE: "Instandhalter", ZoneDE: "Schaltschrank", DefaultSeverity: 4, DefaultExposure: 3}, - {ID: "HP704", OperationalStates: []string{"maintenance"}, NameDE: "Herabfallen schwerer Teile bei Demontage", NameEN: "Heavy parts falling during disassembly", + {ID: "HP704", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Herabfallen schwerer Teile bei Demontage", NameEN: "Heavy parts falling during disassembly", RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"mechanical_hazard"}, SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 75, ScenarioDE: "Schwere Teile fallen bei Demontage herab", TriggerDE: "Fehlende Abstuetzung", HarmDE: "Quetschung, Frakturen, Tod", AffectedDE: "Instandhalter", ZoneDE: "Wartungsbereich", DefaultSeverity: 5, DefaultExposure: 3}, - {ID: "HP705", OperationalStates: []string{"maintenance"}, NameDE: "Vergessenes Werkzeug in Maschine", NameEN: "Forgotten tool in machine", + {ID: "HP705", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Vergessenes Werkzeug in Maschine", NameEN: "Forgotten tool in machine", RequiredComponentTags: []string{"moving_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"mechanical_hazard"}, SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 65, ScenarioDE: "Zurueckgelassenes Werkzeug wird Geschoss", TriggerDE: "Keine Werkzeugkontrolle", HarmDE: "Herausgeschleudertes Teil, Verletzungen", AffectedDE: "Bedienpersonal", ZoneDE: "Arbeitsraum", DefaultSeverity: 4, DefaultExposure: 2}, - {ID: "HP706", OperationalStates: []string{"maintenance"}, NameDE: "Schnittwunden an scharfkantigen Teilen", NameEN: "Cuts on sharp-edged parts", + {ID: "HP706", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Schnittwunden an scharfkantigen Teilen", NameEN: "Cuts on sharp-edged parts", RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"mechanical_hazard"}, SuggestedMeasureIDs: []string{"M005", "M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 45, ScenarioDE: "Scharfe Kanten und Grate verletzen", TriggerDE: "Fehlende Schutzhandschuhe", HarmDE: "Schnittwunden, Abschuerfungen", AffectedDE: "Instandhalter", ZoneDE: "Blechverkleidungen", DefaultSeverity: 2, DefaultExposure: 4}, - {ID: "HP707", OperationalStates: []string{"maintenance"}, NameDE: "Verbrennung an heissen Teilen bei Wartung", NameEN: "Burn on hot parts during maintenance", + {ID: "HP707", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Verbrennung an heissen Teilen bei Wartung", NameEN: "Burn on hot parts during maintenance", RequiredComponentTags: []string{"high_temperature"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"thermal_hazard"}, SuggestedMeasureIDs: []string{"M005", "M082"}, SuggestedEvidenceIDs: []string{"E10"}, Priority: 60, ScenarioDE: "Maschine nicht abgekuehlt vor Wartung", TriggerDE: "Zu kurze Abkuehlzeit", HarmDE: "Kontaktverbrennungen", AffectedDE: "Instandhalter", ZoneDE: "Heizplatten, Motorgehaeuse", DefaultSeverity: 3, DefaultExposure: 4}, - {ID: "HP708", OperationalStates: []string{"maintenance"}, NameDE: "Fehlende Wartungsfreigabe", NameEN: "Missing maintenance permit", + {ID: "HP708", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Fehlende Wartungsfreigabe", NameEN: "Missing maintenance permit", RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"safety_function_failure"}, SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 70, ScenarioDE: "Wartung ohne formale Freigabe", TriggerDE: "Fehlender Erlaubnisschein", HarmDE: "Unerwarteter Maschinenbetrieb", AffectedDE: "Instandhalter", ZoneDE: "Gesamte Maschine", DefaultSeverity: 5, DefaultExposure: 3}, - {ID: "HP709", OperationalStates: []string{"maintenance"}, NameDE: "Biologische Gefaehrdung bei KSS-Wartung", NameEN: "Biological hazard MWF maintenance", + {ID: "HP709", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Biologische Gefaehrdung bei KSS-Wartung", NameEN: "Biological hazard MWF maintenance", RequiredComponentTags: []string{"chemical_risk"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"material_environmental"}, SuggestedMeasureIDs: []string{"M005", "M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 50, @@ -76,28 +76,28 @@ func GetMaintenanceExtPatterns() []HazardPattern { HarmDE: "Hautinfektionen, Atemwegsbeschwerden", AffectedDE: "Instandhalter", ZoneDE: "KSS-System", DefaultSeverity: 2, DefaultExposure: 3}, // — Einrichten / Umruesten (HP710-HP719) — - {ID: "HP710", OperationalStates: []string{"teach_mode"}, NameDE: "Falsche Parameter nach Umruestung", NameEN: "Wrong parameters after changeover", + {ID: "HP710", OperationalStates: []string{"teach_mode"}, HumanRoles: []string{"programmer"}, NameDE: "Falsche Parameter nach Umruestung", NameEN: "Wrong parameters after changeover", RequiredComponentTags: []string{"programmable"}, RequiredLifecycles: []string{"setup"}, GeneratedHazardCats: []string{"safety_function_failure"}, SuggestedMeasureIDs: []string{"M106", "M082"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 75, ScenarioDE: "Falsche Maschinenparameter nach Produktwechsel", TriggerDE: "Falsche Rezeptnummer", HarmDE: "Uebergeschwindigkeit, Werkzeugbruch", AffectedDE: "Einrichter", ZoneDE: "Bedienterminal", DefaultSeverity: 4, DefaultExposure: 3}, - {ID: "HP711", OperationalStates: []string{"maintenance"}, NameDE: "Quetschung bei Werkzeugwechsel", NameEN: "Crushing during tool change", + {ID: "HP711", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Quetschung bei Werkzeugwechsel", NameEN: "Crushing during tool change", RequiredComponentTags: []string{"moving_part", "high_force"}, RequiredLifecycles: []string{"setup"}, GeneratedHazardCats: []string{"mechanical_hazard"}, SuggestedMeasureIDs: []string{"M003", "M054"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 80, ScenarioDE: "Schwere Werkzeuge manuell gewechselt", TriggerDE: "Kein Hebezeug, Finger eingeklemmt", HarmDE: "Quetschung, Amputation", AffectedDE: "Einrichter", ZoneDE: "Werkzeugaufnahme", DefaultSeverity: 4, DefaultExposure: 4}, - {ID: "HP712", OperationalStates: []string{"teach_mode", "manual_operation"}, NameDE: "Unkontrollierte Bewegung bei Testlauf", NameEN: "Uncontrolled movement test run", + {ID: "HP712", OperationalStates: []string{"teach_mode", "manual_operation"}, HumanRoles: []string{"programmer", "maintenance_tech"}, NameDE: "Unkontrollierte Bewegung bei Testlauf", NameEN: "Uncontrolled movement test run", RequiredComponentTags: []string{"moving_part", "programmable"}, RequiredLifecycles: []string{"setup"}, GeneratedHazardCats: []string{"mechanical_hazard"}, SuggestedMeasureIDs: []string{"M106", "M054"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 80, ScenarioDE: "Testlauf mit Person im Gefahrenbereich", TriggerDE: "Volle Geschwindigkeit, kein Schutz", HarmDE: "Erfassen, Quetschen", AffectedDE: "Einrichter", ZoneDE: "Arbeitsraum", DefaultSeverity: 5, DefaultExposure: 3}, - {ID: "HP713", OperationalStates: []string{"teach_mode"}, NameDE: "Einrichtbetrieb ohne reduzierte Geschwindigkeit", NameEN: "Setup without reduced speed", + {ID: "HP713", OperationalStates: []string{"teach_mode"}, HumanRoles: []string{"programmer"}, NameDE: "Einrichtbetrieb ohne reduzierte Geschwindigkeit", NameEN: "Setup without reduced speed", RequiredComponentTags: []string{"moving_part", "programmable"}, RequiredLifecycles: []string{"setup"}, GeneratedHazardCats: []string{"mechanical_hazard", "safety_function_failure"}, SuggestedMeasureIDs: []string{"M106", "M082"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 85, diff --git a/ai-compliance-sdk/internal/iace/hazard_patterns_operational.go b/ai-compliance-sdk/internal/iace/hazard_patterns_operational.go index 863e066..847ec09 100644 --- a/ai-compliance-sdk/internal/iace/hazard_patterns_operational.go +++ b/ai-compliance-sdk/internal/iace/hazard_patterns_operational.go @@ -72,7 +72,7 @@ func GetOperationalHazardPatterns() []HazardPattern { DefaultSeverity: 4, DefaultExposure: 3, }, { - ID: "HP070", NameDE: "Eingriff in laufende Maschine bei Stoerung", OperationalStates: []string{"recovery_mode"}, NameEN: "Intervention in running machine during fault", + ID: "HP070", NameDE: "Eingriff in laufende Maschine bei Stoerung", OperationalStates: []string{"recovery_mode"}, HumanRoles: []string{"operator"}, NameEN: "Intervention in running machine during fault", RequiredComponentTags: []string{"moving_part"}, RequiredLifecycles: []string{"fault_clearing"}, ExcludedComponentTags: []string{"interlocked"}, @@ -120,7 +120,7 @@ func GetOperationalHazardPatterns() []HazardPattern { // Wartung / Instandhaltung (HP073-HP079) // ================================================================ { - ID: "HP073", NameDE: "Wartung ohne LOTO (Lockout/Tagout)", OperationalStates: []string{"maintenance"}, NameEN: "Maintenance without LOTO", + ID: "HP073", NameDE: "Wartung ohne LOTO (Lockout/Tagout)", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameEN: "Maintenance without LOTO", RequiredComponentTags: []string{"moving_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"maintenance_hazard"}, @@ -136,7 +136,7 @@ func GetOperationalHazardPatterns() []HazardPattern { DefaultSeverity: 5, DefaultExposure: 3, }, { - ID: "HP074", OperationalStates: []string{"maintenance"}, NameDE: "Sturz von Wartungsbuehne / Leiter", NameEN: "Fall from maintenance platform / ladder", + ID: "HP074", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Sturz von Wartungsbuehne / Leiter", NameEN: "Fall from maintenance platform / ladder", RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance", "cleaning"}, GeneratedHazardCats: []string{"mechanical_hazard"}, @@ -150,7 +150,7 @@ func GetOperationalHazardPatterns() []HazardPattern { DefaultSeverity: 4, DefaultExposure: 2, }, { - ID: "HP075", OperationalStates: []string{"maintenance"}, NameDE: "Kontakt mit heissen Teilen bei Wartung", NameEN: "Contact with hot parts during maintenance", + ID: "HP075", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Kontakt mit heissen Teilen bei Wartung", NameEN: "Contact with hot parts during maintenance", RequiredComponentTags: []string{"high_temperature"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"thermal_hazard"}, @@ -165,7 +165,7 @@ func GetOperationalHazardPatterns() []HazardPattern { DefaultSeverity: 3, DefaultExposure: 3, }, { - ID: "HP076", OperationalStates: []string{"maintenance"}, NameDE: "Kontakt mit Gefahrstoffen bei Wartung", NameEN: "Contact with hazardous substances during maintenance", + ID: "HP076", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Kontakt mit Gefahrstoffen bei Wartung", NameEN: "Contact with hazardous substances during maintenance", RequiredComponentTags: []string{"chemical_risk"}, RequiredLifecycles: []string{"maintenance", "cleaning"}, GeneratedHazardCats: []string{"material_environmental"}, @@ -179,7 +179,7 @@ func GetOperationalHazardPatterns() []HazardPattern { DefaultSeverity: 3, DefaultExposure: 3, }, { - ID: "HP077", OperationalStates: []string{"maintenance"}, NameDE: "Elektrischer Schlag bei Wartungsarbeiten", NameEN: "Electric shock during maintenance", + ID: "HP077", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Elektrischer Schlag bei Wartungsarbeiten", NameEN: "Electric shock during maintenance", RequiredComponentTags: []string{"high_voltage"}, RequiredLifecycles: []string{"maintenance", "fault_clearing"}, GeneratedHazardCats: []string{"electrical_hazard"}, @@ -195,7 +195,7 @@ func GetOperationalHazardPatterns() []HazardPattern { DefaultSeverity: 5, DefaultExposure: 3, }, { - ID: "HP078", OperationalStates: []string{"maintenance"}, NameDE: "Ergonomische Belastung bei Wartungszugang", NameEN: "Ergonomic strain at maintenance access", + ID: "HP078", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Ergonomische Belastung bei Wartungszugang", NameEN: "Ergonomic strain at maintenance access", RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"ergonomic"}, @@ -228,7 +228,7 @@ func GetOperationalHazardPatterns() []HazardPattern { // Einrichten / Umruesten / Werkzeugwechsel (HP080-HP085) // ================================================================ { - ID: "HP080", NameDE: "Quetschen bei Werkzeugwechsel", OperationalStates: []string{"maintenance", "teach_mode"}, NameEN: "Crushing during tool change", + ID: "HP080", NameDE: "Quetschen bei Werkzeugwechsel", OperationalStates: []string{"maintenance", "teach_mode"}, HumanRoles: []string{"maintenance_tech", "programmer"}, NameEN: "Crushing during tool change", RequiredComponentTags: []string{"crush_point", "high_force"}, RequiredLifecycles: []string{"changeover", "setup"}, GeneratedHazardCats: []string{"mechanical_hazard"}, diff --git a/ai-compliance-sdk/internal/iace/pattern_engine.go b/ai-compliance-sdk/internal/iace/pattern_engine.go index f3c4722..cc33756 100644 --- a/ai-compliance-sdk/internal/iace/pattern_engine.go +++ b/ai-compliance-sdk/internal/iace/pattern_engine.go @@ -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 } diff --git a/ai-compliance-sdk/internal/iace/pattern_engine_test.go b/ai-compliance-sdk/internal/iace/pattern_engine_test.go index fe1a2c1..f50c3e6 100644 --- a/ai-compliance-sdk/internal/iace/pattern_engine_test.go +++ b/ai-compliance-sdk/internal/iace/pattern_engine_test.go @@ -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