sino-bc-cli 1.8.19 → 1.8.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/skills/custom/biz-line-strategy-generate/SKILL.md +49 -56
  3. package/dist/skills/custom/biz-line-strategy-generate/references/api-flow.md +2 -1
  4. package/dist/skills/custom/biz-line-strategy-generate/references/four-strategies.md +6 -6
  5. package/dist/skills/custom/biz-line-strategy-generate/references/metrics.md +14 -16
  6. package/dist/skills/custom/biz-line-strategy-generate/references/output-excel.md +7 -2
  7. package/dist/skills/custom/biz-line-strategy-generate/references/runtime.md +10 -10
  8. package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/build_report.cpython-314.pyc +0 -0
  9. package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/cli_json.cpython-314.pyc +0 -0
  10. package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/fetch_data.cpython-314.pyc +0 -0
  11. package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/summarize.cpython-314.pyc +0 -0
  12. package/dist/skills/custom/biz-line-strategy-generate/scripts/build_report.py +76 -28
  13. package/dist/skills/custom/biz-line-strategy-generate/scripts/cleanup.py +2 -0
  14. package/dist/skills/custom/biz-line-strategy-generate/scripts/cli_json.py +177 -13
  15. package/dist/skills/custom/biz-line-strategy-generate/scripts/fetch_data.py +27 -18
  16. package/dist/skills/custom/biz-line-strategy-generate/scripts/summarize.py +185 -118
  17. package/dist/skills/custom/biz-line-strategy-generate/scripts/test_strategy_rules.py +201 -0
  18. package/package.json +1 -1
@@ -10,83 +10,76 @@ metadata:
10
10
 
11
11
  # 业务线策略生成
12
12
 
13
- 按业务线/项目维度自动生成「预算策略、广告产品策略、受众策略、素材策略」四大策略内容与效果 learning,最终导出为 Excel。这是一条编排流程(多次调用 `biz-line` 域接口 + 本地计算 + 落盘),不是单条 Shortcut。
13
+ 按业务线自动生成四大策略与 learning,导出一份 Excel(`策略` + `明细` 两张表)。编排多次 `biz-line` 调用,不是单条 Shortcut。
14
14
 
15
- **开始前 MUST 先用 Read 工具读取 [`../sino-bc-shared/SKILL.md`](../sino-bc-shared/SKILL.md)**(配置、信封、确认门、禁止拼 HTTP)。查询业务线相关接口的字段口径见 [`../sino-bc-biz-line/SKILL.md`](../sino-bc-biz-line/SKILL.md)。
15
+ **开始前 MUST 先读 [`../sino-bc-shared/SKILL.md`](../sino-bc-shared/SKILL.md)**。字段口径见 [`../sino-bc-biz-line/SKILL.md`](../sino-bc-biz-line/SKILL.md)。
16
+
17
+ 口径来自 2026-09-17 策略优化沟通:主表只放总结,明细进第二张表;第二期结论跟环比,KPI 只作依据。
16
18
 
17
19
  ## When to Use
18
20
 
19
- - 用户要求「生成业务线策略」「生成投放策略」,或提到「策略生成」「策略 learning」并希望产出文件
20
- - 常见变体:
21
- - 「帮我生成第一页业务线的策略」→ 第 1 页,默认页大小
22
- - 「把前 10 个业务线生成策略」→ 前 10 条
23
- - 「给名称为 xxx 的业务线生成策略」→ 按名称筛选
24
- - 「给业务线 A 生成策略,用广告账号 act_123(Facebook)」→ 用户指定账号覆盖接口返回的账号
21
+ - 「生成业务线策略 / 投放策略 / 策略 learning」并要产出文件
22
+ - 「第一页 / 前 N 个 / 名称为 xxx」;用户指定账号则覆盖接口账号
25
23
 
26
- 不要用本技能处理:单纯查询业务线列表/详情/项目(那是只读查询,直接走 `biz-line +list` / `+project-list` 等,不需要走完整生成流程)。
24
+ 不要用本技能处理:只查列表/详情/项目(走 `biz-line +list` 等)。
27
25
 
28
26
  ## Agent 流程
29
27
 
30
- 按顺序执行。**拉数和聚合不要手写**:用本目录 `scripts/fetch_data.py`、`scripts/summarize.py`。Windows 传参、信封解包、翻页见 [`references/runtime.md`](references/runtime.md)。字段口径见 [`references/api-flow.md`](references/api-flow.md)。
28
+ **拉数和聚合不要手写**:用本目录 `scripts/fetch_data.py`、`scripts/summarize.py`。Windows / 信封 / 翻页见 [`references/runtime.md`](references/runtime.md)。
31
29
 
32
- 1. **开请求目录**:`%TEMP%/sino-bc-strategy/<requestId>/`。维护 `excels_written`。
33
- 2. **拉数(一条命令,不要自己循环调几十次 CLI)**:
30
+ 1. **开请求目录**:`%TEMP%/sino-bc-strategy/<requestId>/`。**每次现拉**,不要用旧缓存跳过接口。回包边拉边写进该目录。
31
+ 2. **拉数**(一条命令,不要自己循环几十次 CLI):
34
32
  ```bash
35
- python3 scripts/fetch_data.py --request-dir <本次目录> --name "业务线名" \
36
- --curr-start 2026-09-08 --curr-end 2026-09-10 \
37
- --prev-start 2026-09-06 --prev-end 2026-09-08 \
38
- --account-fb act_xxx --account-gg 123-456 --account-tt 789
33
+ python3 scripts/fetch_data.py --request-dir <本次目录> --name "业务线名"
39
34
  ```
