package handlers import ( "net/http" "sort" "strings" "sync" "github.com/breakpilot/ai-compliance-sdk/internal/ucca" "github.com/gin-gonic/gin" ) // Cached CE corpus document index — built once on first request. var ( ceCorpusOnce sync.Once ceCorpusDocs []ucca.CEDocumentInfo ceCorpusErr error ) // ListCECorpusDocuments returns the deduplicated document index from bp_compliance_ce. // GET /iace/ce-corpus-documents func (h *IACEHandler) ListCECorpusDocuments(c *gin.Context) { ceCorpusOnce.Do(func() { ceCorpusDocs, ceCorpusErr = h.ragClient.ScrollDocumentIndex( c.Request.Context(), "bp_compliance_ce", ) if ceCorpusErr == nil { sort.Slice(ceCorpusDocs, func(i, j int) bool { if ceCorpusDocs[i].Category != ceCorpusDocs[j].Category { return ceCorpusDocs[i].Category < ceCorpusDocs[j].Category } return ceCorpusDocs[i].RegulationID < ceCorpusDocs[j].RegulationID }) } }) if ceCorpusErr != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "failed to load CE corpus index: " + ceCorpusErr.Error(), }) return } // Optional search filter query := strings.ToLower(c.Query("q")) category := c.Query("category") filtered := ceCorpusDocs if query != "" || category != "" { filtered = make([]ucca.CEDocumentInfo, 0, len(ceCorpusDocs)) for _, d := range ceCorpusDocs { if category != "" && d.Category != category { continue } if query != "" { haystack := strings.ToLower(d.RegulationID + " " + d.NameDE + " " + d.NameEN + " " + d.SourceOrg) if !strings.Contains(haystack, query) { continue } } filtered = append(filtered, d) } } // Group by category for the response groups := make(map[string][]ucca.CEDocumentInfo) for _, d := range filtered { cat := d.Category if cat == "" { cat = "other" } groups[cat] = append(groups[cat], d) } totalChunks := 0 for _, d := range filtered { totalChunks += d.ChunkCount } c.JSON(http.StatusOK, gin.H{ "documents": filtered, "groups": groups, "total": len(filtered), "total_chunks": totalChunks, }) }