package handlers import ( "net/http" "strings" "github.com/breakpilot/ai-compliance-sdk/internal/ucca" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // ============================================================================ // RAG Library Search (Phase 6) // ============================================================================ // IACELibrarySearchRequest represents a semantic search against the IACE library corpus. type IACELibrarySearchRequest struct { Query string `json:"query" binding:"required"` Category string `json:"category,omitempty"` TopK int `json:"top_k,omitempty"` Filters []string `json:"filters,omitempty"` } // SearchLibrary handles POST /iace/library-search // Performs semantic search across the IACE hazard/component/measure library in Qdrant. func (h *IACEHandler) SearchLibrary(c *gin.Context) { var req IACELibrarySearchRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } topK := req.TopK if topK <= 0 || topK > 50 { topK = 10 } // Use regulation filter for category-based search within the IACE collection var filters []string if req.Category != "" { filters = append(filters, req.Category) } filters = append(filters, req.Filters...) results, err := h.ragClient.SearchCollection( c.Request.Context(), "bp_iace_libraries", req.Query, filters, topK, ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "RAG search failed", "details": err.Error(), }) return } if results == nil { results = []ucca.LegalSearchResult{} } c.JSON(http.StatusOK, gin.H{ "query": req.Query, "results": results, "total": len(results), }) } // EnrichTechFileSection handles POST /projects/:id/tech-file/:section/enrich // Uses RAG to find relevant library content for a specific tech file section. func (h *IACEHandler) EnrichTechFileSection(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } sectionType := c.Param("section") if sectionType == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "section type required"}) 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 } // Build a contextual query based on section type and project data queryParts := []string{project.MachineName, project.MachineType} switch sectionType { case "risk_assessment_report", "hazard_log_combined": queryParts = append(queryParts, "Gefaehrdungen", "Risikobewertung", "ISO 12100") case "essential_requirements": queryParts = append(queryParts, "Sicherheitsanforderungen", "Maschinenrichtlinie") case "design_specifications": queryParts = append(queryParts, "Konstruktionsspezifikation", "Sicherheitskonzept") case "test_reports": queryParts = append(queryParts, "Pruefbericht", "Verifikation", "Nachweis") case "standards_applied": queryParts = append(queryParts, "harmonisierte Normen", "EN ISO") case "ai_risk_management": queryParts = append(queryParts, "KI-Risikomanagement", "AI Act", "Algorithmen") case "ai_human_oversight": queryParts = append(queryParts, "menschliche Aufsicht", "Human Oversight", "KI-Transparenz") default: queryParts = append(queryParts, sectionType) } query := strings.Join(queryParts, " ") results, err := h.ragClient.SearchCollection( c.Request.Context(), "bp_iace_libraries", query, nil, 5, ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "RAG enrichment failed", "details": err.Error(), }) return } if results == nil { results = []ucca.LegalSearchResult{} } c.JSON(http.StatusOK, gin.H{ "project_id": projectID.String(), "section_type": sectionType, "query": query, "context": results, "total": len(results), }) }