unified_services_deployment.md 39 KB

自动投放双服务部署

代码注释与任务日志约定

生产源代码的自然语言注释和文档字符串统一使用中文;API 字段、环境变量、SQL、 枚举值和第三方接口名称保留原始英文,避免翻译后与真实协议不一致。复杂业务规则、 外部副作用、幂等边界、失败保守策略和兼容逻辑必须说明“为什么”;名称已经完整表达 行为的简单取值函数不重复添加无信息量注释。

所有生产任务入口必须使用统一日志初始化,并至少记录任务名、阶段、开始/成功/失败、 耗时和 trace_id。调度子进程额外记录 child_trace_id,常驻服务的周期任务额外记录 cycle_id;捕获异常时必须使用异常堆栈日志,不能只记录 str(exc) 后静默返回。

1. Jenkins 构建

构建统一完整镜像,不再构建 tencent-realtime-control 独立镜像:

set -e

REGISTRY="registry.cn-hangzhou.aliyuncs.com/stuuudy"
IMAGE="${REGISTRY}/ad-put-agent"

cd "${WORKSPACE}"
docker build \
  -f Dockerfile.auto_put_ad_mini \
  -t "${IMAGE}:latest" \
  -t "${IMAGE}:${BUILD_TIMESTAMP}" \
  .
docker push "${IMAGE}:latest"
docker push "${IMAGE}:${BUILD_TIMESTAMP}"

ssh -i /root/.ssh/piaoquan.pem root@8.222.142.37 \
  "sh /home/sh/deploy.sh ${BUILD_TIMESTAMP}"

2. ECS 文件

将仓库中的 docker-compose.yml 放到 /home/server/docker-compose.yml。 当前部署兼容 ECS 已安装的 Docker Compose 2.27.0

两个服务共用 /home/server/.env。首次部署从仓库根目录唯一生产模板 runtime.env.example 复制,组件目录内的 .env.example 仅保留本地兼容用途:

cp runtime.env.example /home/server/.env
chmod 600 /home/server/.env
docker compose version

原有数据库、ODPS、腾讯、飞书、OSS 和 AI 环境变量全部保留。

生产模板不配置 CREATION_ONLY_ACCOUNT_IDS;指定账户的一次性执行必须使用 下文的严格 --account-id 命令,非法账户会直接退出,不会退化成全账户。 创建流程的广告审批、创意审批和执行汇报统一发送到 FEISHU_CREATION_CHAT_ID;为空时才兼容回退 FEISHU_OPERATOR_CHAT_ID

首次部署增加:

RTC_START_HOUR=12
RTC_STOP_HOUR=21
RTC_POLL_SECONDS=600
RTC_HIGH_CPM=250
RTC_LOW_CPM=190
RTC_BID_UP_RATIO=1.05
RTC_MAX_PARTITION_LAG_HOURS=2
RTC_APPLY_ENABLED=0
RTC_COMMAND_ENABLED=1
RTC_COMMAND_CHAT_ID=oc_xxx
RTC_COMMAND_ALLOWED_OPEN_IDS=ou_xxx
RTC_COMMAND_CONFIRM_TTL_MINUTES=10
RTC_COMMAND_NEXT_DELIVERY_HOUR=6
RTC_COMMAND_DRAFT_TTL_MINUTES=5
RTC_NL_COMMAND_ENABLED=0
RTC_COMMAND_LLM_MODEL=google/gemini-3-flash-preview
RTC_COMMAND_LLM_TIMEOUT_SECONDS=20
RTC_COMMAND_LLM_CONFIDENCE_THRESHOLD=0.85
OPEN_ROUTER_API_KEY=sk-or-v1-xxx

REVENUE_FORECAST_ENABLED=0
REVENUE_FORECAST_POLL_SECONDS=900
REVENUE_FORECAST_START_TIME=06:15
REVENUE_FORECAST_STOP_TIME=22:30
REVENUE_FORECAST_TREND_WINDOW_MINUTES=15
REVENUE_FORECAST_TREND_WINDOW_COUNT=2
REVENUE_FORECAST_TARGET_RATIO=3.5
REVENUE_FORECAST_MAX_LAG_MINUTES=20
REVENUE_FORECAST_VERSION=revenue_forecast_v6_weighted_30m_speed
REVENUE_SPEED_PARAMETER_VERSION=revenue_speed_params_v1
REVENUE_SPEED_LATEST_WEIGHT=0.66666667
REVENUE_SPEED_MINIMUM_SAMPLES=3

