# Agent 项目级改代码原则

## 适用范围

本仓库包含生产级自动投放系统。凡是修改 `examples/auto_put_ad_mini` 下的代码,都要按生产自动化系统处理,不能当成一次性脚本。

## 生产自动化原则

- 优先使用数据库和飞书配置,避免硬编码账户级参数。
- 人工配置应尽量只包含账户 ID、人群包名称、出价、预算、是否执行。
- 每日自动流程应根据腾讯侧当前状态自行判断是否需要创建广告或补创意。
- 飞书配置行关闭时,只代表不再新建广告或补创意,不能因此暂停、删除或改动既有广告。
- 尽量保持幂等。重复运行时应跳过已完成的工作,只补缺口。
- 当前新建广告统一使用每天 `06:00-20:00` 的投放时段,由 `ad_delivery_template.time_series_json` 管理;修改全局时段时要同时保持代码默认值与数据库启用模板一致。
- 飞书「自动化账户」表按账户读取 `日期` 最新的一行作为当前配置,不要求日期等于当天;同账户同日期多行时以表格靠后的行为准。最新行关闭时只停止新建/补创意,不改动腾讯既有资产。显式 `--config-date` 仅用于历史复现。`年龄` 支持如 `45+`、`30-66+`; `地域` 支持中文省市、`不限` 或表示沿用模板的 `排地域`。

## 生产服务架构

- 生产使用同一个完整 `ad-put-agent` 镜像启动两个容器:`ad-control-service` 和 `ad-daily-service`。
- `ad-control-service` 负责唯一飞书 WebSocket、运营暂停/停止/恢复、ROI 表格逐行审批执行和每 10 分钟实时 CPM 调控。
- 实时调控账户范围是历史 `ad_creation_account_config` 与启用白名单的交集;飞书关闭创建配置只停止新建/补创意,不能让既有广告退出实时管理。
- 自动化账户始终使用完整实时调控模式;同一账户同时出现在额外纳管范围时,不能被 `PAUSE_ONLY` 降级。
- `realtime_control_account_scope` 可独立纳管非自动创建账户;其中 `PAUSE_ONLY` 账户只执行 CPM 关停,不能执行 BOOST 或 RESTORE 调价。
- `ad-daily-service` 每天 10:30 调用现有广告/创意创建流程,每 2 小时扫描腾讯创意正式审核结果,并可在每天 11:00 计算和发布日级 ROI 批次。
- 旧 `server.py`、`execute_once.py` 和 `run.py` 的模型调控链路不再作为生产入口,但暂不删除历史代码。
- 飞书生产控制命令优先使用确定性解析;模型只能分类为受限的动作和范围类型,最终账户集合必须由代码从原文和数据库确定,模型不能执行腾讯写操作。
- 飞书自然语言控制只能接入 `ad-control-service` 的唯一 WebSocket,不能新增第二个监听服务。群聊消息只有来自配置群、发送人在允许列表且明确 @机器人时才进入解析;私聊和未 @ 消息必须在调用模型、数据库和腾讯接口前返回。
- 自然语言模型只允许把文本转换为受限动作/范围 JSON,不能注册工具、读取账户清单或直接调用腾讯 API。标准确定性命令优先,模型不可用时标准命令必须继续可用。
- 今日消耗查询由模型识别查询动作和范围,账户集合由数据库确定,数据在触发时从腾讯 API 实时汇总;查询只读、无需二次确认,默认只返回总览。
- 自然语言里的“全部”包含历史自动化账户与 `realtime_control_account_scope` 显式纳管账户的并集;选中账户后操作其符合状态条件的全部广告。模型输出的账户 ID 必须与原始文本一致并再次经过数据库范围校验。
- 裸“暂停/停止/恢复”只能进入有过期时间的多轮补问,不能猜测范围。命令预览必须冻结广告 ID,逐账户展示腾讯当日消耗;报表读取失败时禁止确认。重叠待确认命令必须拒绝,不能自动覆盖或排队。
- `暂停` 只把广告 `begin_date` 延后到下一投放日,保留正常状态和原 `time_series`,由腾讯在次日 06:00 自动投放,系统不能再调用主动开启接口;`停止` 表示持续停止;`恢复` 只解除运营暂停,不能解除仍生效的 CPM 暂停。
- 运营暂停、停止、恢复等群聊命令必须二次确认,且必须来自配置群并 @机器人,发送人必须在允许列表。
- ROI 表格逐行审批是独立入口:默认隐藏的黄色【审批选择】列取消隐藏后填写“批准”即为最终确认,不再经过群聊二次确认。审批表使用获得链接者可编辑权限,但执行目标必须只按数据库中的隐藏幂等键回读,不能信任表格内可编辑的账户、广告、创意、成本或 ROI 字段。
- 运营暂停状态和 CPM 暂停状态必须分开持久化。原本人工暂停的广告不能被系统认领或自动开启;腾讯后台人工重新开启时以人工操作为准。
- 实时控制和飞书写操作必须共用 MySQL advisory lock,并执行腾讯写后回读校验。
- 广告设置 `bid_hold` 时,实时 CPM 调控不得修改其出价;解除 `bid_hold` 只能恢复实时调价资格,不能自动恢复历史价格。
- `RTC_APPLY_ENABLED`、`DAILY_ROI_ENABLED`、`ROI_APPLY_ENABLED` 默认关闭;生产切换前必须先完成 dry-run 和只通知验证。

