Files
breakpilot-compliance/ai-compliance-sdk/internal/iace/document_export_excel.go
Sharang Parnerkar 9f96061631 refactor(go): split training/store, ucca/rules, ucca_handlers, document_export under 500 LOC
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>
2026-04-19 09:29:54 +02:00

262 lines
8.4 KiB
Go

package iace
import (
"fmt"
"github.com/xuri/excelize/v2"
)
// ExportExcel generates an XLSX workbook with project data across multiple sheets
func (e *DocumentExporter) ExportExcel(
project *Project,
sections []TechFileSection,
hazards []Hazard,
assessments []RiskAssessment,
mitigations []Mitigation,
) ([]byte, error) {
if project == nil {
return nil, fmt.Errorf("project must not be nil")
}
f := excelize.NewFile()
defer f.Close()
overviewSheet := "Uebersicht"
f.SetSheetName("Sheet1", overviewSheet)
e.xlsxOverview(f, overviewSheet, project)
hazardSheet := "Gefaehrdungsprotokoll"
f.NewSheet(hazardSheet)
e.xlsxHazardLog(f, hazardSheet, hazards, assessments)
mitigationSheet := "Massnahmen"
f.NewSheet(mitigationSheet)
e.xlsxMitigations(f, mitigationSheet, mitigations)
matrixSheet := "Risikomatrix"
f.NewSheet(matrixSheet)
e.xlsxRiskMatrix(f, matrixSheet, assessments)
sectionSheet := "Sektionen"
f.NewSheet(sectionSheet)
e.xlsxSections(f, sectionSheet, sections)
buf, err := f.WriteToBuffer()
if err != nil {
return nil, fmt.Errorf("failed to write Excel: %w", err)
}
return buf.Bytes(), nil
}
func (e *DocumentExporter) xlsxOverview(f *excelize.File, sheet string, project *Project) {
headerStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Size: 11},
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"D9E1F2"}},
})
f.SetColWidth(sheet, "A", "A", 30)
f.SetColWidth(sheet, "B", "B", 50)
rows := [][]string{
{"Eigenschaft", "Wert"},
{"Maschinenname", project.MachineName},
{"Maschinentyp", project.MachineType},
{"Hersteller", project.Manufacturer},
{"Beschreibung", project.Description},
{"CE-Kennzeichnungsziel", project.CEMarkingTarget},
{"Projektstatus", string(project.Status)},
{"Vollstaendigkeits-Score", fmt.Sprintf("%.1f%%", project.CompletenessScore*100)},
{"Erstellt am", project.CreatedAt.Format("02.01.2006 15:04")},
{"Aktualisiert am", project.UpdatedAt.Format("02.01.2006 15:04")},
}
for i, row := range rows {
rowNum := i + 1
f.SetCellValue(sheet, cellRef("A", rowNum), row[0])
f.SetCellValue(sheet, cellRef("B", rowNum), row[1])
if i == 0 {
f.SetCellStyle(sheet, cellRef("A", rowNum), cellRef("B", rowNum), headerStyle)
}
}
}
func (e *DocumentExporter) xlsxHazardLog(f *excelize.File, sheet string, hazards []Hazard, assessments []RiskAssessment) {
headerStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Size: 10, Color: "FFFFFF"},
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}},
Alignment: &excelize.Alignment{Horizontal: "center"},
})
headers := []string{"Nr", "Name", "Kategorie", "Beschreibung", "S", "E", "P", "A",
"Inherent Risk", "C_eff", "Residual Risk", "Risk Level", "Akzeptabel"}
colWidths := map[string]float64{
"A": 6, "B": 25, "C": 20, "D": 35, "E": 8, "F": 8, "G": 8, "H": 8,
"I": 14, "J": 10, "K": 14, "L": 18, "M": 12,
}
for col, w := range colWidths {
f.SetColWidth(sheet, col, col, w)
}
cols := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"}
for i, h := range headers {
f.SetCellValue(sheet, cellRef(cols[i], 1), h)
}
f.SetCellStyle(sheet, "A1", cellRef(cols[len(cols)-1], 1), headerStyle)
assessMap := buildAssessmentMap(assessments)
for i, hazard := range hazards {
row := i + 2
a := assessMap[hazard.ID.String()]
f.SetCellValue(sheet, cellRef("A", row), i+1)
f.SetCellValue(sheet, cellRef("B", row), hazard.Name)
f.SetCellValue(sheet, cellRef("C", row), hazard.Category)
f.SetCellValue(sheet, cellRef("D", row), hazard.Description)
if a != nil {
f.SetCellValue(sheet, cellRef("E", row), a.Severity)
f.SetCellValue(sheet, cellRef("F", row), a.Exposure)
f.SetCellValue(sheet, cellRef("G", row), a.Probability)
f.SetCellValue(sheet, cellRef("H", row), a.Avoidance)
f.SetCellValue(sheet, cellRef("I", row), fmt.Sprintf("%.1f", a.InherentRisk))
f.SetCellValue(sheet, cellRef("J", row), fmt.Sprintf("%.2f", a.CEff))
f.SetCellValue(sheet, cellRef("K", row), fmt.Sprintf("%.1f", a.ResidualRisk))
f.SetCellValue(sheet, cellRef("L", row), riskLevelLabel(a.RiskLevel))
acceptStr := "Nein"
if a.IsAcceptable {
acceptStr = "Ja"
}
f.SetCellValue(sheet, cellRef("M", row), acceptStr)
r, g, b := riskLevelColor(a.RiskLevel)
style, _ := f.NewStyle(&excelize.Style{
Fill: excelize.Fill{
Type: "pattern",
Pattern: 1,
Color: []string{rgbHex(r, g, b)},
},
Alignment: &excelize.Alignment{Horizontal: "center"},
})
f.SetCellStyle(sheet, cellRef("L", row), cellRef("L", row), style)
}
}
}
func (e *DocumentExporter) xlsxMitigations(f *excelize.File, sheet string, mitigations []Mitigation) {
headerStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Size: 10, Color: "FFFFFF"},
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}},
Alignment: &excelize.Alignment{Horizontal: "center"},
})
headers := []string{"Nr", "Name", "Typ", "Beschreibung", "Status", "Verifikationsmethode", "Ergebnis"}
cols := []string{"A", "B", "C", "D", "E", "F", "G"}
f.SetColWidth(sheet, "A", "A", 6)
f.SetColWidth(sheet, "B", "B", 25)
f.SetColWidth(sheet, "C", "C", 15)
f.SetColWidth(sheet, "D", "D", 35)
f.SetColWidth(sheet, "E", "E", 15)
f.SetColWidth(sheet, "F", "F", 22)
f.SetColWidth(sheet, "G", "G", 25)
for i, h := range headers {
f.SetCellValue(sheet, cellRef(cols[i], 1), h)
}
f.SetCellStyle(sheet, "A1", cellRef(cols[len(cols)-1], 1), headerStyle)
for i, m := range mitigations {
row := i + 2
f.SetCellValue(sheet, cellRef("A", row), i+1)
f.SetCellValue(sheet, cellRef("B", row), m.Name)
f.SetCellValue(sheet, cellRef("C", row), reductionTypeLabel(m.ReductionType))
f.SetCellValue(sheet, cellRef("D", row), m.Description)
f.SetCellValue(sheet, cellRef("E", row), mitigationStatusLabel(m.Status))
f.SetCellValue(sheet, cellRef("F", row), string(m.VerificationMethod))
f.SetCellValue(sheet, cellRef("G", row), m.VerificationResult)
}
}
func (e *DocumentExporter) xlsxRiskMatrix(f *excelize.File, sheet string, assessments []RiskAssessment) {
headerStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Size: 10, Color: "FFFFFF"},
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}},
Alignment: &excelize.Alignment{Horizontal: "center"},
})
f.SetColWidth(sheet, "A", "A", 25)
f.SetColWidth(sheet, "B", "B", 12)
f.SetCellValue(sheet, "A1", "Risikostufe")
f.SetCellValue(sheet, "B1", "Anzahl")
f.SetCellStyle(sheet, "A1", "B1", headerStyle)
counts := countByRiskLevel(assessments)
levels := []RiskLevel{
RiskLevelNotAcceptable,
RiskLevelVeryHigh,
RiskLevelCritical,
RiskLevelHigh,
RiskLevelMedium,
RiskLevelLow,
RiskLevelNegligible,
}
row := 2
for _, level := range levels {
count := counts[level]
f.SetCellValue(sheet, cellRef("A", row), riskLevelLabel(level))
f.SetCellValue(sheet, cellRef("B", row), count)
r, g, b := riskLevelColor(level)
style, _ := f.NewStyle(&excelize.Style{
Fill: excelize.Fill{
Type: "pattern",
Pattern: 1,
Color: []string{rgbHex(r, g, b)},
},
})
f.SetCellStyle(sheet, cellRef("A", row), cellRef("B", row), style)
row++
}
totalStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true},
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"D9E1F2"}},
})
f.SetCellValue(sheet, cellRef("A", row), "Gesamt")
f.SetCellValue(sheet, cellRef("B", row), len(assessments))
f.SetCellStyle(sheet, cellRef("A", row), cellRef("B", row), totalStyle)
}
func (e *DocumentExporter) xlsxSections(f *excelize.File, sheet string, sections []TechFileSection) {
headerStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Size: 10, Color: "FFFFFF"},
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}},
Alignment: &excelize.Alignment{Horizontal: "center"},
})
f.SetColWidth(sheet, "A", "A", 25)
f.SetColWidth(sheet, "B", "B", 40)
f.SetColWidth(sheet, "C", "C", 15)
headers := []string{"Sektion", "Titel", "Status"}
cols := []string{"A", "B", "C"}
for i, h := range headers {
f.SetCellValue(sheet, cellRef(cols[i], 1), h)
}
f.SetCellStyle(sheet, "A1", cellRef(cols[len(cols)-1], 1), headerStyle)
for i, s := range sections {
row := i + 2
f.SetCellValue(sheet, cellRef("A", row), s.SectionType)
f.SetCellValue(sheet, cellRef("B", row), s.Title)
f.SetCellValue(sheet, cellRef("C", row), string(s.Status))
}
}