DAILY_CREATION_ENABLED=0
DAILY_CREATION_HOUR=10
DAILY_CREATION_MINUTE=30
DAILY_REVIEW_ENABLED=0
DAILY_REVIEW_INTERVAL_HOURS=2
DAILY_REJECTED_CREATIVE_CLEANUP_ENABLED=0
DAILY_REJECTED_CREATIVE_APPLY_ENABLED=0
DAILY_UNDERPERFORMING_CREATIVE_CLEANUP_ENABLED=0
DAILY_UNDERPERFORMING_CREATIVE_APPLY_ENABLED=0
DAILY_UNDERPERFORMING_CREATIVE_NEW_MIN_AGE_DAYS=5
DAILY_UNDERPERFORMING_CREATIVE_NEW_MAX_AGE_DAYS=7
DAILY_UNDERPERFORMING_CREATIVE_NEW_IMPRESSIONS_THRESHOLD=100
DAILY_UNDERPERFORMING_CREATIVE_OLD_DAILY_IMPRESSIONS_THRESHOLD=100
DAILY_UNDERPERFORMING_CREATIVE_WINDOW_DAYS=7
DAILY_UNDERPERFORMING_AD_CLEANUP_ENABLED=0
DAILY_UNDERPERFORMING_AD_APPLY_ENABLED=0
DAILY_UNDERPERFORMING_AD_WINDOW_DAYS=3
DAILY_UNDERPERFORMING_AD_MIN_AGE_DAYS=5
DAILY_CLEANUP_CANDIDATE_BATCH_MIN_SIZE=20
DAILY_CLEANUP_CANDIDATE_BATCH_SIZE=100
DAILY_CLEANUP_CANDIDATE_STORE_WORKERS=4
TENCENT_AD_AD_PROCESS_WORKERS=8
DAILY_PARTIAL_CREATIVE_COST_THRESHOLD_YUAN=50
DAILY_WECHAT_MINI_PROGRAM_CREATIVE_COST_THRESHOLD_YUAN=100
DAILY_REJECTED_CREATIVE_DELETE_CLAIM_STALE_MINUTES=30
DAILY_REJECTED_CREATIVE_NOTIFICATION_LOCK_NAME=ad_rejected_creative_notification
# 与清理任务失败告警同一位置;删除失败也复用该位置发送独立通知。
CREATIVE_CLEANUP_FAILURE_FEISHU_CHAT_ID=
CREATIVE_CLEANUP_INTERNAL_ONLY_AGENCIES=小程序-自动化
TENCENT_AD_DELETE_WORKERS=4
TENCENT_AD_DELETE_MIN_INTERVAL_SECONDS=0.25
TENCENT_AD_DELETE_RATE_LIMIT_RETRIES=2
TENCENT_AD_DELETE_RATE_LIMIT_BACKOFF_SECONDS=1
DAILY_REJECTED_CREATIVE_CLEANUP_HOUR=11
DAILY_REJECTED_CREATIVE_CLEANUP_MINUTE=0
DAILY_REJECTED_CREATIVE_RUN_ON_STARTUP=0
DAILY_RUN_ON_STARTUP=0
DAILY_REVIEW_RUN_ON_STARTUP=0
DAILY_SYNC_DELIVERY_TEMPLATE=0

DAILY_ROI_ENABLED=0
DAILY_ROI_HOUR=9
DAILY_ROI_MINUTE=0
DAILY_ROI_POLL_INTERVAL_MINUTES=30
DAILY_ROI_POLL_CUTOFF_HOUR=18
DAILY_ROI_POLL_CUTOFF_MINUTE=0
DAILY_ROI_RUN_ON_STARTUP=0
ROI_INTERNAL_TEST_ENABLED=0
ROI_INTERNAL_TEST_HOUR=15
ROI_INTERNAL_TEST_MINUTE=30
ROI_INTERNAL_TEST_DEDUP_ENABLED=1
ROI_FAILURE_FEISHU_CHAT_ID=
ROI_APPLY_ENABLED=0
ROI_SHEET_APPROVAL_ENABLED=0
ROI_SHEET_APPROVAL_POLL_SECONDS=60
ROI_APPROVAL_TTL_MINUTES=120
ROI_SCALE_RATIO=1.10
ROI_SCALE_COOLDOWN_DAYS=3
ROI_MAX_BASE_RATIO=2.00
ROI_SELF_STOP_COST_SOFT_LOWER_RATIO=0.045
ROI_SELF_STOP_COST_TARGET_RATIO=0.05
ROI_SELF_STOP_COST_SOFT_UPPER_RATIO=0.055
ROI_SELF_STOP_COST_HARD_CAP_RATIO=0.06
ROI_SELF_STOP_THREE_DAY_SHARE=0.60
ROI_SELF_STOP_ONE_DAY_SHARE=0.40
ROI_ONE_DAY_MIN_UV=200
ROI_ONE_DAY_HARD_STOP_ROI=0.20
ROI_ONE_DAY_STOP_QUANTILE=0.10
ROI_PARTNER_STOP_QUANTILE=0.20
ROI_UP_QUANTILE=0.80
ROI_FISSION_PARAMETER_VERSION=20260712_A0-A15_v2
ROI_FEISHU_CHAT_ID=oc_xxx
ROI_AGENCY_WEBHOOK_ENABLED=0
ROI_AGENCY_WEBHOOKS_JSON={}

多个操作人使用英文逗号分隔 RTC_COMMAND_ALLOWED_OPEN_IDS。 代码默认 RTC_APPLY_ENABLED=0,日级创建、审核扫描、ROI 和模板同步也默认关闭; 缺少显式生产配置时不会创建广告或修改腾讯。

DAILY_REJECTED_CREATIVE_CLEANUP_ENABLED=1 会在默认 11:00 注册一个与日级 ROI 并行的独立调度线程。DAILY_REJECTED_CREATIVE_APPLY_ENABLED 默认关闭;关闭时任务只枚举、 判断和保存待清理审计,不调用腾讯写接口,同时将待删除项标记为“建议删除创意(未执行)”并与 需人工判断项一起发送预演通知。任务从 loghubods.opengid_base_data 查询 T-3 至 T-1 三个完整日内 SUM(成本)>0账号id,枚举这些账户的全部未删除创意; 账户范围不附加 usersharedepthvideoidhotsencetype 条件。 审核异常规则的账户范围不依赖 MySQL 白名单或自动创建配置。任务批量读取腾讯正式审核结果: creative_set_approval_status=CREATIVE_SET_APPROVAL_STATUS_DENIED 时删除整条创意。 creative_set_approval_status=CREATIVE_SET_APPROVAL_STATUS_PARTIAL_NORMAL 时,通过腾讯 /daily_reports/getREPORT_LEVEL_DYNAMIC_CREATIVE 层级实时汇总任务执行日前三个自然日 (昨天及前两天,不含今天)的创意消耗。历史三天消耗严格低于 DAILY_PARTIAL_CREATIVE_COST_THRESHOLD_YUAN(默认 50 元)时删除整条创意,达到 50 元 不会自动删除。部分投放中创意如果元素粒度存在名称为“微信小程序”且状态为拒绝的记录,历史三天消耗 严格低于 DAILY_WECHAT_MINI_PROGRAM_CREATIVE_COST_THRESHOLD_YUAN(默认 100 元)时删除,达到 100 元 则进入代理人工判断;实时消耗接口 失败时同样只报警,禁止把失败结果当作 0 消耗。除此之外,普通创意的元素或版位审核拒绝均不触发 任何自动删除;系统不再提供删除拒审元素组件的流程。

