feat: Control Library UI, dedup migration, QA tooling, docs
Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Failing after 31s
CI/CD / test-python-backend-compliance (push) Successful in 1m35s
CI/CD / test-python-document-crawler (push) Successful in 20s
CI/CD / test-python-dsms-gateway (push) Successful in 17s
CI/CD / validate-canonical-controls (push) Successful in 10s
CI/CD / Deploy (push) Has been skipped
Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Failing after 31s
CI/CD / test-python-backend-compliance (push) Successful in 1m35s
CI/CD / test-python-document-crawler (push) Successful in 20s
CI/CD / test-python-dsms-gateway (push) Successful in 17s
CI/CD / validate-canonical-controls (push) Successful in 10s
CI/CD / Deploy (push) Has been skipped
- Control Library: parent control display, ObligationTypeBadge, GenerationStrategyBadge variants, evidence string fallback - API: expose parent_control_uuid/id/title in canonical controls - Fix: DSFA SQLAlchemy 2.0 Row._mapping compatibility - Migration 074: control_parent_links + control_dedup_reviews tables - QA scripts: benchmark, gap analysis, OSCAL import, OWASP cleanup, phase5 normalize, phase74 gap fill, sync_db, run_job - Docs: dedup engine, RAG benchmark, lessons learned, pipeline docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,23 @@
|
||||
|
||||
## Übersicht
|
||||
|
||||
Die Control Quality Pipeline prüft und verbessert die ~9.000 Canonical Controls der Compliance-Bibliothek. Sie nutzt **PDF-basierte Verifizierung** als Ground Truth — jeder Control-Originaltext wird direkt im Quelldokument (PDF) lokalisiert.
|
||||
Die Control Quality Pipeline prüft und verbessert die Canonical Controls der Compliance-Bibliothek. Sie nutzt **PDF-basierte Verifizierung** als Ground Truth — jeder Control-Originaltext wird direkt im Quelldokument (PDF) lokalisiert.
|
||||
|
||||
Alle Scripts liegen in **`scripts/qa/`**. Starten auf dem Mac Mini via Runner-Script:
|
||||
|
||||
```bash
|
||||
# Job starten (laedt .env automatisch, PID-Lock, unbuffered output)
|
||||
ssh macmini "bash ~/Projekte/breakpilot-compliance/scripts/qa/run_job.sh <script.py> [args...]"
|
||||
|
||||
# Status aller Jobs
|
||||
ssh macmini "bash ~/Projekte/breakpilot-compliance/scripts/qa/run_job.sh --status"
|
||||
|
||||
# Log ansehen
|
||||
ssh macmini "bash ~/Projekte/breakpilot-compliance/scripts/qa/run_job.sh --log <script.py>"
|
||||
|
||||
# Job stoppen
|
||||
ssh macmini "bash ~/Projekte/breakpilot-compliance/scripts/qa/run_job.sh --kill <script.py>"
|
||||
```
|
||||
|
||||
## Architektur
|
||||
|
||||
@@ -55,20 +71,24 @@ Jeder Control hat ein Feld `source_original_text` — der Chunk-Text aus dem Que
|
||||
|
||||
| Metrik | Wert |
|
||||
|---|---|
|
||||
| Controls mit source_original_text | 7.943 |
|
||||
| Im PDF lokalisiert | **6.259 (79%)** |
|
||||
| Nicht gefunden (Sprachmismatch) | 1.651 |
|
||||
| Kein PDF vorhanden | 33 |
|
||||
| 100% Match-Rate | 19 Regulations (inkl. DSGVO, KI-VO, NIS2, NIST 800-53) |
|
||||
| Controls mit source_original_text | 5.751 (86%) |
|
||||
| Im PDF lokalisiert | **5.063 (88%)** |
|
||||
| Nicht gefunden | 649 |
|
||||
| Kein PDF vorhanden | 29 |
|
||||
| Recital_suspect markiert | 648 |
|
||||
| 100% Match-Rate | 20+ Regulations (inkl. DSGVO, KI-VO, NIS2, NIST 800-53, Blue Guide) |
|
||||
|
||||
**Verlauf:** v1 (4.110, 52%) → v2 (6.091, 77%) → v3 (6.259, 79%) → v4 +Blue Guide EN (6.803, 86%) → v5 nach Cleanup (5.063/5.741, 88%)
|
||||
|
||||
### Nicht-matchende Controls
|
||||
|
||||
| Ursache | Controls | Erklärung |
|
||||
| Ursache | Controls | Status |
|
||||
|---|---|---|
|
||||
| Blue Guide EN vs. DE PDF | ~562 | Controls aus englischem PDF, wir haben nur deutsches |
|
||||
| OWASP multilingual | ~632 | Controls aus PT/AR/ID/ES-Übersetzungen |
|
||||
| ~~Blue Guide EN vs. DE PDF~~ | ~~562~~ | ✅ Gelöst — EN-PDF beschafft, 544/544 gematcht |
|
||||
| ~~OWASP Top 10 multilingual~~ | ~~324~~ | ✅ Als duplicate markiert — Übersetzungen ohne Mehrwert |
|
||||
| CRA Encoding | ~76 | PDF-Ligaturen/Sonderzeichen-Differenzen |
|
||||
| CISA Secure by Design | ~113 | Falsches PDF (ENISA statt CISA) |
|
||||
| OWASP ASVS | ~173 | PDF-Matching-Problem (meist EN) |
|
||||
|
||||
## Brute-Force-Suche
|
||||
|
||||
@@ -100,34 +120,276 @@ Controls aus Erwägungsgründen (`article_type = preamble`) sind **kein Nachteil
|
||||
|
||||
Die 1.195 v1-Controls **ohne** Originaltext sind manuell erstellt (`strategy=ungrouped`) und haben keine Chunk-Referenz.
|
||||
|
||||
## DB-Status (Stand 2026-03-20)
|
||||
## OWASP Cleanup (2026-03-20)
|
||||
|
||||
- **324 OWASP Top 10 multilingual Controls** → `duplicate` markiert (ZH, AR, ID, FR, ES, PT — Übersetzungen derselben 10 Kategorien)
|
||||
- **47 Controls** mit falscher Quellenzuordnung korrigiert (z.B. als "OWASP Top 10" getaggt, aber tatsächlich aus ASVS/SAMM/API/MASVS)
|
||||
- **~200 OWASP ASVS/SAMM/MASVS EN Controls** behalten — unique Content aus GitHub/Website, nicht im PDF auffindbar
|
||||
|
||||
## NIST OSCAL Import (2026-03-20)
|
||||
|
||||
**776 neue Controls** aus NIST SP 800-53 Rev 5 OSCAL (Public Domain, maschinenlesbar):
|
||||
|
||||
- Quelle: `usnistgov/oscal-content` (JSON Catalog)
|
||||
- Vor allem **Control Enhancements** (z.B. AC-2(3), SC-7(8)) — die atomaren Unteranforderungen
|
||||
- Jeder Control enthält: Statement + Guidance + Assessment-Methoden + Cross-References + Parameters
|
||||
- `pipeline_version = 4`, `generation_strategy = 'oscal_import'`
|
||||
- Kein Pass 0a/0b nötig — Controls sind **bereits atomar**
|
||||
|
||||
| Metrik | Vorher | Nachher |
|
||||
|---|---|---|
|
||||
| SP 800-53 Controls (aktiv) | 1.107 | **1.883** |
|
||||
| OSCAL-Abdeckung | 238/1.014 (23%) | **1.014/1.014 (100%)** |
|
||||
|
||||
## Phase 5: RAG-Deduplizierung + Normalisierung (2026-03-20)
|
||||
|
||||
### Durchgeführte Schritte
|
||||
|
||||
| Schritt | Beschreibung | Controls |
|
||||
|---|---|---|
|
||||
| 5.1 | OSCAL Controls: `source_regulation` in generation_metadata gesetzt | 776 |
|
||||
| 5.2 | v3 Controls ohne Source → `needs_review` mit `missing_source` Flag | 20 |
|
||||
| 5.3 | Leerer Source-Name korrigiert (AT TKG) | 1 |
|
||||
| 5.4 | OWASP regulation_code Fehlzuordnungen korrigiert | 47 |
|
||||
| 5.5 | **duplicate/too_close Controls hart gelöscht** | **3.301** |
|
||||
| 5.6 | Processed Chunks bereinigt (gelöschte Control-IDs entfernt) | 2.520 |
|
||||
|
||||
### Ergebnis
|
||||
|
||||
- **Vorher:** 9.936 Controls (6.635 aktiv, 2.998 duplicate, 303 too_close)
|
||||
- **Nachher:** 6.635 Controls, **alle aktiv** (0 duplicate/too_close)
|
||||
- Alle regulation_codes haben jetzt einheitliche Source-Namen
|
||||
- OWASP-Controls sind korrekt ihren Quellen zugeordnet
|
||||
|
||||
## DB-Status (Stand 2026-03-20, nach Phase 7.4)
|
||||
|
||||
| release_state | Count |
|
||||
|---|---|
|
||||
| draft | 5.365 |
|
||||
| needs_review | 818 |
|
||||
| duplicate | 2.674 |
|
||||
| too_close | 303 |
|
||||
| **Aktiv** | **6.183** |
|
||||
| draft | ~6.030 |
|
||||
| needs_review | 838 |
|
||||
| **Gesamt** | **6.868** |
|
||||
|
||||
## Scripts
|
||||
## Scripts (`scripts/qa/`)
|
||||
|
||||
Alle QA-Scripts liegen in `scripts/qa/`:
|
||||
### Kern-QA (PDF-Matching)
|
||||
|
||||
| Script | Beschreibung |
|
||||
|---|---|
|
||||
| `pdf_qa_all.py` | Haupt-QA: Controls gegen PDFs matchen |
|
||||
| `pdf_qa_inventory.py` | Inventar: Regulations, Controls, PDFs |
|
||||
| `apply_pdf_qa_results.py` | Ergebnisse in DB schreiben |
|
||||
| `preamble_dedup.py` | Preamble vs. Artikel Duplikat-Erkennung |
|
||||
| `qa_dedup_controls.py` | Jaccard-basierte Titel-Dedup |
|
||||
| `qa_normalize_sources.py` | Source-Namen normalisieren |
|
||||
| `db_status.py` | DB-Status-Übersicht |
|
||||
| `pdf_qa_all.py` | **Haupt-QA**: Controls gegen PDFs matchen, Artikel-Index aufbauen. Enthaelt `SOURCE_FILE_MAP`, alle Index-Builder (EU, DE, NIST, OWASP, generic). 526 Zeilen. |
|
||||
| `pdf_qa_inventory.py` | Inventar: Welche Regulations haben Controls, wie viele, welche PDFs existieren |
|
||||
| `apply_pdf_qa_results.py` | Ergebnisse aus `pdf_qa_all.py` in DB schreiben (`article_type`, `recital_suspect`) |
|
||||
| `pdf_article_lookup_poc.py` | POC: Control-Text in PDF lokalisieren, Headings von Cross-Refs unterscheiden |
|
||||
|
||||
## Nächste Schritte
|
||||
### Lueckenanalyse + Control-Generierung
|
||||
|
||||
1. **Blue Guide EN-PDF** beschaffen → +562 Controls matchen
|
||||
2. **CISA Secure by Design** echtes PDF finden → +113 Controls
|
||||
3. **Brute-Force Ergebnisse anwenden** — 44 falsche Source-Zuordnungen korrigieren
|
||||
4. **Frontend-Anzeige** — `article_type` im Control-Detail anzeigen
|
||||
5. **Continuous QA** — Bei neuen Controls automatisch PDF-Match prüfen
|
||||
| Script | Beschreibung |
|
||||
|---|---|
|
||||
| `gap_analysis.py` | **Phase 7.3**: Artikel im PDF vs. Controls in DB vergleichen, Luecken identifizieren |
|
||||
| `phase74_generate_gap_controls.py` | **Phase 7.4**: Neue Controls fuer Luecken via Anthropic API generieren. `pipeline_version=5`. 624 Zeilen. |
|
||||
| `benchmark_llm_controls.py` | LLM-Vergleich: gpt-oss-120b vs. Claude Sonnet fuer Control-Generierung |
|
||||
| `test_pass0a.py` | **Pass 0a Test**: Obligation Extraction + 3-Tier-Klassifizierung (Pflicht/Empfehlung/Kann). Standalone, speichert JSON. |
|
||||
|
||||
### Deduplizierung + Normalisierung
|
||||
|
||||
| Script | Beschreibung |
|
||||
|---|---|
|
||||
| `preamble_dedup.py` | Preamble vs. Artikel Duplikat-Erkennung (Jaccard >= 0.40) |
|
||||
| `qa_dedup_controls.py` | Jaccard-basierte Titel-Deduplizierung |
|
||||
| `qa_apply_and_dedup.py` | Ergebnisse anwenden + Duplikate in einem Schritt markieren |
|
||||
| `qa_normalize_sources.py` | Source-Namen normalisieren (kanonische Namen) |
|
||||
| `phase5_normalize_and_cleanup.py` | **Phase 5**: Normalisierung + 3.301 Duplikate hart loeschen |
|
||||
| `qa_delete_gpsr_dupe.py` | GPSR-Duplikate loeschen |
|
||||
| `delete_gpsr_prod.py` | GPSR-Duplikate aus Production-Qdrant entfernen |
|
||||
|
||||
### Quellen-spezifische Scripts
|
||||
|
||||
| Script | Beschreibung |
|
||||
|---|---|
|
||||
| `blue_guide_en_match.py` | Blue Guide EN-PDF matchen (544/544 Erfolg) |
|
||||
| `owasp_cleanup.py` | OWASP multilingual Cleanup (324 Duplikate) + Source-Fix (47 korrigiert) |
|
||||
| `owasp_github_match.py` | OWASP ASVS/SAMM/MASVS gegen GitHub-Markdown matchen |
|
||||
| `oscal_import.py` | NIST OSCAL Import (776 Controls aus JSON Catalog) |
|
||||
| `oscal_analysis.py` | NIST OSCAL Analyse: Abdeckung, fehlende Controls |
|
||||
|
||||
### Diagnose + Utilities
|
||||
|
||||
| Script | Beschreibung |
|
||||
|---|---|
|
||||
| `db_status.py` | DB-Status: release_state Counts, pipeline_version, source Verteilung |
|
||||
| `debug_low_match.py` | Debugging: Warum matchen Blue Guide / OWASP / CISA schlecht? |
|
||||
| `qa_article_map_all_chunks.py` | Alle Chunks Artikel-Nummern zuordnen (Bulk) |
|
||||
| `backfill_job_66228863.py` | Einmaliger Backfill-Job |
|
||||
| `sync_controls_to_prod.py` | Controls von Dev nach Production synchronisieren |
|
||||
|
||||
### Runner
|
||||
|
||||
| Script | Beschreibung |
|
||||
|---|---|
|
||||
| `run_job.sh` | **Job-Runner**: Laedt `.env`, PID-Lock, Monitoring (`--status`, `--log`, `--kill`) |
|
||||
|
||||
## Phase 7: PDF-Validierung + Enrichment (2026-03-20)
|
||||
|
||||
### 7.1 + 7.2: Controls gegen PDFs validiert + Ergebnisse angewendet ✅
|
||||
|
||||
- 5.063 Controls erfolgreich im Original-PDF lokalisiert (88%)
|
||||
- `article_type` fuer alle gematchten Controls gesetzt
|
||||
- 648 Preamble-Controls als `recital_suspect` in `generation_metadata` markiert
|
||||
- 332 Controls nicht matchbar (OWASP ASVS 132, CISA 72, ENISA 38, OWASP SAMM 31, CRA 28)
|
||||
|
||||
### 7.3: Lueckenanalyse ✅
|
||||
|
||||
**494 Artikel-Luecken** in 15 Quellen identifiziert. Geschaetzt ~300 davon actionable.
|
||||
|
||||
| Source | Luecken | Coverage | Bemerkung |
|
||||
|---|---:|---:|---|
|
||||
| AML-Verordnung | 91 | 5% | Kaum ingestiert |
|
||||
| MiCA | 71 | 52% | Grosse Verordnung |
|
||||
| NIST SP 800-53 | 59 | 83% | Meist Section-Header, nur SA-15 fehlt |
|
||||
| OWASP ASVS 4.0 | 47 | 35% | Requirement-Gruppen fehlen |
|
||||
| Batterieverordnung | 41 | 58% | |
|
||||
| DSGVO | 35 | 65% | Einige Governance/Aufsicht-Artikel |
|
||||
| ENISA ICS/SCADA | 34 | 31% | |
|
||||
| ENISA Supply Chain | 26 | 7% | |
|
||||
| CRA | 23 | 68% | |
|
||||
| NIS2 | 16 | 65% | |
|
||||
| KI-Verordnung | 15 | 87% | Fast komplett |
|
||||
| Maschinenverordnung | 5 | 91% | Fast komplett |
|
||||
|
||||
### 7.4: Neue Controls fuer Luecken generieren ✅ (2026-03-20)
|
||||
|
||||
Script: `phase74_generate_gap_controls.py --resume`
|
||||
|
||||
- **494 Artikel-Luecken** in 15 Quellen → Anthropic Claude Sonnet 4.6
|
||||
- `pipeline_version = 5`, `generation_strategy = 'phase74_gap_fill'`
|
||||
- Direkt PDF-Text als Input (nicht RAG-Chunks)
|
||||
- Starten via: `run_job.sh phase74_generate_gap_controls.py --resume`
|
||||
|
||||
**Ergebnis:**
|
||||
|
||||
| Source | Luecken | Generiert |
|
||||
|---|---:|---:|
|
||||
| AML-Verordnung | 91 | 97 |
|
||||
| MiCA | 71 | 68 |
|
||||
| NIST SP 800-53 | 59 | 19 |
|
||||
| KI-Verordnung | 15 | 15 |
|
||||
| OWASP ASVS 4.0 | 47 | 11 |
|
||||
| Batterieverordnung | 41 | 9 |
|
||||
| DSGVO | 35 | 4 |
|
||||
| OWASP Top 10 | 12 | 3 |
|
||||
| NIS2 | 16 | 3 |
|
||||
| CRA | 23 | 3 |
|
||||
| OECD KI-Empfehlung | 4 | 1 |
|
||||
| **Gesamt** | **494** | **233** |
|
||||
|
||||
Nicht generiert: 75 zu kurzer Text, 29 NIST-Intros, 11 Parse-Errors, 162 ID-Konflikte (COMP-1000 etc.).
|
||||
API-Kosten: ~$7,55 (109 min Laufzeit).
|
||||
|
||||
## Pass 0a: Obligation Extraction — 3-Tier-Klassifizierung
|
||||
|
||||
### Konzept
|
||||
|
||||
Pass 0a zerlegt Rich Controls (~6.000) in **atomare Obligations** per LLM (Claude Sonnet 4.6).
|
||||
Jede Obligation wird durch den **Quality Gate** klassifiziert — nicht gefiltert:
|
||||
|
||||
| obligation_type | Signal | Beispiel |
|
||||
|---|---|---|
|
||||
| **pflicht** | müssen, muss, ist zu, hat zu, shall, must, required | "Der Betreiber muss alle Daten verschluesseln" |
|
||||
| **empfehlung** | soll, sollen, should, sicherstellen, gewaehrleisten, dokumentieren | "Der Betreiber soll regelmaessige Audits durchfuehren" |
|
||||
| **kann** | kann, koennen, darf, duerfen, may, optional | "Der Betreiber kann zusaetzliche Massnahmen ergreifen" |
|
||||
|
||||
**Wichtig:** Nichts wird mehr rejected wegen fehlendem normativem Signal. Obligations ohne Signal werden als `empfehlung` klassifiziert. Rejected werden nur noch: Evidence-Only, zu kurz (<20 Zeichen), fehlender Parent-Link.
|
||||
|
||||
### Warum auch Empfehlungen behalten?
|
||||
|
||||
Empfehlungen helfen Firmen, ihre Systeme sicherer zu machen — ueber das Pflichtprogramm hinaus. Im Frontend erhalten Kunden einen Marker, der klar anzeigt:
|
||||
|
||||
- **Pflicht** = gesetzlich/regulatorisch vorgeschrieben
|
||||
- **Empfehlung** = Best Practice, freiwillig, aber wertvoll
|
||||
- **Kann** = optional, weitergehende Massnahme
|
||||
|
||||
### Quality Gate — Kritische Flags
|
||||
|
||||
| Flag | Kritisch? | Beschreibung |
|
||||
|---|---|---|
|
||||
| `has_normative_signal` | Nein | Informativer Check, kein Ablehnungsgrund |
|
||||
| `obligation_type` | — | Klassifizierung (pflicht/empfehlung/kann) |
|
||||
| `not_evidence_only` | **Ja** | Kein reiner Nachweis-Eintrag |
|
||||
| `min_length` | **Ja** | Mindestens 20 Zeichen |
|
||||
| `has_parent_link` | **Ja** | Verbindung zum Parent-Control |
|
||||
| `single_action` | Nein | Nur ein Hauptverb (heuristisch) |
|
||||
| `not_rationale` | Nein | Keine reine Begruendung |
|
||||
|
||||
### Normative Signal Detection — Regex-Tiers
|
||||
|
||||
```
|
||||
Tier 1 (Pflicht): muessen, muss, ist/sind/hat/haben zu + Infinitiv,
|
||||
Compound-Verben (festzustellen, vorzunehmen),
|
||||
Gerundivum (mitzuteilen, bereitzustellen),
|
||||
shall, must, required
|
||||
|
||||
Tier 2 (Empfehlung): soll, sollen, sollte, sollten,
|
||||
gewaehrleisten, sicherstellen,
|
||||
should, ensure, recommend,
|
||||
dokumentieren, implementieren, ueberpruefen
|
||||
|
||||
Tier 3 (Kann): kann, koennen, darf, duerfen, may, optional
|
||||
```
|
||||
|
||||
### Testergebnisse (3 Iterationen, 2026-03-20)
|
||||
|
||||
| Run | Controls | Obligations | Validated | Rejected | Kosten |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| 1 (v0 Regex) | 10 | ~100 | 68% | 32% | $0,28 |
|
||||
| 2 (v1 Regex) | 50 | ~530 | 78% | 22% | $1,43 |
|
||||
| 3 (v2 Regex) | 50 | ~530 | 86% | 14% | $1,44 |
|
||||
| 4 (3-Tier) | 60 | — | — | — | — |
|
||||
|
||||
Run 4 laeuft mit dem neuen Klassifizierer — statt PASS/REJECT wird jetzt PFLICHT/EMPFEHLUNG/KANN ausgegeben.
|
||||
|
||||
### Scripts
|
||||
|
||||
| Script | Beschreibung |
|
||||
|---|---|
|
||||
| `test_pass0a.py` | **Test-Script**: Standalone (kein SQLAlchemy), psycopg2 + Anthropic API. Speichert Ergebnisse als JSON. |
|
||||
|
||||
```bash
|
||||
# Test mit 10 Controls
|
||||
run_job.sh test_pass0a.py --limit 10
|
||||
|
||||
# Test mit bestimmter Quelle
|
||||
run_job.sh test_pass0a.py --limit 20 --source "DSGVO"
|
||||
|
||||
# Ergebnisse: /tmp/pass0a_results_<N>controls.json
|
||||
```
|
||||
|
||||
### Backend-Code
|
||||
|
||||
- **Klassifizierung:** `backend-compliance/compliance/services/decomposition_pass.py`
|
||||
- `classify_obligation_type()` — 3-Tier-Klassifizierung
|
||||
- `quality_gate()` — gibt `obligation_type` in Flags zurueck
|
||||
- `passes_quality_gate()` — `has_normative_signal` nicht mehr kritisch
|
||||
- `ObligationCandidate.obligation_type` — neues Feld
|
||||
|
||||
### Hochrechnung (basierend auf 50-Control-Runs)
|
||||
|
||||
| Metrik | Wert |
|
||||
|---|---|
|
||||
| Kosten pro Control | ~$0,029 |
|
||||
| Kosten fuer ~6.000 Controls | **~$172** |
|
||||
| Laufzeit (geschaetzt) | ~25h |
|
||||
| Obligations pro Control | ~10,5 |
|
||||
|
||||
---
|
||||
|
||||
## Naechste Schritte
|
||||
|
||||
1. ~~**Phase 5 Cleanup** → 3.301 Duplikate geloescht, Source normalisiert~~ ✅
|
||||
2. ~~**Phase 6 Pipeline-Haertung** → Source aus REGULATION_LICENSE_MAP~~ ✅
|
||||
3. ~~**Phase 7.1-7.3** → PDF-Validierung + Enrichment + Lueckenanalyse~~ ✅
|
||||
4. ~~**Phase 7.4** → 233 neue Controls fuer Luecken generiert ($7,55)~~ ✅
|
||||
5. **Pass 0a** → Obligation Extraction mit 3-Tier-Klassifizierung (Tests laufen, ~$172)
|
||||
6. **Pass 0b** → Atomic Control Composition aus validierten Obligations
|
||||
7. **Pass 1-5** → Multi-Layer Migration (Code + 500 Tests bereits vorhanden)
|
||||
8. **Phase 8** → Qdrant Re-Ingestion (Runtime-Betrieb, ZULETZT)
|
||||
9. **needs_review Triage** — 838 Controls klassifizieren
|
||||
10. **Frontend** — `obligation_type` (Pflicht/Empfehlung/Kann) + `article_type` anzeigen
|
||||
|
||||
206
docs-src/development/rag-pipeline-benchmark.md
Normal file
206
docs-src/development/rag-pipeline-benchmark.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# RAG Pipeline Benchmark & Optimierungen
|
||||
|
||||
Stand: 2026-03-21. Vergleich unserer Implementierung mit State of the Art. Priorisierte Empfehlungen nach Impact/Effort.
|
||||
|
||||
---
|
||||
|
||||
## Aktuelle Pipeline (Ist-Zustand)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Dokumente] -->|Document Crawler| B[Chunks 512/50]
|
||||
B -->|bge-m3| C[Qdrant Dense]
|
||||
C -->|Cosine Search| D[Control Generator v2]
|
||||
D -->|LLM| E[Rich Controls 6.373]
|
||||
E -->|Pass 0a| F[Obligations]
|
||||
F -->|Pass 0b| G[Atomare Controls]
|
||||
G -->|4-Stage Dedup| H[Master Controls ~18K]
|
||||
```
|
||||
|
||||
| Komponente | Implementierung | SOTA-Bewertung |
|
||||
|-----------|----------------|----------------|
|
||||
| **Chunking** | Rekursiv, 512 Zeichen, 50 Overlap | Zu klein fuer Rechtstexte |
|
||||
| **Embedding** | bge-m3 (1024-dim, Ollama) | Gut, aber nur Dense genutzt |
|
||||
| **Vector DB** | Qdrant mit Payload-Filtering | Hybrid Search nicht aktiviert |
|
||||
| **Retrieval** | Pure Dense Cosine Similarity | Kein Re-Ranking, kein BM25 |
|
||||
| **Extraktion** | 3-Tier (Exact → Embedding → LLM) | Solide Architektur |
|
||||
| **Dedup** | 4-Stage (Pattern → Action → Object → Embedding) | Ueberdurchschnittlich |
|
||||
| **QA** | 5-Metrik Similarity + PDF-QA Matching | Gut, RAGAS fehlt |
|
||||
|
||||
---
|
||||
|
||||
## Tier 1: Quick Wins (Tage, nicht Wochen)
|
||||
|
||||
### 1. Chunk-Groesse erhoehen: 512 → 1024, Overlap 50 → 128
|
||||
|
||||
**Problem:** NAACL 2025 Vectara-Studie zeigt: fuer analytische/juristische Queries sind 512-1024 Token optimal. Unsere 512-Zeichen-Chunks (= ~128 Token) sind deutlich zu klein.
|
||||
|
||||
**Unsere Lessons Learned:** "Chunks werden mitten im Absatz abgeschnitten. Artikel- und Paragraphennummern fehlen."
|
||||
|
||||
**Aenderung:** Config-Parameter in `ingest-phase-h.sh` anpassen.
|
||||
|
||||
| Metrik | Vorher | Nachher |
|
||||
|--------|--------|---------|
|
||||
| Chunk Size | 512 chars (~128 Token) | 1024 chars (~256 Token) |
|
||||
| Overlap | 50 chars (10%) | 128 chars (12.5%) |
|
||||
|
||||
**Impact:** HOCH | **Effort:** NIEDRIG
|
||||
|
||||
### 2. Ollama JSON-Mode fuer Obligation Extraction
|
||||
|
||||
**Problem:** `_parse_json` in `decomposition_pass.py` hat Regex-Fallback — das zeigt, dass LLM-Output nicht zuverlaessig JSON ist.
|
||||
|
||||
**Aenderung:** `format: "json"` in Ollama-API-Calls setzen.
|
||||
|
||||
**Impact:** MITTEL | **Effort:** NIEDRIG (1 Parameter)
|
||||
|
||||
### 3. Chain-of-Thought Prompting fuer Pass 0a/0b
|
||||
|
||||
**Problem:** LegalGPT-Framework zeigt: explizite Reasoning-Chains ("Erst Addressat identifizieren, dann Aktion, dann normative Staerke") verbessern Extraktionsqualitaet signifikant.
|
||||
|
||||
**Impact:** MITTEL | **Effort:** NIEDRIG (Prompt Engineering)
|
||||
|
||||
---
|
||||
|
||||
## Tier 2: High Impact, Medium Effort (1-2 Wochen)
|
||||
|
||||
### 4. Hybrid Search (Dense + Sparse) via Qdrant
|
||||
|
||||
**Problem:** Reine Dense-Suche. Juristische Queries enthalten spezifische Begriffe ("DSGVO Art. 35", "Abs. 3"), die BM25/Sparse besser findet.
|
||||
|
||||
**Loesungsansatz:** BGE-M3 generiert bereits Sparse Vectors — wir verwerfen sie aktuell!
|
||||
|
||||
```
|
||||
Qdrant Query API:
|
||||
- Dense: bge-m3 Cosine (wie bisher)
|
||||
- Sparse: bge-m3 Sparse Vectors (neu)
|
||||
- Fusion: Reciprocal Rank Fusion (RRF)
|
||||
```
|
||||
|
||||
**Benchmarks (Anthropic):** 49% weniger fehlgeschlagene Retrievals mit Contextual Retrieval, 67% mit Re-Ranking.
|
||||
|
||||
**Impact:** SEHR HOCH | **Effort:** MITTEL
|
||||
|
||||
### 5. Cross-Encoder Re-Ranking
|
||||
|
||||
**Problem:** Top-5 Ergebnisse direkt an LLM — keine Qualitaetspruefung der Retrieval-Ergebnisse.
|
||||
|
||||
**Loesungsansatz:** BGE Reranker v2 (MIT-Lizenz) auf Top-20 Ergebnisse, dann Top-5 an LLM.
|
||||
|
||||
| Re-Ranker | Lizenz | Empfehlung |
|
||||
|-----------|--------|------------|
|
||||
| BGE Reranker v2 | MIT | Empfohlen |
|
||||
| Jina Reranker v2 | Apache-2.0 | Alternative |
|
||||
| ColBERT v2 | MIT | Spaeter |
|
||||
|
||||
**Impact:** HOCH | **Effort:** MITTEL
|
||||
|
||||
### 6. Cross-Regulation Dedup Pass
|
||||
|
||||
**Problem:** Dedup filtert immer nach `pattern_id` — Controls aus DSGVO Art. 25 und NIS2 Art. 21 (beide Security-by-Design) werden nie verglichen.
|
||||
|
||||
**Loesungsansatz:** Zweiter Qdrant-Search ohne `pattern_id`-Filter nach dem normalen Dedup-Pass.
|
||||
|
||||
**Impact:** HOCH | **Effort:** MITTEL
|
||||
|
||||
### 7. Automatische Regressionstests (Golden Set)
|
||||
|
||||
**Problem:** Keine systematische Qualitaetsmessung nach Pipeline-Aenderungen.
|
||||
|
||||
**Loesungsansatz:** 20-Chunk Golden Set → Control-Generation → Output-Stabilitaet pruefen.
|
||||
|
||||
**Impact:** HOCH | **Effort:** NIEDRIG
|
||||
|
||||
---
|
||||
|
||||
## Tier 3: Strategische Investitionen (Wochen bis Monate)
|
||||
|
||||
### 8. Artikel-Boundary Chunking
|
||||
|
||||
Eigener Splitter fuer EU-Verordnungen und deutsche Gesetze: Split an "Art.", "Artikel", "Paragraph"-Grenzen statt nach Zeichenzahl.
|
||||
|
||||
### 9. RAGAS Evaluation Pipeline
|
||||
|
||||
[RAGAS](https://docs.ragas.io/) mit Golden Dataset (50-100 manuell verifizierte Control-to-Source Mappings). Metriken: Faithfulness, Answer Relevancy, Context Precision, Context Recall.
|
||||
|
||||
### 10. BGE-M3 Fine-Tuning
|
||||
|
||||
Fine-Tuning auf Compliance-Corpus (~6.373 Control-Titel/Objective-Paare). Research zeigt +10-30% Domain-Retrieval-Verbesserung.
|
||||
|
||||
### 11. LLM-as-Judge
|
||||
|
||||
Claude Sonnet bewertet jeden generierten Control auf Faithfulness zum Quelltext (~$0.01/Control).
|
||||
|
||||
### 12. Active Learning aus Review-Queue
|
||||
|
||||
Menschliche Entscheidungen der Dedup Review-Queue nutzen, um Schwellenwerte ueber die Zeit zu optimieren.
|
||||
|
||||
---
|
||||
|
||||
## Nicht empfohlen (niedriger ROI oder Konflikte)
|
||||
|
||||
| Ansatz | Grund |
|
||||
|--------|-------|
|
||||
| Jina v3 Embeddings | **CC-BY-NC-4.0** — verletzt Open Source Policy |
|
||||
| Voyage-law-2 | API-only, proprietaer — kein Self-Hosting |
|
||||
| Semantic Chunking | Benchmarks zeigen keinen Vorteil gegenueber Recursive fuer strukturierte Dokumente |
|
||||
| HyDE als Primaerstrategie | Latenz (+43-60%) + Halluzinationsrisiko |
|
||||
| Knowledge Graph RAG | Massiver Aufwand, unklarer Gewinn bei strukturiertem Rechtskorpus |
|
||||
|
||||
---
|
||||
|
||||
## Embedding-Modell Vergleich
|
||||
|
||||
| Modell | MTEB Score | Multilingual | Kontext | Lizenz | Bewertung |
|
||||
|--------|-----------|-------------|---------|--------|-----------|
|
||||
| **BGE-M3** (aktuell) | 63.0 | 100+ Sprachen | 8192 Token | MIT | Gut, Dense+Sparse+ColBERT |
|
||||
| Jina v3 | 65.5 | 89 Sprachen | 8192 Token | CC-BY-NC | Nicht nutzbar (Lizenz!) |
|
||||
| E5-Mistral-7B | ~65 | Gut | 4096 Token | MIT | Gross, hoher RAM |
|
||||
| Voyage-law-2 | Best Legal | EN Legal | 16K Token | Proprietaer | Nicht nutzbar (API-only) |
|
||||
|
||||
**Fazit:** BGE-M3 bleibt die beste Wahl fuer unseren Stack. Sparse-Vectors aktivieren und Fine-Tuning bringen mehr als ein Modellwechsel.
|
||||
|
||||
---
|
||||
|
||||
## Test-Coverage Analyse
|
||||
|
||||
### Pipeline-Module (567 Tests)
|
||||
|
||||
| Modul | Tests | Bewertung | Fehlende Tests |
|
||||
|-------|-------|-----------|----------------|
|
||||
| Control Generator | 110 | Exzellent | 10-15 Edge Cases |
|
||||
| Obligation Extractor | 107 | Exzellent | 8-10 Edge Cases |
|
||||
| Decomposition Pass | 90 | Exzellent | 5-8 Edge Cases |
|
||||
| Pattern Matcher | 72 | Gut | 10-15 Edge Cases |
|
||||
| Control Dedup | 56 | Exzellent | 5-8 Edge Cases |
|
||||
| Control Composer | 54 | Gut | 8-10 Edge Cases |
|
||||
| Pipeline Adapter | 36 | Gut | 10-15 Edge Cases |
|
||||
| Citation Backfill | 20 | Moderat | 5-8 Edge Cases |
|
||||
| License Gate | 12 | Minimal | 5-8 Edge Cases |
|
||||
| RAG Client | 10 | Minimal | 5-8 Edge Cases |
|
||||
|
||||
### Kritische Luecken (fehlende Tests)
|
||||
|
||||
| Service | Datei | Prioritaet |
|
||||
|---------|-------|------------|
|
||||
| AI Compliance Assistant | `ai_compliance_assistant.py` | HOCH (25-30 Tests noetig) |
|
||||
| PDF Extractor | `pdf_extractor.py` | HOCH (20-25 Tests noetig) |
|
||||
| LLM Provider | `llm_provider.py` | HOCH (15-20 Tests noetig) |
|
||||
| Similarity Detector | `similarity_detector.py` | MITTEL (20-25 Tests noetig) |
|
||||
| Anchor Finder | `anchor_finder.py` | MITTEL |
|
||||
|
||||
### Test-Infrastruktur
|
||||
|
||||
**Fehlend:** Shared `conftest.py` mit gemeinsamen Fixtures (LLM-Mock, DB-Mock, Embedding-Mock). Aktuell sind Fixtures in jedem Test-File dupliziert.
|
||||
|
||||
---
|
||||
|
||||
## Quellen
|
||||
|
||||
- [NAACL 2025 Vectara Chunking Study](https://blog.premai.io/rag-chunking-strategies-the-2026-benchmark-guide/)
|
||||
- [Anthropic Contextual Retrieval](https://www.anthropic.com/news/contextual-retrieval)
|
||||
- [Qdrant Hybrid Search Query API](https://qdrant.tech/articles/hybrid-search/)
|
||||
- [Structure-Aware Chunking for Legal (ACL 2025)](https://aclanthology.org/2025.justnlp-main.19/)
|
||||
- [RAGAS Evaluation Framework](https://docs.ragas.io/)
|
||||
- [BGE Reranker v2 (MIT)](https://huggingface.co/BAAI/bge-reranker-v2-m3)
|
||||
- [LegalGPT / CALLM Framework](https://www.emergentmind.com/topics/compliance-alignment-llm-callm)
|
||||
223
docs-src/development/rag-pipeline-lessons-learned.md
Normal file
223
docs-src/development/rag-pipeline-lessons-learned.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# RAG Pipeline: Lessons Learned & Hardening
|
||||
|
||||
## Übersicht
|
||||
|
||||
Dieses Dokument beschreibt die Erkenntnisse aus dem Aufbau der RAG-Pipeline und die daraus abgeleiteten Maßnahmen zur Härtung. Es dient als Referenz für zukünftige Ingestion-Runs und Pipeline-Erweiterungen.
|
||||
|
||||
## Architektur: Wann brauchen wir RAG vs. Direct PDF?
|
||||
|
||||
### RAG ist nötig für:
|
||||
|
||||
| Use Case | Warum RAG? |
|
||||
|---|---|
|
||||
| **Compliance Advisor (Chat)** | Semantische Suche über 38+ Dokumente in Echtzeit |
|
||||
| **Cross-Regulation Mapping** | "Zeige alle Anforderungen zu Verschlüsselung" über alle Quellen |
|
||||
| **Customer Scope-Filtering** | Nur Chunks aus relevanten Regulations für den Kunden |
|
||||
| **Inkrementelle Updates** | Neues Dokument → nur neue Chunks verarbeiten |
|
||||
|
||||
### RAG ist NICHT nötig für:
|
||||
|
||||
| Use Case | Besser: Direct PDF |
|
||||
|---|---|
|
||||
| **Control-Generierung (Batch)** | PDF → PyMuPDF → Strukturparser → Artikel-Index → API |
|
||||
| **PDF-QA/Verifizierung** | Substring-Match direkt im PDF (schneller, exakter) |
|
||||
| **Artikel/§-Extraktion** | Regex-basierte Extraktion aus PDF-Text |
|
||||
|
||||
### Hybrid-Ansatz (Empfehlung)
|
||||
|
||||
```
|
||||
Control-Generierung: PDF → Strukturparser → Artikel-Index → Anthropic API
|
||||
(KEIN RAG nötig, direkt aus PDF)
|
||||
|
||||
Runtime-Betrieb: Qdrant-RAG für semantische Suche, Chat, Scope-Analyse
|
||||
(RAG mit angereicherten Chunks + Struktur-Metadaten)
|
||||
```
|
||||
|
||||
## Fehler und Root Causes
|
||||
|
||||
### 1. Doppelte Ingestion = Doppelte Controls
|
||||
|
||||
**Problem:** Gleiche PDFs unter verschiedenen Namen ingestiert (z.B. "Maschinenverordnung" und "Verordnung (EU) 2023/1230") → unterschiedliche Chunks (anderes Chunking) → anderer Hash → doppelt verarbeitet → doppelte Controls.
|
||||
|
||||
**Root Cause:**
|
||||
- `regulation_name` aus Chunk-Metadaten statt aus kanonischer Quelle
|
||||
- UNIQUE-Constraint nur `(chunk_hash, collection, document_version)` — nicht global
|
||||
- Kein Check ob `regulation_code` bereits in einer Collection existiert
|
||||
|
||||
**Fix (implementiert):**
|
||||
- `REGULATION_LICENSE_MAP` enthält jetzt kanonische `name`-Werte die den DB-Einträgen entsprechen
|
||||
- `source_citation.source` wird aus `REGULATION_LICENSE_MAP.name` genommen, NICHT aus `chunk.regulation_name`
|
||||
- Phase 5 Cleanup: 3.301 Duplikate hart gelöscht
|
||||
|
||||
**Fix (noch offen):**
|
||||
- Chunk-Hash UNIQUE Constraint global machen: `(chunk_hash, document_version)` statt `(chunk_hash, collection, document_version)`
|
||||
- Vor Ingestion: Check ob `regulation_code` bereits in einer Collection existiert
|
||||
|
||||
### 2. Chunks verlieren Strukturinformation
|
||||
|
||||
**Problem:** Chunks werden mitten im Absatz abgeschnitten. § und Artikelnummern fehlen in den Chunk-Metadaten. Kontext des Kapitels/Abschnitts geht verloren.
|
||||
|
||||
**Root Cause:**
|
||||
- `chunk_strategy=recursive` mit `chunk_size=512, chunk_overlap=50` — zu kleine Chunks
|
||||
- Chunking beachtet keine Dokumentstruktur (Artikel-/Paragraphengrenzen)
|
||||
- Keine Einleitung/Kapitelkontext als Prefix
|
||||
|
||||
**Empfehlung für Re-Ingestion:**
|
||||
- **Strukturiertes Chunking:** Chunks an Artikel-/Paragraphengrenzen schneiden
|
||||
- **Kontext-Prefix:** Kapiteleinleitung und übergeordnete Struktur mitliefern
|
||||
- **Metadaten anreichern:** `article`, `paragraph`, `article_type`, `section_hierarchy`
|
||||
- **Größere Chunks:** Mindestens 1024 Tokens, besser volle Artikel/Paragraphen
|
||||
|
||||
### 3. Cross-Collection-Duplikate
|
||||
|
||||
**Problem:** `nist_csf_2_0` in `bp_compliance_ce` (67 Chunks) UND `bp_compliance_datenschutz` (162 Chunks). EU-Verordnungen sowohl in `bp_compliance_ce` als auch `bp_compliance_gesetze`.
|
||||
|
||||
**Root Cause:** Keine Collection-Zuordnungsregeln. Manuelle Zuweisung bei Ingestion.
|
||||
|
||||
**Fix:** `cleanup-qdrant-duplicates.py` Script bereinigt Cross-Collection-Duplikate.
|
||||
|
||||
**Empfehlung:** Klare Collection-Zuordnungsregeln:
|
||||
- `bp_compliance_ce` = EU-Verordnungen + internationale Standards
|
||||
- `bp_compliance_gesetze` = Deutsche + österreichische Gesetze (NUR nationale Gesetze)
|
||||
- `bp_compliance_datenschutz` = EDPB/WP29 Leitlinien + Privacy Frameworks
|
||||
|
||||
### 4. OWASP Multilingual Controls
|
||||
|
||||
**Problem:** 324 OWASP Top 10 Controls in ZH, AR, ID, FR, ES, PT — Übersetzungen derselben 10 Kategorien. Kein Mehrwert, aber 324 doppelte Controls generiert.
|
||||
|
||||
**Root Cause:** Multilingual PDFs/GitHub-Quellen ohne Spracherkennung ingestiert.
|
||||
|
||||
**Fix:** 324 als `duplicate` markiert und gelöscht.
|
||||
|
||||
**Empfehlung:** Bei Ingestion Spracherkennung + Deduplizierung. Nur DE + EN behalten.
|
||||
|
||||
### 5. Fehlende Artikel/Paragraph-Extraktion
|
||||
|
||||
**Problem:** Chunks haben `article` und `paragraph` oft leer oder falsch. Die LLM-basierte Extraktion bei der Control-Generierung ist unzuverlässig.
|
||||
|
||||
**Root Cause:** Ingestion-Pipeline extrahiert keine Strukturinformation aus dem PDF.
|
||||
|
||||
**Fix (implementiert):** PDF-QA-Pipeline (`pdf_qa_all.py`) matched `source_original_text` gegen Original-PDFs und extrahiert korrekte Artikel/Paragraphen — 86% Match-Rate.
|
||||
|
||||
**Empfehlung:** Bei Re-Ingestion direkt in den Chunk-Metadaten speichern.
|
||||
|
||||
### 6. Job-Tracking nicht persistent
|
||||
|
||||
**Problem:** Generation-Jobs laufen als Background-Tasks. Kein Logging, welche Chunks verarbeitet, Status nur über API abfragbar. Bei API-Timeout oder Restart geht der Fortschritt verloren.
|
||||
|
||||
**Root Cause:** `asyncio.create_task()` hat keinen Recovery-Mechanismus.
|
||||
|
||||
**Fix (teilweise):** `canonical_generation_jobs` Tabelle trackt Jobs. `canonical_processed_chunks` markiert verarbeitete Chunks.
|
||||
|
||||
**Empfehlung:**
|
||||
- Job-Log in DB persistieren (nicht nur stdout)
|
||||
- Fortschritt in `canonical_generation_jobs.progress` als JSONB speichern
|
||||
- Chunk-Level-Status: verarbeitet / übersprungen / Fehler
|
||||
- Recovery-Fähigkeit: Job kann von letztem Checkpoint fortgesetzt werden
|
||||
|
||||
## Empfohlene Metadaten für Re-Ingestion
|
||||
|
||||
### Chunk-Level Metadaten (Qdrant Payload)
|
||||
|
||||
```json
|
||||
{
|
||||
"chunk_text": "...",
|
||||
"regulation_code": "eu_2016_679",
|
||||
"regulation_name_de": "DSGVO (EU) 2016/679",
|
||||
"regulation_name_en": "GDPR (EU) 2016/679",
|
||||
"article": "25",
|
||||
"article_title": "Datenschutz durch Technikgestaltung und datenschutzfreundliche Voreinstellungen",
|
||||
"article_type": "article",
|
||||
"paragraph": "1",
|
||||
"section_hierarchy": ["Kapitel IV", "Abschnitt 2", "Artikel 25"],
|
||||
"chapter_context": "Kapitel IV — Verantwortlicher und Auftragsverarbeiter",
|
||||
"pages": [45, 46],
|
||||
"effective_date": "2018-05-25",
|
||||
"publication_date": "2016-04-27",
|
||||
"document_version": "2016-04-27",
|
||||
"source_language": "de",
|
||||
"source_url": "https://eur-lex.europa.eu/...",
|
||||
"celex": "32016R0679",
|
||||
"license": "EU_LAW",
|
||||
"license_rule": 1,
|
||||
"source_type": "law",
|
||||
"category": "datenschutz",
|
||||
"chunk_position": 42,
|
||||
"total_chunks": 423
|
||||
}
|
||||
```
|
||||
|
||||
### Dokument-Level Metadaten (Corpus Version)
|
||||
|
||||
```json
|
||||
{
|
||||
"regulation_code": "eu_2016_679",
|
||||
"canonical_name_de": "DSGVO (EU) 2016/679",
|
||||
"canonical_name_en": "GDPR (EU) 2016/679",
|
||||
"document_type": "eu_regulation",
|
||||
"effective_date": "2018-05-25",
|
||||
"publication_date": "2016-04-27",
|
||||
"supersedes": null,
|
||||
"superseded_by": null,
|
||||
"source_pdf": "gdpr_regulation_eu_2016_679.pdf",
|
||||
"source_pdf_sha256": "abc123...",
|
||||
"total_articles": 99,
|
||||
"total_recitals": 173,
|
||||
"total_annexes": 0,
|
||||
"ingestion_date": "2026-03-20",
|
||||
"ingestion_version": "v2"
|
||||
}
|
||||
```
|
||||
|
||||
## Pipeline-Härtung Checkliste
|
||||
|
||||
### Vor Ingestion
|
||||
|
||||
- [ ] Prüfen ob `regulation_code` bereits in einer Collection existiert
|
||||
- [ ] PDF-SHA256 gegen bekannte PDFs prüfen (Duplikat-Erkennung)
|
||||
- [ ] `regulation_name` aus `REGULATION_LICENSE_MAP` verwenden, NICHT aus Chunk-Metadaten
|
||||
- [ ] Spracherkennung: Nur DE + EN ingestieren
|
||||
- [ ] Dokument-Metadaten (effective_date, publication_date) recherchieren
|
||||
|
||||
### Während Ingestion
|
||||
|
||||
- [ ] Strukturiertes Chunking an Artikel-/Paragraphengrenzen
|
||||
- [ ] Kontext-Prefix mit Kapiteleinleitung
|
||||
- [ ] Chunk-Metadaten anreichern (article, paragraph, article_type, section_hierarchy)
|
||||
- [ ] Fortschritt in DB loggen
|
||||
|
||||
### Nach Ingestion
|
||||
|
||||
- [ ] Chunk-Count pro `regulation_code` prüfen (Sanity Check)
|
||||
- [ ] PDF-QA gegen Original-PDF laufen lassen
|
||||
- [ ] Cross-Collection-Duplikat-Check
|
||||
- [ ] Corpus-Version in DB eintragen
|
||||
|
||||
### Control-Generierung
|
||||
|
||||
- [ ] `source_citation.source` aus `REGULATION_LICENSE_MAP.name`, NICHT aus Chunk-Metadaten
|
||||
- [ ] Harmonisierung: Threshold 0.85 für Duplikate innerhalb gleicher `regulation_code`
|
||||
- [ ] Cross-Regulation-Harmonisierung bei ähnlichen Themen (z.B. DSGVO Art. 25 ↔ NIS2 Art. 21)
|
||||
- [ ] Job-Fortschritt persistent in DB speichern
|
||||
|
||||
## Workflow: Mac Mini → Production Sync
|
||||
|
||||
```
|
||||
1. Mac Mini: PDF → Qdrant (lokal, http://macmini:6333)
|
||||
2. Mac Mini: Control-Generierung → PostgreSQL (shared, 46.225.100.82:54321)
|
||||
3. QA: PDF-Match, Dedup, Source-Normalisierung
|
||||
4. Qdrant Migration: macmini:6333 → qdrant-dev.breakpilot.ai (scripts/migrate-qdrant.py)
|
||||
5. Deploy: git push gitea → Coolify Build + Deploy
|
||||
```
|
||||
|
||||
**WICHTIG:** PostgreSQL ist SHARED — Änderungen auf Mac Mini sind sofort in Production sichtbar. Qdrant hat getrennte Instanzen (lokal + production) und muss manuell synchronisiert werden.
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Beschreibung |
|
||||
|---|---|
|
||||
| `scripts/ingest-phase-h.sh` | Haupt-Ingestion: 38 Dokumente → Qdrant |
|
||||
| `scripts/cleanup-qdrant-duplicates.py` | Qdrant Duplikat-Cleanup (8 Schritte) |
|
||||
| `scripts/migrate-qdrant.py` | Qdrant Migration: lokal → production |
|
||||
| `scripts/qa/phase5_normalize_and_cleanup.py` | DB Normalisierung + Hard Delete |
|
||||
| `scripts/qa/pdf_qa_all.py` | PDF-Match QA |
|
||||
Reference in New Issue
Block a user