feat(advisor): v3 Clarity Gate — Case model + clarify/answer contract, [n] citations
Builds the FE against the SDK<->FE Clarity-Gate contract (board 2026-07-01 /
advisor-clarity-gate-contract). The advisor is now a CASE, not a chat:
- Request {question, context?}; response {mode: clarify|answer, clarity, general_answer,
answer, evidence, citations, visual_evidence, footnotes}.
- clarify mode: short L1 general answer (marked "allgemeine Definition, ohne Rechtsquelle")
+ domain context chips; picking a chip re-runs the case scoped (-> answer).
- answer mode: markdown answer with clickable [n] citation markers coupled to evidence
cards (highlight + scroll), evidence grouped by document family, visual_evidence
(visual_type), footnotes, honest summary counts (no trust score).
- FE never parses the answer for structure — only the deliberate [n] markers, mapped via
citations[]. New: contract.ts, useAdvisorCase, useCitationHighlight, ClarifyView,
EvidenceUnitCard, VisualEvidencePane, CaseView. Removed the v2 stream/chat components.
NOT deployed: FE shape-switch (JSON modes) must deploy TOGETHER with the SDK endpoint
delivering the contract (board deploy-coupling). Proxy/route.ts unchanged (SDK-owned).
tsc clean, 16 vitest (incl. clarify+answer fixtures), check-loc 0.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { CaseView } from './CaseView'
|
||||
import type { AdvisorCase } from './useAdvisorCase'
|
||||
import type { AdvisorResponse } from '@/lib/sdk/advisor/contract'
|
||||
|
||||
const clarify: AdvisorResponse = {
|
||||
mode: 'clarify',
|
||||
question: 'Was ist PDCA?',
|
||||
clarity: {
|
||||
is_underspecified: true,
|
||||
concentration: 0.38,
|
||||
suggested_contexts: [
|
||||
{ id: 'datenschutz', label: 'Datenschutz' },
|
||||
{ id: 'qm', label: 'Qualitätsmanagement' },
|
||||
],
|
||||
},
|
||||
general_answer: 'PDCA steht für **Plan-Do-Check-Act**.',
|
||||
answer: null,
|
||||
evidence: [],
|
||||
citations: [],
|
||||
visual_evidence: [],
|
||||
footnotes: [],
|
||||
}
|
||||
|
||||
const answer: AdvisorResponse = {
|
||||
mode: 'answer',
|
||||
question: 'PDCA im Datenschutz?',
|
||||
clarity: { is_underspecified: false, dominant_context: 'datenschutz', concentration: 0.88 },
|
||||
answer: 'Der DSM-Zyklus [1] beschreibt den Ablauf.',
|
||||
evidence: [
|
||||
{ evidence_id: 'e1', document: 'DSK Sdm B41', section: 'Art. 5', paragraph: 'Abs. 2', snippet: 'x' },
|
||||
],
|
||||
citations: [
|
||||
{ citation_id: 'c1', evidence_id: 'e1', document: 'DSK Sdm B41', section: 'Art. 5', paragraph: 'Abs. 2' },
|
||||
],
|
||||
visual_evidence: [
|
||||
{ visual_id: 'v1', visual_type: 'flowchart', caption: 'PDCA-Zyklus', document: 'DSK SDM', vision_summary: 's' },
|
||||
],
|
||||
footnotes: [],
|
||||
}
|
||||
|
||||
function mk(response: AdvisorResponse): AdvisorCase {
|
||||
return { id: 'case1', question: response.question, response, selectedContext: null, status: 'done' }
|
||||
}
|
||||
|
||||
describe('CaseView — clarify mode', () => {
|
||||
it('renders the L1 general answer + context chips and fires onSelectContext', () => {
|
||||
const onSel = vi.fn()
|
||||
const { container, getByText } = render(
|
||||
<CaseView c={mk(clarify)} busy={false} onSelectContext={onSel} />,
|
||||
)
|
||||
expect(container.textContent).toContain('Plan-Do-Check-Act')
|
||||
expect(container.textContent).toContain('Allgemeine Definition')
|
||||
fireEvent.click(getByText('Datenschutz'))
|
||||
expect(onSel).toHaveBeenCalledWith('datenschutz')
|
||||
})
|
||||
})
|
||||
|
||||
describe('CaseView — answer mode', () => {
|
||||
it('renders answer with a clickable [n] citation, grouped evidence (friendly name), and visual', () => {
|
||||
const { container } = render(<CaseView c={mk(answer)} busy={false} onSelectContext={() => {}} />)
|
||||
expect(container.textContent).toContain('DSM-Zyklus')
|
||||
expect(container.querySelector('button[title="Beleg 1 anzeigen"]')).not.toBeNull()
|
||||
expect(container.textContent).toContain('DSK Standard-Datenschutzmodell')
|
||||
expect(container.textContent).toContain('PDCA-Zyklus')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user