From e2c0323db2a02b5f50a1c66916190d698591c3d4 Mon Sep 17 00:00:00 2001 From: Floke Date: Mon, 23 Feb 2026 10:53:13 +0000 Subject: [PATCH] [2ff88f42] Finalize Pains & Gains Phase 2 + Matrix Engine v3.2 (Ops Secondary Logic) --- .dev_session/SESSION_INFO | 2 +- .../backend/scripts/generate_matrix.py | 33 +++++++--- connector-superoffice/superoffice_client.py | 63 +++++++++++-------- connector-superoffice/worker.py | 49 ++++++++------- nginx-proxy.conf | 7 +++ 5 files changed, 94 insertions(+), 60 deletions(-) diff --git a/.dev_session/SESSION_INFO b/.dev_session/SESSION_INFO index c05eec09..f286c121 100644 --- a/.dev_session/SESSION_INFO +++ b/.dev_session/SESSION_INFO @@ -1 +1 @@ -{"task_id": "2ff88f42-8544-8050-8245-c3bb852058f4", "token": "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", "session_start_time": "2026-02-23T07:38:02.815686"} \ No newline at end of file +{"task_id": "2ff88f42-8544-8050-8245-c3bb852058f4", "token": "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", "session_start_time": "2026-02-23T09:56:54.647321"} \ No newline at end of file diff --git a/company-explorer/backend/scripts/generate_matrix.py b/company-explorer/backend/scripts/generate_matrix.py index 36e2dfa5..0a030a19 100644 --- a/company-explorer/backend/scripts/generate_matrix.py +++ b/company-explorer/backend/scripts/generate_matrix.py @@ -128,7 +128,7 @@ PERSÖNLICHE HERAUSFORDERUNGEN DES ANSPRECHPARTNERS (PAIN POINTS): }} --- FORMAT --- -Antworte NUR mit einem validen JSON-Objekt. +Antworte NUR mit einem validen JSON-Objekt. Keine Markdown-Blöcke (```json), kein erklärender Text. Format: {{ "subject": "...", @@ -231,9 +231,22 @@ def run_matrix_generation(dry_run: bool = True, force: bool = False, specific_in else: try: result = real_gemini_call(prompt) - # Basic Validation - if not result.get("subject") or not result.get("intro"): - print(" -> Invalid result structure. Skipping.") + + # Normalize Keys (Case-Insensitive) + normalized_result = {} + for k, v in result.items(): + normalized_result[k.lower()] = v + + # Map known variations to standardized keys + if "introduction_textonly" in normalized_result: + normalized_result["intro"] = normalized_result["introduction_textonly"] + if "industry_references_textonly" in normalized_result: + normalized_result["social_proof"] = normalized_result["industry_references_textonly"] + + # Validation using normalized keys + if not normalized_result.get("subject") or not normalized_result.get("intro"): + print(f" -> Invalid result structure. Keys found: {list(result.keys())}") + print(f" -> Raw Result: {json.dumps(result, indent=2)}") continue except Exception as e: @@ -246,16 +259,16 @@ def run_matrix_generation(dry_run: bool = True, force: bool = False, specific_in new_entry = MarketingMatrix( industry_id=ind.id, persona_id=pers.id, - subject=result.get("subject"), - intro=result.get("intro"), - social_proof=result.get("social_proof") + subject=normalized_result.get("subject"), + intro=normalized_result.get("intro"), + social_proof=normalized_result.get("social_proof") ) db.add(new_entry) print(f" -> Created new entry.") else: - existing.subject = result.get("subject") - existing.intro = result.get("intro") - existing.social_proof = result.get("social_proof") + existing.subject = normalized_result.get("subject") + existing.intro = normalized_result.get("intro") + existing.social_proof = normalized_result.get("social_proof") print(f" -> Updated entry.") db.commit() diff --git a/connector-superoffice/superoffice_client.py b/connector-superoffice/superoffice_client.py index d3cfa1d3..b4c537b0 100644 --- a/connector-superoffice/superoffice_client.py +++ b/connector-superoffice/superoffice_client.py @@ -66,43 +66,52 @@ class SuperOfficeClient: logger.error(f"❌ Connection Error during token refresh: {e}") return None - def _get(self, endpoint): - """Generic GET request.""" - if not self.access_token: return None + def _request_with_retry(self, method, endpoint, payload=None, retry=True): + """Helper to handle 401 Unauthorized with auto-refresh.""" + if not self.access_token: + if not self._refresh_access_token(): + return None + + url = f"{self.base_url}/{endpoint}" try: - resp = requests.get(f"{self.base_url}/{endpoint}", headers=self.headers) - resp.raise_for_status() - return resp.json() - except requests.exceptions.HTTPError as e: - logger.error(f"❌ API GET Error for {endpoint} (Status: {e.response.status_code}): {e.response.text}") - logger.debug(f"Response Headers: {e.response.headers}") - return None + if method == "GET": + resp = requests.get(url, headers=self.headers) + elif method == "POST": + resp = requests.post(url, headers=self.headers, json=payload) + elif method == "PUT": + resp = requests.put(url, headers=self.headers, json=payload) + + # 401 Handling + if resp.status_code == 401 and retry: + logger.warning(f"⚠️ 401 Unauthorized for {endpoint}. Attempting Token Refresh...") + new_token = self._refresh_access_token() + if new_token: + self.access_token = new_token + self.headers["Authorization"] = f"Bearer {self.access_token}" + return self._request_with_retry(method, endpoint, payload, retry=False) + else: + logger.error("❌ Token Refresh failed during retry.") + return None - def _put(self, endpoint, payload): - """Generic PUT request.""" - if not self.access_token: return None - try: - resp = requests.put(f"{self.base_url}/{endpoint}", headers=self.headers, json=payload) resp.raise_for_status() return resp.json() - except requests.exceptions.HTTPError as e: - logger.error(f"❌ API PUT Error for {endpoint}: {e.response.text}") - return None - def _post(self, endpoint, payload): - """Generic POST request.""" - if not self.access_token: return None - try: - resp = requests.post(f"{self.base_url}/{endpoint}", headers=self.headers, json=payload) - resp.raise_for_status() - return resp.json() except requests.exceptions.HTTPError as e: - logger.error(f"❌ API POST Error for {endpoint} (Status: {e.response.status_code}): {e.response.text}") + logger.error(f"❌ API {method} Error for {endpoint} (Status: {e.response.status_code}): {e.response.text}") return None except Exception as e: - logger.error(f"❌ Connection Error during POST for {endpoint}: {e}") + logger.error(f"❌ Connection Error during {method} for {endpoint}: {e}") return None + def _get(self, endpoint): + return self._request_with_retry("GET", endpoint) + + def _put(self, endpoint, payload): + return self._request_with_retry("PUT", endpoint, payload) + + def _post(self, endpoint, payload): + return self._request_with_retry("POST", endpoint, payload) + # --- Convenience Wrappers --- def get_person(self, person_id): diff --git a/connector-superoffice/worker.py b/connector-superoffice/worker.py index 9d28f484..08eead86 100644 --- a/connector-superoffice/worker.py +++ b/connector-superoffice/worker.py @@ -115,32 +115,37 @@ def process_job(job, so_client: SuperOfficeClient): try: contact_details = so_client.get_contact(contact_id) - if contact_details: - crm_name = contact_details.get("Name") - crm_website = contact_details.get("UrlAddress") - if not crm_website and "Urls" in contact_details and contact_details["Urls"]: - crm_website = contact_details["Urls"][0].get("Value") + if not contact_details: + raise ValueError(f"Contact {contact_id} not found (API returned None)") + + crm_name = contact_details.get("Name") + crm_website = contact_details.get("UrlAddress") + if not crm_website and "Urls" in contact_details and contact_details["Urls"]: + crm_website = contact_details["Urls"][0].get("Value") + + if settings.UDF_VERTICAL: + udfs = contact_details.get("UserDefinedFields", {}) + so_vertical_val = udfs.get(settings.UDF_VERTICAL) - if settings.UDF_VERTICAL: - udfs = contact_details.get("UserDefinedFields", {}) - so_vertical_val = udfs.get(settings.UDF_VERTICAL) + if so_vertical_val: + val_str = str(so_vertical_val) + if val_str.startswith("[I:"): + val_str = val_str.split(":")[1].strip("]") - if so_vertical_val: - val_str = str(so_vertical_val) - if val_str.startswith("[I:"): - val_str = val_str.split(":")[1].strip("]") - - try: - vertical_map = json.loads(settings.VERTICAL_MAP_JSON) - vertical_map_rev = {str(v): k for k, v in vertical_map.items()} - if val_str in vertical_map_rev: - crm_industry_name = vertical_map_rev[val_str] - logger.info(f"Detected CRM Vertical Override: {so_vertical_val} -> {crm_industry_name}") - except Exception as ex: - logger.error(f"Error mapping vertical ID {val_str}: {ex}") + try: + vertical_map = json.loads(settings.VERTICAL_MAP_JSON) + vertical_map_rev = {str(v): k for k, v in vertical_map.items()} + if val_str in vertical_map_rev: + crm_industry_name = vertical_map_rev[val_str] + logger.info(f"Detected CRM Vertical Override: {so_vertical_val} -> {crm_industry_name}") + except Exception as ex: + logger.error(f"Error mapping vertical ID {val_str}: {ex}") except Exception as e: - logger.warning(f"Failed to fetch contact details for {contact_id}: {e}") + logger.error(f"Failed to fetch contact details for {contact_id}: {e}") + # Critical failure: Without contact details, we cannot provision correctly. + # Raising exception triggers a retry. + raise Exception(f"SuperOffice API Failure: {e}") # --- 3. Company Explorer Provisioning --- ce_url = f"{settings.COMPANY_EXPLORER_URL}/api/provision/superoffice-contact" diff --git a/nginx-proxy.conf b/nginx-proxy.conf index 0daae8a8..d3db4169 100644 --- a/nginx-proxy.conf +++ b/nginx-proxy.conf @@ -182,9 +182,16 @@ http { # Trailing Slash STRIPS the /connector/ prefix! # So /connector/dashboard -> /dashboard proxy_pass http://connector-superoffice:8000/; + + # Standard Proxy Headers proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Websocket Support (just in case) + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; } } }