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 }