|
@@ -51,6 +51,7 @@ from config import (
|
|
|
# 监测链接 / 反馈 ID
|
|
# 监测链接 / 反馈 ID
|
|
|
get_account_feedback_id,
|
|
get_account_feedback_id,
|
|
|
)
|
|
)
|
|
|
|
|
+from tools.delivery_config import BID_MODE_MAX_CONVERSION
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
@@ -80,6 +81,9 @@ class AdCandidate:
|
|
|
# 版位定投场景 wechat_position(2026-06-09 1 账户 N 广告差异化机制)
|
|
# 版位定投场景 wechat_position(2026-06-09 1 账户 N 广告差异化机制)
|
|
|
# None = 无定投(走 site_set 默认全场景)/ list[int] = 勾选具体场景 ID
|
|
# None = 无定投(走 site_set 默认全场景)/ list[int] = 勾选具体场景 ID
|
|
|
wechat_position: Optional[list] = None
|
|
wechat_position: Optional[list] = None
|
|
|
|
|
+ bid_scene: str = "average_cost"
|
|
|
|
|
+ custom_cost_cap_fen: Optional[int] = None
|
|
|
|
|
+ automatic_site_enabled: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
# ═══════════════════════════════════════════
|
|
# ═══════════════════════════════════════════
|
|
@@ -94,6 +98,8 @@ def compute_fingerprint(
|
|
|
age: list,
|
|
age: list,
|
|
|
geo_regions: list,
|
|
geo_regions: list,
|
|
|
wechat_position: Optional[list] = None,
|
|
wechat_position: Optional[list] = None,
|
|
|
|
|
+ automatic_site_enabled: bool = False,
|
|
|
|
|
+ bid_scene: str = "average_cost",
|
|
|
) -> str:
|
|
) -> str:
|
|
|
"""计算"营销内容指纹"用于本地唯一性预校验。
|
|
"""计算"营销内容指纹"用于本地唯一性预校验。
|
|
|
|
|
|
|
@@ -108,6 +114,8 @@ def compute_fingerprint(
|
|
|
"age": age,
|
|
"age": age,
|
|
|
"geo_regions": sorted(geo_regions),
|
|
"geo_regions": sorted(geo_regions),
|
|
|
"wechat_position": sorted(wechat_position) if wechat_position else None,
|
|
"wechat_position": sorted(wechat_position) if wechat_position else None,
|
|
|
|
|
+ "automatic_site_enabled": automatic_site_enabled,
|
|
|
|
|
+ "bid_scene": bid_scene,
|
|
|
}
|
|
}
|
|
|
return hashlib.md5(
|
|
return hashlib.md5(
|
|
|
json.dumps(payload, sort_keys=True, ensure_ascii=False).encode("utf-8")
|
|
json.dumps(payload, sort_keys=True, ensure_ascii=False).encode("utf-8")
|
|
@@ -194,12 +202,14 @@ def enumerate_new_ad_candidates(
|
|
|
tier_label = cfg["audience_tier_label"]
|
|
tier_label = cfg["audience_tier_label"]
|
|
|
custom_audience = [pack_id] if pack_id else None
|
|
custom_audience = [pack_id] if pack_id else None
|
|
|
bid_amount_fen = cfg["bid_amount_fen"]
|
|
bid_amount_fen = cfg["bid_amount_fen"]
|
|
|
|
|
+ automatic_site_enabled = bool(cfg.get("automatic_site_enabled"))
|
|
|
|
|
+ bid_scene = cfg.get("bid_scene") or "average_cost"
|
|
|
|
|
|
|
|
# 差异化策略(2026-06-09 用户确认 + 2026-06-11 缩减):
|
|
# 差异化策略(2026-06-09 用户确认 + 2026-06-11 缩减):
|
|
|
# 广告 #1 wechat_position=None (无定投,走 site_set 默认全场景)
|
|
# 广告 #1 wechat_position=None (无定投,走 site_set 默认全场景)
|
|
|
# 广告 #2 wechat_position=WECHAT_POSITION_TARGETED_PRESET(公众号内容 + 小程序位置)
|
|
# 广告 #2 wechat_position=WECHAT_POSITION_TARGETED_PRESET(公众号内容 + 小程序位置)
|
|
|
# 同 site_set 同 targeting 但 wechat_position 不同 → 腾讯唯一性绕过(参考 77868332 实测)
|
|
# 同 site_set 同 targeting 但 wechat_position 不同 → 腾讯唯一性绕过(参考 77868332 实测)
|
|
|
- wechat_position_variants = [None, WECHAT_POSITION_TARGETED_PRESET]
|
|
|
|
|
|
|
+ wechat_position_variants = [None] if automatic_site_enabled else [None, WECHAT_POSITION_TARGETED_PRESET]
|
|
|
|
|
|
|
|
candidates: list[AdCandidate] = []
|
|
candidates: list[AdCandidate] = []
|
|
|
site_set = cfg["site_set"]
|
|
site_set = cfg["site_set"]
|
|
@@ -214,6 +224,8 @@ def enumerate_new_ad_candidates(
|
|
|
age=cfg["age"],
|
|
age=cfg["age"],
|
|
|
geo_regions=cfg["region_ids"],
|
|
geo_regions=cfg["region_ids"],
|
|
|
wechat_position=wp,
|
|
wechat_position=wp,
|
|
|
|
|
+ automatic_site_enabled=automatic_site_enabled,
|
|
|
|
|
+ bid_scene=bid_scene,
|
|
|
)
|
|
)
|
|
|
if fingerprint in existing_fingerprints:
|
|
if fingerprint in existing_fingerprints:
|
|
|
logger.info(
|
|
logger.info(
|
|
@@ -233,6 +245,9 @@ def enumerate_new_ad_candidates(
|
|
|
site_set=site_set,
|
|
site_set=site_set,
|
|
|
custom_audience=custom_audience,
|
|
custom_audience=custom_audience,
|
|
|
bid_amount_fen=bid_amount_fen,
|
|
bid_amount_fen=bid_amount_fen,
|
|
|
|
|
+ bid_scene=bid_scene,
|
|
|
|
|
+ custom_cost_cap_fen=cfg.get("custom_cost_cap_fen"),
|
|
|
|
|
+ automatic_site_enabled=automatic_site_enabled,
|
|
|
audience_tier_label=tier_label,
|
|
audience_tier_label=tier_label,
|
|
|
age=cfg["age"],
|
|
age=cfg["age"],
|
|
|
location_types=cfg["location_types"],
|
|
location_types=cfg["location_types"],
|
|
@@ -312,9 +327,16 @@ def build_ad_request_body(
|
|
|
"conversion_id": DEFAULT_CONVERSION_ID,
|
|
"conversion_id": DEFAULT_CONVERSION_ID,
|
|
|
# === 出价 / 计费(SOP 稳定拿量)===
|
|
# === 出价 / 计费(SOP 稳定拿量)===
|
|
|
"bid_mode": BID_MODE,
|
|
"bid_mode": BID_MODE,
|
|
|
- "smart_bid_type": SMART_BID_TYPE,
|
|
|
|
|
"bid_strategy": BID_STRATEGY,
|
|
"bid_strategy": BID_STRATEGY,
|
|
|
- "bid_amount": candidate.bid_amount_fen,
|
|
|
|
|
|
|
+ "smart_bid_type": (
|
|
|
|
|
+ "SMART_BID_TYPE_SYSTEMATIC"
|
|
|
|
|
+ if candidate.bid_scene == BID_MODE_MAX_CONVERSION
|
|
|
|
|
+ else SMART_BID_TYPE
|
|
|
|
|
+ ),
|
|
|
|
|
+ "bid_amount": (
|
|
|
|
|
+ 0 if candidate.bid_scene == BID_MODE_MAX_CONVERSION
|
|
|
|
|
+ else candidate.bid_amount_fen
|
|
|
|
|
+ ),
|
|
|
"daily_budget": candidate.daily_budget_fen,
|
|
"daily_budget": candidate.daily_budget_fen,
|
|
|
"auto_acquisition_enabled": AUTO_ACQUISITION_ENABLED,
|
|
"auto_acquisition_enabled": AUTO_ACQUISITION_ENABLED,
|
|
|
"auto_derived_creative_enabled": AUTO_DERIVED_CREATIVE_ENABLED,
|
|
"auto_derived_creative_enabled": AUTO_DERIVED_CREATIVE_ENABLED,
|
|
@@ -324,8 +346,7 @@ def build_ad_request_body(
|
|
|
"end_date": _add_years(begin_date, 1),
|
|
"end_date": _add_years(begin_date, 1),
|
|
|
"time_series": candidate.time_series,
|
|
"time_series": candidate.time_series,
|
|
|
# === 版位 ===
|
|
# === 版位 ===
|
|
|
- "automatic_site_enabled": False,
|
|
|
|
|
- "site_set": candidate.site_set,
|
|
|
|
|
|
|
+ "automatic_site_enabled": candidate.automatic_site_enabled,
|
|
|
# 搜索场景扩量 · 定向拓展(用户 2026-06-05 确认:关)
|
|
# 搜索场景扩量 · 定向拓展(用户 2026-06-05 确认:关)
|
|
|
"search_expand_targeting_switch": SEARCH_EXPAND_TARGETING_SWITCH,
|
|
"search_expand_targeting_switch": SEARCH_EXPAND_TARGETING_SWITCH,
|
|
|
# === 定向 ===
|
|
# === 定向 ===
|
|
@@ -335,10 +356,19 @@ def build_ad_request_body(
|
|
|
"smart_targeting_mode": SMART_TARGETING_MODE,
|
|
"smart_targeting_mode": SMART_TARGETING_MODE,
|
|
|
"targeting": targeting,
|
|
"targeting": targeting,
|
|
|
}
|
|
}
|
|
|
|
|
+ if not candidate.automatic_site_enabled:
|
|
|
|
|
+ body["site_set"] = candidate.site_set
|
|
|
|
|
+ if candidate.bid_scene == BID_MODE_MAX_CONVERSION:
|
|
|
|
|
+ if candidate.custom_cost_cap_fen is None:
|
|
|
|
|
+ raise ValueError(
|
|
|
|
|
+ f"account_id {candidate.account_id} 最大转化量缺少 custom_cost_cap_fen"
|
|
|
|
|
+ )
|
|
|
|
|
+ body["cost_constraint_scene"] = "COST_CONSTRAINT_SCENE_OPEN"
|
|
|
|
|
+ body["custom_cost_cap"] = candidate.custom_cost_cap_fen
|
|
|
# 版位定投场景 wechat_position(2026-06-10 实测修正:POST 时 scene_spec 是顶层字段)
|
|
# 版位定投场景 wechat_position(2026-06-10 实测修正:POST 时 scene_spec 是顶层字段)
|
|
|
# 之前误把 scene_spec 放 targeting 内 → 腾讯 reject code 12813 "包含不识别的参数 scene_spec"
|
|
# 之前误把 scene_spec 放 targeting 内 → 腾讯 reject code 12813 "包含不识别的参数 scene_spec"
|
|
|
# WebFetch /adgroups/add 文档确认:scene_spec 是顶层 struct,wechat_position 是它的子字段
|
|
# WebFetch /adgroups/add 文档确认:scene_spec 是顶层 struct,wechat_position 是它的子字段
|
|
|
- if candidate.wechat_position:
|
|
|
|
|
|
|
+ if candidate.wechat_position and not candidate.automatic_site_enabled:
|
|
|
body["scene_spec"] = {"wechat_position": list(candidate.wechat_position)}
|
|
body["scene_spec"] = {"wechat_position": list(candidate.wechat_position)}
|
|
|
|
|
|
|
|
if AUTO_ACQUISITION_ENABLED and AUTO_ACQUISITION_BUDGET_FEN:
|
|
if AUTO_ACQUISITION_ENABLED and AUTO_ACQUISITION_BUDGET_FEN:
|