40
- 用户没给账号时脚本会先拉 `+list` / `+project-list`(失败则退回 `+detail.projectNames`),再按去重账号拉维度和成效。默认排序/条数仍按 [`api-flow.md`](references/api-flow.md)。多条业务线就按 ID 循环调用本脚本(`--id`),不要把多条混进一次请求目录。
41
- 3. **汇总成文案草稿**:`python3 scripts/summarize.py --request-dir <本次目录>`,得到 `payload.json`。Agent **必须打开 payload 看一眼**:枚举是否已译成中文、词包有没有整包粘贴、learning 有没有把上百条低消耗广告逐条列出。硬伤改 payload 后再导,不要从原始 JSON 重写一套聚合。
42
- 4. **导出 Excel**:`python3 scripts/build_report.py --input <本次目录>/payload.json`。固定 12 列,一个业务线一行;「本期/上期」紧跟业务线,两段日期写在同一单元格。把 `output_path` 记入 `excels_written`。
43
- 5. **收尾**:中途重导过则 `cleanup.py --keep-excel <最终> --written-excel ...`;然后 `cleanup.py --request-dir <本次目录>`。只汇报最终那一份 Excel。
35
+ 未传日期时脚本用 `+list` 的 `periodStart/periodEnd`、`prevPeriodStart/prevPeriodEnd`。`+list` 必须走 `--data`(脚本已封装),禁止 `+list --name`。多条业务线按 `--id` 分目录循环。
36
+ 3. **汇总**:`python3 scripts/summarize.py --request-dir <本次目录>`。打开 `payload.json`:主表文案是否只有总结、明细是否在 `details`、枚举是否中文、词包有没有整包粘贴。硬伤改 payload 再导。
37
+ 4. **导出**:`python3 scripts/build_report.py --input <本次目录>/payload.json`。桌面一份 xlsx,两张表。记下 `output_path`。
38
+ 5. **收尾**:中途重导过才 `cleanup.py --keep-excel <最终> --written-excel ...`。**Excel 生成后不要删 request 目录**,也不要 `cleanup.py --request-dir`。汇报时同时给 Excel 路径和本次 `request_dir`。
39
+
40
+ learning 见 [`references/metrics.md`](references/metrics.md);策略写法见 [`references/four-strategies.md`](references/four-strategies.md)。用户指定本期/上期就用用户的。
41
+
42
+ ## 主表 vs 明细
43
+
44
+ | 位置 | 写什么 |
45
+ | --- | --- |
46
+ | `策略` sheet(12 列,一业务线一行) | 总结:预算金额+占比、出价升/降数量与中位数、命名规律、国家>50 时只写区域聚合 |
47
+ | `明细` sheet | 国家全量、广告组名、出价逐条、兴趣词/关键词、广告名 |
48
+
49
+ 主表禁止堆全量广告组名或逐条出价。少于 10 条出价调整可以在主表列全,同时仍写入明细。
44
50
 
45
- learning 判定口径(双维度、关停、预算 Spend)见 [`references/metrics.md`](references/metrics.md);策略内容写法见 [`references/four-strategies.md`](references/four-strategies.md)。用户指定了本期/上期日期就用用户的,允许两端日期重叠。
51
+ ## 结论规则
46
52
 
47
- 时间范围(本期/上期)默认规则见 [`references/api-flow.md`](references/api-flow.md)「第 5 步」;用户明确给出本期、上期时间时改用用户指定值。
53
+ - **首期**(没有 `prevPeriodStart/End`):对客户 KPI;认不出或没填 → 待观察;展示当期全量兴趣词/关键词/国家,避免全 0。
54
+ - **第二期起**:结论只跟核心指标环比(优先 CPI=消耗/激活)。变好=有效,稳定=待观察,变差=待观察(预警)。KPI / 达标占比只写在「依据」,**不能**单独把结论打成无效。
55
+ - 不要用脚本旧默认 `CPM≤5` 当结论轴。
48
56
 
49
57
  ## 常见误区
50
58
 
