config.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. """
  2. 广告智能决策引擎配置 — auto_put_ad_mini
  3. 运营可直接修改此文件调整决策参数。
  4. 当前模式:智能判断
  5. - 基于 动态 ROI (7日均值) 的精细化决策
  6. - AI 推理结合领域知识
  7. - 三级分类:零消耗待关停(规则)+ 待优化评估(智能)+ 正常运行(规则)
  8. """
  9. import os
  10. import logging
  11. from pathlib import Path
  12. from agent.core.runner import RunConfig, KnowledgeConfig
  13. # 初始化 logger(必须在使用前定义)
  14. logger = logging.getLogger(__name__)
  15. # 加载 .env 文件(如果存在)
  16. try:
  17. from dotenv import load_dotenv
  18. load_dotenv(Path(__file__).parent / ".env")
  19. except ImportError:
  20. pass
  21. # ═══════════════════════════════════════════
  22. # Agent 运行配置
  23. # ═══════════════════════════════════════════
  24. MAIN_CONFIG = RunConfig(
  25. model="anthropic/claude-sonnet-4.5",
  26. temperature=0.3,
  27. max_iterations=50,
  28. name="广告智能调控助手",
  29. tools=[
  30. "fetch_creative_data",
  31. "merge_creative_data",
  32. "calculate_roi_metrics",
  33. "calculate_creative_roi", # 创意级动态 ROI(广告级 pause 候选的二次细化)
  34. "calculate_portfolio_summary",
  35. "get_ads_for_review",
  36. "apply_decisions",
  37. "query_ad_detail", # Mode 2: 查询广告详情
  38. "modify_decisions", # Mode 3: 修改已有决策
  39. "validate_decisions",
  40. "generate_report",
  41. # 执行引擎 + IM 审批(已集成阻塞式审批流):
  42. "execute_decisions",
  43. "check_execution_feedback",
  44. "send_approval_request",
  45. "check_approval_status",
  46. "send_feishu_text_message", # 执行后向您同步 diff / 确认 / 质疑回应
  47. # 飞书文档(报告导入 & 分享):
  48. "import_to_feishu",
  49. # 注:曾考虑用内置 "agent" 工具按 tier 并行委托子 Agent,
  50. # 但框架的 agent 工具只返回文本 summary,主 Agent 拿不回结构化决策,
  51. # 会陷入"无法 apply"的死循环。直接在主 Agent 单次输出完成全部 decisions 更可靠。
  52. ],
  53. skills=[
  54. "ad-domain", # 业务模型:裂变模型、R值、ROI公式、字段定义
  55. "platform-rules", # 平台硬约束:oCPM学习期、调价上限、数据口径
  56. "decision-strategy", # 决策策略:角色 + 基准 + 候选标记 + 年龄策略 + 7种action + 输出规范
  57. "posterior-wisdom", # 后验经验:学习中断/降价恢复/创意冷启动/置信度分级
  58. ],
  59. extra_llm_params={"max_tokens": 32000},
  60. knowledge=KnowledgeConfig(
  61. enable_extraction=False, # 从决策过程中提取后验经验(投放后开启)
  62. enable_completion_extraction=False, # 完成后总结本轮经验(投放后开启)
  63. enable_injection=False, # 决策时自动注入相关历史经验(投放后开启)
  64. owner="ad_mini_team",
  65. ),
  66. )
  67. SKILLS_DIR = str(Path(__file__).parent / "skills")
  68. TRACE_STORE_PATH = ".trace"
  69. LOG_LEVEL = "INFO"
  70. LOG_FILE = None
  71. # ═══════════════════════════════════════════
  72. # 时区配置(海外部署)
  73. # ═══════════════════════════════════════════
  74. TIMEZONE = os.getenv("TZ", "UTC")
  75. logger.info(f"运行时区:{TIMEZONE}")
  76. # ═══════════════════════════════════════════
  77. # V3 数据窗口配置
  78. # ═══════════════════════════════════════════
  79. DATA_WINDOW_DAYS = 14 # 数据采集窗口:14 天历史数据
  80. ROI_CALCULATION_DAYS = 7 # 动态 ROI (7日均值) 计算窗口(保持 7 天)
  81. # ═══════════════════════════════════════════
  82. # V3 决策阈值(默认值,可被 SKILL 覆盖)
  83. # ═══════════════════════════════════════════
  84. MIN_DAILY_COST = 100 # 日消耗 >= 100元才参与 ROI 计算
  85. MIN_AD_AGE_DAYS = 3 # 广告创建 >= 3天才参与决策(与 min_periods 对齐)
  86. ROI_LOW_FACTOR = 0.75 # 动态 ROI (7日均值) < 全体均值 × 0.75 → 关停
  87. NO_SPEND_THRESHOLD = 10 # 7日消耗均值 < 10元 → 关停
  88. STABLE_SPEND_THRESHOLD = 100 # 稳定消耗定义:>100元/天
  89. # ═══════════════════════════════════════════
  90. # 出价调整配置
  91. # ═══════════════════════════════════════════
  92. BID_ADJUSTMENT_ENABLED = True
  93. BID_DOWN_ROI_FACTOR = 0.90 # ROI < 均值×0.90 → 考虑降价(低于渠道均值10%)
  94. BID_UP_ROI_FACTOR = 1.05 # ROI > 均值×1.05 → 考虑提价(高于渠道均值5%)
  95. BID_UP_MAX_SPEND = 1000 # 提价消耗上限:均值消耗<1000才提价(投手经验原文)
  96. BID_CHANGE_MIN_PCT = 0.03 # 最小调幅 3%(兼容旧代码)
  97. BID_CHANGE_MAX_PCT = 0.10 # 最大单次调幅 10%(兼容旧代码)
  98. BID_UP_MIN_PCT = 0.05 # 提价最小幅度 5%
  99. BID_UP_MAX_PCT = 0.10 # 提价最大幅度 10%
  100. BID_DOWN_MIN_PCT = 0.03 # 降价最小幅度 3%
  101. BID_DOWN_MAX_PCT = 0.05 # 降价最大幅度 5%
  102. BID_DOWN_MIN_SPEND = 500 # 降价消耗门槛:7日日均消耗≥500元
  103. BID_FLOOR_YUAN = 0.05 # 出价下限(元)
  104. BID_CEILING_YUAN = 1.00 # 出价上限(元)
  105. # 广告年龄分段(基于决策树图片)
  106. COLD_START_DAYS = 3 # 冷启动期(≤3天):极度保护,几乎不干预
  107. EARLY_GROWTH_DAYS = 7 # 早期成长期(4-7天):可提价放量(满足ROI+消耗条件)
  108. AD_AGE_MATURE = 7 # 成熟期(>7天):全面调控
  109. # 兼容性(已废弃)
  110. AD_AGE_NEWBORN = COLD_START_DAYS # 兼容旧代码
  111. CAUTIOUS_DAYS = EARLY_GROWTH_DAYS # 兼容旧代码
  112. # 高燃烧预警配置
  113. HIGH_BURN_AGE_THRESHOLD = 3 # 广告年龄>3天才检查
  114. HIGH_BURN_COST_THRESHOLD = 300 # 昨日消耗>300元触发预警
  115. ROI_LOW_MIN_YESTERDAY_COST = 300 # 关停消耗门槛:昨日消耗≥300才检查关停(投手经验2.4)
  116. # ═══════════════════════════════════════════
  117. # 创意级 pause 细化配置
  118. # ═══════════════════════════════════════════
  119. # 当广告级判 pause 时,先做创意级二次分析:全员低于阈值才真关广告,部分拖累只关差创意
  120. CREATIVE_PAUSE_ENABLED = True # 总开关:False 时全部走广告级 pause(降级路径)
  121. CREATIVE_MIN_COST_SHARE = 0.15 # 创意 7 日消耗占比 < 此值视为数据稀疏,不纳入"判死刑"
  122. CREATIVE_MIN_AGE_DAYS = 7 # 创意年龄 < 此值视为冷启动,不纳入"判死刑"(对齐广告级 EARLY_GROWTH_DAYS)
  123. CREATIVE_MIN_VALID_DAYS = 3 # 创意有效 ROI 数据天数 < 此值视为不充分,不纳入"判死刑"
  124. CREATIVE_MIN_REMAINING = 2 # 关停后剩余 eligible 创意数 < 此值,升级为广告级 pause
  125. CREATIVE_MAX_PAUSE_COST_SHARE = 0.80 # pause_targets 总占消耗 > 此值,本质是关广告,升级为广告级 pause
  126. CREATIVE_RATELIMIT_DAYS = 7 # 同一创意 7 天内不允许重复 pause
  127. # ═══════════════════════════════════════════
  128. # 安全护栏配置
  129. # ═══════════════════════════════════════════
  130. GUARDRAILS_ENABLED = True
  131. DRY_RUN_MODE = False # 关闭干运行,让护栏正常放行(实际执行由 EXECUTION_ENABLED 控制)
  132. MAX_ADJUSTMENTS_PER_AD_PER_DAY = 2
  133. MIN_ADJUSTMENT_INTERVAL_HOURS = 6
  134. MAX_DAILY_CUMULATIVE_CHANGE_PCT = 0.20 # 日累计调幅上限 20%
  135. MAX_DAILY_OPS = 10000 # 单日最多操作广告数(实际不限制)
  136. DATA_FRESHNESS_MAX_HOURS = 96 # 数据超过 96 小时视为过期(已从48小时放宽至96小时)
  137. # ═══════════════════════════════════════════
  138. # 执行引擎配置
  139. # ═══════════════════════════════════════════
  140. # 执行开关(优先级:数据库 > 环境变量 > 默认值False)
  141. EXECUTION_ENABLED = False
  142. try:
  143. from db import get_system_config
  144. _db_execution_enabled = get_system_config("execution_enabled", default=None)
  145. if _db_execution_enabled is not None:
  146. EXECUTION_ENABLED = _db_execution_enabled
  147. logger.info(f"✅ 从数据库读取执行开关:{EXECUTION_ENABLED}")
  148. else:
  149. # 降级到环境变量
  150. _env_execution_enabled = os.getenv("EXECUTION_ENABLED", "").strip().lower()
  151. if _env_execution_enabled:
  152. EXECUTION_ENABLED = _env_execution_enabled in ("true", "1", "yes")
  153. logger.info(f"从环境变量读取执行开关:{EXECUTION_ENABLED}")
  154. except Exception as e:
  155. logger.warning(f"⚠️ 数据库读取执行开关失败({e}),使用默认值:{EXECUTION_ENABLED}")
  156. API_QPS_LIMIT = 8 # 保守QPS(平台上限10)
  157. API_MAX_RETRIES = 3
  158. TIER1_MAX_CHANGE_PCT = 0.00 # Tier1自动执行已禁用(改为0%,所有操作都需审批)
  159. TIER3_MIN_DAILY_SPEND = 1500 # 高价值广告门槛(元/天)
  160. FEEDBACK_CHECK_HOURS = 6
  161. # ═══════════════════════════════════════════
  162. # IM 审批配置(飞书直连)
  163. # ═══════════════════════════════════════════
  164. IM_ENABLED = True # IM 主开关(True 时审批消息发飞书)
  165. IM_APPROVAL_TIMEOUT_MINUTES = 120 # 审批超时(分钟)— 2小时
  166. IM_APPROVAL_POLL_INTERVAL_SECONDS = 30 # 审批轮询间隔(秒)
  167. # 飞书应用凭据("增长投放"机器人)— 优先从环境变量读取
  168. FEISHU_APP_ID = os.getenv("FEISHU_APP_ID", "cli_a955e97067f85cb3")
  169. FEISHU_APP_SECRET = os.getenv("FEISHU_APP_SECRET", "NQaG4ci1plXRDTgwCqrLJgMLLoA2tdF8")
  170. # 运营审批人飞书信息
  171. FEISHU_OPERATOR_OPEN_ID = os.getenv("FEISHU_OPERATOR_OPEN_ID", "ou_498988d823b61ab89c9afe4310f85bb4")
  172. FEISHU_OPERATOR_CHAT_ID = os.getenv("FEISHU_OPERATOR_CHAT_ID", "oc_88e0a1970a7de02eb5ac225a8b0cedea")
  173. # 投放项目群聊 — 用于接收决策结果通知和审批回复
  174. # 置空则不发送到群,仅发送到个人
  175. FEISHU_AD_PROJECT_CHAT_ID = os.getenv("FEISHU_AD_PROJECT_CHAT_ID", "oc_7940ec97cde40b245cff9cb606ff1ac7")
  176. # 腾讯广告默认账户(测试账户)
  177. TENCENT_AD_ACCOUNT_ID = int(os.getenv("TENCENT_AD_ACCOUNT_ID", "80769799"))
  178. # ═══════════════════════════════════════════
  179. # 账户白名单配置
  180. # ═══════════════════════════════════════════
  181. # 白名单模式开关(优先级:数据库 > 环境变量)
  182. WHITELIST_ENABLED = None
  183. WHITELIST_ACCOUNTS = []
  184. # 尝试从数据库读取配置
  185. try:
  186. from db import get_whitelist_accounts, get_system_config
  187. # 读取白名单开关
  188. WHITELIST_ENABLED = get_system_config("whitelist_enabled", default=None)
  189. # 读取白名单账户列表
  190. WHITELIST_ACCOUNTS = get_whitelist_accounts()
  191. logger.info(f"✅ 从数据库读取白名单配置:{len(WHITELIST_ACCOUNTS)} 个账户")
  192. except Exception as db_error:
  193. logger.warning(f"⚠️ 数据库读取失败({db_error}),降级到环境变量配置")
  194. # 降级方案1:从环境变量读取
  195. _whitelist_str = os.getenv("WHITELIST_ACCOUNTS", "")
  196. if _whitelist_str:
  197. # 格式:逗号分隔,如 "80769799,71305011"
  198. WHITELIST_ACCOUNTS = [int(x.strip()) for x in _whitelist_str.split(",") if x.strip()]
  199. logger.info(f"从环境变量读取白名单:{len(WHITELIST_ACCOUNTS)} 个账户")
  200. else:
  201. # 降级方案2:从文件读取(可选)
  202. _whitelist_file = Path(__file__).parent / "whitelist.json"
  203. if _whitelist_file.exists():
  204. import json
  205. with open(_whitelist_file) as f:
  206. whitelist_data = json.load(f)
  207. WHITELIST_ACCOUNTS = whitelist_data.get("accounts", [])
  208. logger.info(f"从 whitelist.json 读取白名单:{len(WHITELIST_ACCOUNTS)} 个账户")
  209. # 白名单开关降级处理
  210. if WHITELIST_ENABLED is None:
  211. WHITELIST_ENABLED = os.getenv("WHITELIST_ENABLED", "true").lower() == "true"
  212. # 向后兼容:单账户模式
  213. if not WHITELIST_ACCOUNTS:
  214. WHITELIST_ACCOUNTS = [TENCENT_AD_ACCOUNT_ID]
  215. logger.info(f"白名单为空,使用单账户模式:{TENCENT_AD_ACCOUNT_ID}")
  216. logger.info(
  217. f"白名单配置:{'启用' if WHITELIST_ENABLED else '禁用'},"
  218. f"账户数={len(WHITELIST_ACCOUNTS)},列表={WHITELIST_ACCOUNTS[:5]}..."
  219. )
  220. # ═══════════════════════════════════════════
  221. # 实验范围 Scope(MVP)
  222. # ═══════════════════════════════════════════
  223. # 2026-06-08:DB 白名单已直接收窄到 2 个测试账户,不再 override
  224. # 单一真相源 = DB account_whitelist 表 enabled=1 的行
  225. # 若要临时收窄,改这里为 {account_id...}
  226. EXPERIMENTAL_SCOPE_ACCOUNTS = None
  227. EXPERIMENTAL_SCOPE_REASON = "已迁 DB,config 不再 override"
  228. if EXPERIMENTAL_SCOPE_ACCOUNTS is not None:
  229. _scope_before = len(WHITELIST_ACCOUNTS)
  230. WHITELIST_ACCOUNTS = [a for a in WHITELIST_ACCOUNTS if a in EXPERIMENTAL_SCOPE_ACCOUNTS]
  231. # 兜底:如 scope 中账户不在 DB 白名单(或 DB 读取失败),直接采用 scope 列表
  232. if not WHITELIST_ACCOUNTS:
  233. WHITELIST_ACCOUNTS = list(EXPERIMENTAL_SCOPE_ACCOUNTS)
  234. logger.warning(
  235. f"⚠️ EXPERIMENTAL_SCOPE {EXPERIMENTAL_SCOPE_ACCOUNTS} 与现有白名单交集为空,"
  236. f"直接使用 scope 列表"
  237. )
  238. logger.info(
  239. f"🧪 EXPERIMENTAL_SCOPE 启用:WHITELIST_ACCOUNTS 由 {_scope_before} → "
  240. f"{len(WHITELIST_ACCOUNTS)} 个 ({WHITELIST_ACCOUNTS})。原因:{EXPERIMENTAL_SCOPE_REASON}"
  241. )
  242. # ═══════════════════════════════════════════
  243. # 一账一包映射(账户级 audience pack · 2026-06-05 业务确认)
  244. # ═══════════════════════════════════════════
  245. # 用户明确约束:一个账户只用一个人群包,即使有多个广告也不变
  246. # audience_pack_id 由运营提供;LLM 不参与选择
  247. # 字段格式:account_id -> (audience_pack_id, audience_tier_label)
  248. # - audience_pack_id 为 None 表示该账户不使用人群包(仅靠地域/年龄/性别自然定向)
  249. ACCOUNT_AUDIENCE_PACK_MAPPING = {
  250. 83846793: (None, "no_audience_pack"), # 不传 custom_audience,按"泛人群"出价
  251. 83846804: (44722088, "R330+"), # Q-R_330+_WX_UNIONID_2026-06-02 (865 万人)
  252. }
  253. # ═══════════════════════════════════════════════════════════════════
  254. # [CREATION SOP] 广告搭建 SOP 固定参数(2026-06-05 业务确认)
  255. # ═══════════════════════════════════════════════════════════════════
  256. # 投放 SOP:广告搭建 = 固定定向 & 人群 & 出价
  257. # 小程序产品:票圈 | 3亿人喜欢的视频平台
  258. # 几乎所有维度都是固定的,LLM 不参与"营销内容/定向/出价类型"决策
  259. # 唯一可变维度:site_set 组合(3 种) + 出价数值(从区间内取)
  260. # --- 营销内容(全固定)---
  261. MARKETING_GOAL = "MARKETING_GOAL_USER_GROWTH"
  262. MARKETING_SUB_GOAL = "MARKETING_SUB_GOAL_UNKNOWN"
  263. # 修正(2026-06-05 真实样本反推):marketing_carrier_type 是 JUMP_PAGE,不是 MINI_PROGRAM_WECHAT
  264. # 小程序信息通过 marketing_asset_outer_spec 嵌套传递
  265. MARKETING_CARRIER_TYPE = "MARKETING_CARRIER_TYPE_JUMP_PAGE"
  266. MARKETING_CARRIER_NAME = "票圈 | 3亿人喜欢的视频平台"
  267. MARKETING_CARRIER_GH_ID = "gh_ecd1ea0b84cf" # 小程序 GH ID
  268. MARKETING_CARRIER_WX_APP_ID = "wx89e7eb06478361d7" # 小程序 WX AppID(可能 add 时不传,待 dry run)
  269. # marketing_asset_outer_spec 嵌套结构(add 时传)
  270. MARKETING_TARGET_TYPE = "MARKETING_TARGET_TYPE_MINI_PROGRAM_WECHAT"
  271. MARKETING_ASSET_OUTER_SPEC = {
  272. "marketing_target_type": MARKETING_TARGET_TYPE,
  273. "marketing_asset_outer_id": MARKETING_CARRIER_GH_ID,
  274. }
  275. # conversion_id 不传(可选 + 不支持朋友圈版位)
  276. # 改用 optimization_goal 直接走
  277. # ═══════════════════════════════════════════════════════════════════
  278. # 账户级 feedback_id 映射(2026-06-05 业务确认)
  279. # ═══════════════════════════════════════════════════════════════════
  280. # feedback_id = 监测链接 ID,是账户级配置,广告 add 时必填
  281. # 短期:本字典占位;长期:迁到 DB(扩展 account_whitelist 表加列)
  282. # 设计接口:get_account_feedback_id(account_id) → 先查 DB,fallback 本字典
  283. ACCOUNT_FEEDBACK_ID_MAPPING = {
  284. # account_id : feedback_id
  285. 83846793: 6700703, # 用户 2026-06-05 提供(非人群包账户)
  286. 83846804: 6700002, # 用户 2026-06-05 提供(R330+ 人群包账户)
  287. }
  288. def get_account_feedback_id(account_id: int):
  289. """获取账户的 feedback_id(监测链接 ID)。
  290. 优先级:DB(将来) > config.py 字典 > None
  291. 返回 None 时调用方应反问或报错,不能猜测。
  292. """
  293. # TODO: 后续接入 DB,从 account_whitelist 表读
  294. return ACCOUNT_FEEDBACK_ID_MAPPING.get(account_id)
  295. OPTIMIZATION_GOAL = "OPTIMIZATIONGOAL_PROMOTION_VIEW_KEY_PAGE" # 关键页面访问次数(USER_GROWTH 配套)
  296. # 注:2026-06-05 业务确认 — 两个测试账户均走 USER_GROWTH + PAGE_KEY 路线
  297. # 即使 83846804 带人群包,仍用此优化目标,不切换到 BRAND_PROMOTION + CLICK
  298. # --- 定向(全固定 SOP)---
  299. FIXED_TARGETING_AGE = [{"min": 45, "max": 66}] # 45-66 岁(自定义)
  300. FIXED_TARGETING_GENDER = "ALL" # 不限制(不传 gender)
  301. FIXED_TARGETING_LOCATION_TYPES = ["LIVE_IN"] # 常住地
  302. # 地域 region_id 列表 — 从 JSON 读(运营可改 JSON 调整生效地域)
  303. # 来源:ad 95205841163 (account 81214386) 实际投放地域反推
  304. # 实际排除:港澳台 + 东三省 + 河南(共 7 个一级行政区)
  305. FIXED_TARGETING_REGIONS_JSON_PATH = Path(__file__).parent / "data" / "tencent_constants" / "regions_sop_current.json"
  306. try:
  307. import json as _json
  308. with open(FIXED_TARGETING_REGIONS_JSON_PATH, encoding="utf-8") as _f:
  309. _regions_data = _json.load(_f)
  310. FIXED_TARGETING_REGION_IDS = [r["id"] for r in _regions_data["list"]]
  311. logger.info(
  312. f"✅ 从 {FIXED_TARGETING_REGIONS_JSON_PATH.name} 加载 {len(FIXED_TARGETING_REGION_IDS)} 个地域 region_id"
  313. )
  314. except FileNotFoundError:
  315. FIXED_TARGETING_REGION_IDS = []
  316. logger.warning(
  317. f"⚠️ 地域 JSON 未找到:{FIXED_TARGETING_REGIONS_JSON_PATH},新建广告时 targeting.geo_location 为空"
  318. )
  319. # 实际排除的一级行政区(给审批表 / 报告人类可读用)
  320. EXCLUDED_PROVINCES_SEMANTIC = ["香港", "澳门", "台湾", "辽宁", "吉林", "黑龙江", "河南"]
  321. # --- 出价 / 计费 ---
  322. BID_MODE = "BID_MODE_OCPM"
  323. SMART_BID_TYPE = "SMART_BID_TYPE_CUSTOM"
  324. BID_STRATEGY = "BID_STRATEGY_AVERAGE_COST"
  325. # 一键起量(auto_acquisition)默认关闭(用户 2026-06-05 确认,与多数样本一致)
  326. # 注:腾讯硬约束 — 若启用,budget 必须 >= 20000(200 元)
  327. AUTO_ACQUISITION_ENABLED = False
  328. AUTO_ACQUISITION_BUDGET_FEN = 20000 # 占位最小值,enabled=False 时不生效
  329. AUTO_DERIVED_CREATIVE_ENABLED = False
  330. AIM_SMART_TARGETING_ENABLED = False
  331. AIM_SMART_SITE_ENABLED = False
  332. DEEP_CONVERSION_SPEC: dict = {}
  333. # --- 转化(conversion_id)---
  334. # 用户 2026-06-05 指示:两个测试账户都用 1007(与样本 92067863445 一致)
  335. # 长期:迁到 account_whitelist 表加列 conversion_id
  336. DEFAULT_CONVERSION_ID = 1007
  337. # --- 搜索场景扩量 · 定向拓展开关(用户 2026-06-05 反推确认)---
  338. # 3 条线上样本均为 CLOSE,我们若不传腾讯默认 OPEN → 与 SOP 不一致
  339. # 用 ad_api 反推得知字段名:search_expand_targeting_switch
  340. SEARCH_EXPAND_TARGETING_SWITCH = "SEARCH_EXPAND_TARGETING_SWITCH_CLOSE"
  341. # --- 版位(用户 2026-06-05 调整:与样本 92067863445 一致)---
  342. AVAILABLE_SITE_SETS = [
  343. "SITE_SET_WECHAT", # 微信公众号
  344. "SITE_SET_WECHAT_PLUGIN", # 微信插件
  345. "SITE_SET_SEARCH_SCENE", # 搜索场景
  346. ]
  347. # MVP 阶段:单一固定版位组合(差异化先不靠 site_set)
  348. SITE_SET_COMBINATIONS = [
  349. ["SITE_SET_WECHAT", "SITE_SET_WECHAT_PLUGIN", "SITE_SET_SEARCH_SCENE"],
  350. ]
  351. # --- 时段 / 日期(真实样本 95205841163 反推:6:00-22:30 投放)---
  352. # 一天 48 段 × 7 天 = 336 位字符串
  353. TIME_SERIES_ONE_DAY = "000000000000" + "1" * 34 + "00" # 0:00-6:00 关 / 6:00-23:00 投 / 23:00-24:00 关
  354. TIME_SERIES_DEFAULT = TIME_SERIES_ONE_DAY * 7
  355. # 长度自验
  356. assert len(TIME_SERIES_DEFAULT) == 336, f"time_series 长度错误:{len(TIME_SERIES_DEFAULT)}"
  357. DEFAULT_BEGIN_DATE_OFFSET_DAYS = 0 # 创建当日开始
  358. DEFAULT_END_DATE = "0" # 真实样本是字符串 "0",不是 None(腾讯特殊表示长期)
  359. # --- 预算(用户 2026-06-05 确认:200 元/广告)---
  360. # 真实样本是 0(不限),但 MVP 阶段我们设硬上限保护
  361. DEFAULT_DAILY_BUDGET_YUAN = 200 # 单广告日预算
  362. DEFAULT_DAILY_BUDGET_FEN = DEFAULT_DAILY_BUDGET_YUAN * 100 # 元 → 分
  363. # --- 出价区间表(按 audience tier label 索引)---
  364. # 来源:用户提供的投放 SOP 出价区间表
  365. AUDIENCE_BID_RANGES = {
  366. # tier_label : (min_yuan, max_yuan)
  367. "R50_泛惊奇_奇观技艺": (0.35, 0.45),
  368. "R50_泛知识_生活科普": (0.35, 0.45),
  369. "R50_泛知识_时政历史": (0.35, 0.45),
  370. "R50_泛祝福": (0.35, 0.45),
  371. "R50_全品类": (0.25, 0.38),
  372. "R50_同感个体_个人情感": (0.35, 0.45),
  373. "R50_同感个体_退休榜样": (0.35, 0.45),
  374. "R500_全品类": (0.38, 0.48),
  375. "回流100-180": (0.22, 0.28),
  376. "回流180-330": (0.30, 0.40),
  377. "回流330+": (0.35, 0.40),
  378. "R330+": (0.35, 0.40), # 别名,83846804(Q-R_330+)用
  379. "回流50-100": (0.19, 0.22),
  380. "泛人群": (0.19, 0.25),
  381. "no_audience_pack": (0.19, 0.25), # 别名,83846793(无人群包)用
  382. }
  383. # --- 出价取值策略 ---
  384. BID_PICK_STRATEGY = "midpoint" # midpoint / max / min / random
  385. COLD_START_BID_PICK_STRATEGY = "midpoint" # 冷启动期取中位(可改 max 抢量)
  386. # --- 朋友圈版位专属设置(运营标准模板,待提供)---
  387. FEED_AD_SETTING_HEAD_IMAGE_URL = None # TODO
  388. FEED_AD_SETTING_NICK_NAME = None # TODO
  389. FEED_AD_SETTING_CONVERSION_BUTTON_TEXT = "查看详情" # 默认
  390. # --- 单账户起步广告条数(Cold Start)---
  391. COLD_START_PER_ACCOUNT_AD_COUNT = 3 # 对应 3 种 site_set 组合
  392. COLD_START_TOTAL_ADS = COLD_START_PER_ACCOUNT_AD_COUNT * len(WHITELIST_ACCOUNTS) # 跟随 DB
  393. # ═══════════════════════════════════════════
  394. # 输出路径配置
  395. # ═══════════════════════════════════════════
  396. OUTPUTS_DIR = Path(__file__).parent / "outputs"
  397. RAW_DATA_DIR = OUTPUTS_DIR / "raw" # 创意级原始 CSV
  398. AD_STATUS_DIR = OUTPUTS_DIR / "ad_status" # 广告状态 CSV
  399. REPORTS_DIR = OUTPUTS_DIR / "reports" # 决策报告
  400. EXECUTION_LOG_DIR = OUTPUTS_DIR / "execution_log" # 执行审计日志
  401. DATA_DIR = OUTPUTS_DIR / "data" # 运行时数据(如调整历史)
  402. ADJUSTMENT_HISTORY_PATH = DATA_DIR / "adjustment_history.json"
  403. # ═══════════════════════════════════════════
  404. # 人群包系数(保留,用于展示)
  405. # ═══════════════════════════════════════════
  406. AUDIENCE_COEFFICIENTS = {
  407. "R500": 3.0,
  408. "R330+": 2.5,
  409. "R330": 2.0,
  410. "R180": 1.5,
  411. "R100": 1.2,
  412. "R50": 1.0,
  413. "R10": 1.0,
  414. "R2": 1.0,
  415. "default": 1.0,
  416. }
  417. # 从广告名称提取 R 值的匹配顺序
  418. AUDIENCE_TIER_PATTERNS = [
  419. ("R500", ["R500", "R_500", "r500"]),
  420. ("R330+", ["回流330+", "回流330+-", "回流q330", "330+全品类", "R330+", "R_330+"]),
  421. ("R330", ["回流330", "R330", "R_330", "定向330", "r330", "r300"]),
  422. ("R180", ["回流180", "R180", "R_180", "定向180", "r180",
  423. "r180-330", "r180-300", "R100-180", "R_100-180", "r100-180"]),
  424. ("R100", ["回流100", "R100", "R_100", "定向100", "r100", "R50-100"]),
  425. ("R50", ["回流50", "R50", "R_50", "r50"]),
  426. ("R10", ["R_10", "R10", "r10"]),
  427. ("R2", ["R_2", "R2", "r2"]),
  428. ]