feat: Fortschritts-Tracker + Verifikation-Endpoints + Tech-File Erweiterung

- Übersicht: Completeness Gates durch Projektfortschritts-Tracker ersetzt
  (6 CE-Prozessschritte mit Status + Naechster-Schritt Empfehlung)
- Verifikation: GET/POST/DELETE /verifications Endpoints + Alias-Handler
- Tech-File: Anhang IV Struktur-Erweiterung
- Maßnahmen: Expandable Details vorbereitet

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-08 01:02:41 +02:00
parent c4532049d8
commit 89af88ef7d
8 changed files with 184 additions and 280 deletions
@@ -158,178 +158,3 @@ func (h *IACEHandler) VerifyMitigation(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "mitigation verified"})
}
// ============================================================================
// Evidence & Verification
// ============================================================================
// UploadEvidence handles POST /projects/:id/evidence
// Creates a new evidence record for a project.
func (h *IACEHandler) UploadEvidence(c *gin.Context) {
projectID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
return
}
var req struct {
MitigationID *uuid.UUID `json:"mitigation_id,omitempty"`
VerificationPlanID *uuid.UUID `json:"verification_plan_id,omitempty"`
FileName string `json:"file_name" binding:"required"`
FilePath string `json:"file_path" binding:"required"`
FileHash string `json:"file_hash" binding:"required"`
FileSize int64 `json:"file_size" binding:"required"`
MimeType string `json:"mime_type" binding:"required"`
Description string `json:"description,omitempty"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID := rbac.GetUserID(c)
evidence := &iace.Evidence{
ProjectID: projectID,
MitigationID: req.MitigationID,
VerificationPlanID: req.VerificationPlanID,
FileName: req.FileName,
FilePath: req.FilePath,
FileHash: req.FileHash,
FileSize: req.FileSize,
MimeType: req.MimeType,
Description: req.Description,
UploadedBy: userID,
}
if err := h.store.CreateEvidence(c.Request.Context(), evidence); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Audit trail
newVals, _ := json.Marshal(evidence)
h.store.AddAuditEntry(
c.Request.Context(), projectID, "evidence", evidence.ID,
iace.AuditActionCreate, userID.String(), nil, newVals,
)
c.JSON(http.StatusCreated, gin.H{"evidence": evidence})
}
// ListEvidence handles GET /projects/:id/evidence
// Lists all evidence records for a project.
func (h *IACEHandler) ListEvidence(c *gin.Context) {
projectID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
return
}
evidence, err := h.store.ListEvidence(c.Request.Context(), projectID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if evidence == nil {
evidence = []iace.Evidence{}
}
c.JSON(http.StatusOK, gin.H{
"evidence": evidence,
"total": len(evidence),
})
}
// CreateVerificationPlan handles POST /projects/:id/verification-plan
// Creates a new verification plan for a project.
func (h *IACEHandler) CreateVerificationPlan(c *gin.Context) {
projectID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
return
}
var req iace.CreateVerificationPlanRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Override project ID from URL path
req.ProjectID = projectID
plan, err := h.store.CreateVerificationPlan(c.Request.Context(), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Audit trail
userID := rbac.GetUserID(c)
newVals, _ := json.Marshal(plan)
h.store.AddAuditEntry(
c.Request.Context(), projectID, "verification_plan", plan.ID,
iace.AuditActionCreate, userID.String(), nil, newVals,
)
c.JSON(http.StatusCreated, gin.H{"verification_plan": plan})
}
// UpdateVerificationPlan handles PUT /verification-plan/:vid
// Updates a verification plan with the provided fields.
func (h *IACEHandler) UpdateVerificationPlan(c *gin.Context) {
planID, err := uuid.Parse(c.Param("vid"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid verification plan ID"})
return
}
var updates map[string]interface{}
if err := c.ShouldBindJSON(&updates); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
plan, err := h.store.UpdateVerificationPlan(c.Request.Context(), planID, updates)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if plan == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "verification plan not found"})
return
}
c.JSON(http.StatusOK, gin.H{"verification_plan": plan})
}
// CompleteVerification handles POST /verification-plan/:vid/complete
// Marks a verification plan as completed with a result.
func (h *IACEHandler) CompleteVerification(c *gin.Context) {
planID, err := uuid.Parse(c.Param("vid"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid verification plan ID"})
return
}
var req struct {
Result string `json:"result" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID := rbac.GetUserID(c)
if err := h.store.CompleteVerification(
c.Request.Context(), planID, req.Result, userID.String(),
); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "verification completed"})
}
@@ -35,19 +35,30 @@ func (h *IACEHandler) GenerateTechFile(c *gin.Context) {
return
}
// Define the standard CE technical file sections to generate
// Define sections per EU Machinery Regulation 2023/1230 Annex IV structure.
// Core Annex IV sections come first, then supplementary CE-Akte sections.
sectionDefinitions := []struct {
SectionType string
Title string
}{
{"general_description", "General Description of the Machinery"},
{"risk_assessment_report", "Risk Assessment Report"},
{"hazard_log_combined", "Combined Hazard Log"},
{"essential_requirements", "Essential Health and Safety Requirements"},
{"design_specifications", "Design Specifications and Drawings"},
{"test_reports", "Test Reports and Verification Results"},
{"standards_applied", "Applied Harmonised Standards"},
{"declaration_of_conformity", "EU Declaration of Conformity"},
// Annex IV mandatory sections
{"general_description", "Anhang IV.1 — Allgemeine Beschreibung der Maschine"},
{"design_specifications", "Anhang IV.2 — Gesamtplan und Schaltplaene"},
{"component_list", "Anhang IV.3 — Detailplaene und Komponentenliste"},
{"risk_assessment_report", "Anhang IV.4 — Risikobeurteilung"},
{"standards_applied", "Anhang IV.5 — Angewandte harmonisierte Normen"},
{"test_reports", "Anhang IV.6 — Pruefberichte und Ergebnisse"},
{"instructions_for_use", "Anhang IV.7 — Betriebsanleitung"},
{"declaration_of_conformity", "Anhang IV.8 — EU-Konformitaetserklaerung"},
{"assembly_declaration", "Anhang IV.9 — Einbauerklaerung (falls zutreffend)"},
// Supplementary CE-Akte sections
{"hazard_log_combined", "Gefaehrdungsprotokoll (Hazard Log)"},
{"essential_requirements", "Grundlegende Anforderungen (EHSR)"},
{"mitigation_report", "Massnahmenbericht (3-Stufen-Verfahren)"},
{"verification_report", "Verifikationsbericht"},
{"evidence_index", "Nachweisverzeichnis"},
{"classification_report", "Regulatorischer Klassifizierungsbericht"},
{"monitoring_plan", "Post-Market-Monitoring-Plan"},
}
// Check if project has AI components for additional sections
@@ -184,6 +195,7 @@ func (h *IACEHandler) GenerateSingleSection(c *gin.Context) {
"evidence_index": "Evidence Index",
"instructions_for_use": "Instructions for Use",
"monitoring_plan": "Post-Market Monitoring Plan",
"assembly_declaration": "Anhang IV.9 — Einbauerklaerung (falls zutreffend)",
"ai_intended_purpose": "AI System Intended Purpose",
"ai_model_description": "AI Model Description and Training Data",
"ai_risk_management": "AI Risk Management System",
+4
View File
@@ -403,6 +403,10 @@ func registerIACERoutes(v1 *gin.RouterGroup, h *handlers.IACEHandler) {
iaceRoutes.POST("/projects/:id/verification-plan", h.CreateVerificationPlan)
iaceRoutes.PUT("/verification-plan/:vid", h.UpdateVerificationPlan)
iaceRoutes.POST("/verification-plan/:vid/complete", h.CompleteVerification)
iaceRoutes.GET("/projects/:id/verifications", h.ListVerificationPlans)
iaceRoutes.POST("/projects/:id/verifications", h.CreateVerificationAlias)
iaceRoutes.DELETE("/projects/:id/verifications/:vid", h.DeleteVerificationPlan)
iaceRoutes.POST("/projects/:id/verifications/:vid/complete", h.CompleteVerificationAlias)
iaceRoutes.POST("/projects/:id/tech-file/generate", h.GenerateTechFile)
iaceRoutes.GET("/projects/:id/tech-file", h.ListTechFileSections)
iaceRoutes.PUT("/projects/:id/tech-file/:section", h.UpdateTechFileSection)
@@ -247,6 +247,15 @@ func (s *Store) getVerificationPlan(ctx context.Context, id uuid.UUID) (*Verific
return &vp, nil
}
// DeleteVerificationPlan deletes a verification plan by ID
func (s *Store) DeleteVerificationPlan(ctx context.Context, id uuid.UUID) error {
_, err := s.pool.Exec(ctx, `DELETE FROM iace_verification_plans WHERE id = $1`, id)
if err != nil {
return fmt.Errorf("delete verification plan: %w", err)
}
return nil
}
// ============================================================================
// Reference Data Operations
// ============================================================================
@@ -24,6 +24,7 @@ const (
SectionEvidenceIndex = "evidence_index"
SectionInstructionsForUse = "instructions_for_use"
SectionMonitoringPlan = "monitoring_plan"
SectionAssemblyDeclaration = "assembly_declaration"
)
// ============================================================================
@@ -68,6 +69,8 @@ var sectionSystemPrompts = map[string]string{
SectionInstructionsForUse: `Erstelle eine Gliederung fuer die Betriebsanleitung gemaess EU-Maschinenverordnung 2023/1230 Anhang III Abschnitt 1.7.4. Enthalten: 1) Bestimmungsgemaesse Verwendung, 2) Inbetriebnahme, 3) Sicherer Betrieb, 4) Wartung, 5) Restrisiken und Warnhinweise, 6) Ausserbetriebnahme. Beruecksichtige identifizierte Gefaehrdungen.`,
SectionMonitoringPlan: `Erstelle einen Post-Market-Monitoring-Plan fuer das Produkt. Enthalten: 1) Ueberwachungsziele, 2) Datenquellen (Kundenfeedback, Vorfaelle, Updates), 3) Ueberwachungsintervalle, 4) Eskalationsverfahren, 5) Dokumentationspflichten, 6) Verantwortlichkeiten. Beruecksichtige AI Act Art. 72 (Post-Market Monitoring) falls KI-Komponenten vorhanden.`,
SectionAssemblyDeclaration: `Erstelle eine Einbauerklaerung gemaess EU-Maschinenverordnung 2023/1230 Anhang IV.9 fuer eine unvollstaendige Maschine. Enthalten: 1) Hersteller-Angaben, 2) Bezeichnung der unvollstaendigen Maschine, 3) Erklaerung, dass die Inbetriebnahme untersagt ist bis die Gesamtmaschine konform erklaert wurde, 4) Liste der eingehaltenen Anforderungen, 5) Angaben zur technischen Dokumentation. Falls die Maschine vollstaendig ist, vermerke "Nicht zutreffend — vollstaendige Maschine".`,
}
// ============================================================================
@@ -95,6 +98,7 @@ func buildRAGQuery(sectionType string) string {
SectionEvidenceIndex: "Nachweisdokumente Evidence Konformitaetsnachweis Dokumentenindex",
SectionInstructionsForUse: "Betriebsanleitung Benutzerinformation Maschinenverordnung Abschnitt 1.7.4 Sicherheitshinweise",
SectionMonitoringPlan: "Post-Market-Monitoring Ueberwachungsplan AI Act Art. 72 Marktbeobachtung",
SectionAssemblyDeclaration: "Einbauerklaerung unvollstaendige Maschine Maschinenverordnung Anhang IV",
}
if q, ok := ragQueries[sectionType]; ok {
@@ -133,6 +137,7 @@ func sectionDisplayName(sectionType string) string {
SectionEvidenceIndex: "Nachweisverzeichnis",
SectionInstructionsForUse: "Betriebsanleitung (Gliederung)",
SectionMonitoringPlan: "Post-Market-Monitoring-Plan",
SectionAssemblyDeclaration: "Einbauerklaerung",
}
if name, ok := names[sectionType]; ok {
return name