DAILY_UNDERPERFORMING_CREATIVE_CLEANUP_ENABLED=1 会在同一个任务中追加长期未起量判断, 不会注册第二个调度任务。未起量范围独立读取 loghubods.ad_put_tencent_creative_day:先按 account_id + creative_id 取最新一行,再仅保留 is_delete=0 的创意。近 3 日有消耗账户仍只用于审核异常规则;仅因 ODPS 有效创意补入的账户 不会进入审核异常规则。任务先完成全部审核异常判断;腾讯已返回删除状态的创意在任务入口即过滤, 本轮命中审核异常规则的创意也会加入排除集,不再进入未起量判断。真实删除同样严格按 “审核异常创意 → 未起量创意 → 零消耗广告”三个阶段串行执行,防止已删除或待删除创意被重复检测和删除。 审核账户范围或该账户审核接口读取失败时,该账户未起量创意也会保守跳过;广告判断与审核/创意接口隔离, 仍可按广告自身条件继续执行。 搭建天数按上海时区从创建时刻起已完整经过的 24 小时天数计算,创意搭建时间读取 loghubods.ad_put_tencent_creative_day.create_time。新素材的“累计”指标通过腾讯 REPORT_LEVEL_DYNAMIC_CREATIVE 从创意创建日汇总至 T 日,包含当日曝光和消耗;老素材的近 7 日指标仍严格使用 T-7 至 T-1 七个完整自然日,并另查 T 日当日消耗。只判断配置状态为正常、 审核状态为投放中或部分投放中、且所属广告配置状态也为正常的创意;搭建时间缺失、腾讯指标读取失败、 创意或所属广告已暂停/停止、审核中或审核拒绝时一律不按未起量规则删除。规则为:新素材 5 < 搭建天数 <= 7 时,累计曝光严格小于 100 且累计消耗等于 0 才删除;老素材搭建天数大于 7 时, 近 7 天累计消耗等于 0,且日均曝光严格小于 100 或严格大于 100 才删除。新老素材只要当日已有消耗就不删除。按需求保留严格不等号, 日均曝光恰好等于 100 不命中。阈值分别由 DAILY_UNDERPERFORMING_CREATIVE_NEW_MIN_AGE_DAYSDAILY_UNDERPERFORMING_CREATIVE_NEW_MAX_AGE_DAYSDAILY_UNDERPERFORMING_CREATIVE_NEW_IMPRESSIONS_THRESHOLDDAILY_UNDERPERFORMING_CREATIVE_OLD_DAILY_IMPRESSIONS_THRESHOLDDAILY_UNDERPERFORMING_CREATIVE_WINDOW_DAYS 管理。 DAILY_UNDERPERFORMING_CREATIVE_APPLY_ENABLED 是独立真实删除开关,默认关闭;即使拒审创意已经开启 真实删除,新规则也必须先单独预演后才能开启。启用新规则扫描或删除必须配置 FEISHU_AD_PROJECT_CHAT_ID

未起量候选的账户名称和代理商昵称统一读取 loghubods.ad_put_tencent_account:先按 account_idupdate_time/create_time/id 取最新一行,再保留 is_delete=0 的当前账户;account_name 写入审计及 Excel“账户名称”,agent_name 写入审计及长期未起量内部 Excel“代理商昵称”。该昵称只用于内部展示, 不参与代理群路由,也不会使未起量明细进入代理商通知。

全部创意判断完成后,任务才进入广告判断阶段。开启 DAILY_UNDERPERFORMING_AD_CLEANUP_ENABLED=1 后,系统只从上述 ODPS 有效创意的 ad_id 派生广告范围, 不依赖创意审核结果或腾讯创意列表是否读取成功;若腾讯创意实时归属与 ODPS ad_id 不一致,只记录诊断, 不阻断广告按自身条件判断。仅判断配置状态正常、当前处于投放日期范围内,且腾讯 created_time 显示创建后已完整超过 5 天的广告。系统通过 REPORT_LEVEL_ADGROUP 实时汇总 T-3 至 T-1 三个完整自然日消耗,并另查 T 日当日消耗;两者都等于 0 时才进入广告删除候选。 指标接口异常、广告暂停/停止、尚未到开始日期或已经超过结束日期时均不删除。真实删除由独立开关 DAILY_UNDERPERFORMING_AD_APPLY_ENABLED 控制,默认关闭;删除前在全局腾讯写锁内重新读取广告状态和 近 3 日及当日消耗,删除后再次回读确认。广告删除状态统一通过 /v3.0/adgroups/getadgroup_id 精确查询;is_deleted(兼容内部字段名 is_delete)为真,或 system_statusADGROUP_STATUS_DELETED/SMART_ADGROUP_STATUS_DELETED,任一条件成立即视为已删除。普通范围查询未返回 广告时会再显式查询已删除范围;两次都未返回只表示状态未知,会延后处理,不能仅凭列表缺失推断广告已删除。 广告扫描要求创意未起量扫描已开启,不能跳过创意阶段单独运行; 窗口由 DAILY_UNDERPERFORMING_AD_WINDOW_DAYS 管理,默认 3 天;最小年龄由 DAILY_UNDERPERFORMING_AD_MIN_AGE_DAYS 管理,默认 5(严格要求年龄大于该值)。任务若在删除批次中跨过 上海自然日,会把剩余项置为延后,禁止沿用上一日窗口删除。