## ROI 北极星指标与日级调控

- ROI 是指导渠道、账户、广告和创意动作的核心北极星指标,必须作为独立领域模块维护,不能散落在飞书、腾讯 API 或调度代码中。
- ROI 指标计算必须保持纯数据输入/输出,不能依赖飞书审批、腾讯写操作或具体动作执行器。动作策略只能消费一个明确的 `metric_version`。
- 当前日级 ROI 指标版本为 `north_star_roi_t15_v8`,策略版本为 `roi_policy_v15`,报表版本为 `roi_report_v41`,使用已发布参数 `20260712_A0-A15_v2`。T0 裂变人数取 `SUM(t0_fission_uv_root)`,不得使用旧字段 `t0裂变人数`。首层效率收入取 `SUM(效率收入)`,T0 实际裂变收入取当天的 `SUM(裂变效率收入)`;只预测 T1-T15 增量:`T0实际裂变收入 * (传播裂变系数 - 1)`,预测总收入为 `首层效率收入 + T0实际裂变收入 + 预测T1-T15裂变收入`,禁止继续读取已废弃的 `多层t0裂变收入`。
- 小程序传播裂变系数按 `人群包+转化目标精确 -> 人群包回退 -> 转化目标回退 -> 渠道回退` 匹配;公众号按 `合作方+公众号精确 -> 合作方回退 -> 渠道回退` 匹配。样本不足的精确实体不能使用自身系数。
- 小程序日级 ODPS 数据必须保留 `广告优化目标`;缺失目标只能进入人群包回退,不得默认成关键页面访问。企微 ROI 口径未复核,优先使用已发布的合作方 SQL 计算结果,合作方未匹配、样本不足或旧版本无企微参数时默认参考系数为 2.5 并明确标注;企微不进入阈值样本池且不得生成调控动作。
- ROI 报表包含小程序创意级、小程序广告级和公众号的三日汇总与每日明细共 6 个主 Sheet,其中广告级两个 Sheet 默认隐藏。金额、UV 和人数显示三日总量/3;ROI 和裂变率使用三日汇总后的加权口径。汇总表可见尾部列按“三日均值ROI -> 当日效率ROI -> 预测总效率ROI -> 关停线 -> 扩量线（P80） -> 建议动作 -> 建议说明 -> 当前创意状态”排列;三日均值ROI为三日加权实际ROI,当日效率ROI为最新日实际ROI,预测总效率ROI为三日加权预测ROI,不单列最新日预测ROI。当前创意状态只对创意级关停建议只读腾讯并显示正常、已停止或读取失败,其他行留空。审批选择、整体三日排名百分位、最新日UV、覆盖天数、昨日成本、关停规则、预算选择状态、实际关停成本占比、动作原因、阈值样本状态和执行结果只保留为隐藏审计字段;关停线分位点只进入运行摘要和数据库快照,不写入业务 Sheet。三列ROI均使用深红—黄—绿色阶,绿色代表表现好。普通数值显示两位小数,UV、人数、数量和广告年龄显示整数。汇总顺序为关停、扩量、其他中间 ROI、观察,第一条扩量行顶部使用粗线分隔。每日明细的 `dt` 必须位于第一列并按日期倒序。主 Sheet 冻结首行和前 7 列。
- ROI 审批表应展示所有进入阈值样本池的小程序和公众号实体,以及最新日首层 UV>200 的补充观察实体;不能只展示有动作的数据。企微暂不进入本版报表、阈值和动作。正式样本按关停、扩量、其他中间 ROI、观察排列,补充观察置底并按最新日 UV 倒序。
- 成熟参数是独立版本化只读资产。审计后的参数必须通过更新脚本发布到 MySQL 的 `roi_fission_parameter_release` 和 `roi_fission_parameter_value`;日级 ROI 只从数据库消费 `ROI_FISSION_PARAMETER_VERSION` 指定且通过内容哈希校验的版本,不能在日常任务中现场重算或静默回退到镜像文件。离线复算只能生成草稿,经恒等式、行数、匹配率和新旧结果对账后才能发布。
- ROI 报表只上传一次,同一在线表链接发送到 ROI 通知群和 `FEISHU_OPERATOR_CHAT_ID` 投放审批群;群 ID 相同时必须去重。
- ROI 公式、收入/成本归属、裂变口径或实体粒度发生语义变化时,必须升级指标版本并保存新快照;不得覆盖或重算成旧版本历史结果。
- 策略阈值和动作语义使用独立 `policy_version`;指标版本与策略版本必须同时写入每个运行批次。
- 每次运行必须保存完整实体快照、阈值配置、动作建议、可执行性原因和最终执行审计,不能只保存候选或飞书表格。
- 日级 ROI 当前读取 T-1 至 T-3 的连续三日数据,并使用 `usersharedepth<=1`。连续三天每天首层 UV>200、成本>0 且 ROI 有效的小程序创意级和公众号实体分别进入渠道独立样本池。公众号即转使用自身合格实体等权 P20;小程序按 T-1 创意级实际成本5%软预算动态反推关停线和实体分位点,广告级重复聚合成本不得进入分母。
- 小程序三日持续低 ROI 与单日补充关停按 6:4 获得目标成本的60%/40%基础额度,各组按对应预测 ROI 从低到高取连续前缀,未用额度只在剩余候选间软流转。目标占昨日总成本5%,软上限5.5%,绝对上限6%。三日正式判断优先:进入三日正式判断的小程序创意无论是否被预算选中或生成何种动作,都不得再进入单日判断。其余创意仅在最新日 UV>200、成本>0、ROI有效且广告 age>3天时进入单日候选池;最新日预测 ROI 必须同时不高于0.20且不高于候选池实体等权P10才成为单日关停候选。单日规则不使用历史保护。高 ROI 仍使用合格小程序创意实体等权 P80;公众号只通知参考。
- 同广告永久基础出价 3 天内最多上调一次,且不超过首次纳管基础价的 2 倍。调整后必须同步实时 CPM 模块的基础出价,避免恢复旧值。
- ROI 逐行审批有效期默认 120 分钟。腾讯写操作必须与实时调控共用数据库 advisory lock,执行前回读映射和状态,执行后再次回读校验;执行结果必须回写表格并发送飞书通知,通知失败只能重试通知,不能重复腾讯写操作。
- `DAILY_ROI_ENABLED=1`、`ROI_APPLY_ENABLED=0` 只允许计算、快照、报表和审批预览;只有 `DAILY_ROI_ENABLED=1`、`ROI_APPLY_ENABLED=1`、`ROI_SHEET_APPROVAL_ENABLED=1` 时,表格批准后才允许自动执行腾讯写操作。
- 禁止配置 `DAILY_ROI_ENABLED=0`、`ROI_APPLY_ENABLED=1`;服务启动时必须拒绝这种不完整配置。

