# auto_put_ad_mini 生产自动投放系统说明

## 目标

本系统用于服务端 Docker 环境每日自动扫描投放配置,完成腾讯广告自动搭建与创意补量。

人工只维护待投放配置,主流程自动完成:

- 从飞书配置表同步待投放账户
- 校验账户白名单
- 校验/授权腾讯人群包
- 创建缺口广告
- 给广告补足创意
- 视频风险审核
- 内容品类过滤
- 素材召回与质量过滤
- 飞书审批
- 审批通过后创建腾讯动态创意

## 人工配置入口

当前生产配置来自飞书表 `自动化账户`。

每个账户只需要配置:

- 账户 ID
- 人群包名称
- 初始出价
- 单广告预算
- 是否自动化执行

当 `是否自动化执行=是` 时,每日主流程会扫描并处理该账户。为否时,系统不再为该账户新建广告或补创意,但不会暂停或删除既有广告。

单广告预算语义:

- 空白: 使用投放模板默认预算。
- `不限制` / `不限` / `-`: 按腾讯不限预算传 `daily_budget=0`。
- 数字: 按元读取,转换为分后传腾讯。

## 生产目标

当前单广告最终有效创意目标:

```text
TARGET_CREATIVES_PER_AD = 8
```

含义:

- 每条广告最终有效创意数不少于 8 个
- 已有有效创意会计入目标
- `DENIED` 创意不计入有效创意
- 差几个补几个
- 补充创意会先进入飞书审批,审批通过后才调用腾讯创建接口

Phase 1 创意准备默认串行:

```text
CREATIVE_PREPARE_MAX_WORKERS = 1
CREATIVE_PREPARE_TASK_BUFFER = 16
```

- `prepare_one_creative_for_ad` 当前会真实上传图片并调用 `xcx/save` 创建落地计划,不是纯候选选择函数。
- 直接并行该函数会导致多个 worker 基于同一排重快照选中同一 landing/material,产生重复落地计划。
- 因此生产默认保持串行。后续要并行时,应先拆成“纯候选召回/筛选并行 + 图片上传/落地计划创建串行确认”。
- 主线程统一接收 pending record 并做最终 landing/material 排重。

## 承接视频选择

每次为一条广告准备一条创意时,视频最多扫描:

```text
primary source: 默认最多 3 页,每页 100 条
hot source: 默认最多 3 页,每页 100 条
```

执行顺序:

1. 先用账户配置的人群包拉主池视频
2. 主池最多 3 页经过过滤、风险审核、素材召回后仍无法产出可用创意时,再用同一个人群包拉 `source=hot`
3. hot 池同样最多 3 页
4. hot 池同样走风险审核、品类过滤、素材质量过滤

注意: `source=hot` 只改变内容服务的视频来源。默认情况下 `crowdPackage` 使用当前账户配置的人群包;如果配置了视频召回映射,则 primary/hot 都使用映射后的召回人群包。分页结果按 `video_id` 去重合并。

当前默认视频召回映射:

```text
cell*year*商业 -> wx*商业
回流330以上人群 -> R_330+
```

该映射只影响 `videoContentList` 获取视频,不影响腾讯投放定向、人群包授权和 `xcx/save` 落地计划。

内容服务 `source` 也可独立映射。当前默认没有 source 覆盖,会使用 `.env` 的 `PIAOQUANTV_VIDEO_SOURCE=prior`。

```text
回流330以上人群 -> crowdPackage=R_330+, source=prior
```

原因:内容服务中 330 人群包的实际参数是 `R_330+`。

## 视频过滤规则

内容服务 `videoContentList` 返回的 `category` 字段会用于内容品类过滤。

默认过滤:

```text
LANDING_EXCLUDED_CATEGORIES=早中晚好,祝福音乐,历史名人
```

命中后,该视频会在以下步骤前被跳过:

- 视频风险审核
- 素材召回
- xcx/save 落地计划创建

可通过 Docker/K8s 环境变量覆盖:

```bash
LANDING_EXCLUDED_CATEGORIES=早中晚好,祝福音乐,历史名人
```

多个品类用英文逗号分隔。

## 视频风险审核

承接视频在创建落地计划前调用风险接口:

```text
POST https://longvideoapi.piaoquantv.com/longvideoapi/openapi/video/getVideoTagIds
```

当前阈值:

```text
VIDEO_RISK_MAX_ALLOWED_LEVEL=5
```

规则:

- 风险等级 0-5 通过
- 风险等级 6-10 拦截
- 风险接口异常时按不通过处理,避免风险未知的视频进入投放

## 素材召回筛选与排序

`videoContentList` 只负责提供承接视频候选。素材召回特征统一通过 `video_id`
查询 ODPS 表:

```text
loghubods.dwd_video_element_contribution_analysis
```

当前使用的召回特征:

- `解构选题` 列 -> `VIDEO_TOPIC`
- `元素维度=实质, 点类型=灵感点` -> `INSPIRATION_SUBSTANCE`
- `元素维度=实质, 点类型=关键点` -> `KEYPOINT_SUBSTANCE`
- `元素维度=实质, 点类型=目的点` -> `PURPOSE_SUBSTANCE`

`形式` 和 `意图` 不参与素材召回。primary 和 hot 视频都走同一套 ODPS
查询、风险审核、素材召回和审批流程。

素材召回只使用相似度做硬筛:

```text
RECALL_SIM_THRESHOLD=0.8
RECALL_DAYS=180
RECALL_DISPLAY_K=30
```

素材必须满足:

- 相似度 `score >= 0.8`
- 封面 URL 不命中已知黑名单

通过筛选后按历史消耗 `cost` 倒序选择。ROI、CTR、曝光数和相似度会进入创意审批报表,但不再作为硬筛。

## AI 生成图片素材

账户可通过飞书/DB 指定素材来源:

- 空值或 `历史素材`:继续使用历史已投素材召回。
- `AI生成素材`:按承接视频的 ODPS 特征生成新图片素材。

AI 生成素材链路只改变“素材来源”,不改变视频筛选、人群包、出价、预算、落地计划、审批和腾讯创意创建主流程。
当前每个视频通过 `landing_video_id` 读取本地库 `video_element_feature_cache` 中该视频最新分区的 `解构选题`,
并使用贡献分最高的 `standard_element` 生成 1 张 `topic` 图片。
生成图片前会先用 OpenRouter 文本模型清洗原始 `解构选题`,
去掉转发、关注、公众号回复、加群、领取、底部提示、推广话术等互动/营销引导,同时保留核心问题、信息差、反差、情绪价值或生活提醒价值。
清洗后的主题种子不再强制写入“中老年/老人/退休/晚年”等目标用户词;目标用户不等于画面主体。
清洗失败或清洗后仍包含互动/营销词时,本轮跳过该视频,不使用原始 topic 兜底。
标题节点会先生成主标题和 `highlight_terms`;图片生成阶段只能高亮这些词,不能自行选择其他高亮词。
最终图片 prompt 由工程内模板、清洗后的动态承接视频描述、标题/高亮词、以及弱化后的 pattern `visual_direction` 拼接:

- 清洗模板文件:`examples/auto_put_ad_mini/prompts/ai_sanitize_video_description.md`
- 标题模板文件:`examples/auto_put_ad_mini/prompts/ai_cover_copy.md`
- 模板文件:`examples/auto_put_ad_mini/prompts/ai_generated_material.md`
- 动态描述:`解构选题.standard_element` 经文本模型清洗后的主题描述
- 视觉方向:`material_creative_pattern.visual_rule` 只作为可选参考;如果和画面主体多样性、非默认人物、非默认老人正脸冲突,以全局视觉策略为准
- 图片比例:`AI_IMAGE_ASPECT_RATIO`,默认 `16:9`
- 最终输出尺寸:`AI_IMAGE_TARGET_WIDTH` x `AI_IMAGE_TARGET_HEIGHT`,默认 `1280x720`