腾讯当日实时报表存在平台延迟,当日消耗复核只能降低、不能完全消除最近一段时间起量但尚未入报表的风险; 因此两类未起量真实删除开关默认关闭,生产必须先核对预演结果和实际数据延迟,再决定启用时段。

逐账户扫描前会按 TENCENT_AD_TOKEN_PREFETCH_WORKERS(默认 8)并发 预取并缓存 access token;腾讯只读 GET 遇到连接/TLS 中断、超时、408、429 或 5xx 时按 TENCENT_AD_GET_RETRY_ATTEMPTS(默认总尝试 3 次)和指数退避自动重试。腾讯查询、数据库 审计和清理操作仍保持原有顺序。账户未取得 token,或腾讯返回 11002 表示 token 无效时, 本轮按账户安全跳过,不再读取该账户广告、审核结果或消耗,也不计入任务失败退出码;跳过账户 ID 保留在运行摘要和日志中,待授权恢复后由下一次任务自动补扫。账户级创意、广告和审核结果读取按 TENCENT_AD_ACCOUNT_SCAN_WORKERS(默认 8)有限并发,每个账户任务使用独立 HTTP Session; 创意命中判断只在内存完成,达到 DAILY_CLEANUP_CANDIDATE_BATCH_MIN_SIZE(默认 20)后,候选按 DAILY_CLEANUP_CANDIDATE_BATCH_SIZE(默认 100)复用一个 MySQL 事务连接,并由 DAILY_CLEANUP_CANDIDATE_STORE_WORKERS(默认 4,最大 16)并发写入;不再为每条候选单独建连和回读。 整个批次失败时会回滚并按单条幂等路径重试,单条失败不会中断同批其他候选。广告候选的纯内存判断按 TENCENT_AD_AD_PROCESS_WORKERS(默认 8,最大 32)并发。删除前先原子认领数据库记录,再在 RTC_DB_LOCK_NAME 写锁内并发回读、复审和删除;只有认领成功且整创意删除动作保持一致才执行, 历史遗留的组件删除候选不会被加载执行。认领后仅允许持有 DELETING 状态的进程回写结果; 超过 DAILY_REJECTED_CREATIVE_DELETE_CLAIM_STALE_MINUTES(默认 30 分钟)的认领可由下次任务恢复, 并先回读腾讯状态,避免进程异常退出造成永久卡住或重复删除。删除并发数由 TENCENT_AD_DELETE_WORKERS 控制(默认 4,最大 16);所有 worker 共享 TENCENT_AD_DELETE_MIN_INTERVAL_SECONDS(默认 0.25 秒)的进程内请求节流器,避免多账户并发 绕过限频;HTTP 429 或明确的业务限频码最多按 TENCENT_AD_DELETE_RATE_LIMIT_RETRIES(默认 2 次) 并使用 Retry-After 或指数退避重试。网络超时、连接中断、408、5xx 和无效响应不会自动重发删除, 统一记为结果未知并由下一轮回读确认。飞书通知使用独立的 DAILY_REJECTED_CREATIVE_NOTIFICATION_LOCK_NAME MySQL 锁(默认 ad_rejected_creative_notification),拿锁后重新读取待通知记录,避免多实例重复发送;该锁不会在 腾讯写操作期间持有。每条写前状态、回读结果、失败状态和通知状态均持久化;超时导致写结果未知时, 下次运行先回读确认,不会盲目重复写入。

