Sfoglia il codice sorgente

创意审核检查删除 代理商匹配

wangyunpeng 1 settimana fa
parent
commit
16b7d5f013

+ 22 - 3
examples/auto_put_ad_mini/roi_control/agency_delivery.py

@@ -24,12 +24,32 @@ def agency_route_key(agency_name: str) -> str:
     """Resolve the configured short agency key without fuzzy matching."""
 
     normalized = "".join(str(agency_name).split())
-    for prefix in ("小程序-代投-", "小程序-代投"):
+    for prefix in ("小程序-代投-", "小程序-代投", "代投公众号-", "代投公众号"):
         if normalized.startswith(prefix):
             return normalized[len(prefix) :]
     return normalized
 
 
+def resolve_agency_webhook(
+    agency_name: str,
+    routes: dict[str, str] | None,
+) -> str | None:
+    """按配置中的代理简称做包含匹配,命中即返回对应机器人链接。
+
+    与精确等值不同,这里遍历配置 key,只要 key 是 agency_name 的子串
+    (如配置 "像素" 命中 "代投公众号-像素")即视为匹配;多个 key 同时命中时
+    优先取更长的 key,避免短简称误抢。
+    """
+
+    normalized = "".join(str(agency_name or "").split())
+    if not normalized or not routes:
+        return None
+    for key in sorted((k for k in routes if k), key=len, reverse=True):
+        if key in normalized:
+            return routes[key]
+    return None
+
+
 def _file_sha256(path: Path) -> str:
     digest = hashlib.sha256()
     with path.open("rb") as handle:
