sino-bc-cli 1.8.8 → 1.8.10

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.
@@ -0,0 +1,71 @@
1
+ # 接口调用流程
2
+
3
+ 字段口径以 `src/api-doc/oapi/biz-line-strategy.md`(安装后 `dist/api-doc/oapi/`)为准,本文件只写本 skill 场景下的调用顺序与默认参数。全部接口都走 `biz-line +<shortcut>` 或 `biz-line api <name> --data '<json>'`,不要自己拼 HTTP。
4
+
5
+ ## 第 1 步:拉取业务线列表
6
+
7
+ ```bash
8
+ sino-bc-cli biz-line api biz-line-list --data '{"pageNum":1,"pageSize":10,"sortType":"PERIOD","sortDirection":"DESC"}'
9
+ ```
10
+
11
+ - **默认排序**:用户未指定时,按「本期策略周期倒序」,即 `sortType: "PERIOD"`、`sortDirection: "DESC"`。
12
+ - **默认条数**:用户未指定时只取前 10 条,即 `pageSize: 10`;用户说「第一页」按平台默认页大小理解为 10 条(`pageNum:1, pageSize:10`);用户说「前 N 个」则 `pageNum:1, pageSize:N`。
13
+ - **按名称筛选**:用户点名业务线(如「名称为 xxx 的业务线」)改用 `name` 模糊匹配,此时不再套用「前 10 条」限制;`name` 命中多条时把候选列出来,不要擅自选一条。
14
+ - **按 ID 筛选**:已知业务线 ID 时用 `idList`。
15
+ - 返回的每条业务线含 `id`、`name`、`ssoTeamCode`(组别 code,配合 `sso +group-list` 反查组名)、`coreIndex`(核心指标,可能为空)等字段。
16
+
17
+ ## 第 2 步:拉取业务线关联项目与广告账号
18
+
19
+ 对第 1 步选中的每个业务线 ID 调用:
20
+
21
+ ```bash
22
+ sino-bc-cli biz-line api biz-line-project-list --data '{"bizLineId": 1251}'
23
+ ```
24
+
25
+ - 一个业务线可能关联多个项目,每个项目下又有多个广告账号(分渠道:Meta/Facebook、Google、TikTok 等)。
26
+ - **判定是否可生成策略**:按文档 7.1 生成入口的逻辑——若该业务线未绑定项目、或项目下没有关联广告账号,则不允许自动生成,直接跳过并在汇报里说明「未绑定项目/广告账号」;不要对空账号硬凑数据。
27
+ - **用户指定广告账号**:如果用户在本次请求中明确给了广告账号 ID 和所属渠道,直接用用户提供的账号(覆盖接口返回的账号),但仍需要业务线/项目名称等基础信息用于 Excel 输出,因此第 2 步仍要跑一次以取项目名称、项目 PM。
28
+
29
+ ## 第 3 步:按渠道查广告策略维度数据
30
+
31
+ 判定可以生成后,把上一步拿到的广告账号按渠道分组,分别调用对应渠道的维度接口。三渠道 name:
32
+
33
+ | 渠道 | 维度接口 name | Shortcut |
34
+ | --- | --- | --- |
35
+ | Facebook(Meta) | `icenter-strategy-dimension-facebook` | `biz-line +facebook-dimension` |
36
+ | Google | `icenter-strategy-dimension-google` | `biz-line +google-dimension` |
37
+ | TikTok | `icenter-strategy-dimension-tiktok` | `biz-line +tiktok-dimension` |
38
+
39
+ 请求体核心字段(三渠道一致):`startDate` / `endDate`(yyyy-MM-dd,两者必填)、`dimensionValues: {"account_id": ["<账号ID>", ...]}`(该账号所属渠道下的全部账号一次传入)、`dimensions`(分组维度数组,必填,按 [`four-strategies.md`](four-strategies.md) 对应策略维度选取需要的字段)。
40
+
41
+ **国家维度特殊规则**:`country_code` 必须单独一次请求,不能和其他维度混传,否则报错;查国家时服务端会转到跨渠道接口,只返回按国家分组的数据。
42
+
43
+ ## 第 4 步:按渠道查广告成效数据
44
+
45
+ 同样按渠道分组调用成效接口:
46
+
47
+ | 渠道 | 成效接口 name | Shortcut |
48
+ | --- | --- | --- |
49
+ | Facebook(Meta) | `icenter-strategy-metrics-facebook` | `biz-line +facebook-metrics` |
50
+ | Google | `icenter-strategy-metrics-google` | `biz-line +google-metrics` |
51
+ | TikTok | `icenter-strategy-metrics-tiktok` | `biz-line +tiktok-metrics` |
52
+
53
+ 请求体在维度接口基础上多了 `indicators`(成效指标数组,必填)。**接口只支持基础指标**(见 [`metrics.md`](metrics.md) 的基础/衍生指标口径),业务线核心指标若是衍生指标,要展开成其依赖的基础指标一并查出,在 Agent 侧计算衍生值,不要把衍生指标名传给接口(会被忽略或报错)。
54
+
55
+ ## 第 5 步:时间范围默认规则
56
+
57
+ 用户未指定本期/上期时间时,按文档默认逻辑:
58
+
59
+ - **本期**:本次策略生成对应的自然周(周一至周日),即业务线/项目当前的策略周期 `periodStart` ~ `periodEnd`。
60
+ - **上期**:本期 `periodStart` 的上一自然周周一,到该周周日(`previewPeriodStart` = 本期 periodStart 的上一周周一;`previewPeriodEnd` = previewPeriodStart + 6)。
61
+ - 两次调用(本期、上期)分别传各自的 `startDate` / `endDate`,本期与上期返回的数据在 Agent 侧做环比对比,不要在一次请求里跨两周合并查询。
62
+ - 维度接口与成效接口的时间范围最多支持近 21 天,不要传超过 21 天的跨度。
63
+ - 用户明确指定本期、上期起止日期时,直接使用用户指定值,跳过上述默认推算。
64
+
65
+ ## 分页与数据量
66
+
67
+ 维度/成效接口默认每页 10 条(`size`),建议显式传更大的 `size`(不超过 1000)覆盖账号下全部广告,按 `result.total` 判断是否需要翻页;广告量大时优先加大 `dimensions` 粒度或分批翻页,不要漏页导致漏算占比。
68
+
69
+ ## 汇总到 Excel 前的数据整理
70
+
71
+ 按业务线 -> 项目 -> 渠道 三层汇总生成好的策略文案与 learning,最终按 [`output-excel.md`](output-excel.md) 的列规范拼成行数组,一个项目一行。
@@ -0,0 +1,57 @@
1
+ # 四大策略生成
2
+
3
+ 来源:飞书文档《优化工作台广告策略生成》7.3 四大策略生成 + 名词解释表 + 「策略涉及的数据维度+指标」维度 sheet。四大策略固定为:**预算策略、广告产品策略、受众策略、素材策略**,覆盖渠道优先 Meta、Google、TT(Twitter、ASA 部分维度也支持,见下表「支持渠道」列)。
4
+
5
+ ## 生成总则
6
+
7
+ - 策略生成默认按周维度运行,对比本周(本期)与上周(上期)的广告结构配置、消耗占比和核心 KPI 表现。
8
+ - 每个维度的生成内容需覆盖:**策略标签**(本周动作类型归纳,如预算调整/产品结构调整/受众新增/素材上新)、**策略内容**(自然语言描述变化)、**广告动作**(底层依据,如新增/关闭数量、出价变化);效果 learning 见 [`metrics.md`](metrics.md)。
9
+ - 输出文案用优化师能看懂的业务语言,不直接暴露底层字段名(如不要直接写 `targetingInterests`,写「兴趣词包」)。
10
+ - 「设置类字段」(国家、年龄、性别、兴趣词包、关键词、广告组等)判断**新增/关闭/配置变化**,不强依赖消耗数据;「报告类字段」(Spend、广告目标、广告系列类型、成效目标、出价方式等)需结合成效数据计算**占比变化**。
11
+ - 各维度对应的接口字段来自「维度接口」(`+facebook-dimension` / `+google-dimension` / `+tiktok-dimension`)的 `dimensions` 数组取值,账号级过滤用 `dimensionValues.account_id`。
12
+
13
+ ## 预算策略
14
+
15
+ | 字段 | 数据逻辑 | 生成内容示例 | 支持渠道 |
16
+ | --- | --- | --- | --- |
17
+ | 调整出价 | 查出价方式(`bid_strategy`)、出价调整次数、调整前后值。Meta 用 `bid_amount`;Google 用 `cpc_bid_amount`/`cpm_bid_amount`/`cpv_bid_amount`(任一变化即算变化);TikTok 用 `bid_amount`/`conversion_bid`/`deep_cpabid`(任一变化即算变化) | 【调整出价】Meta 出价方式是 xxx,调整了 xx 次,分别从 xx 调整到 xx;GG 本周未发生明显出价调整。 | Meta、Google |
18
+ | Spend(预算) | 按渠道汇总 `spend_usd` 绝对值与占比,对比本周 vs 上周。占比变化在 ±2% 内视为「无变化」,否则输出「渠道:变化 x%」 | 【预算】预算分配有调整,FB +5%,TT:-5%,详细:FB 50%>>55%,TT 40%>>35%,GG 10%>>10% | Meta、Google、TikTok、Twitter、ASA |
19
+
20
+ ## 广告产品策略
21
+
22
+ | 字段 | 数据逻辑 | 生成内容示例 | 支持渠道 |
23
+ | --- | --- | --- | --- |
24
+ | 广告目标(`objective`) | 查本周、上周分媒体分广告目标的消耗占比 | 【广告目标】Meta:Awareness 60%>>68%,Sales 40%>>32%;TT:... | Meta、TikTok、Twitter |
25
+ | 广告系列类型(`advertising_channel_type`) | 查本周、上周分广告系列类型的消耗占比 | 【广告系列类型】Google 预算更集中在 PMax 系列。详细:PMax 60%>>68%,搜索广告 40%>>32% | Google |
26
+ | 成效目标 / 优化目标(`optimization_goal`) | 查本周、上周分成效目标的消耗占比 | 【成效目标】成效目标更聚焦在安装。详细:安装 45%>>60%,点击 55%>>40% | Meta、Google、TikTok |
27
+ | 出价方式(`bid_strategy`) | 查本周、上周出价方式的消耗占比 | 【出价方式】整体更偏向系统自动优化。详细:(按占比列出各出价方式变化) | Meta、Google、TikTok |
28
+
29
+ ## 受众策略
30
+
31
+ | 字段 | 数据逻辑 | 生成内容示例 | 支持渠道 |
32
+ | --- | --- | --- | --- |
33
+ | 国家(`country_code`,须单独查) | 查投放设置上的国家,总结新增/关停 | 【国家】新增 1 个国家:xxx;关停 1 个国家:xxxx,投放区域有小幅调整。 | Meta、Google、TikTok |
34
+ | 兴趣词包(`targeting_interests` / `targeting_flexible_interests` / `targeting_exclusion_interests`) | 查投放设置上的兴趣词包,总结新增/关停 | 【兴趣词包】新增 2 个兴趣词包:短剧、线上娱乐;未发现关停,受众覆盖范围扩大。 | Meta、TikTok(Google 不支持) |
35
+ | 受众类型(`audience_type`) | 查受众类型消耗数据(需加工),总结占比变化 | 【受众类型】无变化。详细:通用 50%>>55%,类似受众 40%>>35%,自定义受众 10%>>10% | Meta、Google、TikTok |
36
+ | 关键词(`keywords`) | 查投放设置上的关键词,总结新增/关停 | 【关键词】新增 3 个关键词:xxxx,关停 1 个低效关键词:xxxx | Google(Meta、TikTok 不支持) |
37
+ | 关键词分类(需在关键词基础上加工分类,无独立接口字段) | 查投放设置上的关键词并加工分类,总结新增/关停 | 【关键词分类】新增通用词 2 个:xxx、竞品词 1 个:xxx,品牌词无明显变化 | ASA |
38
+ | 年龄(`targeting_age`) | 查投放设置上的年龄,总结新增/关停 | 【年龄】年龄定向无明显变化,仍以 18-44 岁人群为主。 | Meta、Google、TikTok |
39
+ | 性别(`targeting_genders`) | 查投放设置上的性别,总结新增/关停 | 【性别】性别定向无明显变化,仍以女性人群为主。 | Meta、Google、TikTok |
40
+ | 广告组(`adgroup_id` / `adgroup_name`) | 查广告组投放数据,总结新增/关停 | 【广告组】新增 4 个广告组:xxxx,关停 2 个低消耗广告组:xxxxx | Meta、Google、TikTok |
41
+
42
+ ## 素材策略
43
+
44
+ | 字段 | 数据逻辑 | 生成内容示例 | 支持渠道 |
45
+ | --- | --- | --- | --- |
46
+ | Ad / Ad name(`ad_id` / `ad_name`) | 查 Ad 的投放报告,总结本周 Ad 新增/关停情况,并根据 `ad_name` 提取关键词,得出图片/视频素材的消耗占比变化 | 【广告】Meta:新增 2 个广告,素材集中在「剧情反转」方向;关停 2 个,素材为「内测人群」方向。TT:新增 1 个广告,素材集中在「强冲突开头」方向。 | Meta、Google、TikTok |
47
+ | 创意类型(`ad_format`) | 查创意类型(图片/视频等)的消耗占比变化 | 【创意类型】整体更偏向视频素材放量。图片 30%>>25%,视频 70%>>75%。 | Meta、Google、TikTok |
48
+
49
+ ## 三类调整对象的对比口径(判定策略是否发生变化时使用)
50
+
51
+ | 类型 | 定义 | 对比方式 |
52
+ | --- | --- | --- |
53
+ | 可环比(如预算调整、媒体占比变化) | 上周有、本周也有 | 本周 vs 上周(同一对象) |
54
+ | 新增(如新国家、新广告、新素材) | 本周新增,上周没有 | 本周新增部分 vs 本周整体 |
55
+ | 关停(如下架广告、关停国家) | 上周有,本周已关停 | 关停后剩余存量整体表现 vs 关停前整体表现 |
56
+
57
+ 预算始终走「可环比」逻辑——看各媒体占比变化,以及业务线整体 KPI 是否达标。
@@ -0,0 +1,97 @@
1
+ # 指标口径与策略效果生成
2
+
3
+ 来源:飞书文档 7.4 策略效果生成 + wiki《策略涉及的数据维度+指标》指标 sheet。
4
+
5
+ ## 基础指标 vs 衍生指标
6
+
7
+ 维度/成效接口(`+facebook-metrics` / `+google-metrics` / `+tiktok-metrics`)的 `indicators` **只支持以下 12 个基础指标**,一次查询按需传其中若干个:
8
+
9
+ | API 字段 | 中文名 |
10
+ | --- | --- |
11
+ | `spend_usd` | 消耗金额(美元) |
12
+ | `impressions` | 曝光量 |
13
+ | `clicks` | 点击量 |
14
+ | `video_views` | 视频观看次数 |
15
+ | `add_to_cart` | 添加购物车次数 |
16
+ | `checkouts` | 发起结账次数 |
17
+ | `purchase` | 购买次数 |
18
+ | `purchase_value` | 购买金额 |
19
+ | `installs` | 应用安装量 |
20
+ | `complete_registration` | 完成注册量 |
21
+ | `leads` | 留下潜客信息次数 |
22
+ | `engagement` | 互动次数 |
23
+
24
+ **互动次数(`engagement`)及以下的指标都是衍生指标**,接口不支持直接查询,必须先查出对应的基础指标,再在 Agent 侧按下表公式计算:
25
+
26
+ | 衍生指标 | 中文名 | 计算公式(基础指标) |
27
+ | --- | --- | --- |
28
+ | `ctr` | 点击率 | `clicks / impressions` |
29
+ | `cpc` | 单次点击成本 | `spend_usd / clicks` |
30
+ | `cpm` | 千次曝光成本 | `spend_usd / impressions * 1000` |
31
+ | `cpi` | 单次下载成本 | `spend_usd / installs` |
32
+ | `cpe` | 单次互动成本 | `spend_usd / engagement` |
33
+ | `cpv` | 单次视频观看成本 | `spend_usd / video_views` |
34
+ | `cpaPurchase` | 单次订单成本 | `spend_usd / purchase` |
35
+ | `cpaAddsToCart` | 单次加入购物车成本 | `spend_usd / add_to_cart` |
36
+ | `cpr` | 单次注册成本 | `spend_usd / complete_registration` |
37
+ | `cpl` | 单次线索成本 | `spend_usd / leads` |
38
+ | `cvrAddsToCart` | 加入购物车转化率 | `add_to_cart / clicks` |
39
+ | `cvrPurchases` | 订单转化率 | `purchase / clicks` |
40
+ | `cvrAppInstalls` | 安装转化率 | `installs / clicks` |
41
+ | `vtr` | 视频观看率 | `video_views / impressions` |
42
+ | `roas` | 投资回报率 | `purchase_value / spend_usd` |
43
+ | `arpp` | 笔单价 | `purchase_value / purchase` |
44
+ | `cvrRegistrationsCompleted` | 注册转化率 | `complete_registration / clicks` |
45
+
46
+ 计算前先确认分母对应的基础指标已经在同一次 `indicators` 请求中一起查出(同一 `dimensions` 粒度下),避免分子分母来自不同粒度导致口径不一致。分母为 0 时该指标记为「-」,不参与后续 KPI 判断。
47
+
48
+ ## 核心指标识别
49
+
50
+ 1. 业务线详情(`biz-line +detail` / `biz-line-list` 返回)里的 `coreIndex` 字段是自由文本,先尝试匹配到上述基础/衍生指标名称(中英文、别名均可,如「安装成本」→ `cpi`)。
51
+ 2. 若 `coreIndex` 为空或无法识别,按投放行业给默认核心指标(ECBR → ROAS,APP/GAME → CPI;此为文档中的候选默认值,如与业务方最新口径冲突以业务方最新说明为准)。行业取值见 `biz-line +categories` 一级节点(1=APP,2=ECBR,3=游戏)。
52
+ 3. 核心指标若是衍生指标,必须展开到其依赖的基础指标(见上表公式),把这些基础指标一并加入本次 `indicators` 查询,不能只查核心指标名。
53
+ 4. 一个业务线可能有多个核心指标,逐个按下方判定流程生成 learning,多个结论在同一个策略 learning 里合并展示。
54
+
55
+ ## 策略效果判定流程(生成 learning)
56
+
57
+ ### 第一步:区分三类调整对象
58
+
59
+ | 类型 | 定义 | 对比方式 |
60
+ | --- | --- | --- |
61
+ | 可环比 | 上周有、本周也有(如预算调整、媒体占比变化) | 本周 vs 上周(同一对象) |
62
+ | 新增 | 本周新增,上周没有(如新国家、新广告、新素材) | 本周新增部分 vs 本周整体 |
63
+ | 关停 | 上周有,本周已关停(如下架广告、关停国家) | 关停后剩余存量整体表现 vs 关停前整体表现 |
64
+
65
+ 预算调整始终走「可环比」逻辑:看各媒体占比变化,以及业务线整体 KPI 是否达标。
66
+
67
+ ### 第二步:判定结论(KPI 为主 + 环比为辅)
68
+
69
+ | 是否达标 KPI | 环比变化 | 结论 | 说明 |
70
+ | --- | --- | --- | --- |
71
+ | ✅ 达标 | 变好或稳定 | 有效 | 达标且没变差 |
72
+ | ✅ 达标 | 变差(>10%) | 待观察(预警) | 达标但下滑明显,再观察一周 |
73
+ | ❌ 未达标 | 变好(>10%) | 待观察(潜力) | 未达标但趋势向好,再给一周机会 |
74
+ | ❌ 未达标 | 稳定或变差 | 无效 | 未达标且没改善,需关停 |
75
+
76
+ ### 评判门槛(命中任一即判定「待观察」,不再往下判断有效/无效)
77
+
78
+ | 门槛 | 规则 | 原因 |
79
+ | --- | --- | --- |
80
+ | 消耗不足 | 本周消耗 < $50 | 数据量太小,指标不稳定 |
81
+ | 天数不足 | 本周有效投放天数 < 3 天 | 投放未跑满一周,样本代表性不足 |
82
+ | 转化不足 | 主 KPI 为转化类指标(CPI/CPA/ROI 等)且转化量 < 10 | 转化样本不够,统计显著性不足 |
83
+ | 无实质动作 | 占比变化在 ±2% 以内,且无新增/关停 | 实际没有发生有效调整,不评价调整成效 |
84
+
85
+ ### 第三步:输出格式(三句话:结论 + 效果数据 + 建议)
86
+
87
+ ```
88
+ 有效。{新广告CPI 是7, 整体是6.7, KPI目标是6.5}。建议继续执行
89
+ 待观察:{预算调整后,CPI上周是15、本周是12,KPI 目标是10}。建议再观察一周
90
+ 无效:{新兴趣词包CPI是18,整体CPI是12,KPI目标是10}。建议关停
91
+ ```
92
+
93
+ learning 文案里的效果数据要能对应到具体调整对象(如「新广告」「预算调整后」),不要只给笼统的整体数字。
94
+
95
+ ## 与「策略内容」的关系
96
+
97
+ 策略内容([`four-strategies.md`](four-strategies.md))描述「做了什么变化」,策略 learning 描述「这个变化效果如何」。四大策略维度分别对应各自的 learning,逐维度独立判定,不要把四个维度的效果合并成一句笼统结论。
@@ -0,0 +1,62 @@
1
+ # 输出 Excel
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) 完成。
4
+
5
+ ## 输出列(固定 12 列,顺序不可改)
6
+
7
+ | 列名 | 取值来源 |
8
+ | --- | --- |
9
+ | 组别 | `biz-line +list` 返回业务线的 `ssoTeamCode` 对应组名,或 `sso +group-list` 反查 |
10
+ | 业务线 | 业务线名称 |
11
+ | 项目名称 | `biz-line +project-list` 返回的项目名称;一个业务线可能对应多个项目,逐项目各出一行 |
12
+ | 项目PM | 项目 PM 姓名 |
13
+ | 预算策略内容 / 预算策略learning | 见 [`four-strategies.md`](four-strategies.md) 预算策略 与 [`metrics.md`](metrics.md) learning 生成规则 |
14
+ | 广告产品策略内容 / 广告产品策略learning | 同上,广告产品策略 |
15
+ | 受众策略内容 / 受众策略learning | 同上,受众策略 |
16
+ | 素材策略内容 / 素材策略learning | 同上,素材策略 |
17
+
18
+ 一个业务线下有多个项目时,逐项目产出一行;同一业务线的多行「组别」「业务线」保持重复填写,不要合并单元格(合并单元格会破坏后续按行读取)。
19
+
20
+ ## 调用方式
21
+
22
+ Agent 在生成完全部行的数据后,组装成 JSON payload 写入临时文件(或直接通过 stdin),再调脚本:
23
+
24
+ ```bash
25
+ python3 skills/biz-line-strategy-generate/scripts/build_report.py --input payload.json
26
+ ```
27
+
28
+ payload 结构:
29
+
30
+ ```json
31
+ {
32
+ "output_path": "可选,未传时默认写到用户桌面 业务线策略生成_YYYYMMDD_HHMMSS.xlsx",
33
+ "rows": [
34
+ {
35
+ "group": "华东组",
36
+ "bizLine": "示例业务线",
37
+ "project": "示例项目",
38
+ "pm": "张三",
39
+ "budgetStrategy": "...",
40
+ "budgetLearning": "...",
41
+ "productStrategy": "...",
42
+ "productLearning": "...",
43
+ "audienceStrategy": "...",
44
+ "audienceLearning": "...",
45
+ "creativeStrategy": "...",
46
+ "creativeLearning": "..."
47
+ }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ 也支持 stdin:`python3 scripts/build_report.py --input - < payload.json`。
53
+
54
+ ## 默认输出位置
55
+
56
+ 用户未指定路径时,默认写到当前用户桌面(`~/Desktop`,找不到则退回主目录),文件名 `业务线策略生成_YYYYMMDD_HHMMSS.xlsx`,避免覆盖上一次的产出。用户明确要求其他路径 / 文件名时,通过 `--output` 或 payload 的 `output_path` 指定。
57
+
58
+ ## 返回值与校验
59
+
60
+ 脚本成功时输出 `{"ok": true, "output_path": "...", "row_count": N}`;失败(如 `rows` 为空)输出 `{"ok": false, "error": "..."}` 并以非 0 退出。Agent 应读取 `output_path` 回报给用户,不要凭假设的路径汇报。
61
+
62
+ 生成后不做自动打开或回读校验(脚本没有 Excel 运行环境可用),Agent 汇报时按 `row_count` 与实际处理的业务线/项目数核对是否一致,不一致要说明原因(如某业务线被 7.1 判定为不可生成而跳过)。
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 把业务线策略生成结果写成 .xlsx(业务线策略生成 skill 专用,纯标准库实现,无第三方依赖)。
5
+
6
+ 职责边界:本脚本只负责「结构化数据 -> Excel 文件」的机械落盘,
7
+ 不做任何数据拉取、策略文案生成或判断——那些都由调用方(Agent)在此之前完成。
8
+
9
+ 用法:
10
+ python3 build_report.py --input payload.json [--output /path/to/file.xlsx]
11
+ cat payload.json | python3 build_report.py --input -
12
+
13
+ payload.json 结构:
14
+ {
15
+ "output_path": "可选,未传时默认写到桌面 业务线策略生成_YYYYMMDD_HHMMSS.xlsx",
16
+ "rows": [
17
+ {
18
+ "group": "组别",
19
+ "bizLine": "业务线名称",
20
+ "project": "项目名称",
21
+ "pm": "项目PM",
22
+ "budgetStrategy": "预算策略内容", "budgetLearning": "预算策略learning",
23
+ "productStrategy": "广告产品策略内容", "productLearning": "广告产品策略learning",
24
+ "audienceStrategy": "受众策略内容", "audienceLearning": "受众策略learning",
25
+ "creativeStrategy": "素材策略内容", "creativeLearning": "素材策略learning"
26
+ }
27
+ ]
28
+ }
29
+
30
+ 列固定为:组别、业务线、项目名称、项目PM、
31
+ 预算策略内容、预算策略learning、广告产品策略内容、广告产品策略learning、
32
+ 受众策略内容、受众策略learning、素材策略内容、素材策略learning。
33
+ """
34
+ import argparse
35
+ import json
36
+ import os
37
+ import sys
38
+ import zipfile
39
+ from datetime import datetime
40
+ from xml.sax.saxutils import escape as xml_escape
41
+
42
+ COLUMNS = [
43
+ ("group", "组别"),
44
+ ("bizLine", "业务线"),
45
+ ("project", "项目名称"),
46
+ ("pm", "项目PM"),
47
+ ("budgetStrategy", "预算策略内容"),
48
+ ("budgetLearning", "预算策略learning"),
49
+ ("productStrategy", "广告产品策略内容"),
50
+ ("productLearning", "广告产品策略learning"),
51
+ ("audienceStrategy", "受众策略内容"),
52
+ ("audienceLearning", "受众策略learning"),
53
+ ("creativeStrategy", "素材策略内容"),
54
+ ("creativeLearning", "素材策略learning"),
55
+ ]
56
+
57
+ # 内容列(非组别/业务线/项目名称/项目PM)默认加宽并开启自动换行
58
+ CONTENT_KEYS = {key for key, _label in COLUMNS[4:]}
59
+
60
+ CONTENT_TYPES_XML = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
61
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
62
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
63
+ <Default Extension="xml" ContentType="application/xml"/>
64
+ <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
65
+ <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
66
+ <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
67
+ <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
68
+ </Types>"""
69
+
70
+ ROOT_RELS_XML = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
71
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
72
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
73
+ </Relationships>"""
74
+
75
+ WORKBOOK_RELS_XML = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
76
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
77
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
78
+ <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
79
+ <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
80
+ </Relationships>"""
81
+
82
+ WORKBOOK_XML = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
83
+ <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
84
+ <sheets><sheet name="业务线策略生成" sheetId="1" r:id="rId1"/></sheets>
85
+ </workbook>"""
86
+
87
+ # cellXfs: 0=默认, 1=表头(粗体+浅底), 2=内容(自动换行+顶对齐)
88
+ STYLES_XML = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
89
+ <styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
90
+ <fonts count="2">
91
+ <font><sz val="10"/><name val="Microsoft YaHei"/></font>
92
+ <font><b/><sz val="10"/><color rgb="FFFFFFFF"/><name val="Microsoft YaHei"/></font>
93
+ </fonts>
94
+ <fills count="3">
95
+ <fill><patternFill patternType="none"/></fill>
96
+ <fill><patternFill patternType="none"/></fill>
97
+ <fill><patternFill patternType="solid"><fgColor rgb="FF4472C4"/><bgColor indexed="64"/></patternFill></fill>
98
+ </fills>
99
+ <borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
100
+ <cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
101
+ <cellXfs count="3">
102
+ <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
103
+ <xf numFmtId="0" fontId="1" fillId="2" borderId="0" xfId="0" applyFont="1" applyFill="1">
104
+ <alignment horizontal="center" vertical="center"/>
105
+ </xf>
106
+ <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1">
107
+ <alignment vertical="top" wrapText="1"/>
108
+ </xf>
109
+ </cellXfs>
110
+ </styleSheet>"""
111
+
112
+
113
+ def col_letter(index: int) -> str:
114
+ """0-based 列号转 Excel 列字母(A, B, ..., Z, AA, ...)"""
115
+ letters = ""
116
+ n = index + 1
117
+ while n > 0:
118
+ n, rem = divmod(n - 1, 26)
119
+ letters = chr(65 + rem) + letters
120
+ return letters
121
+
122
+
123
+ def build_shared_strings(rows):
124
+ """收集全部字符串去重,返回 (sst_xml, header_indices, row_indices)"""
125
+ seen = {}
126
+ order = []
127
+
128
+ def intern(value):
129
+ if value not in seen:
130
+ seen[value] = len(order)
131
+ order.append(value)
132
+ return seen[value]
133
+
134
+ header_indices = [intern(label) for _key, label in COLUMNS]
135
+ row_indices = []
136
+ for row in rows:
137
+ row_indices.append(
138
+ [intern(str(row.get(key, "") or "")) for key, _label in COLUMNS]
139
+ )
140
+
141
+ items = "".join(
142
+ f'<si><t xml:space="preserve">{xml_escape(text)}</t></si>' for text in order
143
+ )
144
+ sst_xml = (
145
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
146
+ f'<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '
147
+ f'count="{len(order)}" uniqueCount="{len(order)}">{items}</sst>'
148
+ )
149
+ return sst_xml, header_indices, row_indices
150
+
151
+
152
+ def build_sheet_xml(header_indices, row_indices):
153
+ """生成 sheet1.xml:首行表头样式 s="1",其余行内容样式 s="2"(自动换行)"""
154
+ col_count = len(COLUMNS)
155
+ dimension_ref = f"A1:{col_letter(col_count - 1)}{len(row_indices) + 1}"
156
+
157
+ cols_xml_parts = []
158
+ for i, (key, _label) in enumerate(COLUMNS):
159
+ width = 32 if key in CONTENT_KEYS else 14
160
+ cols_xml_parts.append(
161
+ f'<col min="{i + 1}" max="{i + 1}" width="{width}" customWidth="1"/>'
162
+ )
163
+ cols_xml = f"<cols>{''.join(cols_xml_parts)}</cols>"
164
+
165
+ def row_xml(row_num, indices, style_id):
166
+ cells = []
167
+ for i, sst_index in enumerate(indices):
168
+ ref = f"{col_letter(i)}{row_num}"
169
+ cells.append(f'<c r="{ref}" s="{style_id}" t="s"><v>{sst_index}</v></c>')
170
+ return f'<row r="{row_num}">{"".join(cells)}</row>'
171
+
172
+ rows_xml_parts = [row_xml(1, header_indices, 1)]
173
+ for offset, indices in enumerate(row_indices):
174
+ rows_xml_parts.append(row_xml(offset + 2, indices, 2))
175
+
176
+ sheet_data = f"<sheetData>{''.join(rows_xml_parts)}</sheetData>"
177
+
178
+ return (
179
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
180
+ '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
181
+ f'<dimension ref="{dimension_ref}"/>'
182
+ '<sheetViews><sheetView workbookViewId="0">'
183
+ '<pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/>'
184
+ '</sheetView></sheetViews>'
185
+ f'{cols_xml}{sheet_data}'
186
+ '</worksheet>'
187
+ )
188
+
189
+
190
+ def default_output_path() -> str:
191
+ """未指定输出路径时,默认写到用户桌面"""
192
+ home = os.path.expanduser("~")
193
+ desktop = os.path.join(home, "Desktop")
194
+ if not os.path.isdir(desktop):
195
+ # 部分中文 Windows / OneDrive 环境桌面路径不同,找不到就退回主目录
196
+ desktop = home
197
+ stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
198
+ return os.path.join(desktop, f"业务线策略生成_{stamp}.xlsx")
199
+
200
+
201
+ def write_xlsx(rows, output_path: str) -> str:
202
+ sst_xml, header_indices, row_indices = build_shared_strings(rows)
203
+ sheet_xml = build_sheet_xml(header_indices, row_indices)
204
+
205
+ out_dir = os.path.dirname(output_path)
206
+ if out_dir and not os.path.isdir(out_dir):
207
+ os.makedirs(out_dir, exist_ok=True)
208
+
209
+ with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
210
+ zf.writestr("[Content_Types].xml", CONTENT_TYPES_XML)
211
+ zf.writestr("_rels/.rels", ROOT_RELS_XML)
212
+ zf.writestr("xl/workbook.xml", WORKBOOK_XML)
213
+ zf.writestr("xl/_rels/workbook.xml.rels", WORKBOOK_RELS_XML)
214
+ zf.writestr("xl/styles.xml", STYLES_XML)
215
+ zf.writestr("xl/sharedStrings.xml", sst_xml)
216
+ zf.writestr("xl/worksheets/sheet1.xml", sheet_xml)
217
+
218
+ return output_path
219
+
220
+
221
+ def main() -> int:
222
+ parser = argparse.ArgumentParser(description=__doc__)
223
+ parser.add_argument(
224
+ "--input", required=True, help="payload JSON 文件路径,传 - 表示从 stdin 读取"
225
+ )
226
+ parser.add_argument(
227
+ "--output",
228
+ default=None,
229
+ help="输出 .xlsx 路径;未传时用 payload.output_path,仍未传则默认桌面",
230
+ )
231
+ args = parser.parse_args()
232
+
233
+ if args.input == "-":
234
+ raw = sys.stdin.read()
235
+ else:
236
+ with open(args.input, "r", encoding="utf-8") as f:
237
+ raw = f.read()
238
+
239
+ payload = json.loads(raw)
240
+ rows = payload.get("rows", [])
241
+ if not isinstance(rows, list) or len(rows) == 0:
242
+ print(json.dumps({"ok": False, "error": "payload.rows 为空或不是数组"}, ensure_ascii=False))
243
+ return 1
244
+
245
+ output_path = args.output or payload.get("output_path") or default_output_path()
246
+ output_path = os.path.abspath(output_path)
247
+
248
+ write_xlsx(rows, output_path)
249
+
250
+ print(
251
+ json.dumps(
252
+ {"ok": True, "output_path": output_path, "row_count": len(rows)},
253
+ ensure_ascii=False,
254
+ )
255
+ )
256
+ return 0
257
+
258
+
259
+ if __name__ == "__main__":
260
+ sys.exit(main())
@@ -12,4 +12,4 @@
12
12
  修改:`sino-bc-cli /set-config env test` 或 `/set-config api-key <值>`。
13
13
 
14
14
  配置文件里还有一个 `apifoxToken`,只服务仓库维护者的 `npm run sync-api-doc`(同步接口文档),
15
- 与业务调用、鉴权都无关。它同样默认打码,`/init` 不会询问它,不要在业务任务里读取或设置它。
15
+ 与业务调用、鉴权都无关。它同样默认打码。`/init` 会询问(回车用内置默认);维护者也可 `sino-bc-cli /set-config APIFOX_TOKEN`。不要在业务任务里读取或设置它。
@@ -2,11 +2,13 @@
2
2
 
3
3
  OpenAPI `x-cli-risk-level` 不是 `read` 的 Shortcut 与 `<domain> api <name>` 都走同一道门(描述含 ⚠️ 的也是)。
4
4
 
5
+ 真实请求发出前,CLI 会先做本地 dry-run(可组装性:环境、API_KEY、path、method、body 形状)。失败是 `validation`,不会发 HTTP,也不是退出码 10。
6
+
5
7
  ```text
