Each of the four oversized files (training/store.go 1569 LOC, ucca/rules.go 1231 LOC, ucca_handlers.go 1135 LOC, document_export.go 1101 LOC) is split by logical group into same-package files, all under the 500-line hard cap. Zero behavior changes, no renamed exported symbols. Also fixed pre-existing hazard_library split (missing functions and duplicate UUID keys from a prior session). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
162 lines
5.7 KiB
Go
162 lines
5.7 KiB
Go
package iace
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ExportDOCX generates a minimal DOCX file containing the CE technical file sections
|
|
func (e *DocumentExporter) ExportDOCX(
|
|
project *Project,
|
|
sections []TechFileSection,
|
|
) ([]byte, error) {
|
|
if project == nil {
|
|
return nil, fmt.Errorf("project must not be nil")
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
zw := zip.NewWriter(&buf)
|
|
|
|
contentTypes := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
<Default Extension="xml" ContentType="application/xml"/>
|
|
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
|
</Types>`
|
|
if err := addZipEntry(zw, "[Content_Types].xml", contentTypes); err != nil {
|
|
return nil, fmt.Errorf("failed to write [Content_Types].xml: %w", err)
|
|
}
|
|
|
|
rels := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
|
</Relationships>`
|
|
if err := addZipEntry(zw, "_rels/.rels", rels); err != nil {
|
|
return nil, fmt.Errorf("failed to write _rels/.rels: %w", err)
|
|
}
|
|
|
|
docRels := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
</Relationships>`
|
|
if err := addZipEntry(zw, "word/_rels/document.xml.rels", docRels); err != nil {
|
|
return nil, fmt.Errorf("failed to write word/_rels/document.xml.rels: %w", err)
|
|
}
|
|
|
|
docXML := e.buildDocumentXML(project, sections)
|
|
if err := addZipEntry(zw, "word/document.xml", docXML); err != nil {
|
|
return nil, fmt.Errorf("failed to write word/document.xml: %w", err)
|
|
}
|
|
|
|
if err := zw.Close(); err != nil {
|
|
return nil, fmt.Errorf("failed to close ZIP: %w", err)
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func (e *DocumentExporter) buildDocumentXML(project *Project, sections []TechFileSection) string {
|
|
var body strings.Builder
|
|
|
|
body.WriteString(docxHeading(fmt.Sprintf("CE-Akte: %s", project.MachineName), 1))
|
|
|
|
metaLines := []string{
|
|
fmt.Sprintf("Hersteller: %s", project.Manufacturer),
|
|
fmt.Sprintf("Maschinentyp: %s", project.MachineType),
|
|
}
|
|
if project.CEMarkingTarget != "" {
|
|
metaLines = append(metaLines, fmt.Sprintf("CE-Kennzeichnungsziel: %s", project.CEMarkingTarget))
|
|
}
|
|
metaLines = append(metaLines,
|
|
fmt.Sprintf("Status: %s", project.Status),
|
|
fmt.Sprintf("Datum: %s", time.Now().Format("02.01.2006")),
|
|
)
|
|
for _, line := range metaLines {
|
|
body.WriteString(docxParagraph(line, false))
|
|
}
|
|
|
|
if project.Description != "" {
|
|
body.WriteString(docxParagraph("", false))
|
|
body.WriteString(docxParagraph(project.Description, true))
|
|
}
|
|
|
|
for _, section := range sections {
|
|
body.WriteString(docxHeading(section.Title, 2))
|
|
body.WriteString(docxParagraph(
|
|
fmt.Sprintf("Typ: %s | Status: %s | Version: %d",
|
|
section.SectionType, string(section.Status), section.Version),
|
|
true,
|
|
))
|
|
|
|
if section.Content != "" {
|
|
for _, line := range strings.Split(section.Content, "\n") {
|
|
body.WriteString(docxParagraph(line, false))
|
|
}
|
|
} else {
|
|
body.WriteString(docxParagraph("(Kein Inhalt vorhanden)", true))
|
|
}
|
|
}
|
|
|
|
body.WriteString(docxParagraph("", false))
|
|
body.WriteString(docxParagraph(
|
|
fmt.Sprintf("Generiert am %s mit BreakPilot AI Compliance SDK",
|
|
time.Now().Format("02.01.2006 15:04")),
|
|
true,
|
|
))
|
|
|
|
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
|
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
xmlns:o="urn:schemas-microsoft-com:office:office"
|
|
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
|
xmlns:v="urn:schemas-microsoft-com:vml"
|
|
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
|
xmlns:w10="urn:schemas-microsoft-com:office:word"
|
|
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
|
|
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
|
|
xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
|
|
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
|
|
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
|
|
mc:Ignorable="w14 wp14">
|
|
<w:body>
|
|
%s
|
|
</w:body>
|
|
</w:document>`, body.String())
|
|
}
|
|
|
|
// ExportJSON returns a JSON representation of the project export data
|
|
func (e *DocumentExporter) ExportJSON(
|
|
project *Project,
|
|
sections []TechFileSection,
|
|
hazards []Hazard,
|
|
assessments []RiskAssessment,
|
|
mitigations []Mitigation,
|
|
classifications []RegulatoryClassification,
|
|
) ([]byte, error) {
|
|
if project == nil {
|
|
return nil, fmt.Errorf("project must not be nil")
|
|
}
|
|
|
|
payload := map[string]interface{}{
|
|
"project": project,
|
|
"sections": sections,
|
|
"hazards": hazards,
|
|
"assessments": assessments,
|
|
"mitigations": mitigations,
|
|
"classifications": classifications,
|
|
"exported_at": time.Now().UTC().Format(time.RFC3339),
|
|
"format_version": "1.0",
|
|
}
|
|
|
|
data, err := json.MarshalIndent(payload, "", " ")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal JSON: %w", err)
|
|
}
|
|
return data, nil
|
|
}
|