# PROJECT_STRUCTURE — 腾讯广告实时调控模块

> 目录：`examples/tencent_realtime_control/`
> 定位：独立管理实时投放指标采集、CPM 状态判断和腾讯广告调控，不依赖广告创建或历史调价分析流程。

本目录实际承载 **4 个相对独立的子系统**，共用同一套基础设施（MySQL / ODPS / 腾讯 API / 飞书）：

1. **实时 CPM 调控**（核心）— 按当天整体小时 CPM 阈值，自动提价/恢复/延后自动化广告
2. **飞书运营命令控制** — 运营人员在指定飞书群 @机器人，用自然语言暂停/恢复/停止/查询广告
3. **实时收入预测** — 预测当天最终收入，计算建议总成本（只读，不写腾讯接口）
4. **日级 ROI 逐行审批**（消费方）— 代码位于 `examples/auto_put_ad_mini/roi_control/`，本目录只启动轮询服务

---

## 一、子系统总览

```
┌────────────────────────────────────────────────────────────────┐
│                    run_control_service.py                       │
│                 (组合根 / Docker ad-control-service)            │
│                                                                │
│  ┌─────────────┐ ┌───────────────┐ ┌──────────────────────┐   │
│  │ FeishuCommand│ │RoiSheetApproval│ │ revenue_forecast_service││
│  │   Service    │ │    Service    │ │  (收入预测后台线程)    │   │
│  └──────┬──────┘ └──────┬────────┘ └──────────┬───────────┘   │
│         │              │                      │               │
│         ▼              ▼                      ▼               │
│  ┌────────────────────────────────────────────────────────┐   │
│  │              run_scheduler.py (10分钟调度循环)          │   │
│  │                        │                               │   │
│  │                        ▼                               │   │
│  │              run_once.py::run_cycle()                  │   │
│  │              CPM 读取 → 决策 → 执行 → 通知             │   │
│  └────────────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────────┘
            ▲                    ▲                    ▲
            │                    │                    │
      ODPS 小时CPM信号     腾讯 Marketing API      MySQL 状态/审计
```

---

## 二、文件清单与职责

### 1. 入口 / 服务装配

| 文件 | 行数 | 职责 |
|------|-----|------|
| `run_control_service.py` | 85 | **生产常驻服务入口**。初始化 schema 后并行启动：飞书命令 WebSocket、ROI 表格审批轮询、收入预测后台线程、CPM 调度循环 `run_forever()`。写操作受 `RTC_APPLY_ENABLED` / `--apply` 控制 |
| `run_scheduler.py` | 112 | CPM 调控的**常驻调度循环**。默认 12:00–21:00 每 600s 触发一次 `run_cycle()`，非投放时段休眠到次日 `RTC_START_HOUR` |
| `run_once.py` | 798 | **单次幂等 CPM 调控周期**（也是调度循环的核心逻辑所在）。包含决策状态机、执行、写后回读、报告落盘。支持 `--apply/--at/--cpm-override/--force-refresh/--test-notification` |
| `run_revenue_forecast.py` | 41 | 单次收入预测执行入口，默认受运行时间窗约束，`--ignore-runtime-window` 可越过 |
| `init_db.py` | 18 | 初始化数据库 schema（读取 `schema.sql` + 运行迁移） |

### 2. 实时 CPM 调控子系统

| 文件 | 行数 | 职责 |
|------|-----|------|
| `realtime_config.py` | 61 | 调控参数：投放时段、CPM 高低阈值、提价比例、分区延迟上限、DB 锁名等（全部可环境变量覆盖） |
| `odps_source.py` | 74 | **CPM 信号源**。从 `loghubods.advertiser_data_da_hour` 读当天整体小时 `真实cpm_总`，返回最新分区 |
| `tencent_client.py` | 629 | 腾讯广告 API 读写封装。**写后回读校验**（默认 3 次×1s），读回不一致抛 `PostWriteVerificationError`。区分"未发出/被拒/结果未知"三类写失败 |
| `storage.py` | 1312 | MySQL 状态与审计存储（全模块最大）。ad 状态、账户范围、每日状态、操作日志、运营命令、暂停状态、收入预测等全部落库逻辑 |
| `feishu_notifier.py` | 607 | 飞书在线表格通知。真实调价/延后/状态变更的窗口生成 xlsx + 发群；dry-run 与无动作不通知 |
| `fetch_daily_hourly_cpm.py` | 60 | 数据检查工具：查看指定日期分时 CPM，落 CSV 并打印 |

**决策逻辑（在 `run_once.py`）**：

```python
decide(cpm, low, high):
    cpm > high(250)   → BOOST   # 基础出价 ×1.05 上调，每天至多一次
    low(190) ≤ cpm ≤ high → RESTORE  # 恢复基础出价 + 恢复策略暂停的广告
    cpm < low(190)    → PAUSE   # 恢复基础价，begin_date 延后到次日（不 SUSPEND）
```

### 3. 飞书运营命令子系统