审核异常自动清理成功项和待人工判断报警项按日级 ROI 数据中的代理归属生成一代理一份 YYYYMMDD_代理名称_创意审核异常处理_批次摘要.xlsx。代理报表仅展示代理、账户、广告、创意、 近 3 天历史消耗、执行操作、配置状态、创意审核状态 (creative_set_approval_status) 和审核不通过原因。 投放调控内部汇总表额外展示消耗日期范围、操作判断原因、元素和版位粒度审核状态/原因及检查时间。 代理归属优先使用日级 ROI 数据中的创意级归属,其次使用账户级唯一归属;仍为空时按账户 ID 从 loghubods.ad_put_tencent_account 按账户读取 id 最大的一条记录;只有该最新记录满足 is_delete=0status=1agent_name 非空时才作为兜底,不会使用更旧记录,也不会覆盖已有归属。 Excel 中 已知状态枚举按腾讯中文描述展示,未知枚举保留原值;删除判断和数据库审计始终使用腾讯返回的 原始枚举值。报表通过现有 ROI_AGENCY_WEBHOOKS_JSON 精确路由到代理群;同批次还会生成包含全部代理创意的汇总表,通过飞书应用 发送到 FEISHU_AD_PROJECT_CHAT_ID 投放调控通知群。代理通知与投放调控汇总通知分别记录成功状态; CREATIVE_CLEANUP_INTERNAL_ONLY_AGENCIES 使用逗号分隔并精确匹配,默认包含“小程序-自动化”;命中后 只进入 FEISHU_AD_PROJECT_CHAT_ID 内部汇总,不生成代理商报表、不要求代理 webhook,内部通知成功后 同时完成该记录的代理通知抑制标记,后续任务不会重复尝试代理通知。 长期未起量创意和近 3 日零消耗广告绝不进入代理商报表或代理商群;系统为这部分单独生成 YYYYMMDD_投放调控_长期未起量创意及广告清理汇总_批次摘要.xlsx,并在同一次任务中向 FEISHU_AD_PROJECT_CHAT_ID 另发一条“长期未起量创意及广告清理汇总通知”。未起量删除不依赖代理归属或 代理 webhook;Excel 会展示账户表中的“代理商昵称”,但仍要求内部群、数据库审计、全局腾讯写锁、 写前回读和写后校验全部成功。 审核异常代理通知与两类内部汇总通知分别记录成功状态; 单一目标失败时只重试失败目标,已成功目标不会重复发送。上传或机器人通知失败时复用已有在线表, 只重试通知,不重复删除。每天的检查使用独立日级审计记录,同一需人工判断创意如果次日仍命中规则, 会按当天新检查再次提醒。代理归属为空,或已解析代理但没有配置对应代理群时,记录置为 DEFERRED, 禁止执行腾讯删除,待归属和路由完整后再处理。启用长期未起量扫描必须设置 FEISHU_AD_PROJECT_CHAT_ID;这类通知和删除均不要求 ROI_AGENCY_WEBHOOK_ENABLED=1。 除 CREATIVE_CLEANUP_INTERNAL_ONLY_AGENCIES 外,审核异常真实删除仍要求对应代理 webhook;所有审核 异常删除都要求内部群已配置。普通代理缺少对应路由时该记录在腾讯写前置为 DEFERRED,内部专属归属 不受 ROI_AGENCY_WEBHOOK_ENABLED 限制。生产启用顺序为:先只开启任务完成只读审核结果核对;审核异常链路再开启 ROI_AGENCY_WEBHOOK_ENABLED=1DAILY_REJECTED_CREATIVE_APPLY_ENABLED=1;长期未起量链路先开启 DAILY_UNDERPERFORMING_CREATIVE_CLEANUP_ENABLED=1 预演并核对内部群的独立消息,确认后再开启 DAILY_UNDERPERFORMING_CREATIVE_APPLY_ENABLED=1;广告链路同样先开启 DAILY_UNDERPERFORMING_AD_CLEANUP_ENABLED=1 预演,确认后再开启 DAILY_UNDERPERFORMING_AD_APPLY_ENABLED=1

腾讯删除失败会继续保留 FAILEDDEFERREDWRITE_OUTCOME_UNKNOWN 数据库审计状态,供后续任务 按现有回读和重试规则补处理,但不再参与清理任务的 code 1 判定。每次任务把删除失败合并为一条独立 飞书告警,发送位置与“创意审核异常及未起量清理任务失败”一致,均使用 CREATIVE_CLEANUP_FAILURE_FEISHU_CHAT_ID,为空时回退 ROI_FAILURE_FEISHU_CHAT_ID / FEISHU_OPERATOR_CHAT_ID。只有非 Token 类扫描错误或内部/代理通知真实失败仍会让任务返回非零退出码。

本地只预演未起量规则时使用:

PYTHONPATH=examples/auto_put_ad_mini:examples/tencent_realtime_control \
  .venv/bin/python examples/auto_put_ad_mini/cleanup_rejected_creatives.py \
  --underperformance-preview-only

该入口不读取审核异常账户范围、不生成审核异常候选,且无论环境变量中的真实删除开关为何值,都会强制关闭 审核异常、未起量创意和零消耗广告的腾讯删除。它只处理当日未起量候选,在本地输出 Excel,并复用 FEISHU_AD_PROJECT_CHAT_ID 发送一条内部群通知;不会读取或发送代理商待通知记录。预览 Excel 只包含 本轮重新确认仍满足规则且成功写入审计表的候选,不会带出当天旧版本误判或本轮已不再满足规则的残留记录。

同一天已经成功发送过通知、需要人工重新生成 Excel 并再次发送内部群时,增加 --force-notification。该参数只能与预演模式一起使用,不会开启任何腾讯删除:

PYTHONPATH=examples/auto_put_ad_mini:examples/tencent_realtime_control \
  .venv/bin/python examples/auto_put_ad_mini/cleanup_rejected_creatives.py \
  --underperformance-preview-only \
  --force-notification

3. ECS 部署脚本

/home/sh/deploy.sh 使用:

#!/bin/bash
set -e

echo "Qingqu@2019" |
  docker login \
    --username=stuuudys \
    registry.cn-hangzhou.aliyuncs.com \
    --password-stdin

VERSION=${1:-latest}
cd /home/server

VERSION=${VERSION} docker compose --env-file /dev/null pull
VERSION=${VERSION} docker compose --env-file /dev/null down --remove-orphans
VERSION=${VERSION} docker compose --env-file /dev/null up -d
VERSION=${VERSION} docker compose --env-file /dev/null ps

继续沿用部署脚本中的 source /home/server/.env,Compose 同时通过标准 env_file 将同一个文件传给两个容器,环境变量文件和加载入口不变。

4. 首次 dry-run

保持 RTC_APPLY_ENABLED=0 和两个 DAILY_*_ENABLED=0

cd /home/server
docker compose --env-file /dev/null config --services
docker compose --env-file /dev/null up -d
docker compose --env-file /dev/null ps
docker compose --env-file /dev/null logs --tail=200 ad_control_service
docker compose --env-file /dev/null logs --tail=200 ad_daily_service

控制服务启动时自动执行实时状态表增量迁移。也可以显式验证:

docker compose --env-file /dev/null run --rm \
  ad_control_service \
  python /app/examples/tencent_realtime_control/init_db.py

收入预测首次上线保持 REVENUE_FORECAST_ENABLED=0,先执行一次只读预测。命令会 读取 ODPS 并写入收入快照和预测结果表,不会调用腾讯或飞书:

docker compose --env-file /dev/null run --rm \
  ad_control_service \
  python /app/examples/tencent_realtime_control/build_revenue_speed_parameters.py \
  --start-date 20260727 --end-date 20260805 \
  --parameter-version revenue_speed_params_v1

docker compose --env-file /dev/null run --rm \
  ad_control_service \
  python /app/examples/tencent_realtime_control/run_revenue_forecast.py \
  --ignore-runtime-window

确认 forecast_method=weighted_30m_speed、参数版本、样本数、P50预测、P10/P90 经验区间、建议总成本、昨日非小程序预留成本和小程序目标成本后, 再设置 REVENUE_FORECAST_ENABLED=1 并只重建 ad_control_service。预留成本临时 读取 opengid_base_data 昨日完整日数据;缺失时返回 WAIT_COST_DATA,不生成 小程序预算。模型参数或连续窗口不可用时会输出 WAIT_SPEED_MODEL 和明确原因, 本轮不生成预测。 已发布参数版本不可覆盖;训练日期变化时发布新版本并同步修改环境变量。

执行无腾讯写入的 CPM dry-run:

docker compose --env-file /dev/null run --rm \
  ad_control_service \
  python /app/examples/tencent_realtime_control/run_once.py \
  --at 2026-07-25T12:00:00 \
  --cpm-override 220 \
  --force-refresh

飞书验证:

  1. 在配置群 @机器人发送 查询暂停状态,确认仍按历史自动化账户与启用的 realtime_control_account_scope 额外纳管账户范围,汇总当前有效运营暂停;不得读取 loghubods.ad_put_tencent_account 全部账号。
  2. 发送 查询全部账户状态查询全部账户投放状态 或将“账户”写成 “账号”;确认账号范围来自 loghubods.ad_put_tencent_account 最新未删除记录,只有 token 正常账号进入广告统计。只有广告在起止日期内、开关正常,且当前北京时间对应的 time_series 半小时位为 1 时才统计为“投放中”;对应位为 0 时统计为“非投放时段”。群内返回 “打开广告状态表格”按钮,表格包含账号汇总、广告明细和异常账号,广告明细需展示今日投放 时段、当前半小时时段和当前时段状态。
  3. 发送 查询今天自动化账户消耗,确认返回腾讯实时汇总且不要求审批。
  4. 发送一个单账户暂停命令,确认机器人返回预览和 cmd_xxx
  5. 回复确认,服务必须提示 dry-run 禁止写腾讯。

5. 指定账户执行一次创建流程

指定账户必须经过日服务入口,以复用生产 MySQL advisory lock,并仍然执行飞书每账户最新配置同步、白名单和投放模板校验:

docker compose run --rm ad_daily_service \
  python /app/examples/auto_put_ad_mini/run_daily_service.py \
  --run-creation-once \
  --account-id 84502339

多个账户重复传参:--account-id 84502339 --account-id 81214386。如只补创意、不新建广告,在服务名前增加 -e CREATION_SKIP_PHASE0=1。正常执行不限制日期,每个账户使用飞书表中日期最新的一行;账户 ID 非正整数、遗漏账户参数或最新配置未启用时均直接退出,不能退化成全账户执行。--config-date 仅用于历史复现。

6. 生产切换

  1. 确认旧 auto_put_ad_mini/server.py 和旧实时容器不再运行。
  2. 设置 RTC_APPLY_ENABLED=1,保持日级创建和 ROI 服务关闭。
  3. 重新 docker compose up -d,观察一个完整实时周期。
  4. 用单账户命令验证暂停、恢复、腾讯回读和飞书结果。
  5. 设置 DAILY_REVIEW_ENABLED=1DAILY_SYNC_DELIVERY_TEMPLATE=1
  6. 审核扫描稳定后设置 DAILY_CREATION_ENABLED=1
  7. 保持 DAILY_RUN_ON_STARTUP=0,防止部署时额外触发完整创建。

日级 ROI 单独分两阶段启用:

当前 north_star_roi_t15_v8 使用版本化传播裂变参数,策略版本为 roi_policy_v15,报表版本为 roi_report_v41。T0 裂变人数取 SUM(t0_fission_uv_root),不再读取旧字段 t0裂变人数。小程序按人群包和转化目标、 公众号按合作方和公众号匹配传播裂变系数;企微暂不进入本版报表、阈值和调控。首层效率收入读取 效率收入,T0 实际裂变收入读取当天的 裂变效率收入, 并以 T0 实际裂变收入乘传播裂变系数预测完整裂变收入。日级任务不会现场重算 cohort 参数。部署前应保持 ROI_FISSION_PARAMETER_VERSION=20260712_A0-A15_v2。参数必须先发布到 MySQL, 启动日志、数据库发布版本和 Excel 摘要中的参数版本、cohort 日期必须一致。 ROI 在线表只上传一次,并发送到 ROI_FEISHU_CHAT_IDFEISHU_OPERATOR_CHAT_ID;两个变量相同时只发送一次。 ROI 表格使用获得链接者可编辑权限。默认隐藏的黄色【审批选择】列取消隐藏后只接受“批准”或“拒绝”, 数据库隐藏幂等键决定真实执行目标,表格中的账户、广告、创意、成本和 ROI 不作为写入参数。