51
- - 不要把接口不支持的衍生指标(CTR/CPC/CPI/ROAS 等)直接传给 `indicators`,必须先查基础指标再本地计算。
52
- - `country_code` 只能单独传或与 `calendar_date` 同传,和其他维度混传会报错;维度接口和成效接口都支持它,国家的成效数据能取到。传它时服务端转跨渠道接口,`headerMap` 列名会变,不要写死列名。
53
- - 判 learning 时不要用「整体数据」评价新增项:除预算策略外,其他三大策略只看**新增值自身**的投放数据;也不要用「全维度达标才算达标」或「一个达标就算达标」,按消耗加权算达标占比。
54
- - **达标占比不等于结论**:达标占比只是双维度判定里的「是否达标」这一个轴,必须再配上环比变化查表才能定结论。漏掉环比轴会把「未达标但趋势向好」误判成无效。
55
- - 环比方向不能只看数值涨跌:CPI 从 15 降到 12 是**变好**(成本类下降为好),ROAS 下降才是变差。上期无数据时环比记「无法对比」,此时达标=有效、未达标=待观察。
56
- - 维度合计 / 策略合计的衍生指标必须**先把基础指标求和、再算比率**,不能对各枚举值的比率取平均(会踩辛普森悖论)。
57
- - 本期只有关停没有新增时,不要退化成「看现有总投放是否达标」,要走关停口径(关停后存量 vs 关停前整体),见 [`references/metrics.md`](references/metrics.md)。
58
- - 出价调整次数不能用区间聚合值推断:出价每天一个快照,必须带 `calendar_date` + `adgroup_id` + `adgroup_name` 按天拉明细,再相邻两天比对计数;粒度是**广告组**,不要下钻到 `ad_id`(会重复计数)。次数按广告组各自统计并在文案里点名广告组,调整前后值取本期首日值和末日值。
59
- - 分析单位是业务线:把该业务线下所有项目的广告账号**按账号 ID 去重**后合并分析,不要按项目分别调接口再拼结果(会重复计消耗、次数翻倍)。
60
- - Excel 不输出「项目PM」列:现阶段没有确认好的项目 PM 取数来源(`biz-line-project-list` 不返回 PM 字段),不要为了补这一列去猜或用业务线级 `pmUserNames` 顶替。
61
- - 组别名称直接用 `ssoTeamCodeName`,本期/上期周期直接用 `periodStart/periodEnd`、`prevPeriodStart/prevPeriodEnd`,都不用自己反查或按自然周推算。
62
- - 未绑定项目/广告账号的业务线不要强行生成策略或编造数据。
63
- - 一个业务线下有多个项目时,Excel 仍是**一个业务线一行**;多个项目名称写在同一单元格内用 `\n` 分隔,不要按项目拆成多行。
64
- - 写文件路径优先用户电脑桌面(`~/Desktop`),不要写进本仓库或临时目录,除非用户另有指定。
65
- - 导出脚本在本 `SKILL.md` 同级的 `scripts/build_report.py`,不要用仓库相对路径 `skills/biz-line-strategy-generate/...`。
66
- - 文案用 `display_*` 或业务中文(如「应用推广」「成本上限」「自定义出价」「应用内事件」),不要把 `OUTCOME_SALES`、`COST_CAP`、`BID_TYPE_CUSTOM`、`IN_APP_EVENT` 原样写进 Excel;关键词/兴趣是**一个词包一个维度值**,只写「新增/关停 N 个」加最多 3 个短词,禁止把逗号分隔的整包贴进单元格。
67
- - learning 第 1 层只写**过了消耗门槛**(≥$50)的新增值;其余写成「另有 N 个新增消耗不足 $50,不纳入聚合」,不要把 200 条 $0 广告逐条列进单元格。
68
- - 无新增且无关停时:所有占比都在 ±2% 内才是「无实质动作」;占比变了超过 ±2% 要按**可环比**走双维度,不要写成无实质动作。
69
- - 不要手写维度/成效翻页。CLI 信封里维度/成效的行在 `data.data`、总数在 `data.total`;列表才是 `data.result`。认错字段会把 1800 行当成 0 行、只拿第 1 页。
70
- - PowerShell 不要手写 `--data '{"id":1}'`(引号常被吃掉);用 `scripts/fetch_data.py`,见 [`references/runtime.md`](references/runtime.md)。
71
- - 每个用户请求只保留**一份最终 Excel**。改文案请改 payload 后再导,并删掉本次 `excels_written` 里的上一份。禁止按桌面 `业务线策略生成_*.xlsx` glob 删除——会误删其他请求的文件。
72
- - 一条业务线的数据拉取和文案完成后,马上删它的原始回包临时文件;整次请求汇报完后删本次 `requestId` 目录。只删本次目录/本次清单,不要动别的 `requestId` 或桌面上非本次写出的 xlsx。
59
+ - 衍生指标不要传给 `indicators`,先查基础指标再本地算。
60
+ - `country_code` 只能单独传或与 `calendar_date` 同传;国家用**投放报告实际国家**,不用设置项。
61
+ - 除预算外,三大策略看新增值自身数据;0%↔0% 残留枚举不当关停。
62
+ - 环比方向:成本类下降为好。维度合计先求和再算比率。
63
+ - 出价按广告组 + `calendar_date` 相邻天比对;主表输出升/降数量、幅度分布/中位数,不要默认罗列。
64
+ - 分析单位是业务线,账号按 ID 去重。Excel 不输出项目 PM。
65
+ - 组别用 `ssoTeamCodeName`,周期用接口 period 字段。
66
+ - 未绑定项目/账号不要编造。多项目仍一行,名称 `\n` 分隔。
67
+ - 文案用中文/`display_*`;词包只写「新增/关停 N 个」+ 最多 3 短词。
68
+ - learning 第 1 层只写消耗 ≥$50 的新增。
69
+ - CLI 回包必须直写文件(`exec_cli_to_file`),`capture_output` 会在约 64KB 截断。翻页上限用脚本 `PAGE_CAP`(≥80),不要手写 30 页就停。
70
+ - 每个请求只留一份最终 Excel;禁止按桌面 `业务线策略生成_*.xlsx` glob 删除。
73
71
 
74
72
  ## References
75
73
 
76
- - [`references/api-flow.md`](references/api-flow.md):完整接口调用顺序、默认排序/分页/时间范围规则
77
- - [`references/four-strategies.md`](references/four-strategies.md):四大策略的字段映射、支持渠道、生成内容示例
78
- - [`references/metrics.md`](references/metrics.md):基础/衍生指标口径、核心指标识别、策略效果 learning 判定流程
79
- - [`references/output-excel.md`](references/output-excel.md):Excel 输出列规范与 `scripts/build_report.py` 用法
80
- - [`references/runtime.md`](references/runtime.md):信封解包、Windows 传参、本次请求目录、一次请求只留最终 Excel
81
- - `scripts/fetch_data.py` / `summarize.py` / `build_report.py` / `cleanup.py`:拉数、汇总草稿、导出、清理
74
+ - [`references/api-flow.md`](references/api-flow.md)
75
+ - [`references/four-strategies.md`](references/four-strategies.md)
76
+ - [`references/metrics.md`](references/metrics.md)
77
+ - [`references/output-excel.md`](references/output-excel.md)
78
+ - [`references/runtime.md`](references/runtime.md)
82
79
 
83
80
  ```bash
84
- # 脚本路径相对本 SKILL.md 所在目录。账号与业务线名只是文档示意,不要当真实请求。
85
- python3 scripts/fetch_data.py --request-dir "$TMPDIR/sino-bc-strategy/<requestId>" --name "示例业务线" \
86
- --curr-start 2026-09-08 --curr-end 2026-09-10 --prev-start 2026-09-06 --prev-end 2026-09-08 \
87
- --account-fb act_xxx --account-gg 123-456 --account-tt 789
81
+ python3 scripts/fetch_data.py --request-dir "$TMPDIR/sino-bc-strategy/<requestId>" --name "示例业务线"
88
82
  python3 scripts/summarize.py --request-dir "$TMPDIR/sino-bc-strategy/<requestId>"
89
83
  python3 scripts/build_report.py --input "$TMPDIR/sino-bc-strategy/<requestId>/payload.json"
90
- python3 scripts/cleanup.py --keep-excel <最终xlsx> --written-excel <本次xlsx...>
91
- python3 scripts/cleanup.py --request-dir "$TMPDIR/sino-bc-strategy/<requestId>"
84
+ # 默认不要:python3 scripts/cleanup.py --request-dir ...
92
85
  ```
