# Control Generator Pipeline Automatische Generierung von Canonical Controls aus dem gesamten RAG-Korpus (~105.000 Chunks aus Gesetzen, Verordnungen und Standards). **Backend:** `backend-compliance/compliance/services/control_generator.py` **Routes:** `backend-compliance/compliance/api/control_generator_routes.py` **API-Prefix:** `/api/compliance/v1/canonical/generate` --- ## Pipeline-Uebersicht Die Pipeline durchlaeuft 7 Stufen, um aus RAG-Chunks eigenstaendige Security/Compliance Controls zu erzeugen: ```mermaid flowchart TD A[1. RAG Scan] -->|Alle Chunks laden| B[2. License Classify] B -->|Rule 1/2| C[3a. Structure Batch] B -->|Rule 3| D[3b. Reform Batch] C --> E[4. Harmonize] D --> E E -->|Duplikat| F[Als Duplikat markieren] E -->|Neu| G[5. Anchor Search] G --> H[6. Store Control] H --> I[7. Mark Processed] ``` | Stufe | Name | Beschreibung | |-------|------|-------------| | 1 | **RAG Scan** | Laedt unverarbeitete Chunks aus Qdrant (Scroll-API), filtert per SHA-256-Hash | | 2 | **License Classify** | Bestimmt die Lizenzregel (Rule 1/2/3) anhand `regulation_code` | | 3a | **Structure (Batch)** | Rule 1+2: Strukturiert Originaltext als Control (Anthropic API) | | 3b | **Reform (Batch)** | Rule 3: Vollstaendige Reformulierung ohne Originaltext (Anthropic API) | | 4 | **Harmonize** | Embedding-basierte Duplikaterkennung (bge-m3, Cosine > 0.85) | | 5 | **Anchor Search** | Findet Open-Source-Referenzen (OWASP, NIST, ENISA) | | 6 | **Store** | Persistiert Control in `canonical_controls` mit Metadaten | | 7 | **Mark Processed** | Markiert jeden Chunk als verarbeitet (auch bei Skip/Error/Duplikat) | --- ## Pipeline-Versionen Die Pipeline hat zwei Versionen. Die Version wird als `pipeline_version` auf `canonical_controls` und `canonical_processed_chunks` gespeichert. ### v1 (Original) | Eigenschaft | Wert | |-------------|------| | **Vorfilter** | Lokales LLM (llama3.2 3B) entscheidet ob Chunk relevant | | **Anthropic-Prompt** | Alter Prompt ohne null-Skip | | **Annexe/Anhaenge** | Kein Schutz — wurden haeufig faelschlich als irrelevant uebersprungen | | **`pipeline_version`** | `1` | ### v2 (Aktuell) | Eigenschaft | Wert | |-------------|------| | **Vorfilter** | Optional (`skip_prefilter`). Wenn aktiviert, entscheidet Anthropic API selbst | | **Anthropic-Prompt** | Neuer Prompt mit **null-Skip**: API gibt `null` fuer Chunks ohne Anforderung zurueck | | **Annexe/Anhaenge** | Explizit geschuetzt — Prompt-Anweisung: "Anhaenge/Annexe enthalten oft KONKRETE technische Anforderungen — diese MUESSEN als Control erfasst werden!" | | **`pipeline_version`** | `2` | #### Wesentliche Aenderungen v1 → v2 1. **Relevanz-Entscheidung an Anthropic delegiert** — Das lokale LLM (Vorfilter) ist optional. Die Anthropic API entscheidet selbst, welche Chunks Controls enthalten, indem sie `null` fuer irrelevante Chunks zurueckgibt. 2. **null-Skip im JSON-Array** — Das Ergebnis-Array enthaelt `null`-Eintraege fuer Chunks ohne umsetzbare Anforderung. Kein separater Vorfilter-Schritt noetig. 3. **Annexe/Anhaenge geschuetzt** — Explizite Prompt-Anweisung verhindert, dass technische Anforderungen in Anhaengen uebersprungen werden. #### Datenbank-Feld ```sql -- Migration 062 ALTER TABLE canonical_controls ADD COLUMN pipeline_version smallint NOT NULL DEFAULT 1; ALTER TABLE canonical_processed_chunks ADD COLUMN pipeline_version smallint NOT NULL DEFAULT 1; ``` Neue Controls erhalten automatisch `pipeline_version = 2`. Bestehende (v1) behalten `1`, damit sie spaeter identifiziert und ggf. reprocessiert werden koennen. --- ## Konfiguration ### Request-Parameter (`GenerateRequest`) | Parameter | Typ | Default | Beschreibung | |-----------|-----|---------|-------------| | `collections` | `List[str]` | Alle 5 Collections | Qdrant-Collections zum Durchsuchen | | `domain` | `str` | — | Filter auf eine Domain (z.B. `AUTH`, `NET`) | | `regulation_filter` | `List[str]` | — | Prefix-Matching auf `regulation_code` (z.B. `["eu_2023_1230", "owasp_"]`) | | `skip_prefilter` | `bool` | `false` | Ueberspringt lokalen LLM-Vorfilter, sendet alle Chunks an die Anthropic API | | `batch_size` | `int` | `5` | Chunks pro Anthropic-API-Call | | `max_controls` | `int` | `50` | Maximale Anzahl Controls pro Job (0 = unbegrenzt) | | `max_chunks` | `int` | `1000` | Maximale Chunks pro Job (0 = unbegrenzt, respektiert Dokumentgrenzen) | | `skip_web_search` | `bool` | `false` | Ueberspringt Web-Suche in der Anchor-Findung (Stufe 5) | | `dry_run` | `bool` | `false` | Trockenlauf ohne DB-Schreibzugriffe (synchron, mit Controls im Response) | !!! info "`regulation_filter` — Prefix-Matching" Der Filter vergleicht den `regulation_code` jedes Chunks per Prefix. Beispiel: `["eu_2023_1230"]` erfasst nur Chunks aus der Maschinenverordnung. `["owasp_"]` erfasst alle OWASP-Dokumente (OWASP ASVS, OWASP SAMM, etc.). Gross-/Kleinschreibung wird ignoriert. ### Umgebungsvariablen | Variable | Default | Beschreibung | |----------|---------|-------------| | `ANTHROPIC_API_KEY` | — | API-Key fuer Anthropic Claude (Pflicht) | | `CONTROL_GEN_ANTHROPIC_MODEL` | `claude-sonnet-4-6` | Anthropic-Modell fuer Strukturierung/Reformulierung | | `OLLAMA_URL` | `http://host.docker.internal:11434` | Lokaler Ollama-Server (Vorfilter + QA) | | `CONTROL_GEN_OLLAMA_MODEL` | `qwen3.5:35b-a3b` | Lokales LLM-Modell fuer Vorfilter und QA-Arbitrierung | | `CONTROL_GEN_LLM_TIMEOUT` | `180` | Timeout in Sekunden pro Anthropic-API-Call | ### Pipeline-interne Konstanten | Konstante | Wert | Beschreibung | |-----------|------|-------------| | `PIPELINE_VERSION` | `2` | Aktuelle Pipeline-Version | | `HARMONIZATION_THRESHOLD` | `0.85` | Cosine-Similarity-Schwelle fuer Duplikaterkennung | | `max_tokens` | `8192` | Maximale Token-Laenge der LLM-Antwort | --- ## API Endpoints Alle Endpoints unter `/api/compliance/v1/canonical/`. ### Uebersicht | Methode | Pfad | Beschreibung | |---------|------|-------------| | `POST` | `/generate` | Generierungs-Job starten (laeuft im Hintergrund) | | `GET` | `/generate/status/{job_id}` | Status eines laufenden Jobs abfragen | | `GET` | `/generate/jobs` | Alle Jobs auflisten (paginiert) | | `GET` | `/generate/processed-stats` | Verarbeitungsstatistik pro Collection | | `GET` | `/generate/review-queue` | Controls zur manuellen Pruefung | | `POST` | `/generate/review/{control_id}` | Review eines einzelnen Controls abschliessen | | `POST` | `/generate/bulk-review` | Bulk-Review nach `release_state` | | `POST` | `/generate/qa-reclassify` | QA-Reklassifizierung bestehender Controls | | `GET` | `/blocked-sources` | Gesperrte Quellen (Rule 3) auflisten | | `POST` | `/blocked-sources/cleanup` | Cleanup-Workflow fuer gesperrte Quellen starten | --- ### POST `/v1/canonical/generate` — Job starten Startet einen Generierungs-Job im Hintergrund. Gibt sofort eine `job_id` zurueck. **Request:** ```json { "collections": ["bp_compliance_gesetze"], "regulation_filter": ["eu_2023_1230"], "skip_prefilter": false, "batch_size": 5, "max_chunks": 500, "max_controls": 0, "skip_web_search": false, "dry_run": false } ``` **Response (200):** ```json { "job_id": "a1b2c3d4-...", "status": "running", "message": "Generation started in background. Poll /generate/status/{job_id} for progress." } ``` **Beispiel:** ```bash # Alle Chunks der Maschinenverordnung verarbeiten curl -X POST https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate \ -H 'Content-Type: application/json' \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' \ -d '{ "collections": ["bp_compliance_ce"], "regulation_filter": ["eu_2023_1230"], "max_chunks": 200, "batch_size": 5 }' ``` ```bash # Dry Run: Keine DB-Aenderungen, Controls im Response curl -X POST https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate \ -H 'Content-Type: application/json' \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' \ -d '{ "collections": ["bp_compliance_gesetze"], "max_chunks": 10, "dry_run": true }' ``` ```bash # Ohne Vorfilter: Alle Chunks direkt an Anthropic API curl -X POST https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate \ -H 'Content-Type: application/json' \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' \ -d '{ "collections": ["bp_compliance_gesetze"], "regulation_filter": ["bdsg"], "skip_prefilter": true, "max_chunks": 100 }' ``` !!! warning "Kosten beachten" Ohne `regulation_filter` und mit `max_chunks: 0` werden **alle** ~105.000 Chunks verarbeitet. Das verursacht erhebliche Anthropic-API-Kosten (~$700). --- ### GET `/v1/canonical/generate/status/{job_id}` — Job-Status Gibt den vollstaendigen Status eines Jobs zurueck inkl. Metriken und Fehler. **Beispiel:** ```bash curl https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate/status/a1b2c3d4-... \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' ``` **Response:** ```json { "id": "a1b2c3d4-...", "status": "completed", "total_chunks_scanned": 500, "controls_generated": 48, "controls_verified": 45, "controls_needs_review": 3, "controls_too_close": 0, "controls_duplicates_found": 12, "controls_qa_fixed": 5, "config": { "..." }, "started_at": "2026-03-17T10:00:00+00:00", "completed_at": "2026-03-17T10:15:32+00:00" } ``` --- ### GET `/v1/canonical/generate/jobs` — Alle Jobs Paginierte Liste aller Generierungs-Jobs. **Query-Parameter:** | Parameter | Default | Beschreibung | |-----------|---------|-------------| | `limit` | `20` | Anzahl Jobs (1-100) | | `offset` | `0` | Offset fuer Paginierung | **Beispiel:** ```bash curl "https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate/jobs?limit=5" \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' ``` --- ### GET `/v1/canonical/generate/review-queue` — Review-Queue Listet Controls auf, die eine manuelle Pruefung benoetigen. **Query-Parameter:** | Parameter | Default | Beschreibung | |-----------|---------|-------------| | `release_state` | `needs_review` | Filter: `needs_review`, `too_close`, `duplicate` | | `limit` | `50` | Anzahl (1-200) | **Beispiel:** ```bash curl "https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate/review-queue?release_state=needs_review&limit=10" \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' ``` --- ### POST `/v1/canonical/generate/review/{control_id}` — Review abschliessen Schliesst die manuelle Pruefung eines Controls ab. **Request:** ```json { "action": "approve", "release_state": "draft", "notes": "Inhaltlich korrekt, Severity passt." } ``` **Moegliche `action`-Werte:** | Action | Neuer State | Beschreibung | |--------|-------------|-------------| | `approve` | `draft` (oder per `release_state` ueberschreiben) | Control freigeben | | `reject` | `deprecated` | Control verwerfen | | `needs_rework` | `needs_review` | Zurueck in die Queue | **Beispiel:** ```bash curl -X POST https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate/review/AUTH-042 \ -H 'Content-Type: application/json' \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' \ -d '{"action": "approve", "release_state": "draft"}' ``` --- ### POST `/v1/canonical/generate/bulk-review` — Bulk-Review Aendert den `release_state` aller Controls, die einen bestimmten State haben. **Request:** ```json { "release_state": "needs_review", "action": "approve", "new_state": "draft" } ``` **Beispiel:** ```bash # Alle needs_review Controls auf draft setzen curl -X POST https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate/bulk-review \ -H 'Content-Type: application/json' \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' \ -d '{"release_state": "needs_review", "action": "approve", "new_state": "draft"}' ``` --- ### GET `/v1/canonical/generate/processed-stats` — Verarbeitungsstatistik Liefert Statistiken pro RAG-Collection. **Beispiel:** ```bash curl https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate/processed-stats \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' ``` **Response:** ```json { "stats": [ { "collection": "bp_compliance_gesetze", "processed_chunks": 45200, "direct_adopted": 1850, "llm_reformed": 120, "skipped": 43230, "total_chunks_estimated": 0, "pending_chunks": 0 } ] } ``` --- ## Kosten und Performance ### Kostenabschaetzung | Metrik | Wert | |--------|------| | **Kosten pro Chunk** | ~$0.0067 (Anthropic API, Batch-Modus) | | **Yield (Controls/Chunks)** | ~4.5-10% (nur Chunks mit konkreten Anforderungen erzeugen Controls) | | **Vorfilter-Ersparnis** | ~55% der API-Kosten wenn aktiviert (irrelevante Chunks werden lokal aussortiert) | ### Performance-Kennzahlen | Metrik | Wert | |--------|------| | **Batch-Groesse** | 5 Chunks pro API-Call (Default) | | **API-Aufrufe Reduktion** | ~80% weniger Aufrufe durch Batching | | **LLM-Timeout** | 180 Sekunden pro Call | | **QA-Overhead** | ~2s pro Control (nur bei Disagreement, ~10-15% der Controls) | ### RAG Collections | Collection | Inhalte | Erwartete Regel | |-----------|---------|----------------| | `bp_compliance_gesetze` | Deutsche Gesetze (BDSG, TTDSG, TKG etc.) | Rule 1 | | `bp_compliance_datenschutz` | Datenschutz-Leitlinien + EU-Verordnungen | Rule 1/2 | | `bp_compliance_ce` | CE/Sicherheitsstandards | Rule 1/2/3 | | `bp_dsfa_corpus` | DSFA-Korpus | Rule 1/2 | | `bp_legal_templates` | Rechtsvorlagen | Rule 1 | ### Aktuelle Groessenordnung | Metrik | Wert | |--------|------| | RAG-Chunks gesamt | ~105.000 (nach Dedup 2026-03-16) | | Verarbeitete Chunks | ~105.000 | | Generierte Controls | **~4.738** | | Konversionsrate | ~4,5% | --- ## Lizenz-Klassifikation (3-Regel-System) Jeder Chunk wird basierend auf `regulation_code` einer Lizenzregel zugeordnet: | Regel | Typ | Original erlaubt? | Beispiele | |-------|-----|-------------------|----------| | **Rule 1** (free_use) | EU-Gesetze, NIST, DE-Gesetze, Public Domain | Ja | DSGVO, BDSG, NIS2, AI Act | | **Rule 2** (citation_required) | CC-BY, CC-BY-SA | Ja, mit Zitation | OWASP ASVS, OWASP SAMM | | **Rule 3** (restricted) | Proprietaer | Nein, volle Reformulierung | BSI TR-03161, ISO 27001 | ### Verarbeitung nach Regel - **Rule 1+2 → `_structure_batch()`**: Anthropic strukturiert den Originaltext als Control. Ein API-Call fuer den gesamten Batch. - **Rule 3 → `_reformulate_batch()`**: Anthropic reformuliert vollstaendig — kein Originaltext, keine Quellennamen. Ein API-Call fuer den gesamten Batch. ### Batch Processing Die Pipeline sammelt Chunks in Batches (Default: 5 Chunks) und sendet sie in einem einzigen Anthropic-API-Call. 1. Relevante Chunks werden mit Lizenz-Info in `pending_batch` gesammelt 2. Bei `batch_size` erreicht → `_flush_batch()` 3. Batch wird nach Lizenzregel getrennt: Rule 1+2 → `_structure_batch()`, Rule 3 → `_reformulate_batch()` 4. Ergebnis: JSON-Array mit genau N Elementen (`null` fuer irrelevante Chunks) **Fallback:** Bei Batch-Fehler (Timeout, Parsing-Error) wird automatisch auf Einzelverarbeitung zurueckgefallen. --- ## Chunk-Tracking (Processed Chunks) ### Tabelle `canonical_processed_chunks` | Spalte | Typ | Beschreibung | |--------|-----|-------------| | `chunk_hash` | VARCHAR(64) | SHA-256 Hash des Chunk-Textes | | `collection` | VARCHAR(100) | Qdrant-Collection | | `regulation_code` | VARCHAR(100) | Quell-Regulation (z.B. `bdsg`, `eu_2016_679`) | | `license_rule` | INTEGER | 1, 2 oder 3 | | `processing_path` | VARCHAR(20) | Wie der Chunk verarbeitet wurde | | `generated_control_ids` | JSONB | UUIDs der generierten Controls | | `pipeline_version` | SMALLINT | Pipeline-Version (1 oder 2) | | `job_id` | UUID | Referenz auf den Generierungs-Job | **UNIQUE Constraint:** `(chunk_hash, collection, document_version)` — verhindert Doppelverarbeitung. ### Processing Paths | Wert | Stufe | Bedeutung | |------|-------|-----------| | `prefilter_skip` | 2 | Lokaler LLM-Vorfilter: Chunk nicht relevant | | `structured` | 3a | Einzelner Chunk strukturiert (Rule 1/2) | | `structured_batch` | 3a | Batch-Strukturierung (Rule 1/2) | | `llm_reform` | 3b | Einzelner Chunk reformuliert (Rule 3) | | `llm_reform_batch` | 3b | Batch-Reformulierung (Rule 3) | | `no_control` | 3 | LLM konnte kein Control ableiten (null im Array) | | `store_failed` | 6 | DB-Speichern fehlgeschlagen | | `error` | — | Unerwarteter Fehler | --- ## QA Validation (Automatische Qualitaetspruefung) Die QA-Stufe validiert die Klassifizierung jedes generierten Controls: 1. **LLM-Category:** Anthropic liefert `category` und `domain` im JSON-Response 2. **Keyword-Detection:** `_detect_category(chunk.text)` liefert eine zweite Meinung 3. **Stimmen beide ueberein?** → Schneller Pfad (kein QA noetig) 4. **Bei Disagreement:** Lokales LLM (Ollama) arbitriert 5. **Auto-Fix:** Category/Domain werden automatisch korrigiert Die QA-Metriken werden in `generation_metadata` gespeichert: ```json { "qa_category_fix": {"from": "authentication", "to": "finance", "reason": "IFRS-Thema"}, "qa_domain_fix": {"from": "AUTH", "to": "FIN", "reason": "Finanzregulierung"} } ``` ### Recital-Erkennung (Erwägungsgrund-Detektion) Die QA-Stufe prueft zusaetzlich, ob der `source_original_text` eines Controls tatsaechlich aus einem Gesetzesartikel stammt — oder aus einem Erwaegungsgrund (Recital). Erwaegungsgruende enthalten keine normativen Pflichten und fuehren zu falsch zugeordneten Controls. **Erkennungsmethoden:** | Methode | Pattern | Beispiel | |---------|---------|----------| | **Regex** | `\((\d{1,3})\)\s*\n` — Erwaegungsgrund-Nummern | `(126)\nUm den Verwaltungsaufwand...` | | **Phrasen** | Typische Recital-Formulierungen (≥2 Treffer) | "daher sollte", "in Erwägung nachstehender Gründe" | **Ergebnis bei Verdacht:** - `release_state` wird auf `needs_review` gesetzt - `generation_metadata.recital_suspect = true` - `generation_metadata.recital_detection` enthaelt Details: ```json { "recital_suspect": true, "recital_detection": { "recital_suspect": true, "recital_numbers": ["126", "127"], "recital_phrases": ["daher sollte"], "detection_method": "regex+phrases" } } ``` **Funktion:** `_detect_recital(text)` in `control_generator.py` **Hintergrund:** Bei der Analyse von ~5.500 Controls mit Quelltext wurden 1.555 (28%) als Erwaegungsgrund-Verdacht identifiziert. Der Document Crawler unterschied nicht zwischen Artikeltext und Erwaegungsgruenden, was zu falschen `article`/`paragraph`-Zuordnungen fuehrte. ### QA-Reklassifizierung bestehender Controls ```bash # Dry Run: Welche AUTH-Controls sind falsch klassifiziert? curl -X POST https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate/qa-reclassify \ -H 'Content-Type: application/json' \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' \ -d '{"limit": 50, "dry_run": true, "filter_domain_prefix": "AUTH"}' # Korrekturen anwenden: curl -X POST https://api-dev.breakpilot.ai/api/compliance/v1/canonical/generate/qa-reclassify \ -H 'Content-Type: application/json' \ -H 'X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000' \ -d '{"limit": 50, "dry_run": false, "filter_domain_prefix": "AUTH"}' ``` --- ## Quelldateien | Datei | Beschreibung | |-------|-------------| | `backend-compliance/compliance/services/control_generator.py` | 7-Stufen-Pipeline mit Batch Processing | | `backend-compliance/compliance/api/control_generator_routes.py` | REST API Endpoints | | `backend-compliance/compliance/services/license_gate.py` | Lizenz-Gate-Logik | | `backend-compliance/compliance/services/similarity_detector.py` | Too-Close-Detektor (5 Metriken) | | `backend-compliance/compliance/services/rag_client.py` | RAG-Client (Qdrant Search + Scroll) | | `backend-compliance/migrations/046_control_generator.sql` | Job-Tracking, Chunk-Tracking Tabellen | | `backend-compliance/migrations/048_processing_path_expand.sql` | Erweiterte Processing-Path-Werte | | `backend-compliance/migrations/062_pipeline_version.sql` | `pipeline_version` Spalte | | `backend-compliance/tests/test_control_generator.py` | 81+ Tests (Lizenz, Domain, Batch, Pipeline, Recital) | --- ## Verwandte Dokumentation - [Canonical Control Library (CP-CLIB)](canonical-control-library.md) — Domains, Datenmodell, Too-Close-Detektor, CI/CD Validation - [Multi-Layer Control Architecture](canonical-control-library.md#multi-layer-control-architecture) — 10-Stage Pipeline-Erweiterung mit Obligations, Patterns, Crosswalk