审批表包含小程序创意级、小程序广告级和公众号的三日汇总与每日明细共 6 个主 Sheet,广告级两个 Sheet 默认隐藏。 金额、UV 和人数展示三日总量/3,ROI 和裂变率使用三日汇总后的加权口径。连续三天每天 首层 UV>200、成本>0 且 ROI 有效的小程序创意和公众号实体分别建立渠道独立样本池。公众号即转使用自身合格实体等权 P20;小程序使用 T-1 创意级实际成本5%软预算动态反推关停线和实体分位点,禁止把广告级重复成本加入分母。 广告级直接从 ODPS 原始数据按广告去重,复用小程序三日动态关停线但不进入创意成本预算样本池;低于关停线且广告 age>3 天时生成广告级关停建议,批准后暂停整个广告。 小程序三日持续低 ROI 与单日补充规则按 6:4 获得目标成本的60%/40%基础额度。两组分别按三日加权预测 ROI、T-1预测 ROI 从低到高取连续前缀;目标关停成本占昨日总成本5%,软上限5.5%,绝对上限6%,未用额度只在剩余候选间软流转。 三日正式判断严格优先:已满足连续三日正式样本条件的小程序创意,无论预算选择或最终动作如何,都不得进入单日判断。其余创意最新日首层 UV>200、成本>0、ROI有效且广告 age>3天时进入单日候选池,预测总效率 ROI 同时≤0.20和候选池实体等权P10才成为关停候选;不再使用历史保护。 三日汇总可见尾部列按“三日均值ROI → 当日效率ROI → 预测总效率ROI → 关停线 → 扩量线(P80) → 建议动作 → 建议说明”排列;三列分别是三日加权实际ROI、最新日实际ROI和三日加权预测ROI,不单列最新日预测ROI。小程序创意级、广告级可见关停动作分别显示为“关停创意”“关停广告”,底层仍保存统一动作值供执行器消费。创意关停按三日持续低 ROI、单日绝对低 ROI 且P10两类建议说明分组排序,同类内部按对应 ROI 升序。公众号不参与扩量,扩量线留空。 整体三日排名百分位、是否低于三日关停线、最新日UV、覆盖天数、昨日成本、关停规则、预算选择状态、实际关停成本占比、阈值样本状态和执行结果只保留为隐藏审计字段;底层动作原因通过“建议说明”展示。 每日明细 Sheet 的 dt 位于第一列并按日期倒序。主 Sheet 冻结首行和前 7 列, 关停线列为动态“关停线”;“关停线分位点”只进入运行摘要和数据库快照,不写入业务 Sheet。所有小程序创意级和广告级关停统一要求广告 age>3 天。汇总表将三日总 T0 裂变人数 / 三日总首层 UV 的加权比例展示为“日均T0裂变率”。合格小程序创意实体等权 P80 为扩量线,处于头部20%且广告 age≥3 时生成扩量建议。汇总顺序为关停、扩量、中间观察、条件不足观察;第一条扩量行和扩量后第一条观察行顶部均使用粗线分隔。三列ROI均使用深红—黄—绿色阶,绿色代表表现好。普通数值显示两位小数,UV、人数、数量和广告年龄显示整数;两个“裂变系数-总裂变UV/…”比率列固定显示两位小数。“传播裂变系数匹配”保留审计数据但默认隐藏。 创意级“当前创意状态”只对关停建议只读腾讯状态并显示正常、已停止或读取失败,其他行留空;“审批选择”默认隐藏。 完整主报表写入成功后,服务额外按“小程序投流”渠道的“代理名称”生成一代理一份 roi_agency_advice_v9 工作簿,文件名为 YYYYMMDD_代理名称_调控建议.xlsx。代理版可见 Sheet 名为“小程序创意调控建议”,并保留默认隐藏的“小程序广告级三日汇总”,不包含每日明细;包名、广告age、日均首层UV、建议说明、收入、预测总效率ROI、阈值、排名、两个裂变系数、日均T0裂变人数/率、审批和执行审计列均物理删除。“评分”使用最新日实际ROI并保留两位小数,不参与代理动作计算;建议动作仍按内部策略生成,并明确显示“关停创意”或“关停广告”。代理表生成不改变主报表、数据库快照或主审批链接。

生产正式任务每天北京时间09:00开始检查。任务固定以T-1为结束日,并在创建MySQL运行批次、生成报表和发送飞书前校验 loghubods.opengid_base_data 的精确T-1分区;小程序投流或公众号投流相关数据任一为空时不启动计算,每30分钟重新检查一次。18:00执行最后一次检查:数据已就绪则执行,仍未就绪则发送一次红色截止告警并停止当天轮询,禁止静默回退到更早分区。目标日期已经存在任意正式 roi_metric_run 时直接跳过,不重复计算、不重复发送主表或代理表;内部测试批次不影响正式任务判断。

总表继续由飞书应用按 ROI_FEISHU_CHAT_ID / FEISHU_OPERATOR_CHAT_ID 发布。代理表发布由 ROI_AGENCY_WEBHOOK_ENABLED 独立控制,完整机器人地址仅允许保存在密钥系统或真实 .envROI_AGENCY_WEBHOOKS_JSON,JSON key 使用代理简称。飞书应用先上传并开放在线表链接,再由对应代理机器人发送可点击卡片;未配置代理记录为跳过,不回退到总群。同一 run_id + 代理 + 代理报表版本 幂等审计,已上传表格在通知重试时复用,避免重复建表。机器人地址只保存哈希指纹,不能写入数据库或日志。

ROI_INTERNAL_TEST_ENABLED=1 时,服务每天北京时间15:30注册独立内部任务。该任务复算后使用空管理账户集合,不生成可审批腾讯动作,只把“自动化投放”分表发送到 ROI_AGENCY_WEBHOOKS_JSON 的“内部”路由,不发送完整主表、正式总群或代理群;缺少该分表时任务失败并告警。它不注册startup任务,服务重启不会主动补发。正式任务始终去重;内部测试由 ROI_INTERNAL_TEST_DEDUP_ENABLED 控制,生产默认1,测试环境显式设为0时每次触发生成独立测试run并重新发送。

