package handlers import ( "net/http" "github.com/breakpilot/ai-compliance-sdk/internal/iace" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // ============================================================================ // ISO 12100 Endpoints // ============================================================================ // ListLifecyclePhases handles GET /lifecycle-phases // Returns the 12 machine lifecycle phases with DE/EN labels. func (h *IACEHandler) ListLifecyclePhases(c *gin.Context) { phases, err := h.store.ListLifecyclePhases(c.Request.Context()) if err != nil { // Fallback: return hardcoded 25 phases if DB table not yet migrated phases = []iace.LifecyclePhaseInfo{ {ID: "transport", LabelDE: "Transport", LabelEN: "Transport", Sort: 1}, {ID: "storage", LabelDE: "Lagerung", LabelEN: "Storage", Sort: 2}, {ID: "assembly", LabelDE: "Montage", LabelEN: "Assembly", Sort: 3}, {ID: "installation", LabelDE: "Installation", LabelEN: "Installation", Sort: 4}, {ID: "commissioning", LabelDE: "Inbetriebnahme", LabelEN: "Commissioning", Sort: 5}, {ID: "parameterization", LabelDE: "Parametrierung", LabelEN: "Parameterization", Sort: 6}, {ID: "setup", LabelDE: "Einrichten / Setup", LabelEN: "Setup", Sort: 7}, {ID: "normal_operation", LabelDE: "Normalbetrieb", LabelEN: "Normal Operation", Sort: 8}, {ID: "automatic_operation", LabelDE: "Automatikbetrieb", LabelEN: "Automatic Operation", Sort: 9}, {ID: "manual_operation", LabelDE: "Handbetrieb", LabelEN: "Manual Operation", Sort: 10}, {ID: "teach_mode", LabelDE: "Teach-Modus", LabelEN: "Teach Mode", Sort: 11}, {ID: "production_start", LabelDE: "Produktionsstart", LabelEN: "Production Start", Sort: 12}, {ID: "production_stop", LabelDE: "Produktionsstopp", LabelEN: "Production Stop", Sort: 13}, {ID: "process_monitoring", LabelDE: "Prozessueberwachung", LabelEN: "Process Monitoring", Sort: 14}, {ID: "cleaning", LabelDE: "Reinigung", LabelEN: "Cleaning", Sort: 15}, {ID: "maintenance", LabelDE: "Wartung", LabelEN: "Maintenance", Sort: 16}, {ID: "inspection", LabelDE: "Inspektion", LabelEN: "Inspection", Sort: 17}, {ID: "calibration", LabelDE: "Kalibrierung", LabelEN: "Calibration", Sort: 18}, {ID: "fault_clearing", LabelDE: "Stoerungsbeseitigung", LabelEN: "Fault Clearing", Sort: 19}, {ID: "repair", LabelDE: "Reparatur", LabelEN: "Repair", Sort: 20}, {ID: "changeover", LabelDE: "Umruestung", LabelEN: "Changeover", Sort: 21}, {ID: "software_update", LabelDE: "Software-Update", LabelEN: "Software Update", Sort: 22}, {ID: "remote_maintenance", LabelDE: "Fernwartung", LabelEN: "Remote Maintenance", Sort: 23}, {ID: "decommissioning", LabelDE: "Ausserbetriebnahme", LabelEN: "Decommissioning", Sort: 24}, {ID: "disposal", LabelDE: "Demontage / Entsorgung", LabelEN: "Dismantling / Disposal", Sort: 25}, } } if phases == nil { phases = []iace.LifecyclePhaseInfo{} } c.JSON(http.StatusOK, gin.H{ "lifecycle_phases": phases, "total": len(phases), }) } // ListProtectiveMeasures handles GET /protective-measures-library // Returns the protective measures library, optionally filtered by ?reduction_type and ?hazard_category. func (h *IACEHandler) ListProtectiveMeasures(c *gin.Context) { reductionType := c.Query("reduction_type") hazardCategory := c.Query("hazard_category") all := iace.GetProtectiveMeasureLibrary() var filtered []iace.ProtectiveMeasureEntry for _, entry := range all { if reductionType != "" && entry.ReductionType != reductionType { continue } if hazardCategory != "" && entry.HazardCategory != hazardCategory && entry.HazardCategory != "general" && entry.HazardCategory != "" { continue } filtered = append(filtered, entry) } if filtered == nil { filtered = []iace.ProtectiveMeasureEntry{} } c.JSON(http.StatusOK, gin.H{ "protective_measures": filtered, "total": len(filtered), }) } // ValidateMitigationHierarchy handles POST /projects/:id/validate-mitigation-hierarchy // Validates if the proposed mitigation type follows the 3-step hierarchy principle. func (h *IACEHandler) ValidateMitigationHierarchy(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } var req iace.ValidateMitigationHierarchyRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Get existing mitigations for the hazard mitigations, err := h.store.ListMitigations(c.Request.Context(), req.HazardID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } _ = projectID // projectID used for authorization context warnings := h.engine.ValidateProtectiveMeasureHierarchy(req.ReductionType, mitigations) c.JSON(http.StatusOK, iace.ValidateMitigationHierarchyResponse{ Valid: len(warnings) == 0, Warnings: warnings, }) } // ListRoles handles GET /roles // Returns the 20 affected person roles reference data. func (h *IACEHandler) ListRoles(c *gin.Context) { roles, err := h.store.ListRoles(c.Request.Context()) if err != nil { // Fallback: return hardcoded roles if DB table not yet migrated roles = []iace.RoleInfo{ {ID: "operator", LabelDE: "Maschinenbediener", LabelEN: "Machine Operator", Sort: 1}, {ID: "setter", LabelDE: "Einrichter", LabelEN: "Setter", Sort: 2}, {ID: "maintenance_tech", LabelDE: "Wartungstechniker", LabelEN: "Maintenance Technician", Sort: 3}, {ID: "service_tech", LabelDE: "Servicetechniker", LabelEN: "Service Technician", Sort: 4}, {ID: "cleaning_staff", LabelDE: "Reinigungspersonal", LabelEN: "Cleaning Staff", Sort: 5}, {ID: "production_manager", LabelDE: "Produktionsleiter", LabelEN: "Production Manager", Sort: 6}, {ID: "safety_officer", LabelDE: "Sicherheitsbeauftragter", LabelEN: "Safety Officer", Sort: 7}, {ID: "electrician", LabelDE: "Elektriker", LabelEN: "Electrician", Sort: 8}, {ID: "software_engineer", LabelDE: "Softwareingenieur", LabelEN: "Software Engineer", Sort: 9}, {ID: "maintenance_manager", LabelDE: "Instandhaltungsleiter", LabelEN: "Maintenance Manager", Sort: 10}, {ID: "plant_operator", LabelDE: "Anlagenfahrer", LabelEN: "Plant Operator", Sort: 11}, {ID: "qa_inspector", LabelDE: "Qualitaetssicherung", LabelEN: "Quality Assurance", Sort: 12}, {ID: "logistics_staff", LabelDE: "Logistikpersonal", LabelEN: "Logistics Staff", Sort: 13}, {ID: "subcontractor", LabelDE: "Fremdfirma / Subunternehmer", LabelEN: "Subcontractor", Sort: 14}, {ID: "visitor", LabelDE: "Besucher", LabelEN: "Visitor", Sort: 15}, {ID: "auditor", LabelDE: "Auditor", LabelEN: "Auditor", Sort: 16}, {ID: "it_admin", LabelDE: "IT-Administrator", LabelEN: "IT Administrator", Sort: 17}, {ID: "remote_service", LabelDE: "Fernwartungsdienst", LabelEN: "Remote Service", Sort: 18}, {ID: "plant_owner", LabelDE: "Betreiber", LabelEN: "Plant Owner / Operator", Sort: 19}, {ID: "emergency_responder", LabelDE: "Notfallpersonal", LabelEN: "Emergency Responder", Sort: 20}, } } if roles == nil { roles = []iace.RoleInfo{} } c.JSON(http.StatusOK, gin.H{ "roles": roles, "total": len(roles), }) } // ListEvidenceTypes handles GET /evidence-types // Returns the 50 evidence/verification types reference data. func (h *IACEHandler) ListEvidenceTypes(c *gin.Context) { types, err := h.store.ListEvidenceTypes(c.Request.Context()) if err != nil { // Fallback: return empty if not migrated types = []iace.EvidenceTypeInfo{} } if types == nil { types = []iace.EvidenceTypeInfo{} } category := c.Query("category") if category != "" { var filtered []iace.EvidenceTypeInfo for _, t := range types { if t.Category == category { filtered = append(filtered, t) } } if filtered == nil { filtered = []iace.EvidenceTypeInfo{} } types = filtered } c.JSON(http.StatusOK, gin.H{ "evidence_types": types, "total": len(types), }) } // ============================================================================ // Component Library & Energy Sources (Phase 1) // ============================================================================ // ListComponentLibrary handles GET /component-library // Returns the built-in component library with optional category filter. func (h *IACEHandler) ListComponentLibrary(c *gin.Context) { category := c.Query("category") all := iace.GetComponentLibrary() var filtered []iace.ComponentLibraryEntry for _, entry := range all { if category != "" && entry.Category != category { continue } filtered = append(filtered, entry) } if filtered == nil { filtered = []iace.ComponentLibraryEntry{} } c.JSON(http.StatusOK, gin.H{ "components": filtered, "total": len(filtered), }) } // ListEnergySources handles GET /energy-sources // Returns the built-in energy source library. func (h *IACEHandler) ListEnergySources(c *gin.Context) { sources := iace.GetEnergySources() c.JSON(http.StatusOK, gin.H{ "energy_sources": sources, "total": len(sources), }) } // ============================================================================ // Tag Taxonomy (Phase 2) // ============================================================================ // ListTags handles GET /tags // Returns the tag taxonomy with optional domain filter. func (h *IACEHandler) ListTags(c *gin.Context) { domain := c.Query("domain") all := iace.GetTagTaxonomy() var filtered []iace.TagEntry for _, entry := range all { if domain != "" && entry.Domain != domain { continue } filtered = append(filtered, entry) } if filtered == nil { filtered = []iace.TagEntry{} } c.JSON(http.StatusOK, gin.H{ "tags": filtered, "total": len(filtered), }) } // ============================================================================ // Hazard Patterns & Pattern Engine (Phase 3+4) // ============================================================================ // ListHazardPatterns handles GET /hazard-patterns // Returns all built-in hazard patterns. func (h *IACEHandler) ListHazardPatterns(c *gin.Context) { patterns := iace.GetBuiltinHazardPatterns() patterns = append(patterns, iace.GetExtendedHazardPatterns()...) c.JSON(http.StatusOK, gin.H{ "patterns": patterns, "total": len(patterns), }) } // MatchPatterns handles POST /projects/:id/match-patterns // Runs the pattern engine against the project's components and energy sources. func (h *IACEHandler) MatchPatterns(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } // Verify project exists project, err := h.store.GetProject(c.Request.Context(), projectID) if err != nil || project == nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } var input iace.MatchInput if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } engine := iace.NewPatternEngine() result := engine.Match(input) c.JSON(http.StatusOK, result) } // ApplyPatternResults handles POST /projects/:id/apply-patterns // Accepts matched patterns and creates concrete hazards, mitigations, and // verification plans in the project. func (h *IACEHandler) ApplyPatternResults(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } tenantID, err := getTenantID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } project, err := h.store.GetProject(c.Request.Context(), projectID) if err != nil || project == nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } var req struct { AcceptedHazards []iace.CreateHazardRequest `json:"accepted_hazards"` AcceptedMeasures []iace.CreateMitigationRequest `json:"accepted_measures"` AcceptedEvidence []iace.CreateVerificationPlanRequest `json:"accepted_evidence"` SourcePatternIDs []string `json:"source_pattern_ids"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx := c.Request.Context() var createdHazards int var createdMeasures int var createdEvidence int // Create hazards for _, hazardReq := range req.AcceptedHazards { hazardReq.ProjectID = projectID _, err := h.store.CreateHazard(ctx, hazardReq) if err != nil { continue } createdHazards++ } // Create mitigations for _, mitigReq := range req.AcceptedMeasures { _, err := h.store.CreateMitigation(ctx, mitigReq) if err != nil { continue } createdMeasures++ } // Create verification plans for _, evidReq := range req.AcceptedEvidence { evidReq.ProjectID = projectID _, err := h.store.CreateVerificationPlan(ctx, evidReq) if err != nil { continue } createdEvidence++ } // Audit trail h.store.AddAuditEntry(ctx, projectID, "pattern_matching", projectID, iace.AuditActionCreate, tenantID.String(), nil, mustMarshalJSON(map[string]interface{}{ "source_patterns": req.SourcePatternIDs, "created_hazards": createdHazards, "created_measures": createdMeasures, "created_evidence": createdEvidence, }), ) c.JSON(http.StatusOK, gin.H{ "created_hazards": createdHazards, "created_measures": createdMeasures, "created_evidence": createdEvidence, "message": "Pattern results applied successfully", }) } // SuggestMeasuresForHazard handles POST /projects/:id/hazards/:hid/suggest-measures // Suggests measures for a specific hazard based on its tags and category. func (h *IACEHandler) SuggestMeasuresForHazard(c *gin.Context) { hazardID, err := uuid.Parse(c.Param("hid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid hazard ID"}) return } hazard, err := h.store.GetHazard(c.Request.Context(), hazardID) if err != nil || hazard == nil { c.JSON(http.StatusNotFound, gin.H{"error": "hazard not found"}) return } // Find measures matching the hazard category all := iace.GetProtectiveMeasureLibrary() var suggested []iace.ProtectiveMeasureEntry for _, m := range all { if m.HazardCategory == hazard.Category || m.HazardCategory == "general" { suggested = append(suggested, m) } } if suggested == nil { suggested = []iace.ProtectiveMeasureEntry{} } c.JSON(http.StatusOK, gin.H{ "hazard_id": hazardID.String(), "hazard_category": hazard.Category, "suggested_measures": suggested, "total": len(suggested), }) } // SuggestEvidenceForMitigation handles POST /projects/:id/mitigations/:mid/suggest-evidence // Suggests evidence types for a specific mitigation. func (h *IACEHandler) SuggestEvidenceForMitigation(c *gin.Context) { mitigationID, err := uuid.Parse(c.Param("mid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid mitigation ID"}) return } mitigation, err := h.store.GetMitigation(c.Request.Context(), mitigationID) if err != nil || mitigation == nil { c.JSON(http.StatusNotFound, gin.H{"error": "mitigation not found"}) return } // Map reduction type to relevant evidence tags var relevantTags []string switch mitigation.ReductionType { case iace.ReductionTypeDesign: relevantTags = []string{"design_evidence", "analysis_evidence"} case iace.ReductionTypeProtective: relevantTags = []string{"test_evidence", "inspection_evidence"} case iace.ReductionTypeInformation: relevantTags = []string{"training_evidence", "operational_evidence"} } resolver := iace.NewTagResolver() suggested := resolver.FindEvidenceByTags(relevantTags) if suggested == nil { suggested = []iace.EvidenceTypeInfo{} } c.JSON(http.StatusOK, gin.H{ "mitigation_id": mitigationID.String(), "reduction_type": string(mitigation.ReductionType), "suggested_evidence": suggested, "total": len(suggested), }) }