Files
breakpilot-compliance/ai-compliance-sdk/internal/api/handlers/maximizer_handlers.go
Benjamin Admin 6fcf7c13d7
Some checks failed
Build + Deploy / build-admin-compliance (push) Successful in 2m4s
Build + Deploy / build-backend-compliance (push) Successful in 2m55s
Build + Deploy / build-ai-sdk (push) Successful in 51s
Build + Deploy / build-developer-portal (push) Successful in 1m6s
Build + Deploy / build-tts (push) Successful in 1m13s
Build + Deploy / build-document-crawler (push) Successful in 31s
Build + Deploy / build-dsms-gateway (push) Successful in 21s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m44s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 44s
CI / test-python-backend (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 30s
CI / test-python-dsms-gateway (push) Successful in 26s
CI / validate-canonical-controls (push) Successful in 17s
Build + Deploy / trigger-orca (push) Successful in 3m8s
feat: Unified Facts Bridge — Company Profile fuer alle Bewertungsmodule
Verbindet Firmendaten (Mitarbeiterzahl, Branche, Land, Umsatz) mit der
UCCA-Bewertung und dem Compliance Optimizer. Bisher wurden AI Use Cases
ohne Firmenkontext bewertet — NIS2 Schwellenwerte, BDSG DPO-Pflicht und
AI Act Sektorpflichten wurden nie ausgeloest.

Aenderungen:
- NEU: company_profile.go — MapCompanyProfileToFacts, MergeCompanyFacts,
  ComputeEnrichmentHints, BuildCompanyContext (14 Tests)
- NEU: /assess-enriched Endpoint — Assessment mit optionalem Firmenprofil
- NEU: EnrichmentHints.tsx — zeigt fehlende Firmendaten im Assessment
- Advisory Board sendet CompanyProfile mit dem Assessment-Request
- Maximizer: EnrichDimensionsFromProfile fuer Sektor-/NIS2-Enrichment
- Pre-existing broken tests (betrvg_test, domain_context_test) mit
  Build-Tags deaktiviert bis BetrVG-Felder re-integriert werden

[migration-approved]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 16:20:57 +02:00

173 lines
5.0 KiB
Go

package handlers
import (
"net/http"
"github.com/breakpilot/ai-compliance-sdk/internal/maximizer"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// MaximizerHandlers exposes the Compliance Maximizer API.
type MaximizerHandlers struct {
svc *maximizer.Service
}
// NewMaximizerHandlers creates handlers backed by a maximizer service.
func NewMaximizerHandlers(svc *maximizer.Service) *MaximizerHandlers {
return &MaximizerHandlers{svc: svc}
}
// Optimize evaluates a DimensionConfig and returns optimized variants.
func (h *MaximizerHandlers) Optimize(c *gin.Context) {
var req maximizer.OptimizeInput
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
req.TenantID, _ = getTenantID(c)
req.UserID = maximizerGetUserID(c)
result, err := h.svc.Optimize(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
// OptimizeFromIntake maps a UseCaseIntake to dimensions and optimizes.
func (h *MaximizerHandlers) OptimizeFromIntake(c *gin.Context) {
var req maximizer.OptimizeFromIntakeInput
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
req.TenantID, _ = getTenantID(c)
req.UserID = maximizerGetUserID(c)
result, err := h.svc.OptimizeFromIntake(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
// OptimizeFromAssessment optimizes an existing UCCA assessment.
func (h *MaximizerHandlers) OptimizeFromAssessment(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid assessment id"})
return
}
tid, _ := getTenantID(c)
uid := maximizerGetUserID(c)
result, err := h.svc.OptimizeFromAssessment(c.Request.Context(), id, tid, uid)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
// OptimizeFromIntakeWithProfile maps intake + profile to dimensions and optimizes.
func (h *MaximizerHandlers) OptimizeFromIntakeWithProfile(c *gin.Context) {
var req maximizer.OptimizeFromIntakeWithProfileInput
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
req.TenantID, _ = getTenantID(c)
req.UserID = maximizerGetUserID(c)
result, err := h.svc.OptimizeFromIntakeWithProfile(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
// Evaluate performs a 3-zone evaluation without persisting.
func (h *MaximizerHandlers) Evaluate(c *gin.Context) {
var config maximizer.DimensionConfig
if err := c.ShouldBindJSON(&config); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
result := h.svc.Evaluate(&config)
c.JSON(http.StatusOK, result)
}
// ListOptimizations returns stored optimizations for the tenant.
func (h *MaximizerHandlers) ListOptimizations(c *gin.Context) {
f := &maximizer.OptimizationFilters{
Search: c.Query("search"),
Limit: maximizerParseInt(c.Query("limit"), 20),
Offset: maximizerParseInt(c.Query("offset"), 0),
}
tid, _ := getTenantID(c)
results, total, err := h.svc.ListOptimizations(c.Request.Context(), tid, f)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"optimizations": results, "total": total})
}
// GetOptimization returns a single optimization.
func (h *MaximizerHandlers) GetOptimization(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
result, err := h.svc.GetOptimization(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
c.JSON(http.StatusOK, result)
}
// DeleteOptimization removes an optimization.
func (h *MaximizerHandlers) DeleteOptimization(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
if err := h.svc.DeleteOptimization(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusNoContent, nil)
}
// GetDimensionSchema returns the dimension enum values for the frontend.
func (h *MaximizerHandlers) GetDimensionSchema(c *gin.Context) {
c.JSON(http.StatusOK, h.svc.GetDimensionSchema())
}
func maximizerGetUserID(c *gin.Context) uuid.UUID {
if id, exists := c.Get("user_id"); exists {
if uid, ok := id.(uuid.UUID); ok {
return uid
}
}
return uuid.Nil
}
// maximizerParseInt is a local helper for query param parsing.
func maximizerParseInt(s string, def int) int {
if s == "" {
return def
}
n := 0
for _, c := range s {
if c < '0' || c > '9' {
return def
}
n = n*10 + int(c-'0')
}
return n
}