CREATIVE_CREATION_TODO.md 9.6 KB

模块 B 创意搭建 — Code Review TODO

来源:2026-06-08 端到端打通后的工程 review。 状态:端到端可单广告挂创意,但生产批量上线前必须做完 P0。 验证基线:83846793 / 106275052398 已成功挂上 10030789982(已手动删除)。

P0 — 必须修(影响业务正确性)

P0-NEW-1. 生产 cron 化前必须修:Phase 0 广告池"真已满"判定 + 唯一性预校验(2026-06-10 发现)

  • 现状:
    • phase0_create_ads 检查 configured_status == AD_STATUS_NORMAL 判定"广告满了"
    • 失败的 SUSPEND/DENIED 广告不算 NORMAL → Phase 0 视为"未满" → enumerate 重复 candidate
    • candidate 走完飞书审批 → 真 POST → 撞腾讯唯一性 reject code 1901634(腾讯文档:删除后 2 年内仍占唯一性槽位)
    • 运营每天审批表都是"建失败"的广告,体验差
  • 触发场景:
    • 任意 Phase 0 POST 失败留下的 SUSPEND 广告
    • 任意运营在腾讯后台手动暂停的广告
    • 跨日 cron 不断重试建同 SOP 广告
  • 修复方向:
    1. Phase 0 广告池判定:不只看 NORMAL,要算所有未删除广告(含 SUSPEND/DENIED)— 它们仍占唯一性槽位
    2. fingerprint 预校验集成:在 enumerate_new_ad_candidates 调用前,反查账户所有现存广告(含已删除 is_deleted=True),按 6 字段 + targeting 算指纹 set,enumerate 时 skip 已存在指纹
    3. 不主动 delete:主循环只读、判定、跳过,不删除腾讯广告(开发期手动调试除外)
  • 相关:整个 P0 章原本就有的 "唯一性预校验" 工作,本条把它升级为 cron 化前的硬阻塞

P0-NEW-2. 调试期手动删广告 ≠ 生产清理机制(2026-06-10 自我警醒)

  • 现状:开发期我用 _post('/adgroups/delete', ...) 手动清场重测,这只是调试手段
  • 生产 cron 启动后绝不能靠开发者手动删 — 出问题应该靠 P0-NEW-1 主循环跳过
  • 文档化:写进 K8s cronjob_creation.yaml 部署 README,提醒"不要手动 delete 腾讯广告"

P0-1. DEFAULT_JUMP_PATH 改动态生成 ✅ 已完成(2026-06-08)

  • 实现方式:接入 piaoquantv xcx/save 接口
  • 落地:新建 tools/landing_plan.py 封装接口;creative_creation.pyDEFAULT_JUMP_PATH 常量
  • mini_program_path 改为来自 xcx/save 返回的 pageUrl
  • 验证:两次调用 plan_id=8061 → 8062,page_url 每次新生成

P0-2. build_creative_name 防重名 ✅ 由 P0-1 副作用解决(2026-06-08)

  • 原方案:客户端加 uuid 进 hash 输入
  • 实际方案:xcx/save 返回的 pageUrl 内嵌 rootSourceId,直接用作 dynamic_creative_name
  • 命名生成职责完全交由服务侧管,客户端不再持有命名逻辑
  • 落地:删 tools/creative_creation.py::build_creative_name 函数

P1 — 生产稳定性

P1-1. upload_image_to_account 迁移到 tools/ad_api.py ✅ 已完成(2026-06-08)

  • 落地:tools/ad_api.pyimages_add(account_id, image_url) -> str(sync public,与 _post/_get/_check 同层)
  • tools/creative_creation.pyupload_image_to_account,改 import from tools.ad_api import images_add
  • 验证:同账户重复调用返回相同 image_id(MD5 幂等)
  • 复用:模块 A 后续挂"广告主图"也用同一函数

P1-2. 幂等保护(防止重复创建) ✅ 已完成(2026-06-08)

  • 落地:tools/creative_creation.py::find_existing_creative_by_image(account_id, adgroup_id, image_id)
  • 判等键:同 adgroup + 同 material_image_id(不按 creative_name,因为 name 每次 xcx/save 都新)
  • 接入:create_creative_for_ad(..., skip_if_exists=True) 默认开启,POST 前先反查 → 命中直接返回已有 dynamic_creative_id
  • 验证:None 路径反查 image_id=999999999999999 返回 None(命中路径因当前 adgroup 无创意 skip,逻辑同 None 路径反向)
  • 副作用:每次创建多 1 次 /dynamic_creatives/get 调用(QPS 多消耗 1,在 P1-3 接限流时算入)

P1-3. 接入 execution_engine 的 QPS + retry ⏸ 暂缓(2026-06-08 用户决策)

  • 暂缓原因:单账户串行挂创意场景,实际 QPS 远低于腾讯 10/s 硬限,不必预先处理
  • piaoquantv 侧已确认无限流(用户 2026-06-08 确认)
  • 现状评估:链路 3 次腾讯调用(/dynamic_creatives/get + /images/add + /dynamic_creatives/add)+ 1 次 piaoquantv xcx/save,单条创意挂载 < 1s
  • 触发条件:如果未来真撞限(腾讯返回 limit 错误码)→ 在 ad_api.py::_post / _get 加一层全局 sync token bucket 即可(execution_engine.TokenBucket 是 async 版,需要 sync 改造)
  • 不在调用方加 — 改一处而非每个 caller 改

P1-4. 定义领域异常

  • 现状:全部 RuntimeError,调用方无法区分错误类型
  • 方案:
    • ImageUploadError(account_id, image_url, tencent_code)
    • MissingBrandError(account_id)
    • CreativeRejectError(adgroup_id, tencent_code, field)

