feat: A4 preview + example data + company profile presets

Feature 1: DIN A4 Preview
- Markdown→HTML renderer (inline, no dependency)
- A4 page container (210mm × 297mm) with print styling
- Toggle between "Vorschau" (rendered A4) and "Markdown" (raw)
- Print button opens new window with @page A4 CSS
- Purple theme for headings, styled tables

Feature 2: Example Data Button
- "Beispieldaten" button in Generator header
- Loads examples/{templateType}_{lang}.json
- Prefills all context fields for instant full preview

Feature 3: Company Profile Presets
- 10 industry presets: SaaS Startup, Consumer App, E-Commerce,
  IT-Agentur, Maschinenbau, Rechtsanwalt, Arztpraxis, Handwerk,
  Bildung, Enterprise
- Each with pre-filled CompanyProfile + scope hints + recommended docs
- PresetSelector component (card grid with icons)
- "Manuell ausfuellen" skip option

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-03 07:38:18 +02:00
parent 64700b355e
commit a56ea2c843
4 changed files with 558 additions and 38 deletions
@@ -0,0 +1,49 @@
'use client'
import { COMPANY_PROFILE_PRESETS, type CompanyProfilePreset } from '@/lib/sdk/company-profile-presets'
interface PresetSelectorProps {
onSelect: (preset: CompanyProfilePreset) => void
onSkip: () => void
}
export function PresetSelector({ onSelect, onSkip }: PresetSelectorProps) {
return (
<div className="space-y-6">
<div className="text-center">
<h2 className="text-xl font-bold text-gray-900">Welcher Unternehmenstyp passt zu Ihnen?</h2>
<p className="text-sm text-gray-500 mt-2">
Waehlen Sie eine Vorlage fuer Ihre Branche alle Felder werden vorbefuellt
und Sie koennen anschliessend anpassen.
</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3">
{COMPANY_PROFILE_PRESETS.map((preset) => (
<button
key={preset.id}
onClick={() => onSelect(preset)}
className="flex flex-col items-center gap-2 p-4 bg-white border border-gray-200 rounded-xl hover:border-purple-400 hover:shadow-md transition-all text-center group"
>
<span className="text-3xl">{preset.icon}</span>
<span className="text-sm font-medium text-gray-900 group-hover:text-purple-700">
{preset.label}
</span>
<span className="text-xs text-gray-500 leading-tight">
{preset.description}
</span>
</button>
))}
</div>
<div className="text-center">
<button
onClick={onSkip}
className="text-sm text-gray-400 hover:text-gray-600 underline"
>
Manuell ausfuellen (ohne Vorlage)
</button>
</div>
</div>
)
}
@@ -1,5 +1,6 @@
'use client'
import { useState } from 'react'
import { LegalTemplateResult } from '@/lib/sdk/types'
import { RuleEngineResult } from '../ruleEngine'
@@ -12,6 +13,72 @@ interface GeneratorPreviewTabProps {
onExportMarkdown: () => void
}
// ============================================================================
// Lightweight Markdown → HTML (no dependency needed)
// ============================================================================
function markdownToHtml(md: string): string {
let html = md
// Escape HTML entities first
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
// Headings
html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>')
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>')
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>')
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>')
// Horizontal rules
html = html.replace(/^---$/gm, '<hr/>')
// Bold + Italic
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>')
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>')
// Links
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="text-purple-600 underline">$1</a>')
// Tables (simple)
html = html.replace(/^\|(.+)\|$/gm, (match) => {
const cells = match.split('|').filter(c => c.trim())
const isHeader = cells.every(c => /^[\s-:]+$/.test(c))
if (isHeader) return '<!-- separator -->'
const tag = 'td'
return '<tr>' + cells.map(c => `<${tag}>${c.trim()}</${tag}>`).join('') + '</tr>'
})
// Wrap consecutive table rows
html = html.replace(/((?:<tr>.*<\/tr>\n?<!-- separator -->\n?)?(?:<tr>.*<\/tr>\n?)+)/g, (block) => {
const rows = block.split('\n').filter(r => r.startsWith('<tr>'))
if (rows.length === 0) return block
const headerRow = rows[0].replace(/<td>/g, '<th>').replace(/<\/td>/g, '</th>')
const bodyRows = rows.slice(1).join('\n')
return `<table><thead>${headerRow}</thead><tbody>${bodyRows}</tbody></table>`
})
// Remove separator comments
html = html.replace(/<!-- separator -->\n?/g, '')
// Unordered lists
html = html.replace(/^- (.+)$/gm, '<li>$1</li>')
html = html.replace(/((?:<li>.*<\/li>\n?)+)/g, '<ul>$1</ul>')
// Paragraphs (lines that aren't already HTML)
html = html.replace(/^(?!<[a-z/]|$)(.+)$/gm, '<p>$1</p>')
// Clean up empty paragraphs
html = html.replace(/<p>\s*<\/p>/g, '')
return html
}
// ============================================================================
// Component
// ============================================================================
export default function GeneratorPreviewTab({
template,
ruleResult,
@@ -20,12 +87,17 @@ export default function GeneratorPreviewTab({
onCopy,
onExportMarkdown,
}: GeneratorPreviewTabProps) {
const [viewMode, setViewMode] = useState<'preview' | 'markdown'>('preview')
const htmlContent = markdownToHtml(renderedContent)
return (
<div className="space-y-4">
{/* Violations */}
{ruleResult && ruleResult.violations.length > 0 && (
<div className="bg-red-50 border border-red-200 rounded-xl p-4">
<p className="text-sm font-semibold text-red-700 mb-2">
🔴 {ruleResult.violations.length} Fehler
{ruleResult.violations.length} Fehler
</p>
<ul className="space-y-1">
{ruleResult.violations.map((v) => (
@@ -36,6 +108,8 @@ export default function GeneratorPreviewTab({
</ul>
</div>
)}
{/* Warnings */}
{ruleResult && ruleResult.warnings.filter((w) => w.id !== 'WARN_LEGAL_REVIEW').length > 0 && (
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-4">
<ul className="space-y-1">
@@ -43,69 +117,133 @@ export default function GeneratorPreviewTab({
.filter((w) => w.id !== 'WARN_LEGAL_REVIEW')
.map((w) => (
<li key={w.id} className="text-xs text-yellow-700">
🟡 <span className="font-mono font-medium">[{w.id}]</span> {w.message}
<span className="font-mono font-medium">[{w.id}]</span> {w.message}
</li>
))}
</ul>
</div>
)}
{/* Legal notice */}
{ruleResult && (
<div className="bg-blue-50 border border-blue-200 rounded-xl p-3">
<p className="text-xs text-blue-700">
Rechtlicher Hinweis: Diese Vorlage ist MIT-lizenziert. Vor Produktionseinsatz
wird eine rechtliche Überprüfung dringend empfohlen.
Rechtlicher Hinweis: Diese Vorlage ist MIT-lizenziert. Vor Produktionseinsatz
wird eine rechtliche Ueberpruefung dringend empfohlen.
</p>
</div>
)}
{ruleResult && ruleResult.appliedDefaults.length > 0 && (
<p className="text-xs text-gray-400">
Defaults angewendet: {ruleResult.appliedDefaults.join(', ')}
</p>
)}
{/* Toolbar */}
<div className="flex items-center justify-between flex-wrap gap-2">
<span className="text-sm text-gray-600">
{missing.length > 0 && (
<span className="text-orange-600">
{missing.length} Platzhalter noch nicht ausgefüllt
</span>
)}
</span>
<div className="flex gap-2">
<div className="flex gap-1 bg-gray-100 rounded-lg p-0.5">
<button
onClick={onCopy}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs border border-gray-200 rounded-lg hover:bg-gray-50 text-gray-600 transition-colors"
onClick={() => setViewMode('preview')}
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
viewMode === 'preview' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500'
}`}
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
Kopieren
Vorschau
</button>
<button
onClick={onExportMarkdown}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs border border-gray-200 rounded-lg hover:bg-gray-50 text-gray-600 transition-colors"
onClick={() => setViewMode('markdown')}
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
viewMode === 'markdown' ? 'bg-white text-gray-900 shadow-sm' : 'text-gray-500'
}`}
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
Markdown
</button>
</div>
<div className="flex items-center gap-2">
{missing.length > 0 && (
<span className="text-xs text-orange-600">
{missing.length} Platzhalter offen
</span>
)}
<button onClick={onCopy} className="px-3 py-1.5 text-xs border border-gray-200 rounded-lg hover:bg-gray-50 text-gray-600">
Kopieren
</button>
<button onClick={onExportMarkdown} className="px-3 py-1.5 text-xs border border-gray-200 rounded-lg hover:bg-gray-50 text-gray-600">
Markdown
</button>
<button
onClick={() => window.print()}
className="flex items-center gap-1.5 px-4 py-1.5 text-xs bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
onClick={() => {
const printWindow = window.open('', '_blank')
if (!printWindow) return
printWindow.document.write(`<!DOCTYPE html><html><head><title>${template.documentTitle || 'Dokument'}</title><style>
@page { size: A4; margin: 25mm 20mm; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 11pt; line-height: 1.6; color: #1a202c; max-width: 170mm; margin: 0 auto; }
h1 { font-size: 18pt; color: #5b21b6; margin: 24pt 0 8pt; border-bottom: 2px solid #7c3aed; padding-bottom: 4pt; }
h2 { font-size: 14pt; color: #1f2937; margin: 18pt 0 6pt; }
h3 { font-size: 12pt; color: #374151; margin: 12pt 0 4pt; }
h4 { font-size: 11pt; color: #4b5563; margin: 10pt 0 4pt; }
table { width: 100%; border-collapse: collapse; margin: 8pt 0; font-size: 10pt; }
th { background: #f5f3ff; color: #5b21b6; font-weight: 600; text-align: left; padding: 6pt 8pt; border: 1px solid #e5e7eb; }
td { padding: 5pt 8pt; border: 1px solid #e5e7eb; vertical-align: top; }
ul { padding-left: 20pt; }
li { margin: 2pt 0; }
hr { border: none; border-top: 1px solid #e5e7eb; margin: 16pt 0; }
a { color: #7c3aed; }
p { margin: 4pt 0; }
strong { font-weight: 600; }
</style></head><body>${htmlContent}</body></html>`)
printWindow.document.close()
printWindow.print()
}}
className="px-4 py-1.5 text-xs bg-purple-600 text-white rounded-lg hover:bg-purple-700"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg>
PDF drucken
</button>
</div>
</div>
<div className="bg-gray-50 rounded-xl border border-gray-200 p-6 max-h-[600px] overflow-y-auto">
<pre className="text-sm text-gray-800 whitespace-pre-wrap leading-relaxed font-sans">
{renderedContent}
</pre>
</div>
{/* Content */}
{viewMode === 'markdown' ? (
<div className="bg-gray-50 rounded-xl border border-gray-200 p-6 max-h-[800px] overflow-y-auto">
<pre className="text-sm text-gray-800 whitespace-pre-wrap leading-relaxed font-mono">
{renderedContent}
</pre>
</div>
) : (
<div className="bg-gray-100 rounded-xl p-8 flex justify-center overflow-y-auto max-h-[85vh]">
{/* A4 Page */}
<div
className="bg-white shadow-lg border border-gray-300"
style={{
width: '210mm',
minHeight: '297mm',
padding: '25mm 20mm',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
fontSize: '11pt',
lineHeight: '1.6',
color: '#1a202c',
}}
>
<style>{`
.a4-content h1 { font-size: 18pt; color: #5b21b6; margin: 24pt 0 8pt; border-bottom: 2px solid #7c3aed; padding-bottom: 4pt; }
.a4-content h2 { font-size: 14pt; color: #1f2937; margin: 18pt 0 6pt; }
.a4-content h3 { font-size: 12pt; color: #374151; margin: 12pt 0 4pt; }
.a4-content h4 { font-size: 11pt; color: #4b5563; margin: 10pt 0 4pt; }
.a4-content table { width: 100%; border-collapse: collapse; margin: 8pt 0; font-size: 10pt; }
.a4-content th { background: #f5f3ff; color: #5b21b6; font-weight: 600; text-align: left; padding: 6pt 8pt; border: 1px solid #e5e7eb; }
.a4-content td { padding: 5pt 8pt; border: 1px solid #e5e7eb; vertical-align: top; }
.a4-content ul { padding-left: 20pt; margin: 4pt 0; }
.a4-content li { margin: 2pt 0; }
.a4-content hr { border: none; border-top: 1px solid #e5e7eb; margin: 16pt 0; }
.a4-content a { color: #7c3aed; text-decoration: underline; }
.a4-content p { margin: 4pt 0; }
.a4-content strong { font-weight: 600; }
`}</style>
<div
className="a4-content"
dangerouslySetInnerHTML={{ __html: htmlContent }}
/>
</div>
</div>
)}
{/* Attribution */}
{template.attributionRequired && template.attributionText && (
<div className="text-xs text-orange-600 bg-orange-50 p-3 rounded-lg border border-orange-200">
<strong>Attribution erforderlich:</strong> {template.attributionText}
@@ -160,6 +160,33 @@ export default function GeneratorSection({
</div>
)}
</div>
<div className="flex items-center gap-2 shrink-0">
<button
onClick={() => {
// Load example data for current template type
const templateType = template.templateType || ''
const lang = template.language || 'de'
const exampleFile = `/sdk/document-generator/examples/${templateType}_${lang}.json`
fetch(exampleFile)
.then(r => r.ok ? r.json() : null)
.then(data => {
if (!data?.context) return
const ctx = data.context
for (const [section, fields] of Object.entries(ctx)) {
if (typeof fields === 'object' && fields) {
for (const [key, value] of Object.entries(fields as Record<string, unknown>)) {
onContextChange(section as keyof TemplateContext, key, value)
}
}
}
})
.catch(() => {/* no example available */})
}}
className="px-3 py-1 text-xs bg-blue-50 text-blue-600 border border-blue-200 rounded-lg hover:bg-blue-100 transition-colors"
>
Beispieldaten
</button>
</div>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors shrink-0" aria-label="Schließen">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -0,0 +1,306 @@
/**
* Company Profile Presets — Branchenvorlagen fuer typische Kundenszenarien
*
* Jeder Preset enthaelt ein vorbefuelltes CompanyProfile + typische Scope-Antworten.
* Der Kunde waehlt beim Onboarding ein Profil und passt es dann an.
*/
export interface CompanyProfilePreset {
id: string
label: string
description: string
icon: string
/** Vorbefuellte CompanyProfile-Felder */
profile: {
legalForm: string
industry: string[]
businessModel: string
companySize: string
employeeCount: string
headquartersCountry: string
targetMarkets: string[]
isDataController: boolean
isDataProcessor: boolean
}
/** Typische Scope-Antworten fuer diese Branche */
scopeHints: Record<string, string>
/** Typische Dokumente die diese Branche braucht */
recommendedDocs: string[]
}
export const COMPANY_PROFILE_PRESETS: CompanyProfilePreset[] = [
{
id: 'saas_startup',
label: 'SaaS Startup',
description: 'B2B Software-Startup, 1-5 Mitarbeiter, Cloud-basiert, remote-first',
icon: '🚀',
profile: {
legalForm: 'GmbH',
industry: ['tech'],
businessModel: 'b2b',
companySize: 'micro',
employeeCount: '1-9',
headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'],
isDataController: true,
isDataProcessor: true,
},
scopeHints: {
org_employee_count: '1-9',
org_industry: 'tech',
org_business_model: 'b2b',
proc_ai_usage: 'yes',
tech_hosting_location: 'eu',
tech_encryption_transit: 'yes',
tech_encryption_rest: 'yes',
comp_documentation_level: 'basic',
},
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'cookie_policy', 'dpa'],
},
{
id: 'consumer_app',
label: 'App Startup (Consumer)',
description: 'B2C Mobile App, 1-5 Mitarbeiter, App Store, Nutzerdaten',
icon: '📱',
profile: {
legalForm: 'GmbH',
industry: ['tech'],
businessModel: 'b2c',
companySize: 'micro',
employeeCount: '1-9',
headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'],
isDataController: true,
isDataProcessor: false,
},
scopeHints: {
org_employee_count: '1-9',
org_industry: 'tech',
org_business_model: 'b2c',
data_volume: '1000-10000',
proc_tracking: 'yes',
prod_consent_management: 'yes',
tech_hosting_location: 'eu',
},
recommendedDocs: ['privacy_policy', 'impressum', 'terms_of_use', 'cookie_policy', 'community_guidelines'],
},
{
id: 'ecommerce',
label: 'E-Commerce / Online-Shop',
description: 'Online-Handel B2C, 5-20 Mitarbeiter, Webshop, Zahlungsabwicklung',
icon: '🛒',
profile: {
legalForm: 'GmbH',
industry: ['retail'],
businessModel: 'b2c',
companySize: 'small',
employeeCount: '10-49',
headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'],
isDataController: true,
isDataProcessor: false,
},
scopeHints: {
org_employee_count: '10-49',
org_industry: 'retail',
org_business_model: 'b2c',
prod_webshop: 'yes',
data_volume: '10000-100000',
tech_hosting_location: 'eu',
prod_consent_management: 'yes',
},
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'widerruf', 'cookie_policy', 'cookie_banner'],
},
{
id: 'it_agency',
label: 'IT-Dienstleister / Agentur',
description: 'IT-Beratung oder Agentur, 10-50 Mitarbeiter, Kundenprojekte',
icon: '💻',
profile: {
legalForm: 'GmbH',
industry: ['tech'],
businessModel: 'b2b',
companySize: 'small',
employeeCount: '10-49',
headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'],
isDataController: true,
isDataProcessor: true,
},
scopeHints: {
org_employee_count: '10-49',
org_industry: 'tech',
org_business_model: 'b2b',
proc_ai_usage: 'yes',
tech_hosting_location: 'eu',
comp_vendor_management: 'yes',
comp_training: 'yes',
},
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'dpa', 'nda', 'employee_dsi'],
},
{
id: 'maschinenbau',
label: 'Maschinenbau KMU',
description: 'Maschinenbau B2B, 50-200 Mitarbeiter, Produktion, CE-Kennzeichnung',
icon: '🏭',
profile: {
legalForm: 'GmbH',
industry: ['manufacturing'],
businessModel: 'b2b',
companySize: 'medium',
employeeCount: '50-249',
headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU'],
isDataController: true,
isDataProcessor: false,
},
scopeHints: {
org_employee_count: '50-249',
org_industry: 'manufacturing',
org_business_model: 'b2b',
proc_employee_monitoring: 'no',
tech_hosting_location: 'eu',
comp_vendor_management: 'yes',
comp_documentation_level: 'structured',
},
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'dpa', 'employee_dsi', 'applicant_dsi', 'whistleblower_policy', 'tom_documentation'],
},
{
id: 'law_firm',
label: 'Rechtsanwaltskanzlei',
description: 'Kanzlei, 5-20 Mitarbeiter, Mandantendaten, besondere Vertraulichkeit',
icon: '⚖️',
profile: {
legalForm: 'PartG',
industry: ['legal'],
businessModel: 'b2b',
companySize: 'small',
employeeCount: '1-9',
headquartersCountry: 'DE',
targetMarkets: ['DE'],
isDataController: true,
isDataProcessor: false,
},
scopeHints: {
org_employee_count: '1-9',
org_industry: 'legal',
org_business_model: 'b2b',
data_art9: 'no',
tech_encryption_transit: 'yes',
tech_encryption_rest: 'yes',
comp_documentation_level: 'basic',
},
recommendedDocs: ['privacy_policy', 'impressum', 'dpa', 'employee_dsi', 'applicant_dsi'],
},
{
id: 'healthcare',
label: 'Arztpraxis / Gesundheit',
description: 'Gesundheitswesen, 5-50 Mitarbeiter, Patientendaten (Art. 9), hoher Schutzbedarf',
icon: '🏥',
profile: {
legalForm: 'GbR',
industry: ['healthcare'],
businessModel: 'b2c',
companySize: 'small',
employeeCount: '1-9',
headquartersCountry: 'DE',
targetMarkets: ['DE'],
isDataController: true,
isDataProcessor: false,
},
scopeHints: {
org_employee_count: '1-9',
org_industry: 'healthcare',
org_business_model: 'b2c',
data_art9: 'yes',
tech_encryption_transit: 'yes',
tech_encryption_rest: 'yes',
comp_documentation_level: 'basic',
},
recommendedDocs: ['privacy_policy', 'impressum', 'dpa', 'employee_dsi', 'tom_documentation', 'vvt_register', 'dsfa'],
},
{
id: 'handwerk',
label: 'Handwerksbetrieb',
description: 'Handwerk, 5-20 Mitarbeiter, Kundendaten, einfache IT',
icon: '🔧',
profile: {
legalForm: 'GmbH',
industry: ['crafts'],
businessModel: 'b2c',
companySize: 'small',
employeeCount: '1-9',
headquartersCountry: 'DE',
targetMarkets: ['DE'],
isDataController: true,
isDataProcessor: false,
},
scopeHints: {
org_employee_count: '1-9',
org_industry: 'other',
org_business_model: 'b2c',
data_art9: 'no',
tech_hosting_location: 'eu',
comp_documentation_level: 'none',
},
recommendedDocs: ['privacy_policy', 'impressum', 'agb'],
},
{
id: 'education',
label: 'Bildungseinrichtung',
description: 'Schule, Hochschule oder Weiterbildung, 20-100 Mitarbeiter, Schuelerdaten',
icon: '🎓',
profile: {
legalForm: 'gGmbH',
industry: ['education'],
businessModel: 'b2c',
companySize: 'medium',
employeeCount: '10-49',
headquartersCountry: 'DE',
targetMarkets: ['DE'],
isDataController: true,
isDataProcessor: false,
},
scopeHints: {
org_employee_count: '10-49',
org_industry: 'education',
org_business_model: 'b2c',
data_minors: 'yes',
tech_hosting_location: 'eu',
comp_training: 'yes',
},
recommendedDocs: ['privacy_policy', 'impressum', 'dpa', 'employee_dsi', 'dsfa', 'tom_documentation'],
},
{
id: 'enterprise',
label: 'Konzern / Enterprise',
description: 'Grossunternehmen, 500+ Mitarbeiter, international, reguliert, ISO 27001',
icon: '🏢',
profile: {
legalForm: 'AG',
industry: ['finance'],
businessModel: 'b2b',
companySize: 'enterprise',
employeeCount: '1000+',
headquartersCountry: 'DE',
targetMarkets: ['DE', 'EU', 'US'],
isDataController: true,
isDataProcessor: true,
},
scopeHints: {
org_employee_count: '1000+',
org_industry: 'finance',
org_business_model: 'b2b',
org_cert_target: 'iso27001',
data_art9: 'yes',
data_volume: '>1000000',
proc_ai_usage: 'yes',
tech_third_country: 'yes',
tech_hosting_location: 'eu_us_adequacy',
comp_vendor_management: 'yes',
comp_training: 'yes',
comp_documentation_level: 'comprehensive',
},
recommendedDocs: ['privacy_policy', 'impressum', 'agb', 'dpa', 'nda', 'sla', 'employee_dsi', 'applicant_dsi', 'whistleblower_policy', 'tom_documentation', 'vvt_register', 'isms_manual', 'dsfa', 'transfer_impact_assessment'],
},
]