Split these files that exceeded the 500-line hard cap: - privacy-policy.ts (965 LOC) -> sections + renderers - academy/api.ts (787 LOC) -> courses + mock-data - whistleblower/api.ts (755 LOC) -> operations + mock-data - vvt-profiling.ts (659 LOC) -> data + logic - cookie-banner.ts (595 LOC) -> config + embed - dsr/types.ts (581 LOC) -> core + api types - tom-generator/rules-engine.ts (560 LOC) -> evaluator + gap-analysis - datapoint-helpers.ts (548 LOC) -> generators + validators Each original file becomes a barrel re-export for backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
172 lines
4.9 KiB
TypeScript
172 lines
4.9 KiB
TypeScript
/**
|
|
* TOM Rules Engine — Gap Analysis & Helper Functions
|
|
*
|
|
* Singleton instance, convenience functions, and gap analysis logic.
|
|
*/
|
|
|
|
import {
|
|
DerivedTOM,
|
|
EvidenceDocument,
|
|
GapAnalysisResult,
|
|
MissingControl,
|
|
PartialControl,
|
|
MissingEvidence,
|
|
RulesEngineResult,
|
|
RulesEngineEvaluationContext,
|
|
} from './types'
|
|
import { getControlById } from './controls/loader'
|
|
import { TOMRulesEngine } from './rules-evaluator'
|
|
|
|
// =============================================================================
|
|
// SINGLETON INSTANCE
|
|
// =============================================================================
|
|
|
|
let rulesEngineInstance: TOMRulesEngine | null = null
|
|
|
|
export function getTOMRulesEngine(): TOMRulesEngine {
|
|
if (!rulesEngineInstance) {
|
|
rulesEngineInstance = new TOMRulesEngine()
|
|
}
|
|
return rulesEngineInstance
|
|
}
|
|
|
|
// =============================================================================
|
|
// GAP ANALYSIS
|
|
// =============================================================================
|
|
|
|
export function performGapAnalysis(
|
|
derivedTOMs: DerivedTOM[],
|
|
documents: EvidenceDocument[]
|
|
): GapAnalysisResult {
|
|
const missingControls: MissingControl[] = []
|
|
const partialControls: PartialControl[] = []
|
|
const missingEvidence: MissingEvidence[] = []
|
|
const recommendations: string[] = []
|
|
|
|
let totalScore = 0
|
|
let totalWeight = 0
|
|
|
|
const applicableTOMs = derivedTOMs.filter(
|
|
(tom) => tom.applicability === 'REQUIRED' || tom.applicability === 'RECOMMENDED'
|
|
)
|
|
|
|
for (const tom of applicableTOMs) {
|
|
const control = getControlById(tom.controlId)
|
|
if (!control) continue
|
|
|
|
const weight = tom.applicability === 'REQUIRED' ? 3 : 1
|
|
totalWeight += weight
|
|
|
|
if (tom.implementationStatus === 'NOT_IMPLEMENTED') {
|
|
missingControls.push({
|
|
controlId: tom.controlId,
|
|
reason: `${control.name.de} ist nicht implementiert`,
|
|
priority: control.priority,
|
|
})
|
|
} else if (tom.implementationStatus === 'PARTIAL') {
|
|
partialControls.push({
|
|
controlId: tom.controlId,
|
|
missingAspects: tom.evidenceGaps,
|
|
})
|
|
totalScore += weight * 0.5
|
|
} else {
|
|
totalScore += weight
|
|
}
|
|
|
|
const linkedEvidenceIds = tom.linkedEvidence
|
|
const requiredEvidence = control.evidenceRequirements
|
|
const providedEvidence = documents.filter((doc) =>
|
|
linkedEvidenceIds.includes(doc.id)
|
|
)
|
|
|
|
if (providedEvidence.length < requiredEvidence.length) {
|
|
const missing = requiredEvidence.filter(
|
|
(req) =>
|
|
!providedEvidence.some(
|
|
(doc) =>
|
|
doc.documentType === 'POLICY' ||
|
|
doc.documentType === 'CERTIFICATE' ||
|
|
doc.originalName.toLowerCase().includes(req.toLowerCase())
|
|
)
|
|
)
|
|
|
|
if (missing.length > 0) {
|
|
missingEvidence.push({
|
|
controlId: tom.controlId,
|
|
requiredEvidence: missing,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
const overallScore =
|
|
totalWeight > 0 ? Math.round((totalScore / totalWeight) * 100) : 0
|
|
|
|
if (missingControls.length > 0) {
|
|
const criticalMissing = missingControls.filter((mc) => mc.priority === 'CRITICAL')
|
|
if (criticalMissing.length > 0) {
|
|
recommendations.push(
|
|
`${criticalMissing.length} kritische Kontrollen sind nicht implementiert. Diese sollten priorisiert werden.`
|
|
)
|
|
}
|
|
}
|
|
|
|
if (partialControls.length > 0) {
|
|
recommendations.push(
|
|
`${partialControls.length} Kontrollen sind nur teilweise implementiert. Vervollstaendigen Sie die Implementierung.`
|
|
)
|
|
}
|
|
|
|
if (missingEvidence.length > 0) {
|
|
recommendations.push(
|
|
`Fuer ${missingEvidence.length} Kontrollen fehlen Nachweisdokumente. Laden Sie die entsprechenden Dokumente hoch.`
|
|
)
|
|
}
|
|
|
|
if (overallScore >= 80) {
|
|
recommendations.push(
|
|
'Ihr TOM-Compliance-Score ist gut. Fuehren Sie regelmaessige Ueberpruefungen durch.'
|
|
)
|
|
} else if (overallScore >= 50) {
|
|
recommendations.push(
|
|
'Ihr TOM-Compliance-Score erfordert Verbesserungen. Fokussieren Sie sich auf die kritischen Luecken.'
|
|
)
|
|
} else {
|
|
recommendations.push(
|
|
'Ihr TOM-Compliance-Score ist niedrig. Eine systematische Ueberarbeitung der Massnahmen wird empfohlen.'
|
|
)
|
|
}
|
|
|
|
return {
|
|
overallScore,
|
|
missingControls,
|
|
partialControls,
|
|
missingEvidence,
|
|
recommendations,
|
|
generatedAt: new Date(),
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// HELPER FUNCTIONS
|
|
// =============================================================================
|
|
|
|
export function evaluateControlsForContext(
|
|
context: RulesEngineEvaluationContext
|
|
): RulesEngineResult[] {
|
|
return getTOMRulesEngine().evaluateControls(context)
|
|
}
|
|
|
|
export function deriveTOMsForContext(
|
|
context: RulesEngineEvaluationContext
|
|
): DerivedTOM[] {
|
|
return getTOMRulesEngine().deriveAllTOMs(context)
|
|
}
|
|
|
|
export function performQuickGapAnalysis(
|
|
derivedTOMs: DerivedTOM[],
|
|
documents: EvidenceDocument[]
|
|
): GapAnalysisResult {
|
|
return performGapAnalysis(derivedTOMs, documents)
|
|
}
|