+ Die Branchenauswahl steuert welche branchenspezifischen Gefaehrdungsmuster (z.B. Medizintechnik, Lebensmittel, Aufzuege) bei der Risikoanalyse beruecksichtigt werden. Branchenfremde Muster werden automatisch ausgeblendet.
+
+
+ onChange('industry_sectors', v)}
+ options={INDUSTRY_SECTOR_OPTIONS}
+ helpText="Waehlen Sie alle zutreffenden Branchen. Bei Mehrfachauswahl werden alle relevanten Gefaehrdungen beruecksichtigt."
+ />
+
)
}
diff --git a/admin-compliance/app/sdk/iace/[projectId]/interview/_types.ts b/admin-compliance/app/sdk/iace/[projectId]/interview/_types.ts
index c158a15..4214098 100644
--- a/admin-compliance/app/sdk/iace/[projectId]/interview/_types.ts
+++ b/admin-compliance/app/sdk/iace/[projectId]/interview/_types.ts
@@ -35,6 +35,9 @@ export interface LimitsFormData {
// Section 6: Betroffene Personen
person_groups: string[]
qualification_requirements: string
+
+ // Section 7: Einsatzbereich / Branche (fuer Pattern-Filterung)
+ industry_sectors: string[]
}
export const EMPTY_LIMITS_FORM: LimitsFormData = {
@@ -59,6 +62,7 @@ export const EMPTY_LIMITS_FORM: LimitsFormData = {
pneumatic_hydraulic_interfaces: '',
person_groups: [],
qualification_requirements: '',
+ industry_sectors: [],
}
export const AREA_OF_USE_OPTIONS = [
@@ -77,6 +81,43 @@ export const OPERATING_MODE_OPTIONS = [
'Wartung',
]
+export const INDUSTRY_SECTOR_OPTIONS = [
+ 'Allgemeiner Maschinenbau',
+ 'Automobil / Zulieferer',
+ 'Robotik / Cobot',
+ 'Medizintechnik',
+ 'Lebensmittel / Getraenke',
+ 'Verpackung',
+ 'Pharma / Chemie',
+ 'Bau / Baumaschinen',
+ 'Forst / Holzbearbeitung',
+ 'Aufzuege / Foerdertechnik',
+ 'Textil',
+ 'Landmaschinen',
+ 'Druck / Papier',
+ 'Metall / CNC',
+ 'Schweissen / Oberflaechentechnik',
+]
+
+/** Maps display labels to MachineTypes for pattern engine filtering */
+export const INDUSTRY_TO_MACHINE_TYPES: Record = {
+ 'Allgemeiner Maschinenbau': ['general_industry'],
+ 'Automobil / Zulieferer': ['automotive'],
+ 'Robotik / Cobot': ['robotics_cobot', 'cobot'],
+ 'Medizintechnik': ['medical_device', 'infusion_pump', 'ventilator', 'patient_monitor'],
+ 'Lebensmittel / Getraenke': ['food_processing'],
+ 'Verpackung': ['packaging'],
+ 'Pharma / Chemie': ['chemical', 'pharmaceutical'],
+ 'Bau / Baumaschinen': ['construction', 'crane', 'excavator'],
+ 'Forst / Holzbearbeitung': ['forestry', 'woodworking', 'circular_saw'],
+ 'Aufzuege / Foerdertechnik': ['elevator', 'lift', 'escalator', 'conveyor'],
+ 'Textil': ['textile', 'spinning', 'weaving', 'finishing'],
+ 'Landmaschinen': ['agricultural', 'tractor', 'harvester'],
+ 'Druck / Papier': ['printing'],
+ 'Metall / CNC': ['cnc', 'metalworking', 'lathe', 'milling'],
+ 'Schweissen / Oberflaechentechnik': ['welding', 'surface_treatment'],
+}
+
export const PERSON_GROUP_OPTIONS = [
'Bedienpersonal',
'Einrichter',
@@ -93,7 +134,7 @@ export interface FormSection {
number: number
title: string
description: string
- icon: 'clipboard' | 'target' | 'alert' | 'box' | 'link' | 'users'
+ icon: 'clipboard' | 'target' | 'alert' | 'box' | 'link' | 'users' | 'briefcase'
}
export const FORM_SECTIONS: FormSection[] = [
@@ -139,4 +180,11 @@ export const FORM_SECTIONS: FormSection[] = [
description: 'Personengruppen und Qualifikationsanforderungen',
icon: 'users',
},
+ {
+ id: 'industry_sector',
+ number: 7,
+ title: 'Einsatzbereich / Branche',
+ description: 'Branche bestimmt welche branchenspezifischen Gefaehrdungen beruecksichtigt werden',
+ icon: 'briefcase',
+ },
]
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 a41f204..67f3e45 100644
--- a/ai-compliance-sdk/internal/api/handlers/iace_handler_init.go
+++ b/ai-compliance-sdk/internal/api/handlers/iace_handler_init.go
@@ -1,7 +1,6 @@
package handlers
import (
- "encoding/json"
"fmt"
"net/http"
@@ -89,7 +88,6 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
if len(existingComps) == 0 && len(parseResult.Components) > 0 {
created := 0
for _, comp := range parseResult.Components {
- // Derive component type from tags
compType := deriveComponentType(comp.Tags)
_, cerr := h.store.CreateComponent(ctx, iace.CreateComponentRequest{
ProjectID: projectID,
@@ -117,9 +115,8 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
energyIDs = append(energyIDs, e.SourceID)
}
- // Merge explicit operational_states from UI with parsed states from narrative
operationalStates := mergeStringSlices(parseResult.OperationalStates, extractOperationalStatesFromMetadata(project.Metadata))
- stateTransitions := parseResult.StateTransitions
+ machineTypes := extractIndustrySectorsFromMetadata(project.Metadata)
engine := iace.NewPatternEngine()
matchOutput := engine.Match(iace.MatchInput{
@@ -128,8 +125,9 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
LifecyclePhases: parseResult.LifecyclePhases,
CustomTags: parseResult.CustomTags,
OperationalStates: operationalStates,
- StateTransitions: stateTransitions,
+ StateTransitions: parseResult.StateTransitions,
HumanRoles: parseResult.Roles,
+ MachineTypes: machineTypes,
})
steps = append(steps, InitStep{
Name: "Patterns abgeglichen",
@@ -143,14 +141,12 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
hazardIDsByCategory := make(map[string]uuid.UUID)
if len(existingHazards) == 0 && len(matchOutput.MatchedPatterns) > 0 {
- // Get first component for hazard assignment
comps, _ := h.store.ListComponents(ctx, projectID)
var defaultCompID uuid.UUID
if len(comps) > 0 {
defaultCompID = comps[0].ID
}
- // Deduplicate by category — one hazard per category
created := 0
seenCat := make(map[string]bool)
for _, mp := range matchOutput.MatchedPatterns {
@@ -164,18 +160,13 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
if name == "" {
name = cat
}
- scenario := mp.ScenarioDE
- hazardType := mp.GeneratedHazardType
- if hazardType == "" {
- hazardType = iace.DefaultHazardType
- }
hz, cerr := h.store.CreateHazard(ctx, iace.CreateHazardRequest{
ProjectID: projectID,
ComponentID: defaultCompID,
Name: name,
- Description: scenario,
+ Description: mp.ScenarioDE,
Category: cat,
- Scenario: scenario,
+ Scenario: mp.ScenarioDE,
Function: iace.EncodeOpStates(mp.OperationalStates),
TriggerEvent: mp.TriggerDE,
PossibleHarm: mp.HarmDE,
@@ -198,7 +189,7 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
}
steps = append(steps, hazardStep)
- // ── Step 6: Create mitigations (pattern-suggested + category fallback) ──
+ // ── Step 6: Create mitigations ──
existingMits, _ := h.store.ListMitigationsByProject(ctx, projectID)
mitStep := InitStep{Name: "Massnahmen erstellt", Status: "skipped"}
@@ -214,7 +205,6 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
created := 0
usedMeasureIDs := make(map[string]bool)
- // A) Pattern-suggested measures (direct reference)
for _, sm := range matchOutput.SuggestedMeasures {
entry, ok := measureByID[sm.MeasureID]
if !ok || usedMeasureIDs[sm.MeasureID] {
@@ -229,10 +219,8 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
rt = iace.ReductionTypeInformation
}
_, cerr := h.store.CreateMitigation(ctx, iace.CreateMitigationRequest{
- HazardID: hazardID,
- ReductionType: rt,
- Name: entry.Name,
- Description: entry.Description,
+ HazardID: hazardID, ReductionType: rt,
+ Name: entry.Name, Description: entry.Description,
})
if cerr == nil {
created++
@@ -240,13 +228,10 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
}
}
- // B) Category fallback — for each hazard category, add measures
- // from the library that match (but weren't pattern-suggested)
for hazCat, hazID := range hazardIDsByCategory {
measCat := patternCatToMeasureCat(hazCat)
- candidates := measuresByCat[measCat]
added := 0
- for _, m := range candidates {
+ for _, m := range measuresByCat[measCat] {
if usedMeasureIDs[m.ID] || added >= 8 {
break
}
@@ -255,10 +240,8 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
rt = iace.ReductionTypeInformation
}
_, cerr := h.store.CreateMitigation(ctx, iace.CreateMitigationRequest{
- HazardID: hazID,
- ReductionType: rt,
- Name: m.Name,
- Description: m.Description,
+ HazardID: hazID, ReductionType: rt,
+ Name: m.Name, Description: m.Description,
})
if cerr == nil {
created++
@@ -267,7 +250,6 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
}
}
}
-
mitStep = InitStep{Name: "Massnahmen erstellt", Status: "done", Count: created}
} else if len(existingMits) > 0 {
mitStep.Details = "Bereits vorhanden"
@@ -285,11 +267,7 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
if normResult != nil {
normCount = len(normResult.ANorms) + len(normResult.B1Norms) + len(normResult.B2Norms) + len(normResult.CNorms)
}
- steps = append(steps, InitStep{
- Name: "Normen vorgeschlagen",
- Status: "done",
- Count: normCount,
- })
+ steps = append(steps, InitStep{Name: "Normen vorgeschlagen", Status: "done", Count: normCount})
// ── Audit trail ──
h.store.AddAuditEntry(ctx, projectID, "project_initialization", projectID,
@@ -301,172 +279,9 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
"project_id": projectID.String(),
"steps": steps,
"summary": gin.H{
- "components": steps[1].Count,
- "patterns": steps[2].Count,
- "hazards": steps[3].Count,
- "mitigations": steps[4].Count,
- "norms": steps[5].Count,
+ "components": steps[1].Count, "patterns": steps[2].Count,
+ "hazards": steps[3].Count, "mitigations": steps[4].Count,
+ "norms": steps[5].Count,
},
})
}
-
-// extractNarrativeFromMetadata builds a combined text from the limits_form.
-func extractNarrativeFromMetadata(metadata json.RawMessage) string {
- if metadata == nil {
- return ""
- }
- var meta map[string]json.RawMessage
- if err := json.Unmarshal(metadata, &meta); err != nil {
- return ""
- }
- limitsRaw, ok := meta["limits_form"]
- if !ok {
- return ""
- }
- var limits map[string]interface{}
- if err := json.Unmarshal(limitsRaw, &limits); err != nil {
- return ""
- }
-
- textFields := []string{
- "general_description", "intended_purpose", "foreseeable_misuse",
- "space_limits", "time_limits", "environmental_conditions",
- "energy_sources", "materials_processed", "operating_modes",
- "maintenance_requirements", "personnel_requirements",
- "interfaces_description", "control_system_description",
- "safety_functions_description",
- }
- var result string
- for _, field := range textFields {
- if v, ok := limits[field]; ok {
- if s, ok := v.(string); ok && s != "" {
- result += s + "\n\n"
- }
- }
- }
- return result
-}
-
-// patternCatToMeasureCat maps pattern hazard categories to measure categories.
-// Patterns use "mechanical_hazard", measures use "mechanical".
-func patternCatToMeasureCat(patternCat string) string {
- m := map[string]string{
- "mechanical_hazard": "mechanical",
- "electrical_hazard": "electrical",
- "thermal_hazard": "thermal",
- "noise_vibration": "noise_vibration",
- "pneumatic_hydraulic": "pneumatic_hydraulic",
- "material_environmental": "material_environmental",
- "ergonomic": "ergonomic",
- "ergonomic_hazard": "ergonomic",
- "software_fault": "software_control",
- "safety_function_failure": "safety_function",
- "fire_explosion": "thermal",
- "radiation_hazard": "material_environmental",
- "unauthorized_access": "cyber_network",
- "communication_failure": "cyber_network",
- "firmware_corruption": "cyber_network",
- "logging_audit_failure": "cyber_network",
- "ai_misclassification": "ai_specific",
- "false_classification": "ai_specific",
- "model_drift": "ai_specific",
- "data_poisoning": "ai_specific",
- "sensor_spoofing": "ai_specific",
- "unintended_bias": "ai_specific",
- "sensor_fault": "software_control",
- "configuration_error": "software_control",
- "update_failure": "software_control",
- "hmi_error": "software_control",
- "emc_hazard": "electrical",
- "maintenance_hazard": "mechanical",
- "mode_confusion": "software_control",
- }
- if cat, ok := m[patternCat]; ok {
- return cat
- }
- return "general"
-}
-
-// deriveComponentType guesses the component type from its tags.
-func deriveComponentType(tags []string) iace.ComponentType {
- for _, t := range tags {
- switch {
- case t == "software" || t == "has_software":
- return iace.ComponentTypeSoftware
- case t == "firmware" || t == "has_firmware":
- return iace.ComponentTypeFirmware
- case t == "has_ai" || t == "ai_model":
- return iace.ComponentTypeAIModel
- case t == "hmi" || t == "display" || t == "touchscreen":
- return iace.ComponentTypeHMI
- case t == "sensor" || t == "camera":
- return iace.ComponentTypeSensor
- case t == "electric_motor" || t == "electric_drive":
- return iace.ComponentTypeElectrical
- case t == "networked" || t == "ethernet" || t == "wifi":
- return iace.ComponentTypeNetwork
- case t == "hydraulic" || t == "pneumatic":
- return iace.ComponentTypeActuator
- }
- }
- return iace.ComponentTypeMechanical
-}
-
-// extractOperationalStatesFromMetadata reads the explicit operational_states
-// selection that the user set via the Betriebszustand-UI.
-func extractOperationalStatesFromMetadata(metadata json.RawMessage) []string {
- if metadata == nil {
- return nil
- }
- var meta map[string]json.RawMessage
- if err := json.Unmarshal(metadata, &meta); err != nil {
- return nil
- }
- raw, ok := meta["operational_states"]
- if !ok {
- return nil
- }
- var states []string
- if err := json.Unmarshal(raw, &states); err != nil {
- return nil
- }
- return states
-}
-
-// mergeStringSlices merges two string slices, deduplicating entries.
-func mergeStringSlices(a, b []string) []string {
- seen := make(map[string]bool, len(a)+len(b))
- var result []string
- for _, s := range a {
- if !seen[s] {
- seen[s] = true
- result = append(result, s)
- }
- }
- for _, s := range b {
- if !seen[s] {
- seen[s] = true
- result = append(result, s)
- }
- }
- return result
-}
-
-// findHazardForMeasureByCategory finds a matching hazard for a measure.
-func findHazardForMeasureByCategory(measureCat string, hazardsByCategory map[string]uuid.UUID) uuid.UUID {
- // Direct match
- if id, ok := hazardsByCategory[measureCat]; ok {
- return id
- }
- // Fuzzy match — "mechanical" matches "mechanical_hazard"
- for cat, id := range hazardsByCategory {
- if len(measureCat) > 3 && len(cat) > 3 && cat[:4] == measureCat[:4] {
- return id
- }
- }
- // Fallback: first hazard
- for _, id := range hazardsByCategory {
- return id
- }
- return uuid.Nil
-}
diff --git a/ai-compliance-sdk/internal/api/handlers/iace_handler_init_helpers.go b/ai-compliance-sdk/internal/api/handlers/iace_handler_init_helpers.go
new file mode 100644
index 0000000..6f5fb2e
--- /dev/null
+++ b/ai-compliance-sdk/internal/api/handlers/iace_handler_init_helpers.go
@@ -0,0 +1,207 @@
+package handlers
+
+import (
+ "encoding/json"
+
+ "github.com/breakpilot/ai-compliance-sdk/internal/iace"
+ "github.com/google/uuid"
+)
+
+// extractNarrativeFromMetadata builds a combined text from the limits_form.
+func extractNarrativeFromMetadata(metadata json.RawMessage) string {
+ if metadata == nil {
+ return ""
+ }
+ var meta map[string]json.RawMessage
+ if err := json.Unmarshal(metadata, &meta); err != nil {
+ return ""
+ }
+ limitsRaw, ok := meta["limits_form"]
+ if !ok {
+ return ""
+ }
+ var limits map[string]interface{}
+ if err := json.Unmarshal(limitsRaw, &limits); err != nil {
+ return ""
+ }
+
+ textFields := []string{
+ "general_description", "intended_purpose", "foreseeable_misuse",
+ "space_limits", "time_limits", "environmental_conditions",
+ "energy_sources", "materials_processed", "operating_modes",
+ "maintenance_requirements", "personnel_requirements",
+ "interfaces_description", "control_system_description",
+ "safety_functions_description",
+ }
+ var result string
+ for _, field := range textFields {
+ if v, ok := limits[field]; ok {
+ if s, ok := v.(string); ok && s != "" {
+ result += s + "\n\n"
+ }
+ }
+ }
+ return result
+}
+
+// patternCatToMeasureCat maps pattern hazard categories to measure categories.
+func patternCatToMeasureCat(patternCat string) string {
+ m := map[string]string{
+ "mechanical_hazard": "mechanical", "electrical_hazard": "electrical",
+ "thermal_hazard": "thermal", "noise_vibration": "noise_vibration",
+ "pneumatic_hydraulic": "pneumatic_hydraulic", "material_environmental": "material_environmental",
+ "ergonomic": "ergonomic", "ergonomic_hazard": "ergonomic",
+ "software_fault": "software_control", "safety_function_failure": "safety_function",
+ "fire_explosion": "thermal", "radiation_hazard": "material_environmental",
+ "unauthorized_access": "cyber_network", "communication_failure": "cyber_network",
+ "firmware_corruption": "cyber_network", "logging_audit_failure": "cyber_network",
+ "ai_misclassification": "ai_specific", "false_classification": "ai_specific",
+ "model_drift": "ai_specific", "data_poisoning": "ai_specific",
+ "sensor_spoofing": "ai_specific", "unintended_bias": "ai_specific",
+ "sensor_fault": "software_control", "configuration_error": "software_control",
+ "update_failure": "software_control", "hmi_error": "software_control",
+ "emc_hazard": "electrical", "maintenance_hazard": "mechanical",
+ "mode_confusion": "software_control", "chemical_risk": "material_environmental",
+ }
+ if cat, ok := m[patternCat]; ok {
+ return cat
+ }
+ return "general"
+}
+
+// deriveComponentType guesses the component type from its tags.
+func deriveComponentType(tags []string) iace.ComponentType {
+ for _, t := range tags {
+ switch {
+ case t == "software" || t == "has_software":
+ return iace.ComponentTypeSoftware
+ case t == "firmware" || t == "has_firmware":
+ return iace.ComponentTypeFirmware
+ case t == "has_ai" || t == "ai_model":
+ return iace.ComponentTypeAIModel
+ case t == "hmi" || t == "display" || t == "touchscreen":
+ return iace.ComponentTypeHMI
+ case t == "sensor" || t == "camera":
+ return iace.ComponentTypeSensor
+ case t == "electric_motor" || t == "electric_drive":
+ return iace.ComponentTypeElectrical
+ case t == "networked" || t == "ethernet" || t == "wifi":
+ return iace.ComponentTypeNetwork
+ case t == "hydraulic" || t == "pneumatic":
+ return iace.ComponentTypeActuator
+ }
+ }
+ return iace.ComponentTypeMechanical
+}
+
+// extractOperationalStatesFromMetadata reads the explicit operational_states
+// selection that the user set via the Betriebszustand-UI.
+func extractOperationalStatesFromMetadata(metadata json.RawMessage) []string {
+ if metadata == nil {
+ return nil
+ }
+ var meta map[string]json.RawMessage
+ if err := json.Unmarshal(metadata, &meta); err != nil {
+ return nil
+ }
+ raw, ok := meta["operational_states"]
+ if !ok {
+ return nil
+ }
+ var states []string
+ if err := json.Unmarshal(raw, &states); err != nil {
+ return nil
+ }
+ return states
+}
+
+// mergeStringSlices merges two string slices, deduplicating entries.
+func mergeStringSlices(a, b []string) []string {
+ seen := make(map[string]bool, len(a)+len(b))
+ var result []string
+ for _, s := range a {
+ if !seen[s] {
+ seen[s] = true
+ result = append(result, s)
+ }
+ }
+ for _, s := range b {
+ if !seen[s] {
+ seen[s] = true
+ result = append(result, s)
+ }
+ }
+ return result
+}
+
+// extractIndustrySectorsFromMetadata reads the industry_sectors selection
+// from project metadata and maps them to MachineTypes for pattern filtering.
+func extractIndustrySectorsFromMetadata(metadata json.RawMessage) []string {
+ if metadata == nil {
+ return nil
+ }
+ var meta map[string]json.RawMessage
+ if err := json.Unmarshal(metadata, &meta); err != nil {
+ return nil
+ }
+ limitsRaw, ok := meta["limits_form"]
+ if !ok {
+ return nil
+ }
+ var limits map[string]json.RawMessage
+ if err := json.Unmarshal(limitsRaw, &limits); err != nil {
+ return nil
+ }
+ sectorsRaw, ok := limits["industry_sectors"]
+ if !ok {
+ return nil
+ }
+ var sectors []string
+ if err := json.Unmarshal(sectorsRaw, §ors); err != nil {
+ return nil
+ }
+ labelMap := map[string][]string{
+ "Allgemeiner Maschinenbau": {"general_industry"},
+ "Automobil / Zulieferer": {"automotive"},
+ "Robotik / Cobot": {"robotics_cobot", "cobot"},
+ "Medizintechnik": {"medical_device", "infusion_pump", "ventilator", "patient_monitor"},
+ "Lebensmittel / Getraenke": {"food_processing"},
+ "Verpackung": {"packaging"},
+ "Pharma / Chemie": {"chemical", "pharmaceutical"},
+ "Bau / Baumaschinen": {"construction", "crane", "excavator"},
+ "Forst / Holzbearbeitung": {"forestry", "woodworking", "circular_saw"},
+ "Aufzuege / Foerdertechnik": {"elevator", "lift", "escalator", "conveyor"},
+ "Textil": {"textile", "spinning", "weaving", "finishing"},
+ "Landmaschinen": {"agricultural", "tractor", "harvester"},
+ "Druck / Papier": {"printing"},
+ "Metall / CNC": {"cnc", "metalworking", "lathe", "milling"},
+ "Schweissen / Oberflaechentechnik": {"welding", "surface_treatment"},
+ }
+ var result []string
+ seen := make(map[string]bool)
+ for _, sector := range sectors {
+ for _, mt := range labelMap[sector] {
+ if !seen[mt] {
+ seen[mt] = true
+ result = append(result, mt)
+ }
+ }
+ }
+ return result
+}
+
+// findHazardForMeasureByCategory finds a matching hazard for a measure.
+func findHazardForMeasureByCategory(measureCat string, hazardsByCategory map[string]uuid.UUID) uuid.UUID {
+ if id, ok := hazardsByCategory[measureCat]; ok {
+ return id
+ }
+ for cat, id := range hazardsByCategory {
+ if len(measureCat) > 3 && len(cat) > 3 && cat[:4] == measureCat[:4] {
+ return id
+ }
+ }
+ for _, id := range hazardsByCategory {
+ return id
+ }
+ return uuid.Nil
+}
diff --git a/ai-compliance-sdk/internal/iace/pattern_engine.go b/ai-compliance-sdk/internal/iace/pattern_engine.go
index bb6c91c..2fbe310 100644
--- a/ai-compliance-sdk/internal/iace/pattern_engine.go
+++ b/ai-compliance-sdk/internal/iace/pattern_engine.go
@@ -20,6 +20,10 @@ type MatchInput struct {
// FailureModes are the active failure mode IDs relevant for this project.
// Used to filter patterns that require specific failure modes.
FailureModes []string `json:"failure_modes,omitempty"`
+ // MachineTypes are the industry sectors / machine types for this project.
+ // Patterns with MachineTypes filter only fire if at least one matches.
+ // Empty = all patterns fire (backwards compatible).
+ MachineTypes []string `json:"machine_types,omitempty"`
}
// MatchOutput contains the results of pattern matching.
@@ -317,6 +321,22 @@ func (e *PatternEngine) Match(input MatchInput) *MatchOutput {
// patternMatches checks if a pattern fires given the resolved tag set, lifecycle phases,
// operational states, and state transitions.
func patternMatches(p HazardPattern, tagSet map[string]bool, input MatchInput) bool {
+ // If pattern requires specific machine types, project must match at least one.
+ // Patterns without MachineTypes fire for ALL projects (backwards compatible).
+ if len(p.MachineTypes) > 0 && len(input.MachineTypes) > 0 {
+ found := false
+ mtSet := toSet(input.MachineTypes)
+ for _, mt := range p.MachineTypes {
+ if mtSet[mt] {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+
// All required component tags must be present (AND)
for _, t := range p.RequiredComponentTags {
if !tagSet[t] {