| 文件 | 行数 | 职责 |
|------|-----|------|
| `feishu_command_service.py` | 395 | 飞书 WebSocket 入口。校验群/发送人/@机器人后串行处理消息；多轮补问草稿、命令预览、确认/取消 |
| `operator_commands.py` | 198 | **确定性命令解析**。`ParsedCommand` / `CommandIntent` 数据类，暂停/停止/恢复/状态/今日消耗等动作的规则解析 |
| `command_intent_parser.py` | 218 | **受限 NLU**。标准命令走确定性解析；`RTC_NL_COMMAND_ENABLED=1` 时模型只返回动作+范围 JSON，不可读库/调接口 |
| `operator_control.py` | 899 | 命令**预览与执行**。预览冻结广告快照 + 冲突检测；确认后按账户/广告执行暂停/恢复/停止，含写后回读、状态机流转 |
| `today_spend_query.py` | 79 | 只读的今日消耗汇总查询（全部/自动化/指定账户），逐账户调腾讯报表聚合 |

**命令生命周期**：`解析 → 预览(冻结明细) → 10分钟内确认 → 执行 → 回读校验 → 审计落库`

### 4. 实时收入预测子系统

| 文件 | 行数 | 职责 |
|------|-----|------|
| `revenue_forecast.py` | 57 | 领域数据类：`RevenueObservation`、`RevenueTrendWindow`、`RevenueForecast` |
| `revenue_forecast_config.py` | 115 | 预测配置：运行时段、15分钟窗口数、目标成本比例、参数版本、DB 锁名 |
| `revenue_forecast_source.py` | 250 | ODPS 取数：`ads_ad_own_package_detail_15min` 15分钟收入序列、`opengid_base_data` 渠道成本、当日快照 |
| `revenue_speed_forecast.py` | 243 | **加权速度预测算法**。`build_speed_samples` → `aggregate_speed_parameters`（发布 P10/P50/P90）→ `calculate_speed_forecast` |
| `revenue_forecast_repository.py` | 370 | 预测结果/观测/速度样本/已发布参数的 MySQL 持久化；参数版本发布后不可覆盖 |
| `revenue_forecast_job.py` | 212 | 单轮预测周期：取数 → 质量校验(VALID/STALE/CORRECTED) → 载入参数 → 计算 → 落库 |
| `revenue_forecast_service.py` | 77 | 后台调度线程（默认关闭，`REVENUE_FORECAST_ENABLED=1` 启用） |
| `build_revenue_speed_parameters.py` | 118 | 从完整历史日构建并**发布速度参数版本**（`--start-date/--end-date/--parameter-version`） |
| `backtest_revenue_speed_forecast.py` | 284 | 严格走步回测：每个预测日只用此前日期训练，只读 ODPS，不写 MySQL |

### 5. 实验 / 测试 / 配置

| 文件 | 行数 | 职责 |
|------|-----|------|
| `adjust_bid_experiment_20260729.py` | 284 | 一次性八广告分组调价实验（2026-07-29），默认只预览，带 `bid_hold` 释放与账户范围注册 |
| `test_feishu_natural_commands.py` | 439 | 飞书命令链路单测：确定性解析、NLU 兜底、预览、调度器唤醒、暂停执行 |
| `test_revenue_forecast.py` | 158 | 收入预测算法单测：速度样本构建、参数聚合、预测计算、异常分支 |
| `schema.sql` | 420 | 全部表结构（见下） |
| `requirements.txt` | 7 | pandas / openpyxl / pyodps / pymysql / python-dotenv / requests |
| `.env.example` | 76 | 环境变量样例（本地开发兼容；生产以仓库根 `runtime.env.example` 为准） |
| `__init__.py` | 1 | 模块标记 |

### 6. 外部依赖（不在本目录）

| 路径 | 用途 |
|------|------|
| `examples/auto_put_ad_mini/roi_control/` | 日级 ROI 指标计算与审批表发布，`run_control_service.py` 启动其 `RoiSheetApprovalService` |
| `agent.tools.builtin.feishu.feishu_client` | 框架内置飞书客户端（WebSocket / 发消息） |

---

## 三、数据库表（`schema.sql`）

| 表 | 用途 |
|----|------|
| `realtime_control_daily_state` | 每日 CPM 调控状态（最后分区/决策/刷新时间） |
| `realtime_control_ad_state` | **广告基准与状态**：base_bid_fen、boosted_date、bid_hold、paused_by_strategy、operator_pause_* |
| `realtime_control_account_scope` | 实时调控显式账户范围（control_mode：FULL / PAUSE_ONLY） |
| `realtime_control_action_log` | 每次真实动作的审计日志 |
| `operator_command` / `operator_command_item` / `operator_command_draft` | 飞书运营命令、冻结明细、多轮草稿 |
| `roi_metric_run` / `roi_fission_parameter_*` / `roi_entity_snapshot` / `roi_action_item` / `roi_agency_delivery` | 日级 ROI 指标运行与审批动作（ROI 子系统表，由 `auto_put_ad_mini` 写入） |
| `revenue_forecast_observation` / `result` / `speed_sample` / `speed_parameter` | 收入预测的原始快照、版本化结果、历史速度样本、已发布参数 |

