import type { ScopeQuestionBlockId, ScopeProfilingQuestion, ScopeProfilingAnswer, } from './compliance-scope-types' import type { CompanyProfile } from './types' import { HIDDEN_SCORING_QUESTIONS, } from './compliance-scope-profiling-blocks' import { SCOPE_QUESTION_BLOCKS, } from './compliance-scope-profiling-vvt-blocks' /** * Prefill scope answers from CompanyProfile. */ export function prefillFromCompanyProfile( profile: CompanyProfile ): ScopeProfilingAnswer[] { const answers: ScopeProfilingAnswer[] = [] // dpoName -> org_has_dsb (auto-filled, not shown in UI) if (profile.dpoName && profile.dpoName.trim() !== '') { answers.push({ questionId: 'org_has_dsb', value: true, }) } // offerings -> prod_type mapping (auto-filled, not shown in UI) if (profile.offerings && profile.offerings.length > 0) { const prodTypes: string[] = [] const offeringsLower = profile.offerings.map((o) => o.toLowerCase()) if (offeringsLower.some((o) => o.includes('webapp') || o.includes('web'))) { prodTypes.push('webapp') } if ( offeringsLower.some((o) => o.includes('mobile') || o.includes('app')) ) { prodTypes.push('mobile') } if (offeringsLower.some((o) => o.includes('saas') || o.includes('cloud'))) { prodTypes.push('saas') } if ( offeringsLower.some( (o) => o.includes('onpremise') || o.includes('on-premise') ) ) { prodTypes.push('onpremise') } if (offeringsLower.some((o) => o.includes('api'))) { prodTypes.push('api') } if (offeringsLower.some((o) => o.includes('iot') || o.includes('hardware'))) { prodTypes.push('iot') } if ( offeringsLower.some( (o) => o.includes('beratung') || o.includes('consulting') ) ) { prodTypes.push('beratung') } if ( offeringsLower.some( (o) => o.includes('handel') || o.includes('shop') || o.includes('commerce') ) ) { prodTypes.push('handel') } if (prodTypes.length > 0) { answers.push({ questionId: 'prod_type', value: prodTypes, }) } // webshop auto-fill if (offeringsLower.some((o) => o.includes('webshop') || o.includes('shop'))) { answers.push({ questionId: 'prod_webshop', value: true, }) } } return answers } /** * Get auto-filled scoring values for questions removed from UI. */ export function getAutoFilledScoringAnswers( profile: CompanyProfile ): ScopeProfilingAnswer[] { const answers: ScopeProfilingAnswer[] = [] if (profile.employeeCount != null) { answers.push({ questionId: 'org_employee_count', value: profile.employeeCount }) } if (profile.annualRevenue) { answers.push({ questionId: 'org_annual_revenue', value: profile.annualRevenue }) } if (profile.industry && profile.industry.length > 0) { answers.push({ questionId: 'org_industry', value: profile.industry.join(', ') }) } if (profile.businessModel) { answers.push({ questionId: 'org_business_model', value: profile.businessModel }) } if (profile.dpoName && profile.dpoName.trim() !== '') { answers.push({ questionId: 'org_has_dsb', value: true }) } return answers } /** * Get profile info summary for display in "Aus Profil" info boxes. */ export function getProfileInfoForBlock( profile: CompanyProfile, blockId: ScopeQuestionBlockId ): { label: string; value: string }[] { const items: { label: string; value: string }[] = [] if (blockId === 'organisation') { if (profile.industry && profile.industry.length > 0) items.push({ label: 'Branche', value: profile.industry.join(', ') }) if (profile.employeeCount) items.push({ label: 'Mitarbeiter', value: profile.employeeCount }) if (profile.annualRevenue) items.push({ label: 'Umsatz', value: profile.annualRevenue }) if (profile.businessModel) items.push({ label: 'Geschäftsmodell', value: profile.businessModel }) if (profile.dpoName) items.push({ label: 'DSB', value: profile.dpoName }) } if (blockId === 'product') { if (profile.offerings && profile.offerings.length > 0) { items.push({ label: 'Angebote', value: profile.offerings.join(', ') }) } const hasWebshop = profile.offerings?.some(o => o.toLowerCase().includes('webshop') || o.toLowerCase().includes('shop')) if (hasWebshop) items.push({ label: 'Webshop', value: 'Ja' }) } return items } /** * Prefill scope answers from VVT profiling answers */ export function prefillFromVVTAnswers( vvtAnswers: Record ): ScopeProfilingAnswer[] { const answers: ScopeProfilingAnswer[] = [] const reverseMap: Record = {} for (const block of SCOPE_QUESTION_BLOCKS) { for (const q of block.questions) { if (q.mapsToVVTQuestion) { reverseMap[q.mapsToVVTQuestion] = q.id } } } for (const [vvtQuestionId, vvtValue] of Object.entries(vvtAnswers)) { const scopeQuestionId = reverseMap[vvtQuestionId] if (scopeQuestionId) { answers.push({ questionId: scopeQuestionId, value: vvtValue }) } } return answers } /** * Prefill scope answers from Loeschfristen profiling answers */ export function prefillFromLoeschfristenAnswers( lfAnswers: Array<{ questionId: string; value: unknown }> ): ScopeProfilingAnswer[] { const answers: ScopeProfilingAnswer[] = [] const reverseMap: Record = {} for (const block of SCOPE_QUESTION_BLOCKS) { for (const q of block.questions) { if (q.mapsToLFQuestion) { reverseMap[q.mapsToLFQuestion] = q.id } } } for (const lfAnswer of lfAnswers) { const scopeQuestionId = reverseMap[lfAnswer.questionId] if (scopeQuestionId) { answers.push({ questionId: scopeQuestionId, value: lfAnswer.value }) } } return answers } /** * Export scope answers in VVT format */ export function exportToVVTAnswers( scopeAnswers: ScopeProfilingAnswer[] ): Record { const vvtAnswers: Record = {} for (const answer of scopeAnswers) { let question: ScopeProfilingQuestion | undefined for (const block of SCOPE_QUESTION_BLOCKS) { question = block.questions.find((q) => q.id === answer.questionId) if (question) break } if (question?.mapsToVVTQuestion) { vvtAnswers[question.mapsToVVTQuestion] = answer.value } } return vvtAnswers } /** * Export scope answers in Loeschfristen format */ export function exportToLoeschfristenAnswers( scopeAnswers: ScopeProfilingAnswer[] ): Array<{ questionId: string; value: unknown }> { const lfAnswers: Array<{ questionId: string; value: unknown }> = [] for (const answer of scopeAnswers) { let question: ScopeProfilingQuestion | undefined for (const block of SCOPE_QUESTION_BLOCKS) { question = block.questions.find((q) => q.id === answer.questionId) if (question) break } if (question?.mapsToLFQuestion) { lfAnswers.push({ questionId: question.mapsToLFQuestion, value: answer.value }) } } return lfAnswers } /** * Export scope answers for TOM generator */ export function exportToTOMProfile( scopeAnswers: ScopeProfilingAnswer[] ): Record { const tomProfile: Record = {} const getVal = (qId: string) => getAnswerValue(scopeAnswers, qId) tomProfile.industry = getVal('org_industry') tomProfile.employeeCount = getVal('org_employee_count') tomProfile.hasDataMinors = getVal('data_minors') tomProfile.hasSpecialCategories = Array.isArray(getVal('data_art9')) ? (getVal('data_art9') as string[]).length > 0 : false tomProfile.hasAutomatedDecisions = getVal('proc_adm_scoring') tomProfile.usesAI = Array.isArray(getVal('proc_ai_usage')) ? !(getVal('proc_ai_usage') as string[]).includes('keine') : false tomProfile.hasThirdCountryTransfer = getVal('tech_third_country') tomProfile.hasEncryptionRest = getVal('tech_encryption_rest') tomProfile.hasEncryptionTransit = getVal('tech_encryption_transit') tomProfile.hasIncidentResponse = getVal('proc_incident_response') tomProfile.hasDeletionConcept = getVal('proc_deletion_concept') tomProfile.hasRegularAudits = getVal('proc_regular_audits') tomProfile.hasTraining = getVal('proc_training') return tomProfile } /** * Check if a block is complete (all required questions answered) */ export function isBlockComplete( answers: ScopeProfilingAnswer[], blockId: ScopeQuestionBlockId ): boolean { const block = SCOPE_QUESTION_BLOCKS.find((b) => b.id === blockId) if (!block) return false const requiredQuestions = block.questions.filter((q) => q.required) const answeredQuestionIds = new Set(answers.map((a) => a.questionId)) return requiredQuestions.every((q) => answeredQuestionIds.has(q.id)) } /** * Get progress for a specific block (0-100) */ export function getBlockProgress( answers: ScopeProfilingAnswer[], blockId: ScopeQuestionBlockId ): number { const block = SCOPE_QUESTION_BLOCKS.find((b) => b.id === blockId) if (!block) return 0 const requiredQuestions = block.questions.filter((q) => q.required) if (requiredQuestions.length === 0) return 100 const answeredQuestionIds = new Set(answers.map((a) => a.questionId)) const answeredCount = requiredQuestions.filter((q) => answeredQuestionIds.has(q.id) ).length return Math.round((answeredCount / requiredQuestions.length) * 100) } /** * Get total progress across all blocks (0-100) */ export function getTotalProgress(answers: ScopeProfilingAnswer[]): number { let totalRequired = 0 let totalAnswered = 0 const answeredQuestionIds = new Set(answers.map((a) => a.questionId)) for (const block of SCOPE_QUESTION_BLOCKS) { const requiredQuestions = block.questions.filter((q) => q.required) totalRequired += requiredQuestions.length totalAnswered += requiredQuestions.filter((q) => answeredQuestionIds.has(q.id) ).length } if (totalRequired === 0) return 100 return Math.round((totalAnswered / totalRequired) * 100) } /** * Get answer value for a specific question */ export function getAnswerValue( answers: ScopeProfilingAnswer[], questionId: string ): unknown { const answer = answers.find((a) => a.questionId === questionId) return answer?.value } /** * Get all questions as a flat array (including hidden auto-filled questions) */ export function getAllQuestions(): ScopeProfilingQuestion[] { return [ ...SCOPE_QUESTION_BLOCKS.flatMap((block) => block.questions), ...HIDDEN_SCORING_QUESTIONS, ] } /** * Get unanswered required questions, optionally filtered by block. */ export function getUnansweredRequiredQuestions( answers: ScopeProfilingAnswer[], blockId?: ScopeQuestionBlockId ): { blockId: ScopeQuestionBlockId; blockTitle: string; question: ScopeProfilingQuestion }[] { const answeredIds = new Set(answers.map((a) => a.questionId)) const blocks = blockId ? SCOPE_QUESTION_BLOCKS.filter((b) => b.id === blockId) : SCOPE_QUESTION_BLOCKS const result: { blockId: ScopeQuestionBlockId; blockTitle: string; question: ScopeProfilingQuestion }[] = [] for (const block of blocks) { for (const q of block.questions) { if (q.required && !answeredIds.has(q.id)) { result.push({ blockId: block.id, blockTitle: block.title, question: q }) } } } return result }