Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
330 lines
8.3 KiB
Go
330 lines
8.3 KiB
Go
// Package api provides HTTP handlers for the Compliance Engine
|
|
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/breakpilot/compliance-sdk/services/compliance-engine/internal/ucca"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Assess performs a full compliance assessment
|
|
func Assess(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var state map[string]interface{}
|
|
if err := c.ShouldBindJSON(&state); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
result := engine.Assess(state)
|
|
result.Timestamp = time.Now().Format(time.RFC3339)
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
}
|
|
|
|
// AssessControl assesses a single control
|
|
func AssessControl(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var req struct {
|
|
ControlID string `json:"control_id" binding:"required"`
|
|
State map[string]interface{} `json:"state"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
controls := engine.GetControls()
|
|
ctrl, exists := controls[req.ControlID]
|
|
if !exists {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Control not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"control": ctrl,
|
|
"status": "ASSESSED",
|
|
"compliance": true,
|
|
"recommendations": []string{},
|
|
})
|
|
}
|
|
}
|
|
|
|
// AssessRegulation assesses compliance with a specific regulation
|
|
func AssessRegulation(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var req struct {
|
|
RegulationCode string `json:"regulation_code" binding:"required"`
|
|
State map[string]interface{} `json:"state"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
regulations := engine.GetRegulations()
|
|
reg, exists := regulations[req.RegulationCode]
|
|
if !exists {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Regulation not found"})
|
|
return
|
|
}
|
|
|
|
result := engine.Assess(req.State)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"regulation": reg,
|
|
"score": result.ByRegulation[req.RegulationCode],
|
|
"findings": filterFindings(result.Findings, req.RegulationCode),
|
|
"assessed_at": time.Now().Format(time.RFC3339),
|
|
})
|
|
}
|
|
}
|
|
|
|
// CalculateScore calculates the compliance score
|
|
func CalculateScore(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var state map[string]interface{}
|
|
if err := c.ShouldBindJSON(&state); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
result := engine.Assess(state)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"overall": result.OverallScore,
|
|
"trend": result.Trend,
|
|
"by_regulation": result.ByRegulation,
|
|
"by_domain": result.ByDomain,
|
|
"calculated_at": time.Now().Format(time.RFC3339),
|
|
})
|
|
}
|
|
}
|
|
|
|
// ScoreBreakdown provides a detailed score breakdown
|
|
func ScoreBreakdown(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var state map[string]interface{}
|
|
if err := c.ShouldBindJSON(&state); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
result := engine.Assess(state)
|
|
|
|
// Calculate detailed breakdown
|
|
breakdown := gin.H{
|
|
"overall": gin.H{
|
|
"score": result.OverallScore,
|
|
"max": 100,
|
|
"trend": result.Trend,
|
|
"description": getScoreDescription(result.OverallScore),
|
|
},
|
|
"by_regulation": []gin.H{},
|
|
"by_domain": []gin.H{},
|
|
"by_category": gin.H{
|
|
"documentation": 75,
|
|
"technical": 80,
|
|
"organizational": 70,
|
|
},
|
|
}
|
|
|
|
for reg, score := range result.ByRegulation {
|
|
breakdown["by_regulation"] = append(
|
|
breakdown["by_regulation"].([]gin.H),
|
|
gin.H{"code": reg, "score": score, "status": getStatus(score)},
|
|
)
|
|
}
|
|
|
|
for domain, score := range result.ByDomain {
|
|
breakdown["by_domain"] = append(
|
|
breakdown["by_domain"].([]gin.H),
|
|
gin.H{"domain": domain, "score": score, "status": getStatus(score)},
|
|
)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, breakdown)
|
|
}
|
|
}
|
|
|
|
// GetObligations returns all regulatory obligations
|
|
func GetObligations(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
regulations := engine.GetRegulations()
|
|
obligations := []gin.H{}
|
|
|
|
for code, reg := range regulations {
|
|
for _, article := range reg.Articles {
|
|
obligations = append(obligations, gin.H{
|
|
"id": code + "-" + article,
|
|
"regulation_code": code,
|
|
"regulation_name": reg.Name,
|
|
"article": article,
|
|
"status": "PENDING",
|
|
})
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"obligations": obligations,
|
|
"total": len(obligations),
|
|
})
|
|
}
|
|
}
|
|
|
|
// GetObligationsByRegulation returns obligations for a specific regulation
|
|
func GetObligationsByRegulation(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
regCode := c.Param("regulation")
|
|
|
|
regulations := engine.GetRegulations()
|
|
reg, exists := regulations[regCode]
|
|
if !exists {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Regulation not found"})
|
|
return
|
|
}
|
|
|
|
obligations := []gin.H{}
|
|
for _, article := range reg.Articles {
|
|
obligations = append(obligations, gin.H{
|
|
"id": regCode + "-" + article,
|
|
"regulation_code": regCode,
|
|
"article": article,
|
|
"status": "PENDING",
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"regulation": reg,
|
|
"obligations": obligations,
|
|
"total": len(obligations),
|
|
})
|
|
}
|
|
}
|
|
|
|
// GetControlsCatalog returns the controls catalog
|
|
func GetControlsCatalog(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
controls := engine.GetControls()
|
|
result := []gin.H{}
|
|
|
|
for _, ctrl := range controls {
|
|
result = append(result, gin.H{
|
|
"id": ctrl.ID,
|
|
"name": ctrl.Name,
|
|
"description": ctrl.Description,
|
|
"domain": ctrl.Domain,
|
|
"category": ctrl.Category,
|
|
"objective": ctrl.Objective,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"controls": result,
|
|
"total": len(result),
|
|
})
|
|
}
|
|
}
|
|
|
|
// GetControlsByDomain returns controls for a specific domain
|
|
func GetControlsByDomain(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
domain := c.Param("domain")
|
|
controls := engine.GetControlsByDomain(domain)
|
|
|
|
result := []gin.H{}
|
|
for _, ctrl := range controls {
|
|
result = append(result, gin.H{
|
|
"id": ctrl.ID,
|
|
"name": ctrl.Name,
|
|
"description": ctrl.Description,
|
|
"category": ctrl.Category,
|
|
"objective": ctrl.Objective,
|
|
"guidance": ctrl.Guidance,
|
|
"evidence": ctrl.Evidence,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"domain": domain,
|
|
"controls": result,
|
|
"total": len(result),
|
|
})
|
|
}
|
|
}
|
|
|
|
// ListPolicies lists all loaded policies
|
|
func ListPolicies(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"total_rules": engine.RuleCount(),
|
|
"total_regulations": len(engine.GetRegulations()),
|
|
"total_controls": len(engine.GetControls()),
|
|
"regulations": getRegulationCodes(engine),
|
|
})
|
|
}
|
|
}
|
|
|
|
// GetPolicy returns details of a specific policy
|
|
func GetPolicy(engine *ucca.Engine) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
policyID := c.Param("id")
|
|
|
|
regulations := engine.GetRegulations()
|
|
if reg, exists := regulations[policyID]; exists {
|
|
c.JSON(http.StatusOK, reg)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Policy not found"})
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func filterFindings(findings []ucca.Finding, regulation string) []ucca.Finding {
|
|
result := []ucca.Finding{}
|
|
for _, f := range findings {
|
|
if f.Regulation == regulation {
|
|
result = append(result, f)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getScoreDescription(score int) string {
|
|
switch {
|
|
case score >= 90:
|
|
return "Excellent - Full compliance achieved"
|
|
case score >= 75:
|
|
return "Good - Minor improvements needed"
|
|
case score >= 50:
|
|
return "Fair - Significant work required"
|
|
default:
|
|
return "Poor - Major compliance gaps exist"
|
|
}
|
|
}
|
|
|
|
func getStatus(score int) string {
|
|
switch {
|
|
case score >= 80:
|
|
return "COMPLIANT"
|
|
case score >= 60:
|
|
return "PARTIAL"
|
|
default:
|
|
return "NON_COMPLIANT"
|
|
}
|
|
}
|
|
|
|
func getRegulationCodes(engine *ucca.Engine) []string {
|
|
regulations := engine.GetRegulations()
|
|
codes := make([]string, 0, len(regulations))
|
|
for code := range regulations {
|
|
codes = append(codes, code)
|
|
}
|
|
return codes
|
|
}
|