From cfc130a544c3814333d34da1281ca5642310b272 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Tue, 28 Apr 2026 07:29:28 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20UCCA=20assessment=20=E2=80=94=20send=20b?= =?UTF-8?q?oolean=20intake=20flags,=20flatten=20nested=20response,=20map?= =?UTF-8?q?=20risk=E2=86=92escalation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .../compliance/api/agent_analyze_routes.py | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/backend-compliance/compliance/api/agent_analyze_routes.py b/backend-compliance/compliance/api/agent_analyze_routes.py index b4cb5da..6bda31c 100644 --- a/backend-compliance/compliance/api/agent_analyze_routes.py +++ b/backend-compliance/compliance/api/agent_analyze_routes.py @@ -157,34 +157,87 @@ async def _classify(client: httpx.AsyncClient, text: str) -> str: async def _assess(client: httpx.AsyncClient, text: str, classification: str) -> dict: - """Run UCCA assessment via SDK.""" + """Run UCCA assessment via SDK. Returns flattened result dict.""" try: + # UCCA expects boolean intake flags, not string categories resp = await client.post(f"{SDK_URL}/sdk/v1/ucca/assess", headers=SDK_HEADERS, json={ "use_case_text": text[:3000], "domain": classification, - "data_categories": ["personal_data", "tracking", "cookies", "third_party_sharing"], + "data_types": { + "personal_data": True, + "customer_data": True, + "location_data": "tracking" in text.lower() or "standort" in text.lower(), + "images": False, + "biometric_data": "biometrisch" in text.lower(), + "minor_data": "kinder" in text.lower() or "minderjährig" in text.lower(), + }, + "purpose": { + "marketing": "werbung" in text.lower() or "marketing" in text.lower(), + "analytics": "analyse" in text.lower() or "analytics" in text.lower(), + "profiling": "profil" in text.lower() or "personalis" in text.lower(), + "automation": False, + "customer_support": False, + }, + "automation": "partially_automated", + "outputs": { + "content_generation": False, + "recommendations_to_users": "empfehl" in text.lower(), + "data_export": "export" in text.lower() or "uebertrag" in text.lower(), + }, }) - return resp.json() + data = resp.json() + # Flatten: UCCA wraps result under "assessment" and "result" + assessment = data.get("assessment", data.get("result", data)) + result = data.get("result", {}) + return { + "risk_level": assessment.get("risk_level", result.get("risk_level", "unknown")), + "risk_score": assessment.get("risk_score", result.get("risk_score", 0)), + "escalation_level": _risk_to_escalation(assessment.get("risk_level", "")), + "triggered_rules": assessment.get("triggered_rules", result.get("triggered_rules", [])), + "required_controls": assessment.get("required_controls", result.get("required_controls", [])), + "summary": result.get("summary", ""), + "recommendation": result.get("recommendation", ""), + "dsfa_recommended": assessment.get("dsfa_recommended", False), + } except Exception as e: logger.warning("Assessment failed: %s", e) return {"risk_level": "unknown", "risk_score": 0, "escalation_level": "E0"} +def _risk_to_escalation(risk_level: str) -> str: + """Map UCCA risk level to escalation level.""" + mapping = { + "MINIMAL": "E0", + "LIMITED": "E1", + "HIGH": "E2", + "UNACCEPTABLE": "E3", + } + return mapping.get(risk_level.upper() if risk_level else "", "E0") + + def _build_summary(url: str, classification: str, assessment: dict, role: str) -> str: """Build a German manager summary.""" risk = assessment.get("risk_level", "unbekannt") score = assessment.get("risk_score", 0) findings = assessment.get("triggered_rules", []) controls = assessment.get("required_controls", []) + recommendation = assessment.get("recommendation", "") + dsfa = assessment.get("dsfa_recommended", False) findings_text = "\n".join(f"- {f}" for f in findings[:5]) if findings else "Keine" controls_text = "\n".join(f"- {c}" for c in controls[:5]) if controls else "Keine" - return ( - f"Dokumenttyp: {classification}\n" - f"Quelle: {url}\n" - f"Risikobewertung: {risk} ({score}/100)\n" - f"Zustaendig: {role}\n\n" - f"Findings:\n{findings_text}\n\n" - f"Erforderliche Massnahmen:\n{controls_text}" - ) + parts = [ + f"Dokumenttyp: {classification}", + f"Quelle: {url}", + f"Risikobewertung: {risk} ({score}/100)", + f"Zustaendig: {role}", + f"DSFA empfohlen: {'Ja' if dsfa else 'Nein'}", + "", + f"Findings:\n{findings_text}", + "", + f"Erforderliche Massnahmen:\n{controls_text}", + ] + if recommendation: + parts.extend(["", f"Empfehlung: {recommendation}"]) + return "\n".join(parts)