6
- --dry-run 只打印 method / url / body,不发请求
8
+ --dry-run 走同一套本地 dry-run,只打印 method / url / body,不发请求
7
9
  无 --yes TTY:展示 env + 动作 + 关键参数,交互确认
8
10
  非 TTY:exit 10,error.type = confirmation_required
9
- --yes 直接发起请求
11
+ --yes 本地 dry-run 通过后发起请求
10
12
  ```
11
13
 
12
14
  Agent 收到退出码 `10` 时:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sino-bc-biz-line
3
- description: Use when 查询或操作 BeyondClick 业务线、周策略生成、广告结构、广告成效,或用户提到 biz-line。
3
+ description: Use when 查询或操作 BeyondClick 业务线、周策略、投放渠道/品类、关联项目、广告策略维度与成效,或用户提到 biz-line。
4
4
  ---
5
5
 
6
6
  # biz-line
@@ -41,3 +41,13 @@ sino-bc-cli biz-line api biz-line-list --data '{}'
41
41
  | 切换业务线状态 | `+status --id --status` | ⚠️;枚举见 [`references/enums.md`](references/enums.md) |
42
42
  | 删除业务线 | `+delete --id` | ⚠️ |
43
43
  | 新建 / 编辑 / 删除策略 | `+strategy-save` / `+strategy-update` / `+strategy-delete` | ⚠️;编辑带 `version` 乐观锁 |
44
+ | 业务线详情 | `biz-line +detail` | `{ "id": <bizLineId> }` |
45
+ | 查询投放策略消耗信息 | `biz-line +spend` | |
46
+ | 查询业务线关联项目和广告账号 | `biz-line +project-list` | |
47
+ | 查询项目分页列表 | `biz-line +queryByPage` | |
48
+ | Facebook广告策略维度数据 | `biz-line +facebook-dimension` | 只返回策略配置,不含成效;`country_code` 必须单独传 |
49
+ | Google广告策略维度数据 | `biz-line +google-dimension` | 只返回策略配置,不含成效;`country_code` 必须单独传 |
50
+ | TikTok广告策略维度数据 | `biz-line +tiktok-dimension` | 只返回策略配置,不含成效;`country_code` 必须单独传 |
51
+ | Facebook广告策略成效数据 | `biz-line +facebook-metrics` | 只返回成效指标,不含策略配置 |
52
+ | Google广告策略成效数据 | `biz-line +google-metrics` | 只返回成效指标,不含策略配置 |
53
+ | TikTok广告策略成效数据 | `biz-line +tiktok-metrics` | 只返回成效指标,不含策略配置 |
@@ -5,15 +5,14 @@
5
5
  | 用户在问 | name | 注意 |
6
6
  | --- | --- | --- |
7
7
  | 业务线详情 | `biz-line-detail` | `{ "id": <bizLineId> }` |
8
- | 策略周期列表 | `biz-line-strategy-period-list` | |
9
- | 策略周期内关联项目周表消耗汇总 | `biz-line-strategy-spend` | |
10
- | 业务线关联项目列表 | `biz-line-project-list` | |
11
- | 查询优化项目分页列表 | `optimize-project-query-by-page` | |
12
- | Facebook广告策略维度数据 | `icenter-strategy-dimension-facebook` | |
13
- | Google广告策略维度数据 | `icenter-strategy-dimension-google` | |
14
- | TikTok广告策略维度数据 | `icenter-strategy-dimension-tiktok` | |
15
- | Facebook广告策略成效数据 | `icenter-strategy-metrics-facebook` | |
16
- | Google广告策略成效数据 | `icenter-strategy-metrics-google` | |
17
- | TikTok广告策略成效数据 | `icenter-strategy-metrics-tiktok` | |
8
+ | 查询投放策略消耗信息 | `biz-line-strategy-spend` | |
9
+ | 查询业务线关联项目和广告账号 | `biz-line-project-list` | |
10
+ | 查询项目分页列表 | `optimize-project-query-by-page` | |
11
+ | Facebook广告策略维度数据 | `icenter-strategy-dimension-facebook` | 只返回策略配置,不含成效;`country_code` 必须单独传 |
12
+ | Google广告策略维度数据 | `icenter-strategy-dimension-google` | 只返回策略配置,不含成效;`country_code` 必须单独传 |
13
+ | TikTok广告策略维度数据 | `icenter-strategy-dimension-tiktok` | 只返回策略配置,不含成效;`country_code` 必须单独传 |
14
+ | Facebook广告策略成效数据 | `icenter-strategy-metrics-facebook` | 只返回成效指标,不含策略配置 |
15
+ | Google广告策略成效数据 | `icenter-strategy-metrics-google` | 只返回成效指标,不含策略配置 |
16
+ | TikTok广告策略成效数据 | `icenter-strategy-metrics-tiktok` | 只返回成效指标,不含策略配置 |
18
17
 
19
18
  描述含 ⚠️ 的 name 同样走确认门。请求体按用户条件组装,禁止原样提交文档示例业务线 ID。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sino-bc-cli",
3
- "version": "1.8.8",
3
+ "version": "1.8.10",
4
4
  "description": "BeyondClick CLI - 命令行工具集合,提供快捷访问 BeyondClick 平台的命令行接口",
5
5
  "preferGlobal": true,
6
6
  "bin": {