@@ -6,6 +6,7 @@
6
6
 
7
7
  ```bash
8
8
  sino-bc-cli biz-line api biz-line-list --data '{"pageNum":1,"pageSize":10,"sortType":"PERIOD","sortDirection":"DESC"}'
9
+ # 按名称:同样走 --data(scripts/list_query_body),不要 +list --name
9
10
  ```
10
11
 
11
12
  - **默认排序**:用户未指定时,按「本期策略周期倒序」,即 `sortType: "PERIOD"`、`sortDirection: "DESC"`。
@@ -93,7 +94,7 @@ sino-bc-cli biz-line api biz-line-project-list --data '{"bizLineId": 1251}'
93
94
 
94
95
  ## 分页与数据量
95
96
 
96
- 维度/成效接口默认每页 10 条(`size`),建议显式传更大的 `size`(不超过 1000)覆盖账号下全部广告,按 `result.total` 判断是否需要翻页;广告量大时优先加大 `dimensions` 粒度或分批翻页,不要漏页导致漏算占比。
97
+ 维度/成效接口默认每页 10 条(`size`),脚本显式 `size=1000`,按 `data.total` 翻页,上限 `PAGE_CAP=80`(TikTok 出价按天可到 4~5 万行)。回包直写文件。每次现拉,不要跳过已有页;Excel 落盘后保留 request 目录。
97
98
 
98
99
  ## 汇总到 Excel 前的数据整理
99
100
 
@@ -15,8 +15,8 @@
15
15
 
16
16
  | 字段 | 数据逻辑 | 生成内容示例 | 支持渠道 |
17
17
  | --- | --- | --- | --- |
18
- | 调整出价 | 出价字段是**每天一个快照**,按**广告组**粒度按天拉取后相邻两天对比来数调整次数,详见下方「出价调整次数怎么算」。Meta 用 `bid_amount`;Google 用 `cpc_bid_amount`/`cpm_bid_amount`/`cpv_bid_amount`(任一变化即算一次变化);TikTok 用 `bid_amount`/`conversion_bid`/`deep_cpabid`(任一变化即算一次变化)。出价方式取 `bid_strategy` | 【调整出价】Meta 出价方式是 xxx,广告组 A 调整了 3 次,从 1.5 调整到 2.2;广告组 B 调整了 1 次,从 0.8 调整到 0.9;GG 本周未发生明显出价调整。 | Meta、Google |
19
- | Spend(预算) | 按渠道汇总 `spend_usd` 绝对值与占比,对比本周 vs 上周。占比变化在 ±2% 内视为「无变化」,否则输出「渠道:变化 x%」 | 【预算】换行后每渠道一行:`FB:50.0% → 55.0%(+5.0%)` | Meta、Google、TikTok、Twitter、ASA |
18
+ | 调整出价 | 按广告组按天比对(见下)。主表只写**上调/下调广告组数量、幅度中位数**;条目 <10 才在主表列全。逐条进 `明细` sheet。 | 【调整出价】Meta:上调 3 个、下调 2 个;幅度中位数 0.40 | Meta、Google、TikTok |
19
+ | Spend(预算) | 每个渠道必须同时写**金额绝对值 + 占比**(`budget_abs_share`)。占比升但金额降(或相反)要在主表标 ⚠️ | 【预算】`FB:$10,000 → $8,000(-2,000);8.8% → 11.6%(+2.8%);⚠️占比升但金额降` | Meta、Google、TikTok、Twitter、ASA |
20
20
 
21
21
  ### 出价调整次数怎么算
22
22
 
@@ -25,7 +25,7 @@
25
25
  1. **按天 + 按广告组粒度拉数据**:`dimensions` 里必须同时包含 `calendar_date`、`adgroup_id`、`adgroup_name`(名称要写进文案,必须一起取),以及该渠道的出价字段。少了 `calendar_date` 就只能拿到区间聚合值,数不出次数;少了 `adgroup_id` 会把不同广告组的出价混成一行,得出的「变化」是伪变化。不要下钻到 `ad_id`,那会把同一个广告组的一次出价变更重复计成多次。
26
26
  2. **按广告组分组、按日期升序排列**:同一个 `adgroup_id` 的记录按 `calendar_date` 从早到晚排好。
27
27
  3. **相邻两天两两对比**:同一广告组第 N 天与第 N-1 天的出价值不同 → 该广告组计 1 次出价调整。多个出价字段的渠道(Google 的 CPC/CPM/CPV,TikTok 的 bid_amount/conversion_bid/deep_cpabid)中,**同一天内任一字段发生变化就整体算 1 次**,不要按字段分别累加成多次。
28
- 4. **次数按广告组各自统计,不跨广告组相加**:文案里逐个广告组说明「广告组 X 调整了 N 次」,让读者知道是哪个广告组调了几次。同一渠道下有多个广告组发生调整时,逐个列出。
28
+ 4. **主表统计、明细列全**:主表用 `bid_adjust_stats` 写上调/下调数量与幅度中位数;<10 条才在主表列全。全部逐条写入 `payload.details`(kind=出价)。不要默认在主表点名几十个广告组。
29
29
  5. **调整前后值取本期首末值**:前值 = 该广告组**本期第一天**的出价,后值 = **本期最后一天**的出价,写成「从 首日值 调整到 末日值」。不要把中间每一次的跳变都串起来罗列。
30
30
  6. **边界处理**:
31
31
  - 某天缺快照(广告组当天未投放)→ 跳过该天,用其前后两个**有值**的相邻快照继续比对,不要把「无值」当成一次变化。首末值同理取区间内第一个/最后一个**有值**的快照。
@@ -62,20 +62,20 @@ Facebook 维度接口请求示例(本期按天 + 按广告组拉出价):
62
62
 
63
63
  | 字段 | 数据逻辑 | 生成内容示例 | 支持渠道 |
64
64
  | --- | --- | --- | --- |