`storage.py::initialize_schema()` 会在建表后额外执行 `realtime_control_ad_state` 的增量列迁移。

---

## 四、关键数据流

### CPM 调控（每 10 分钟）

```
ODPS 小时CPM (odps_source)
   │ fetch_latest_cpm()
   ▼
run_cycle() ── 校验分区延迟 ≤2h / 时段 12–21 点
   │ decide() → BOOST / RESTORE / PAUSE / CUTOFF / WAIT_DATA / OFF_HOURS
   ▼
execute_inventory_action()
   ├─ load_ad_states() 取每账户广告状态（MySQL）
   ├─ tencent.get_ads() 拉实时广告
   ├─ target_for_ad() 计算目标出价/状态/begin_date
   └─ tencent.update_ad()/update_ad_begin_dates() 写腾讯（含回读校验）
   ▼
feishu_notifier.send_action_notification() ── 真实变更才发飞书在线表格
   ▼
upsert_ad_state() / insert_action_log() ── 状态与审计落库
```

### 飞书命令（消息触发）

```
飞书群 @机器人
   ▼ FeishuCommandService._authorized() 校验群/人/@
   ▼ understand()：确定性解析 → (可选)LLM 兜底
   ▼ preview_write_command()：冻结广告快照 + 冲突检测 → 回复预览
   ▼ 用户「确认 cmd_xxx」(10分钟内)
   ▼ execute_confirmed_command()：锁内执行 → 写后回读 → 审计
```

---

## 五、配置要点（`.env.example` / `realtime_config.py`）

| 变量 | 默认 | 含义 |
|------|------|------|
| `RTC_START_HOUR` / `RTC_STOP_HOUR` | 12 / 21 | CPM 调控投放时段 |
| `RTC_POLL_SECONDS` | 600 | 调控轮询间隔 |
| `RTC_HIGH_CPM` / `RTC_LOW_CPM` | 250 / 190 | 提价 / 延后阈值 |
| `RTC_BID_UP_RATIO` | 1.05 | 提价比例 |
| `RTC_APPLY_ENABLED` | 0 | **总闸**：所有入口默认 dry-run，为 1 才写腾讯 |
| `RTC_COMMAND_ENABLED` / `RTC_COMMAND_CHAT_ID` / `RTC_COMMAND_ALLOWED_OPEN_IDS` | - | 飞书命令开关与白名单 |
| `RTC_NL_COMMAND_ENABLED` | 0 | 启用 LLM 兜底解析 |
| `REVENUE_FORECAST_ENABLED` | 0 | 收入预测后台服务开关 |
| `REVENUE_SPEED_PARAMETER_VERSION` | revenue_speed_params_v1 | 已发布速度参数版本（只读） |

> ⚠️ 所有执行入口默认 **dry-run**，只有 `--apply` 或 `RTC_APPLY_ENABLED=1` 才调用腾讯写接口；`--at` / `--cpm-override` 仅限 dry-run（`run_once.py` 会校验）。

---

## 六、常用运行命令

```bash
# 初始化数据库
.venv/bin/python examples/tencent_realtime_control/init_db.py

# 单次 CPM 调控（dry-run）
.venv/bin/python examples/tencent_realtime_control/run_once.py
# 验证指定 CPM 分支
.venv/bin/python examples/tencent_realtime_control/run_once.py --at 2026-07-24T15:00:00 --cpm-override 260
# 真实执行
.venv/bin/python examples/tencent_realtime_control/run_once.py --apply

# 生产常驻服务（含飞书命令 + ROI 审批 + 收入预测 + CPM 循环）
RTC_APPLY_ENABLED=0 .venv/bin/python examples/tencent_realtime_control/run_control_service.py

# 收入预测：发布参数 / 单次执行 / 走步回测
.venv/bin/python examples/tencent_realtime_control/build_revenue_speed_parameters.py --start-date 20260727 --end-date 20260805 --parameter-version revenue_speed_params_v1
.venv/bin/python examples/tencent_realtime_control/run_revenue_forecast.py
.venv/bin/python examples/tencent_realtime_control/backtest_revenue_speed_forecast.py --history-start 20260727 --start-date 20260728 --end-date 20260805

# 查看当天分时 CPM
.venv/bin/python examples/tencent_realtime_control/fetch_daily_hourly_cpm.py
```

---

## 七、扩展指引

- **调整 CPM 阈值/时段** → 改 `realtime_config.py` 或环境变量，无需动业务代码
- **新增决策分支** → 在 `run_once.py::decide()` 与 `target_for_ad()` 中扩展，并补充 `feishu_notifier._action_text()` 的动作文案
- **新增飞书命令** → `operator_commands.py` 定义动作常量与解析 → `operator_control.py` 实现预览/执行 → `feishu_command_service.py` 接入消息分发
- **训练新预测参数** → 用 `build_revenue_speed_parameters.py` 发布新版本号（发布后不可覆盖）
- **任何涉及腾讯写操作的新功能** → 复用 `tencent_client.py` 的写后回读校验与失败分类，不要裸调 API
