Files
breakpilot-compliance/admin-compliance/lib/sdk/loeschfristen-export.ts
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

354 lines
10 KiB
TypeScript

// =============================================================================
// Loeschfristen Module - Export & Report Generation
// JSON, CSV, Markdown-Compliance-Report und Browser-Download
// =============================================================================
import {
LoeschfristPolicy,
RETENTION_DRIVER_META,
DELETION_METHOD_LABELS,
STATUS_LABELS,
TRIGGER_LABELS,
formatRetentionDuration,
getEffectiveDeletionTrigger,
} from './loeschfristen-types'
import {
runComplianceCheck,
ComplianceCheckResult,
ComplianceIssueSeverity,
} from './loeschfristen-compliance'
// =============================================================================
// JSON EXPORT
// =============================================================================
interface PolicyExportEnvelope {
exportDate: string
version: string
totalPolicies: number
policies: LoeschfristPolicy[]
}
/**
* Exportiert alle Policies als pretty-printed JSON.
* Enthaelt Metadaten (Exportdatum, Version, Anzahl).
*/
export function exportPoliciesAsJSON(policies: LoeschfristPolicy[]): string {
const exportData: PolicyExportEnvelope = {
exportDate: new Date().toISOString(),
version: '1.0',
totalPolicies: policies.length,
policies: policies,
}
return JSON.stringify(exportData, null, 2)
}
// =============================================================================
// CSV EXPORT
// =============================================================================
/**
* Escapes a CSV field value according to RFC 4180.
* Fields containing commas, double quotes, or newlines are wrapped in quotes.
* Existing double quotes are doubled.
*/
function escapeCSVField(value: string): string {
if (
value.includes(',') ||
value.includes('"') ||
value.includes('\n') ||
value.includes('\r') ||
value.includes(';')
) {
return `"${value.replace(/"/g, '""')}"`
}
return value
}
/**
* Formats a date string to German locale format (DD.MM.YYYY).
* Returns empty string for null/undefined/empty values.
*/
function formatDateDE(dateStr: string | null | undefined): string {
if (!dateStr) return ''
try {
const date = new Date(dateStr)
if (isNaN(date.getTime())) return ''
return date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
} catch {
return ''
}
}
/**
* Exportiert alle Policies als CSV mit BOM fuer Excel-Kompatibilitaet.
* Trennzeichen ist Semikolon (;) fuer deutschsprachige Excel-Versionen.
*/
export function exportPoliciesAsCSV(policies: LoeschfristPolicy[]): string {
const BOM = '\uFEFF'
const SEPARATOR = ';'
const headers = [
'LF-Nr.',
'Datenobjekt',
'Beschreibung',
'Loeschtrigger',
'Aufbewahrungstreiber',
'Frist',
'Startereignis',
'Loeschmethode',
'Verantwortlich',
'Status',
'Legal Hold aktiv',
'Letzte Pruefung',
'Naechste Pruefung',
]
const rows: string[] = []
// Header row
rows.push(headers.map(escapeCSVField).join(SEPARATOR))
// Data rows
for (const policy of policies) {
const effectiveTrigger = getEffectiveDeletionTrigger(policy)
const triggerLabel = TRIGGER_LABELS[effectiveTrigger]
const driverLabel = policy.retentionDriver
? RETENTION_DRIVER_META[policy.retentionDriver].label
: ''
const durationLabel = formatRetentionDuration(
policy.retentionDuration,
policy.retentionUnit
)
const methodLabel = DELETION_METHOD_LABELS[policy.deletionMethod]
const statusLabel = STATUS_LABELS[policy.status]
// Combine responsiblePerson and responsibleRole
const responsible = [policy.responsiblePerson, policy.responsibleRole]
.filter((s) => s.trim())
.join(' / ')
const legalHoldActive = policy.hasActiveLegalHold ? 'Ja' : 'Nein'
const row = [
policy.policyId,
policy.dataObjectName,
policy.description,
triggerLabel,
driverLabel,
durationLabel,
policy.startEvent,
methodLabel,
responsible || '-',
statusLabel,
legalHoldActive,
formatDateDE(policy.lastReviewDate),
formatDateDE(policy.nextReviewDate),
]
rows.push(row.map(escapeCSVField).join(SEPARATOR))
}
return BOM + rows.join('\r\n')
}
// =============================================================================
// COMPLIANCE SUMMARY (MARKDOWN)
// =============================================================================
const SEVERITY_LABELS: Record<ComplianceIssueSeverity, string> = {
CRITICAL: 'Kritisch',
HIGH: 'Hoch',
MEDIUM: 'Mittel',
LOW: 'Niedrig',
}
const SEVERITY_EMOJI: Record<ComplianceIssueSeverity, string> = {
CRITICAL: '[!!!]',
HIGH: '[!!]',
MEDIUM: '[!]',
LOW: '[i]',
}
/**
* Returns a textual rating based on the compliance score.
*/
function getScoreRating(score: number): string {
if (score >= 90) return 'Ausgezeichnet'
if (score >= 75) return 'Gut'
if (score >= 50) return 'Verbesserungswuerdig'
if (score >= 25) return 'Mangelhaft'
return 'Kritisch'
}
/**
* Generiert einen Markdown-formatierten Compliance-Bericht.
* Enthaelt: Uebersicht, Score, Issue-Liste, Empfehlungen.
*/
export function generateComplianceSummary(
policies: LoeschfristPolicy[],
vvtDataCategories?: string[]
): string {
const result: ComplianceCheckResult = runComplianceCheck(policies, vvtDataCategories)
const now = new Date()
const lines: string[] = []
// Header
lines.push('# Compliance-Bericht: Loeschfristen')
lines.push('')
lines.push(
`**Erstellt am:** ${now.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })} um ${now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} Uhr`
)
lines.push('')
// Overview
lines.push('## Uebersicht')
lines.push('')
lines.push(`| Kennzahl | Wert |`)
lines.push(`|----------|------|`)
lines.push(`| Gepruefte Policies | ${result.stats.total} |`)
lines.push(`| Bestanden | ${result.stats.passed} |`)
lines.push(`| Beanstandungen | ${result.stats.failed} |`)
lines.push(`| Compliance-Score | **${result.score}/100** (${getScoreRating(result.score)}) |`)
lines.push('')
// Severity breakdown
lines.push('## Befunde nach Schweregrad')
lines.push('')
lines.push('| Schweregrad | Anzahl |')
lines.push('|-------------|--------|')
const severityOrder: ComplianceIssueSeverity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']
for (const severity of severityOrder) {
const count = result.stats.bySeverity[severity]
lines.push(`| ${SEVERITY_LABELS[severity]} | ${count} |`)
}
lines.push('')
// Status distribution of policies
const statusCounts: Record<string, number> = {}
for (const policy of policies) {
const label = STATUS_LABELS[policy.status]
statusCounts[label] = (statusCounts[label] || 0) + 1
}
lines.push('## Policy-Status-Verteilung')
lines.push('')
lines.push('| Status | Anzahl |')
lines.push('|--------|--------|')
for (const [label, count] of Object.entries(statusCounts)) {
lines.push(`| ${label} | ${count} |`)
}
lines.push('')
// Issues list
if (result.issues.length === 0) {
lines.push('## Befunde')
lines.push('')
lines.push('Keine Beanstandungen gefunden. Alle Policies sind konform.')
lines.push('')
} else {
lines.push('## Befunde')
lines.push('')
// Group issues by severity
for (const severity of severityOrder) {
const issuesForSeverity = result.issues.filter((i) => i.severity === severity)
if (issuesForSeverity.length === 0) continue
lines.push(`### ${SEVERITY_LABELS[severity]} ${SEVERITY_EMOJI[severity]}`)
lines.push('')
for (const issue of issuesForSeverity) {
const policyRef =
issue.policyId !== '-' ? ` (${issue.policyId})` : ''
lines.push(`**${issue.title}**${policyRef}`)
lines.push('')
lines.push(`> ${issue.description}`)
lines.push('')
lines.push(`Empfehlung: ${issue.recommendation}`)
lines.push('')
lines.push('---')
lines.push('')
}
}
}
// Recommendations summary
lines.push('## Zusammenfassung der Empfehlungen')
lines.push('')
if (result.stats.bySeverity.CRITICAL > 0) {
lines.push(
`1. **Sofortmassnahmen erforderlich:** ${result.stats.bySeverity.CRITICAL} kritische(r) Befund(e) muessen umgehend behoben werden (Legal Hold-Konflikte).`
)
}
if (result.stats.bySeverity.HIGH > 0) {
lines.push(
`${result.stats.bySeverity.CRITICAL > 0 ? '2' : '1'}. **Hohe Prioritaet:** ${result.stats.bySeverity.HIGH} Befund(e) mit hoher Prioritaet (fehlende Trigger/Rechtsgrundlagen) sollten zeitnah bearbeitet werden.`
)
}
if (result.stats.bySeverity.MEDIUM > 0) {
lines.push(
`- **Mittlere Prioritaet:** ${result.stats.bySeverity.MEDIUM} Befund(e) betreffen ueberfaellige Pruefungen, fehlende Verantwortlichkeiten oder nicht abgedeckte Datenkategorien.`
)
}
if (result.stats.bySeverity.LOW > 0) {
lines.push(
`- **Niedrige Prioritaet:** ${result.stats.bySeverity.LOW} Befund(e) betreffen veraltete Entwuerfe, die finalisiert oder archiviert werden sollten.`
)
}
if (result.issues.length === 0) {
lines.push(
'Alle Policies sind konform. Stellen Sie sicher, dass die naechsten Pruefungstermine eingehalten werden.'
)
}
lines.push('')
// Footer
lines.push('---')
lines.push('')
lines.push(
'*Dieser Bericht wurde automatisch generiert und ersetzt keine rechtliche Beratung. Die Verantwortung fuer die DSGVO-Konformitaet liegt beim Verantwortlichen (Art. 4 Nr. 7 DSGVO).*'
)
return lines.join('\n')
}
// =============================================================================
// BROWSER DOWNLOAD UTILITY
// =============================================================================
/**
* Loest einen Datei-Download im Browser aus.
* Erstellt ein temporaeres Blob-URL und simuliert einen Link-Klick.
*
* @param content - Der Dateiinhalt als String
* @param filename - Der gewuenschte Dateiname (z.B. "loeschfristen-export.json")
* @param mimeType - Der MIME-Typ (z.B. "application/json", "text/csv;charset=utf-8")
*/
export function downloadFile(
content: string,
filename: string,
mimeType: string
): void {
const blob = new Blob([content], { type: mimeType })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
}