65
- | 国家(`country_code`,须单独请求,只能与 `calendar_date` 同传) | 查投放设置上的国家,总结新增/关停。成效接口也支持该维度,可按国家取成效用于 learning | 【国家】新增 1 个国家:xxx;关停 1 个国家:xxxx,投放区域有小幅调整。 | Meta、Google、TikTok |
65
+ | 国家(`country_code`,须单独请求,只能与 `calendar_date` 同传) | 用**投放报告实际国家**,不用设置项。主表写新增/关停数量+示例;>50 个只写区域聚合,全量进明细。首期展示当期全量 | 【国家】新增 1 个:xxx | Meta、Google、TikTok |
66
66
  | 兴趣词包(`targeting_interests` / `targeting_flexible_interests` / `targeting_exclusion_interests`) | 查投放设置上的兴趣词包,总结新增/关停 | 【兴趣词包】新增 2 个兴趣词包:短剧、线上娱乐;未发现关停,受众覆盖范围扩大。 | Meta、TikTok(Google 不支持) |
67
67
  | 受众类型(`audience_type`) | 查受众类型消耗数据(需加工),总结占比变化 | 【受众类型】无变化。详细:通用 50%>>55%,类似受众 40%>>35%,自定义受众 10%>>10% | Meta、Google、TikTok |
68
68
  | 关键词(`keywords`) | 查投放设置上的关键词,总结新增/关停 | 【关键词】新增 3 个关键词:xxxx,关停 1 个低效关键词:xxxx | Google(Meta、TikTok 不支持) |
69
69
  | 关键词分类(需在关键词基础上加工分类,无独立接口字段) | 查投放设置上的关键词并加工分类,总结新增/关停 | 【关键词分类】新增通用词 2 个:xxx、竞品词 1 个:xxx,品牌词无明显变化 | ASA |
70
70
  | 年龄(`targeting_age`) | 查投放设置上的年龄,总结新增/关停 | 【年龄】年龄定向无明显变化,仍以 18-44 岁人群为主。 | Meta、Google、TikTok |
71
71
  | 性别(`targeting_genders`) | 查投放设置上的性别,总结新增/关停 | 【性别】性别定向无明显变化,仍以女性人群为主。 | Meta、Google、TikTok |
72
- | 广告组(`adgroup_id` / `adgroup_name`) | 查广告组投放数据,总结新增/关停 | 【广告组】新增 4 个广告组:xxxx,关停 2 个低消耗广告组:xxxxx | Meta、Google、TikTok |
72
+ | 广告组(`adgroup_id` / `adgroup_name`) | 主表只写新增/关停数量 + `naming_patterns` 命名规律;全量名称进明细 | 【广告组】Meta:新增 17 个,关停 26 个;命名规律:KPOP、两性 | Meta、Google、TikTok |
73
73
 
74
74
  ## 素材策略
75
75
 
76
76
  | 字段 | 数据逻辑 | 生成内容示例 | 支持渠道 |
77
77
  | --- | --- | --- | --- |
78
- | Ad / Ad name(`ad_id` / `ad_name`) | 查 Ad 的投放报告,总结本周 Ad 新增/关停情况,并根据 `ad_name` 提取关键词,得出图片/视频素材的消耗占比变化 | 【广告】Meta:新增 2 个广告,素材集中在「剧情反转」方向;关停 2 个,素材为「内测人群」方向。TT:新增 1 个广告,素材集中在「强冲突开头」方向。 | Meta、Google、TikTok |
78
+ | Ad / Ad name(`ad_id` / `ad_name`) | 主表写数量 + 命名规律(与广告组同一套 `naming_patterns`);空名称不要写成方向「(空)」。全量进明细 | 【广告】Meta:新增 72 个,关停 121 个;命名规律:旅行、两性 | Meta、Google、TikTok |
79
79
  | 创意类型(`ad_format`) | 查创意类型(图片/视频等)的消耗占比变化 | 【创意类型】整体更偏向视频素材放量。图片 30%>>25%,视频 70%>>75%。 | Meta、Google、TikTok |
80
80
 
81
81
  ## 三类调整对象的对比口径(判定策略是否发生变化时使用)
@@ -92,29 +92,27 @@
92
92
  | 新增 | 本周新增,上周没有(如新国家、新广告、新素材) | 本周新增部分 vs 本周整体 |
93
93
  | 关停 | 上周有,本周已关停(如下架广告、关停国家) | 关停后剩余存量整体表现 vs 关停前整体表现 |
94
94
 
95
- 预算策略始终走「可环比」逻辑(各媒体总数据 + 渠道占比变化);其他三大策略以「新增」为主,新增值的 KPI 达标情况是判定主依据。
95
+ 预算策略始终走「可环比」逻辑(各媒体总数据 + 渠道占比变化);其他三大策略以「新增」为主写内容。新增值 KPI / 达标占比只作 learning 依据,不决定第二期总结论。
96
96
 
97
- ### 第二步:判定结论(KPI 为主 + 环比为辅,双维度)
97
+ ### 第二步:判定结论(2026-09-17 会议:第二期跟环比,KPI 只作依据)
98
98
 
99
- **本表的作用范围**(重要):
99
+ 实现函数:`cli_json.period_verdict`。旧表 `dual_verdict` 仅留作对照,**不要**再拿「达标占比≥70%」把总结论打成无效。
100
100
 
101
- - ✅ 四大策略各自的**总 learning 结论**(预算策略、广告产品策略、受众策略、素材策略,各查一次表)
102
- - ✅ 预算策略里 **Spend 子项的次级 learning 结论**(Spend 子项也要同时看达标与环比,单独查一次表)
103
- - ❌ 不作用于第 1 层(每个新增枚举值)和第 2 层(维度合计)——那两层只回答「是否达标 KPI」,作为分项证据
101
+ | 阶段 | 结论怎么定 | KPI 怎么用 |
102
+ | --- | --- | --- |
103
+ | **首期**(无 `prevPeriodStart/End`) | 能识别客户 KPI 则按是否达标:达标=有效,否则待观察;认不出/没填=待观察 | 结论轴 |
104
+ | **第二期起** | 核心指标环比(优先 CPI=消耗/激活,成本类下降为好):变好=有效,稳定=待观察,变差=待观察(预警) | 只写在「依据」,不单独改结论 |
105
+
106
+ 第 1 / 2 层仍可写分项是否过消耗门槛、CPI 对照,作为备查,不决定四大策略总结论。
104
107
 
