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.
- package/dist/index.js +1 -1
- package/dist/skills/custom/biz-line-strategy-generate/SKILL.md +49 -56
- package/dist/skills/custom/biz-line-strategy-generate/references/api-flow.md +2 -1
- package/dist/skills/custom/biz-line-strategy-generate/references/four-strategies.md +6 -6
- package/dist/skills/custom/biz-line-strategy-generate/references/metrics.md +14 -16
- package/dist/skills/custom/biz-line-strategy-generate/references/output-excel.md +7 -2
- package/dist/skills/custom/biz-line-strategy-generate/references/runtime.md +10 -10
- package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/build_report.cpython-314.pyc +0 -0
- package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/cli_json.cpython-314.pyc +0 -0
- package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/fetch_data.cpython-314.pyc +0 -0
- package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/summarize.cpython-314.pyc +0 -0
- package/dist/skills/custom/biz-line-strategy-generate/scripts/build_report.py +76 -28
- package/dist/skills/custom/biz-line-strategy-generate/scripts/cleanup.py +2 -0
- package/dist/skills/custom/biz-line-strategy-generate/scripts/cli_json.py +177 -13
- package/dist/skills/custom/biz-line-strategy-generate/scripts/fetch_data.py +27 -18
- package/dist/skills/custom/biz-line-strategy-generate/scripts/summarize.py +185 -118
- package/dist/skills/custom/biz-line-strategy-generate/scripts/test_strategy_rules.py +201 -0
- package/package.json +1 -1
|
@@ -10,83 +10,76 @@ metadata:
|
|
|
10
10
|
|
|
11
11
|
# 业务线策略生成
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
按业务线自动生成四大策略与 learning,导出一份 Excel(`策略` + `明细` 两张表)。编排多次 `biz-line` 调用,不是单条 Shortcut。
|
|
14
14
|
|
|
15
|
-
**开始前 MUST
|
|
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
|
-
-
|
|
20
|
-
-
|
|
21
|
-
- 「帮我生成第一页业务线的策略」→ 第 1 页,默认页大小
|
|
22
|
-
- 「把前 10 个业务线生成策略」→ 前 10 条
|
|
23
|
-
- 「给名称为 xxx 的业务线生成策略」→ 按名称筛选
|
|
24
|
-
- 「给业务线 A 生成策略,用广告账号 act_123(Facebook)」→ 用户指定账号覆盖接口返回的账号
|
|
21
|
+
- 「生成业务线策略 / 投放策略 / 策略 learning」并要产出文件
|
|
22
|
+
- 「第一页 / 前 N 个 / 名称为 xxx」;用户指定账号则覆盖接口账号
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
不要用本技能处理:只查列表/详情/项目(走 `biz-line +list` 等)。
|
|
27
25
|
|
|
28
26
|
## Agent 流程
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
**拉数和聚合不要手写**:用本目录 `scripts/fetch_data.py`、`scripts/summarize.py`。Windows / 信封 / 翻页见 [`references/runtime.md`](references/runtime.md)。
|
|
31
29
|
|
|
32
|
-
1. **开请求目录**:`%TEMP%/sino-bc-strategy/<requestId
|
|
33
|
-
2.
|
|
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
|
-
|
|
41
|
-
3.
|
|
42
|
-
4.
|
|
43
|
-
5.
|
|
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
|
-
|
|
51
|
+
## 结论规则
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
- **首期**(没有 `prevPeriodStart/End`):对客户 KPI;认不出或没填 → 待观察;展示当期全量兴趣词/关键词/国家,避免全 0。
|
|
54
|
+
- **第二期起**:结论只跟核心指标环比(优先 CPI=消耗/激活)。变好=有效,稳定=待观察,变差=待观察(预警)。KPI / 达标占比只写在「依据」,**不能**单独把结论打成无效。
|
|
55
|
+
- 不要用脚本旧默认 `CPM≤5` 当结论轴。
|
|
48
56
|
|
|
49
57
|
## 常见误区
|
|
50
58
|
|
|
51
|
-
-
|
|
52
|
-
- `country_code` 只能单独传或与 `calendar_date`
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
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)
|
|
79
|
-
- [`references/output-excel.md`](references/output-excel.md)
|
|
80
|
-
- [`references/runtime.md`](references/runtime.md)
|
|
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
|
-
|
|
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 --
|
|
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
|
|
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
|
-
| 调整出价 |
|
|
19
|
-
| Spend(预算) |
|
|
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.
|
|
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` 同传) |
|
|
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`) |
|
|
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`) |
|
|
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
|
-
预算策略始终走「可环比」逻辑(各媒体总数据 +
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
| 阶段 | 结论怎么定 | KPI 怎么用 |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| **首期**(无 `prevPeriodStart/End`) | 能识别客户 KPI 则按是否达标:达标=有效,否则待观察;认不出/没填=待观察 | 结论轴 |
|
|
104
|
+
| **第二期起** | 核心指标环比(优先 CPI=消耗/激活,成本类下降为好):变好=有效,稳定=待观察,变差=待观察(预警) | 只写在「依据」,不单独改结论 |
|
|
105
|
+
|
|
106
|
+
第 1 / 2 层仍可写分项是否过消耗门槛、CPI 对照,作为备查,不决定四大策略总结论。
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
| --- | --- | --- | --- |
|
|
107
|
-
| ✅ 达标 | 变好或稳定 | 有效 | 达标且没变差 |
|
|
108
|
-
| ✅ 达标 | 变差(>10%) | 待观察(预警) | 达标但下滑明显,再观察一周 |
|
|
109
|
-
| ❌ 未达标 | 变好(>10%) | 待观察(潜力) | 未达标但趋势向好,再给一周机会 |
|
|
110
|
-
| ❌ 未达标 | 稳定或变差 | 无效 | 未达标且没改善,需关停 |
|
|
108
|
+
~~旧双轴表(KPI × 环比 → 无效)已停用。~~
|
|
111
109
|
|
|
112
|
-
|
|
110
|
+
**依据里可以写的两个轴**(第二期不拿「达标」轴改结论):
|
|
113
111
|
|
|
114
|
-
| 轴 |
|
|
112
|
+
| 轴 | 预算 Spend | 其他三大策略 |
|
|
115
113
|
| --- | --- | --- |
|
|
116
|
-
|
|
|
117
|
-
| 环比变化 |
|
|
114
|
+
| 核心指标 | 全渠道 CPI(消耗/激活),没有激活再退回 CPM | 过门槛新增的消耗加权达标占比(仅备查) |
|
|
115
|
+
| 环比变化 | 全渠道核心指标:本期 vs 上期(**第二期结论轴**) | 同上,业务线级同一口径 |
|
|
118
116
|
|
|
119
117
|
**环比变化的判定**:`变化幅度 = (本期值 - 上期值) / 上期值`,绝对值 ≤10% 记「稳定」,>10% 按方向记「变好」或「变差」。
|
|
120
118
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 输出 Excel
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
对**一条**业务线:接口全部拉完 →
|
|
42
|
-
|
|
43
|
-
不要在文案还没生成时删原始数据。跳过(未绑定项目/账号)的业务线若没有建目录,则不用删。
|
|
43
|
+
对**一条**业务线:接口全部拉完 → 文案写入 `payload.json` → **保留** `bl-<bizLineId>/` 原始回包,不要删。跳过(未绑定项目/账号)的业务线若没有建目录,则不用处理。
|
|
44
44
|
|
|
45
45
|
## 整次请求结束时清理
|
|
46
46
|
|
|
47
|
-
Excel
|
|
47
|
+
Excel 成功落盘之后:
|
|
48
48
|
|
|
49
|
-
1. 按下面规则处理 Excel
|
|
50
|
-
2.
|
|
51
|
-
3.
|
|
49
|
+
1. 按下面规则处理 Excel(只动本次清单的旧 xlsx)。
|
|
50
|
+
2. **保留**整个 `%TEMP%/sino-bc-strategy/<requestId>/`(原始回包 + `payload.json`),汇报时带上该路径。
|
|
51
|
+
3. 只有用户明确要求清缓存时才 `cleanup.py --request-dir`。不要删其他 `requestId`。
|
|
52
52
|
|
|
53
|
-
|
|
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`。
|
|
Binary file
|
package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/cli_json.cpython-314.pyc
ADDED
|
Binary file
|
package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/fetch_data.cpython-314.pyc
ADDED
|
Binary file
|
package/dist/skills/custom/biz-line-strategy-generate/scripts/__pycache__/summarize.cpython-314.pyc
ADDED
|
Binary file
|
|
@@ -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
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
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
|
|
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
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
"""生成
|
|
163
|
-
|
|
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(
|
|
168
|
-
if key in
|
|
191
|
+
for i, (key, _label) in enumerate(columns):
|
|
192
|
+
if key in content_keys:
|
|
169
193
|
width = COL_WIDTH_CONTENT
|
|
170
|
-
elif key in
|
|
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(
|
|
217
|
-
|
|
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
|
-
{
|
|
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
|
)
|