# Tencentads Delivery Smart Create

> 专用于创建智投(AIM+)营销单元（原广告组）的 SDK 化技能。当用户意图涉及智投、AIM+、艾米、智能投放、自动投放、小店智投、smart delivery 的**创建**场景时，使用本技能。覆盖场景：小店艾米、商品艾米、线索艾米、内容艾米、APP艾米(游戏/阅读/AI应用)、小游戏跑量、爆剧跑量、小说智投、全域通、视频号直播智投等。本技能通过专用脚本（而非全局 CLI 工具）调用 API，脚本已处理复杂分支、字段过滤和数据转换。

- Skill: `ahang1598/tencentads-delivery-smart-create` (Agent Skill, multi-file: 21 files)
- Install (CLI): `npx skillmds@latest add ahang1598/tencentads-delivery-smart-create`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ahang1598/tencentads-delivery-smart-create/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT. See LICENSE for full terms.
- Author: ahang1598 (https://skillmd.com/u/ahang1598)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/ahang1598/tencentads-delivery-smart-create

---


> **前置依赖**：需安装 `tencentads-cli`（Node.js ≥ 20）。执行 `npm install -g tencentads-cli@latest` 安装或升级；版本过低时 `tencentads` 会给出提示。

本技能是智投广告的**执行指南**——按 7 个步骤顺序完成一条智投广告的创建。**严格按步骤顺序执行，后面的步骤依赖前面的输出，不可跳步。**

所有 API 调用均通过本技能的**专用脚本**执行，脚本负责：API 分支路由、返回数据裁剪、格式转换（rules_json 解析、geo 编码查找等）。Agent 专注于：从脚本返回结果中做出业务决策（选哪个组合、选哪个转化目标）。

> **脚本调用格式统一**：`node scripts/<脚本名>.mjs '<JSON 参数>'`
> 执行脚本时先进入本 skill 根目录，再按相对 `scripts/` 路径调用。

### ⚠️ 跨平台 JSON 参数传递规则（经实测验证）

脚本调用格式为 `node scripts/<脚本名>.mjs '<JSON参数>'`，但 **JSON 参数的引号包裹方式因操作系统/终端而异**，传递不当会导致 `JSON.parse` 报错（如 `Expected property name or '}' in JSON at position 1`）。

| 终端环境 | 正确写法 | 说明 |
|---------|---------|------|
| **Linux / macOS (Bash/Zsh)** | `node scripts/xxx.mjs '{"key":"value"}'` | ✅ 单引号包裹，内部双引号原样保留 |
| **Windows Git Bash** | `node scripts/xxx.mjs '{"key":"value"}'` | ✅ 同 Bash |
| **Windows CMD** | `node scripts/xxx.mjs "{\"key\":\"value\"}"` | ✅ 双引号包裹 + 反斜杠转义 |
| **Windows CMD (备选)** | `node scripts/xxx.mjs "{""key"":""value""}"` | ✅ 双引号包裹 + 双双引号转义 |
| **Windows PowerShell 5.x** | `node --% scripts/xxx.mjs "{\"key\":\"value\"}"` | ✅ 必须加 `--%` 停止解析符 |
| **Windows PowerShell 5.x (备选)** | `` node scripts/xxx.mjs "{\`"key\`":\`"value\`"}" `` | ✅ 反斜杠 + 反引号组合转义 |

> **⛔ PowerShell 5.x 是重灾区**：单引号 `'...'`、反引号 `` `" `` 、反斜杠 `\"` 三种常见写法**全部失败**（双引号会被吞掉）。必须使用 `--% ` 停止解析符或 `` \`" `` 组合转义。
> **⛔ Windows CMD 不支持单引号包裹字符串**，单引号会被当作普通字符传入脚本，导致 JSON 解析失败。

```
⛔ 执行顺序（不可跳步）：

步骤1 → 步骤2 → 步骤3 → 步骤4 → 步骤5 → 步骤6 → 步骤7
确定      获取      获取       确定      获取      配置       组装请求体
智投场景  四元组    推广产品   版位      转化目标  定向+时段  → create-adgroup.mjs
                    +载体               +出价
```

---

## 执行协议（高优先级，覆盖后文）

> 若本节与下文"继续执行""不要重复查询"等表述冲突，以本节为准。

1. **⛔ 步骤 1 → 2 → 3 是绝对必须执行的前三步，不可跳过。** 即使用户 input 中看起来已包含场景、四元组、商品信息，仍必须调用 `get-rules.mjs` 确认四元组、调用 `get-assets.mjs` 获取推广资产 ID。原因：不同账号的可选组合不同，自然语言描述无法直接转为精确的枚举值和 ID。步骤 4-6 中的查询子动作在满足"显式证据白名单"时可跳过，但步骤 1-3 不可。
2. **进入下一步前，必须先拿到上一步输出并确认来源。** 合法来源只有三类：用户在原始需求中明确给出、当前 session 前一步脚本返回、当前 session 前一步 API 返回。
3. **自然语言意图不能替代结构化字段。** "小游戏拉新""微信小游戏""新客增长"等描述只能用于步骤 1 识别场景，不能直接当作四元组、营销载体 ID、`conversion_id` 的依据。
4. **进入步骤 7 前必须能回答每个关键字段来自哪里。** 说不清来源的字段，不要猜。
5. **⚠️ 错误处理与重试限制**：每个脚本调用最多重试 **1 次**（换参数或换调用方式）。如果 2 次仍失败（如返回 "Mock not found"、空结果、报错），**立即跳过该步骤，用已有数据继续推进到下一步直至步骤 7**。严禁反复重试同一脚本、读取脚本源码、或切换到非脚本方式（如 `tencentads-cli api`）尝试绕过。宁可在最终请求中缺少某个字段，也不要耗尽所有轮次。
6. **显式证据白名单（仅适用于步骤 4-6 的查询子动作，步骤 1-3 不适用）**：
   - 四元组查询：用户明确给出 `marketing_goal`、`marketing_sub_goal`、`marketing_target_type`、`marketing_carrier_type` 这 4 个枚举值全部；或当前 session 已从 `get-rules.mjs` 返回中得到。
   - 推广产品 / 营销载体查询：用户明确给出构造请求体所需的完整 ID；或当前 session 已从 `get-assets.mjs` 返回中得到。
   - 转化目标查询：用户明确给出 `conversion_id`；或当前 session 已从 `get-conversions.mjs` 返回中得到。
   - 定向查询：用户没有任何定向约束时可跳过；只要用户给了定向要求，地域/设备编码通过 `get-targeting-lookup.mjs` 查询，其他枚举通过 `get-enum-options.mjs` 查询。

---

## 步骤 1（⛔ 最先执行）：确定智投场景

> **前置**：用户意图
> **输出**：`smart_delivery_platform`、`delivery_scene`（数值）

根据用户意图从下表匹配 `smart_delivery_platform` 枚举值。枚举统一前缀 `SMART_DELIVERY_PLATFORM_EDITION_`（表中用 `...` 省略）。

> ⚠️ 小店枚举名与中文业务名严重不一致：`SINGLE_PRODUCT` = 短直双开（不是"单品"），`PRODUCT_OR_LIVE` = 小店单链路（不是"品商直播合开"）。

| 分类 | 场景名称 | 枚举 key | value | 识别关键词 |
|------|----------|----------|-------|-----------|
| 小店(3000) | 短直双开 | `..._WECHAT_STORE_SINGLE_PRODUCT` | `3001` | 短直双开、短视频+直播、双链路 |
| 小店(3000) | 小店单链路智投 | `..._WECHAT_STORE_PRODUCT_OR_LIVE` | `3002` | 单链路、小店单链路 |
| 小店(3000) | 全店托管智投 | `..._WECHAT_STORE_MANAGEMENT` | `3003` | 全店托管、托管 |
| 小店(3000) | 推直播间 | `..._WECHAT_STORE_LIVE` | `3004` | 推直播间、直播间引流 |
| 小店(3000) | 推商品 | `..._WECHAT_STORE_PRODUCT` | `3005` | 推商品（仅指小店的"推商品"入口，不带"智投"二字） |
| 生态(1000) | 爆剧跑量 | `..._ECOLOGY_PLAYLET` | `1001` | 爆剧、短剧、微短剧 |
| 生态(1000) | 线索跑量 | `..._ECOLOGY_LEADS` | `1002` | 线索艾米、线索跑量、表单 |
| 生态(1000) | 小游戏跑量 | `..._MINI_GAME_PROMOTION` | `1003` | 小游戏 |
| 生态(1000) | 商品智投 | `..._DRUG_PRODUCT` | `1018` | 商品艾米、商品智投、推商品智投、医药智投（注意：带"智投"二字的"推商品智投"属于此场景，不是小店的"推商品"） |
| 生态(1000) | 小说智投 | `..._FICTION` | `1019` | 小说、网文 |
| 全域通(4000) | 全域通-直播 | `..._QYT_LIVE` | `4001` | 全域通+直播 |
| 全域通(4000) | 全域通-直购 | `..._QYT_WECHAT_STORE` | `4002` | 全域通+直购/小店 |
| APP(6000) | 游戏应用智投 | `..._GAME_APP` | `6001` | 游戏APP、游戏应用 |
| APP(6000) | 阅读应用智投 | `..._READING_APP` | `6002` | 阅读APP、阅读应用 |
| APP(6000) | AI应用智投 | `..._AI_APP` | `6003` | AI应用、AI APP |

**步骤 1 完成后你应该有**：`smart_delivery_platform` 枚举值，以及同场景的数值 `delivery_scene`（即上表 `value` 列）。

> **字段含义拆开记**：
> - `smart_delivery_platform`：字符串枚举，如 `SMART_DELIVERY_PLATFORM_EDITION_MINI_GAME_PROMOTION`
> - `delivery_scene`：数值 value，如 `1003`
> - 调 `get-rules.mjs` 时，`delivery_scene` 传 `smart_delivery_platform` **字符串枚举**，不是数值 `1003`
> - 调 `get-conversions.mjs` 等投放端内部接口时，再传数值 `delivery_scene`

---

## 步骤 2（⛔ 默认必须执行，仅白名单可跳过）：获取四元组

> **前置**：`account_id` + 步骤 1 的 `smart_delivery_platform`
> **输出**：`marketing_goal`、`marketing_sub_goal`、`marketing_target_type`、`marketing_carrier_type`

**四元组**是腾讯广告的产品术语，指**营销内容 + 转化/优化目标的完整匹配集合**，共10个字段：
- 营销内容（4个）：`marketing_goal`、`marketing_sub_goal`、`marketing_carrier_type`、`marketing_target_type`
- 转化/优化目标（6个）：`optimization_goal`、`deep_behavior_optimization_goal`、`deep_behavior_advanced_goal`、`deep_worth_optimization_goal`、`deep_worth_advanced_goal`、`forward_link_assist`

这10个字段作为一个整体定义广告的投放语义，**必须从脚本返回的可选范围中选取，禁止通过推理猜测**。

**为什么不能猜？**
- 不同账号准入的组合不同，猜错会导致 `adgroups/add` 报错
- 看似相同的场景（如"小游戏推广"），在不同账号上对应的 `marketing_goal` 可能完全不同
- 四元组错误会级联导致后续推广产品、转化目标等全部错误

**调用方式**：

```bash
node scripts/get-rules.mjs '{"account_id":"<ACCOUNT_ID>","delivery_scene":"<smart_delivery_platform枚举值>"}'
```

示例：
```bash
node scripts/get-rules.mjs '{"account_id":"123456789","delivery_scene":"SMART_DELIVERY_PLATFORM_EDITION_MINI_GAME_PROMOTION"}'
```

**脚本已处理**：内部解析 `rules_json` 嵌套 JSON 字符串，展开四层嵌套树为扁平组合列表。

**返回示例**：
```json
{
  "combinations": [
    {
      "marketing_goal": "MARKETING_GOAL_APP_PROMOTED",
      "marketing_sub_goal": "MARKETING_SUB_GOAL_APP_INSTALL",
      "marketing_target_type": "MARKETING_TARGET_TYPE_WECHAT_MINI_GAME",
      "marketing_carrier_type": "MARKETING_CARRIER_TYPE_MINI_PROGRAM"
    }
  ]
}
```

**Agent 需要做的决策**：从 `combinations` 中确定唯一的一组四元组。

### 情况 A：返回 1 个组合 → 直接确定

`combinations` 只有 1 条时，四元组已唯一确定，**不需要任何选择逻辑**，直接取该条进入步骤 3。

### 情况 B：返回多个组合 → 需要按规则选择

`combinations` 有多条时，按以下**优先级从高到低**逐层过滤，直到缩小为唯一一组。**切忌乱猜四元组，一旦选错后续推广产品、转化目标等全部级联错误。**

**选择规则（按优先级）**：

1. **（最高优先级）通过资产 ID 反推**：

   > **触发条件**：用户在 input 中明确给出了 `推广产品ID`、`产品ID`、`推广资产ID`、`资产ID`、`应用ID`、`游戏ID`、`APP ID`、`marketing_asset_id`、`product_id`、`marketing_asset_outer_id` 等资产标识。

   资产 ID 能直接关联到某个 `marketing_target_type`，因此可反推缩小四元组范围。调用 `get-assets-by-rules.mjs`：

   ```bash
   node scripts/get-assets-by-rules.mjs '{"account_id":"<ACCOUNT_ID>","combinations":<get-rules返回的combinations数组>}'
   ```

   脚本内部自动对 `marketing_target_type` 去重，每种只调一次 API，返回 `asset_map`（以 `marketing_target_type` 为 key）。返回结构与 `get-assets.mjs` **完全一致**（含 `flat_params`、`type` 等字段），可直接复用于步骤 3 的资产选择：

   ```json
   {
     "asset_map": {
       "MARKETING_TARGET_TYPE_REAL_ESTATE": {
         "asset_type": "INDUSTRY",
         "assets": [
           {
             "marketing_asset_id": "342574",
             "name": "京投发展森与天成",
             "type": "REAL_ESTATE",
             "flat_params": {
               "asset_id": "342574"
             }
           }
         ]
       },
       "MARKETING_TARGET_TYPE_WECHAT_MINI_GAME": {
         "asset_type": "ONEID",
         "assets": [
           {
             "marketing_asset_outer_id": "wx123",
             "marketing_carrier_id": "wx123",
             "name": "某小游戏",
             "type": "WECHAT_MINI_GAME",
             "flat_params": {
               "asset_id": "wx123",
               "carrier_id": "wx123",
               "asset_name": "某小游戏",
               "carrier_name": "某小游戏"
             }
           }
         ]
       }
     }
   }
   ```

   **反推流程**：
   - 遍历 `asset_map` 各 target_type 下的 `assets`，用 `flat_params.asset_id` 统一匹配用户给出的资产 ID（无需区分 `marketing_asset_id` / `marketing_asset_outer_id` / `product_outer_id`，`flat_params.asset_id` 已归一化）
   - **找到** → 确定了该资产所属的 `marketing_target_type`，在 `combinations` 中筛选含该 target_type 的组合。若缩小到唯一一组，四元组确定；若仍有多组（同一 target_type 对应多个 goal/carrier），继续走后续规则。同时**直接保存该资产的 `flat_params`**，步骤 3 选资产时无需再查
   - **找不到** → 资产 ID 反推失败，**不代表用户给的 ID 是错的**（例如小游戏 ID 可能不在 API 返回的列表中），跳过此规则，继续按后续规则选择

2. **从用户意图提取关键约束**：
   - 用户提到了"直播"/"直播间"/"视频号直播" → `marketing_carrier_type` 应含 `WECHAT_CHANNELS_LIVE`
   - 用户提到了"小游戏" → `marketing_target_type` 应含 `WECHAT_MINI_GAME`
   - 用户提到了"小程序" → `marketing_target_type` 应含 `MINI_PROGRAM_WECHAT`
   - 用户提到了"小店商品"/"商品页" → `marketing_target_type` 应含 `WECHAT_STORE_PRODUCT`
   - 用户提到了"跳转页面" → `marketing_carrier_type` 应含 `JUMP_PAGE`
   - 用户提到了"APP"/"应用" → `marketing_carrier_type` 应含 `APP_ANDROID` 或 `APP_IOS`

3. **按 `marketing_goal` 匹配用户的营销目标**：
   - 用户提到"卖货"/"下单"/"成交"/"GMV"/"ROI" → `MARKETING_GOAL_PRODUCT_SALES`
   - 用户提到"拉新"/"注册"/"激活"/"付费" → `MARKETING_GOAL_USER_GROWTH`
   - 用户提到"品牌"/"曝光" → `MARKETING_GOAL_BRAND_PROMOTION`
   - 用户提到"涨粉"/"加关注" → `MARKETING_GOAL_INCREASE_FANS_INTERACTION`
   - 用户提到"留资"/"表单"/"线索" → `MARKETING_GOAL_LEAD_RETENTION`

4. **短直双开场景特殊规则**：短直双开（`WECHAT_STORE_SINGLE_PRODUCT`）有两条链路（直播+短视频），**只需创建一条广告组**。按照直播链路选择四元组——即 `marketing_carrier_type` = `MARKETING_CARRIER_TYPE_WECHAT_CHANNELS_LIVE`、`marketing_target_type` = `MARKETING_TARGET_TYPE_WECHAT_CHANNELS_LIVE`。

5. **若过滤后仍有多个组合 → 必须向用户确认，禁止猜测**。将剩余的所有组合选项列出给用户选择。

6. **`marketing_sub_goal` 只能复用 `get-rules.mjs` 返回值，禁止语义改写**：例如用户说"注册"、"新客"时，不要自造 `MARKETING_SUB_GOAL_USER_REGISTER`；必须原样使用返回的枚举值。

> **智投强约束**：`delivery_scene` 参数传的是 `smart_delivery_platform` 字符串枚举，不是数值。

**步骤 2 完成后你应该有**：四元组 4 个值（从 combinations 数组的匹配项取）。

---

## 步骤 3：获取推广产品 + 营销载体

> **前置**：步骤 2 的 `marketing_target_type`
> **输出**：选定 asset 的 `flat_params`（ONEID 类和视频号直播类已包含 `carrier_id`；行业/商品库类不含 `carrier_id`，若 `carrier_type` 需要载体则需从用户处获取）

**调用方式**：

```bash
node scripts/get-assets.mjs '{"account_id":"<ACCOUNT_ID>","marketing_target_type":"<...>"}'
```

**脚本已处理**：自动根据 `marketing_target_type` 判断分类（ONEID / 商品库 / 行业产品库），路由到正确的 API，返回统一格式的资产列表。

**返回示例**：
```json
{
  "asset_type": "ONEID",
  "assets": [
    {
      "marketing_asset_outer_id": "wx1234567890",
      "marketing_carrier_id": "wx1234567890",
      "name": "以闪亮之名",
      "type": "WECHAT_MINI_GAME",
      "flat_params": {
        "asset_id": "wx1234567890",
        "carrier_id": "wx1234567890",
        "asset_name": "以闪亮之名",
        "carrier_name": "以闪亮之名"
      }
    }
  ]
}
```

> **重要**：如果步骤 2 中已通过 `get-assets-by-rules.mjs` 获取了 `asset_map`，且当前 `marketing_target_type` 在 map 中已有结果，**可复用该结果，无需再调 `get-assets.mjs`**。

**Agent 需要做的决策**：从返回的资产列表中确定用户要使用的资产。

**资产选择规则（按优先级）**：

1. **用户给了资产 ID**（`marketing_asset_id`、`product_id`、`marketing_asset_outer_id` 等）：
   - **在资产列表中找到** → 直接使用
   - **在资产列表中找不到** → **不判定为错误**，保留用户给的 ID 继续流程，交给后台接口校验
2. **用户给了资产名称但没给 ID** → 按名称模糊匹配资产列表，匹配到 1 条直接使用，匹配到多条列出让用户选
3. **用户未提及任何资产信息** → 若只有 1 条资产直接使用；若有多条，展示列表让用户确认选择

### 3A. 字段组装规则（flat_params 直接透传）

选定资产后，**直接使用该 asset 的 `flat_params` 对象**，在调用 `create-adgroup.mjs` 时将 `flat_params` 里的 key-value 展开到请求体顶层即可。`create-adgroup.mjs` 会根据 `marketing_target_type` 自动组装 API 所需的嵌套结构（`marketing_asset_outer_spec` / `marketing_asset_id` / `marketing_carrier_detail`）。

**flat_params 各字段含义**（与 `create-adgroup.mjs` 方式一入参完全对齐，共 7 个）：

| flat_params key | 含义 | 来源 |
|----------------|------|------|
| `asset_id` | 推广产品 ID | ONEID→outer_id / 行业→asset_id / 商品库→catalog_id |
| `carrier_id` | 载体 ID | ONEID 类和视频号直播类→自动填入（= outer_id，和 asset_id 同值）；⚠️ **行业/商品库类不含此字段**，若 `carrier_type` 需要载体则需从用户处获取后补入 |
| `catalog_id` | 商品目录 ID | 仅商品库类 |
| `asset_sub_id` | 子标识 | 商品库→product_outer_id；直播预约→notice_id（需 Agent 从 live_notices 中选择后补入） |
| `asset_name` | 资产名称 | ONEID 类自动填入 asset 的 name；create-adgroup.mjs 写入 `marketing_asset_outer_name`（仅 APP_QUICK_APP / PC_GAME 生效） |
| `carrier_name` | 载体名称 | ONEID 类自动填入 asset 的 name；create-adgroup.mjs 写入 `marketing_carrier_name`（仅 QUICK_APP / PC_GAME 载体类型生效，其他类型自动忽略） |
| `sub_carrier_id` | 载体子标识 | 直播预约→notice_id（需 Agent 从 live_notices 中选择后补入）；create-adgroup.mjs 写入 `marketing_sub_carrier_id` |

> **多传无害**：`asset_name`、`carrier_name` 在非 QUICK_APP / PC_GAME 类型时，`create-adgroup.mjs` 内部会自动过滤掉，不会传给 API。

**示例——Agent 选定 asset 后传给 create-adgroup.mjs 的参数**：
```json
{
  "account_id": "123456789",
  "adgroup_name": "...",
  "marketing_target_type": "MARKETING_TARGET_TYPE_WECHAT_MINI_GAME",
  "asset_id": "wx1234567890",
  "carrier_id": "wx1234567890",
  "asset_name": "以闪亮之名",
  "carrier_name": "以闪亮之名",
  "...其他字段..."
}
```
> 上面的 `asset_id`、`carrier_id`、`asset_name`、`carrier_name` 都是从 `assets[n].flat_params` 里展开的。

**不再需要手动组装以下嵌套结构**（`create-adgroup.mjs` 自动处理）：
- ❌ 不需要手动构造 `marketing_asset_outer_spec`
- ❌ 不需要手动构造 `marketing_carrier_detail`
- ❌ 不需要手动设置 `marketing_asset_id`（行业类）
- ✅ 只需把 `flat_params` 展开透传

### 3B. 强约束

- **白名单例外**：用户在原始需求里已明确给出完整 ID（如 `product_id`、`marketing_asset_outer_id`、`catalog_id`、`product_outer_id`、`marketing_asset_id`、`marketing_carrier_id`），则可直接使用并跳过查询。
- **空字符串不是合法结果**：不要把空字符串当作"已完成步骤 3"。查不到且用户也未提供时，保留缺失状态。
- **字段名强约束**：`marketing_asset_outer_spec` 里使用 `marketing_asset_outer_id` / `marketing_asset_outer_sub_id`，**不要写** `outer_id` / `outer_sub_id`。

**步骤 3 完成后你应该有**：选定 asset 的 `flat_params`（包含 `asset_id`、`carrier_id` 等，将在步骤 7 展开透传给 `create-adgroup.mjs`）。

---

## 步骤 4（⛔ 默认必须执行）：获取版位列表

> **前置**：步骤 1 的 `smart_delivery_platform` + 步骤 2 的四元组
> **输出**：`site_set`（数组），仅供后续 `get-conversions.mjs` 使用

智投场景下版位固定为**智能版位**（系统将智能进行版位择优投放），用户不可选择或修改。如果用户提到了版位相关需求，应告知用户：「智投场景下版位为系统智能择优投放，无需手动选择」。

本步骤的唯一目的是获取当前场景的 `site_set` 列表，**供步骤 5 查询转化目标时使用**。

```bash
node scripts/get-site-set.mjs '{"account_id":"<ACCOUNT_ID>","marketing_goal":"<MARKETING_GOAL_枚举key>","marketing_sub_goal":"<MARKETING_SUB_GOAL_枚举key>","marketing_target_type":"<MARKETING_TARGET_TYPE_枚举key>","marketing_carrier_type":"<MARKETING_CARRIER_TYPE_枚举key>"}'
```

**返回**：
```json
{
  "auto_site_set": ["SITE_SET_WECHAT", "SITE_SET_MOBILE_UNION"]
}
```

- `site_set` 列表是动态的，**不要手写示例数组**
- 后续 `get-conversions.mjs` 必须使用此处返回的真实 `site_set`

**步骤 4 完成后你应该有**：`site_set` 数组（来自脚本返回）。

---

## 步骤 5：获取转化目标 + 确定出价

> **前置**：步骤 1 的 `delivery_scene`（数值）+ 步骤 2 的四元组 + 步骤 4 的 `site_set`
> **输出**：`conversion_id`、`bid_amount`、出价相关字段

### 5A. 获取转化目标

```bash
node scripts/get-conversions.mjs '{"account_id":"<ACCOUNT_ID>","delivery_scene":<数值>,"site_set":["..."],"marketing_goal":"<MARKETING_GOAL_枚举key>","marketing_sub_goal":"<MARKETING_SUB_GOAL_枚举key>","marketing_carrier_type":"<MARKETING_CARRIER_TYPE_枚举key>","marketing_target_type":"<MARKETING_TARGET_TYPE_枚举key>","create_source_type":"<CreateSourceType的枚举值，用户未提及来源时不传此参数>"}'
```

> **注意**：`get-conversions.mjs` 的入参中，四元组字段统一传**字符串枚举 key**（如 `"MARKETING_GOAL_USER_GROWTH"`），脚本内部自动转为数值。**不需要传 `product_type`、`live_video_mode`、`live_video_sub_mode`**，脚本会根据 `marketing_carrier_type` + `delivery_scene` 自动推导。`delivery_scene` 传步骤 1 的数值。
>
> **特别注意 `marketing_sub_goal`**：这里传的必须是**步骤 2 选中的原始 key**，不能因为用户说了"注册"、"下载"、"安装"，或转化名称里带这些词，就改写成 `MARKETING_SUB_GOAL_USER_REGISTER`、`MARKETING_SUB_GOAL_MINI_GAME_APP_INSTALL` 之类的自造枚举。
>
> **`create_source_type`**：如果用户指定了转化来源，则根据用户意图传入——`PLATFORM`平台转化或`SELF_CREATED`自建转化等。用户未提及来源时不传此参数。

**脚本已处理**：通过 `access_status` 过滤仅返回已完成接入的转化目标，标注深度转化类型，裁剪无关字段。

**返回示例**：
```json
{
  "goals": [
    {
      "conversion_id": 12345,
      "name": "小游戏注册",
      "optimization_goal": "OPTIMIZATIONGOAL_APP_REGISTER",
      "bid_mode": "BID_MODE_OCPM",
      "has_deep_conversion": false,
      "create_source_type": "PLATFORM"
    },
    {
      "conversion_id": 12346,
      "name": "注册-七日变现ROI",
      "optimization_goal": "OPTIMIZATIONGOAL_APP_REGISTER",
      "bid_mode": "BID_MODE_OCPM",
      "has_deep_conversion": true,
      "deep_conversion_type": "DEEP_CONVERSION_WORTH",
      "deep_conversion_worth_goal": "OPTIMIZATIONGOAL_MONETIZATION_ROAS_7DAY",
      "create_source_type": "SELF_CREATED"
    }
  ]
}
```

**Agent 需要做的决策——conversion_id 选择规则**：

1. **用户提供了 ROI 系数** → **必须**选 `has_deep_conversion=true` 的转化ID
2. **用户未提供 ROI 系数** → 选与用户指定优化目标匹配的项
3. **用户未指定** → 取 goals 数组第一个项
4. **用户指定了转化创建来源**（如"自建转化"/"自建归因"/"自建"/"自定义转化"→ `SELF_CREATED`，"平台预置"/"平台转化"/"平台上报"/"系统预置"/"预置转化"→ `PLATFORM`）→ 在调用 `get-conversions.mjs` 时传入 `create_source_type` 过滤；**用户未提及来源时不传此参数**

**强约束**：
- `conversion_id` 只能来自：用户明确给出的 ID，或 `get-conversions.mjs` 返回
- **白名单例外**：用户在原始需求中已明确给出 `conversion_id` 数值 ID，则直接使用
- **`conversion_id: 0` 视为错误占位，不可提交**
- 若用户未明确给出且也没有成功查询到，不要编造 `0`、空字符串或占位值

### 5B. 出价字段

| 字段 | 类型 | 必填 | 说明 | 如何获取 |
|------|------|------|------|----------|
| `conversion_id` | integer | **是** | 转化ID（**放在请求体顶层**） | 5A 获取 |
| `bid_amount` | integer | **是** | 出价（单位：**分**，顶层） | 用户提供，"出价42元" → `4200` |
| `bid_mode` | enum | 否 | 出价方式 | 智投默认 `BID_MODE_OCPM` |
| `smart_bid_type` | enum | 否 | 出价类型 | `SMART_BID_TYPE_CUSTOM`（手动）或 `SMART_BID_TYPE_SYSTEMATIC`（自动） |
| `smart_cost_cap` | integer | 否 | 自动出价成本上限（分） | 用户提供 |
| `daily_budget` | integer | 否 | 日预算（分，5000~400000000） | 用户提供，"日预算1000元" → `100000` |
| `deep_conversion_worth_rate` | float | 否 | 深度ROI（对应 `DEEP_CONVERSION_WORTH`） | 用户说"深度ROI 1.5" → `1.5` |
| `deep_conversion_worth_advanced_rate` | float | 否 | 深度辅助ROI（对应 `DEEP_CONVERSION_WORTH_ADVANCED`） | 用户说"深度辅助ROI 1.812" → `1.812` |
| `deep_conversion_behavior_bid` | integer | 否 | 深度优化行为出价（分） | 用户提供 |
| `bid_scene` | enum | 场景依赖 | 出价场景 | 小游戏跑量传 `BID_SCENE_NORMAL_AVERAGE`；其他不传 |

**智投出价核心规则**：
1. **`conversion_id` 和 `bid_amount` 放在请求体顶层**
2. **不需要传 `optimization_goal`** — 智投的优化目标隐含在 `conversion_id` 中
3. 金额单位是**分**（10000分 = 100元）
4. `deep_conversion_worth_rate` 和 `deep_conversion_worth_advanced_rate` 是**两个不同字段**，支持3位小数

**步骤 5 完成后你应该有**：`conversion_id`（顶层，非 0，非空）、`bid_amount`、出价相关可选字段。

---

## 步骤 6：配置定向 + 投放时间

> **前置**：用户意图中的定向需求
> **输出**：`targeting`、`begin_date`、`end_date`、`delivery_time_ranges`

### 6A. 定向配置

- **用户未提及任何定向时**（通投）→ `targeting` 传空对象 `{}`，**跳过以下所有定向步骤**，直接进入 6B
- **用户有定向需求时** → 按下方规则构造 `targeting`

#### 智能定向模式（smart_targeting_mode）

> ⚠️ `smart_targeting_mode` 是**请求体顶层字段**（不在 `targeting` 对象内），控制定向方式——AI 自动探索还是人工圈选。与 `targeting` 内的定向内容字段（地域/年龄/性别等）是不同层级的概念。

- **用户说"不使用智能定向"/"关闭智能定向"/"手动定向"** → 传 `"smart_targeting_mode": "SMART_TARGETING_MANUAL"`
- **用户说"使用智能定向"/"开启智能定向"** → 传 `"smart_targeting_mode": "SMART_TARGETING_AUTO"`
- **用户未提及** → 不传此字段

> 不同智投场景支持的定向维度不同。如果传入了当前场景不支持的定向字段，创建时会报错并列出不支持的字段。收到此报错后，告知用户输入合适的定向。


智投广告支持**完整定向能力**，包括地域、年龄、性别、操作系统、学历、设备价格、微信广告行为等多维度定向。

**定向查询触发规则（命中任一项就必须先调用 `get-targeting-lookup.mjs`）**：
- 用户给了地域、省市区、常驻地 → `type: "geo"`
- 用户给了设备品牌 / 型号 → `type: "device"`

**不需要调用 `get-targeting-lookup.mjs` 的定向维度（脚本自动匹配枚举）**：
- **性别**：直接用 `["MALE"]` / `["FEMALE"]` / 不传
- **年龄**：直接用 `[{"min":25,"max":29}, {"min":30,"max":39}]` 格式的数组。**`min` 和 `max` 均为闭区间（包含边界值）**，即用户说"25~29岁"→ `{"min":25,"max":29}`，不是 `{"min":25,"max":30}`。每个区间对应用户给出的一段年龄范围，如果用户说了多个不同年龄段区间，就按照多个区间构造，**不要合并连续段**
- **排除已转化**：通过 `get-enum-options.mjs '{"fields":["excluded_dimension","excluded_day"]}'` 查询枚举后构造
- **操作系统**（`user_os`）：通过 `get-enum-options.mjs '{"fields":["user_os"]}'` 查询枚举值后构造（支持系统+版本号, 传 `["IOS"]` / `["ANDROID"]`（全版本），或用简化格式如 `["ANDROID_10+"]` 表示 Android 10 及以上
- **排除操作系统**（`excluded_os`）：通过 `get-enum-options.mjs '{"fields":["excluded_os"]}'` 查询枚举后构造，同 `user_os` 的简化格式，也支持 `WINDOWS`、`HARMONY` 等直接枚举
- **联网方式**（`network_type`）：通过 `get-enum-options.mjs '{"fields":["network_type"]}'` 查询枚举后构造，传用户提到的联网方式即可，如 `["WIFI"]`、`["4G"]`、`["5G"]`，脚本自动匹配为 API 枚举（如 `4G` → `NET_4G`）
- **学历**（`education`）：通过 `get-enum-options.mjs '{"fields":["education"]}'` 查询枚举后构造，传中文即可，如 `["本科", "硕士"]`，脚本自动匹配为 API 枚举（如 `本科` → `BACHELOR`）
- **设备价格**（`device_price`）：通过 `get-enum-options.mjs '{"fields":["device_price"]}'` 查询枚举后构造，传简化描述即可，如 `["2500以上"]`、`["1500-3500"]`，脚本自动展开为对应的价格区间枚举
- **微信广告行为**（`wechat_ad_behavior`）：通过 `get-enum-options.mjs '{"fields":["wechat_ad_behavior_actions"]}'` 查询枚举后构造，传中文即可，如 `{"actions": ["注册过小游戏"], "mini_game_wechat_registered_activity": "30天未活跃"}`，脚本自动匹配为 API 枚举
- **微信广告行为排除**：通过 `get-enum-options.mjs '{"fields":["wechat_ad_behavior_excluded_actions"]}'` 查询枚举后构造
- **其他枚举定向**（婚恋状态/游戏消费能力/应用安装状态等）：均通过 `get-enum-options.mjs` 查询

> ⚠️ `get-targeting-lookup.mjs` **只支持 `type: "geo"` 和 `type: "device"` 两种查询**，不支持 age、gender 等。年龄和性别不需要编码查询，直接用结构化值。

**只有在以下情况才可跳过定向查询**：用户完全没有给任何地域或设备定向约束。

**地域编码查询**：
```bash
node scripts/get-targeting-lookup.mjs '{"type":"geo","keyword":"广东"}'
# 支持批量：keyword 用空格分隔
node scripts/get-targeting-lookup.mjs '{"type":"geo","keyword":"北京 上海 广东"}'
```

> **⚠️ 地域查询效率规则（P0 级，违反会导致轮次耗尽被终止）**：
> 1. **一次查完所有地域**：脚本是本地文件查询，**无数量限制**。把用户给的所有城市名用空格拼接到一个 keyword 里一次调用即可，**不要分批，不要逐个查询**。即使 100+ 个城市也只需 1 次调用。
> 2. **省级优先原则**：如果用户说"北京"、"广东"等省级地域，直接用省名查询，会返回省级编码（如 110000）。**不要拆解为区级编码**，除非用户明确列举了具体的区/市。
> 3. **不加行政区前缀**：直接用城市名（如 `"南宁 成都 杭州"`），不要加"广西南宁"、"四川成都"等前缀。
> 4. **重名消歧**：如果查询结果中有重名地域（如"朝阳区"同时返回北京和长春的），根据用户上下文和 `parent` 字段筛选正确的。

**地域排除**（用户表达"排除某省/不投某省/除 X 外全国投放"）：
调 `get-geo-exclude.mjs`，直接返回排除后剩余省份的编码，取 `id` 组成 `regions` 数组：
```bash
node scripts/get-geo-exclude.mjs '{"exclude":"河北"}'
# → {"results":[{"id":110000,"name":"北京市","level":"province"},{"id":120000,"name":"天津市","level":"province"},...]}
```

**设备品牌型号 ID 查询**：
```bash
node scripts/get-targeting-lookup.mjs '{"type":"device","keyword":"华为"}'
```

**地域输出示例**：
```json
{
  "results": [
    {"id": 440000, "name": "广东省", "level": "province", "city_level": 4}
  ]
}
```

**设备输出示例**：
```json
{
  "results": [
    {"id": 10001, "name": "华为 Mate 60"},
    {"id": 10002, "name": "华为 P60"}
  ]
}
```

**智投支持的完整定向维度**（以下字段均放在 `targeting: { ... }` 对象内，不要放到请求体顶层）：

| 定向维度 | `targeting` 内的字段 | 说明 | 获取方式 |
|----------|-----------------|------|----------|
| 地域 | `geo_location.regions` + `geo_location.location_types` | `regions` 是**纯整数数组**（如 `[440000]`），取地域查询返回的 `id` 值；`location_types` 常用 `LIVE_IN`。**`geo_location` 只传本表列出的子字段，不要自行推测添加字段** | ✅ `get-targeting-lookup.mjs type:geo` + 枚举查询 `location_types` |
| 商圈 | `geo_location.business_districts` | 商圈 ID 数组（integer[]） | 用户提供商圈 ID |
| 自定义位置 | `geo_location.custom_locations` | 经纬度+半径定向，格式: `[{"longitude":113.26,"latitude":23.13,"radius":3000}]`，radius 单位米 | 用户提供 |
| 性别 | `gender` | 枚举查询 | ❌ 查枚举后直接构造 |
| 年龄 | `age` | 数组格式: `[{"min":25,"max":29}, {"min":30,"max":39}]`（**`min`/`max` 均为闭区间**，按用户原始区间构造，**不要合并连续段**） | ❌ 直接构造（无枚举） |
| 操作系统 | `user_os` | 支持系统+版本号（如 `IOS_VERSION_18`），枚举查询 | ❌ 查枚举后直接构造 |
| 排除操作系统 | `excluded_os` | 枚举查询 | ❌ 查枚举后直接构造 |
| 学历 | `education` | 枚举查询，传中文（如 `["本科", "硕士"]`）或枚举 key 均可，脚本自动匹配 | ❌ 查枚举后直接构造 |
| 婚恋状态 | `marital_status` | 枚举查询 | ❌ 查枚举后直接构造 |
| 联网方式 | `network_type` | 枚举查询 | ❌ 查枚举后直接构造 |
| 设备价格 | `device_price` | 枚举查询，传简化描述（如 `["2500以上"]`、`["1500-3500"]`）或枚举 key 均可，脚本自动展开 | ❌ 查枚举后直接构造 |
| 设备品牌型号 | `device_brand_model` | 嵌套结构：`{"included_list":[5,9]}` 定向 / `{"excluded_list":[1]}` 排除，**数字 ID** | ✅ `get-targeting-lookup.mjs type:device` |
| 应用安装状态 | `app_install_status` | 枚举查询（仅推广 APP 时可用） | ❌ 查枚举后直接构造 |
| 自定义人群 | `custom_audience` | 人群包 ID 列表 | 用户提供 |
| 排除人群 | `excluded_custom_audience` | 排除的人群包 ID | 用户提供 |
| 排除已转化 | `excluded_converted_audience` | 见下方格式说明 | ❌ 查枚举后直接构造 |
| 微信广告行为 | `wechat_ad_behavior` | 见下方格式说明 | ❌ 查枚举后直接构造 |

**⛔ 所有枚举值禁止凭记忆猜测，必须通过 `get-enum-options.mjs` 查询确认**：

```bash
# 查询单个/多个字段的枚举
node scripts/get-enum-options.mjs '{"fields":["education","device_price","excluded_dimension"]}'

# 查询定向相关的所有枚举
node scripts/get-enum-options.mjs '{"category":"targeting"}'
```

可查询的定向枚举字段包括：`gender`、`education`、`user_os`、`excluded_os`、`network_type`、`device_price`、`marital_status`、`app_install_status`、`location_types`、`excluded_dimension`、`excluded_day`、`wechat_ad_behavior_actions`、`wechat_ad_behavior_excluded_actions`、`smart_targeting_mode`。

> ⚠️ 智投场景下，出价方式（`bid_mode`）固定为 `BID_MODE_OCPM`，转化目标从 `get-conversions.mjs` 返回获取，版位策略固定为自动版位 —— 这些字段**不需要**通过 `get-enum-options.mjs` 查询。`get-enum-options.mjs` 在智投中**仅用于定向枚举查询**。

**`excluded_converted_audience` 格式**（仅在用户明确提到"排除已转化"时才添加，禁止自行添加）：
```json
{
  "excluded_dimension": "<通过 get-enum-options.mjs 查 excluded_dimension>",
  "excluded_day": "<通过 get-enum-options.mjs 查 excluded_day>"
}
```

**`wechat_ad_behavior` 格式**（仅在用户明确提到微信广告行为排除时才添加，禁止自行添加）：

用户说"排除已关注公众号的用户"、"排除已注册小游戏的用户"等时，构造此字段。`excluded_actions` 的枚举通过 `get-enum-options.mjs '{"fields":["wechat_ad_behavior_excluded_actions"]}'` 查询。

```json
"wechat_ad_behavior": {
  "excluded_actions": ["GDT_WECHAT_OFFICIAL_ACCOUNT_FOLLOWED"],
  "wechat_official_account_id": ["wx18c408376c727a19"]
}
```
- 涉及公众号行为时，需同时传 `wechat_official_account_id`（用户给的公众号 ID）
- 涉及企业微信行为时，需同时传 `corp_id`

**强约束**：
- 命中触发规则后，必须先调用 `get-targeting-lookup.mjs` 获取编码，再构造 `targeting`
- 不要把自然语言直接翻成粗粒度占位值
- **`targeting` 及其子结构只传上方表格中列出的字段和子字段**，不要自行推测或类推添加文档中未出现的字段
- **⛔ 所有枚举值禁止凭记忆猜测，必须通过 `get-enum-options.mjs` 查询确认**

**targeting 构造示例**：

```json
{
  "bid_amount": 5000,
  "targeting": {
    "age": [{"min": 25, "max": 29}, {"min": 30, "max": 39}],
    "gender": ["MALE"],
    "geo_location": {
      "regions": [440000, 310000],
      "location_types": ["LIVE_IN"]
    }
  }
}
```

### 6B. 投放时间与状态

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `begin_date` | string | **是** | 开始日期，格式 `YYYY-MM-DD` |
| `end_date` | string | **是** | 结束日期，格式 `YYYY-MM-DD`；未指定传 `""`（长期投放） |
| `first_day_begin_time` | string | 否 | 首日开始时间，格式 `HH:ii:ss` |
| `delivery_time_ranges` | string[] | **是** | 投放时段数组，半小时精度。|
| `configured_status` | enum | 否 | 广告状态，默认暂停(`AD_STATUS_SUSPEND`)；用户明确要求上线时传 `AD_STATUS_NORMAL` |

**`delivery_time_ranges` 格式说明**：

数组中每条格式为:全时段/全天投放/未设置：`"all"`；指定时段： `"<Weekday> <HH:MM>~<HH:MM>"`，时间精度为半小时（只支持 `:00` 或 `:30`）；
**示例**：`["all"]`=全时段；`["Monday 09:00~18:00",...,"Sunday 09:00~18:00"]`=每天9点到18点（支持 Monday-Sunday ）

```json
// 全时段（用户未指定投放时段）
"delivery_time_ranges": ["all"]
// 周二全天
"delivery_time_ranges": ["Tuesday 00:00~24:00"]
// 周一上午9点到12点，周二下午14点到18点
"delivery_time_ranges": ["Monday 09:00~12:00","Tuesday 14:00~18:00"]
// 周二全天
"delivery_time_ranges": ["Tuesday 00:00~24:00"]
```

**时间精度规则**：
- 时间只能是整点 `:00` 或半点 `:30`
- ⚠️ **如果用户说的时间不是整半小时**（如 "10:00~10:15"），需要向用户确认：精度只支持半小时，可以选择 "10:00~10:30"（多了 15 分钟），请用户确认或调整
- ⚠️ **end 边界规则**：当用户说 "23:59"、"24:00"、"午夜" 或"投到当天结束"时，一律用 `24:00` 作为结束时间
- 星期名支持全称（Monday-Sunday）

**Agent 需要做的**：从用户自然语言中理解投放时段意图，转为 `delivery_time_ranges` 数组。常见自然语言映射：
- "全天投放" / 未指定 → `["all"]`
- "工作日" → Monday 到 Friday
- "周末" → Saturday + Sunday
- "每天 X 到 Y" → 7 天都写上相同时段
- "某天"  → 如果用户没有表达时间段默认 00:00~24:00，例如某天是星期二：Tuesday 00:00~24:00
- "排除周三下午" → 列出除周三下午外的所有时段

**步骤 6 完成后你应该有**：`targeting`（如需）、`begin_date`、`end_date`、`delivery_time_ranges`。

---

## 步骤 7：组装请求体 → 创建广告组

> **前置**：步骤 1-6 的所有输出
> **动作**：按检查清单组装完整请求体，调用 `create-adgroup.mjs`

### 7A. 版位与探索策略（智投固定值，脚本自动处理）

| 字段 | 值 | 说明 |
|------|-----|------|
| `automatic_site_enabled` | `true` | 脚本自动设置，无需传入 |
| `site_set` | 不传 | 智能版位，系统自动择优 |
| `exploration_strategy` | 推荐 `AUTOMATIC_EXPLORATION` | |

### 7B. 智投专有字段

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `smart_delivery_platform` | enum | **是** | 步骤 1 确定的智投场景标识 |
| `smart_delivery_aigc_creative` | struct | **推荐必传** | AIGC自动创意，用户未提及默认关闭 |
| `smart_delivery_history_comp_reused_creative` | struct | **推荐必传** | 组件全库智选，用户未提及默认关闭 |
| `auto_derived_creative_enabled` | boolean | 推荐传 | 自动衍生视频创意，推荐 `true` |
| `smart_delivery_period_switch` | enum | 否 | 周期达成开关，`PERIOD_SWITCH_ON` / `PERIOD_SWITCH_OFF`。仅用户明确要求"周期达成"/"周期稳投"时传入 |
| `smart_delivery_period_days` | enum | 周期达成时**必填** | 周期天数，支持 `PERIOD_DAYS_THREE`(3天) / `PERIOD_DAYS_SEVEN`(7天) |
| `smart_delivery_period_budget` | integer | 周期达成时**必填** | 周期总预算（单位：**分**），约束：≥ 3 × 出价 × 周期天数 |
| `smart_delivery_period_continue` | enum | 周期达成时**必填** | 续投开关，`PERIOD_CONTINUE_SWITCH_ON`（长期自动续投）/ `PERIOD_CONTINUE_SWITCH_OFF`（单周期结束即停） |
| `short_play_pay_type` | enum | 爆剧跑量场景可选 | 短剧售卖方式类型，详见 [references/short-play-pay-type.md](references/short-play-pay-type.md) |
| `sell_strategy_id` | integer | 条件必填 | 售卖策略 ID，`short_play_pay_type` 为收费剧时**必填** |

**smart_delivery_aigc_creative（AIGC自动创意）**：
```json
// 关闭
{"is_open": false, "supply_strategy_type": ["SUPPLY_STRATEGY_TYPE_AIGC"]}
// 开启
{"is_open": true, "supply_strategy_type": ["SUPPLY_STRATEGY_TYPE_AIGC"]}
```

**smart_delivery_history_comp_reused_creative（全库智选）**：
```json
// 关闭
{"is_open": false, "supply_strategy_type": ["SUPPLY_STRATEGY_TYPE_HISTORY_COMP_REUSE"]}
// 开启
{"is_open": true, "supply_strategy_type": ["SUPPLY_STRATEGY_TYPE_HISTORY_COMP_REUSE"]}
```

> ⚠️ **废弃字段**：不要使用 `smart_delivery_scene_spec`、`smart_delivery_auto_creative`、`aigc_creative_switch`。

#### 爆剧跑量场景专有字段

当 `smart_delivery_platform` = `SMART_DELIVERY_PLATFORM_EDITION_ECOLOGY_PLAYLET`（爆剧跑量）时，支持设置短剧售卖方式和场景规格。**脚本自动校验：收费剧必须提供售卖策略 ID。**

- `short_play_pay_type`：`SHORT_PLAY_PAY_TYPE_FREE_PLAY`（免费剧）/ `SHORT_PLAY_PAY_TYPE_CHARGE_PLAY`（收费剧）
- `sell_strategy_id`：收费剧时**必填**，免费剧不需要

> 枚举值、校验规则、请求体示例详见 [references/short-play-pay-type.md](references/short-play-pay-type.md)

**周期达成（Period Completion）**：

完整字段定义、约束规则和 JSON 示例见 [references/smart-delivery-period.md](references/smart-delivery-period.md)（可通过 `load_skill_reference` 加载）。以下是关键要点：

- **触发条件**：仅在用户明确提出"周期达成"/"周期稳投"需求时才开启，不要自行添加
- **必填字段**：`smart_delivery_period_switch`=`PERIOD_SWITCH_ON` + `smart_delivery_period_days` + `smart_delivery_period_budget` + `smart_delivery_period_continue`
- **预算约束**：`smart_delivery_period_budget` ≥ 3 × `bid_amount` × 周期天数
- **禁止字段**：开启时不允许传 `daily_budget`、`total_budget`；`end_date` 由脚本统一设为 `""`，后端根据 `begin_date` + 周期天数自动计算实际结束日期
- **出价限制**：不允许使用自动出价（`smart_bid_type` 不能为 `SMART_BID_TYPE_SYSTEMATIC`）
- **支持场景**：线索智投（`SMART_DELIVERY_PLATFORM_EDITION_ECOLOGY_LEADS`）

#### ⛔ 创意字段场景化必填规则（P0 级，脚本会校验拦截）

以下两条规则由 `create-adgroup.mjs` 自动校验，不满足时会报错拦截。**Agent 必须在组装请求体时主动满足这些规则，避免被脚本拦截。**

**规则 A：视频号直播场景必填 `sku_id` + `catalog_id`**

| 触发条件 | 说明 |
|----------|------|
| `smart_delivery_platform` = 3002（小店单链路）或 3004（推直播间） | 投放场景 |
| `marketing_target_type` = `MARKETING_TARGET_TYPE_WECHAT_CHANNELS_LIVE` | 推广产品类型为视频号直播 |
| `marketing_carrier_type` = `MARKETING_CARRIER_TYPE_WECHAT_CHANNELS_LIVE` | 载体为视频号直播 |

**三个条件同时满足时**，`smart_delivery_aigc_creative`为开启状态时，必须额外包含：
- `sku_id`（string）— 商品 SKU ID
- `catalog_id`（integer）— 商品目录 ID

这两个值需要从用户处获取。如果用户未提供，**必须向用户询问**，不可省略。

```json
// 视频号直播场景 AIGC 创意（开启 + sku_id + catalog_id）
{
  "smart_delivery_aigc_creative": {
    "is_open": true,
    "supply_strategy_type": ["SUPPLY_STRATEGY_TYPE_AIGC"],
    "sku_id": "10000299440478",
    "catalog_id": 1023958
  }
}
```

**规则 B：非视频号直播场景必填品牌形象**

| 触发条件 | 说明 |
|----------|------|
| `smart_delivery_platform` = 3002（小店单链路）或 3003（全店托管）或 3005（推商品） | 投放场景 |
| `marketing_target_type` ≠ `MARKETING_TARGET_TYPE_WECHAT_CHANNELS_LIVE` | 推广产品类型**不是**视频号直播 |
| `marketing_carrier_type` ≠ `MARKETING_CARRIER_TYPE_WECHAT_CHANNELS_LIVE` | 载体**不是**视频号直播 |

**三个条件同时满足时**，`smart_delivery_aigc_creative` 或 `smart_delivery_history_comp_reused_creative`处于开启状态时，必须满足：
1. 在 `creative_components.brand` 数组中填写至少一个 `component_id`（品牌形象组件 ID）
2. 在 `supply_strategy_type` 数组中包含 `SUPPLY_STRATEGY_TYPE_CUSTOMER_MANUAL_CREATION`（与原有策略类型并存）
3. 注意：`SUPPLY_STRATEGY_TYPE_CUSTOMER_MANUAL_CREATION` 必须和原有的策略类型并存，不能单独使用。
结构示例：
```json
// 非视频号直播场景 AIGC 创意（开启 + 品牌形象）
{
  "smart_delivery_aigc_creative": {
    "is_open": true,
    "supply_strategy_type": [
      "SUPPLY_STRATEGY_TYPE_AIGC",
      "SUPPLY_STRATEGY_TYPE_CUSTOMER_MANUAL_CREATION"
    ],
    "creative_components": {
      "brand": [{"component_id": 1917953657934}]
    }
  }
}
// 非视频号直播场景 组件全库智选（开启 + 品牌形象）
{
  "smart_delivery_history_comp_reused_creative": {
    "is_open": true,
    "supply_strategy_type": [
      "SUPPLY_STRATEGY_TYPE_HISTORY_COMP_REUSE",
      "SUPPLY_STRATEGY_TYPE_CUSTOMER_MANUAL_CREATION"
    ],
    "creative_components": {
      "brand": [{"component_id": 1917953657934}]
    }
  }
}
```

> **⚠️ 注意**：`SUPPLY_STRATEGY_TYPE_CUSTOMER_MANUAL_CREATION` 和 `creative_components.brand` 必须**同时存在**，缺一不可。只传 brand 不加策略类型、或只加策略类型不传 brand，都会导致创建失败。

### 7C. project_ability_list（全店托管多商品场景）

**适用场景**：`smart_delivery_platform` = `SMART_DELIVERY_PLATFORM_EDITION_WECHAT_STORE_MANAGEMENT`（全店托管）且用户指定了多个商品时，或任何需要传入多个商品营销表达式的场景。

**规则**：
- 当用户指定多个商品（如"含2个商品"、"商品A和商品B"）时，**每个商品对应一条 `project_ability_list` 项**
- `project_ability_list` 替代顶层的 `marketing_asset_outer_spec`：各商品的 asset 信息放入每个 item 的 `marketing_expression.marketing_asset_outer_spec` 内
- 四元组（`marketing_goal`、`marketing_sub_goal`、`marketing_carrier_type`）在每个 item 内重复

**结构示例（2个微信小店商品，营销载体为跳转页面）**：
```json
{
  "project_ability_list": [
    {
      "project_ability_type": "ABILITY_TYPE_MARKETING_EXPRESSION",
      "ability_content": {
        "marketing_expression": {
          "marketing_goal": "MARKETING_GOAL_PRODUCT_SALES",
          "marketing_sub_goal": "MARKETING_SUB_GOAL_UNKNOWN",
          "marketing_carrier_type": "MARKETING_CARRIER_TYPE_JUMP_PAGE",
          "marketing_asset_outer_spec": {
            "marketing_target_type": "MARKETING_TARGET_TYPE_WECHAT_STORE_PRODUCT",
            "marketing_asset_outer_id": "<catalog_id>",
            "marketing_asset_outer_sub_id": "<product_outer_id_1>"
          }
        }
      }
    },
    {
      "project_ability_type": "ABILITY_TYPE_MARKETING_EXPRESSION",
      "ability_content": {
        "marketing_expression": {
          "marketing_goal": "MARKETING_GOAL_PRODUCT_SALES",
          "marketing_sub_goal": "MARKETING_SUB_GOAL_UNKNOWN",
          "marketing_carrier_type": "MARKETING_CARRIER_TYPE_JUMP_PAGE",
          "marketing_asset_outer_spec": {
            "marketing_target_type": "MARKETING_TARGET_TYPE_WECHAT_STORE_PRODUCT",
            "marketing_asset_outer_id": "<catalog_id>",
            "marketing_asset_outer_sub_id": "<product_outer_id_2>"
          }
        }
      }
    }
  ]
}
```

**字段来源**：
- `catalog_id`（即 `marketing_asset_outer_id`）和 `product_outer_id`（即 `marketing_asset_outer_sub_id`）来自步骤 3 `get-assets.mjs` 返回的 `assets[n].catalog_id` 和 `assets[n].product_outer_id`
- 单商品场景仍用顶层 `marketing_asset_outer_spec`，无需 `project_ability_list`

### 7D. 创建前自检（Pre-flight Check，⛔ 必须在调用 create-adgroup.mjs 前完成）

在组装完请求体后、真正调用创建接口前，逐项核对以下内容：
- **用户需求一致性**：用户明确给出的每个条件（定向、出价、预算、三元组、商品等）是否都被**原样保留**在请求体中，未被丢弃、修改或替换
- **字段来源可追溯**：四元组、转化目标、营销载体等关键字段是否来自**实际脚本查询结果**（而非猜测或编造）
- **编码已确认**：定向条件中的地域编码、设备 ID 等是否经过**查询脚本确认**
- **金额单位正确**：所有金额字段是否已转换为**分**

如有任何字段与用户原始需求不一致，必须**停止并说明差异原因**，等待用户确认后才能继续。

**字段检查清单（20项）**：

| # | 检查项 | 要点 |
|---|--------|------|
| 1 | `account_id` | 广告主账号ID |
| 2 | `adgroup_name` | 用户提供的原文名称，不要编造 |
| 3 | `smart_delivery_platform` | 步骤 1 确定，与场景匹配 |
| 4 | 四元组 | `marketing_goal`、`marketing_sub_goal`、`marketing_target_type`、`marketing_carrier_type` — **统一使用字符串枚举 key**（如 `"MARKETING_GOAL_USER_GROWTH"`），`create-adgroup.mjs` 脚本内部自动转为数值。**默认全部从步骤 2 的脚本返回中选出**；只有用户明确给出 4 个枚举值全部时才可直接使用 |
| 5 | `marketing_target_type` 位置 | 传在**请求体顶层**即可，`create-adgroup.mjs` 会自动将其移入 `marketing_asset_outer_spec` 内部（ONEID类/商品库类）或用于 `marketing_asset_id` 组装（行业产品库类）。**不需要手动放进 spec** |
| 6 | 资产/载体扁平字段 | 步骤 3 选定 asset 的 `flat_params` 各 key-value **展开到请求体顶层**（如 `asset_id`、`carrier_id`、`catalog_id` 等）；ONEID 类和视频号直播类已含 `carrier_id`；**行业/商品库类不含 `carrier_id`**，若 `carrier_type` 需要载体则需从用户处获取后补入；**不要手动构造 `marketing_carrier_detail`** |
| 7 | `marketing_asset_outer_spec` / `marketing_asset_id` | **不需要手动构造**，`create-adgroup.mjs` 根据 `asset_id` + `marketing_target_type` 自动组装 |
| 8 | `conversion_id`（顶层） | 步骤 5 获取。有 ROI 系数时选 `has_deep_conversion=true` 的；用户明确给出则直接使用；**禁止写 `0`、空字符串或漏传** |
| 9 | `bid_amount`（顶层） | 单位分 |
| 10 | `begin_date` / `end_date` | 未指定 end_date 传 `""` |
| 11 | `delivery_time_ranges` | 投放时段数组，每条格式 `"<Weekday> <HH:MM>~<HH:MM>"`，如 `["Monday 09:00~18:00", "Tuesday 09:00~18:00"]`；传 `["all"]` 表示全时段投放。支持 Monday-Sunday  |
| 12 | `bid_mode` | 默认 `BID_MODE_OCPM` |
| 13 | `automatic_site_enabled` | 智投必须 `true` |
| 14 | `smart_delivery_aigc_creative` | 必传，用户未提及时关闭。**视频号直播场景(3002/3004+视频号直播target+视频号直播carrier)开启时必须含 `sku_id` 和 `catalog_id`**；**非视频号直播场景(3002/3003/3005)开启时必须含 `creative_components.brand` 和 `SUPPLY_STRATEGY_TYPE_CUSTOMER_MANUAL_CREATION`**。用户未提供品牌形象时，脚本会自动查询品牌形象列表返回供选择 |
| 15 | `smart_delivery_history_comp_reused_creative` | 必传，用户未提及时关闭。**非视频号直播场景(3002/3003/3005)开启时必须含 `creative_components.brand` 和 `SUPPLY_STRATEGY_TYPE_CUSTOMER_MANUAL_CREATION`**。用户未提供品牌形象时，脚本会自动查询品牌形象列表返回供选择 |
| 16 | `bid_scene` | 小游戏跑量传 `BID_SCENE_NORMAL_AVERAGE`，其他不传 |
| 17 | `targeting` 来源 | 只要用户给了任何定向约束，地域/设备必须通过 `get-targeting-lookup.mjs` 查编码，枚举通过 `get-enum-options.mjs` 查询；定向白名单由 `create-adgroup.mjs` 自动校验，不支持的字段会报错返回 |
| 18 | `search_expansion_switch` | 用户要求时传 `SEARCH_EXPANSION_SWITCH_OPEN`，未提及不传 |
| 19 | `project_ability_list` | 全店托管多商品场景必须用此字段替代顶层 `marketing_asset_outer_spec`；单商品不需要 |
| 20 | `wechat_ad_behavior` | 用户提到微信广告行为排除时，通过 `get-enum-options.mjs` 查询枚举后构造；**用户未提及 → 不传** |
| 21 | 定向枚举值 | `education`、`network_type`、`device_price`、`excluded_dimension`、`excluded_day` 等定向枚举**必须通过 `get-enum-options.mjs` 查询确认，禁止凭记忆猜测** |
| 22 | `material_package_id`（素材标签） | 用户提及素材标签/素材包时按 [references/material-labels.md](references/material-labels.md) 取值；未提及 → **不传** |
| 23 | `smart_targeting_mode` | 智能定向模式。`SMART_TARGETING_MANUAL`（手动定向）=不使用/关闭智能定向，`SMART_TARGETING_AUTO`（智能定向）=开启/使用智能定向。枚举通过 `get-enum-options.mjs '{"fields":["smart_targeting_mode"]}'` 查询。用户未提及不传 |
| 24 | `short_play_pay_type` + `sell_strategy_id` | 仅爆剧跑量场景。用户提及短剧售卖方式时设置；收费剧必须提供 `sell_strategy_id`；未提及 → **不传** |
| 25 | 周期达成字段 | 用户明确要求"周期达成"/"周期稳投"时传入 `smart_delivery_period_switch`=`PERIOD_SWITCH_ON` + `smart_delivery_period_days` + `smart_delivery_period_budget` + `smart_delivery_period_continue`；预算 ≥ 3×出价×天数；禁止同时传 `daily_budget`/`total_budget`/`end_date`；用户未提及 → **不传任何周期达成字段** |

> ⚠️ **定向字段禁止自行添加**：`excluded_converted_audience`、`wechat_ad_behavior` 等定向字段，**只有用户明确要求时才加入 targeting**。用户没提到的定向维度一律不传。

### 7E. 执行创建

```bash
node scripts/create-adgroup.mjs '<完整参数JSON>'
```

**返回示例（成功）**：
```json
{
  "success": true,
  "adgroup_id": 987654321
}
```

**返回示例（失败）**：
```json
{
  "success": false,
  "error": { "code": 1000014, "message": "...", "message_cn": "..." }
}
```

---

## 用户意图提取规则

| 用户输入 | 对应字段 | 转换规则 |
|---------|----------|----------|
| 场景关键词 | `smart_delivery_platform` | 按步骤 1 场景参考表匹配 |
| 四元组相关 | `marketing_goal` 等 | **从步骤 2 脚本返回中选取**，禁止猜测。**统一使用字符串枚举 key**，脚本内部转数值 |
| "出价136.54元" | `bid_amount` | → `13654`（×100，分） |
| "日预算1000元" | `daily_budget` | → `100000`（×100，分） |
| "深度ROI 1.5" | `deep_conversion_worth_rate` | → `1.5`，需选 `has_deep_conversion=true` 的转化ID |
| "深度辅助ROI 1.812" | `deep_conversion_worth_advanced_rate` | → `1.812`，需选 ROI 类转化ID |
| "全国投放"/"不限地域" | `targeting.geo_location` | 不传 `geo_location` |
| "排除X省"/"不投X省"/"除X外全国" | `targeting.geo_location` | → 调 `get-geo-exclude.mjs`，取返回 `results` 中的 `id` 组成 `regions` 数组，加 `location_types: ["LIVE_IN"]` |
| 指定省市 | `targeting.geo_location` | → `get-targeting-lookup.mjs` 查编码，取返回的 `id` 组成纯整数数组放入 `regions`（如 `[440000]`），`location_types: ["LIVE_IN"]` |
| 指定性别 | `targeting.gender` | → 通过 `get-enum-options.mjs` 查 `gender` 枚举后构造 |
| 指定年龄 | `targeting.age` | → `[{"min":25,"max":29}, {"min":30,"max":39}]`，**`min`/`max` 均为闭区间（包含边界值）**，即"25~29岁"→ `{"min":25,"max":29}`，不是 `{"min":25,"max":30}`。按用户给出的区间直接构造，如果用户说了多个不同年龄段，**不要合并** |
| 指定设备 | `targeting.device_brand_model` | → `get-targeting-lookup.mjs` 搜数字ID |
| 指定操作系统 | `targeting.user_os` | 全版本：`["ANDROID"]` / `["IOS"]`；指定版本范围：如"Android 10及以上"→ `["ANDROID_VERSION_10", ..., "ANDROID_VERSION_15"]`，"iOS 14及以上"→ `["IOS_VERSION_14", ..., "IOS_VERSION_18"]` |
| 排除操作系统 | `targeting.excluded_os` | 排除特定系统版本，如排除鸿蒙纯净版 → `["ANDROID_PURE_MODE"]`，排除低版本Android → `["ANDROID_VERSION_1", "ANDROID_VERSION_2", ..., "ANDROID_VERSION_4"]` |
| 指定联网方式 | `targeting.network_type` | 如"仅WiFi"→ `["WIFI"]`，"4G及以上"→ `["NET_4G", "NET_5G", "WIFI"]` |
| 排除已转化 | `targeting.excluded_converted_audience` | → 按定向规则填写 |
| 排除小游戏注册用户（N天未活跃） | `targeting.wechat_ad_behavior` | → `{"excluded_actions": ["MINI_GAME_WECHAT_REGISTERED"], "mini_game_wechat_registered_activity": "THIRTY_DAYS_NO_ACTIVE"}` |
| 定向枚举值 | `education`、`network_type`、`device_price` 等 | **必须通过 `get-enum-options.mjs` 查询确认，禁止凭记忆猜测** |
| 项目名称 | `adgroup_name` | 按用户原文 |
| "转化来源/自建转化/平台预置" | `create_source_type` | → `CreateSourceType`，枚举通过 `get-enum-options.mjs '{"fields":["create_source_type"]}'` 查询 |
| "AIGC开启"/"AIGC创意"/"AIGC" | `smart_delivery_aigc_creative` | → **struct**：`{"is_open": true, "supply_strategy_type": ["SUPPLY_STRATEGY_TYPE_AIGC"]}`。|
| "全库智选开启"/"组件全库智选"/"历史组件复用" | `sma

…(truncated)
