feat(advisor): Case Workspace v2 — Evidence grouping, human names, 3-column, summary
Reworks the advisor toward a Compliance Case Workspace (review feedback): - Rename user-facing "Quellen" -> "Evidence". - Evidence grouped by document/regulation family (count + expandable) — no more unsorted DSK/DSK/DPF/... jumble. - Human-readable regulation names via a display registry (DSK Sdm B51 -> "DSK Standard-Datenschutzmodell (SDM)" / Kapitel B51); generic, bridges G2. - Evidence summary "Antwort basiert auf" with meaningful counts; Regelwerke = distinct FAMILIES (fixes the inflated count). NO fabricated trust score (needs a defined basis). - Expanded mode = 3-column workspace (question+summary | answer | evidence, independent scroll) + history switcher; narrow mode stays stacked. - Prompt: push aggressive markdown structure (## per aspect, numbered phases). Deferred/coordinated on board: C8 diagrams (RAG contract), answer<->evidence coupling [1] (needs LLM citation anchors — phase 2), G1 retrieval relevance + G2 metadata (RAG). tsc clean, 17 vitest, check-loc 0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { resolveRegulation } from '../advisor/regulation-display'
|
||||
|
||||
describe('resolveRegulation', () => {
|
||||
it('groups DSK SDM building blocks under one family + extracts the chapter', () => {
|
||||
const b51 = resolveRegulation({ code: 'dsk_sdm_b51', short: 'DSK Sdm B51' })
|
||||
const b41 = resolveRegulation({ code: 'dsk_sdm_b41', short: 'DSK Sdm B41' })
|
||||
const v31 = resolveRegulation({ code: 'dsk_sdm_v31', short: 'DSK Sdm V31' })
|
||||
expect(b51.familyKey).toBe('dsk_sdm')
|
||||
expect(b41.familyKey).toBe('dsk_sdm')
|
||||
expect(v31.familyKey).toBe('dsk_sdm')
|
||||
expect(b51.familyLabel).toContain('Standard-Datenschutzmodell')
|
||||
expect(b51.chapter).toBe('B51')
|
||||
expect(v31.chapter).toBe('V31')
|
||||
})
|
||||
|
||||
it('maps known regulations to friendly family keys', () => {
|
||||
expect(resolveRegulation({ code: 'cra', short: 'CRA' }).familyKey).toBe('cra')
|
||||
expect(resolveRegulation({ code: 'nis2', short: 'NIS2' }).familyKey).toBe('nis2')
|
||||
expect(resolveRegulation({ code: 'dpf', short: 'DPF' }).familyKey).toBe('dpf')
|
||||
expect(resolveRegulation({ code: 'dsgvo', short: 'DS-GVO' }).familyKey).toBe('dsgvo')
|
||||
expect(resolveRegulation({ code: 'bdsg', short: 'BDSG' }).familyKey).toBe('bdsg')
|
||||
})
|
||||
|
||||
it('falls back to code as family + short as label for unknown regulations', () => {
|
||||
const r = resolveRegulation({ code: 'xyz_reg', short: 'XYZ' })
|
||||
expect(r.familyKey).toBe('xyz_reg')
|
||||
expect(r.familyLabel).toBe('XYZ')
|
||||
expect(r.chapter).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
// Human-readable display for regulations. Maps messy codes/short-names to a stable FAMILY key +
|
||||
// friendly label (+ chapter for multi-part works like the DSK SDM). Presentation layer only:
|
||||
// it bridges G2 (clean RAG metadata) and keeps working once codes are clean. Extend the table freely.
|
||||
|
||||
import type { RegulationRef } from './evidence'
|
||||
|
||||
export interface RegulationDisplay {
|
||||
familyKey: string // stable key used to GROUP evidence
|
||||
familyLabel: string // human-readable regulation name
|
||||
chapter?: string // e.g. "B51" for a DSK SDM building block
|
||||
}
|
||||
|
||||
interface Rule {
|
||||
test: RegExp
|
||||
key: string
|
||||
label: string
|
||||
chapter?: RegExp
|
||||
}
|
||||
|
||||
// Order matters: more specific patterns first.
|
||||
const RULES: Rule[] = [
|
||||
{
|
||||
test: /dsk.?sdm|standard.?datenschutzmodell|(^|[^a-z])sdm([^a-z]|$)/i,
|
||||
key: 'dsk_sdm',
|
||||
label: 'DSK Standard-Datenschutzmodell (SDM)',
|
||||
chapter: /\b([A-Z]\d{1,3})\b/,
|
||||
},
|
||||
{ test: /cyber.?resilience|(^|[^a-z])cra([^a-z]|$)/i, key: 'cra', label: 'Cyber Resilience Act (CRA)' },
|
||||
{ test: /(^|[^a-z])nis.?2([^a-z]|$)/i, key: 'nis2', label: 'NIS2-Richtlinie' },
|
||||
{ test: /data.?privacy.?framework|(^|[^a-z])dpf([^a-z]|$)/i, key: 'dpf', label: 'EU-US Data Privacy Framework' },
|
||||
{ test: /maschinen|2023.?1230/i, key: 'maschinenvo', label: 'Maschinenverordnung (EU) 2023/1230' },
|
||||
{ test: /ds.?gvo|gdpr/i, key: 'dsgvo', label: 'DSGVO – Datenschutz-Grundverordnung' },
|
||||
{ test: /(^|[^a-z])bdsg([^a-z]|$)/i, key: 'bdsg', label: 'BDSG – Bundesdatenschutzgesetz' },
|
||||
{ test: /tdddg|ttdsg/i, key: 'tddg', label: 'TDDDG (Digitale-Dienste-Datenschutz)' },
|
||||
{ test: /edpb|edsa|(^|[^a-z])wp\s?\d+/i, key: 'edpb', label: 'EDPB / DSK Leitlinien' },
|
||||
{ test: /(^|[^a-z])bsi([^a-z]|$)/i, key: 'bsi', label: 'BSI' },
|
||||
]
|
||||
|
||||
export function resolveRegulation(reg: RegulationRef): RegulationDisplay {
|
||||
const hay = `${reg.code || ''} ${reg.short || ''} ${reg.name || ''}`
|
||||
for (const r of RULES) {
|
||||
if (r.test.test(hay)) {
|
||||
const chapter = r.chapter
|
||||
? r.chapter.exec(reg.short || reg.code || '')?.[1] || undefined
|
||||
: undefined
|
||||
return { familyKey: r.key, familyLabel: r.label, chapter }
|
||||
}
|
||||
}
|
||||
return {
|
||||
familyKey: reg.code || reg.short || 'unknown',
|
||||
familyLabel: reg.short || reg.name || reg.code || 'Regelwerk',
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user