a9b04e5286
Rework the Compliance Advisor header ("Diese Antwort stuetzt sich auf")
to describe the EVIDENCE rather than the documents: binding
Rechtsgrundlagen split from Leitlinien (soft-law guidance), a
per-regulation breakdown, plus Abbildungen, Fussnoten and Evidence Units.
No fabricated trust score — objective counts only.
- bindingness is a canonical Legal-KG fact (APEX rule): added an optional
EvidenceUnit.bindingness contract seam; the FE renders the split from it
and degrades to a neutral per-regulation breakdown when it is absent
(SDK/RAG asked via board to populate it in /retrieve).
- evidence-grouping.ts: pure, tested grouping/counting model.
- route.ts: optional `audience` field (tonality) kept out of the retrieval
question; answers lead with a "Kurz gesagt" summary, structured by theme.
- E2E + unit tests updated for the evidence framing.
Not deployed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
groupByFamily,
|
|
provisionSummary,
|
|
summarizeEvidence,
|
|
type FamilyGroup,
|
|
} from '../advisor/evidence-grouping'
|
|
import type { EvidenceUnit } from '../advisor/contract'
|
|
|
|
function u(p: Partial<EvidenceUnit> & { document: string }): EvidenceUnit {
|
|
return { evidence_id: Math.random().toString(36).slice(2), ...p }
|
|
}
|
|
|
|
// The Datenschutzerklärung scenario the user reviewed: 6 Kernnormen (5 DSGVO Artikel + § 25 TDDDG)
|
|
// + 2 Leitlinien (DSK, EDPB) across 8 evidence units.
|
|
const DSE: EvidenceUnit[] = [
|
|
u({ document: 'DSGVO', section: 'Art. 6', bindingness: 'binding' }),
|
|
u({ document: 'DSGVO', section: 'Art. 7', bindingness: 'binding' }),
|
|
u({ document: 'DSGVO', section: 'Art. 12', bindingness: 'binding' }),
|
|
u({ document: 'DSGVO', section: 'Art. 13', bindingness: 'binding' }),
|
|
u({ document: 'DSGVO', section: 'Art. 14', bindingness: 'binding' }),
|
|
u({ document: 'TDDDG', section: '§ 25', bindingness: 'binding' }),
|
|
u({ document: 'DSK', bindingness: 'guidance' }),
|
|
u({ document: 'EDPB WP 259', bindingness: 'guidance' }),
|
|
]
|
|
|
|
describe('groupByFamily', () => {
|
|
it('groups a family and collects distinct provisions in order', () => {
|
|
const groups = groupByFamily(DSE)
|
|
const dsgvo = groups.find((g) => g.key === 'dsgvo')!
|
|
expect(dsgvo.units).toBe(5)
|
|
expect(dsgvo.sections).toEqual(['Art. 6', 'Art. 7', 'Art. 12', 'Art. 13', 'Art. 14'])
|
|
expect(dsgvo.bindingness).toBe('binding')
|
|
})
|
|
|
|
it('does not duplicate a repeated section', () => {
|
|
const groups = groupByFamily([
|
|
u({ document: 'DSGVO', section: 'Art. 13', bindingness: 'binding' }),
|
|
u({ document: 'DSGVO', section: 'Art. 13', bindingness: 'binding' }),
|
|
])
|
|
expect(groups[0].sections).toEqual(['Art. 13'])
|
|
expect(groups[0].units).toBe(2)
|
|
})
|
|
})
|
|
|
|
describe('summarizeEvidence', () => {
|
|
it('splits binding norms from guidance with correct counts', () => {
|
|
const m = summarizeEvidence(DSE)
|
|
expect(m.hasBindingness).toBe(true)
|
|
expect(m.normProvisions).toBe(6) // 5 DSGVO Artikel + § 25 TDDDG
|
|
expect(m.guidanceCount).toBe(2) // DSK + EDPB
|
|
expect(m.unitCount).toBe(8)
|
|
expect(m.norms.map((g) => g.key).sort()).toEqual(['dsgvo', 'tddg'])
|
|
})
|
|
|
|
it('degrades to a neutral breakdown when bindingness is absent', () => {
|
|
const m = summarizeEvidence([
|
|
u({ document: 'DSGVO', section: 'Art. 30' }),
|
|
u({ document: 'CRA', section: 'Art. 14' }),
|
|
])
|
|
expect(m.hasBindingness).toBe(false)
|
|
expect(m.groups).toHaveLength(2)
|
|
expect(m.normProvisions).toBe(0)
|
|
expect(m.guidanceCount).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('provisionSummary', () => {
|
|
const g = (sections: string[], units = sections.length): FamilyGroup => ({
|
|
key: 'k',
|
|
label: 'L',
|
|
sections,
|
|
units,
|
|
bindingness: 'binding',
|
|
})
|
|
|
|
it('names Artikel, §§, single provisions and bare units', () => {
|
|
expect(provisionSummary(g(['Art. 6', 'Art. 7', 'Art. 13']))).toBe('3 Artikel')
|
|
expect(provisionSummary(g(['§ 25']))).toBe('§ 25')
|
|
expect(provisionSummary(g(['§ 25', '§ 26']))).toBe('2 §§')
|
|
expect(provisionSummary(g(['Art. 13', '§ 25', 'Anhang I']))).toBe('3 Fundstellen')
|
|
expect(provisionSummary(g([], 3))).toBe('3 Fundstellen')
|
|
expect(provisionSummary(g([], 1))).toBe('1 Fundstelle')
|
|
})
|
|
})
|