""" Change-Request Engine — Regelbasierte Generierung von Change-Requests. Generates change requests when compliance-relevant events occur: - New high-risk use case → "DSFA erstellen" - AI involvement → "DSFA Section 3/8 aktualisieren" - New data categories → "VVT-Eintrag anlegen" - VVT dpia_required toggle → "DSFA erstellen" - New retention requirement → "Loeschfrist anlegen" """ import json import logging from typing import List, Optional from sqlalchemy import text from sqlalchemy.orm import Session logger = logging.getLogger(__name__) def generate_change_requests_for_vvt( db: Session, tenant_id: str, activity_data: dict, created_by: str = "system", ) -> List[str]: """Generate CRs when a VVT activity is created or updated. Returns list of created CR IDs. """ cr_ids = [] # Rule 1: dpia_required=true → suggest DSFA if activity_data.get("dpia_required"): cr_id = _create_cr( db, tenant_id, trigger_type="vvt_dpia_required", target_document_type="dsfa", proposal_title=f"DSFA erstellen für VVT-Aktivität '{activity_data.get('name', 'Unbenannt')}'", proposal_body=f"Die VVT-Aktivität '{activity_data.get('name')}' wurde als DSFA-pflichtig markiert. " f"Eine Datenschutz-Folgenabschätzung nach Art. 35 DSGVO ist erforderlich.", proposed_changes={ "source": "vvt_activity", "activity_name": activity_data.get("name"), "activity_vvt_id": activity_data.get("vvt_id"), }, priority="high", created_by=created_by, ) if cr_id: cr_ids.append(cr_id) # Rule 2: New data categories → suggest Loeschfrist categories = activity_data.get("personal_data_categories", []) if categories: cr_id = _create_cr( db, tenant_id, trigger_type="vvt_data_categories", target_document_type="loeschfristen", proposal_title=f"Löschfrist für {len(categories)} Datenkategorie(n) prüfen", proposal_body=f"Die VVT-Aktivität '{activity_data.get('name')}' verarbeitet folgende Datenkategorien: " f"{', '.join(categories)}. Prüfen Sie, ob Löschfristen definiert sind.", proposed_changes={ "source": "vvt_activity", "categories": categories, }, priority="normal", created_by=created_by, ) if cr_id: cr_ids.append(cr_id) return cr_ids def generate_change_requests_for_use_case( db: Session, tenant_id: str, use_case_data: dict, created_by: str = "system", ) -> List[str]: """Generate CRs when a high-risk or AI use case is created. Returns list of created CR IDs. """ cr_ids = [] risk_level = use_case_data.get("risk_level", "low") involves_ai = use_case_data.get("involves_ai", False) title = use_case_data.get("title", "Unbenannt") # Rule: High-risk use case → DSFA if risk_level in ("high", "critical"): cr_id = _create_cr( db, tenant_id, trigger_type="use_case_high_risk", target_document_type="dsfa", proposal_title=f"DSFA erstellen für '{title}' (Risiko: {risk_level})", proposal_body="Ein neuer Use Case mit hohem Risiko wurde erstellt. " "Art. 35 DSGVO verlangt eine DSFA für Hochrisiko-Verarbeitungen.", proposed_changes={ "source": "use_case", "title": title, "risk_level": risk_level, }, priority="critical" if risk_level == "critical" else "high", created_by=created_by, ) if cr_id: cr_ids.append(cr_id) # Rule: AI involvement → DSFA section update if involves_ai: cr_id = _create_cr( db, tenant_id, trigger_type="use_case_ai", target_document_type="dsfa", target_section="section_3", proposal_title=f"DSFA Sektion 3/8 aktualisieren (KI in '{title}')", proposal_body=f"Der Use Case '{title}' nutzt KI-Systeme. " f"Die DSFA-Risikoanalyse (Sektion 3) und Maßnahmen (Sektion 8) müssen aktualisiert werden.", proposed_changes={ "source": "use_case", "title": title, "involves_ai": True, }, priority="high", created_by=created_by, ) if cr_id: cr_ids.append(cr_id) return cr_ids def _create_cr( db: Session, tenant_id: str, trigger_type: str, target_document_type: str, proposal_title: str, proposal_body: str = "", proposed_changes: dict = None, priority: str = "normal", target_document_id: str = None, target_section: str = None, trigger_source_id: str = None, created_by: str = "system", ) -> Optional[str]: """Internal helper to insert a change request.""" try: result = db.execute( text(""" INSERT INTO compliance_change_requests (tenant_id, trigger_type, trigger_source_id, target_document_type, target_document_id, target_section, proposal_title, proposal_body, proposed_changes, priority, created_by) VALUES (:tid, :trigger, :source_id, :doc_type, :doc_id, :section, :title, :body, CAST(:changes AS jsonb), :priority, :by) RETURNING id """), { "tid": tenant_id, "trigger": trigger_type, "source_id": trigger_source_id, "doc_type": target_document_type, "doc_id": target_document_id, "section": target_section, "title": proposal_title, "body": proposal_body, "changes": json.dumps(proposed_changes or {}), "priority": priority, "by": created_by, }, ) row = result.fetchone() return str(row[0]) if row else None except Exception as e: logger.error(f"Failed to create change request: {e}") return None