AI 图先上传到 OSS 并进入飞书人工审批。只有人工 `approve` 后,Phase 3 才会把图片上传到腾讯 `images/add` 并创建创意。
如果账户配置 `生成失败是否回退历史素材=是`,AI 生成失败或无候选时才回退历史素材;否则本轮跳过。

AI 图默认上传到:

- OSS bucket:`art-pubbucket`
- OSS 目录:`auto_put_tencent/image`
- 对外 URL 域名:`https://rescdn.yishihui.com/`

手动调试生成效果可运行:

```bash
.venv/bin/python examples/auto_put_ad_mini/debug_generate_ai_material.py --video-id 71187017 --title "视频标题"
```

调试脚本按 `video_id` 读取本地特征缓存表,只生成并上传 OSS,打印图片 URL,不会创建腾讯广告或创意。

素材排重分两层:

- 历史排重只读取已有明确结果的素材使用记录,按同人群包下的 `material_id` 排除。
- 本轮审批候选做内存展示去重,同一 `crowd_package + material_id` 只进入当前审批表一次。该规则不写入历史排重库,进程结束即失效。

落地页视频排重按素材来源拆池:

- 历史素材召回链路:同一 `crowd_package + landing_video_id` 默认最多使用 1 次。
- AI 生成素材链路:同一 `crowd_package + landing_video_id` 默认最多使用 1 次。
- 两条链路互不占用名额。历史素材已用过的 landing,不阻止 AI 生成素材链路继续使用;AI 使用记录也不阻止历史素材链路按自己的规则判断。
- 跨轮近期排重默认读取 `creative_material_usage` 和 `creative_creation_task`,只按同人群包最近 7 天已提交腾讯、已执行或已有明确投放结果的 `landing_video_id` 统计使用次数。
- 未提交腾讯的 `prepared` 记录不参与跨轮排重;端到端中断重跑时会优先作为可恢复缓存复用,避免重复选视频和重复生成 AI 图。
- 同一广告内同一 `landing_video_id` 仍保留限频保护,默认最多 1 次。

可通过环境变量调整同广告落地页视频本轮限频:

```bash
MAX_SAME_LANDING_PER_AD_IN_RUN=1
CREATIVE_LANDING_DEDUPE_LOOKBACK_DAYS=7
PIAOQUANTV_VIDEO_MAX_PAGES=3
```

## 热门兜底配置

热门兜底由以下环境变量控制:

```bash
PIAOQUANTV_HOT_FALLBACK_ENABLED=true
PIAOQUANTV_HOT_FALLBACK_SOURCE=hot
```

生产默认开启。

主池不足和主池无法产出可用创意是两个不同口径:

- 视频列表不足: 主池返回条数少于请求条数时,视频获取层可补 hot
- 创意产出不足: 主池 100 条无法产出 pending creative 时,创意准备层继续跑 hot 100 条

生产投放以“最终有效创意数达到目标”为准。

## Docker/K8s 必配环境变量

除数据库、飞书、腾讯、ODPS 等基础密钥外,生产自动投放建议显式配置:

```bash
PIAOQUANTV_TOKEN=...
PIAOQUANTV_VIDEO_TYPE=5
PIAOQUANTV_VIDEO_SOURCE=prior
PIAOQUANTV_HOT_FALLBACK_ENABLED=true
PIAOQUANTV_HOT_FALLBACK_SOURCE=hot
LANDING_EXCLUDED_CATEGORIES=早中晚好,祝福音乐,历史名人
VIDEO_RISK_MAX_ALLOWED_LEVEL=5
VIDEO_RECALL_CROWD_PACKAGE_MAP={"cell*year*商业":"wx*商业","回流330以上人群":"R_330+"}
MAX_SAME_LANDING_PER_AD_IN_RUN=1
PIAOQUANTV_VIDEO_MAX_PAGES=3
CREATIVE_LANDING_DEDUPE_LOOKBACK_DAYS=7
TARGET_CREATIVES_PER_AD=8
CREATIVE_PREPARE_MAX_WORKERS=1
CREATIVE_PREPARE_TASK_BUFFER=16
TENCENT_AUDIENCE_SOURCE_ACCOUNT_ID=55615440
TENCENT_AUDIENCE_GRANT_BUSINESS_ID=12312
OPENROUTER_API_KEY=...
OPENROUTER_TEXT_MODEL=google/gemini-3-flash-preview
OPENROUTER_IMAGE_MODEL=google/gemini-3.1-flash-image
AI_IMAGE_PATTERN_TOP_K=1
ALIYUN_OSS_ENDPOINT=oss-xxx.aliyuncs.com
ALIYUN_OSS_BUCKET=art-pubbucket
ALIYUN_OSS_ACCESS_KEY_ID=...
ALIYUN_OSS_ACCESS_KEY_SECRET=...
AI_IMAGE_OSS_PREFIX=auto_put_tencent/image
AI_IMAGE_PUBLIC_BASE_URL=https://rescdn.yishihui.com
AI_IMAGE_TARGET_WIDTH=1280
AI_IMAGE_TARGET_HEIGHT=720
```

OSS key 已迁移到本工程环境变量。生产 Docker 也应显式注入这些变量,不依赖外层参考工程。

## 临时收窄运行

每日主流程默认会扫描全部启用账户。应急验证或定向补量时，账户范围使用严格 CLI 参数，人群包和是否跳过广告创建仍可用当次进程环境变量：

```bash
docker compose run --rm \
  -e CREATION_SKIP_PHASE0=1 \
  ad_daily_service \
  python /app/examples/auto_put_ad_mini/run_daily_service.py \
  --run-creation-once \
  --account-id 84502339
```

含义:

- `CREATION_SKIP_PHASE0=1`: 本次跳过广告创建,只做创意准备、审批和提交。
- `CREATION_ONLY_CROWD_PACKAGES`: 本次只处理指定人群包的账户;多个值用英文逗号分隔。
- `--account-id`: 本次只处理指定账户 ID；多个账户重复传参。每个账户读取飞书表日期最新的一行，最新配置未启用或输入非法时直接退出。

这些参数和开关只影响当次进程。生产定时任务不传参数时，仍按默认全量启用账户运行。旧 `CREATION_ONLY_ACCOUNT_IDS` 已停用，避免非法值被解析为空后误跑全部账户。

创意审批表包含独立的 `素材预览` 和 `素材链接` 两列:`素材预览` 用于图片/查看入口,`素材链接` 保留原始素材 URL 的 `HYPERLINK` 公式。当前 `决策` 列为 `AB`,审批轮询也读取 `AB` 列。

素材封面默认会作为缩略图嵌入 xlsx 后再上传飞书在线表格。若需要临时关闭嵌图,可设置:

```bash
CREATION_APPROVAL_EMBED_IMAGES=0
```

飞书导入若不保留 xlsx 内嵌图片,审批表仍保留链接兜底;后续可改用飞书 Sheets 写图片 API。

## 当前已知限制

- `R330` 账户如果源账户无法解析到 ONLINE/SUCCESS 人群包,Phase 0 会跳过该账户。
- `泛人群` 主池可能返回 0,需要依赖 `source=hot` 兜底。
- 当前素材只按相似度准入并按消耗排序;低曝光/低 CTR 素材可能进入审批表,需要运营结合报表判断。
- AI 生成素材 v1 不做机器预审,质量和合规依赖飞书人工审批 + 腾讯审核。
- 每次 pending creative 准备会调用 `xcx/save` 生成落地计划;端到端测试中断不会自动清理已生成的落地计划。

## 上线检查

上线前至少确认:

- 飞书配置表可读
- `是否自动化执行` 只对目标账户开启
- 目标账户在 `account_whitelist` 启用
- 人群包名称可在源账户解析并授权到目标广告账户
- 品牌图和监测链接模板可自动初始化
- Docker/K8s 环境变量与本文件一致
- 手动跑一次 `execute_creation_once.py` 能生成飞书审批表
