This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +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
}