Files
breakpilot-compliance/ai-compliance-sdk/internal/api/handlers/obligations_handlers.go
Sharang Parnerkar 13f57c4519 refactor(go): split obligations, portfolio, rbac, whistleblower handlers and stores, roadmap parser
Split 7 files exceeding the 500 LOC hard cap into 16 files, all under 500 LOC.
No exported symbols renamed; zero behavior changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 10:00:15 +02:00

285 lines
9.1 KiB
Go

// PARTIAL DEPRECATED — CRUD-Overlap mit Python backend-compliance
//
// Die folgenden CRUD-Endpoints sind deprecated, da Python (obligation_routes.py)
// nun als Primary fuer List/Create/Update/Delete dient:
// - ListObligations, CreateObligation, GetObligation, UpdateObligation, DeleteObligation
//
// AKTIV bleiben (einzigartige AI-Features ohne Python-Aequivalent):
// - AssessFromScope, RunGapAnalysis, ExportAssessment, ExportDirect
// - ListAssessments, GetAssessment, UpdateAssessment
// - TOM-Controls: ListTOMControls, MapTOMControls, GetTOMGapAnalysis
// - GetObligationsByFramework, GetFrameworks
//
// Go-Routen werden NICHT entfernt (Abwaertskompatibilitaet), aber Frontend
// nutzt Python-Backend fuer alle CRUD-Operationen.
package handlers
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/breakpilot/ai-compliance-sdk/internal/rbac"
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
)
// ObligationsHandlers handles API requests for the generic obligations framework
type ObligationsHandlers struct {
registry *ucca.ObligationsRegistry
store *ucca.ObligationsStore // Optional: for persisting assessments
tomIndex *ucca.TOMControlIndex
tomMapper *ucca.TOMObligationMapper
gapAnalyzer *ucca.TOMGapAnalyzer
}
// NewObligationsHandlers creates a new ObligationsHandlers instance
func NewObligationsHandlers() *ObligationsHandlers {
h := &ObligationsHandlers{
registry: ucca.NewObligationsRegistry(),
}
h.initTOM()
return h
}
// NewObligationsHandlersWithStore creates a new ObligationsHandlers with a store
func NewObligationsHandlersWithStore(store *ucca.ObligationsStore) *ObligationsHandlers {
h := &ObligationsHandlers{
registry: ucca.NewObligationsRegistry(),
store: store,
}
h.initTOM()
return h
}
// initTOM initializes TOM control index, mapper, and gap analyzer
func (h *ObligationsHandlers) initTOM() {
tomIndex, err := ucca.LoadTOMControls()
if err != nil {
fmt.Printf("Warning: Could not load TOM controls: %v\n", err)
return
}
h.tomIndex = tomIndex
mapping, err := ucca.LoadV2TOMMapping()
if err != nil {
regs, err2 := ucca.LoadAllV2Regulations()
if err2 == nil {
var allObligations []ucca.V2Obligation
for _, reg := range regs {
allObligations = append(allObligations, reg.Obligations...)
}
h.tomMapper = ucca.NewTOMObligationMapperFromObligations(tomIndex, allObligations)
}
} else {
h.tomMapper = ucca.NewTOMObligationMapper(tomIndex, mapping)
}
if h.tomMapper != nil {
h.gapAnalyzer = ucca.NewTOMGapAnalyzer(h.tomMapper, tomIndex)
}
}
// RegisterRoutes registers all obligations-related routes
func (h *ObligationsHandlers) RegisterRoutes(r *gin.RouterGroup) {
obligations := r.Group("/obligations")
{
obligations.POST("/assess", h.AssessObligations)
obligations.GET("/:assessmentId", h.GetAssessment)
obligations.GET("/:assessmentId/by-regulation", h.GetByRegulation)
obligations.GET("/:assessmentId/by-deadline", h.GetByDeadline)
obligations.GET("/:assessmentId/by-responsible", h.GetByResponsible)
obligations.POST("/export/memo", h.ExportMemo)
obligations.POST("/export/direct", h.ExportMemoFromOverview)
obligations.GET("/regulations", h.ListRegulations)
obligations.GET("/regulations/:regulationId/decision-tree", h.GetDecisionTree)
obligations.POST("/quick-check", h.QuickCheck)
obligations.POST("/assess-from-scope", h.AssessFromScope)
obligations.GET("/tom-controls/for-obligation/:obligationId", h.GetTOMControlsForObligation)
obligations.POST("/gap-analysis", h.GapAnalysis)
obligations.GET("/tom-controls/:controlId/obligations", h.GetObligationsForControl)
}
}
// AssessObligations assesses which obligations apply based on provided facts
// POST /sdk/v1/ucca/obligations/assess
func (h *ObligationsHandlers) AssessObligations(c *gin.Context) {
tenantID := rbac.GetTenantID(c)
if tenantID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Tenant ID required"})
return
}
var req ucca.ObligationsAssessRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
return
}
if req.Facts == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Facts are required"})
return
}
overview := h.registry.EvaluateAll(tenantID, req.Facts, req.OrganizationName)
var warnings []string
if len(overview.ApplicableRegulations) == 0 {
warnings = append(warnings, "Keine der konfigurierten Regulierungen scheint anwendbar zu sein. Bitte prüfen Sie die eingegebenen Daten.")
}
if overview.ExecutiveSummary.OverdueObligations > 0 {
warnings = append(warnings, "Es gibt überfällige Pflichten, die sofortige Aufmerksamkeit erfordern.")
}
if h.store != nil {
assessment := &ucca.ObligationsAssessment{
ID: overview.ID,
TenantID: tenantID,
OrganizationName: req.OrganizationName,
Facts: req.Facts,
Overview: overview,
Status: "completed",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
CreatedBy: rbac.GetUserID(c),
}
if err := h.store.CreateAssessment(c.Request.Context(), assessment); err != nil {
c.Set("store_error", err.Error())
}
}
c.JSON(http.StatusOK, ucca.ObligationsAssessResponse{
Overview: overview,
Warnings: warnings,
})
}
// GetAssessment retrieves a stored assessment by ID
// GET /sdk/v1/ucca/obligations/:assessmentId
func (h *ObligationsHandlers) GetAssessment(c *gin.Context) {
tenantID := rbac.GetTenantID(c)
assessmentID := c.Param("assessmentId")
if h.store == nil {
c.JSON(http.StatusNotImplemented, gin.H{"error": "Persistence not configured"})
return
}
id, err := uuid.Parse(assessmentID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid assessment ID"})
return
}
assessment, err := h.store.GetAssessment(c.Request.Context(), tenantID, id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Assessment not found"})
return
}
c.JSON(http.StatusOK, assessment.Overview)
}
// QuickCheck performs a quick obligations check without persistence
// POST /sdk/v1/ucca/obligations/quick-check
func (h *ObligationsHandlers) QuickCheck(c *gin.Context) {
var req struct {
EmployeeCount int `json:"employee_count"`
AnnualRevenue float64 `json:"annual_revenue"`
BalanceSheetTotal float64 `json:"balance_sheet_total,omitempty"`
Country string `json:"country"`
PrimarySector string `json:"primary_sector"`
SpecialServices []string `json:"special_services,omitempty"`
IsKRITIS bool `json:"is_kritis,omitempty"`
ProcessesPersonalData bool `json:"processes_personal_data,omitempty"`
UsesAI bool `json:"uses_ai,omitempty"`
IsFinancialInstitution bool `json:"is_financial_institution,omitempty"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
return
}
facts := &ucca.UnifiedFacts{
Organization: ucca.OrganizationFacts{
EmployeeCount: req.EmployeeCount,
AnnualRevenue: req.AnnualRevenue,
BalanceSheetTotal: req.BalanceSheetTotal,
Country: req.Country,
EUMember: isEUCountry(req.Country),
},
Sector: ucca.SectorFacts{
PrimarySector: req.PrimarySector,
SpecialServices: req.SpecialServices,
IsKRITIS: req.IsKRITIS,
KRITISThresholdMet: req.IsKRITIS,
IsFinancialInstitution: req.IsFinancialInstitution,
},
DataProtection: ucca.DataProtectionFacts{
ProcessesPersonalData: req.ProcessesPersonalData,
},
AIUsage: ucca.AIUsageFacts{
UsesAI: req.UsesAI,
},
Financial: ucca.FinancialFacts{
IsRegulated: req.IsFinancialInstitution,
},
}
tenantID := rbac.GetTenantID(c)
if tenantID == uuid.Nil {
tenantID = uuid.New()
}
overview := h.registry.EvaluateAll(tenantID, facts, "")
c.JSON(http.StatusOK, gin.H{
"applicable_regulations": overview.ApplicableRegulations,
"total_obligations": len(overview.Obligations),
"critical_obligations": overview.ExecutiveSummary.CriticalObligations,
"sanctions_summary": overview.SanctionsSummary,
"executive_summary": overview.ExecutiveSummary,
})
}
// AssessFromScope assesses obligations from a ScopeDecision
// POST /sdk/v1/ucca/obligations/assess-from-scope
func (h *ObligationsHandlers) AssessFromScope(c *gin.Context) {
tenantID := rbac.GetTenantID(c)
if tenantID == uuid.Nil {
tenantID = uuid.New()
}
var scope ucca.ScopeDecision
if err := c.ShouldBindJSON(&scope); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
return
}
facts := ucca.MapScopeToFacts(&scope)
overview := h.registry.EvaluateAll(tenantID, facts, "")
if h.tomMapper != nil {
overview.TOMControlRequirements = h.tomMapper.DeriveControlsFromObligations(overview.Obligations)
}
var warnings []string
if len(overview.ApplicableRegulations) == 0 {
warnings = append(warnings, "Keine anwendbaren Regulierungen gefunden. Pruefen Sie die Scope-Angaben.")
}
c.JSON(http.StatusOK, ucca.ObligationsAssessResponse{
Overview: overview,
Warnings: warnings,
})
}