## 实时业务收入预测

- 实时收入预测的累计收入和最近两个窗口统一读取 `loghubods.ads_ad_own_package_detail_15min` 当天 `package_cost_times_today`;不依赖昨日曲线。日表只补充实时业务指标和次日最终对账。
- `ads_ad_own_package_detail_15min.report_date` 表示 15 分钟窗口的开始时间;预测时点和报表展示必须使用窗口结束时间,即 `report_date + 15 分钟`,不能提前使用尚未结束的窗口。
- 每个新 `report_date` 快照及对应预测必须幂等写入 MySQL 用于审计;MySQL 不作为近期趋势信号来源。
- 第一阶段预测是独立只读能力,只输出预测全天收入、置信区间和建议总成本;不得调用腾讯写接口、飞书审批或 ROI 动作执行器。
- 默认从每天 `06:15` 开始,按自然 15 分钟刻度预测一次。主模型使用当前时点最近两个连续15分钟窗口;窗口不足、不连续、数据过期或累计值异常时必须停止预测并记录原因,不能补造趋势数据。
- 5 分钟与 15 分钟区间收入可能无法与日表最终收入对齐;预测内部的今日累计和速度窗口必须保持同一15分钟 `today` 口径,日表不得混入预测公式。历史训练只接受96个连续窗口的完整日,异常日期不能进入参数发布或算法评价。
- `REVENUE_FORECAST_ENABLED` 默认关闭。算法、成本系数或窗口口径变化时必须升级 `REVENUE_FORECAST_VERSION`,保留历史预测结果。
- 当前主预测使用最近两个15分钟窗口的加权速度乘以历史同时间点剩余倍数。最近窗口默认权重为2/3,历史参数必须按版本离线发布到 MySQL;实时任务只能读取 `REVENUE_SPEED_PARAMETER_VERSION` 指定版本,不能现场扫描历史或使用预测日及未来数据。
- 剩余倍数参数必须保存训练日期范围、样本数、P10/P50/P90 和 MAD。P50 用于正式预测,P10/P90 只表示历史经验区间;参数缺失、样本不足或窗口异常时停止本次预测并记录原因,不得使用其他算法兜底。
- 收入预测模型上线前必须做严格走步回测,至少比较 MAPE、带符号偏差、P90 绝对误差、超过20%误差比例和相邻预测跳变。第一阶段结果不得直接触发腾讯或飞书调控。

