feat: CE-Akte mit Anhang IV + Tech-File Sections fuer alle 4 Projekte

- 9 Sections nach EU MVO 2023/1230 Anhang IV (alle approved)
- Store fixes: html_content, tenant_id, nullable columns
- Frontend: _constants.ts mit Section-Types extrahiert
- 65 Verifikationseintraege automatisch generiert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-08 01:49:14 +02:00
parent 51d91d20ed
commit c27022d11b
3 changed files with 145 additions and 125 deletions
@@ -0,0 +1,106 @@
/**
* CE-Akte section type metadata — icons and descriptions for the tech-file viewer.
* Structured per EU Machinery Regulation 2023/1230 Annex IV.
*/
export const SECTION_TYPES: Record<string, { icon: string; description: string }> = {
// Annex IV mandatory sections (EU Machinery Regulation 2023/1230)
general_description: {
icon: '🏭',
description: 'Anhang IV.1 — Allgemeine Beschreibung der Maschine mit bestimmungsgemaesser Verwendung',
},
design_specifications: {
icon: '📐',
description: 'Anhang IV.2 — Gesamtplan, Schaltplaene und Systemarchitektur',
},
component_list: {
icon: '🔧',
description: 'Anhang IV.3 — Detailplaene und Verzeichnis aller sicherheitsrelevanten Komponenten',
},
risk_assessment_report: {
icon: '📊',
description: 'Anhang IV.4 — Risikobeurteilung nach ISO 12100 mit allen bewerteten Gefaehrdungen',
},
standards_applied: {
icon: '📏',
description: 'Anhang IV.5 — Angewandte harmonisierte Normen und deren Vermutungswirkung',
},
test_reports: {
icon: '🧪',
description: 'Anhang IV.6 — Pruefberichte und Verifikationsergebnisse',
},
instructions_for_use: {
icon: '📖',
description: 'Anhang IV.7 — Betriebsanleitung mit Sicherheitshinweisen',
},
declaration_of_conformity: {
icon: '📜',
description: 'Anhang IV.8 — EU-Konformitaetserklaerung',
},
assembly_declaration: {
icon: '🔩',
description: 'Anhang IV.9 — Einbauerklaerung fuer unvollstaendige Maschinen',
},
// Supplementary CE-Akte sections
hazard_log_combined: {
icon: '⚠️',
description: 'Vollstaendiges Gefaehrdungsprotokoll (Hazard Log) mit S/E/P-Bewertungen',
},
essential_requirements: {
icon: '📋',
description: 'Grundlegende Anforderungen (EHSR) nach MVO Anhang III',
},
mitigation_report: {
icon: '🛡️',
description: 'Uebersicht aller Schutzmassnahmen nach 3-Stufen-Verfahren',
},
verification_report: {
icon: '✅',
description: 'Verifikationsplan und Ergebnisse aller Nachweisverfahren',
},
evidence_index: {
icon: '📎',
description: 'Index aller Nachweisdokumente mit Verknuepfungen',
},
classification_report: {
icon: '🏷️',
description: 'Regulatorische Klassifikation (AI Act, MVO, CRA, NIS2)',
},
monitoring_plan: {
icon: '📡',
description: 'Post-Market Surveillance und Ueberwachungsplan',
},
// AI-specific sections (when AI components present)
ai_intended_purpose: {
icon: '🎯',
description: 'Bestimmungsgemaesser Zweck des KI-Systems (AI Act Art. 13)',
},
ai_model_description: {
icon: '🧠',
description: 'KI-Modellbeschreibung, Trainingsdaten und Architektur',
},
ai_risk_management: {
icon: '⚙️',
description: 'KI-Risikomanagementsystem (AI Act Art. 9)',
},
ai_human_oversight: {
icon: '👁️',
description: 'Menschliche Aufsicht und Kontrollmassnahmen (AI Act Art. 14)',
},
}
export const STATUS_CONFIG: Record<string, { label: string; color: string; bgColor: string }> = {
empty: { label: 'Leer', color: 'text-gray-500', bgColor: 'bg-gray-100' },
draft: { label: 'Entwurf', color: 'text-yellow-700', bgColor: 'bg-yellow-100' },
generated: { label: 'Generiert', color: 'text-blue-700', bgColor: 'bg-blue-100' },
reviewed: { label: 'Geprueft', color: 'text-orange-700', bgColor: 'bg-orange-100' },
approved: { label: 'Freigegeben', color: 'text-green-700', bgColor: 'bg-green-100' },
}
export const EXPORT_FORMATS: { value: string; label: string; extension: string }[] = [
{ value: 'pdf', label: 'PDF', extension: '.pdf' },
{ value: 'xlsx', label: 'Excel', extension: '.xlsx' },
{ value: 'docx', label: 'Word', extension: '.docx' },
{ value: 'md', label: 'Markdown', extension: '.md' },
{ value: 'json', label: 'JSON', extension: '.json' },
]
@@ -4,6 +4,7 @@ import React, { useState, useEffect, useRef } from 'react'
import { useParams } from 'next/navigation'
import { TechFileEditor } from '@/components/sdk/iace/TechFileEditor'
import { ReportGenerator } from './_components/ReportGenerator'
import { SECTION_TYPES, STATUS_CONFIG, EXPORT_FORMATS } from './_constants'
interface TechFileSection {
id: string
@@ -18,108 +19,6 @@ interface TechFileSection {
required: boolean
}
const SECTION_TYPES: Record<string, { icon: string; description: string }> = {
// Annex IV mandatory sections (EU Machinery Regulation 2023/1230)
general_description: {
icon: '🏭',
description: 'Anhang IV.1 — Allgemeine Beschreibung der Maschine mit bestimmungsgemaesser Verwendung',
},
design_specifications: {
icon: '📐',
description: 'Anhang IV.2 — Gesamtplan, Schaltplaene und Systemarchitektur',
},
component_list: {
icon: '🔧',
description: 'Anhang IV.3 — Detailplaene und Verzeichnis aller sicherheitsrelevanten Komponenten',
},
risk_assessment_report: {
icon: '📊',
description: 'Anhang IV.4 — Risikobeurteilung nach ISO 12100 mit allen bewerteten Gefaehrdungen',
},
standards_applied: {
icon: '📏',
description: 'Anhang IV.5 — Angewandte harmonisierte Normen und deren Vermutungswirkung',
},
test_reports: {
icon: '🧪',
description: 'Anhang IV.6 — Pruefberichte und Verifikationsergebnisse',
},
instructions_for_use: {
icon: '📖',
description: 'Anhang IV.7 — Betriebsanleitung mit Sicherheitshinweisen',
},
declaration_of_conformity: {
icon: '📜',
description: 'Anhang IV.8 — EU-Konformitaetserklaerung',
},
assembly_declaration: {
icon: '🔩',
description: 'Anhang IV.9 — Einbauerklaerung fuer unvollstaendige Maschinen',
},
// Supplementary CE-Akte sections
hazard_log_combined: {
icon: '⚠️',
description: 'Vollstaendiges Gefaehrdungsprotokoll (Hazard Log) mit S/E/P-Bewertungen',
},
essential_requirements: {
icon: '📋',
description: 'Grundlegende Anforderungen (EHSR) nach MVO Anhang III',
},
mitigation_report: {
icon: '🛡️',
description: 'Uebersicht aller Schutzmassnahmen nach 3-Stufen-Verfahren',
},
verification_report: {
icon: '✅',
description: 'Verifikationsplan und Ergebnisse aller Nachweisverfahren',
},
evidence_index: {
icon: '📎',
description: 'Index aller Nachweisdokumente mit Verknuepfungen',
},
classification_report: {
icon: '🏷️',
description: 'Regulatorische Klassifikation (AI Act, MVO, CRA, NIS2)',
},
monitoring_plan: {
icon: '📡',
description: 'Post-Market Surveillance und Ueberwachungsplan',
},
// AI-specific sections (when AI components present)
ai_intended_purpose: {
icon: '🎯',
description: 'Bestimmungsgemaesser Zweck des KI-Systems (AI Act Art. 13)',
},
ai_model_description: {
icon: '🧠',
description: 'KI-Modellbeschreibung, Trainingsdaten und Architektur',
},
ai_risk_management: {
icon: '⚙️',
description: 'KI-Risikomanagementsystem (AI Act Art. 9)',
},
ai_human_oversight: {
icon: '👁️',
description: 'Menschliche Aufsicht und Kontrollmassnahmen (AI Act Art. 14)',
},
}
const STATUS_CONFIG: Record<string, { label: string; color: string; bgColor: string }> = {
empty: { label: 'Leer', color: 'text-gray-500', bgColor: 'bg-gray-100' },
draft: { label: 'Entwurf', color: 'text-yellow-700', bgColor: 'bg-yellow-100' },
generated: { label: 'Generiert', color: 'text-blue-700', bgColor: 'bg-blue-100' },
reviewed: { label: 'Geprueft', color: 'text-orange-700', bgColor: 'bg-orange-100' },
approved: { label: 'Freigegeben', color: 'text-green-700', bgColor: 'bg-green-100' },
}
const EXPORT_FORMATS: { value: string; label: string; extension: string }[] = [
{ value: 'pdf', label: 'PDF', extension: '.pdf' },
{ value: 'xlsx', label: 'Excel', extension: '.xlsx' },
{ value: 'docx', label: 'Word', extension: '.docx' },
{ value: 'md', label: 'Markdown', extension: '.md' },
{ value: 'json', label: 'JSON', extension: '.json' },
]
function StatusBadge({ status }: { status: string }) {
const config = STATUS_CONFIG[status] || STATUS_CONFIG.empty
return (
+38 -23
View File
@@ -14,8 +14,18 @@ import (
// Tech File Section Operations
// ============================================================================
// CreateTechFileSection creates a new section in the technical file
// CreateTechFileSection creates a new section in the technical file.
// tenantID is extracted from the project's owning tenant.
func (s *Store) CreateTechFileSection(ctx context.Context, projectID uuid.UUID, sectionType, title, content string) (*TechFileSection, error) {
// Resolve tenant_id from the project
var tenantID uuid.UUID
err := s.pool.QueryRow(ctx,
`SELECT tenant_id FROM iace_projects WHERE id = $1`, projectID,
).Scan(&tenantID)
if err != nil {
return nil, fmt.Errorf("resolve tenant_id for project %s: %w", projectID, err)
}
tf := &TechFileSection{
ID: uuid.New(),
ProjectID: projectID,
@@ -28,19 +38,19 @@ func (s *Store) CreateTechFileSection(ctx context.Context, projectID uuid.UUID,
UpdatedAt: time.Now().UTC(),
}
_, err := s.pool.Exec(ctx, `
_, err = s.pool.Exec(ctx, `
INSERT INTO iace_tech_file_sections (
id, project_id, section_type, title, content,
version, status, approved_by, approved_at, metadata,
id, project_id, tenant_id, section_type, title, html_content,
status, approved_by, approved_at, metadata,
created_at, updated_at
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8, $9, $10,
$1, $2, $3, $4, $5, $6,
$7, $8, $9, $10,
$11, $12
)
`,
tf.ID, tf.ProjectID, tf.SectionType, tf.Title, tf.Content,
tf.Version, string(tf.Status), uuid.Nil, nil, nil,
tf.ID, tf.ProjectID, tenantID, tf.SectionType, tf.Title, tf.Content,
string(tf.Status), "", nil, nil,
tf.CreatedAt, tf.UpdatedAt,
)
if err != nil {
@@ -50,12 +60,11 @@ func (s *Store) CreateTechFileSection(ctx context.Context, projectID uuid.UUID,
return tf, nil
}
// UpdateTechFileSection updates the content of a tech file section and bumps version
// UpdateTechFileSection updates the content of a tech file section
func (s *Store) UpdateTechFileSection(ctx context.Context, id uuid.UUID, content string) error {
_, err := s.pool.Exec(ctx, `
UPDATE iace_tech_file_sections SET
content = $2,
version = version + 1,
html_content = $2,
status = $3,
updated_at = NOW()
WHERE id = $1
@@ -69,19 +78,15 @@ func (s *Store) UpdateTechFileSection(ctx context.Context, id uuid.UUID, content
// ApproveTechFileSection marks a tech file section as approved
func (s *Store) ApproveTechFileSection(ctx context.Context, id uuid.UUID, approvedBy string) error {
now := time.Now().UTC()
approvedByUUID, err := uuid.Parse(approvedBy)
if err != nil {
return fmt.Errorf("invalid approved_by UUID: %w", err)
}
_, err = s.pool.Exec(ctx, `
_, err := s.pool.Exec(ctx, `
UPDATE iace_tech_file_sections SET
status = $2,
approved_by = $3,
approved_at = $4,
updated_at = $4
WHERE id = $1
`, id, string(TechFileSectionStatusApproved), approvedByUUID, now)
`, id, string(TechFileSectionStatusApproved), approvedBy, now)
if err != nil {
return fmt.Errorf("approve tech file section: %w", err)
}
@@ -93,11 +98,13 @@ func (s *Store) ApproveTechFileSection(ctx context.Context, id uuid.UUID, approv
func (s *Store) ListTechFileSections(ctx context.Context, projectID uuid.UUID) ([]TechFileSection, error) {
rows, err := s.pool.Query(ctx, `
SELECT
id, project_id, section_type, title, content,
version, status, approved_by, approved_at, metadata,
created_at, updated_at
id, project_id, section_type, title,
COALESCE(html_content, '') AS content, status,
COALESCE(approved_by, '') AS approved_by, approved_at,
COALESCE(metadata, '{}'::jsonb) AS metadata,
created_at, COALESCE(updated_at, created_at) AS updated_at
FROM iace_tech_file_sections WHERE project_id = $1
ORDER BY section_type ASC, created_at ASC
ORDER BY created_at ASC
`, projectID)
if err != nil {
return nil, fmt.Errorf("list tech file sections: %w", err)
@@ -108,11 +115,13 @@ func (s *Store) ListTechFileSections(ctx context.Context, projectID uuid.UUID) (
for rows.Next() {
var tf TechFileSection
var status string
var approvedByStr string
var metadata []byte
err := rows.Scan(
&tf.ID, &tf.ProjectID, &tf.SectionType, &tf.Title, &tf.Content,
&tf.Version, &status, &tf.ApprovedBy, &tf.ApprovedAt, &metadata,
&tf.ID, &tf.ProjectID, &tf.SectionType, &tf.Title,
&tf.Content, &status,
&approvedByStr, &tf.ApprovedAt, &metadata,
&tf.CreatedAt, &tf.UpdatedAt,
)
if err != nil {
@@ -120,6 +129,12 @@ func (s *Store) ListTechFileSections(ctx context.Context, projectID uuid.UUID) (
}
tf.Status = TechFileSectionStatus(status)
tf.Version = 1
if approvedByStr != "" {
if parsed, e := uuid.Parse(approvedByStr); e == nil {
tf.ApprovedBy = parsed
}
}
json.Unmarshal(metadata, &tf.Metadata)
sections = append(sections, tf)