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>
103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
/**
|
|
* E2E: Compliance Advisor — Clarity Gate (v3 contract)
|
|
*
|
|
* Drives the floating advisor widget end-to-end against a stubbed /api/sdk/compliance-advisor/chat
|
|
* (contract fixtures), so the whole FE chain is exercised without the RAG/LLM backend:
|
|
* - underspecified question -> clarify mode (L1 general answer + domain context chips)
|
|
* - specific question -> answer mode (markdown + [n] citation coupling + evidence pane)
|
|
* - clarify -> pick a context -> scoped answer
|
|
* Runs on CI / macmini (needs the Next app on :3002).
|
|
*/
|
|
|
|
import { test, expect } from '../fixtures/sdk-fixtures'
|
|
|
|
const CHAT_ROUTE = '**/api/sdk/compliance-advisor/chat'
|
|
const openAdvisor = 'Compliance Advisor oeffnen'
|
|
const inputPlaceholder = 'Frage eingeben...'
|
|
|
|
const CLARIFY = {
|
|
mode: 'clarify',
|
|
question: 'Was ist PDCA?',
|
|
clarity: {
|
|
is_underspecified: true,
|
|
concentration: 0.3,
|
|
suggested_contexts: [
|
|
{ id: 'datenschutz', label: 'Datenschutz' },
|
|
{ id: 'cyber', label: 'Cybersecurity' },
|
|
],
|
|
},
|
|
general_answer: 'PDCA steht für **Plan-Do-Check-Act**.',
|
|
answer: null,
|
|
evidence: [],
|
|
citations: [],
|
|
visual_evidence: [],
|
|
footnotes: [],
|
|
}
|
|
|
|
const ANSWER = {
|
|
mode: 'answer',
|
|
question: 'CRA Meldefrist',
|
|
clarity: { is_underspecified: false, dominant_context: 'cyber', concentration: 0.88 },
|
|
answer: 'Die Meldung erfolgt unverzüglich [1].',
|
|
evidence: [
|
|
{ evidence_id: 'e1', document: 'CRA', section: 'Art. 14', paragraph: 'Abs. 1', snippet: 'unverzüglich melden', bindingness: 'binding' },
|
|
],
|
|
citations: [
|
|
{ citation_id: 'c1', number: 1, evidence_id: 'e1', document: 'CRA', section: 'Art. 14', paragraph: 'Abs. 1' },
|
|
],
|
|
visual_evidence: [],
|
|
footnotes: [],
|
|
}
|
|
|
|
async function ask(page: import('@playwright/test').Page, question: string) {
|
|
await page.getByRole('button', { name: openAdvisor }).click()
|
|
const input = page.getByPlaceholder(inputPlaceholder)
|
|
await input.fill(question)
|
|
await input.press('Enter')
|
|
}
|
|
|
|
test.describe('Compliance Advisor — Clarity Gate', () => {
|
|
test('underspecified question -> clarify (L1 definition + context chips, no evidence)', async ({ sdkPage }) => {
|
|
await sdkPage.route(CHAT_ROUTE, (r) =>
|
|
r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(CLARIFY) }),
|
|
)
|
|
await ask(sdkPage, 'Was ist PDCA?')
|
|
|
|
await expect(sdkPage.getByText('Allgemeine Definition')).toBeVisible()
|
|
await expect(sdkPage.getByText('Plan-Do-Check-Act')).toBeVisible()
|
|
await expect(sdkPage.getByRole('button', { name: 'Datenschutz' })).toBeVisible()
|
|
await expect(sdkPage.getByRole('button', { name: 'Cybersecurity' })).toBeVisible()
|
|
})
|
|
|
|
test('specific question -> answer with [n] citation + evidence pane', async ({ sdkPage }) => {
|
|
await sdkPage.route(CHAT_ROUTE, (r) =>
|
|
r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(ANSWER) }),
|
|
)
|
|
await ask(sdkPage, 'CRA Meldefrist')
|
|
|
|
await expect(sdkPage.getByText(/unverzüglich/)).toBeVisible()
|
|
await expect(sdkPage.getByTitle('Beleg 1 anzeigen')).toBeVisible()
|
|
// bindingness present -> header splits into Rechtsgrundlagen vs Leitlinien (evidence framing)
|
|
await expect(sdkPage.getByText('Rechtsgrundlagen').first()).toBeVisible()
|
|
// family name resolved for the user (shown both in the summary breakdown and the evidence card)
|
|
await expect(sdkPage.getByText('Cyber Resilience Act (CRA)').first()).toBeVisible()
|
|
})
|
|
|
|
test('clarify -> pick a context -> scoped answer', async ({ sdkPage }) => {
|
|
let calls = 0
|
|
await sdkPage.route(CHAT_ROUTE, (r) => {
|
|
calls += 1
|
|
r.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(calls === 1 ? CLARIFY : ANSWER),
|
|
})
|
|
})
|
|
await ask(sdkPage, 'Was ist PDCA?')
|
|
await sdkPage.getByRole('button', { name: 'Datenschutz' }).click()
|
|
|
|
await expect(sdkPage.getByText(/unverzüglich/)).toBeVisible()
|
|
await expect(sdkPage.getByTitle('Beleg 1 anzeigen')).toBeVisible()
|
|
})
|
|
})
|