feat: Template recommendation engine — bridges scope to document generator
Fixes critical gap: 50+ templates were unreachable because the Compliance Scope Engine only outputs 23 document types, while the database has 70+. New: templateRecommendations.ts - 25 template rules that map scope answers to specific templates - Covers ALL previously orphaned templates (HR-DSI, whistleblower, AI policy, BYOD, security policies, community guidelines, etc.) - Each rule evaluates scope answers + compliance level to determine required/recommended/optional status - Key triggers: - employee_count > 0 → employee_dsi, applicant_dsi - employee_count >= 50 → whistleblower_policy (HinSchG Pflicht!) - ai_usage != none → ai_usage_policy - business_model = platform → community_guidelines, terms_of_use - cert_target = iso27001 → isms_manual - webshop = yes → widerruf Updated: scopeDefaults.ts - getRecommendedDocuments() expanded with all 60+ document types - L1→L4 graduated recommendation (required/recommended/optional) Updated: _constants.ts - Consolidated AI governance into internal_policies category Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* Template Recommendations — Maps scope answers to document templates
|
||||
*
|
||||
* Bridges the gap between the Compliance Scope Engine (23 ScopeDocumentTypes)
|
||||
* and the Document Generator (70+ database templates).
|
||||
*
|
||||
* The scope engine recommends high-level document categories (vvt, tom, dsfa...).
|
||||
* This module recommends SPECIFIC templates based on additional context from
|
||||
* the CompanyProfile and scope answers.
|
||||
*/
|
||||
|
||||
import type { ComplianceDepthLevel } from '../../lib/sdk/compliance-scope-types/core-levels'
|
||||
import type { ScopeProfilingAnswer } from '../../lib/sdk/compliance-scope-types/state'
|
||||
|
||||
// ============================================================================
|
||||
// Template recommendation rules
|
||||
// ============================================================================
|
||||
|
||||
interface TemplateRule {
|
||||
/** Database document_type */
|
||||
templateType: string
|
||||
/** Human-readable label */
|
||||
label: string
|
||||
/** When to recommend this template */
|
||||
condition: (answers: Map<string, string>, level: ComplianceDepthLevel, profile: Record<string, unknown>) => 'required' | 'recommended' | 'optional' | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Rules that map scope answers + profile to specific template recommendations.
|
||||
* These cover templates NOT directly output by the scope engine.
|
||||
*/
|
||||
const TEMPLATE_RULES: TemplateRule[] = [
|
||||
// ── HR-DSI ──────────────────────────────────────────────────────────────
|
||||
{
|
||||
templateType: 'employee_dsi',
|
||||
label: 'Mitarbeiter-Datenschutzinformation',
|
||||
condition: (answers, level) => {
|
||||
const empCount = answers.get('org_employee_count')
|
||||
if (empCount && empCount !== 'none' && empCount !== '0') return level >= 'L2' ? 'required' : 'recommended'
|
||||
return null
|
||||
},
|
||||
},
|
||||
{
|
||||
templateType: 'applicant_dsi',
|
||||
label: 'Bewerber-Datenschutzinformation',
|
||||
condition: (answers, level) => {
|
||||
const empCount = answers.get('org_employee_count')
|
||||
if (empCount && empCount !== 'none' && empCount !== '0') return level >= 'L2' ? 'recommended' : 'optional'
|
||||
return null
|
||||
},
|
||||
},
|
||||
|
||||
// ── Whistleblower ───────────────────────────────────────────────────────
|
||||
{
|
||||
templateType: 'whistleblower_policy',
|
||||
label: 'Hinweisgeberrichtlinie (HinSchG)',
|
||||
condition: (answers) => {
|
||||
const empCount = answers.get('org_employee_count')
|
||||
// HinSchG Pflicht ab 50 MA
|
||||
if (empCount === '50_249' || empCount === '250_999' || empCount === '1000_plus') return 'required'
|
||||
return null
|
||||
},
|
||||
},
|
||||
|
||||
// ── KI ──────────────────────────────────────────────────────────────────
|
||||
{
|
||||
templateType: 'ai_usage_policy',
|
||||
label: 'KI-Nutzungsrichtlinie',
|
||||
condition: (answers) => {
|
||||
const aiUsage = answers.get('proc_ai_usage')
|
||||
if (aiUsage && aiUsage !== 'none' && aiUsage !== 'no') return 'required'
|
||||
return null
|
||||
},
|
||||
},
|
||||
|
||||
// ── BYOD ────────────────────────────────────────────────────────────────
|
||||
{
|
||||
templateType: 'byod_policy',
|
||||
label: 'BYOD-Richtlinie',
|
||||
condition: (answers, level) => {
|
||||
// BYOD relevant fuer Unternehmen mit Mitarbeitern
|
||||
if (level >= 'L3') return 'recommended'
|
||||
return 'optional'
|
||||
},
|
||||
},
|
||||
|
||||
// ── Social Media ────────────────────────────────────────────────────────
|
||||
{
|
||||
templateType: 'social_media_dsi',
|
||||
label: 'Social-Media-Datenschutzinformation',
|
||||
condition: (_answers, level) => {
|
||||
// Fast jedes Unternehmen hat Social Media
|
||||
return level >= 'L2' ? 'recommended' : 'optional'
|
||||
},
|
||||
},
|
||||
|
||||
// ── Videokonferenzen ────────────────────────────────────────────────────
|
||||
{
|
||||
templateType: 'video_conference_dsi',
|
||||
label: 'Videokonferenz-Datenschutzinformation',
|
||||
condition: (_answers, level) => {
|
||||
if (level >= 'L3') return 'recommended'
|
||||
return 'optional'
|
||||
},
|
||||
},
|
||||
|
||||
// ── Security Policies (nur ab L3/L4) ───────────────────────────────────
|
||||
{
|
||||
templateType: 'information_security_policy',
|
||||
label: 'Informationssicherheitsrichtlinie',
|
||||
condition: (_answers, level) => {
|
||||
if (level >= 'L3') return 'required'
|
||||
if (level === 'L2') return 'recommended'
|
||||
return null
|
||||
},
|
||||
},
|
||||
{
|
||||
templateType: 'password_policy',
|
||||
label: 'Passwortrichtlinie',
|
||||
condition: (_answers, level) => level >= 'L2' ? 'recommended' : 'optional',
|
||||
},
|
||||
{
|
||||
templateType: 'encryption_policy',
|
||||
label: 'Verschluesselungsrichtlinie',
|
||||
condition: (_answers, level) => level >= 'L3' ? 'recommended' : 'optional',
|
||||
},
|
||||
{
|
||||
templateType: 'access_control_policy',
|
||||
label: 'Zugriffskontrollrichtlinie',
|
||||
condition: (_answers, level) => level >= 'L3' ? 'recommended' : 'optional',
|
||||
},
|
||||
|
||||
// ── Security Concepts (nur ab L3) ──────────────────────────────────────
|
||||
{
|
||||
templateType: 'it_security_concept',
|
||||
label: 'IT-Sicherheitskonzept',
|
||||
condition: (_answers, level) => level >= 'L3' ? 'required' : 'optional',
|
||||
},
|
||||
{
|
||||
templateType: 'backup_recovery_concept',
|
||||
label: 'Backup-Recovery-Konzept',
|
||||
condition: (_answers, level) => level >= 'L3' ? 'recommended' : 'optional',
|
||||
},
|
||||
{
|
||||
templateType: 'logging_concept',
|
||||
label: 'Logging-Konzept',
|
||||
condition: (_answers, level) => level >= 'L3' ? 'recommended' : 'optional',
|
||||
},
|
||||
{
|
||||
templateType: 'access_control_concept',
|
||||
label: 'Zugriffskonzept',
|
||||
condition: (_answers, level) => level >= 'L3' ? 'recommended' : 'optional',
|
||||
},
|
||||
|
||||
// ── Plattform/UGC ──────────────────────────────────────────────────────
|
||||
{
|
||||
templateType: 'community_guidelines',
|
||||
label: 'Gemeinschaftsrichtlinien',
|
||||
condition: (answers) => {
|
||||
const model = answers.get('org_business_model')
|
||||
if (model === 'platform' || model === 'marketplace' || model === 'social') return 'required'
|
||||
return null
|
||||
},
|
||||
},
|
||||
{
|
||||
templateType: 'terms_of_use',
|
||||
label: 'Nutzungsbedingungen',
|
||||
condition: (answers) => {
|
||||
const model = answers.get('org_business_model')
|
||||
if (model === 'platform' || model === 'marketplace' || model === 'social' || model === 'saas') return 'required'
|
||||
return null
|
||||
},
|
||||
},
|
||||
{
|
||||
templateType: 'media_content_policy',
|
||||
label: 'Medien- und Inhalte-Richtlinie',
|
||||
condition: (answers) => {
|
||||
const model = answers.get('org_business_model')
|
||||
if (model === 'platform' || model === 'media') return 'recommended'
|
||||
return null
|
||||
},
|
||||
},
|
||||
|
||||
// ── E-Commerce ─────────────────────────────────────────────────────────
|
||||
{
|
||||
templateType: 'widerruf',
|
||||
label: 'Widerrufsbelehrung',
|
||||
condition: (answers) => {
|
||||
const shop = answers.get('prod_webshop')
|
||||
if (shop && shop !== 'no') return 'required'
|
||||
return null
|
||||
},
|
||||
},
|
||||
{
|
||||
templateType: 'consent_texts',
|
||||
label: 'Einwilligungstexte (Double-Opt-In)',
|
||||
condition: (answers) => {
|
||||
const consent = answers.get('prod_consent_management')
|
||||
if (consent && consent !== 'no') return 'recommended'
|
||||
return 'optional'
|
||||
},
|
||||
},
|
||||
|
||||
// ── Impressum + Cookie ─────────────────────────────────────────────────
|
||||
{
|
||||
templateType: 'impressum',
|
||||
label: 'Impressum',
|
||||
condition: () => 'required', // Immer Pflicht
|
||||
},
|
||||
{
|
||||
templateType: 'cookie_policy',
|
||||
label: 'Cookie-Richtlinie',
|
||||
condition: () => 'required', // Immer Pflicht bei Websites
|
||||
},
|
||||
|
||||
// ── ISMS (nur bei Zertifizierungsziel) ─────────────────────────────────
|
||||
{
|
||||
templateType: 'isms_manual',
|
||||
label: 'ISMS-Handbuch',
|
||||
condition: (answers) => {
|
||||
const cert = answers.get('org_cert_target')
|
||||
if (cert === 'iso27001' || cert === 'iso27701' || cert === 'tisax') return 'required'
|
||||
return null
|
||||
},
|
||||
},
|
||||
|
||||
// ── Vendor/BCM (nur ab L4 oder bei Vendor-Management) ─────────────────
|
||||
{
|
||||
templateType: 'vendor_risk_management_policy',
|
||||
label: 'Vendor-Risikomanagement',
|
||||
condition: (answers, level) => {
|
||||
const vendor = answers.get('comp_vendor_management')
|
||||
if (vendor && vendor !== 'no') return 'recommended'
|
||||
if (level === 'L4') return 'required'
|
||||
return null
|
||||
},
|
||||
},
|
||||
{
|
||||
templateType: 'business_continuity_policy',
|
||||
label: 'Business-Continuity-Richtlinie',
|
||||
condition: (_answers, level) => level === 'L4' ? 'required' : 'optional',
|
||||
},
|
||||
]
|
||||
|
||||
// ============================================================================
|
||||
// Public API
|
||||
// ============================================================================
|
||||
|
||||
export interface TemplateRecommendation {
|
||||
templateType: string
|
||||
label: string
|
||||
requirement: 'required' | 'recommended' | 'optional'
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates all template rules against the user's scope answers and profile.
|
||||
* Returns a prioritized list of template recommendations.
|
||||
*/
|
||||
export function evaluateTemplateRecommendations(
|
||||
scopeAnswers: ScopeProfilingAnswer[],
|
||||
level: ComplianceDepthLevel,
|
||||
profile: Record<string, unknown> = {},
|
||||
): TemplateRecommendation[] {
|
||||
const answerMap = new Map<string, string>()
|
||||
for (const a of scopeAnswers) {
|
||||
answerMap.set(a.questionId, String(a.value))
|
||||
}
|
||||
|
||||
const results: TemplateRecommendation[] = []
|
||||
|
||||
for (const rule of TEMPLATE_RULES) {
|
||||
const requirement = rule.condition(answerMap, level, profile)
|
||||
if (requirement) {
|
||||
results.push({
|
||||
templateType: rule.templateType,
|
||||
label: rule.label,
|
||||
requirement,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort: required first, then recommended, then optional
|
||||
const order = { required: 0, recommended: 1, optional: 2 }
|
||||
results.sort((a, b) => order[a.requirement] - order[b.requirement])
|
||||
|
||||
return results
|
||||
}
|
||||
Reference in New Issue
Block a user