Files
breakpilot-compliance/ai-compliance-sdk/internal/ucca/pdf_export.go
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:28 +01:00

511 lines
16 KiB
Go

package ucca
import (
"bytes"
"encoding/base64"
"fmt"
"time"
"github.com/jung-kurt/gofpdf"
)
// PDFExporter generates PDF documents from obligations assessments
type PDFExporter struct {
language string
}
// NewPDFExporter creates a new PDF exporter
func NewPDFExporter(language string) *PDFExporter {
if language == "" {
language = "de"
}
return &PDFExporter{language: language}
}
// ExportManagementMemo exports the management obligations overview as a PDF
func (e *PDFExporter) ExportManagementMemo(overview *ManagementObligationsOverview) (*ExportMemoResponse, error) {
pdf := gofpdf.New("P", "mm", "A4", "")
// Set UTF-8 support with DejaVu font (fallback to core fonts)
pdf.SetFont("Helvetica", "", 12)
// Add first page
pdf.AddPage()
// Add title
e.addTitle(pdf, overview)
// Add executive summary
e.addExecutiveSummary(pdf, overview)
// Add applicable regulations
e.addApplicableRegulations(pdf, overview)
// Add sanctions summary
e.addSanctionsSummary(pdf, overview)
// Add obligations table
e.addObligationsTable(pdf, overview)
// Add incident deadlines if present
if len(overview.IncidentDeadlines) > 0 {
e.addIncidentDeadlines(pdf, overview)
}
// Add footer with generation date
e.addFooter(pdf, overview)
// Generate PDF bytes
var buf bytes.Buffer
if err := pdf.Output(&buf); err != nil {
return nil, fmt.Errorf("failed to generate PDF: %w", err)
}
// Encode as base64
content := base64.StdEncoding.EncodeToString(buf.Bytes())
return &ExportMemoResponse{
Content: content,
ContentType: "application/pdf",
Filename: fmt.Sprintf("pflichten-uebersicht-%s.pdf", time.Now().Format("2006-01-02")),
GeneratedAt: time.Now(),
}, nil
}
// addTitle adds the document title
func (e *PDFExporter) addTitle(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) {
pdf.SetFont("Helvetica", "B", 24)
pdf.SetTextColor(0, 0, 0)
title := "Regulatorische Pflichten-Uebersicht"
if e.language == "en" {
title = "Regulatory Obligations Overview"
}
pdf.CellFormat(0, 15, title, "", 1, "C", false, 0, "")
// Organization name
if overview.OrganizationName != "" {
pdf.SetFont("Helvetica", "", 14)
pdf.CellFormat(0, 10, overview.OrganizationName, "", 1, "C", false, 0, "")
}
// Date
pdf.SetFont("Helvetica", "I", 10)
dateStr := overview.AssessmentDate.Format("02.01.2006")
pdf.CellFormat(0, 8, fmt.Sprintf("Stand: %s", dateStr), "", 1, "C", false, 0, "")
pdf.Ln(10)
}
// addExecutiveSummary adds the executive summary section
func (e *PDFExporter) addExecutiveSummary(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) {
e.addSectionHeader(pdf, "Executive Summary")
summary := overview.ExecutiveSummary
// Stats table
pdf.SetFont("Helvetica", "", 11)
stats := []struct {
label string
value string
}{
{"Anwendbare Regulierungen", fmt.Sprintf("%d", summary.TotalRegulations)},
{"Gesamtzahl Pflichten", fmt.Sprintf("%d", summary.TotalObligations)},
{"Kritische Pflichten", fmt.Sprintf("%d", summary.CriticalObligations)},
{"Kommende Fristen (30 Tage)", fmt.Sprintf("%d", summary.UpcomingDeadlines)},
{"Compliance Score", fmt.Sprintf("%d%%", summary.ComplianceScore)},
}
for _, stat := range stats {
pdf.SetFont("Helvetica", "B", 11)
pdf.CellFormat(80, 7, stat.label+":", "", 0, "L", false, 0, "")
pdf.SetFont("Helvetica", "", 11)
pdf.CellFormat(0, 7, stat.value, "", 1, "L", false, 0, "")
}
// Key risks
if len(summary.KeyRisks) > 0 {
pdf.Ln(5)
pdf.SetFont("Helvetica", "B", 11)
pdf.CellFormat(0, 7, "Wesentliche Risiken:", "", 1, "L", false, 0, "")
pdf.SetFont("Helvetica", "", 10)
for _, risk := range summary.KeyRisks {
pdf.CellFormat(10, 6, "", "", 0, "L", false, 0, "")
pdf.CellFormat(0, 6, "- "+risk, "", 1, "L", false, 0, "")
}
}
// Recommended actions
if len(summary.RecommendedActions) > 0 {
pdf.Ln(5)
pdf.SetFont("Helvetica", "B", 11)
pdf.CellFormat(0, 7, "Empfohlene Massnahmen:", "", 1, "L", false, 0, "")
pdf.SetFont("Helvetica", "", 10)
for _, action := range summary.RecommendedActions {
pdf.CellFormat(10, 6, "", "", 0, "L", false, 0, "")
pdf.CellFormat(0, 6, "- "+action, "", 1, "L", false, 0, "")
}
}
pdf.Ln(10)
}
// addApplicableRegulations adds the applicable regulations section
func (e *PDFExporter) addApplicableRegulations(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) {
e.addSectionHeader(pdf, "Anwendbare Regulierungen")
pdf.SetFont("Helvetica", "", 10)
// Table header
pdf.SetFillColor(240, 240, 240)
pdf.SetFont("Helvetica", "B", 10)
pdf.CellFormat(60, 8, "Regulierung", "1", 0, "L", true, 0, "")
pdf.CellFormat(50, 8, "Klassifizierung", "1", 0, "L", true, 0, "")
pdf.CellFormat(30, 8, "Pflichten", "1", 0, "C", true, 0, "")
pdf.CellFormat(50, 8, "Grund", "1", 1, "L", true, 0, "")
pdf.SetFont("Helvetica", "", 10)
for _, reg := range overview.ApplicableRegulations {
pdf.CellFormat(60, 7, reg.Name, "1", 0, "L", false, 0, "")
pdf.CellFormat(50, 7, truncateString(reg.Classification, 25), "1", 0, "L", false, 0, "")
pdf.CellFormat(30, 7, fmt.Sprintf("%d", reg.ObligationCount), "1", 0, "C", false, 0, "")
pdf.CellFormat(50, 7, truncateString(reg.Reason, 25), "1", 1, "L", false, 0, "")
}
pdf.Ln(10)
}
// addSanctionsSummary adds the sanctions summary section
func (e *PDFExporter) addSanctionsSummary(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) {
e.addSectionHeader(pdf, "Sanktionsrisiken")
sanctions := overview.SanctionsSummary
pdf.SetFont("Helvetica", "", 11)
// Max financial risk
if sanctions.MaxFinancialRisk != "" {
pdf.SetFont("Helvetica", "B", 11)
pdf.CellFormat(60, 7, "Max. Finanzrisiko:", "", 0, "L", false, 0, "")
pdf.SetFont("Helvetica", "", 11)
pdf.SetTextColor(200, 0, 0)
pdf.CellFormat(0, 7, sanctions.MaxFinancialRisk, "", 1, "L", false, 0, "")
pdf.SetTextColor(0, 0, 0)
}
// Personal liability
pdf.SetFont("Helvetica", "B", 11)
pdf.CellFormat(60, 7, "Persoenliche Haftung:", "", 0, "L", false, 0, "")
pdf.SetFont("Helvetica", "", 11)
liabilityText := "Nein"
if sanctions.PersonalLiabilityRisk {
liabilityText = "Ja - Geschaeftsfuehrung kann persoenlich haften"
pdf.SetTextColor(200, 0, 0)
}
pdf.CellFormat(0, 7, liabilityText, "", 1, "L", false, 0, "")
pdf.SetTextColor(0, 0, 0)
// Criminal liability
pdf.SetFont("Helvetica", "B", 11)
pdf.CellFormat(60, 7, "Strafrechtliche Konsequenzen:", "", 0, "L", false, 0, "")
pdf.SetFont("Helvetica", "", 11)
criminalText := "Nein"
if sanctions.CriminalLiabilityRisk {
criminalText = "Moeglich"
pdf.SetTextColor(200, 0, 0)
}
pdf.CellFormat(0, 7, criminalText, "", 1, "L", false, 0, "")
pdf.SetTextColor(0, 0, 0)
// Summary
if sanctions.Summary != "" {
pdf.Ln(3)
pdf.SetFont("Helvetica", "I", 10)
pdf.MultiCell(0, 5, sanctions.Summary, "", "L", false)
}
pdf.Ln(10)
}
// addObligationsTable adds the obligations table
func (e *PDFExporter) addObligationsTable(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) {
e.addSectionHeader(pdf, "Pflichten-Uebersicht")
// Group by priority
criticalObls := []Obligation{}
highObls := []Obligation{}
otherObls := []Obligation{}
for _, obl := range overview.Obligations {
switch obl.Priority {
case PriorityCritical, ObligationPriority("kritisch"):
criticalObls = append(criticalObls, obl)
case PriorityHigh, ObligationPriority("hoch"):
highObls = append(highObls, obl)
default:
otherObls = append(otherObls, obl)
}
}
// Critical obligations
if len(criticalObls) > 0 {
pdf.SetFont("Helvetica", "B", 11)
pdf.SetTextColor(200, 0, 0)
pdf.CellFormat(0, 8, fmt.Sprintf("Kritische Pflichten (%d)", len(criticalObls)), "", 1, "L", false, 0, "")
pdf.SetTextColor(0, 0, 0)
e.addObligationsList(pdf, criticalObls)
}
// High priority obligations
if len(highObls) > 0 {
pdf.SetFont("Helvetica", "B", 11)
pdf.SetTextColor(200, 100, 0)
pdf.CellFormat(0, 8, fmt.Sprintf("Hohe Prioritaet (%d)", len(highObls)), "", 1, "L", false, 0, "")
pdf.SetTextColor(0, 0, 0)
e.addObligationsList(pdf, highObls)
}
// Other obligations
if len(otherObls) > 0 {
pdf.SetFont("Helvetica", "B", 11)
pdf.CellFormat(0, 8, fmt.Sprintf("Weitere Pflichten (%d)", len(otherObls)), "", 1, "L", false, 0, "")
e.addObligationsList(pdf, otherObls)
}
}
// addObligationsList adds a list of obligations
func (e *PDFExporter) addObligationsList(pdf *gofpdf.Fpdf, obligations []Obligation) {
// Check if we need a new page
if pdf.GetY() > 250 {
pdf.AddPage()
}
pdf.SetFont("Helvetica", "", 9)
for _, obl := range obligations {
// Check if we need a new page
if pdf.GetY() > 270 {
pdf.AddPage()
}
// Obligation ID and title
pdf.SetFont("Helvetica", "B", 9)
pdf.CellFormat(25, 5, obl.ID, "", 0, "L", false, 0, "")
pdf.SetFont("Helvetica", "", 9)
pdf.CellFormat(0, 5, truncateString(obl.Title, 80), "", 1, "L", false, 0, "")
// Legal basis
if len(obl.LegalBasis) > 0 {
pdf.SetFont("Helvetica", "I", 8)
pdf.CellFormat(25, 4, "", "", 0, "L", false, 0, "")
legalText := ""
for i, lb := range obl.LegalBasis {
if i > 0 {
legalText += ", "
}
legalText += lb.Norm
}
pdf.CellFormat(0, 4, truncateString(legalText, 100), "", 1, "L", false, 0, "")
}
// Deadline
if obl.Deadline != nil {
pdf.SetFont("Helvetica", "", 8)
pdf.CellFormat(25, 4, "", "", 0, "L", false, 0, "")
deadlineText := "Frist: "
if obl.Deadline.Date != nil {
deadlineText += obl.Deadline.Date.Format("02.01.2006")
} else if obl.Deadline.Duration != "" {
deadlineText += obl.Deadline.Duration
} else if obl.Deadline.Interval != "" {
deadlineText += obl.Deadline.Interval
}
pdf.CellFormat(0, 4, deadlineText, "", 1, "L", false, 0, "")
}
// Responsible
pdf.SetFont("Helvetica", "", 8)
pdf.CellFormat(25, 4, "", "", 0, "L", false, 0, "")
pdf.CellFormat(0, 4, fmt.Sprintf("Verantwortlich: %s", obl.Responsible), "", 1, "L", false, 0, "")
pdf.Ln(2)
}
pdf.Ln(5)
}
// addIncidentDeadlines adds the incident deadlines section
func (e *PDFExporter) addIncidentDeadlines(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) {
// Check if we need a new page
if pdf.GetY() > 220 {
pdf.AddPage()
}
e.addSectionHeader(pdf, "Meldepflichten bei Vorfaellen")
pdf.SetFont("Helvetica", "", 10)
// Group by regulation
byRegulation := make(map[string][]IncidentDeadline)
for _, deadline := range overview.IncidentDeadlines {
byRegulation[deadline.RegulationID] = append(byRegulation[deadline.RegulationID], deadline)
}
for regID, deadlines := range byRegulation {
pdf.SetFont("Helvetica", "B", 10)
regName := regID
for _, reg := range overview.ApplicableRegulations {
if reg.ID == regID {
regName = reg.Name
break
}
}
pdf.CellFormat(0, 7, regName, "", 1, "L", false, 0, "")
pdf.SetFont("Helvetica", "", 9)
for _, dl := range deadlines {
pdf.CellFormat(40, 6, dl.Phase+":", "", 0, "L", false, 0, "")
pdf.SetFont("Helvetica", "B", 9)
pdf.CellFormat(30, 6, dl.Deadline, "", 0, "L", false, 0, "")
pdf.SetFont("Helvetica", "", 9)
pdf.CellFormat(0, 6, "an "+dl.Recipient, "", 1, "L", false, 0, "")
}
pdf.Ln(3)
}
pdf.Ln(5)
}
// addFooter adds the document footer
func (e *PDFExporter) addFooter(pdf *gofpdf.Fpdf, overview *ManagementObligationsOverview) {
pdf.SetY(-30)
pdf.SetFont("Helvetica", "I", 8)
pdf.SetTextColor(128, 128, 128)
pdf.CellFormat(0, 5, fmt.Sprintf("Generiert am %s mit BreakPilot AI Compliance SDK", time.Now().Format("02.01.2006 15:04")), "", 1, "C", false, 0, "")
pdf.CellFormat(0, 5, "Dieses Dokument ersetzt keine rechtliche Beratung.", "", 1, "C", false, 0, "")
}
// addSectionHeader adds a section header
func (e *PDFExporter) addSectionHeader(pdf *gofpdf.Fpdf, title string) {
pdf.SetFont("Helvetica", "B", 14)
pdf.SetTextColor(50, 50, 50)
pdf.CellFormat(0, 10, title, "", 1, "L", false, 0, "")
pdf.SetTextColor(0, 0, 0)
// Underline
pdf.SetDrawColor(200, 200, 200)
pdf.Line(10, pdf.GetY(), 200, pdf.GetY())
pdf.Ln(5)
}
// truncateString truncates a string to maxLen characters
func truncateString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen-3] + "..."
}
// ExportMarkdown exports the overview as Markdown (for compatibility)
func (e *PDFExporter) ExportMarkdown(overview *ManagementObligationsOverview) (*ExportMemoResponse, error) {
var buf bytes.Buffer
// Title
buf.WriteString(fmt.Sprintf("# Regulatorische Pflichten-Uebersicht\n\n"))
if overview.OrganizationName != "" {
buf.WriteString(fmt.Sprintf("**Organisation:** %s\n\n", overview.OrganizationName))
}
buf.WriteString(fmt.Sprintf("**Stand:** %s\n\n", overview.AssessmentDate.Format("02.01.2006")))
// Executive Summary
buf.WriteString("## Executive Summary\n\n")
summary := overview.ExecutiveSummary
buf.WriteString(fmt.Sprintf("| Metrik | Wert |\n"))
buf.WriteString(fmt.Sprintf("|--------|------|\n"))
buf.WriteString(fmt.Sprintf("| Anwendbare Regulierungen | %d |\n", summary.TotalRegulations))
buf.WriteString(fmt.Sprintf("| Gesamtzahl Pflichten | %d |\n", summary.TotalObligations))
buf.WriteString(fmt.Sprintf("| Kritische Pflichten | %d |\n", summary.CriticalObligations))
buf.WriteString(fmt.Sprintf("| Kommende Fristen (30 Tage) | %d |\n", summary.UpcomingDeadlines))
buf.WriteString(fmt.Sprintf("| Compliance Score | %d%% |\n\n", summary.ComplianceScore))
// Key Risks
if len(summary.KeyRisks) > 0 {
buf.WriteString("### Wesentliche Risiken\n\n")
for _, risk := range summary.KeyRisks {
buf.WriteString(fmt.Sprintf("- %s\n", risk))
}
buf.WriteString("\n")
}
// Recommended Actions
if len(summary.RecommendedActions) > 0 {
buf.WriteString("### Empfohlene Massnahmen\n\n")
for _, action := range summary.RecommendedActions {
buf.WriteString(fmt.Sprintf("- %s\n", action))
}
buf.WriteString("\n")
}
// Applicable Regulations
buf.WriteString("## Anwendbare Regulierungen\n\n")
buf.WriteString("| Regulierung | Klassifizierung | Pflichten | Grund |\n")
buf.WriteString("|-------------|-----------------|-----------|-------|\n")
for _, reg := range overview.ApplicableRegulations {
buf.WriteString(fmt.Sprintf("| %s | %s | %d | %s |\n", reg.Name, reg.Classification, reg.ObligationCount, reg.Reason))
}
buf.WriteString("\n")
// Sanctions Summary
buf.WriteString("## Sanktionsrisiken\n\n")
sanctions := overview.SanctionsSummary
if sanctions.MaxFinancialRisk != "" {
buf.WriteString(fmt.Sprintf("- **Max. Finanzrisiko:** %s\n", sanctions.MaxFinancialRisk))
}
buf.WriteString(fmt.Sprintf("- **Persoenliche Haftung:** %v\n", sanctions.PersonalLiabilityRisk))
buf.WriteString(fmt.Sprintf("- **Strafrechtliche Konsequenzen:** %v\n\n", sanctions.CriminalLiabilityRisk))
if sanctions.Summary != "" {
buf.WriteString(fmt.Sprintf("*%s*\n\n", sanctions.Summary))
}
// Obligations
buf.WriteString("## Pflichten-Uebersicht\n\n")
for _, obl := range overview.Obligations {
buf.WriteString(fmt.Sprintf("### %s - %s\n\n", obl.ID, obl.Title))
buf.WriteString(fmt.Sprintf("**Prioritaet:** %s | **Verantwortlich:** %s\n\n", obl.Priority, obl.Responsible))
if len(obl.LegalBasis) > 0 {
buf.WriteString("**Rechtsgrundlage:** ")
for i, lb := range obl.LegalBasis {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(lb.Norm)
}
buf.WriteString("\n\n")
}
buf.WriteString(fmt.Sprintf("%s\n\n", obl.Description))
}
// Incident Deadlines
if len(overview.IncidentDeadlines) > 0 {
buf.WriteString("## Meldepflichten bei Vorfaellen\n\n")
for _, dl := range overview.IncidentDeadlines {
buf.WriteString(fmt.Sprintf("- **%s:** %s an %s\n", dl.Phase, dl.Deadline, dl.Recipient))
}
buf.WriteString("\n")
}
// Footer
buf.WriteString("---\n\n")
buf.WriteString(fmt.Sprintf("*Generiert am %s mit BreakPilot AI Compliance SDK*\n", time.Now().Format("02.01.2006 15:04")))
return &ExportMemoResponse{
Content: buf.String(),
ContentType: "text/markdown",
Filename: fmt.Sprintf("pflichten-uebersicht-%s.md", time.Now().Format("2006-01-02")),
GeneratedAt: time.Now(),
}, nil
}