105
- | 是否达标 KPI | 环比变化 | 结论 | 说明 |
106
- | --- | --- | --- | --- |
107
- | ✅ 达标 | 变好或稳定 | 有效 | 达标且没变差 |
108
- | ✅ 达标 | 变差(>10%) | 待观察(预警) | 达标但下滑明显,再观察一周 |
109
- | ❌ 未达标 | 变好(>10%) | 待观察(潜力) | 未达标但趋势向好,再给一周机会 |
110
- | ❌ 未达标 | 稳定或变差 | 无效 | 未达标且没改善,需关停 |
108
+ ~~旧双轴表(KPI × 环比 → 无效)已停用。~~
111
109
 
112
- **两个轴分别怎么算**:
110
+ **依据里可以写的两个轴**(第二期不拿「达标」轴改结论):
113
111
 
114
- | 轴 | 预算策略 Spend 子项 / 预算策略总结论 | 其他三大策略总结论 |
112
+ | 轴 | 预算 Spend | 其他三大策略 |
115
113
  | --- | --- | --- |
116
- | 是否达标 KPI | 全渠道加总后的核心指标 vs KPI 目标值 | 第 3 层消耗加权达标占比 ≥70% 即「达标」 |
117
- | 环比变化 | 全渠道加总核心指标:本期 vs 上期 | 业务线级核心指标:本期 vs 上期(同一口径下整体对比) |
114
+ | 核心指标 | 全渠道 CPI(消耗/激活),没有激活再退回 CPM | 过门槛新增的消耗加权达标占比(仅备查) |
115
+ | 环比变化 | 全渠道核心指标:本期 vs 上期(**第二期结论轴**) | 同上,业务线级同一口径 |
118
116
 
119
117
  **环比变化的判定**:`变化幅度 = (本期值 - 上期值) / 上期值`,绝对值 ≤10% 记「稳定」,>10% 按方向记「变好」或「变差」。
120
118
 
@@ -1,6 +1,6 @@
1
1
  # 输出 Excel
2
2
 
3
- 生成结果的落盘环节,用本 skill 附带脚本 `scripts/build_report.py`(纯 Python 标准库,无第三方依赖,不需要 `pip install`)。脚本只做「结构化数据 → `.xlsx`」的机械转换,不做任何数据拉取或文案生成——那些在此之前由 Agent 按 [`api-flow.md`](api-flow.md)、[`four-strategies.md`](four-strategies.md)、[`metrics.md`](metrics.md) 完成。
3
+ `scripts/build_report.py` 只做「结构化数据 → `.xlsx`」。一份文件两张表:**`策略`**(摘要,对应会议主页面)和 **`明细`**(对应二级页)。
4
4
 
5
5
  ## 输出列(固定 12 列,顺序不可改)
6
6
 
@@ -38,11 +38,16 @@ python3 scripts/summarize.py --request-dir <本次 requestId 目录>
38
38
  python3 scripts/build_report.py --input <本次目录>/payload.json
39
39
  ```
40
40
 
41
+ `payload.details` 写入 `明细` sheet,列固定为:业务线、明细类型、动作、渠道、名称、补充。
42
+
41
43
  payload 结构:
42
44
 
43
45
  ```json
