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:
@@ -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 { useParams } from 'next/navigation'
|
||||||
import { TechFileEditor } from '@/components/sdk/iace/TechFileEditor'
|
import { TechFileEditor } from '@/components/sdk/iace/TechFileEditor'
|
||||||
import { ReportGenerator } from './_components/ReportGenerator'
|
import { ReportGenerator } from './_components/ReportGenerator'
|
||||||
|
import { SECTION_TYPES, STATUS_CONFIG, EXPORT_FORMATS } from './_constants'
|
||||||
|
|
||||||
interface TechFileSection {
|
interface TechFileSection {
|
||||||
id: string
|
id: string
|
||||||
@@ -18,108 +19,6 @@ interface TechFileSection {
|
|||||||
required: boolean
|
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 }) {
|
function StatusBadge({ status }: { status: string }) {
|
||||||
const config = STATUS_CONFIG[status] || STATUS_CONFIG.empty
|
const config = STATUS_CONFIG[status] || STATUS_CONFIG.empty
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -14,8 +14,18 @@ import (
|
|||||||
// Tech File Section Operations
|
// 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) {
|
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{
|
tf := &TechFileSection{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
@@ -28,19 +38,19 @@ func (s *Store) CreateTechFileSection(ctx context.Context, projectID uuid.UUID,
|
|||||||
UpdatedAt: time.Now().UTC(),
|
UpdatedAt: time.Now().UTC(),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.pool.Exec(ctx, `
|
_, err = s.pool.Exec(ctx, `
|
||||||
INSERT INTO iace_tech_file_sections (
|
INSERT INTO iace_tech_file_sections (
|
||||||
id, project_id, section_type, title, content,
|
id, project_id, tenant_id, section_type, title, html_content,
|
||||||
version, status, approved_by, approved_at, metadata,
|
status, approved_by, approved_at, metadata,
|
||||||
created_at, updated_at
|
created_at, updated_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5,
|
$1, $2, $3, $4, $5, $6,
|
||||||
$6, $7, $8, $9, $10,
|
$7, $8, $9, $10,
|
||||||
$11, $12
|
$11, $12
|
||||||
)
|
)
|
||||||
`,
|
`,
|
||||||
tf.ID, tf.ProjectID, tf.SectionType, tf.Title, tf.Content,
|
tf.ID, tf.ProjectID, tenantID, tf.SectionType, tf.Title, tf.Content,
|
||||||
tf.Version, string(tf.Status), uuid.Nil, nil, nil,
|
string(tf.Status), "", nil, nil,
|
||||||
tf.CreatedAt, tf.UpdatedAt,
|
tf.CreatedAt, tf.UpdatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -50,12 +60,11 @@ func (s *Store) CreateTechFileSection(ctx context.Context, projectID uuid.UUID,
|
|||||||
return tf, nil
|
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 {
|
func (s *Store) UpdateTechFileSection(ctx context.Context, id uuid.UUID, content string) error {
|
||||||
_, err := s.pool.Exec(ctx, `
|
_, err := s.pool.Exec(ctx, `
|
||||||
UPDATE iace_tech_file_sections SET
|
UPDATE iace_tech_file_sections SET
|
||||||
content = $2,
|
html_content = $2,
|
||||||
version = version + 1,
|
|
||||||
status = $3,
|
status = $3,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1
|
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
|
// ApproveTechFileSection marks a tech file section as approved
|
||||||
func (s *Store) ApproveTechFileSection(ctx context.Context, id uuid.UUID, approvedBy string) error {
|
func (s *Store) ApproveTechFileSection(ctx context.Context, id uuid.UUID, approvedBy string) error {
|
||||||
now := time.Now().UTC()
|
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
|
UPDATE iace_tech_file_sections SET
|
||||||
status = $2,
|
status = $2,
|
||||||
approved_by = $3,
|
approved_by = $3,
|
||||||
approved_at = $4,
|
approved_at = $4,
|
||||||
updated_at = $4
|
updated_at = $4
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`, id, string(TechFileSectionStatusApproved), approvedByUUID, now)
|
`, id, string(TechFileSectionStatusApproved), approvedBy, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("approve tech file section: %w", err)
|
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) {
|
func (s *Store) ListTechFileSections(ctx context.Context, projectID uuid.UUID) ([]TechFileSection, error) {
|
||||||
rows, err := s.pool.Query(ctx, `
|
rows, err := s.pool.Query(ctx, `
|
||||||
SELECT
|
SELECT
|
||||||
id, project_id, section_type, title, content,
|
id, project_id, section_type, title,
|
||||||
version, status, approved_by, approved_at, metadata,
|
COALESCE(html_content, '') AS content, status,
|
||||||
created_at, updated_at
|
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
|
FROM iace_tech_file_sections WHERE project_id = $1
|
||||||
ORDER BY section_type ASC, created_at ASC
|
ORDER BY created_at ASC
|
||||||
`, projectID)
|
`, projectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("list tech file sections: %w", err)
|
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() {
|
for rows.Next() {
|
||||||
var tf TechFileSection
|
var tf TechFileSection
|
||||||
var status string
|
var status string
|
||||||
|
var approvedByStr string
|
||||||
var metadata []byte
|
var metadata []byte
|
||||||
|
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&tf.ID, &tf.ProjectID, &tf.SectionType, &tf.Title, &tf.Content,
|
&tf.ID, &tf.ProjectID, &tf.SectionType, &tf.Title,
|
||||||
&tf.Version, &status, &tf.ApprovedBy, &tf.ApprovedAt, &metadata,
|
&tf.Content, &status,
|
||||||
|
&approvedByStr, &tf.ApprovedAt, &metadata,
|
||||||
&tf.CreatedAt, &tf.UpdatedAt,
|
&tf.CreatedAt, &tf.UpdatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,6 +129,12 @@ func (s *Store) ListTechFileSections(ctx context.Context, projectID uuid.UUID) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
tf.Status = TechFileSectionStatus(status)
|
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)
|
json.Unmarshal(metadata, &tf.Metadata)
|
||||||
|
|
||||||
sections = append(sections, tf)
|
sections = append(sections, tf)
|
||||||
|
|||||||
Reference in New Issue
Block a user