P2 — 架构清晰度

P2-1. 抽 tools/account_assets_repo.py

  • 集中:get_account_brand + 后续 get_account_audience_pack + get_account_jump_template
  • 现状:DB 访问散落各 tool

P2-2. 腾讯枚举集中

  • tools/ad_api.pyclass TencentAdStatus / TencentDeliveryMode / TencentCreativeType
  • 替换:"AD_STATUS_NORMAL" / "DELIVERY_MODE_COMPONENT" / "DYNAMIC_CREATIVE_TYPE_PROGRAM" 等 magic string

P2-3. 完善 logging 链路

  • 召回 / 上传 / build / POST / 反查 全程 step=xxx duration_ms=yyy

P2-4. 模块 B 主循环 + 协作 hook

  • find_ads_needing_creatives(account_id) -> list[ad_id](filter system_status=CREATIVE_EMPTYcreative_count < MIN)
  • 配合 plan 阶段 f 的 execute_creation_once.py 主入口

P3 — 可演进性

P3-1. build_creative_request_body 引入 CreativeContext dataclass

  • 把 brand / jump_path / button_text 等 SOP 字段打包,减少 8 参数

P3-2. 83846804 账户跑同样流程验证

  • brand 已就绪(40915884255),DB 已填,直接可用

P3-3. 加模块 B README

  • 位置:tools/README_creative_creation.md
  • 内容:数据流图 / 接口列表 / 已知腾讯错误码表 / 故障排查清单

P3-4. 图片 Content-Type 自动检测

  • 现状:硬编码 image/jpeg
  • 方案:mimetypes.guess_type(url) 或读 magic bytes

P3-6. 飞书 IM 消息升级为 interactive 卡片(2026-06-09 发现 unfurling 不可靠)

  • 现状:feishu_doc._send_link_messagemsg_type=text,完全依赖飞书客户端 link unfurling 自动展开 sheet 预览
  • 实测问题:同一段代码,飞书后端 link preview 服务行为变化导致原本能展开的消息现在不展开(用户 2026-06-09 反馈连他原有调控审批消息也不展开了)
  • 影响范围:调控审批 + 模块 B 创意审批共用同一段代码,改动会同时改善两个子系统
  • 升级方案:msg_type=interactive,卡片 JSON 显式定义标题/摘要/按钮,飞书客户端强制渲染,不依赖 unfurling
  • 终极方案:用飞书的 share_sheet / sheet embed 消息类型(待查文档),IM 客户端 100% 强制展开 sheet 预览
  • 工程量:50-100 行代码,改 _send_link_message 一处

P3-5. 飞书审批表素材预览 — 升级嵌入图(write-images API)

  • 现状:cell 用 =HYPERLINK(material.cover, "查看素材") 公式,运营点击在浏览器看图
  • 已知问题:rescdn.yishihui.com 有 Referer ACL 防盗链,飞书 sheet 点链接首次访问会 403,运营需要"右键新标签"或粘贴 URL 重试
  • 升级方案:调飞书 POST /open-apis/sheets/v2/spreadsheets/{token}/values_image,把素材图二进制流嵌入 cell,运营 review 时直接看图
  • 风险点:飞书 server 拉图是否被 yishihui CDN 拦截待验证;如被拦,需跟 yishihui 运维协调 CDN 白名单(加 feishu.cn 或放行空 Referer)
  • 触发条件:挂创意量上百条/天时再升级 — 当前 ≤ 5 条/天,运营 5 次点击代价 < 工程协调成本

P3-7. AI 生成素材画面主体多样性优化

  • 现象:近期 AI 生成素材里背景经常是中老年人物,且几乎每张都有人物主体。
  • 初步判断:当前 prompt 多次强调目标用户为中老年、生活场景、家庭/邻里/父母子女关系,图片模型容易把"目标用户"误理解为"画面必须出现老年人"。
  • 优化方向:
    • 区分"目标用户"和"画面主体":目标用户是 45-75 岁用户,但画面主体可以是物品、清单、场景、事件现场、道具特写、讲解场面、自然/工业过程等。
    • 不强制人物:只有 pattern 或视频主题确实需要人物情绪/关系时才出现人物。
    • 不强制老人形象:即使有人物,也可以是中年人、家庭成员、工作人员、邻里、讲解者或背影/手部等,不必总是正脸老人。
    • 后续评估 prompt 是否过度绑定"真实生活化/家庭场景",避免所有素材都长成同一类家庭老人封面。

已知腾讯错误码 — 排查表

code 含义 修复方向
18001 缺失必填参数 image_id image 组件不能用 image_url,必须先上传拿 image_id
1800269 品牌形象必填 必须传 brand 组件 + brand_name + brand_image_id
1530003 图片不存在或已删除 image_id 跨账户不可复用,必须按账户独立上传
1801159 提交的组件无法匹配到创意形式(图片素材比例/长宽/大小/时长不符) 召回素材不一定符合腾讯创意规格,需 try-fallback;长期方案:召回阶段加图片尺寸校验,或限定 URL 模式仅取 adPutTencent/image/ 路径

已验证过的非业务事实

  • 同账户图片 MD5 幂等:重复上传同图返回相同 image_id(无需本地缓存)
  • 跨账户图片不可复用:同图在不同账户下 image_id 不同
  • 普通素材审核:DYNAMIC_CREATIVE_STATUS_PENDING → 2-4 小时
  • 广告 system_statusCREATIVE_EMPTY 切走有几秒到几分钟延迟(腾讯后端聚合状态)