|
@@ -36,7 +36,7 @@ from tools.creative_review import (
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
SHANGHAI = ZoneInfo("Asia/Shanghai")
|
|
SHANGHAI = ZoneInfo("Asia/Shanghai")
|
|
|
-REPORT_VERSION = "creative_rejection_cleanup_v13"
|
|
|
|
|
|
|
+REPORT_VERSION = "creative_rejection_cleanup_v14"
|
|
|
OPERATOR_SUMMARY_ROUTE = "投放调控汇总"
|
|
OPERATOR_SUMMARY_ROUTE = "投放调控汇总"
|
|
|
PERFORMANCE_SUMMARY_ROUTE = "长期未起量清理汇总"
|
|
PERFORMANCE_SUMMARY_ROUTE = "长期未起量清理汇总"
|
|
|
PERFORMANCE_REPORT_VERSION = (
|
|
PERFORMANCE_REPORT_VERSION = (
|
|
@@ -60,6 +60,7 @@ PERFORMANCE_RULE_PREFIX = "PERFORMANCE_"
|
|
|
DELETE_AD = "DELETE_AD"
|
|
DELETE_AD = "DELETE_AD"
|
|
|
DEFAULT_PARTIAL_CREATIVE_COST_THRESHOLD_YUAN = 50.0
|
|
DEFAULT_PARTIAL_CREATIVE_COST_THRESHOLD_YUAN = 50.0
|
|
|
DEFAULT_WECHAT_MINI_PROGRAM_COST_THRESHOLD_YUAN = 100.0
|
|
DEFAULT_WECHAT_MINI_PROGRAM_COST_THRESHOLD_YUAN = 100.0
|
|
|
|
|
+DEFAULT_PARTIAL_CREATIVE_PROTECTION_DAYS = 3
|
|
|
DEFAULT_DELETE_CLAIM_STALE_MINUTES = 30
|
|
DEFAULT_DELETE_CLAIM_STALE_MINUTES = 30
|
|
|
DEFAULT_PERFORMANCE_NEW_MIN_AGE_DAYS = 5
|
|
DEFAULT_PERFORMANCE_NEW_MIN_AGE_DAYS = 5
|
|
|
DEFAULT_PERFORMANCE_NEW_MAX_AGE_DAYS = 7
|
|
DEFAULT_PERFORMANCE_NEW_MAX_AGE_DAYS = 7
|
|
@@ -79,6 +80,8 @@ AGENCY_REPORT_COLUMNS = (
|
|
|
"创意ID",
|
|
"创意ID",
|
|
|
"创意名称",
|
|
"创意名称",
|
|
|
"近3天累计历史消耗(元)",
|
|
"近3天累计历史消耗(元)",
|
|
|
|
|
+ "当日消耗(元)",
|
|
|
|
|
+ "近3天及当日累计消耗(元)",
|
|
|
"配置状态",
|
|
"配置状态",
|
|
|
"创意审核状态",
|
|
"创意审核状态",
|
|
|
"审核不通过原因",
|
|
"审核不通过原因",
|
|
@@ -100,6 +103,8 @@ OPERATOR_REPORT_COLUMNS = (
|
|
|
"规则窗口累计消耗(元)",
|
|
"规则窗口累计消耗(元)",
|
|
|
"规则指标日期范围",
|
|
"规则指标日期范围",
|
|
|
"近3天累计历史消耗(元)",
|
|
"近3天累计历史消耗(元)",
|
|
|
|
|
+ "当日消耗(元)",
|
|
|
|
|
+ "近3天及当日累计消耗(元)",
|
|
|
"消耗日期范围",
|
|
"消耗日期范围",
|
|
|
"配置状态",
|
|
"配置状态",
|
|
|
"创意审核状态",
|
|
"创意审核状态",
|
|
@@ -205,6 +210,24 @@ def partial_creative_cost_threshold_fen() -> int:
|
|
|
return int(round(yuan * 100))
|
|
return int(round(yuan * 100))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def partial_creative_protection_days() -> int:
|
|
|
|
|
+ raw = os.getenv(
|
|
|
|
|
+ "DAILY_PARTIAL_CREATIVE_PROTECTION_DAYS",
|
|
|
|
|
+ str(DEFAULT_PARTIAL_CREATIVE_PROTECTION_DAYS),
|
|
|
|
|
+ )
|
|
|
|
|
+ try:
|
|
|
|
|
+ days = int(raw)
|
|
|
|
|
+ except (TypeError, ValueError) as exc:
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ "DAILY_PARTIAL_CREATIVE_PROTECTION_DAYS must be an integer"
|
|
|
|
|
+ ) from exc
|
|
|
|
|
+ if days < 0:
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ "DAILY_PARTIAL_CREATIVE_PROTECTION_DAYS must not be negative"
|
|
|
|
|
+ )
|
|
|
|
|
+ return days
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def wechat_mini_program_cost_threshold_fen() -> int:
|
|
def wechat_mini_program_cost_threshold_fen() -> int:
|
|
|
raw = os.getenv(
|
|
raw = os.getenv(
|
|
|
"DAILY_WECHAT_MINI_PROGRAM_CREATIVE_COST_THRESHOLD_YUAN",
|
|
"DAILY_WECHAT_MINI_PROGRAM_CREATIVE_COST_THRESHOLD_YUAN",
|
|
@@ -638,6 +661,10 @@ def determine_cleanup_action(
|
|
|
raw_result: dict | None,
|
|
raw_result: dict | None,
|
|
|
*,
|
|
*,
|
|
|
recent_cost_fen: int | None = None,
|
|
recent_cost_fen: int | None = None,
|
|
|
|
|
+ current_day_cost_fen: int | None = None,
|
|
|
|
|
+ creative_created_at: Any = None,
|
|
|
|
|
+ as_of_datetime: Any = None,
|
|
|
|
|
+ protection_days: int = DEFAULT_PARTIAL_CREATIVE_PROTECTION_DAYS,
|
|
|
cost_threshold_fen: int = 5000,
|
|
cost_threshold_fen: int = 5000,
|
|
|
wechat_cost_threshold_fen: int = 10000,
|
|
wechat_cost_threshold_fen: int = 10000,
|
|
|
spend_error: str | None = None,
|
|
spend_error: str | None = None,
|
|
@@ -655,81 +682,109 @@ def determine_cleanup_action(
|
|
|
"recent_cost_fen": recent_cost_fen,
|
|
"recent_cost_fen": recent_cost_fen,
|
|
|
}
|
|
}
|
|
|
if approval_status == CREATIVE_PARTIAL_NORMAL_STATUS:
|
|
if approval_status == CREATIVE_PARTIAL_NORMAL_STATUS:
|
|
|
|
|
+ common = {
|
|
|
|
|
+ "cleanup_rule_type": REVIEW_PARTIAL_RULE,
|
|
|
|
|
+ "component_ids": [],
|
|
|
|
|
+ "element_ids": [],
|
|
|
|
|
+ "recent_cost_fen": recent_cost_fen,
|
|
|
|
|
+ "current_day_cost_fen": current_day_cost_fen,
|
|
|
|
|
+ }
|
|
|
|
|
+ created_at = _as_shanghai_datetime(creative_created_at)
|
|
|
|
|
+ evaluated_at = _as_shanghai_datetime(as_of_datetime)
|
|
|
|
|
+ if evaluated_at is None:
|
|
|
|
|
+ evaluated_at = datetime.now(SHANGHAI)
|
|
|
|
|
+ if created_at is None:
|
|
|
|
|
+ return {
|
|
|
|
|
+ **common,
|
|
|
|
|
+ "cleanup_action": ALERT_ONLY,
|
|
|
|
|
+ "action_reason": "部分投放中且创意搭建时间缺失,需人工判断",
|
|
|
|
|
+ }
|
|
|
|
|
+ elapsed = evaluated_at - created_at
|
|
|
|
|
+ age_days = elapsed.days
|
|
|
|
|
+ age_context = {
|
|
|
|
|
+ **common,
|
|
|
|
|
+ "creative_created_at": created_at,
|
|
|
|
|
+ "creative_age_days": age_days,
|
|
|
|
|
+ }
|
|
|
|
|
+ if elapsed < timedelta(0):
|
|
|
|
|
+ return {
|
|
|
|
|
+ **age_context,
|
|
|
|
|
+ "cleanup_action": ALERT_ONLY,
|
|
|
|
|
+ "action_reason": "部分投放中且创意搭建时间晚于检查时间,需人工判断",
|
|
|
|
|
+ }
|
|
|
|
|
+ if elapsed <= timedelta(days=protection_days):
|
|
|
|
|
+ return {
|
|
|
|
|
+ **age_context,
|
|
|
|
|
+ "cleanup_action": ALERT_ONLY,
|
|
|
|
|
+ "action_reason": (
|
|
|
|
|
+ f"部分投放中且创意搭建时间未超过{protection_days}天,"
|
|
|
|
|
+ "本轮不删除,需人工处理审核异常"
|
|
|
|
|
+ ),
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (
|
|
|
|
|
+ spend_error
|
|
|
|
|
+ or recent_cost_fen is None
|
|
|
|
|
+ or current_day_cost_fen is None
|
|
|
|
|
+ ):
|
|
|
|
|
+ reason = "部分投放中,历史或当日消耗读取失败,需人工判断"
|
|
|
|
|
+ if spend_error:
|
|
|
|
|
+ reason = f"{reason}:{spend_error}"
|
|
|
|
|
+ return {
|
|
|
|
|
+ **age_context,
|
|
|
|
|
+ "cleanup_action": ALERT_ONLY,
|
|
|
|
|
+ "action_reason": reason,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ total_cost_fen = recent_cost_fen + current_day_cost_fen
|
|
|
|
|
+ spend_context = {
|
|
|
|
|
+ **age_context,
|
|
|
|
|
+ "total_cost_fen": total_cost_fen,
|
|
|
|
|
+ }
|
|
|
if has_rejected_wechat_mini_program_element(raw_result):
|
|
if has_rejected_wechat_mini_program_element(raw_result):
|
|
|
- if spend_error or recent_cost_fen is None:
|
|
|
|
|
- reason = (
|
|
|
|
|
- "部分投放中且微信小程序元素审核拒绝,"
|
|
|
|
|
- "近3天历史消耗读取失败,需人工判断"
|
|
|
|
|
- )
|
|
|
|
|
- if spend_error:
|
|
|
|
|
- reason = f"{reason}:{spend_error}"
|
|
|
|
|
|
|
+ if total_cost_fen >= wechat_cost_threshold_fen:
|
|
|
return {
|
|
return {
|
|
|
|
|
+ **spend_context,
|
|
|
"cleanup_action": ALERT_ONLY,
|
|
"cleanup_action": ALERT_ONLY,
|
|
|
- "cleanup_rule_type": REVIEW_PARTIAL_RULE,
|
|
|
|
|
- "component_ids": [],
|
|
|
|
|
- "element_ids": [],
|
|
|
|
|
- "action_reason": reason,
|
|
|
|
|
- "recent_cost_fen": None,
|
|
|
|
|
- }
|
|
|
|
|
- if recent_cost_fen >= wechat_cost_threshold_fen:
|
|
|
|
|
- return {
|
|
|
|
|
- "cleanup_action": ALERT_ONLY,
|
|
|
|
|
- "cleanup_rule_type": REVIEW_PARTIAL_RULE,
|
|
|
|
|
- "component_ids": [],
|
|
|
|
|
- "element_ids": [],
|
|
|
|
|
"action_reason": (
|
|
"action_reason": (
|
|
|
"部分投放中且微信小程序元素审核拒绝,近3天历史消耗"
|
|
"部分投放中且微信小程序元素审核拒绝,近3天历史消耗"
|
|
|
- f"{recent_cost_fen / 100:.2f}元不低于"
|
|
|
|
|
|
|
+ f"{recent_cost_fen / 100:.2f}元、当日消耗"
|
|
|
|
|
+ f"{current_day_cost_fen / 100:.2f}元,合计"
|
|
|
|
|
+ f"{total_cost_fen / 100:.2f}元不低于"
|
|
|
f"{wechat_cost_threshold_fen / 100:.2f}元,需人工判断是否删除"
|
|
f"{wechat_cost_threshold_fen / 100:.2f}元,需人工判断是否删除"
|
|
|
),
|
|
),
|
|
|
- "recent_cost_fen": recent_cost_fen,
|
|
|
|
|
}
|
|
}
|
|
|
return {
|
|
return {
|
|
|
|
|
+ **spend_context,
|
|
|
"cleanup_action": DELETE_CREATIVE,
|
|
"cleanup_action": DELETE_CREATIVE,
|
|
|
- "cleanup_rule_type": REVIEW_PARTIAL_RULE,
|
|
|
|
|
- "component_ids": [],
|
|
|
|
|
- "element_ids": [],
|
|
|
|
|
"action_reason": (
|
|
"action_reason": (
|
|
|
"部分投放中且微信小程序元素审核拒绝,近3天历史消耗"
|
|
"部分投放中且微信小程序元素审核拒绝,近3天历史消耗"
|
|
|
- f"{recent_cost_fen / 100:.2f}元低于"
|
|
|
|
|
|
|
+ f"{recent_cost_fen / 100:.2f}元、当日消耗"
|
|
|
|
|
+ f"{current_day_cost_fen / 100:.2f}元,合计"
|
|
|
|
|
+ f"{total_cost_fen / 100:.2f}元低于"
|
|
|
f"{wechat_cost_threshold_fen / 100:.2f}元"
|
|
f"{wechat_cost_threshold_fen / 100:.2f}元"
|
|
|
),
|
|
),
|
|
|
- "recent_cost_fen": recent_cost_fen,
|
|
|
|
|
- }
|
|
|
|
|
- if spend_error or recent_cost_fen is None:
|
|
|
|
|
- reason = "部分投放中,近3天历史消耗读取失败,需人工判断"
|
|
|
|
|
- if spend_error:
|
|
|
|
|
- reason = f"{reason}:{spend_error}"
|
|
|
|
|
- return {
|
|
|
|
|
- "cleanup_action": ALERT_ONLY,
|
|
|
|
|
- "cleanup_rule_type": REVIEW_PARTIAL_RULE,
|
|
|
|
|
- "component_ids": [],
|
|
|
|
|
- "element_ids": [],
|
|
|
|
|
- "action_reason": reason,
|
|
|
|
|
- "recent_cost_fen": None,
|
|
|
|
|
}
|
|
}
|
|
|
- if recent_cost_fen < cost_threshold_fen:
|
|
|
|
|
|
|
+ if total_cost_fen < cost_threshold_fen:
|
|
|
return {
|
|
return {
|
|
|
|
|
+ **spend_context,
|
|
|
"cleanup_action": DELETE_CREATIVE,
|
|
"cleanup_action": DELETE_CREATIVE,
|
|
|
- "cleanup_rule_type": REVIEW_PARTIAL_RULE,
|
|
|
|
|
- "component_ids": [],
|
|
|
|
|
- "element_ids": [],
|
|
|
|
|
"action_reason": (
|
|
"action_reason": (
|
|
|
- f"部分投放中且近3天历史消耗{recent_cost_fen / 100:.2f}元"
|
|
|
|
|
|
|
+ f"部分投放中且近3天历史消耗{recent_cost_fen / 100:.2f}元、"
|
|
|
|
|
+ f"当日消耗{current_day_cost_fen / 100:.2f}元,合计"
|
|
|
|
|
+ f"{total_cost_fen / 100:.2f}元"
|
|
|
f"低于{cost_threshold_fen / 100:.2f}元"
|
|
f"低于{cost_threshold_fen / 100:.2f}元"
|
|
|
),
|
|
),
|
|
|
- "recent_cost_fen": recent_cost_fen,
|
|
|
|
|
}
|
|
}
|
|
|
return {
|
|
return {
|
|
|
|
|
+ **spend_context,
|
|
|
"cleanup_action": ALERT_ONLY,
|
|
"cleanup_action": ALERT_ONLY,
|
|
|
- "cleanup_rule_type": REVIEW_PARTIAL_RULE,
|
|
|
|
|
- "component_ids": [],
|
|
|
|
|
- "element_ids": [],
|
|
|
|
|
"action_reason": (
|
|
"action_reason": (
|
|
|
- f"部分投放中且近3天历史消耗{recent_cost_fen / 100:.2f}元,"
|
|
|
|
|
|
|
+ f"部分投放中且近3天历史消耗{recent_cost_fen / 100:.2f}元、"
|
|
|
|
|
+ f"当日消耗{current_day_cost_fen / 100:.2f}元,合计"
|
|
|
|
|
+ f"{total_cost_fen / 100:.2f}元,"
|
|
|
"需人工判断是否删除"
|
|
"需人工判断是否删除"
|
|
|
),
|
|
),
|
|
|
- "recent_cost_fen": recent_cost_fen,
|
|
|
|
|
}
|
|
}
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
@@ -808,6 +863,7 @@ def _token_skipped_scan_result(account_id: int, error: Any):
|
|
|
"missing_source_ad_ids": 0,
|
|
"missing_source_ad_ids": 0,
|
|
|
"missing_create_time": 0,
|
|
"missing_create_time": 0,
|
|
|
},
|
|
},
|
|
|
|
|
+ {},
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1216,6 +1272,7 @@ def upsert_cleanup_candidate(
|
|
|
)
|
|
)
|
|
|
extended_values = (
|
|
extended_values = (
|
|
|
cleanup_rule_type,
|
|
cleanup_rule_type,
|
|
|
|
|
+ record.get("current_day_cost_fen"),
|
|
|
record.get("account_name"),
|
|
record.get("account_name"),
|
|
|
record.get("agent_name"),
|
|
record.get("agent_name"),
|
|
|
record.get("creative_created_at"),
|
|
record.get("creative_created_at"),
|
|
@@ -1234,6 +1291,7 @@ def upsert_cleanup_candidate(
|
|
|
UPDATE creative_rejection_cleanup_item AS item
|
|
UPDATE creative_rejection_cleanup_item AS item
|
|
|
JOIN (
|
|
JOIN (
|
|
|
SELECT %s AS cleanup_rule_type,
|
|
SELECT %s AS cleanup_rule_type,
|
|
|
|
|
+ %s AS current_day_cost_fen,
|
|
|
%s AS account_name,
|
|
%s AS account_name,
|
|
|
%s AS agent_name,
|
|
%s AS agent_name,
|
|
|
%s AS creative_created_at,
|
|
%s AS creative_created_at,
|
|
@@ -1252,6 +1310,7 @@ def upsert_cleanup_candidate(
|
|
|
AND incoming.check_date=item.check_date
|
|
AND incoming.check_date=item.check_date
|
|
|
SET item.notified_at=CASE
|
|
SET item.notified_at=CASE
|
|
|
WHEN NOT (item.cleanup_rule_type <=> incoming.cleanup_rule_type)
|
|
WHEN NOT (item.cleanup_rule_type <=> incoming.cleanup_rule_type)
|
|
|
|
|
+ OR NOT (item.current_day_cost_fen <=> incoming.current_day_cost_fen)
|
|
|
OR NOT (item.account_name <=> COALESCE(NULLIF(incoming.account_name,''), item.account_name))
|
|
OR NOT (item.account_name <=> COALESCE(NULLIF(incoming.account_name,''), item.account_name))
|
|
|
OR NOT (item.agent_name <=> COALESCE(NULLIF(incoming.agent_name,''), item.agent_name))
|
|
OR NOT (item.agent_name <=> COALESCE(NULLIF(incoming.agent_name,''), item.agent_name))
|
|
|
OR NOT (item.creative_created_at <=> incoming.creative_created_at)
|
|
OR NOT (item.creative_created_at <=> incoming.creative_created_at)
|
|
@@ -1268,6 +1327,7 @@ def upsert_cleanup_candidate(
|
|
|
NOW()
|
|
NOW()
|
|
|
)
|
|
)
|
|
|
WHEN NOT (item.cleanup_rule_type <=> incoming.cleanup_rule_type)
|
|
WHEN NOT (item.cleanup_rule_type <=> incoming.cleanup_rule_type)
|
|
|
|
|
+ OR NOT (item.current_day_cost_fen <=> incoming.current_day_cost_fen)
|
|
|
OR NOT (item.account_name <=> COALESCE(NULLIF(incoming.account_name,''), item.account_name))
|
|
OR NOT (item.account_name <=> COALESCE(NULLIF(incoming.account_name,''), item.account_name))
|
|
|
OR NOT (item.agent_name <=> COALESCE(NULLIF(incoming.agent_name,''), item.agent_name))
|
|
OR NOT (item.agent_name <=> COALESCE(NULLIF(incoming.agent_name,''), item.agent_name))
|
|
|
OR NOT (item.creative_created_at <=> incoming.creative_created_at)
|
|
OR NOT (item.creative_created_at <=> incoming.creative_created_at)
|
|
@@ -1278,6 +1338,7 @@ def upsert_cleanup_candidate(
|
|
|
END,
|
|
END,
|
|
|
item.operator_notified_at=CASE
|
|
item.operator_notified_at=CASE
|
|
|
WHEN NOT (item.cleanup_rule_type <=> incoming.cleanup_rule_type)
|
|
WHEN NOT (item.cleanup_rule_type <=> incoming.cleanup_rule_type)
|
|
|
|
|
+ OR NOT (item.current_day_cost_fen <=> incoming.current_day_cost_fen)
|
|
|
OR NOT (item.account_name <=> COALESCE(NULLIF(incoming.account_name,''), item.account_name))
|
|
OR NOT (item.account_name <=> COALESCE(NULLIF(incoming.account_name,''), item.account_name))
|
|
|
OR NOT (item.agent_name <=> COALESCE(NULLIF(incoming.agent_name,''), item.agent_name))
|
|
OR NOT (item.agent_name <=> COALESCE(NULLIF(incoming.agent_name,''), item.agent_name))
|
|
|
OR NOT (item.creative_created_at <=> incoming.creative_created_at)
|
|
OR NOT (item.creative_created_at <=> incoming.creative_created_at)
|
|
@@ -1288,6 +1349,7 @@ def upsert_cleanup_candidate(
|
|
|
END,
|
|
END,
|
|
|
item.cleanup_status=CASE
|
|
item.cleanup_status=CASE
|
|
|
WHEN NOT (item.cleanup_rule_type <=> incoming.cleanup_rule_type)
|
|
WHEN NOT (item.cleanup_rule_type <=> incoming.cleanup_rule_type)
|
|
|
|
|
+ OR NOT (item.current_day_cost_fen <=> incoming.current_day_cost_fen)
|
|
|
OR NOT (item.account_name <=> COALESCE(NULLIF(incoming.account_name,''), item.account_name))
|
|
OR NOT (item.account_name <=> COALESCE(NULLIF(incoming.account_name,''), item.account_name))
|
|
|
OR NOT (item.agent_name <=> COALESCE(NULLIF(incoming.agent_name,''), item.agent_name))
|
|
OR NOT (item.agent_name <=> COALESCE(NULLIF(incoming.agent_name,''), item.agent_name))
|
|
|
OR NOT (item.creative_created_at <=> incoming.creative_created_at)
|
|
OR NOT (item.creative_created_at <=> incoming.creative_created_at)
|
|
@@ -1298,6 +1360,7 @@ def upsert_cleanup_candidate(
|
|
|
ELSE item.cleanup_status
|
|
ELSE item.cleanup_status
|
|
|
END,
|
|
END,
|
|
|
item.cleanup_rule_type=incoming.cleanup_rule_type,
|
|
item.cleanup_rule_type=incoming.cleanup_rule_type,
|
|
|
|
|
+ item.current_day_cost_fen=incoming.current_day_cost_fen,
|
|
|
item.account_name=COALESCE(
|
|
item.account_name=COALESCE(
|
|
|
NULLIF(incoming.account_name,''), item.account_name
|
|
NULLIF(incoming.account_name,''), item.account_name
|
|
|
),
|
|
),
|
|
@@ -1536,6 +1599,7 @@ def update_cleanup_item(item_id: int, **values: Any) -> bool:
|
|
|
"target_component_ids_json",
|
|
"target_component_ids_json",
|
|
|
"target_element_ids_json",
|
|
"target_element_ids_json",
|
|
|
"recent_cost_fen",
|
|
"recent_cost_fen",
|
|
|
|
|
+ "current_day_cost_fen",
|
|
|
"cost_start_date",
|
|
"cost_start_date",
|
|
|
"cost_end_date",
|
|
"cost_end_date",
|
|
|
"action_reason",
|
|
"action_reason",
|
|
@@ -2041,6 +2105,17 @@ def _write_report(
|
|
|
if recent_cost_fen is None
|
|
if recent_cost_fen is None
|
|
|
else f"{int(recent_cost_fen) / 100:.2f}"
|
|
else f"{int(recent_cost_fen) / 100:.2f}"
|
|
|
)
|
|
)
|
|
|
|
|
+ current_day_cost_fen = row.get("current_day_cost_fen")
|
|
|
|
|
+ current_day_cost_yuan = (
|
|
|
|
|
+ ""
|
|
|
|
|
+ if current_day_cost_fen is None
|
|
|
|
|
+ else f"{int(current_day_cost_fen) / 100:.2f}"
|
|
|
|
|
+ )
|
|
|
|
|
+ total_review_cost_yuan = (
|
|
|
|
|
+ ""
|
|
|
|
|
+ if recent_cost_fen is None or current_day_cost_fen is None
|
|
|
|
|
+ else f"{(int(recent_cost_fen) + int(current_day_cost_fen)) / 100:.2f}"
|
|
|
|
|
+ )
|
|
|
rule_type = str(row.get("cleanup_rule_type") or REVIEW_DENIED_RULE)
|
|
rule_type = str(row.get("cleanup_rule_type") or REVIEW_DENIED_RULE)
|
|
|
is_performance = _is_performance_rule(rule_type)
|
|
is_performance = _is_performance_rule(rule_type)
|
|
|
cost_start = _display_date(row.get("cost_start_date"))
|
|
cost_start = _display_date(row.get("cost_start_date"))
|
|
@@ -2098,6 +2173,12 @@ def _write_report(
|
|
|
"近3天累计历史消耗(元)": (
|
|
"近3天累计历史消耗(元)": (
|
|
|
"" if is_performance else recent_cost_yuan
|
|
"" if is_performance else recent_cost_yuan
|
|
|
),
|
|
),
|
|
|
|
|
+ "当日消耗(元)": (
|
|
|
|
|
+ "" if is_performance else current_day_cost_yuan
|
|
|
|
|
+ ),
|
|
|
|
|
+ "近3天及当日累计消耗(元)": (
|
|
|
|
|
+ "" if is_performance else total_review_cost_yuan
|
|
|
|
|
+ ),
|
|
|
"消耗日期范围": (
|
|
"消耗日期范围": (
|
|
|
f"{cost_start} ~ {cost_end}"
|
|
f"{cost_start} ~ {cost_end}"
|
|
|
if not is_performance and cost_start and cost_end else ""
|
|
if not is_performance and cost_start and cost_end else ""
|
|
@@ -2152,6 +2233,8 @@ def _write_report(
|
|
|
"广告近3日日均消耗(元)": 24,
|
|
"广告近3日日均消耗(元)": 24,
|
|
|
"规则指标日期范围": 24,
|
|
"规则指标日期范围": 24,
|
|
|
"近3天累计历史消耗(元)": 22,
|
|
"近3天累计历史消耗(元)": 22,
|
|
|
|
|
+ "当日消耗(元)": 16,
|
|
|
|
|
+ "近3天及当日累计消耗(元)": 24,
|
|
|
"消耗日期范围": 24,
|
|
"消耗日期范围": 24,
|
|
|
"执行操作": 16,
|
|
"执行操作": 16,
|
|
|
"操作判断原因": 60,
|
|
"操作判断原因": 60,
|
|
@@ -2194,6 +2277,7 @@ def write_cleanup_reports(
|
|
|
str(row.get("cleanup_rule_type") or ""),
|
|
str(row.get("cleanup_rule_type") or ""),
|
|
|
str(row.get("cleanup_status") or ""),
|
|
str(row.get("cleanup_status") or ""),
|
|
|
str(row.get("recent_cost_fen")),
|
|
str(row.get("recent_cost_fen")),
|
|
|
|
|
+ str(row.get("current_day_cost_fen")),
|
|
|
str(row.get("metric_impressions")),
|
|
str(row.get("metric_impressions")),
|
|
|
str(row.get("metric_daily_avg_impressions")),
|
|
str(row.get("metric_daily_avg_impressions")),
|
|
|
_display_date(row.get("cost_end_date")),
|
|
_display_date(row.get("cost_end_date")),
|
|
@@ -2259,6 +2343,7 @@ def write_cleanup_operator_summary(
|
|
|
str(row.get("cleanup_rule_type") or ""),
|
|
str(row.get("cleanup_rule_type") or ""),
|
|
|
str(row.get("cleanup_status") or ""),
|
|
str(row.get("cleanup_status") or ""),
|
|
|
str(row.get("recent_cost_fen")),
|
|
str(row.get("recent_cost_fen")),
|
|
|
|
|
+ str(row.get("current_day_cost_fen")),
|
|
|
str(row.get("metric_impressions")),
|
|
str(row.get("metric_impressions")),
|
|
|
str(row.get("metric_daily_avg_impressions")),
|
|
str(row.get("metric_daily_avg_impressions")),
|
|
|
_display_date(row.get("cost_end_date")),
|
|
_display_date(row.get("cost_end_date")),
|
|
@@ -2471,6 +2556,7 @@ def _scan_one_account(
|
|
|
str | None,
|
|
str | None,
|
|
|
str | None,
|
|
str | None,
|
|
|
dict[str, int],
|
|
dict[str, int],
|
|
|
|
|
+ dict[int, int],
|
|
|
]:
|
|
]:
|
|
|
account_id = int(account["account_id"])
|
|
account_id = int(account["account_id"])
|
|
|
empty_source_diagnostics = {
|
|
empty_source_diagnostics = {
|
|
@@ -2549,6 +2635,7 @@ def _scan_one_account(
|
|
|
"creative review scan failed account=%d", account_id
|
|
"creative review scan failed account=%d", account_id
|
|
|
)
|
|
)
|
|
|
cost_by_id: dict[int, int] = {}
|
|
cost_by_id: dict[int, int] = {}
|
|
|
|
|
+ current_day_cost_by_id: dict[int, int] = {}
|
|
|
spend_error = None
|
|
spend_error = None
|
|
|
partial_ids = [
|
|
partial_ids = [
|
|
|
int(row["dynamic_creative_id"])
|
|
int(row["dynamic_creative_id"])
|
|
@@ -2579,6 +2666,32 @@ def _scan_one_account(
|
|
|
spend_start_date,
|
|
spend_start_date,
|
|
|
spend_end_date,
|
|
spend_end_date,
|
|
|
)
|
|
)
|
|
|
|
|
+ if spend_error is None:
|
|
|
|
|
+ spend_current_day = (
|
|
|
|
|
+ current_day_date
|
|
|
|
|
+ if current_day_date is not None
|
|
|
|
|
+ else spend_end_date + timedelta(days=1)
|
|
|
|
|
+ )
|
|
|
|
|
+ try:
|
|
|
|
|
+ current_day_cost_by_id = tencent.get_dynamic_creative_costs(
|
|
|
|
|
+ account_id,
|
|
|
|
|
+ partial_ids,
|
|
|
|
|
+ spend_current_day,
|
|
|
|
|
+ spend_current_day,
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception as exc:
|
|
|
|
|
+ spend_error = str(exc)
|
|
|
|
|
+ if _is_access_token_unavailable_error(exc):
|
|
|
|
|
+ logger.warning(
|
|
|
|
|
+ "account scan skipped: access token unavailable account=%d",
|
|
|
|
|
+ account_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ return _token_skipped_scan_result(account_id, exc)
|
|
|
|
|
+ logger.exception(
|
|
|
|
|
+ "creative current-day cost scan failed account=%d date=%s",
|
|
|
|
|
+ account_id,
|
|
|
|
|
+ spend_current_day,
|
|
|
|
|
+ )
|
|
|
performance_metrics: dict[int, dict[str, Any]] = {}
|
|
performance_metrics: dict[int, dict[str, Any]] = {}
|
|
|
performance_error = None
|
|
performance_error = None
|
|
|
source_creatives = performance_source_creatives or {}
|
|
source_creatives = performance_source_creatives or {}
|
|
@@ -2878,6 +2991,7 @@ def _scan_one_account(
|
|
|
ad_metric_error,
|
|
ad_metric_error,
|
|
|
review_error,
|
|
review_error,
|
|
|
source_diagnostics,
|
|
source_diagnostics,
|
|
|
|
|
+ current_day_cost_by_id,
|
|
|
)
|
|
)
|
|
|
except Exception as exc:
|
|
except Exception as exc:
|
|
|
return (
|
|
return (
|
|
@@ -2894,6 +3008,7 @@ def _scan_one_account(
|
|
|
None,
|
|
None,
|
|
|
None,
|
|
None,
|
|
|
empty_source_diagnostics,
|
|
empty_source_diagnostics,
|
|
|
|
|
+ {},
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@@ -3095,6 +3210,7 @@ def run_rejected_creative_cleanup(
|
|
|
days=ad_window_days - 1
|
|
days=ad_window_days - 1
|
|
|
)
|
|
)
|
|
|
cost_threshold_fen = partial_creative_cost_threshold_fen()
|
|
cost_threshold_fen = partial_creative_cost_threshold_fen()
|
|
|
|
|
+ protection_days = partial_creative_protection_days()
|
|
|
wechat_cost_threshold_fen = wechat_mini_program_cost_threshold_fen()
|
|
wechat_cost_threshold_fen = wechat_mini_program_cost_threshold_fen()
|
|
|
|
|
|
|
|
# 审核范围/代理上下文与未起量 ODPS 范围相互隔离。审核数据异常时,
|
|
# 审核范围/代理上下文与未起量 ODPS 范围相互隔离。审核数据异常时,
|
|
@@ -3151,7 +3267,10 @@ def run_rejected_creative_cleanup(
|
|
|
performance_account_metadata: dict[int, dict[str, str]] = {}
|
|
performance_account_metadata: dict[int, dict[str, str]] = {}
|
|
|
performance_account_metadata_error: str | None = None
|
|
performance_account_metadata_error: str | None = None
|
|
|
underperformance_enabled = performance_enabled or ad_cleanup_enabled
|
|
underperformance_enabled = performance_enabled or ad_cleanup_enabled
|
|
|
- if underperformance_enabled:
|
|
|
|
|
|
|
+ # 部分投放审核异常也必须读取创意搭建时间。该库存查询与未起量规则共用,
|
|
|
|
|
+ # 但仅未起量规则可以据此扩展账户扫描范围。
|
|
|
|
|
+ inventory_required = underperformance_enabled or bool(review_account_ids)
|
|
|
|
|
+ if inventory_required:
|
|
|
try:
|
|
try:
|
|
|
performance_inventory = fetch_active_creative_inventory(odps_client)
|
|
performance_inventory = fetch_active_creative_inventory(odps_client)
|
|
|
except Exception as exc:
|
|
except Exception as exc:
|
|
@@ -3161,12 +3280,18 @@ def run_rejected_creative_cleanup(
|
|
|
for source in performance_inventory:
|
|
for source in performance_inventory:
|
|
|
account_id = int(source["account_id"])
|
|
account_id = int(source["account_id"])
|
|
|
creative_id = int(source["creative_id"])
|
|
creative_id = int(source["creative_id"])
|
|
|
|
|
+ if (
|
|
|
|
|
+ not underperformance_enabled
|
|
|
|
|
+ and account_id not in review_account_ids
|
|
|
|
|
+ ):
|
|
|
|
|
+ continue
|
|
|
performance_sources_by_account[account_id][creative_id] = source
|
|
performance_sources_by_account[account_id][creative_id] = source
|
|
|
- accounts_by_id.setdefault(
|
|
|
|
|
- account_id,
|
|
|
|
|
- {"account_id": account_id, "account_name": ""},
|
|
|
|
|
- )
|
|
|
|
|
- if performance_sources_by_account:
|
|
|
|
|
|
|
+ if underperformance_enabled:
|
|
|
|
|
+ accounts_by_id.setdefault(
|
|
|
|
|
+ account_id,
|
|
|
|
|
+ {"account_id": account_id, "account_name": ""},
|
|
|
|
|
+ )
|
|
|
|
|
+ if underperformance_enabled and performance_sources_by_account:
|
|
|
try:
|
|
try:
|
|
|
performance_account_metadata = fetch_tencent_account_metadata(
|
|
performance_account_metadata = fetch_tencent_account_metadata(
|
|
|
odps_client
|
|
odps_client
|
|
@@ -3359,9 +3484,11 @@ def run_rejected_creative_cleanup(
|
|
|
ad_metric_error,
|
|
ad_metric_error,
|
|
|
review_error,
|
|
review_error,
|
|
|
source_diagnostics,
|
|
source_diagnostics,
|
|
|
|
|
+ current_day_cost_by_id,
|
|
|
) = future.result()
|
|
) = future.result()
|
|
|
except Exception as exc:
|
|
except Exception as exc:
|
|
|
creatives, ads, raw_by_id, cost_by_id = [], {}, {}, {}
|
|
creatives, ads, raw_by_id, cost_by_id = [], {}, {}, {}
|
|
|
|
|
+ current_day_cost_by_id = {}
|
|
|
spend_error, account_scanned = None, 0
|
|
spend_error, account_scanned = None, 0
|
|
|
error = f"account={account_id} scan failed: {exc}"
|
|
error = f"account={account_id} scan failed: {exc}"
|
|
|
performance_metrics, performance_error = {}, None
|
|
performance_metrics, performance_error = {}, None
|
|
@@ -3424,6 +3551,7 @@ def run_rejected_creative_cleanup(
|
|
|
ads,
|
|
ads,
|
|
|
raw_by_id,
|
|
raw_by_id,
|
|
|
cost_by_id,
|
|
cost_by_id,
|
|
|
|
|
+ current_day_cost_by_id,
|
|
|
spend_error,
|
|
spend_error,
|
|
|
performance_metrics,
|
|
performance_metrics,
|
|
|
ad_metrics,
|
|
ad_metrics,
|
|
@@ -3457,6 +3585,7 @@ def run_rejected_creative_cleanup(
|
|
|
ads,
|
|
ads,
|
|
|
raw_by_id,
|
|
raw_by_id,
|
|
|
cost_by_id,
|
|
cost_by_id,
|
|
|
|
|
+ current_day_cost_by_id,
|
|
|
spend_error,
|
|
spend_error,
|
|
|
performance_metrics,
|
|
performance_metrics,
|
|
|
_ad_metrics,
|
|
_ad_metrics,
|
|
@@ -3478,6 +3607,10 @@ def run_rejected_creative_cleanup(
|
|
|
creative,
|
|
creative,
|
|
|
raw_result,
|
|
raw_result,
|
|
|
recent_cost_fen=cost_by_id.get(creative_id),
|
|
recent_cost_fen=cost_by_id.get(creative_id),
|
|
|
|
|
+ current_day_cost_fen=current_day_cost_by_id.get(creative_id),
|
|
|
|
|
+ creative_created_at=performance_creation_times.get(key),
|
|
|
|
|
+ as_of_datetime=effective_now,
|
|
|
|
|
+ protection_days=protection_days,
|
|
|
cost_threshold_fen=cost_threshold_fen,
|
|
cost_threshold_fen=cost_threshold_fen,
|
|
|
wechat_cost_threshold_fen=wechat_cost_threshold_fen,
|
|
wechat_cost_threshold_fen=wechat_cost_threshold_fen,
|
|
|
spend_error=(spend_error if is_partial else None),
|
|
spend_error=(spend_error if is_partial else None),
|
|
@@ -3579,6 +3712,7 @@ def run_rejected_creative_cleanup(
|
|
|
ads,
|
|
ads,
|
|
|
raw_by_id,
|
|
raw_by_id,
|
|
|
cost_by_id,
|
|
cost_by_id,
|
|
|
|
|
+ current_day_cost_by_id,
|
|
|
spend_error,
|
|
spend_error,
|
|
|
performance_metrics,
|
|
performance_metrics,
|
|
|
ad_metrics,
|
|
ad_metrics,
|
|
@@ -3589,6 +3723,7 @@ def run_rejected_creative_cleanup(
|
|
|
ads,
|
|
ads,
|
|
|
raw_by_id,
|
|
raw_by_id,
|
|
|
cost_by_id,
|
|
cost_by_id,
|
|
|
|
|
+ current_day_cost_by_id,
|
|
|
spend_error,
|
|
spend_error,
|
|
|
performance_metrics,
|
|
performance_metrics,
|
|
|
ad_metrics,
|
|
ad_metrics,
|
|
@@ -3765,6 +3900,7 @@ def run_rejected_creative_cleanup(
|
|
|
ads,
|
|
ads,
|
|
|
_raw_by_id,
|
|
_raw_by_id,
|
|
|
_cost_by_id,
|
|
_cost_by_id,
|
|
|
|
|
+ _current_day_cost_by_id,
|
|
|
_spend_error,
|
|
_spend_error,
|
|
|
_performance_metrics,
|
|
_performance_metrics,
|
|
|
ad_metrics,
|
|
ad_metrics,
|
|
@@ -4242,27 +4378,28 @@ def run_rejected_creative_cleanup(
|
|
|
),
|
|
),
|
|
|
None,
|
|
None,
|
|
|
)
|
|
)
|
|
|
- fresh_cost_fen = None
|
|
|
|
|
- fresh_spend_error = None
|
|
|
|
|
- if approval_status == CREATIVE_PARTIAL_NORMAL_STATUS:
|
|
|
|
|
- try:
|
|
|
|
|
- fresh_cost_fen = (
|
|
|
|
|
- delete_client.get_dynamic_creative_costs(
|
|
|
|
|
- account_id,
|
|
|
|
|
- [creative_id],
|
|
|
|
|
- spend_start_date,
|
|
|
|
|
- spend_end_date,
|
|
|
|
|
- ).get(creative_id, 0)
|
|
|
|
|
- )
|
|
|
|
|
- except Exception as spend_exc:
|
|
|
|
|
- fresh_spend_error = str(spend_exc)
|
|
|
|
|
|
|
+ # 审核异常删除复用本轮账户扫描已经查询并落库的消耗,
|
|
|
|
|
+ # 写锁内只回读创意状态和正式审核结果,不重复请求消耗报表。
|
|
|
|
|
+ confirmed_review_action = confirmed_actions.get(
|
|
|
|
|
+ (account_id, creative_id),
|
|
|
|
|
+ {},
|
|
|
|
|
+ )
|
|
|
fresh_action = determine_cleanup_action(
|
|
fresh_action = determine_cleanup_action(
|
|
|
before,
|
|
before,
|
|
|
fresh_raw,
|
|
fresh_raw,
|
|
|
- recent_cost_fen=fresh_cost_fen,
|
|
|
|
|
|
|
+ recent_cost_fen=confirmed_review_action.get(
|
|
|
|
|
+ "recent_cost_fen"
|
|
|
|
|
+ ),
|
|
|
|
|
+ current_day_cost_fen=confirmed_review_action.get(
|
|
|
|
|
+ "current_day_cost_fen"
|
|
|
|
|
+ ),
|
|
|
|
|
+ creative_created_at=performance_creation_times.get(
|
|
|
|
|
+ (account_id, creative_id)
|
|
|
|
|
+ ),
|
|
|
|
|
+ as_of_datetime=effective_now,
|
|
|
|
|
+ protection_days=protection_days,
|
|
|
cost_threshold_fen=cost_threshold_fen,
|
|
cost_threshold_fen=cost_threshold_fen,
|
|
|
wechat_cost_threshold_fen=wechat_cost_threshold_fen,
|
|
wechat_cost_threshold_fen=wechat_cost_threshold_fen,
|
|
|
- spend_error=fresh_spend_error,
|
|
|
|
|
)
|
|
)
|
|
|
if not _same_cleanup_action(
|
|
if not _same_cleanup_action(
|
|
|
action,
|
|
action,
|
|
@@ -4282,9 +4419,18 @@ def run_rejected_creative_cleanup(
|
|
|
target_component_ids_json="[]",
|
|
target_component_ids_json="[]",
|
|
|
target_element_ids_json="[]",
|
|
target_element_ids_json="[]",
|
|
|
recent_cost_fen=fresh_action.get("recent_cost_fen"),
|
|
recent_cost_fen=fresh_action.get("recent_cost_fen"),
|
|
|
|
|
+ current_day_cost_fen=fresh_action.get(
|
|
|
|
|
+ "current_day_cost_fen"
|
|
|
|
|
+ ),
|
|
|
cost_start_date=spend_start_date,
|
|
cost_start_date=spend_start_date,
|
|
|
cost_end_date=spend_end_date,
|
|
cost_end_date=spend_end_date,
|
|
|
action_reason=fresh_action["action_reason"],
|
|
action_reason=fresh_action["action_reason"],
|
|
|
|
|
+ creative_created_at=fresh_action.get(
|
|
|
|
|
+ "creative_created_at"
|
|
|
|
|
+ ),
|
|
|
|
|
+ creative_age_days=fresh_action.get(
|
|
|
|
|
+ "creative_age_days"
|
|
|
|
|
+ ),
|
|
|
reject_reason=_reject_reason(
|
|
reject_reason=_reject_reason(
|
|
|
fresh_raw,
|
|
fresh_raw,
|
|
|
str(before.get("system_status") or ""),
|
|
str(before.get("system_status") or ""),
|