package handlers import ( "encoding/json" "net/http" "time" "github.com/breakpilot/ai-compliance-sdk/internal/iace" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // ImportGroundTruth handles POST /projects/:id/benchmark/import-gt // Stores Ground Truth data in project metadata.ground_truth. func (h *IACEHandler) ImportGroundTruth(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } ctx := c.Request.Context() project, err := h.store.GetProject(ctx, projectID) if err != nil || project == nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } var gt iace.GroundTruth if err := c.ShouldBindJSON(>); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ground truth JSON: " + err.Error()}) return } if gt.ImportedAt == "" { gt.ImportedAt = time.Now().Format("2006-01-02") } // Merge into existing metadata meta := make(map[string]json.RawMessage) if project.Metadata != nil { _ = json.Unmarshal(project.Metadata, &meta) } gtJSON, _ := json.Marshal(gt) meta["ground_truth"] = gtJSON mergedMeta, _ := json.Marshal(meta) err = h.store.UpdateProjectMetadata(ctx, projectID, mergedMeta) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to store ground truth"}) return } c.JSON(http.StatusOK, gin.H{ "message": "ground truth imported", "entry_count": len(gt.Entries), "source_file": gt.SourceFile, }) } // RunBenchmark handles GET /projects/:id/benchmark?gt_project_id=:gtId // Compares engine hazards from project :id against GT from project :gtId. // If gt_project_id is omitted, looks for GT in the same project's metadata. func (h *IACEHandler) RunBenchmark(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } ctx := c.Request.Context() // Determine GT source gtProjectID := projectID if gtParam := c.Query("gt_project_id"); gtParam != "" { parsed, err := uuid.Parse(gtParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid gt_project_id"}) return } gtProjectID = parsed } // Load GT gtProject, err := h.store.GetProject(ctx, gtProjectID) if err != nil || gtProject == nil { c.JSON(http.StatusNotFound, gin.H{"error": "GT project not found"}) return } gt, err := iace.ParseGroundTruth(gtProject.Metadata) if err != nil || gt == nil { c.JSON(http.StatusNotFound, gin.H{"error": "no ground truth data in project metadata"}) return } // Load engine hazards + mitigations hazards, err := h.store.ListHazards(ctx, projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load hazards"}) return } mitigations, err := h.store.ListMitigationsByProject(ctx, projectID) if err != nil { mitigations = nil } result := iace.CompareBenchmark(gt, hazards, mitigations) c.JSON(http.StatusOK, result) } // GetBenchmarkSummary handles GET /projects/:id/benchmark/summary // Returns lightweight coverage metrics without full match details. func (h *IACEHandler) GetBenchmarkSummary(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } ctx := c.Request.Context() gtProjectID := projectID if gtParam := c.Query("gt_project_id"); gtParam != "" { parsed, err := uuid.Parse(gtParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid gt_project_id"}) return } gtProjectID = parsed } gtProject, err := h.store.GetProject(ctx, gtProjectID) if err != nil || gtProject == nil { c.JSON(http.StatusNotFound, gin.H{"error": "GT project not found"}) return } gt, err := iace.ParseGroundTruth(gtProject.Metadata) if err != nil || gt == nil { c.JSON(http.StatusNotFound, gin.H{"error": "no ground truth data"}) return } hazards, err := h.store.ListHazards(ctx, projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load hazards"}) return } mitigations, _ := h.store.ListMitigationsByProject(ctx, projectID) result := iace.CompareBenchmark(gt, hazards, mitigations) c.JSON(http.StatusOK, gin.H{ "coverage_score": result.CoverageScore, "measure_coverage": result.MeasureCoverage, "total_gt": result.TotalGT, "total_engine": result.TotalEngine, "matched_count": len(result.MatchedPairs), "missing_count": len(result.MissingFromEngine), "extra_count": len(result.ExtraInEngine), "category_breakdown": result.CategoryBreakdown, }) }