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:
Benjamin Admin
2026-07-01 10:38:06 +02:00
parent 3884038b06
commit 591cae5ebc
12 changed files with 362 additions and 125 deletions
@@ -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',
}
}