## 模块 B 创意创建规则

- 目标是单广告最终合格创意数,不是单次生成的 pending 行数。
- 当前目标:每条广告最终有效创意不少于 8 个。
- `DENIED` 创意不计入有效创意。
- 每次创意准备中,每个视频来源最多获取并尝试 100 条视频。
- 先尝试 primary 视频来源。
- 如果 primary 视频经过品类过滤、风险审核、素材质量过滤后仍无法产出可用 pending creative,再用同一个 `crowdPackage` 尝试 `source=hot`。
- `source=hot` 只能改变内容服务的视频来源,不能改变账户配置的人群包。
- hot 兜底必须走和 primary 完全一致的品类过滤、风险审核、素材召回和审批流程。

## 承接视频安全和质量

- 创建落地计划或腾讯创意前,必须先做视频风险审核。
- 默认风险阈值是 5。风险等级 6-10 应拦截。
- 风险接口异常时要保守处理,不能创建落地计划。
- 使用 `videoContentList.category` 做内容品类过滤。
- 默认过滤品类是 `早中晚好`、`祝福音乐`、`历史名人`。
- 品类过滤必须发生在风险审核、素材召回和 `xcx/save` 之前。
- 不要把 `categoryName` 当作内容品类使用;它是召回逻辑使用的语义/视觉特征字段。

## 素材召回规则

- `videoContentList` 只负责选择承接视频,素材召回特征必须通过 `video_id` 查询 ODPS 表 `loghubods.dwd_video_element_contribution_analysis`。
- primary 和 hot 视频都必须走同一套 ODPS 特征查询和素材召回逻辑。
- 当前只使用 ODPS 特征中的 `解构选题` 列和 `实质` 维度;`形式`、`意图` 不参与素材召回。
- 当前召回映射:
  - `解构选题` -> `VIDEO_TOPIC`
  - `实质 + 灵感点` -> `INSPIRATION_SUBSTANCE`
  - `实质 + 关键点` -> `KEYPOINT_SUBSTANCE`
  - `实质 + 目的点` -> `PURPOSE_SUBSTANCE`
