49171e841f
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>
106 lines
3.5 KiB
TypeScript
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 })
|
|
})
|
|
})
|