44
46
  {
45
47
  "output_path": "可选,未传时默认写到用户桌面 业务线策略生成_YYYYMMDD_HHMMSS.xlsx",
48
+ "details": [
49
+ {"bizLine": "示例", "kind": "国家", "action": "新增", "channel": "TikTok", "name": "西撒哈拉", "extra": "消耗 $0"}
50
+ ],
46
51
  "rows": [
47
52
  {
48
53
  "group": "华东组",
@@ -77,7 +82,7 @@ payload 结构:
77
82
  3. 若必须重导:新文件成功后再删清单里的旧路径,可用 `scripts/cleanup.py --keep-excel <最终> --written-excel <旧1> --written-excel <旧2>`。
78
83
  4. **不要**用桌面 glob(`*策略生成*.xlsx`)再「只留最新」;未出现在本次 `excels_written` 里的文件一律当别人的,不准删。
79
84
 
80
- 汇报给用户时只给最终路径。临时 JSON 的清理见 [`runtime.md`](runtime.md)。
85
+ 汇报给用户时给最终 Excel 路径,并附带本次 request 目录。原始 JSON 默认保留,见 [`runtime.md`](runtime.md)。
81
86
 
82
87
  ## 返回值与校验
83
88
 
@@ -5,8 +5,10 @@
5
5
  ## 调用 CLI
6
6
 
7
7
  - 输出用 `--format json`,**不要**传 `--json`(会 `validation`:未知参数)。
8
- - `--data` 必须是合法 JSON 对象字符串。PowerShell 把 JSON 写进命令行时经常把引号吃掉,变成非法 JSON。
8
+ - `--data` 必须是合法 JSON 对象字符串。`+list` 的名称/ID 也放进 `--data`(`list_query_body`),禁止 `+list --name` / `--id`。
9
+ - CLI 回包用 `exec_cli_to_file` **直写文件**,不要 `capture_output=True`(沙箱里约 64KB 截断,出价/广告组明细会变成空页)。
9
10
  - **推荐直接跑 `scripts/fetch_data.py`**(内部用参数数组调 CLI,`shell=False`)。不要在 PowerShell 里手写 `--data '{"id":1}'`,也不要为了躲引号去写死本机 `dist/index.js` 路径。
11
+ - **每次现拉**,不要拿上次 request 目录当命中缓存跳过接口。拉到的页立刻落盘。Excel 生成后**默认保留**整个 request 目录,不要 `cleanup.py --request-dir`。
10
12
  - 成功看信封 `ok == true` 或退出码 0。
11
13
  - 回包必须写 UTF-8 文件再解析。合并文件是 JSON **数组**(以 `[` 开头),`json.loads` 要从第一个 `[` 或 `{` 里更靠前的那个开始,不能先 `find("{")`——否则会把第一行对象当全文,解析失败后当成空数据。
12
14
 
@@ -38,19 +40,17 @@
38
40
 
39
41
  ## 按业务线清理临时文件
40
42
 
41
- 对**一条**业务线:接口全部拉完 → 四大策略文案和 learning 已写入内存/payload 行 → **立即删除**该业务线的 `bl-<bizLineId>/`(含 `.body.json` / `.out.json` 等原始回包)。
42
-
43
- 不要在文案还没生成时删原始数据。跳过(未绑定项目/账号)的业务线若没有建目录,则不用删。
43
+ 对**一条**业务线:接口全部拉完 → 文案写入 `payload.json` → **保留** `bl-<bizLineId>/` 原始回包,不要删。跳过(未绑定项目/账号)的业务线若没有建目录,则不用处理。
44
44
 
45
45
  ## 整次请求结束时清理
46
46
 
47
- Excel 成功落盘、已向用户汇报最终路径之后:
47
+ Excel 成功落盘之后:
48
48
 
49
- 1. 按下面规则处理 Excel(只动本次清单)。
50
- 2. 删除整个 `%TEMP%/sino-bc-strategy/<requestId>/`(含 `payload.json`、`manifest.json`、残留子目录)。
51
- 3. 不要删 `%TEMP%/sino-bc-strategy/` 下**其他** `requestId` 目录。
49
+ 1. 按下面规则处理 Excel(只动本次清单的旧 xlsx)。
50
+ 2. **保留**整个 `%TEMP%/sino-bc-strategy/<requestId>/`(原始回包 + `payload.json`),汇报时带上该路径。
51
+ 3. 只有用户明确要求清缓存时才 `cleanup.py --request-dir`。不要删其他 `requestId`。
52
52
 
53
- 中途失败也要尽量删掉本次 `requestId` 目录,避免和下次请求的缓存混在一起。
53
+ 中途失败也保留本次 `requestId` 目录,便于对照回包排障;不要和下次请求共用同一目录。新请求必须新建 `requestId`。
54
54
 
55
55
  可用本技能脚本:
56
56
 
@@ -69,4 +69,4 @@ python3 scripts/cleanup.py --keep-excel <最终xlsx> --written-excel <本次写
69
69
  - **禁止**:`Remove-Item -Force` 扫整个桌面时间戳范围。
70
70
  - 其他请求、用户自己放在桌面的 xlsx,一律不动。
71
71
 
72
- 向用户汇报时只给**最终**那一份路径。
72
+ 向用户汇报最终 Excel 路径,并附带本次 `request_dir`。
@@ -13,16 +13,15 @@
13
13
  payload.json 结构:
14
14
  {
15
15
  "output_path": "可选,未传时默认写到桌面 业务线策略生成_YYYYMMDD_HHMMSS.xlsx",
16
- "rows": [
16
+ "rows": [ { "...策略摘要行..." } ],
17
+ "details": [
17
18
  {
18
- "group": "组别",
19
- "bizLine": "业务线名称",
20
- "period": "本期:2026-09-08~2026-09-10\n上期:2026-09-06~2026-09-08",
21
- "project": "项目A\n项目B",
22
- "budgetStrategy": "预算策略内容", "budgetLearning": "预算策略learning",
23
- "productStrategy": "广告产品策略内容", "productLearning": "广告产品策略learning",
24
- "audienceStrategy": "受众策略内容", "audienceLearning": "受众策略learning",
25
- "creativeStrategy": "素材策略内容", "creativeLearning": "素材策略learning"
19
+ "bizLine": "业务线",
20
+ "kind": "国家|广告组|出价|兴趣词|关键词|广告",
21
+ "action": "新增|关停|上调|下调|当期",
22
+ "channel": "Meta|Google|TikTok",
23
+ "name": "名称",
24
+ "extra": "补充"
26
25
  }
27
26
  ]
28
27
  }
@@ -58,6 +57,15 @@ COLUMNS = [
58
57
  ]
59
58
 
60
59
  # 策略内容列文字量最大,给最宽的列宽(前 4 列是基础信息列)
60
+ DETAIL_COLUMNS = [
61
+ ("bizLine", "业务线"),
62
+ ("kind", "明细类型"),
63
+ ("action", "动作"),
64
+ ("channel", "渠道"),
65
+ ("name", "名称"),
66
+ ("extra", "补充"),
67
+ ]
68
+
61
69
  CONTENT_KEYS = {key for key, _label in COLUMNS[4:]}
62
70
 
63
71
  # 列宽:策略内容列最宽;项目名称列因单元格内多行罗列项目而适度加宽
@@ -72,6 +80,7 @@ CONTENT_TYPES_XML = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
72
80
  <Default Extension="xml" ContentType="application/xml"/>
73
81
  <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
74
82
  <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
83
+ <Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
75
84
  <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
76
85
  <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
77
86
  </Types>"""
@@ -86,11 +95,15 @@ WORKBOOK_RELS_XML = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
86
95
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
87
96
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
88
97
  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
98
+ <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet2.xml"/>
89
99
  </Relationships>"""
90
100
 
91
101
  WORKBOOK_XML = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
92
102
  <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
93
- <sheets><sheet name="业务线策略生成" sheetId="1" r:id="rId1"/></sheets>
103
+ <sheets>
104
+ <sheet name="策略" sheetId="1" r:id="rId1"/>
105
+ <sheet name="明细" sheetId="2" r:id="rId4"/>
106
+ </sheets>
94
107
  </workbook>"""
95
108
 
96
109
  # cellXfs: 0=默认, 1=表头(粗体+浅底), 2=内容(自动换行+顶对齐)
@@ -129,8 +142,7 @@ def col_letter(index: int) -> str:
129
142
  return letters
130
143
 
131
144
 
132
- def build_shared_strings(rows):
133
- """收集全部字符串去重,返回 (sst_xml, header_indices, row_indices)"""
145
+ def _intern_factory():
134
146
  seen = {}
135
147
  order = []
136
148
 
@@ -140,11 +152,21 @@ def build_shared_strings(rows):
140
152
  order.append(value)
141
153
  return seen[value]
142
154
 
155
+ return intern, order
156
+
157
+
158
+ def build_shared_strings(rows, details=None):
159
+ """收集策略表 + 明细表全部字符串去重。"""
160
+ intern, order = _intern_factory()
143
161
  header_indices = [intern(label) for _key, label in COLUMNS]
144
- row_indices = []
145
- for row in rows:
146
- row_indices.append(
147
- [intern(str(row.get(key, "") or "")) for key, _label in COLUMNS]
162
+ row_indices = [
163
+ [intern(str(row.get(key, "") or "")) for key, _label in COLUMNS] for row in rows
164
+ ]
165
+ detail_header = [intern(label) for _key, label in DETAIL_COLUMNS]
166
+ detail_rows = []
167
+ for row in details or []:
168
+ detail_rows.append(
169
+ [intern(str(row.get(key, "") or "")) for key, _label in DETAIL_COLUMNS]
148
170
  )
149
171
 
150
172
  items = "".join(
@@ -155,19 +177,21 @@ def build_shared_strings(rows):
155
177
  f'<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '
156
178
  f'count="{len(order)}" uniqueCount="{len(order)}">{items}</sst>'
157
179
  )
158
- return sst_xml, header_indices, row_indices
180
+ return sst_xml, header_indices, row_indices, detail_header, detail_rows
159
181
 
160
182
 
161
- def build_sheet_xml(header_indices, row_indices):
162
- """生成 sheet1.xml:首行表头样式 s="1",其余行内容样式 s="2"(自动换行)"""
163
- col_count = len(COLUMNS)
183
+ def build_sheet_xml(header_indices, row_indices, columns, content_keys=None, multiline_keys=None):
184
+ """生成 worksheet xml:首行表头样式 s="1",其余行内容样式 s="2"(自动换行)"""
185
+ content_keys = content_keys or set()
186
+ multiline_keys = multiline_keys or set()
187
+ col_count = len(columns)
164
188
  dimension_ref = f"A1:{col_letter(col_count - 1)}{len(row_indices) + 1}"
165
189
 
166
190
  cols_xml_parts = []
167
- for i, (key, _label) in enumerate(COLUMNS):
168
- if key in CONTENT_KEYS:
191
+ for i, (key, _label) in enumerate(columns):
192
+ if key in content_keys:
169
193
  width = COL_WIDTH_CONTENT
170
- elif key in MULTILINE_KEYS:
194
+ elif key in multiline_keys:
171
195
  width = COL_WIDTH_MULTILINE
172
196
  else:
173
197
  width = COL_WIDTH_DEFAULT
@@ -212,9 +236,24 @@ def default_output_path() -> str:
212
236
  return os.path.join(desktop, f"业务线策略生成_{stamp}.xlsx")
213
237
 
214
238
 
215
- def write_xlsx(rows, output_path: str) -> str:
216
- sst_xml, header_indices, row_indices = build_shared_strings(rows)
217
- sheet_xml = build_sheet_xml(header_indices, row_indices)
239
+ def write_xlsx(rows, output_path: str, details=None) -> str:
240
+ sst_xml, header_indices, row_indices, detail_header, detail_rows = build_shared_strings(
241
+ rows, details
242
+ )
243
+ sheet_xml = build_sheet_xml(
244
+ header_indices,
245
+ row_indices,
246
+ COLUMNS,
247
+ content_keys=CONTENT_KEYS,
248
+ multiline_keys=MULTILINE_KEYS,
249
+ )
250
+ detail_xml = build_sheet_xml(
251
+ detail_header,
252
+ detail_rows,
253
+ DETAIL_COLUMNS,
254
+ content_keys={"name", "extra"},
255
+ multiline_keys=set(),
256
+ )
218
257
 
219
258
  out_dir = os.path.dirname(output_path)
220
259
  if out_dir and not os.path.isdir(out_dir):
@@ -228,6 +267,7 @@ def write_xlsx(rows, output_path: str) -> str:
228
267
  zf.writestr("xl/styles.xml", STYLES_XML)
229
268
  zf.writestr("xl/sharedStrings.xml", sst_xml)
230
269
  zf.writestr("xl/worksheets/sheet1.xml", sheet_xml)
270
+ zf.writestr("xl/worksheets/sheet2.xml", detail_xml)
231
271
 
232
272
  return output_path
233
273
 
@@ -258,12 +298,20 @@ def main() -> int:
258
298
 
259
299
  output_path = args.output or payload.get("output_path") or default_output_path()
260
300
  output_path = os.path.abspath(output_path)
301
+ details = payload.get("details") or []
302
+ if not isinstance(details, list):
303
+ details = []
261
304
 
262
- write_xlsx(rows, output_path)
305
+ write_xlsx(rows, output_path, details=details)
263
306
 
264
307
  print(
265
308
  json.dumps(
266
- {"ok": True, "output_path": output_path, "row_count": len(rows)},
309
+ {
310
+ "ok": True,
311
+ "output_path": output_path,
312
+ "row_count": len(rows),
313
+ "detail_count": len(details),
314
+ },
267
315
  ensure_ascii=False,
268
316
  )
269
317
  )
@@ -2,6 +2,8 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  """只清理「本次请求」登记过的临时目录和 Excel。
4
4
 
5
+ 默认生成流程:Excel 落盘后不要删 request 目录(现拉回包要留着备查)。
6
+ 只有用户明确要求清缓存时才传 --request-dir。
5
7
  不会按桌面文件名 glob,也不会删除未出现在 --written-excel 里的文件。
6
8
  """
7
9
  from __future__ import annotations