正式轮询的数据就绪检查不会把来源延迟当成任务失败,也不会每30分钟发送失败告警。18:00仍未就绪时只发送一次截止告警。正式任务实际启动后或15:30内部测试的子进程启动失败、非零退出时,调度服务通过飞书应用向 ROI_FAILURE_FEISHU_CHAT_ID 发送红色失败告警;该变量为空时回退 FEISHU_OPERATOR_CHAT_ID。失败告警本身发送失败会记录完整异常堆栈,不会吞掉或替换原任务异常。每次子任务启动、成功或失败日志都包含唯一 child_trace_id;子进程内的业务日志沿用该值,可在 SLS 中直接检索同一次执行的完整日志。APScheduler 的执行异常、错过执行和并发上限事件额外记录 job_id、计划执行时间、异常对象及 traceback;主线程和后台线程的未捕获异常由统一日志钩子兜底。实时调控与收入预测的每一轮分别记录 cycle_id、耗时和执行结果。创意清理存在部分成功时会保留已完成的删除和通知,同时以 partial_failure 记录扫描、删除和通知三类错误数量及明细,并返回非零退出码提示人工检查,不能把部分遗漏静默成成功。

  1. 首次部署先执行下方参数发布命令并完成回读校验。
  2. 设置 DAILY_ROI_ENABLED=1ROI_APPLY_ENABLED=0ROI_SHEET_APPROVAL_ENABLED=0,观察09:00起每30分钟的T-1来源校验;数据就绪后应只生成一份数据库快照和可编辑飞书表,18:00未就绪应只收到一次截止告警。
  3. 核对阈值、账户可执行范围、黄色审批列和动作数量。
  4. 再设置 ROI_APPLY_ENABLED=1ROI_SHEET_APPROVAL_ENABLED=1,重建 ad-control-service;批准一条低风险测试行,核对腾讯回读、表格结果和群通知。
  5. 生产审批有效期默认 120 分钟;过期行不能执行。表格逐行选择“拒绝”不会产生腾讯写操作。
  6. 不允许 DAILY_ROI_ENABLED=0ROI_APPLY_ENABLED=1,也不允许仅开启表格审批而关闭 ROI 写操作;服务启动阶段会拒绝不完整配置。

首次启用前可手工运行一个“不发飞书、不写腾讯”的计算批次。该命令会读取 ODPS、 初始化/写入 ROI 数据库快照并在挂载目录生成本地 Excel:

docker compose run --rm ad_daily_service \
  python /app/examples/auto_put_ad_mini/run_daily_roi.py \
  --end-date 20260725

确认数据库快照和本地报告后,再增加 --send-feishu 验证在线表、黄色审批列和链接编辑权限。 此时必须保持 ROI_APPLY_ENABLED=0ROI_SHEET_APPROVAL_ENABLED=0,不会轮询或执行表格选择。

如果同一日期窗口的上游历史数据被修订,必须提供稳定的数据修订标识生成新批次,不能覆盖或删除已发布快照:

docker compose run --rm ad_daily_service \
  python /app/examples/auto_put_ad_mini/run_daily_roi.py \
  --end-date 20260729 \
  --source-revision datafix_20260730_1 \
  --send-feishu

相同 --source-revision 重复执行会复用同一批次;新的修订必须使用新的标识。日常调度不传此参数。

离线复算新参数必须与日级任务分开执行。以下命令默认只生成 SQL,不访问 ODPS:

docker compose run --rm ad_daily_service \
  python /app/examples/auto_put_ad_mini/refresh_roi_fission_multiplier.py \
  --cohort-date 20260712 \
  --sql-only

去掉 --sql-only 才会固定执行小程序、公众号和企微三条只读 cohort SQL,并生成草稿 CSV/Excel。草稿不会自动 进入生产;必须审计并在 roi_control/fission_multiplier.py 登记版本和文件 SHA256。 小程序和公众号写入联合发布草稿,企微另行生成独立草稿。企微正式版本只需 登记企微 CSV 的 SHA256,数据库参数行只允许合作方精确匹配;不存在企微文件或参数行的旧版本继续使用默认参考系数 2.5。 首次上线或切换版本前,显式发布到 MySQL:

docker compose run --rm ad_daily_service \
  python /app/examples/auto_put_ad_mini/refresh_roi_fission_multiplier.py \
  --publish-version 20260712_A0-A15_v2 \
  --published-by production-deploy

脚本会初始化参数表、写入不可变版本并立即回读校验。相同版本相同内容重复执行会 安全跳过;相同版本内容不同会拒绝覆盖。发布成功后再设置 ROI_FISSION_PARAMETER_VERSION 并启用日级 ROI。

6. 验收与回滚

验收:

docker compose --env-file /dev/null ps
docker compose --env-file /dev/null logs --since=30m ad_control_service
docker compose --env-file /dev/null logs --since=30m ad_daily_service

ad_daily_service 启动日志应包含:

Daily ROI polling configured start=09:00 interval=30m cutoff=18:00

应确认:

  • 两个容器均为 healthy。
  • 实时循环每 10 分钟运行且没有双实例锁冲突。
  • 飞书只有一个 WebSocket 消费者。
  • 10:30 只出现一次创建任务。
  • 审核扫描每 2 小时最多一个实例。
  • 09:00至18:00正式轮询只生成一个目标日期的正式ROI批次,已有正式批次后不再计算或补发;15:30内部测试是否去重由独立变量控制。
  • ROI 审批执行与实时 CPM 调控不并发写腾讯,基础出价调整后两边状态一致。

回滚时将 VERSION 改为上一个镜像版本并重新部署。数据库新增表和列可以保留, 旧代码不会读取这些字段,不需要执行破坏性回滚。