All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 31s
CI / test-python-backend-compliance (push) Successful in 29s
CI / test-python-document-crawler (push) Successful in 19s
CI / test-python-dsms-gateway (push) Successful in 26s
- Go obligations_handlers.go: CRUD-Overlap als deprecated markiert, AI-Features (Assess/Gap/TOM/Export) bleiben aktiv - Python obligation_routes.py: x-user-id Header + Audit-Logging an 4 Write-Endpoints - 3 UCCA Proxy-Dateien: Default X-Tenant-ID + X-User-ID Headers - Tests von 39 auf 62 erweitert (+23 Route-Integration-Tests mit mock_db/TestClient) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
700 lines
22 KiB
Go
700 lines
22 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"
|
|
"strconv"
|
|
"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
|
|
|
|
// Try to load v2 TOM mapping
|
|
mapping, err := ucca.LoadV2TOMMapping()
|
|
if err != nil {
|
|
// Build mapping from v2 regulation files
|
|
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")
|
|
{
|
|
// Assessment endpoints
|
|
obligations.POST("/assess", h.AssessObligations)
|
|
obligations.GET("/:assessmentId", h.GetAssessment)
|
|
|
|
// Grouping/filtering endpoints
|
|
obligations.GET("/:assessmentId/by-regulation", h.GetByRegulation)
|
|
obligations.GET("/:assessmentId/by-deadline", h.GetByDeadline)
|
|
obligations.GET("/:assessmentId/by-responsible", h.GetByResponsible)
|
|
|
|
// Export endpoints
|
|
obligations.POST("/export/memo", h.ExportMemo)
|
|
obligations.POST("/export/direct", h.ExportMemoFromOverview)
|
|
|
|
// Metadata endpoints
|
|
obligations.GET("/regulations", h.ListRegulations)
|
|
obligations.GET("/regulations/:regulationId/decision-tree", h.GetDecisionTree)
|
|
|
|
// Quick check endpoint (no persistence)
|
|
obligations.POST("/quick-check", h.QuickCheck)
|
|
|
|
// v2: Scope-based assessment
|
|
obligations.POST("/assess-from-scope", h.AssessFromScope)
|
|
|
|
// v2: TOM Control endpoints
|
|
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
|
|
}
|
|
|
|
// Evaluate all regulations against the facts
|
|
overview := h.registry.EvaluateAll(tenantID, req.Facts, req.OrganizationName)
|
|
|
|
// Generate warnings if any
|
|
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.")
|
|
}
|
|
|
|
// Optionally persist the assessment
|
|
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 {
|
|
// Log but don't fail - assessment was still generated
|
|
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)
|
|
}
|
|
|
|
// GetByRegulation returns obligations grouped by regulation
|
|
// GET /sdk/v1/ucca/obligations/:assessmentId/by-regulation
|
|
func (h *ObligationsHandlers) GetByRegulation(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
|
|
}
|
|
|
|
grouped := h.registry.GroupByRegulation(assessment.Overview.Obligations)
|
|
|
|
c.JSON(http.StatusOK, ucca.ObligationsByRegulationResponse{
|
|
Regulations: grouped,
|
|
})
|
|
}
|
|
|
|
// GetByDeadline returns obligations grouped by deadline timeframe
|
|
// GET /sdk/v1/ucca/obligations/:assessmentId/by-deadline
|
|
func (h *ObligationsHandlers) GetByDeadline(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
|
|
}
|
|
|
|
grouped := h.registry.GroupByDeadline(assessment.Overview.Obligations)
|
|
|
|
c.JSON(http.StatusOK, grouped)
|
|
}
|
|
|
|
// GetByResponsible returns obligations grouped by responsible role
|
|
// GET /sdk/v1/ucca/obligations/:assessmentId/by-responsible
|
|
func (h *ObligationsHandlers) GetByResponsible(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
|
|
}
|
|
|
|
grouped := h.registry.GroupByResponsible(assessment.Overview.Obligations)
|
|
|
|
c.JSON(http.StatusOK, ucca.ObligationsByResponsibleResponse{
|
|
ByRole: grouped,
|
|
})
|
|
}
|
|
|
|
// ExportMemo exports the obligations overview as a C-Level memo
|
|
// POST /sdk/v1/ucca/obligations/export/memo
|
|
func (h *ObligationsHandlers) ExportMemo(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c)
|
|
|
|
var req ucca.ExportMemoRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
|
return
|
|
}
|
|
|
|
if h.store == nil {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Persistence not configured"})
|
|
return
|
|
}
|
|
|
|
id, err := uuid.Parse(req.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
|
|
}
|
|
|
|
// Create exporter
|
|
exporter := ucca.NewPDFExporter(req.Language)
|
|
|
|
// Generate export based on format
|
|
var response *ucca.ExportMemoResponse
|
|
switch req.Format {
|
|
case "pdf":
|
|
response, err = exporter.ExportManagementMemo(assessment.Overview)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate PDF", "details": err.Error()})
|
|
return
|
|
}
|
|
case "markdown", "":
|
|
response, err = exporter.ExportMarkdown(assessment.Overview)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate Markdown", "details": err.Error()})
|
|
return
|
|
}
|
|
default:
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid format. Use 'markdown' or 'pdf'"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// ExportMemoFromOverview exports an overview directly (without persistence)
|
|
// POST /sdk/v1/ucca/obligations/export/direct
|
|
func (h *ObligationsHandlers) ExportMemoFromOverview(c *gin.Context) {
|
|
var req struct {
|
|
Overview *ucca.ManagementObligationsOverview `json:"overview"`
|
|
Format string `json:"format"` // "markdown" or "pdf"
|
|
Language string `json:"language,omitempty"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
|
return
|
|
}
|
|
|
|
if req.Overview == nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Overview is required"})
|
|
return
|
|
}
|
|
|
|
exporter := ucca.NewPDFExporter(req.Language)
|
|
|
|
var response *ucca.ExportMemoResponse
|
|
var err error
|
|
switch req.Format {
|
|
case "pdf":
|
|
response, err = exporter.ExportManagementMemo(req.Overview)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate PDF", "details": err.Error()})
|
|
return
|
|
}
|
|
case "markdown", "":
|
|
response, err = exporter.ExportMarkdown(req.Overview)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate Markdown", "details": err.Error()})
|
|
return
|
|
}
|
|
default:
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid format. Use 'markdown' or 'pdf'"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// ListRegulations returns all available regulation modules
|
|
// GET /sdk/v1/ucca/obligations/regulations
|
|
func (h *ObligationsHandlers) ListRegulations(c *gin.Context) {
|
|
modules := h.registry.ListModules()
|
|
|
|
c.JSON(http.StatusOK, ucca.AvailableRegulationsResponse{
|
|
Regulations: modules,
|
|
})
|
|
}
|
|
|
|
// GetDecisionTree returns the decision tree for a specific regulation
|
|
// GET /sdk/v1/ucca/obligations/regulations/:regulationId/decision-tree
|
|
func (h *ObligationsHandlers) GetDecisionTree(c *gin.Context) {
|
|
regulationID := c.Param("regulationId")
|
|
|
|
tree, err := h.registry.GetDecisionTree(regulationID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, tree)
|
|
}
|
|
|
|
// 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 {
|
|
// Organization basics
|
|
EmployeeCount int `json:"employee_count"`
|
|
AnnualRevenue float64 `json:"annual_revenue"`
|
|
BalanceSheetTotal float64 `json:"balance_sheet_total,omitempty"`
|
|
Country string `json:"country"`
|
|
|
|
// Sector
|
|
PrimarySector string `json:"primary_sector"`
|
|
SpecialServices []string `json:"special_services,omitempty"`
|
|
IsKRITIS bool `json:"is_kritis,omitempty"`
|
|
|
|
// Quick flags
|
|
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
|
|
}
|
|
|
|
// Build UnifiedFacts from quick check request
|
|
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,
|
|
},
|
|
}
|
|
|
|
// Quick evaluation
|
|
tenantID := rbac.GetTenantID(c)
|
|
if tenantID == uuid.Nil {
|
|
tenantID = uuid.New() // Generate temporary ID for quick check
|
|
}
|
|
|
|
overview := h.registry.EvaluateAll(tenantID, facts, "")
|
|
|
|
// Return simplified result
|
|
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
|
|
}
|
|
|
|
// Convert scope to facts
|
|
facts := ucca.MapScopeToFacts(&scope)
|
|
|
|
// Evaluate
|
|
overview := h.registry.EvaluateAll(tenantID, facts, "")
|
|
|
|
// Enrich with TOM control requirements if available
|
|
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,
|
|
})
|
|
}
|
|
|
|
// GetTOMControlsForObligation returns TOM controls linked to an obligation
|
|
// GET /sdk/v1/ucca/obligations/:id/tom-controls
|
|
func (h *ObligationsHandlers) GetTOMControlsForObligation(c *gin.Context) {
|
|
obligationID := c.Param("obligationId")
|
|
|
|
if h.tomMapper == nil {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "TOM mapping not available"})
|
|
return
|
|
}
|
|
|
|
controls := h.tomMapper.GetControlsForObligation(obligationID)
|
|
controlIDs := h.tomMapper.GetControlIDsForObligation(obligationID)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"obligation_id": obligationID,
|
|
"control_ids": controlIDs,
|
|
"controls": controls,
|
|
"count": len(controls),
|
|
})
|
|
}
|
|
|
|
// GapAnalysis performs a TOM control gap analysis
|
|
// POST /sdk/v1/ucca/obligations/gap-analysis
|
|
func (h *ObligationsHandlers) GapAnalysis(c *gin.Context) {
|
|
if h.gapAnalyzer == nil {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Gap analysis not available"})
|
|
return
|
|
}
|
|
|
|
var req ucca.GapAnalysisRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
result := h.gapAnalyzer.Analyze(&req)
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// GetObligationsForControl returns obligations linked to a TOM control
|
|
// GET /sdk/v1/ucca/obligations/tom-controls/:controlId/obligations
|
|
func (h *ObligationsHandlers) GetObligationsForControl(c *gin.Context) {
|
|
controlID := c.Param("controlId")
|
|
|
|
if h.tomMapper == nil {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "TOM mapping not available"})
|
|
return
|
|
}
|
|
|
|
obligationIDs := h.tomMapper.GetObligationsForControl(controlID)
|
|
|
|
var control *ucca.TOMControl
|
|
if h.tomIndex != nil {
|
|
control, _ = h.tomIndex.GetControl(controlID)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"control_id": controlID,
|
|
"control": control,
|
|
"obligation_ids": obligationIDs,
|
|
"count": len(obligationIDs),
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Helper Functions
|
|
// ============================================================================
|
|
|
|
func generateMemoMarkdown(overview *ucca.ManagementObligationsOverview) string {
|
|
content := "# Pflichten-Übersicht für die Geschäftsführung\n\n"
|
|
content += "**Datum:** " + overview.AssessmentDate.Format("02.01.2006") + "\n"
|
|
if overview.OrganizationName != "" {
|
|
content += "**Organisation:** " + overview.OrganizationName + "\n"
|
|
}
|
|
content += "\n---\n\n"
|
|
|
|
// Executive Summary
|
|
content += "## Executive Summary\n\n"
|
|
content += "| Kennzahl | Wert |\n"
|
|
content += "|----------|------|\n"
|
|
content += "| Anwendbare Regulierungen | " + itoa(overview.ExecutiveSummary.TotalRegulations) + " |\n"
|
|
content += "| Gesamtzahl Pflichten | " + itoa(overview.ExecutiveSummary.TotalObligations) + " |\n"
|
|
content += "| Kritische Pflichten | " + itoa(overview.ExecutiveSummary.CriticalObligations) + " |\n"
|
|
content += "| Überfällige Pflichten | " + itoa(overview.ExecutiveSummary.OverdueObligations) + " |\n"
|
|
content += "| Anstehende Fristen (30 Tage) | " + itoa(overview.ExecutiveSummary.UpcomingDeadlines) + " |\n"
|
|
content += "\n"
|
|
|
|
// Key Risks
|
|
if len(overview.ExecutiveSummary.KeyRisks) > 0 {
|
|
content += "### Hauptrisiken\n\n"
|
|
for _, risk := range overview.ExecutiveSummary.KeyRisks {
|
|
content += "- ⚠️ " + risk + "\n"
|
|
}
|
|
content += "\n"
|
|
}
|
|
|
|
// Recommended Actions
|
|
if len(overview.ExecutiveSummary.RecommendedActions) > 0 {
|
|
content += "### Empfohlene Maßnahmen\n\n"
|
|
for i, action := range overview.ExecutiveSummary.RecommendedActions {
|
|
content += itoa(i+1) + ". " + action + "\n"
|
|
}
|
|
content += "\n"
|
|
}
|
|
|
|
// Applicable Regulations
|
|
content += "## Anwendbare Regulierungen\n\n"
|
|
for _, reg := range overview.ApplicableRegulations {
|
|
content += "### " + reg.Name + "\n\n"
|
|
content += "- **Klassifizierung:** " + reg.Classification + "\n"
|
|
content += "- **Begründung:** " + reg.Reason + "\n"
|
|
content += "- **Anzahl Pflichten:** " + itoa(reg.ObligationCount) + "\n"
|
|
content += "\n"
|
|
}
|
|
|
|
// Sanctions Summary
|
|
content += "## Sanktionsrisiken\n\n"
|
|
content += overview.SanctionsSummary.Summary + "\n\n"
|
|
if overview.SanctionsSummary.MaxFinancialRisk != "" {
|
|
content += "- **Maximales Bußgeld:** " + overview.SanctionsSummary.MaxFinancialRisk + "\n"
|
|
}
|
|
if overview.SanctionsSummary.PersonalLiabilityRisk {
|
|
content += "- **Persönliche Haftung:** Ja ⚠️\n"
|
|
}
|
|
content += "\n"
|
|
|
|
// Critical Obligations
|
|
content += "## Kritische Pflichten\n\n"
|
|
for _, obl := range overview.Obligations {
|
|
if obl.Priority == ucca.PriorityCritical {
|
|
content += "### " + obl.ID + ": " + obl.Title + "\n\n"
|
|
content += obl.Description + "\n\n"
|
|
content += "- **Verantwortlich:** " + string(obl.Responsible) + "\n"
|
|
if obl.Deadline != nil {
|
|
if obl.Deadline.Date != nil {
|
|
content += "- **Frist:** " + obl.Deadline.Date.Format("02.01.2006") + "\n"
|
|
} else if obl.Deadline.Duration != "" {
|
|
content += "- **Frist:** " + obl.Deadline.Duration + "\n"
|
|
}
|
|
}
|
|
if obl.Sanctions != nil && obl.Sanctions.MaxFine != "" {
|
|
content += "- **Sanktion:** " + obl.Sanctions.MaxFine + "\n"
|
|
}
|
|
content += "\n"
|
|
}
|
|
}
|
|
|
|
// Incident Deadlines
|
|
if len(overview.IncidentDeadlines) > 0 {
|
|
content += "## Meldepflichten bei Sicherheitsvorfällen\n\n"
|
|
content += "| Phase | Frist | Empfänger |\n"
|
|
content += "|-------|-------|-----------|\n"
|
|
for _, deadline := range overview.IncidentDeadlines {
|
|
content += "| " + deadline.Phase + " | " + deadline.Deadline + " | " + deadline.Recipient + " |\n"
|
|
}
|
|
content += "\n"
|
|
}
|
|
|
|
content += "---\n\n"
|
|
content += "*Dieses Dokument wurde automatisch generiert und ersetzt keine Rechtsberatung.*\n"
|
|
|
|
return content
|
|
}
|
|
|
|
func isEUCountry(country string) bool {
|
|
euCountries := map[string]bool{
|
|
"DE": true, "AT": true, "BE": true, "BG": true, "HR": true, "CY": true,
|
|
"CZ": true, "DK": true, "EE": true, "FI": true, "FR": true, "GR": true,
|
|
"HU": true, "IE": true, "IT": true, "LV": true, "LT": true, "LU": true,
|
|
"MT": true, "NL": true, "PL": true, "PT": true, "RO": true, "SK": true,
|
|
"SI": true, "ES": true, "SE": true,
|
|
}
|
|
return euCountries[country]
|
|
}
|
|
|
|
func itoa(i int) string {
|
|
return strconv.Itoa(i)
|
|
}
|