- 同一视频的多个召回 query 可以并行调用 `batchByText`,再按 `material_id` 合并去重。
- 当前素材硬筛只看相似度:`score >= 0.8`。
- 曝光、CTR、ROI 只作为审批展示和兜底排序参考,不作为硬筛。
- 素材通过相似度筛选后,默认按历史消耗 `cost` 倒序选择。
- 飞书「素材来源」支持 `history`、`ai_generated`、`external_recall`;外部素材链路必须独立执行,不能改变历史召回和原 AI 生成链路的行为。
- 外部素材使用独立 `sourceLabels` 召回,相似度准入后按 `loghubods.cooperate_top_cards_daily_v1.card_cover_id` 关联配置窗口内 `访问uv` 之和倒序,默认窗口为 90 天,同 UV 时按相似度倒序。
- 外部素材通过参考图编辑只清除已有播放按钮或播放器控件,不主动改写原图主题、标题、主体和版式;派生图必须经过 AI 预审和飞书人工审批,审批通过后才上传腾讯并创建创意。
- 落地页视频去重按素材来源拆池。历史素材链路同一 `crowd_package + landing_video_id` 默认最多 1 次。
- AI 生成素材链路和历史素材链路互不占用 landing 去重名额;AI 链路同一 `crowd_package + landing_video_id` 默认最多 1 次。
- 外部素材链路拥有独立 landing 去重池,同一 `crowd_package + landing_video_id` 默认最多 1 次。
- 落地页视频跨轮近期排重只能基于已提交腾讯、已执行或已有明确投放结果的记录。
- 未提交腾讯的 `prepared` / `pending` 记录不能进入跨轮排重;它们只能用于当前运行内去重,或作为中断后可复用的恢复缓存。
- 同一广告内同一 `landing_video_id` 仍保留限频保护,默认最多 1 次。
- 素材需要跨账户/跨天排重,默认按同人群包下近期使用过的 `material_id` 排除。
- 当前运行内的审批候选需要做轻量展示去重:同一 `crowd_package + material_id` 只进入本轮审批表一次;该去重只存在内存,不写入历史排重库。
- 当前 `prepare_one_creative_for_ad` 会真实上传图片并创建 `xcx/save` 落地计划,默认不能直接并行执行;如需并行,必须先拆分为纯候选选择和副作用串行确认两段。
- `videoContentList` 每个 source 默认最多读取 3 页,每页 100 条,按 `video_id` 去重合并。

## 视频召回人群包映射

- 腾讯投放、人群包授权、落地计划仍使用账户配置的人群包。
- 内容服务 `videoContentList` 的 `crowdPackage` 可以有独立映射。
- 当前默认映射:
  - `cell*year*商业` 获取视频时映射为 `wx*商业`
  - `回流330以上人群` 获取视频时映射为 `R_330+`
- `source=hot` 只能改变内容服务的视频来源,不能改变映射后的召回人群包。
- 内容服务 `source` 也可以按人群包做独立映射。
- 当前默认没有 source 覆盖;`回流330以上人群` 获取视频时使用 `.env` 中的 `prior`。

## 腾讯人群包和资产规则

- 创建广告前,人群包 ID 必须验证为目标广告账户可见。
- 跨账号使用人群包时,要走腾讯人群授权接口,授权后再次验证目标账户可见。
- 品牌图 ID 是账户级资产,不能复用其他账户的 `brand_image_id`。
- 监测链接 `feedback_id` 是账户级资产,应通过配置模板查找或创建。

## 飞书同步规则

- 飞书配置同步是每日主流程的一部分。
- 飞书同步失败时应停止创建流程。
- `是否自动化执行` 不等于 `是` 时,只关闭新建/补量,不删除 DB 配置,不修改腾讯既有资产。
- 不要因为飞书行被关闭就删除或回滚线上状态。

## 代码修改纪律

- 修改范围要收敛在当前生产链路相关模块内。
- 运营可能需要调整的生产行为,应提供配置项。
- 属于账户、人群包、预算、出价、品牌、监测链接的值,优先进入飞书或 DB,不要写死在代码里。
- 运行 `examples/auto_put_ad_mini` 下的 Python 脚本时,默认使用仓库根目录 `.venv/bin/python`;不要直接使用系统 `python3`,避免缺少 FastAPI、PyODPS、OSS 等项目依赖导致误判。
- 端到端执行前,必须意识到可能产生外部副作用:腾讯广告/创意创建、图片上传、DataNexus 监测链接创建、人群包授权、`xcx/save` 落地计划创建。
- 端到端流程中断时,已经完成的外部副作用不会自动回滚。
- 行为、环境变量或运营流程变化时,要同步更新面向生产的文档。
