Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e4c5c0907 | |||
| e646091ba2 | |||
| 069b855b49 | |||
| 01af9b56a6 | |||
| 017c9b3c12 |
@@ -24,6 +24,20 @@ describe('advisor-rag', () => {
|
|||||||
expect(out).toEqual([{ content: 'Art. 35 DSGVO ...', source: 'DSGVO', score: 0.91 }])
|
expect(out).toEqual([{ content: 'Art. 35 DSGVO ...', source: 'DSGVO', score: 0.91 }])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('haengt article/paragraph an die Quelle an (Fallback ohne article_label)', () => {
|
||||||
|
const out = mod.mapSdkResults([
|
||||||
|
{ text: 'Pflicht zur Benennung ...', regulation_short: 'BDSG', article: '§ 38', paragraph: '(1)', score: 0.8 },
|
||||||
|
])
|
||||||
|
expect(out).toEqual([{ content: 'Pflicht zur Benennung ...', source: 'BDSG § 38 (1)', score: 0.8 }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('nutzt article_label direkt, wenn vorhanden (druckbare Fundstelle)', () => {
|
||||||
|
const out = mod.mapSdkResults([
|
||||||
|
{ text: 'x', regulation_short: 'BDSG', article: '38', paragraph: '1', sub: 'Satz 2', article_label: 'BDSG § 38 Abs. 1', score: 0.9 },
|
||||||
|
])
|
||||||
|
expect(out[0].source).toBe('BDSG § 38 Abs. 1')
|
||||||
|
})
|
||||||
|
|
||||||
it('faellt auf regulation_name/code zurueck und filtert leere Inhalte', () => {
|
it('faellt auf regulation_name/code zurueck und filtert leere Inhalte', () => {
|
||||||
const out = mod.mapSdkResults([
|
const out = mod.mapSdkResults([
|
||||||
{ text: '', regulation_short: 'X' },
|
{ text: '', regulation_short: 'X' },
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
* — damit profitiert der Advisor vom reicheren Embedding.
|
* — damit profitiert der Advisor vom reicheren Embedding.
|
||||||
*
|
*
|
||||||
* Fehler je Collection werden geschluckt (graceful: Antwort ohne diesen Treffer).
|
* Fehler je Collection werden geschluckt (graceful: Antwort ohne diesen Treffer).
|
||||||
|
* Fundstellen via article_label sind live ab dem Prod-Re-Ingest 2026-06.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const SDK_URL =
|
const SDK_URL =
|
||||||
@@ -31,6 +32,12 @@ interface SdkRagResult {
|
|||||||
regulation_code?: string
|
regulation_code?: string
|
||||||
regulation_name?: string
|
regulation_name?: string
|
||||||
regulation_short?: string
|
regulation_short?: string
|
||||||
|
article_label?: string
|
||||||
|
article?: string
|
||||||
|
paragraph?: string
|
||||||
|
sub?: string
|
||||||
|
citation_style?: string
|
||||||
|
is_recital?: boolean
|
||||||
category?: string
|
category?: string
|
||||||
source_url?: string
|
source_url?: string
|
||||||
score?: number
|
score?: number
|
||||||
@@ -47,7 +54,15 @@ export function mapSdkResults(results: SdkRagResult[] | undefined): ScoredPassag
|
|||||||
return (results || [])
|
return (results || [])
|
||||||
.map((r) => ({
|
.map((r) => ({
|
||||||
content: r.text || '',
|
content: r.text || '',
|
||||||
source: r.regulation_short || r.regulation_name || r.regulation_code || 'Unbekannt',
|
// Fundstelle: article_label ist die fertig formatierte, druckbare Quelle aus der
|
||||||
|
// Ingestion ("BDSG § 38 Abs. 1"); Fallback baut sie aus den strukturierten Feldern
|
||||||
|
// (bzw. alt-ingestierte Chunks ohne Legal-Metadaten). Siehe rag_reingest_spec.md §2/§7.
|
||||||
|
source:
|
||||||
|
(r.article_label && r.article_label.trim()) ||
|
||||||
|
[r.regulation_short || r.regulation_name || r.regulation_code, r.article, r.paragraph, r.sub]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ') ||
|
||||||
|
'Unbekannt',
|
||||||
score: typeof r.score === 'number' ? r.score : 0,
|
score: typeof r.score === 'number' ? r.score : 0,
|
||||||
}))
|
}))
|
||||||
.filter((p) => p.content)
|
.filter((p) => p.content)
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ interface SdkRagResult {
|
|||||||
regulation_code?: string
|
regulation_code?: string
|
||||||
regulation_name?: string
|
regulation_name?: string
|
||||||
regulation_short?: string
|
regulation_short?: string
|
||||||
|
article_label?: string
|
||||||
|
article?: string
|
||||||
|
paragraph?: string
|
||||||
|
sub?: string
|
||||||
// Rueckwaerts-kompatibel, falls eine Quelle noch das alte rag-service-Format liefert:
|
// Rueckwaerts-kompatibel, falls eine Quelle noch das alte rag-service-Format liefert:
|
||||||
content?: string
|
content?: string
|
||||||
source_name?: string
|
source_name?: string
|
||||||
@@ -56,12 +60,13 @@ export async function queryRAG(query: string, topK = 3, collection?: string): Pr
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
.map((r, i) => {
|
.map((r, i) => {
|
||||||
|
const base =
|
||||||
|
r.regulation_short || r.regulation_name || r.regulation_code || r.source_name || r.source_code
|
||||||
|
// article_label = fertig formatierte Fundstelle aus der Ingestion ("BDSG § 38 Abs. 1");
|
||||||
|
// Fallback baut sie aus den strukturierten Feldern. Siehe rag_reingest_spec.md §2/§7.
|
||||||
const source =
|
const source =
|
||||||
r.regulation_short ||
|
(r.article_label && r.article_label.trim()) ||
|
||||||
r.regulation_name ||
|
[base, r.article, r.paragraph, r.sub].filter(Boolean).join(' ') ||
|
||||||
r.regulation_code ||
|
|
||||||
r.source_name ||
|
|
||||||
r.source_code ||
|
|
||||||
'Unbekannt'
|
'Unbekannt'
|
||||||
const content = r.text || r.content || ''
|
const content = r.text || r.content || ''
|
||||||
return `[Quelle ${i + 1}: ${source}]\n${content}`
|
return `[Quelle ${i + 1}: ${source}]\n${content}`
|
||||||
|
|||||||
@@ -95,12 +95,29 @@ func (c *LegalRAGClient) searchInternal(ctx context.Context, collection string,
|
|||||||
|
|
||||||
results := make([]LegalSearchResult, len(hits))
|
results := make([]LegalSearchResult, len(hits))
|
||||||
for i, hit := range hits {
|
for i, hit := range hits {
|
||||||
|
// Legal-Metadaten nach rag_reingest_spec.md §2: bevorzugt die normalisierten Felder
|
||||||
|
// (article_label/regulation_code/article/...); Fallback auf alte Feldnamen, solange der
|
||||||
|
// Korpus noch nicht re-ingestiert ist (regulation_id, section="§ 38").
|
||||||
|
regCode := getString(hit.Payload, "regulation_code")
|
||||||
|
if regCode == "" {
|
||||||
|
regCode = getString(hit.Payload, "regulation_id")
|
||||||
|
}
|
||||||
|
article := getString(hit.Payload, "article")
|
||||||
|
if article == "" {
|
||||||
|
article = getString(hit.Payload, "section")
|
||||||
|
}
|
||||||
results[i] = LegalSearchResult{
|
results[i] = LegalSearchResult{
|
||||||
Text: getString(hit.Payload, "chunk_text"),
|
Text: getString(hit.Payload, "chunk_text"),
|
||||||
RegulationCode: getString(hit.Payload, "regulation_id"),
|
RegulationCode: regCode,
|
||||||
RegulationName: getString(hit.Payload, "regulation_name_de"),
|
RegulationName: getString(hit.Payload, "regulation_name_de"),
|
||||||
RegulationShort: getString(hit.Payload, "regulation_short"),
|
RegulationShort: getString(hit.Payload, "regulation_short"),
|
||||||
Category: getString(hit.Payload, "category"),
|
Category: getString(hit.Payload, "category"),
|
||||||
|
ArticleLabel: getString(hit.Payload, "article_label"),
|
||||||
|
Article: article,
|
||||||
|
Paragraph: getString(hit.Payload, "paragraph"),
|
||||||
|
Sub: getString(hit.Payload, "sub"),
|
||||||
|
IsRecital: getBool(hit.Payload, "is_recital"),
|
||||||
|
CitationStyle: getString(hit.Payload, "citation_style"),
|
||||||
Pages: getIntSlice(hit.Payload, "pages"),
|
Pages: getIntSlice(hit.Payload, "pages"),
|
||||||
SourceURL: getString(hit.Payload, "source"),
|
SourceURL: getString(hit.Payload, "source"),
|
||||||
Score: hit.Score,
|
Score: hit.Score,
|
||||||
|
|||||||
@@ -191,6 +191,13 @@ func (c *LegalRAGClient) ScrollDocumentIndex(ctx context.Context, collection str
|
|||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
|
|
||||||
|
func getBool(m map[string]interface{}, key string) bool {
|
||||||
|
if v, ok := m[key].(bool); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func getString(m map[string]interface{}, key string) string {
|
func getString(m map[string]interface{}, key string) string {
|
||||||
if v, ok := m[key]; ok {
|
if v, ok := m[key]; ok {
|
||||||
if s, ok := v.(string); ok {
|
if s, ok := v.(string); ok {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package ucca
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// LegalSearchResult represents a single search result from the compliance corpus.
|
// LegalSearchResult represents a single search result from the compliance corpus.
|
||||||
|
// Legal-Zitatfelder (article_label/article/paragraph/...) sind live ab dem
|
||||||
|
// Prod-Re-Ingest 2026-06 (siehe docs-src/development/rag_reingest_spec.md §2).
|
||||||
type LegalSearchResult struct {
|
type LegalSearchResult struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
RegulationCode string `json:"regulation_code"`
|
RegulationCode string `json:"regulation_code"`
|
||||||
@@ -10,7 +12,11 @@ type LegalSearchResult struct {
|
|||||||
RegulationShort string `json:"regulation_short"`
|
RegulationShort string `json:"regulation_short"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Article string `json:"article,omitempty"`
|
Article string `json:"article,omitempty"`
|
||||||
|
ArticleLabel string `json:"article_label,omitempty"`
|
||||||
Paragraph string `json:"paragraph,omitempty"`
|
Paragraph string `json:"paragraph,omitempty"`
|
||||||
|
Sub string `json:"sub,omitempty"`
|
||||||
|
IsRecital bool `json:"is_recital,omitempty"`
|
||||||
|
CitationStyle string `json:"citation_style,omitempty"`
|
||||||
Pages []int `json:"pages,omitempty"`
|
Pages []int `json:"pages,omitempty"`
|
||||||
SourceURL string `json:"source_url"`
|
SourceURL string `json:"source_url"`
|
||||||
Score float64 `json:"score"`
|
Score float64 `json:"score"`
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Prüfer-Matrix — Meta-Modell der Doc-Check-Plattform
|
||||||
|
|
||||||
|
> **Status:** Plattformkonzept, **eingefroren 2026-06-21**. Abgeleitet aus 4 kalibrierten Modulen (DSE, Cookie, Impressum, AGB). Erweitert `verification_method.md` (5→8 Klassen) und fügt die `decision_method`-Achse hinzu.
|
||||||
|
> **Kernsatz:** *Nicht jedes Control braucht denselben Richter.* Der **Kontrolltyp bestimmt den Prüfer** — nicht alles ist ein Text-/LLM-Problem.
|
||||||
|
|
||||||
|
## 0. Die Architektur-Verschiebung
|
||||||
|
|
||||||
|
**Vorher (implizit):** `Control → Embedding → LLM → Finding`.
|
||||||
|
|
||||||
|
**Jetzt (empirisch bewiesen):**
|
||||||
|
```
|
||||||
|
Control → [scope-gate] → artifact_type → verification_method → decision_method
|
||||||
|
→ passender Prüfer → Evidence → Finding (severity-getiert)
|
||||||
|
```
|
||||||
|
|
||||||
|
Vier strukturell verschiedene Dokumenttypen führten immer wieder auf dieselbe Meta-Struktur. Das ist größer als jeder Einzel-Fix: es ist mit hoher Wahrscheinlichkeit das Routing-Prinzip für alle ~14.000 Master Controls.
|
||||||
|
|
||||||
|
## 1. Empirische Basis (4 Module)
|
||||||
|
|
||||||
|
| Modul | dominanter Prüfer | Beleg |
|
||||||
|
|---|---|---|
|
||||||
|
| DSE | CONTENT (LLM/Embedding) | Kriterien-Kalibrierung, FP 11→6 % |
|
||||||
|
| Cookie-Banner | BEHAVIOR | Enforcement / Dark-Pattern (Playwright) |
|
||||||
|
| Cookie-Policy | CONTENT + REFERENCE | Inhalt + Verweise |
|
||||||
|
| Impressum | FIELD + PRESENTATION (+ SCOPE-Gate) | Feld-Matcher FP 0 %, Präsentation re-routed |
|
||||||
|
| AGB | CONTENT (KEYWORD→EMBEDDING→LLM) + REFERENCE (+ SCOPE-Gate) | 71 % FP → ~0; LLM nur 2/21 Items |
|
||||||
|
|
||||||
|
## 2. Achse 1 — `verification_method` (welcher Prüfer-TYP)
|
||||||
|
|
||||||
|
| verification_method | Prüfer | Leitfrage | Beleg | Reifegrad |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **CONTENT** | Embedding + LLM-Kaskade | Was steht (als Offenlegung) im Text? | DSE, Cookie-Policy | kalibriert |
|
||||||
|
| **FIELD** | Regex / Extraktion (Feldmatrix) | Welche Pflichtfelder existieren + sind valide? | Impressum (HRB, USt-IdNr, Anschrift) | ✓ FP 0 % |
|
||||||
|
| **REFERENCE** | Link-Resolver | Gibt es einen klaren Verweis/Link, löst er auf? | AGB `data_protection` | ✓ 7/7 |
|
||||||
|
| **BEHAVIOR** | Playwright + API | Manipuliert die UI die Entscheidung? | Cookie-Banner (Reject=Accept, Pre-Consent-Cookies) | Matrix vorhanden |
|
||||||
|
| **PRESENTATION** | Playwright UI-Sensor | Auffindbar / sichtbar / erreichbar? | Impressum „leicht erkennbar" | re-routed |
|
||||||
|
| **PROCESS** | Audit / Evidence | Gibt es einen internen Nachweis? | VVT, TOM, interne Richtlinie | Checkliste |
|
||||||
|
| **TECHNICAL** | Scanner (Repo / Netz / Config) | Ist die technische Maßnahme implementiert? | geplant: CRA, NIS2, ISO 27001 | offen |
|
||||||
|
| **CONTRACTUAL** | Clause-Engine | Ist die Klausel vorhanden + rechtskonform? | AGB (delivery/warranty; Defekte → Stage 3) | teilweise |
|
||||||
|
|
||||||
|
**CONTENT vs CONTRACTUAL:** CONTENT = Offenlegungs-Prosa (DSE nennt Zwecke). CONTRACTUAL = Vertragsklauseln (AGB-Haftung/Lieferung). Beide können Embedding+LLM nutzen — die Trennung ist die Rechtsnatur + die spätere Defekt-Prüfung (Klausel rechtswidrig?).
|
||||||
|
|
||||||
|
**PRESENTATION ≠ BEHAVIOR:** beide Playwright, andere Rechtslogik. PRESENTATION = Auffindbarkeit/Sichtbarkeit; BEHAVIOR = Entscheidungs-Manipulation/Dark-Pattern.
|
||||||
|
|
||||||
|
## 3. Achse 2 — `decision_method` (WIE innerhalb CONTENT/CONTRACTUAL entschieden wird)
|
||||||
|
|
||||||
|
Die AGB-Entdeckung: **Controls INNERHALB eines Prüfer-Typs brauchen verschiedene Entscheider.** Eskalation nur bei Bedarf (Kostendisziplin):
|
||||||
|
|
||||||
|
| decision_method | Mechanismus | Wann | Beleg (AGB) |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **KEYWORD** | Regex-Match | Pflicht eindeutig formuliert | Keyword-Layer |
|
||||||
|
| **EMBEDDING** | per-Item-Cosinus-Schwelle (Doc-Chunks × Item-Paraphrasen) | Prosa, semantisch trennbar | 13/21 Items, 0 Fehl-Rescue |
|
||||||
|
| **LLM** | Clause-Retrieval (**ganze §-Abschnitte**) + starkes Modell, present/absent | semantisch eng (Embedding trennt nicht) | 2/21 Items (delivery/warranty), 14/14 |
|
||||||
|
|
||||||
|
`CONTENT_SIMPLE` = KEYWORD/EMBEDDING reicht; `CONTENT_COMPLEX` = LLM nötig. AGB-Bilanz: **81 % deterministisch, 19 % LLM-fähig**, LLM real nur bei Keyword-Miss.
|
||||||
|
|
||||||
|
## 4. Durable Per-Control-Metadaten (das Routing-Vokabular)
|
||||||
|
|
||||||
|
| Feld | Zweck |
|
||||||
|
|---|---|
|
||||||
|
| `artifact_type` | gegen welches Artefakt geprüft wird → Scanner-Routing |
|
||||||
|
| `obligation_type` | Rechtsnatur: Pflicht / Empfehlung / Kann → Tier |
|
||||||
|
| `check_intent` | was die Prüfung bezweckt |
|
||||||
|
| `reference_allowed` | darf per Verweis erfüllt werden → REFERENCE statt CONTENT |
|
||||||
|
| `scope` / `scope_requires` | Applicability-Gate (Geschäftsmodell, Rechtsform) — **vor** allen Prüfern |
|
||||||
|
| `verification_method` | Achse 1 (Prüfer-Typ) |
|
||||||
|
| `decision_method` | Achse 2 (Entscheider innerhalb CONTENT/CONTRACTUAL) |
|
||||||
|
| `severity` | HIGH / MEDIUM / LOW → Finding vs Empfehlung |
|
||||||
|
|
||||||
|
## 5. Hart erarbeitete Plattform-Prinzipien
|
||||||
|
|
||||||
|
1. **Route, don't uniformly-LLM** — verschiedene Controls, verschiedene Prüfer.
|
||||||
|
2. Eskaliere **KEYWORD → EMBEDDING → LLM nur bei Bedarf** (AGB: 17/21 ohne LLM).
|
||||||
|
3. Embedding: **per-Item-Schwellen** (globale Schwelle scheitert bei juristischer Prosa — PASS/FAIL überlappen global, trennen per-Item).
|
||||||
|
4. LLM-Judge: **ganze §-Abschnitte** schlagen Top-k-Chunks; **starken Tier pinnen** (billig-zuerst-Kaskade eskaliert selbstbewusst-falsche Antworten NICHT, weil die Confidence-Heuristik genauigkeits-blind ist); **present/absent** trennen von der Defekt-Prüfung.
|
||||||
|
5. **REFERENCE (Link) ist ein eigener billiger Prüfer** — keinen „siehe Datenschutzerklärung"-Verweis durch ein LLM jagen.
|
||||||
|
6. **SCOPE-Gate (Applicability) ist vor allen Prüfern** — N/A-Controls werden nie geprüft.
|
||||||
|
7. **Severity → Finding vs Empfehlung** (Tier, nicht droppen).
|
||||||
|
8. *Was im Text nicht beweisbar ist, gehört nicht in den Text-Check.*
|
||||||
|
|
||||||
|
## 6. Schema-Status
|
||||||
|
|
||||||
|
Kein DB-Eingriff (DB eingefroren). `verification_method` + `decision_method` als **abgeleitete Tags** in `control_classification` (aus `artifact_type` / `obligation_type` / `check_intent` + Item-Kalibrierung). `canonical_controls.verification_method` existiert (~4 % befüllt, gröbere Enterprise-Taxonomie) — **nicht** das Doc-Check-Routing.
|
||||||
|
|
||||||
|
## 7. Verbindlichkeit
|
||||||
|
|
||||||
|
Dies ist der **Vertrag**, gegen den implementiert wird. Die AGB-Integration und die nächsten Module (Nutzungsbedingungen, Widerruf, CRA, MaschVO, DORA, NIS2, ISO 27001, AI-Act, VVT, TOM) bauen **dieselbe** Routing-Schicht — nicht modul-lokal. Reihenfolge: **(1) diese Matrix einfrieren → (2) AGB integrieren → (3) Nutzungsbedingungen → (4) Widerruf.**
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Plattform-Validierung der Doc-Check-Kalibrierung — `platform_validation_v1`
|
||||||
|
|
||||||
|
> **Status:** Plattform-Methodik validiert über 3 strukturell verschiedene Dokumentklassen (2026-06-19).
|
||||||
|
> **Zweck:** Nicht ein Modul dokumentieren, sondern den **Kalibrierungsprozess** und die **empirische Fehlerkarte** der Engine — damit die *Ursachen* erhalten bleiben (nicht nur die Messwerte). Erkenntnis > Metrik.
|
||||||
|
|
||||||
|
## 1. Was hier validiert wurde
|
||||||
|
|
||||||
|
Vor dieser Runde war unklar, ob der Restfehler der Doc-Check-Engine aus dem **LLM**, dem **Embedding**, dem **Prompt**, der **Applicability** oder dem **Control-Katalog** stammt — alles vermischt. Nach DSE + Cookie + Impressum existiert eine **belastbare Taxonomie der Fehlerursachen**, und der **Kalibrierungsprozess** hat in drei sehr unterschiedlichen Domänen geliefert. Das ist die eigentliche Errungenschaft — größer als jede einzelne Zahl.
|
||||||
|
|
||||||
|
## 2. Der Kalibrierungsprozess (wiederverwendbarer Kern)
|
||||||
|
|
||||||
|
1. **Opus-GT** je `(Firma × Control)` über 5–9 repräsentative Firmen (stärkstes Modell, NICHT Haiku).
|
||||||
|
2. **Engine-Messung** (Keyword → BGE-M3-Embedding → robuster LLM-Judge) vs GT.
|
||||||
|
3. **FP-Cluster** — wiederkehrende Controls statt Einzel-Findings (systematisch ≠ zufällig).
|
||||||
|
4. **Ursachen-Klassifikation** je FP: `SCOPE` / `ARTIFACT_TYPE` / `CRITERIA` / `JUDGE`.
|
||||||
|
5. **Fix** der dominanten Ursache (versioniert, mit Rechtsnotiz).
|
||||||
|
6. **Re-Messung** — Pflicht: FP↓ **und** FN stabil. Plus **Anti-Overfit** auf ungesehenen Firmen.
|
||||||
|
|
||||||
|
## 3. Plattform-Fehlerkarte (Kernergebnis)
|
||||||
|
|
||||||
|
| Modul | Dominante Ursache | Hebel | Ergebnis | Status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **DSE** | Kriterien zu streng | Kriterien-Kalibrierung (11 Controls) | FP 11 % → **6 %**, FN ~7 %; **generalisiert** (8 Firmen; fresh FP 7 % / FN 5 %) | Release-Candidate |
|
||||||
|
| **Cookie** | `artifact_type` (Banner ≠ Richtlinie) | 31 Banner-Controls → `COOKIE_BANNER`; 21 Kriterien (Kategorie statt Pro-Cookie, Zitat optional), Pro-Cookie = Best-Practice | Precision 0,81 → **0,95**, Recall 0,26 → **0,44**, verpasste Lücken → **0 %**, abs. FP 71 → 54 | Wave-1 (dev) |
|
||||||
|
| **Impressum** | **Scope** (GT-NA 48 %) + **Feld-Extraktion** + **Präsentation** | Scope-Gate (14 raus) + **Feldmatrix-Matcher** (Fakten) + **PRESENTATION_CHECK**-Re-Route (5) | roh: SCOPE-FP 105 / JUDGE-FP 66 → **Text-Check FP 0 % / FN 2 %** | Release-Candidate |
|
||||||
|
|
||||||
|
## 4. Meta-Befunde
|
||||||
|
|
||||||
|
- **Die generische Architektur bewährt sich.** Jede Domäne hat ein *anderes* dominantes Problem — `artifact_type` / `obligation_type` / `scope` tragen unterschiedlich stark. Eine gute generische Architektur erzeugt nicht überall denselben Effekt, sondern löst je Domäne ein anderes Problem. Genau das ist eingetreten.
|
||||||
|
- **Die Zielarchitektur ist domänen-adaptiv, nicht uniform.** „Embedding → OVH → Claude" ist nicht überall richtig: bei **Prosa** (DSE/Cookie) ist die LLM-Kaskade der Hebel; bei **strukturierten Faktendokumenten** (Impressum) ist das LLM sogar schwach (es verfehlt Adressen/Felder, die *dastehen*) → dort schlagen **Scope-Gate + deterministischer Feld-Matcher** den LLM-Judge.
|
||||||
|
- **Wiederkehrendes Anti-Muster:** „vermeintlicher Judge-Fehler → eigentlich Katalog-Fehler" (Scope, Präsentations-statt-Inhalt, Fehl-Typisierung). Erst NACH den Katalog-Fixes ist der Rest ein *echter* Judge-Fehler.
|
||||||
|
|
||||||
|
## 4b. Die `verification_method`-Achse (Synthese — die eigentliche Lehre)
|
||||||
|
|
||||||
|
Nicht jede Compliance-Pflicht ist ein Textproblem. Die 5 entdeckten Fehlerklassen mappen auf **5 Prüfer-Typen** — eine neue Routing-Metadaten-Achse `verification_method`, die einem Control sagt, *welcher Prüfer* zuständig ist (nicht alles an den LLM):
|
||||||
|
|
||||||
|
| `verification_method` | Prüfer | Frage | Beispiel | Status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **CONTENT** | Embedding + LLM-Kaskade (OVH→Claude) | Was steht da? | DSE nennt Zwecke; Cookie-Policy | DSE/Cookie kalibriert |
|
||||||
|
| **FIELD** | Regex/Parser (Feldmatrix) | Welche Felder existieren? | HRB, USt-IdNr, Adresse | Impressum-Fakten ✓ (FP 0 %) |
|
||||||
|
| **PRESENTATION** | Playwright (Sichtbarkeit/Erreichbarkeit) | Ist es auffindbar/wahrnehmbar? | Impressum leicht erkennbar, ständig verfügbar; Footer nicht verdeckt | Re-Route gemacht; Check offen |
|
||||||
|
| **BEHAVIOR** | Playwright + API (Interaktion) | Manipuliert es die Entscheidung? | Reject = Accept, Consent VOR Cookie, kein Dark Pattern | Cookie-Banner-Matrix existiert |
|
||||||
|
| **PROCESS** | Audit/Nachweis | Gibt es internen Nachweis? | VVT, interne Richtlinie, Audit-Entscheidung | Org-Checkliste |
|
||||||
|
|
||||||
|
**PRESENTATION ≠ BEHAVIOR** (beide Playwright, andere Rechtslogik): Präsentation = *Auffindbarkeit/Sichtbarkeit/Zugänglichkeit* (Impressum leicht erkennbar); Behavior = *Entscheidungs-Manipulation/Dark-Pattern* (Reject versteckt). Getrennt halten.
|
||||||
|
|
||||||
|
**Playwright wird damit vom Crawler zum Compliance-Sensor:** es prüft, was kein LLM kann — `display:none`, `font-size:4px`, Cookie-Layer verdeckt den Footer. LLM sieht `<a href="/impressum">` und sagt „erfüllt"; Playwright sieht die Verdeckung und sagt „nicht erfüllt".
|
||||||
|
|
||||||
|
**Kern-Regel der Architektur:** *Was im Text nicht beweisbar ist, gehört nicht in den Text-Check.* → route per `verification_method`. Sobald die Klassen sauber getrennt sind, sinken die FP fast automatisch (Impressum: SCOPE+JUDGE 171 → Text-Check-FP 0).
|
||||||
|
|
||||||
|
**Schema-Status:** `canonical_controls.verification_method` existiert (nur ~4 % befüllt, andere/gröbere Taxonomie document/code_review/tool/hybrid), `doc_check_controls` hat sie nicht. Die hier definierte Doc-Check-Routing-Achse ist **aus `control_classification` (artifact_type/obligation_type/check_intent) ableitbar** → kein Schema-Eingriff (eingefroren) nötig; als abgeleitetes Tag in `control_classification` führen.
|
||||||
|
|
||||||
|
## 5. Mess-Disziplin (prove-don't-handwave)
|
||||||
|
|
||||||
|
- GT mit dem stärksten Modell (`claude-opus-4-8`), nicht Haiku (zu lasch).
|
||||||
|
- Robust gegen LLM-Leerantworten: Retry + `INSUFFICIENT_EVIDENCE`/Eskalation statt FEHLT (ein realer Produktions-Bug, der die FP künstlich aufblähte).
|
||||||
|
- **Anti-Overfit:** Kriterien am Gesetz kalibrieren, dann auf *ungesehenen* Firmen gegenprüfen (DSE: 5 Original + 3 frische → stabile Zahlen = kein Overfit).
|
||||||
|
- OVH ist stochastisch (±~Rauschen je Lauf) und strenger als Opus → der Rest-FP konvergiert über Module auf **OVH-Über-Strenge**.
|
||||||
|
- **Zirkularitäts-Leitplanke:** Claude = Opus-GT-Modell → ein Claude-Tier-Sim misst die *Kaskaden-Reichweite* (erreicht Opus-Niveau), nicht eine unabhängige Validierung.
|
||||||
|
|
||||||
|
## 6. Offen (Reihenfolge)
|
||||||
|
|
||||||
|
1. **Claude-Tier-Sim (DSE + Cookie):** quantifiziert den verbleibenden **reinen** Judge-Fehler nach allen Katalog-Fixes — die letzte große unbekannte Variable. Erwartung: kleiner als roh, weil viel „Judge" sich als Katalog entpuppte.
|
||||||
|
2. **Impressum-Fix:** Rechtsform-Scope-Gate (#33) verdrahten + deterministischer Feld-Matcher + Re-Messung.
|
||||||
|
3. **Cookie Wave-2** (Cluster-E) + Produktions-Re-Route der 31 Banner-Controls (`control_classification`).
|
||||||
|
4. **Produktivschaltung** DSE + Cookie (zuletzt; verify-first DB-Write).
|
||||||
|
|
||||||
|
## 7. Artefakte
|
||||||
|
|
||||||
|
- DSE: `docs-src/development/dse_v1_validation.md`, `dse_criteria_changelog.json`/`dse_criteria_backup.json`.
|
||||||
|
- Cookie: `cookie_criteria_changelog.json`/`cookie_criteria_backup.json`/`cookie_best_practice.json` (Container `/tmp`), Cluster-Map.
|
||||||
|
- Impressum: `impressum_fp_by_cause.json` (SCOPE/JUDGE-Split).
|
||||||
|
- Gedächtnis: `project_engine_quality.md` (Detail je Modul). Werkzeuge: `cc_gt_opus_*`, `cc_engine_*`, `cc_*_candidates*` (alle macmini `/tmp`).
|
||||||
|
- **Alle Control-Änderungen nur auf macmini-dev**, versioniert, reversibel; Prod-Schaltung ausstehend.
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# RAG Re-Ingest-Spezifikation — Zitierfähige Chunks (Cross-Session-Vertrag)
|
||||||
|
|
||||||
|
> **Status:** v1 — Vertrag zwischen **core-Session** (Ingestion/Pipeline) und **compliance-Session** (Consumer: ai-sdk/Advisor).
|
||||||
|
> **Datum:** 2026-06-19
|
||||||
|
> **Ziel:** Chunks neu ingestieren mit sauberem Text + vollständigen **Legal-Metadaten**, damit Controls/Findings **zitierfähig** werden ("BDSG § 38 Abs. 1", "Art. 13 Abs. 1 lit. c DSGVO") — **ohne** die teuren Pass 0a/0b/Dedup.
|
||||||
|
|
||||||
|
## 0. Kernentscheidung (Frage 1): Re-Link statt Regenerieren
|
||||||
|
|
||||||
|
Controls hängen **an `control_uuid`, nicht an Chunks** (`canonical_controls` hat keine `chunk_id`/`chunk_hash`-Spalte; ebenso atom_classification, control_classification, doc_check_controls). **Ein Chunk-Re-Ingest bricht keine Control.** Das einzige stale-werdende Artefakt ist das Idempotenz-Ledger `compliance.canonical_processed_chunks` (Key `sha256(text)+collection+document_version`) — relevant nur für *zukünftige* Generierung.
|
||||||
|
|
||||||
|
→ **Pass 0a/0b/Dedup entfallen.** Stattdessen werden Controls per **Textabgleich** an die neuen Chunks **re-gelinkt** (Zitat-Anreicherung). Reichweite:
|
||||||
|
- **7 %** der 315.914 Controls haben `source_original_text` (Re-Link-Anker) → direkter Abgleich.
|
||||||
|
- **~93 % Atome** erben das Zitat über `parent_control_uuid`.
|
||||||
|
- **self-written** brauchen kein Chunk-Zitat (eigene Bibliothek).
|
||||||
|
- **unmatched Reste** → billiges per-Control-LLM-Zitat (Tier-3), keine Regenerierung.
|
||||||
|
|
||||||
|
## 1. Ingestion-Anforderungen (Frage 2)
|
||||||
|
|
||||||
|
1. **`chunk_strategy="legal"` EXPLIZIT setzen.** (Korrektur zur Historie: `recursive` aliased inzwischen auf `chunk_text_legal`, `embedding-service/main.py:1079/1093`, live-verifiziert — Upload mit `recursive` lieferte `section:"§ 38"`, `paragraph:"(1)"`, `paragraph_num:1`. Trotzdem `legal` explizit, nicht aufs Alias verlassen.)
|
||||||
|
2. **Deterministische Chunk-ID** (heute random) = `sha1(regulation_code|article|paragraph|chunk_index|document_version)` → stabiler Re-Link + Alt/Neu-Koexistenz.
|
||||||
|
3. **`chunk_hash` IN die Payload** schreiben (heute nur im PG-Ledger) = `sha256(normalisierter chunk_text)`.
|
||||||
|
4. **Echte `document_version`** (heute hardcoded `"1.0"`) → Re-Chunk kollidiert sonst im Ledger.
|
||||||
|
5. **Upload-before-delete** je Collection (alte Chunks erst nach Verify löschen).
|
||||||
|
|
||||||
|
## 2. PAYLOAD-FELD-VERTRAG (verbindlich — Consumer liest GENAU diese Namen)
|
||||||
|
|
||||||
|
Die ai-sdk/Advisor liest die **consumer-facing** Felder. Ingestion füllt alle.
|
||||||
|
|
||||||
|
| Feld | Typ | Consumer | Beschreibung / Beispiel |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `article_label` | string | **JA (Anzeige)** | **Fertig formatiert, direkt druckbar.** "BDSG § 38 Abs. 1" · "Art. 13 Abs. 1 lit. c DSGVO". Ingestion formatiert (kennt §- vs Art.-Stil). |
|
||||||
|
| `regulation_code` | string | **JA** | Kurzcode, UPPERCASE: `BDSG`,`DSGVO`,`TTDSG`,`DDG`,`CRA`,`NIS2` |
|
||||||
|
| `citation_style` | enum | **JA** | `paragraph` (§-Gesetze) \| `article` (EU-Verordnungen) — steuert §/Art.-Rendering, falls Consumer selbst formatiert |
|
||||||
|
| `article` | string | **JA** | bare Nummer: `"38"` bzw. `"13"` (egal ob § oder Art.) |
|
||||||
|
| `paragraph` | string | **JA** | bare Absatz: `"1"` (nicht `"(1)"`) |
|
||||||
|
| `sub` | string\|null | **JA** | feinste Granularität: `"lit. c"` · `"Satz 2"` · `"Nr. 3"` |
|
||||||
|
| `is_recital` | bool | **JA** | Erwägungsgrund vs operativer Artikel |
|
||||||
|
| `regulation_name` | string | optional | Volltext: "Bundesdatenschutzgesetz" |
|
||||||
|
| `page` | int\|null | optional | Seite (PDF-Quellen) |
|
||||||
|
| `chunk_text` | string | **JA** | sauberer Text (keine Soft-Hyphens/OCR-Reste) |
|
||||||
|
| `section_header` | string | optional | Kontext-Überschrift, **separat** (nicht inline im chunk_text) |
|
||||||
|
| `chunk_id` | string | — | deterministisch (s. §1.2) |
|
||||||
|
| `chunk_hash` | string | — | sha256(normalisierter Text) |
|
||||||
|
| `document_id`, `document_version`, `chunk_index` | — | — | Identität/Versionierung |
|
||||||
|
| `doc_type`, `use_case[]`, `source_type`, `license`, `bundesland`, `year` | — | Scope | Routing/Scope (source_type: gesetz/leitlinie/urteil) |
|
||||||
|
|
||||||
|
**Verbindlich:** `article_label` ist der bevorzugte Anzeige-Pfad (Ingestion ownt die Zitat-Formatierung, weil sie die Regulierung kennt). Die strukturierten Teile (`regulation_code`/`article`/`paragraph`/`sub`) sind zusätzlich für Filtern/Gruppieren da.
|
||||||
|
|
||||||
|
## 3. Normalisierung alt → neu (core-seitiger Transform)
|
||||||
|
|
||||||
|
Der legal-Chunker emittiert heute uneinheitlich; Ingestion normalisiert:
|
||||||
|
|
||||||
|
| heute (raw payload) | → neu |
|
||||||
|
|---|---|
|
||||||
|
| `section` = `"§ 38"` / `"Artikel 13"` | `citation_style` (§→`paragraph`, Art/Artikel→`article`) + `article`=`"38"`/`"13"` |
|
||||||
|
| `section_title` | `section_header` |
|
||||||
|
| `paragraph`=`"(1)"` / `paragraph_num`=1 | `paragraph`=`"1"` |
|
||||||
|
| (lit./Satz/Nr. aus `_PARAGRAPH_RE`) | `sub` (best-effort) |
|
||||||
|
| `regulation_id`/`regulation_short` (extra_metadata) | `regulation_code` (UPPERCASE) |
|
||||||
|
| — | `article_label` (formatiert aus regulation_code+style+article+paragraph+sub) |
|
||||||
|
|
||||||
|
`article_label`-Formatierung:
|
||||||
|
- `paragraph`-Stil: `"{regulation_code} § {article} Abs. {paragraph}"` (+ `" {sub}"`)
|
||||||
|
- `article`-Stil: `"Art. {article} Abs. {paragraph} {sub} {regulation_code}"`
|
||||||
|
|
||||||
|
## 4. Control-Re-Link (core)
|
||||||
|
|
||||||
|
Erweiterung von `control-pipeline/services/citation_backfill.py` (heute 3-Tier, Tier-1 = `sha256(source_original_text)` → Chunk-Hash-Index): Tier-1 bricht beim Re-Chunk (neuer Text → neuer Hash) → **Fuzzy/Embedding-Alignment** ergänzen (`source_original_text` ↔ neue Chunk-Texte, Substring + Cosine). Präzedenz: PDF-QA-Matcher (~52 % Trefferquote). Füllt `canonical_controls.source_citation = {regulation_code, article, paragraph, sub, page, source_type, license, url}`. Atome erben über `parent_control_uuid`. `doc_check_controls` re-derivieren danach automatisch zitierfähig (`derive_doc_check_controls.py` liest `source_citation->>'article'/'source'`).
|
||||||
|
|
||||||
|
**Beim künftigen Generieren IMMER `source_original_text` setzen** (warum heute nur 7 % re-linkbar sind).
|
||||||
|
|
||||||
|
## 5. Pipeline-Reihenfolge
|
||||||
|
|
||||||
|
1. Re-Ingest je Collection: `strategy="legal"`, deterministische IDs, neue `document_version`, normalisierte Payload + `chunk_hash`.
|
||||||
|
2. Verify: Zähler alt/neu + Stichprobe `article`/`paragraph`/`article_label` befüllt.
|
||||||
|
3. Re-Link (`citation_backfill` erweitert) → `source_citation`; Atome erben.
|
||||||
|
4. Reste → Tier-3-LLM-Zitat.
|
||||||
|
5. Alte Chunks löschen.
|
||||||
|
6. *(optional, nur für künftige Generierung)* Ledger `canonical_processed_chunks` neu aufbauen.
|
||||||
|
|
||||||
|
## 6. Arbeitsteilung + Akzeptanz
|
||||||
|
|
||||||
|
**core-Session:** Ingest-Spec umsetzen, `citation_backfill` Hash→Fuzzy→Embedding, Payload-Normalisierung (§3), deterministische IDs/`chunk_hash`/`document_version`, AGG-Lücke.
|
||||||
|
|
||||||
|
**compliance-Session (Consumer):** ai-sdk `legal_rag_client.go` + Advisor/Drafting auf die **§2-Feldnamen** ummappen (kein Deploy vor Pin — jetzt gepinnt), Prod-Qdrant-Verify, **6-Fragen-Re-Test** (sind §38 BDSG / AGG / CRA Art. 14 grounded zitiert?).
|
||||||
|
|
||||||
|
**Akzeptanzkriterium:** Advisor rendert für eine Beispielfrage eine echte Fundstelle aus `article_label` (z. B. "BDSG § 38 Abs. 1"), nicht nur "Quelle: BDSG".
|
||||||
|
|
||||||
|
## 7. Consumer-Detailabschnitt (compliance-Session — implementiert 2026-06-19)
|
||||||
|
|
||||||
|
Status: Code steht + getestet (17 Tests grün, tsc sauber). **Deploy gekoppelt an den Re-Ingest** (liest bis dahin leere Felder → graceful, Advisor zeigt wie heute "Quelle: BDSG").
|
||||||
|
|
||||||
|
### 7.1 ai-sdk: Payload → Response (`internal/ucca/legal_rag_client.go` `searchInternal`, Struct `legal_rag_types.go`)
|
||||||
|
|
||||||
|
| Response-Feld (JSON) | gelesen aus Payload | Fallback (alt-Korpus) |
|
||||||
|
|---|---|---|
|
||||||
|
| `article_label` | `article_label` | — (leer → Consumer baut selbst) |
|
||||||
|
| `regulation_code` | `regulation_code` | → `regulation_id` |
|
||||||
|
| `article` | `article` | → `section` ("§ 38") |
|
||||||
|
| `paragraph` | `paragraph` | — |
|
||||||
|
| `sub` | `sub` | — |
|
||||||
|
| `citation_style` | `citation_style` | — |
|
||||||
|
| `is_recital` | `is_recital` (bool) | — |
|
||||||
|
| `text` | `chunk_text` | — |
|
||||||
|
| `regulation_name` | `regulation_name_de` | — |
|
||||||
|
| `regulation_short` | `regulation_short` | — |
|
||||||
|
| `category`,`pages`,`source_url`,`score` | `category`,`pages`,`source`,(score) | — |
|
||||||
|
|
||||||
|
→ `/sdk/v1/rag/search` liefert diese Felder snake_case. Neuer Bool-Helfer `getBool` ergänzt.
|
||||||
|
|
||||||
|
### 7.2 Advisor + Drafting: Fundstellen-Format
|
||||||
|
|
||||||
|
Beide Konsumenten (`admin-compliance/lib/sdk/agents/advisor-rag.ts`, `.../drafting-engine/rag-query.ts`) bilden die Quellenzeile so:
|
||||||
|
|
||||||
|
```
|
||||||
|
source = article_label?.trim() // bevorzugt: druckbar aus Ingestion
|
||||||
|
|| [regulation_short|regulation_name|regulation_code, article, paragraph, sub]
|
||||||
|
.filter(Boolean).join(' ') // Fallback: strukturiert zusammensetzen
|
||||||
|
|| 'Unbekannt'
|
||||||
|
Ausgabe je Treffer: [Quelle N: {source}]\n{text}
|
||||||
|
```
|
||||||
|
|
||||||
|
→ Der Advisor **druckt `article_label` direkt** (kein §-vs-Art-Ableiten); `citation_style` nur nötig, falls wir später selbst formatieren. Erfüllt das Akzeptanzkriterium (§6): "BDSG § 38 Abs. 1" statt nur "Quelle: BDSG".
|
||||||
|
|
||||||
|
### 7.3 Deploy-Kopplung
|
||||||
|
Code ist additiv/safe (neue Felder leer bis Re-Ingest). **Kein Solo-Deploy** — geht mit dem Re-Ingest-Go-Live live, danach **6-Fragen-Re-Test** auf prod (§38/AGG/CRA Art. 14 grounded zitiert?).
|
||||||
|
|
||||||
|
## 8. Offen (core)
|
||||||
|
- **AGG-Lücke**: Quelle (§ 15 Abs. 4 — Bewerberdaten-Frist) zu spezifizieren + ingestieren.
|
||||||
|
- Soft-Hyphen/OCR-Normalisierung des `chunk_text` — Regelsatz definieren.
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# `verification_method` — die Prüfer-Routing-Achse
|
||||||
|
|
||||||
|
> **Status:** Architektur-Achse (2026-06-19), abgeleitet aus der 3-Modul-Kalibrierung (DSE / Cookie / Impressum).
|
||||||
|
> **Kernsatz:** *Nicht jede Compliance-Pflicht ist ein Textproblem.* `verification_method` sagt einem Control, **welcher Prüfer** zuständig ist — damit nicht alles am LLM hängt.
|
||||||
|
|
||||||
|
## 1. Warum diese Achse existiert
|
||||||
|
|
||||||
|
Die Kalibrierung von drei strukturell verschiedenen Dokumentklassen zeigte drei **verschiedene** dominante Fehlerursachen — und alle ließen sich auf die *Wahl des falschen Prüfers* zurückführen:
|
||||||
|
|
||||||
|
- **DSE** (Prosa): LLM-Urteil zu streng → Kriterien-Kalibrierung. Prüfer war richtig (LLM), Kriterien falsch.
|
||||||
|
- **Cookie** (Banner ≠ Richtlinie): Controls am falschen Artefakt geprüft → `artifact_type`-Re-Route.
|
||||||
|
- **Impressum** (Faktendokument): LLM verfehlt Felder, die *dastehen* (Adresse, HRB) → deterministischer Feld-Matcher schlägt den LLM. Und 5 Controls waren **gar nicht im Text beweisbar** (Erreichbarkeit/Verfügbarkeit) → gehören an Playwright, nicht an den Text-Check.
|
||||||
|
|
||||||
|
**Regel:** *Was im Text nicht beweisbar ist, gehört nicht in den Text-Check.* Sobald die Klassen sauber getrennt sind, sinken die False Positives fast automatisch (Beleg Impressum: SCOPE+JUDGE 171 Roh-FP → Text-Check-FP 0).
|
||||||
|
|
||||||
|
## 2. Die fünf Klassen
|
||||||
|
|
||||||
|
| `verification_method` | Prüfer | Leitfrage | Beispiel | Reifegrad |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **CONTENT** | Embedding-Recall + LLM-Kaskade (OVH→Claude) | Was steht da? | DSE nennt Verarbeitungszwecke; Cookie-Richtlinie | DSE/Cookie kalibriert |
|
||||||
|
| **FIELD** | Regex / Parser (Feldmatrix) | Welche Felder existieren + sind valide? | HRB, USt-IdNr, Anschrift, E-Mail+Telefon | Impressum-Fakten ✓ (FP 0 %) |
|
||||||
|
| **PRESENTATION** | Playwright (Rendering-Sensor) | Ist es auffindbar / wahrnehmbar / erreichbar? | Impressum „leicht erkennbar", ständig verfügbar, Footer nicht verdeckt | Re-Route gemacht, Checker offen |
|
||||||
|
| **BEHAVIOR** | Playwright + API (Interaktion) | Manipuliert die UI die Entscheidung? | Reject = Accept, Cookies VOR Consent, Dark Pattern | Cookie-Banner-Matrix vorhanden |
|
||||||
|
| **PROCESS** | Audit / Nachweis | Gibt es einen internen Nachweis? | VVT, interne Richtlinie, Audit-Entscheidung | Org-Checkliste |
|
||||||
|
|
||||||
|
## 3. PRESENTATION ≠ BEHAVIOR
|
||||||
|
|
||||||
|
Beide nutzen Playwright, prüfen aber **verschiedene Rechtslogik** — getrennt halten:
|
||||||
|
|
||||||
|
- **PRESENTATION** = Auffindbarkeit / Sichtbarkeit / Zugänglichkeit. Beispiel: Impressum-Link erreichbar, nicht in 4px-Schrift, nicht hinter `display:none`, nicht dauerhaft vom Cookie-Layer verdeckt.
|
||||||
|
- **BEHAVIOR** = Entscheidungs-Manipulation / Dark-Pattern. Beispiel: „Ablehnen" versteckt, Vorauswahl gesetzt, Consent technisch ignoriert.
|
||||||
|
|
||||||
|
## 4. Playwright als Compliance-Sensor (nicht Crawler)
|
||||||
|
|
||||||
|
Playwright prüft, was **kein** LLM kann: Der LLM sieht `<a href="/impressum">` und urteilt „erfüllt"; der Sensor sieht, dass das Element verdeckt / unsichtbar / unerreichbar ist und urteilt „nicht erfüllt". Drei technische Prüfer langfristig:
|
||||||
|
|
||||||
|
- **Content-Checker** → LLM (CONTENT)
|
||||||
|
- **Structure-Checker** → Regex/Parser (FIELD)
|
||||||
|
- **Presentation-Checker** → Playwright (PRESENTATION + BEHAVIOR)
|
||||||
|
|
||||||
|
## 5. Schema-Status & Verortung
|
||||||
|
|
||||||
|
- `canonical_controls.verification_method` **existiert**, aber nur ~4 % befüllt und mit *anderer*, gröberer Taxonomie (`document` / `code_review` / `tool` / `hybrid` — generische Enterprise-Verifikation, nicht das Doc-Check-Routing).
|
||||||
|
- `doc_check_controls` hat **keine** `verification_method`-Spalte.
|
||||||
|
- → Die hier definierte Doc-Check-Routing-Achse ist **neu**, aber **ableitbar** aus den schon vorhandenen `control_classification`-Achsen (`artifact_type` / `obligation_type` / `check_intent`). **Kein** Schema-Eingriff nötig (DB ist eingefroren) — als abgeleitetes Tag in `control_classification` führen.
|
||||||
|
|
||||||
|
Heuristik für die Ableitung (Startpunkt, nicht final):
|
||||||
|
|
||||||
|
| Signal | → verification_method |
|
||||||
|
|---|---|
|
||||||
|
| `artifact_type = COOKIE_BANNER`, Interaktionspflicht | BEHAVIOR |
|
||||||
|
| Pflicht zu Erreichbarkeit / Sichtbarkeit / „ständig verfügbar" | PRESENTATION |
|
||||||
|
| Faktenfeld (Anschrift, Register, Kennung) | FIELD |
|
||||||
|
| `obligation_type` Prozess / Nachweis ohne Außenwirkung | PROCESS |
|
||||||
|
| sonst (inhaltliche Offenlegung in Prosa) | CONTENT |
|
||||||
|
|
||||||
|
## 6. Warum das über die 3 Module hinaus zählt
|
||||||
|
|
||||||
|
Für die nächsten Module (CRA, Maschinenverordnung, NIS2, TISAX, ISO 27001) ist diese Achse vermutlich fast so wichtig wie `artifact_type`: viele dieser Pflichten sind **PROCESS** oder **BEHAVIOR**, kein Textinhalt. Wer sie an den LLM-Text-Check hängt, erzeugt systematische False Positives. Das ist die eigentliche Erkenntnis der Kalibrierung: **nicht** dass DSE/Cookie/Impressum funktionieren, sondern dass klar wurde, *welcher Prüfer für welche Art von Pflicht zuständig ist*.
|
||||||
Reference in New Issue
Block a user