Files
breakpilot-compliance/admin-compliance/lib/sdk/__tests__/advisor-evidence-adapter.test.ts
T
Benjamin Admin 49171e841f feat(advisor): Evidence Workspace — structured panes, markdown, sources as knowledge units
Rebuilds the Compliance Advisor floating widget from a plain chat into an Evidence
Workspace: pinned last question, markdown-rendered answer (clean prose), and separate
panes for Sources (hierarchical Knowledge Units), Figures (C8, conditional) and
Footnotes (C-FN), plus a stats bar (Quellen/Regelwerke/Diagramme/Fußnoten). Scrollable
turn history; stays a floating icon on every SDK page.

Architecture (user direction): the frontend renders ONLY structured evidence and NEVER
parses the answer text. The proxy now returns a JSON AdvisorEvidenceMeta line followed
by the streamed markdown answer; advisor-rag exposes structured results; an adapter maps
RAG/compiler output to the frontend envelope. Figures/footnotes wire in once the
RAG-ingestion contract lands (requested on the board) — figures pane is conditional.

- lib/sdk/advisor/{evidence,evidence-adapter}.ts (+ adapter test, 7 cases)
- components/sdk/advisor/* panes + in-house safe Markdown (no new dep, no dangerouslySetInnerHTML) + test
- useAdvisorStream (meta-line parse + streamed answer) + useAdvisorEmail (escaped)
- proxy: evidence-meta-v1 envelope + clean-prose prompt (no inline citations)
- tsc clean, 11 vitest pass, check-loc 0. ESLint not installed in this node_modules -> CI lints on push.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-01 07:46:37 +02:00

106 lines
3.5 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { adaptEvidence } from '../advisor/evidence-adapter'
import type { SdkRagResult } from '../agents/advisor-rag'
describe('adaptEvidence', () => {
it('maps a structured RAG result to a hierarchical Knowledge Unit', () => {
const results: SdkRagResult[] = [
{
text: 'Der Verantwortliche fuehrt ein Verzeichnis ...',
regulation_code: 'DSGVO',
regulation_short: 'DSGVO',
article_label: 'Art. 30 DSGVO',
article: 'Art. 30',
paragraph: 'Abs. 1',
source_url: 'https://example.test/dsgvo-30',
score: 0.9,
},
]
const { sources, stats } = adaptEvidence({ results })
expect(sources).toHaveLength(1)
expect(sources[0].label).toBe('Art. 30 DSGVO')
expect(sources[0].section).toBe('Art. 30')
expect(sources[0].paragraph).toBe('Abs. 1')
expect(sources[0].open?.originalUrl).toBe('https://example.test/dsgvo-30')
expect(sources[0].snippet).toContain('Verzeichnis')
expect(stats.sources).toBe(1)
expect(stats.regulations).toBe(1)
})
it('dedupes the same citation and keeps the highest score', () => {
const base: SdkRagResult = {
text: 'x',
regulation_code: 'CRA',
regulation_short: 'CRA',
article_label: 'Annex I',
article: 'Annex I',
}
const { sources } = adaptEvidence({
results: [
{ ...base, score: 0.4 },
{ ...base, score: 0.8 },
],
})
expect(sources).toHaveLength(1)
expect(sources[0].score).toBe(0.8)
})
it('counts distinct regulations in stats', () => {
const { stats } = adaptEvidence({
results: [
{ text: 'a', regulation_code: 'DSGVO', article_label: 'Art. 5' },
{ text: 'b', regulation_code: 'DSGVO', article_label: 'Art. 6' },
{ text: 'c', regulation_code: 'BDSG', article_label: '§ 38' },
],
})
expect(stats.sources).toBe(3)
expect(stats.regulations).toBe(2)
})
it('labels recitals as Erwaegungsgrund', () => {
const { sources } = adaptEvidence({
results: [{ text: 'r', regulation_code: 'DSGVO', is_recital: true, article: '47' }],
})
expect(sources[0].section).toBe('Erwägungsgrund 47')
})
it('maps figures (C8) to figure units and counts them', () => {
const { figures, stats } = adaptEvidence({
results: [],
figures: [
{
figure_id: 'fig-pdca',
label: 'Abbildung 3',
caption: 'PDCA-Zyklus',
regulation_short: 'EDPB WP248',
vision_summary: 'Kreislauf Plan-Do-Check-Act',
image_url: 'https://example.test/abb3.png',
},
],
})
expect(figures).toHaveLength(1)
expect(figures[0].label).toBe('Abbildung 3')
expect(figures[0].caption).toBe('PDCA-Zyklus')
expect(figures[0].imageUrl).toBe('https://example.test/abb3.png')
expect(stats.figures).toBe(1)
})
it('maps footnotes (C-FN) and counts them', () => {
const { footnotes, stats } = adaptEvidence({
results: [],
footnotes: [{ number: 17, regulation_short: 'EDPB WP248', section: 'Kapitel III.B', text: 'siehe ...' }],
})
expect(footnotes).toHaveLength(1)
expect(footnotes[0].ref).toBe('Fußnote 17')
expect(stats.footnotes).toBe(1)
})
it('returns empty evidence for empty input', () => {
const meta = adaptEvidence({})
expect(meta.sources).toEqual([])
expect(meta.figures).toEqual([])
expect(meta.footnotes).toEqual([])
expect(meta.stats).toEqual({ sources: 0, regulations: 0, figures: 0, footnotes: 0 })
})
})