@@ -145,8 +165,7 @@ def publish_agency_reports(
     try:
         for report in reports:
             agency_name = str(report["agency_name"])
-            route_key = agency_route_key(agency_name)
-            webhook_url = routes.get(route_key)
+            webhook_url = resolve_agency_webhook(agency_name, routes)
             path = Path(str(report["report"]))
             delivery_id: int | None = None
             try:

+ 23 - 0
examples/auto_put_ad_mini/test_roi_agency_delivery.py

@@ -14,6 +14,7 @@ from roi_control.agency_delivery import (
     AgencyWebhookNotifier,
     agency_route_key,
     publish_agency_reports,
+    resolve_agency_webhook,
 )
 from roi_control.config import AgencyWebhookConfig, RoiConfig
 from roi_control.feishu import RoiFeishuPublisher
@@ -136,8 +137,30 @@ class AgencyDeliveryTest(unittest.TestCase):
     def test_route_key_is_exact_and_only_removes_known_prefix(self):
         self.assertEqual(agency_route_key("小程序-代投-棱镜"), "棱镜")
         self.assertEqual(agency_route_key("小程序 -代投-贝湉"), "贝湉")
+        self.assertEqual(agency_route_key("代投公众号-像素"), "像素")
+        self.assertEqual(agency_route_key("代投公众号-棱镜"), "棱镜")
         self.assertEqual(agency_route_key("自动化投放"), "自动化投放")
 
+    def test_resolve_agency_webhook_matches_configured_key_by_substring(self):
+        routes = {"像素": "url-pixel", "棱镜": "url-lengjing", "翱鲨": "url-aosha"}
+        self.assertEqual(
+            resolve_agency_webhook("代投公众号-像素", routes), "url-pixel"
+        )
+        self.assertEqual(
+            resolve_agency_webhook("代投公众号-棱镜", routes), "url-lengjing"
+        )
+        self.assertEqual(
+            resolve_agency_webhook("小程序-代投-翱鲨", routes), "url-aosha"
+        )
+        self.assertIsNone(resolve_agency_webhook("未知代理", routes))
+        self.assertIsNone(resolve_agency_webhook("", routes))
+
+    def test_resolve_agency_webhook_prefers_longest_configured_key(self):
+        routes = {"像素": "url-short", "像素A": "url-long"}
+        self.assertEqual(
+            resolve_agency_webhook("代投公众号-像素A", routes), "url-long"
+        )
+
     def test_service_error_redaction_removes_webhook_secret(self):
         config = AgencyWebhookConfig(enabled=True, webhooks={"棱镜": WEBHOOK})
         error = RuntimeError(f"POST {WEBHOOK} failed")

+ 5 - 7
examples/auto_put_ad_mini/tools/creative_rejection_cleanup.py

@@ -19,7 +19,7 @@ from openpyxl.styles import Alignment, Font, PatternFill
 from openpyxl.utils import get_column_letter
 
 from db.connection import get_connection
-from roi_control.agency_delivery import publish_agency_reports
+from roi_control.agency_delivery import publish_agency_reports, resolve_agency_webhook
 from roi_control.config import AgencyWebhookConfig
 from roi_control.feishu import RoiFeishuPublisher
 from storage import advisory_lock, initialize_schema
@@ -1496,12 +1496,10 @@ def run_rejected_creative_cleanup(
             )
             if agency and agency != item.get("agency_name"):
                 update_cleanup_item(item_id, agency_name=agency)
-            route_key = "".join(agency.split())
-            for prefix in ("小程序-代投-", "小程序-代投"):
-                if route_key.startswith(prefix):
-                    route_key = route_key[len(prefix) :]
-                    break
-            if not agency or route_key not in (webhook_config.webhooks or {}):
+            webhook_url = resolve_agency_webhook(
+                agency, webhook_config.webhooks or {}
+            )
+            if not agency or not webhook_url:
                 reason = (
                     "代理商归属为空,禁止自动删除"
                     if not agency

+ 39 - 1
examples/tencent_realtime_control/tencent_client.py

@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 import json
+import logging
 import os
 import time
 import uuid
@@ -14,6 +15,9 @@ import requests
 from storage import connect
 
 
+logger = logging.getLogger(__name__)
+
+
 ACTIVE_STATUS = "AD_STATUS_NORMAL"
 SUSPEND_STATUS = "AD_STATUS_SUSPEND"
 DELETED_STATUS = "AD_STATUS_DELETED"
@@ -499,8 +503,22 @@ class TencentClient:
             try:
                 creative = self.get_dynamic_creative(account_id, dynamic_creative_id)
                 last_actual = {
-                    "configured_status": creative.get("configured_status")
+                    "configured_status": creative.get("configured_status"),
+                    "system_status": creative.get("system_status"),
+                    "creative_set_approval_status": creative.get(
+                        "creative_set_approval_status"
+                    ),
+                    "dynamic_creative_name": creative.get("dynamic_creative_name"),
                 }
+                logger.info(
+                    "creative delete readback not converged account=%s creative=%s "
+                    "attempt=%d/%d readback=%s",
+                    account_id,
+                    dynamic_creative_id,
+                    attempt,
+                    self.verify_attempts,
+                    last_actual,
+                )
             except Exception as exc:
                 if str(exc).startswith("Dynamic creative not found:"):
                     return {
@@ -510,8 +528,28 @@ class TencentClient:
                         "deleted_from_listing": True,
                     }
                 last_actual = {"verification_error": str(exc)}
+                logger.info(
+                    "creative delete readback error account=%s creative=%s "
+                    "attempt=%d/%d error=%s",
+                    account_id,
+                    dynamic_creative_id,
+                    attempt,
+                    self.verify_attempts,
+                    exc,
+                )
             if attempt < self.verify_attempts:
                 time.sleep(self.verify_delay_seconds)
+        logger.error(
+            "creative delete verification FAILED account=%s creative=%s "
+            "delete_response=%s verify_attempts=%s verify_delay_seconds=%s "
+            "readback=%s",
+            account_id,
+            dynamic_creative_id,
+            data,
+            self.verify_attempts,
+            self.verify_delay_seconds,
+            last_actual,
+        )
         raise PostWriteVerificationError(
             account_id=account_id,
             adgroup_id=dynamic_creative_id,