fix(scope-engine): Normalize UPPERCASE trigger docs to lowercase ScopeDocumentType
All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 56s
CI/CD / test-python-backend-compliance (push) Successful in 42s
CI/CD / test-python-document-crawler (push) Successful in 24s
CI/CD / test-python-dsms-gateway (push) Successful in 26s
CI/CD / deploy-hetzner (push) Successful in 2m57s
All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 56s
CI/CD / test-python-backend-compliance (push) Successful in 42s
CI/CD / test-python-document-crawler (push) Successful in 24s
CI/CD / test-python-dsms-gateway (push) Successful in 26s
CI/CD / deploy-hetzner (push) Successful in 2m57s
Critical bug fix: mandatoryDocuments in Hard-Trigger-Rules used UPPERCASE names (VVT, TOM, DSE) that never matched lowercase ScopeDocumentType keys (vvt, tom, dsi). This meant no trigger documents were ever recognized as mandatory in buildDocumentScope(). - Add normalizeDocType() mapping function with alias support (DSE→dsi, LOESCHKONZEPT→lf, DSR_PROZESS→betroffenenrechte, etc.) - Fix buildDocumentScope() to use normalized doc types - Fix estimateEffort() to use lowercase keys matching ScopeDocumentType - Add 2 tests for UPPERCASE normalization and alias resolution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -265,6 +265,42 @@ describe('buildDocumentScope', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('normalizes UPPERCASE trigger doc names to lowercase ScopeDocumentType', () => {
|
||||||
|
const t = trigger('HT-test', 'L2', {
|
||||||
|
category: 'test',
|
||||||
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
||||||
|
})
|
||||||
|
const docs = complianceScopeEngine.buildDocumentScope('L2', [t], [])
|
||||||
|
const vvt = docs.find((d: any) => d.documentType === 'vvt')
|
||||||
|
const tom = docs.find((d: any) => d.documentType === 'tom')
|
||||||
|
const dsfa = docs.find((d: any) => d.documentType === 'dsfa')
|
||||||
|
expect(vvt).toBeDefined()
|
||||||
|
expect(vvt!.requirement).toBe('mandatory')
|
||||||
|
expect(vvt!.triggeredBy).toContain('HT-test')
|
||||||
|
expect(tom).toBeDefined()
|
||||||
|
expect(tom!.requirement).toBe('mandatory')
|
||||||
|
expect(dsfa).toBeDefined()
|
||||||
|
expect(dsfa!.requirement).toBe('mandatory')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('normalizes aliased doc names (DSE→dsi, LOESCHKONZEPT→lf)', () => {
|
||||||
|
const t = trigger('HT-alias', 'L2', {
|
||||||
|
category: 'test',
|
||||||
|
mandatoryDocuments: ['DSE', 'LOESCHKONZEPT', 'DSR_PROZESS'],
|
||||||
|
})
|
||||||
|
const docs = complianceScopeEngine.buildDocumentScope('L2', [t], [])
|
||||||
|
const dsi = docs.find((d: any) => d.documentType === 'dsi')
|
||||||
|
const lf = docs.find((d: any) => d.documentType === 'lf')
|
||||||
|
const betroffenenrechte = docs.find((d: any) => d.documentType === 'betroffenenrechte')
|
||||||
|
expect(dsi).toBeDefined()
|
||||||
|
expect(dsi!.requirement).toBe('mandatory')
|
||||||
|
expect(dsi!.triggeredBy).toContain('HT-alias')
|
||||||
|
expect(lf).toBeDefined()
|
||||||
|
expect(lf!.requirement).toBe('mandatory')
|
||||||
|
expect(betroffenenrechte).toBeDefined()
|
||||||
|
expect(betroffenenrechte!.requirement).toBe('mandatory')
|
||||||
|
})
|
||||||
|
|
||||||
it('documents sorted: mandatory first', () => {
|
it('documents sorted: mandatory first', () => {
|
||||||
const decision = complianceScopeEngine.evaluate([
|
const decision = complianceScopeEngine.evaluate([
|
||||||
ans('data_art9', ['gesundheit']),
|
ans('data_art9', ['gesundheit']),
|
||||||
|
|||||||
@@ -1328,6 +1328,38 @@ export class ComplianceScopeEngine {
|
|||||||
return maxDepthLevel(levelFromScore, maxTriggerLevel)
|
return maxDepthLevel(levelFromScore, maxTriggerLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalisiert UPPERCASE Dokumenttyp-Bezeichner aus den Hard-Trigger-Rules
|
||||||
|
* auf die lowercase ScopeDocumentType-Schlüssel.
|
||||||
|
*/
|
||||||
|
private normalizeDocType(raw: string): ScopeDocumentType | null {
|
||||||
|
const mapping: Record<string, ScopeDocumentType> = {
|
||||||
|
VVT: 'vvt',
|
||||||
|
TOM: 'tom',
|
||||||
|
DSFA: 'dsfa',
|
||||||
|
DSE: 'dsi',
|
||||||
|
AGB: 'vertragsmanagement',
|
||||||
|
AVV: 'av_vertrag',
|
||||||
|
COOKIE_BANNER: 'einwilligung',
|
||||||
|
EINWILLIGUNGEN: 'einwilligung',
|
||||||
|
TRANSFER_DOKU: 'daten_transfer',
|
||||||
|
AUDIT_CHECKLIST: 'audit_log',
|
||||||
|
VENDOR_MANAGEMENT: 'vertragsmanagement',
|
||||||
|
LOESCHKONZEPT: 'lf',
|
||||||
|
DSR_PROZESS: 'betroffenenrechte',
|
||||||
|
NOTFALLPLAN: 'notfallplan',
|
||||||
|
AI_ACT_DOKU: 'ai_act_doku',
|
||||||
|
WIDERRUFSBELEHRUNG: 'widerrufsbelehrung',
|
||||||
|
PREISANGABEN: 'preisangaben',
|
||||||
|
FERNABSATZ_INFO: 'fernabsatz_info',
|
||||||
|
STREITBEILEGUNG: 'streitbeilegung',
|
||||||
|
PRODUKTSICHERHEIT: 'produktsicherheit',
|
||||||
|
}
|
||||||
|
// Falls raw bereits ein gueltiger ScopeDocumentType ist
|
||||||
|
if (raw in DOCUMENT_SCOPE_MATRIX) return raw as ScopeDocumentType
|
||||||
|
return mapping[raw] ?? null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Baut den Dokumenten-Scope basierend auf Level und Triggers
|
* Baut den Dokumenten-Scope basierend auf Level und Triggers
|
||||||
*/
|
*/
|
||||||
@@ -1338,11 +1370,18 @@ export class ComplianceScopeEngine {
|
|||||||
): RequiredDocument[] {
|
): RequiredDocument[] {
|
||||||
const requiredDocs: RequiredDocument[] = []
|
const requiredDocs: RequiredDocument[] = []
|
||||||
const mandatoryFromTriggers = new Set<ScopeDocumentType>()
|
const mandatoryFromTriggers = new Set<ScopeDocumentType>()
|
||||||
|
// Mapping: normalisierter DocType → original Rule-Strings (fuer triggeredBy Lookup)
|
||||||
|
const triggerDocOrigins = new Map<ScopeDocumentType, string[]>()
|
||||||
|
|
||||||
// Sammle mandatory docs aus Triggern
|
// Sammle mandatory docs aus Triggern (normalisiert)
|
||||||
for (const trigger of triggers) {
|
for (const trigger of triggers) {
|
||||||
for (const doc of trigger.mandatoryDocuments) {
|
for (const doc of trigger.mandatoryDocuments) {
|
||||||
mandatoryFromTriggers.add(doc as ScopeDocumentType)
|
const normalized = this.normalizeDocType(doc)
|
||||||
|
if (normalized) {
|
||||||
|
mandatoryFromTriggers.add(normalized)
|
||||||
|
if (!triggerDocOrigins.has(normalized)) triggerDocOrigins.set(normalized, [])
|
||||||
|
triggerDocOrigins.get(normalized)!.push(doc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1352,6 +1391,7 @@ export class ComplianceScopeEngine {
|
|||||||
const isMandatoryFromTrigger = mandatoryFromTriggers.has(docType)
|
const isMandatoryFromTrigger = mandatoryFromTriggers.has(docType)
|
||||||
|
|
||||||
if (requirement === 'mandatory' || isMandatoryFromTrigger) {
|
if (requirement === 'mandatory' || isMandatoryFromTrigger) {
|
||||||
|
const originDocs = triggerDocOrigins.get(docType) ?? []
|
||||||
requiredDocs.push({
|
requiredDocs.push({
|
||||||
documentType: docType,
|
documentType: docType,
|
||||||
label: DOCUMENT_TYPE_LABELS[docType],
|
label: DOCUMENT_TYPE_LABELS[docType],
|
||||||
@@ -1361,7 +1401,7 @@ export class ComplianceScopeEngine {
|
|||||||
sdkStepUrl: DOCUMENT_SDK_STEP_MAP[docType],
|
sdkStepUrl: DOCUMENT_SDK_STEP_MAP[docType],
|
||||||
triggeredBy: isMandatoryFromTrigger
|
triggeredBy: isMandatoryFromTrigger
|
||||||
? triggers
|
? triggers
|
||||||
.filter((t) => t.mandatoryDocuments.includes(docType as any))
|
.filter((t) => t.mandatoryDocuments.some((d) => originDocs.includes(d)))
|
||||||
.map((t) => t.ruleId)
|
.map((t) => t.ruleId)
|
||||||
: [],
|
: [],
|
||||||
})
|
})
|
||||||
@@ -1410,29 +1450,33 @@ export class ComplianceScopeEngine {
|
|||||||
* Schätzt den Aufwand für ein Dokument (in Stunden)
|
* Schätzt den Aufwand für ein Dokument (in Stunden)
|
||||||
*/
|
*/
|
||||||
private estimateEffort(docType: ScopeDocumentType): number {
|
private estimateEffort(docType: ScopeDocumentType): number {
|
||||||
const effortMap: Record<ScopeDocumentType, number> = {
|
const effortMap: Partial<Record<ScopeDocumentType, number>> = {
|
||||||
VVT: 8,
|
vvt: 8,
|
||||||
TOM: 12,
|
tom: 12,
|
||||||
DSFA: 16,
|
dsfa: 16,
|
||||||
AVV: 4,
|
av_vertrag: 4,
|
||||||
DSE: 6,
|
dsi: 6,
|
||||||
EINWILLIGUNGEN: 6,
|
einwilligung: 6,
|
||||||
LOESCHKONZEPT: 10,
|
lf: 10,
|
||||||
TRANSFER_DOKU: 8,
|
daten_transfer: 8,
|
||||||
DSR_PROZESS: 8,
|
betroffenenrechte: 8,
|
||||||
NOTFALLPLAN: 12,
|
notfallplan: 12,
|
||||||
COOKIE_BANNER: 4,
|
vertragsmanagement: 10,
|
||||||
AGB: 6,
|
audit_log: 8,
|
||||||
WIDERRUFSBELEHRUNG: 3,
|
risikoanalyse: 6,
|
||||||
PREISANGABEN: 2,
|
schulung: 4,
|
||||||
FERNABSATZ_INFO: 4,
|
datenpannen: 6,
|
||||||
STREITBEILEGUNG: 1,
|
zertifizierung: 8,
|
||||||
PRODUKTSICHERHEIT: 8,
|
datenschutzmanagement: 12,
|
||||||
AI_ACT_DOKU: 12,
|
iace_ce_assessment: 8,
|
||||||
AUDIT_CHECKLIST: 8,
|
widerrufsbelehrung: 3,
|
||||||
VENDOR_MANAGEMENT: 10,
|
preisangaben: 2,
|
||||||
|
fernabsatz_info: 4,
|
||||||
|
streitbeilegung: 1,
|
||||||
|
produktsicherheit: 8,
|
||||||
|
ai_act_doku: 12,
|
||||||
}
|
}
|
||||||
return effortMap[docType] || 6
|
return effortMap[docType] ?? 6
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user