siluzan-tso-cli 1.1.34-beta.3 → 1.1.34-beta.4

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/README.md CHANGED
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
51
51
  siluzan-tso init --force # 强制覆盖已存在文件
52
52
  ```
53
53
 
54
- > **注意**:当前为测试版(1.1.34-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.34-beta.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -4361,7 +4361,15 @@ async function enrichOverviewRecordWithRealtimeBalance(media, accountId, config,
4361
4361
  const bal = balanceMap.get(accountId);
4362
4362
  if (bal == null) return record;
4363
4363
  const out = { ...record };
4364
- applyRealtimeBalanceIfNeeded(out, bal);
4364
+ const overviewBudget = typeof out.remainingAccountBudget === "number" ? out.remainingAccountBudget : void 0;
4365
+ const balanceTarget = out;
4366
+ applyRealtimeBalanceIfNeeded(balanceTarget, bal);
4367
+ if (typeof balanceTarget.remainingAccountBudget === "number") {
4368
+ out.balance = balanceTarget.remainingAccountBudget;
4369
+ if (overviewBudget === 0 && bal.remainingAccountBudget != null && bal.remainingAccountBudget > 0) {
4370
+ out._balanceSource = "GetMediaAccountInfo";
4371
+ }
4372
+ }
4365
4373
  if (bal.name && out["accountName"] == null && out["mediaCustomerName"] == null) {
4366
4374
  out["mediaCustomerName"] = bal.name;
4367
4375
  }
@@ -107843,8 +107851,84 @@ function enrichBingOverviewDerivedMetrics(record, startDate, endDate) {
107843
107851
  }
107844
107852
  return out;
107845
107853
  }
107854
+ function parseYmd(ymd) {
107855
+ const parts = ymd.trim().split("-").map((x) => parseInt(x, 10));
107856
+ if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) return null;
107857
+ const d = new Date(parts[0], parts[1] - 1, parts[2]);
107858
+ return Number.isNaN(d.getTime()) ? null : d;
107859
+ }
107860
+ function formatYmd(d) {
107861
+ const y = d.getFullYear();
107862
+ const m = String(d.getMonth() + 1).padStart(2, "0");
107863
+ const day = String(d.getDate()).padStart(2, "0");
107864
+ return `${y}-${m}-${day}`;
107865
+ }
107866
+ function calcBingPreviousPeriodRange(startDate, endDate) {
107867
+ const diffDays = countInclusiveCalendarDays(startDate, endDate);
107868
+ if (diffDays <= 0) return null;
107869
+ const start = parseYmd(startDate);
107870
+ if (!start) return null;
107871
+ const prevEnd = new Date(start);
107872
+ prevEnd.setDate(prevEnd.getDate() - 1);
107873
+ const prevStart = new Date(prevEnd);
107874
+ prevStart.setDate(prevStart.getDate() - (diffDays - 1));
107875
+ return { startDate: formatYmd(prevStart), endDate: formatYmd(prevEnd) };
107876
+ }
107877
+ function isBingPeriodBlockEmpty(period) {
107878
+ if (!period || typeof period !== "object") return true;
107879
+ const p = period;
107880
+ const spend = num(p.spend) ?? 0;
107881
+ const impressions = num(p.impressions) ?? 0;
107882
+ const clicks = num(p.clicks) ?? 0;
107883
+ return spend <= 0 && impressions <= 0 && clicks <= 0;
107884
+ }
107885
+ function syncOverviewBalanceFields(record) {
107886
+ const out = { ...record };
107887
+ const budget = num(out.remainingAccountBudget);
107888
+ if (budget != null && budget >= 0) {
107889
+ const balanceField = num(out.balance);
107890
+ if (balanceField == null || balanceField <= 0 && budget > 0) {
107891
+ out.balance = budget;
107892
+ }
107893
+ }
107894
+ return out;
107895
+ }
107846
107896
 
107847
107897
  // src/commands/report-bing-analysis/run.ts
107898
+ async function enrichBingOverviewPreviousPeriod(config, accountId, record, startDate, endDate, verbose) {
107899
+ if (!isBingPeriodBlockEmpty(record.previousPeriod)) {
107900
+ return record;
107901
+ }
107902
+ const prevRange = calcBingPreviousPeriodRange(startDate, endDate);
107903
+ if (!prevRange) return record;
107904
+ if (bingRangeTouchesTodayOrYesterday(prevRange.startDate, prevRange.endDate)) {
107905
+ return record;
107906
+ }
107907
+ try {
107908
+ const params = new URLSearchParams({
107909
+ startDate: prevRange.startDate,
107910
+ endDate: prevRange.endDate
107911
+ });
107912
+ const url = reportingUrl2(config, accountId, "OverviewSectionData", params.toString());
107913
+ const prevRaw = await fetchBingJson(config, url, verbose);
107914
+ if (!prevRaw || typeof prevRaw !== "object" || Array.isArray(prevRaw)) {
107915
+ return record;
107916
+ }
107917
+ const prevRecord = prevRaw;
107918
+ const prevPeriod = prevRecord.currentPeriod;
107919
+ if (isBingPeriodBlockEmpty(prevPeriod)) {
107920
+ return record;
107921
+ }
107922
+ return {
107923
+ ...record,
107924
+ previousPeriod: prevPeriod,
107925
+ previousPeriodDateRange: prevRange,
107926
+ _previousPeriodSource: "OverviewSectionData-second-fetch"
107927
+ };
107928
+ } catch {
107929
+ return record;
107930
+ }
107931
+ }
107848
107932
  async function runReportBingOverview(opts) {
107849
107933
  const id = assertBingAccountId(opts.account);
107850
107934
  const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
@@ -107862,7 +107946,16 @@ async function runReportBingOverview(opts) {
107862
107946
  raw,
107863
107947
  opts.verbose
107864
107948
  );
107865
- data = enrichBingOverviewDerivedMetrics(withBalance, startDate, endDate);
107949
+ const withDerived = enrichBingOverviewDerivedMetrics(withBalance, startDate, endDate);
107950
+ const withPrevious = await enrichBingOverviewPreviousPeriod(
107951
+ config,
107952
+ id,
107953
+ withDerived,
107954
+ startDate,
107955
+ endDate,
107956
+ !!opts.verbose
107957
+ );
107958
+ data = syncOverviewBalanceFields(withPrevious);
107866
107959
  }
107867
107960
  if (opts.jsonOut) {
107868
107961
  await emitBingSnapshot(opts, "bing-overview", id, startDate, endDate, data);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.34-beta.3",
4
- "publishedAt": 1782963676464
3
+ "version": "1.1.34-beta.4",
4
+ "publishedAt": 1782984153033
5
5
  }
@@ -327,7 +327,7 @@ siluzan-tso report bing-keywords -a <mediaCustomerId> --start 2026-03-01 --end 2
327
327
  siluzan-tso report bing-audience-merged -a <mediaCustomerId> --json-out ./snap-bing
328
328
  ```
329
329
 
330
- 报告模板:`report-templates/bing-period-report.md`。
330
+ 报告模板:`report-templates/bing-period-report.md`(**每个 section 须写「分析」**;**余额**:overview 快照常为 0,读落盘 `balance`/`remainingAccountBudget` 或 `balance` 命令;**环比**:网关 `previousPeriod` 常空,CLI 二次拉上一周期回填)。
331
331
 
332
332
  ---
333
333
 
@@ -1,7 +1,8 @@
1
1
  # Bing(Microsoft Advertising / BingV2)— 账户分析报告(模板纲要)
2
2
 
3
3
  > 统计区间:`{startDate}` ~ `{endDate}`(**不可包含今天或昨天**,见下文)
4
- > 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
4
+ > 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
5
+ > **默认交付**:**HTML**(Agent 脚本读落盘 JSON 生成;禁止仅 Markdown 摘要或纯 JSON 充当终稿)。用户指定 Excel 时 Agent 脚本写 xlsx,**无** CLI `render` 子命令。
5
6
 
6
7
  数据块:总览、设备、地域、受众(年龄/性别)、系列、广告组、广告、关键词、搜索字词。
7
8
 
@@ -15,38 +16,170 @@
15
16
 
16
17
  ---
17
18
 
19
+ ## 拉数(一次目录)
20
+
21
+ ```bash
22
+ mkdir -p ./snap-bing
23
+
24
+ siluzan-tso list-accounts -m BingV2 -k <mediaCustomerId> --json-out ./snap-bing
25
+
26
+ siluzan-tso report bing-overview -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
27
+ siluzan-tso balance -m BingV2 --accounts <mediaCustomerId> --json-out ./snap-bing
28
+ siluzan-tso report bing-campaigns -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
29
+ siluzan-tso report bing-device -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
30
+ siluzan-tso report bing-geographic -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
31
+ siluzan-tso report bing-audience-merged -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
32
+ siluzan-tso report bing-ad-groups -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
33
+ siluzan-tso report bing-ads -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
34
+ siluzan-tso report bing-keywords -a <mediaCustomerId> --start <S> --end <E> --limit 100 --json-out ./snap-bing
35
+ siluzan-tso report bing-search-terms -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
36
+ ```
37
+
38
+ - 写脚本前先读各 `bing-*-<id>.outline.txt`,再读 `.json`(见 `references/core/agent-conventions.md` §三)。
39
+ - TopN、排序、汇总均在脚本内完成,禁止心算。
40
+ - 金额字段已为**元**;`ctr` / `conversionRate` 等为 **0~1 小数**,展示百分比用 `(v * 100).toFixed(2) + '%'`。
41
+
42
+ ---
43
+
44
+ ## 分析纪律(全章节强约束)
45
+
46
+ **每个 section 的数据表格/图表之后,必须紧跟该 section 的「分析」小节**;整份报告**禁止**只有数据、没有分析。
47
+
48
+ | 要求 | 说明 |
49
+ | --- | --- |
50
+ | **总结** | 引用**当 section 落盘 JSON** 中的数字(TopN、占比、合计、环比);可对比账户 KPI 或 section 内均值 |
51
+ | **建议** | 1~3 条可执行项(预算、出价、暂停、否词、设备/地域调价等),须点名系列/关键词/国家/设备并引用数据 |
52
+ | **禁止** | 编造数字、空分析、全章共用一段笼统话而不分 section |
53
+ | **环比** | 仅 **总览** `currentPeriod` vs **`previousPeriod`**(CLI 可能二次拉数回填);**previousPeriod 全 0 时不写 0% 环比** |
54
+ | **缺数据** | 某 section 拉数失败或 `items` 为空 → 分析写 `[数据不可用:<原因>]`,**仍须保留分析小节标题** |
55
+
56
+ 报告 HTML 建议结构:按下方章节顺序,每章 = **标题 → 数据表/指标卡 → 「分析」**(含总结 + 建议)。
57
+
58
+ ---
59
+
18
60
  ## 1. 执行摘要(总览)
19
61
 
20
62
  - **CLI**:`siluzan-tso report bing-overview -a <mediaCustomerId> [--start … --end …] --json-out <dir>`
21
- - **余额**:若 `OverviewSectionData` 误返 `remainingAccountBudget: 0`,CLI 会自动用 `GetMediaAccountInfo` 实时余额回填(与 Web 账户列表一致);**仍建议**单查余额用 `balance -m BingV2`。
22
- - **日均消耗 / 活跃天数**:Bing 网关常不返回 `averageDailyCost`、`activeDays`(或为 0)。CLI 落盘前会:① 缺 `totalCost` 时用 `currentPeriod.spend`;② 缺 `activeDays` 时用 `--start`~`--end` 含首尾日历天数;③ `averageDailyCost = totalCost / activeDays`(保留 2 位小数)。报告脚本直接读落盘字段即可。
23
63
 
24
- ## 2. 设备与地域
64
+ ### 余额(必读 · 勿读 overview 快照)
65
+
66
+ `OverviewSectionData` 里的 `balance` / `remainingAccountBudget` **不是实时余额**,是报告生成时的快照,**常为 0 或未同步**。
67
+
68
+ | 正确做法 | 错误做法 |
69
+ | --- | --- |
70
+ | 读落盘 JSON 的 **`remainingAccountBudget`** 或 **`balance`**(CLI 已用 `GetMediaAccountInfo` 回填) | 直接相信 overview 原始响应里的 0 |
71
+ | 字段 **`_balanceSource: "GetMediaAccountInfo"`** 表示已校正 | 把 `$0.00` 写进报告而不说明 |
72
+ | 仍须独立核对:`siluzan-tso balance -m BingV2 --accounts <id> --json-out <dir>` | 用 `stats` 或 overview 推断余额 |
73
+
74
+ CLI 落盘时会:overview 余额为 0 → 调 `GetMediaAccountInfo` → 写回 `remainingAccountBudget` 与 **`balance`**(与 Web `filterOverviewData` 一致)。
75
+
76
+ ### 环比(必读 · 勿读空 previousPeriod)
77
+
78
+ 网关内嵌的 **`previousPeriod` 常全为 0**。CLI 若检测到为空,会按 Web `calcPrePeriodTimeRange` 规则再请求**上一周期**的 `OverviewSectionData`,将其 **`currentPeriod`** 写入 `previousPeriod`,并标注 **`_previousPeriodSource: "OverviewSectionData-second-fetch"`**。
79
+
80
+ | 场景 | 报告写法 |
81
+ | --- | --- |
82
+ | `previousPeriod.spend > 0`(含二次拉数回填) | 可写消耗/点击/转化/CTR/CPC/CPA 环比 |
83
+ | `previousPeriod` 仍为空或全 0 | **不写环比为 0%**;写「上一周期数据不可用」 |
84
+ | 有 `previousPeriodDateRange` | 分析中注明对比区间 |
85
+
86
+ ### 日均消耗 / 活跃天数
87
+
88
+ Bing 网关常不返回 `averageDailyCost`、`activeDays`(或为 0)。CLI 落盘前会:① 缺 `totalCost` 时用 `currentPeriod.spend`;② 缺 `activeDays` 时用 `--start`~`--end` 含首尾日历天数;③ `averageDailyCost = totalCost / activeDays`(保留 2 位小数)。
89
+
90
+ **数据呈现**:区间消耗、展示、点击、转化、CTR、CPC、CPA、日均消耗、**实时余额**(`balance`)、优化分;**有效** `previousPeriod` 时展示环比。
91
+
92
+ **分析(必写)**:
93
+
94
+ - **总结**:本期 vs 上期消耗/点击/转化/CTR/CPC/CPA 变化(**仅当 previousPeriod 有数据**);展示份额(`searchImpressionShare`)及预算/排名丢失份额;余额与日均消耗可支撑天数(用 **`balance` / `remainingAccountBudget`**,非 overview 快照 0)。
95
+ - **建议**:账户级预算或投放节奏(引用具体百分比或金额);若展示份额丢失高 → 预算或出价方向;1~3 条。
96
+
97
+ ---
98
+
99
+ ## 2. 设备
100
+
101
+ - **CLI**:`report bing-device` → `DeviceSectionData`(行在 `devices[]`)
102
+
103
+ **数据呈现**:各 `deviceType` 的展示、点击、消耗、CTR、CPC、转化、CPA;建议附消耗占比。
25
104
 
26
- - 设备:`report bing-device` → `DeviceSectionData`
27
- - 地域:`report bing-geographic` → `GeographicSectionData`(响应中常见 `countries` 等字段,**以实际 JSON 为准**)
105
+ **分析(必写)**:
28
106
 
29
- ## 3. 受众
107
+ - **总结**:主消耗设备、各设备 CPA/CPC 差异、高消耗低转化设备。
108
+ - **建议**:设备出价调整或预算向高转化设备倾斜(写设备名 + 表中数字),1~3 条。
30
109
 
31
- - 年龄:`report bing-age-audience` → `AgeAudienceData`
32
- - 性别:`report bing-gender-audience` → `GenderAudienceData`
33
- - 合并输出:`report bing-audience-merged`(**固定输出合并 JSON**,含年龄+性别)
110
+ ---
111
+
112
+ ## 3. 地域
113
+
114
+ - **CLI**:`report bing-geographic` → `GeographicSectionData`(常见 `countries[]`,**以 outline 为准**)
115
+
116
+ **数据呈现**:Top 国家/地区(建议 Top 10)消耗、点击、转化、CTR、CPC、CPA。
117
+
118
+ **分析(必写)**:
119
+
120
+ - **总结**:地域消耗集中度、高转化 vs 高消耗低转化国家/地区。
121
+ - **建议**:地域加价/降价/排除或单独系列(写 `countryOrRegion` + 数据),1~3 条。
122
+
123
+ ---
124
+
125
+ ## 4. 受众
126
+
127
+ - **CLI**:推荐 `report bing-audience-merged`(年龄+性别合并 JSON);或分别 `bing-age-audience` / `bing-gender-audience`
128
+
129
+ **数据呈现**:年龄段、性别的展示、点击、消耗、CTR、CPC(按 `data.ageAudience.audience` / `data.genderAudience.audience`)。
130
+
131
+ **分析(必写)**:
132
+
133
+ - **总结**:主力年龄段/性别、转化或 CPA 更优的受众段、无效花费受众。
134
+ - **建议**:受众出价_modifier 或排除(引用 `audience` 字段 + 指标),1~3 条。
135
+
136
+ ---
34
137
 
35
- ## 4. 广告结构
138
+ ## 5. 广告结构(系列 / 广告组 / 广告)
36
139
 
37
- - 系列:`report bing-campaigns` `CampaignReport`
38
- - 广告组:`report bing-ad-groups` `AdGroupReport`
39
- - 广告:`report bing-ads` `AdReport`
140
+ | 层级 | CLI | 落盘 |
141
+ | --- | --- | --- |
142
+ | 系列 | `report bing-campaigns` | `bing-campaigns-*.json`(数组行) |
143
+ | 广告组 | `report bing-ad-groups` | `bing-ad-groups-*.json` |
144
+ | 广告 | `report bing-ads` | `bing-ads-*.json` |
40
145
 
41
- ## 5. 关键词与搜索字词
146
+ **数据呈现**:各表按消耗降序;系列含 `campaignStatus`;广告组可含质量分相关字段(以 outline 为准)。
42
147
 
43
- - 关键词:`report bing-keywords` → `KeywordReport`,Query 含 `limit`(默认 **100**)、`orderByCost=true`
44
- - 搜索字词:`report bing-search-terms` → `SearchQueryReport`,参数同上
148
+ **分析(必写,三个子块各写一段,不可合并为一句带过)**:
149
+
150
+ 1. **系列分析 — 总结**:Top 系列及费用占比、暂停/活跃系列效果差异、合计 CTR/CPC/CPA。**建议**:预算增减、暂停或放量具体 `campaignName`,1~3 条。
151
+ 2. **广告组分析 — 总结**:高消耗广告组、质量分或 CPA 异常组。**建议**:出价、暂停或结构优化(写 `adGroupName` + 数据),1~3 条。
152
+ 3. **广告分析 — 总结**:Top 消耗创意、CTR/转化表现分化。**建议**:暂停低效广告、复制高效创意方向,1~3 条。
153
+
154
+ ---
155
+
156
+ ## 6. 关键词与搜索字词
157
+
158
+ | 类型 | CLI | 说明 |
159
+ | --- | --- | --- |
160
+ | 关键词 | `report bing-keywords` | 默认 `limit=100`、`orderByCost=true` |
161
+ | 搜索字词 | `report bing-search-terms` | 同上 |
162
+
163
+ **数据呈现**:Top 关键词/搜索词表(消耗、CTR、CPC、转化、CPA、质量分/匹配类型等,以 outline 为准)。
164
+
165
+ **分析(必写,两块各写一段)**:
166
+
167
+ 1. **关键词 — 总结**:高转化词、高消耗低转化或零转化词、匹配类型分布。**建议**:暂停/降价/提价/改匹配(写 `keyword` + 数据),1~3 条。
168
+ 2. **搜索字词 — 总结**:高意向词、应加为关键词的词、应否定的无关词(对比触发 `keyword`)。**建议**:加词、否词(写 `searchQuery` + 数据),1~3 条。
169
+
170
+ ---
171
+
172
+ ## 7. 报告收尾(全账户)
173
+
174
+ 在以上各 section 分析之后,可增加 **「优化建议汇总」**(3~5 条),跨 section 归纳优先级,**须与前面各 section 分析一致、不得矛盾**;可呼应对外客户话术(若用户需要)。
175
+
176
+ ---
45
177
 
46
- ## 6. 附录
178
+ ## 附录
47
179
 
48
180
  - 鉴权:与 TSO 其他接口相同(`config show` 中 `tsoApiBaseUrl` / Token)。
49
181
  - 与 steward「优化报告」区别:见 `meta-period-report.md` 末节;此处为**实时分析 JSON**。
182
+ - 交付前自检:见 `references/core/agent-conventions.md` §七(币种、区间、章节齐全、数字来自脚本 stdout)。
50
183
 
51
184
  ---
52
185
 
@@ -91,6 +91,7 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
91
91
  - **R2(A2)**:统计区间,中文可读:`YYYY年M月D日 - YYYY年M月D日`。
92
92
  - **R3**:表头(列名、顺序须与下表一致)。
93
93
  - **R4 起**:数据行;列宽、自动筛选、首行冻结、降序排序为可选,**不得**因此改数。
94
+ - **表末「分析」**(强约束):数据区(含「合计」行若有)结束后 **留白 1~2 行**,必须写入 **「分析」** 标题区块;须含 **总结**(引用**当 Sheet 表格**中的数字,可 TopN / 占比 / 与合计行对比)+ **建议**(1~3 条可执行项);禁止编造、禁止留空;各 Sheet 具体分析要点见下表「表末「分析」」行。
94
95
  - **百分比列**:样表为 0~1 小数(如 7.95% 存 `0.08`);与运营约定二选一,**禁止**同一文件混用。
95
96
  - **金额**:与 `references/analytics/account-analytics.md` 一致;`campaigns.budgetAmountYuan`、`spend`、`averageCpc`、`costPerConversion` 均为元,可直接写入。
96
97
 
@@ -98,31 +99,32 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
98
99
 
99
100
  ### Sheet `账户报告`
100
101
 
101
- | 行 | 内容 |
102
- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
103
- | R1 | `广告系列报告`(如运营改名,可改文案但 Sheet 名仍为 `账户报告`) |
104
- | R2 | 统计区间 |
105
- | R3(A→K,11 列) | `广告系列` \| `预算` \| `费用` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
106
- | R4… | 来自 `campaigns-*.json` 的 `items[]`(`schemaVersion 3` 行统一在 `items`):`campaignName`、`budgetAmountYuan`(元)、`spend`、`impressions`、`clicks`、`ctr`(已归一直接写入「点击率」)、`averageCpc`、`allConversions`→「所有转化次数」、`conversions`→「转化次数」、`costPerConversion`、`conversionRate`(已归一直接写入「转化率」);缺 `allConversions` 时与「转化次数」同值或填 `0` / `—`,并在脚注说明 |
107
- | 末行「合计」 | **广告系列**列填 `总计`;**预算**列填 `--`;**展示 / 点击 / 所有转化次数 / 转化次数 / 费用** 做列求和;**点击率** = 合计点击 ÷ 合计展示;**转化率** = 合计转化 ÷ 合计点击(**禁止**用合计转化 ÷ 合计展示);**每次转化费用** = 合计费用 ÷ 合计转化(转化为 0 时填 `—`);禁止对各行比率取算术平均 |
102
+ | 行 | 内容 |
103
+ | --- | --- |
104
+ | R1 | `广告系列报告`(如运营改名,可改文案但 Sheet 名仍为 `账户报告`) |
105
+ | R2 | 统计区间 |
106
+ | R3(A→K,11 列) | `广告系列` \| `预算` \| `费用` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
107
+ | R4… | 来自 `campaigns-*.json` 的 `items[]`(`schemaVersion 3` 行统一在 `items`):`campaignName`、`budgetAmountYuan`(元)、`spend`、`impressions`、`clicks`、`ctr`(已归一直接写入「点击率」)、`averageCpc`、`allConversions`→「所有转化次数」、`conversions`→「转化次数」、`costPerConversion`、`conversionRate`(已归一直接写入「转化率」);缺 `allConversions` 时与「转化次数」同值或填 `0` / `—`,并在脚注说明 |
108
+ | 末行「合计」 | **广告系列**列填 `总计`;**预算**列填 `--`;**展示 / 点击 / 所有转化次数 / 转化次数 / 费用** 做列求和;**点击率** = 合计点击 ÷ 合计展示;**转化率** = 合计转化 ÷ 合计点击(**禁止**用合计转化 ÷ 合计展示);**每次转化费用** = 合计费用 ÷ 合计转化(转化为 0 时填 `—`);禁止对各行比率取算术平均 |
109
+ | 表末「分析」 | **总结**:Top 消耗系列及费用占比、合计/系列 CTR·CPC·CPA 水平、预算与费用对比(预算跑满/空耗/超支系列);可对照 stats/overview 账户级指标。**建议**:预算增减、暂停或放量具体系列(须写系列名 + 引用表中数字),1~3 条 |
108
110
 
109
- 表下**留白若干行**后写 **「数据复盘」**:
111
+ 表下**留白若干行**后写 **「数据复盘」**(账户 Sheet 汇总向,与各 Sheet 表末「分析」互补,数字须一致):
110
112
 
111
113
  - 首行标题:`{结束日所在月份}月份数据复盘:`(例:`12月份数据复盘:`)。
112
- - 5 条结构化要点:
114
+ - 5 条结构化要点(**账户 Sheet 总览**;关键词/搜索词/设备/国家细节以各 Sheet **表末「分析」** 为准,此处可摘要呼应、**不得矛盾**):
113
115
  1. **账户层级**:整体消耗、展示、点击、点击率与 3% 基准对比、询盘/转化数、平均询盘成本(数据来自 stats/overview 与上表合计)。
114
116
  总结:
115
117
  建议:
116
- 2. **关键词层级**:高转化词、高消耗低转化词、出价/竞争力建议。
118
+ 2. **关键词层级**(摘要;详述见 Sheet `关键词` 表末分析):高转化词、高消耗低转化词、出价/竞争力。
117
119
  总结:
118
120
  建议:
119
- 3. **搜索字词层级**:标红/高意向词、否词与加词建议。
121
+ 3. **搜索字词层级**(摘要;详述见 Sheet `搜索词` 表末分析):高意向词、否词与加词。
120
122
  总结:
121
123
  建议:
122
- 4. **设备层级**(基于 `campaign-device`):各设备在系列上的消耗/转化分布;可点出高消耗系列×设备组合。
124
+ 4. **设备层级**(摘要;详述见 Sheet `设备` 表末分析):设备消耗/转化分布。
123
125
  总结:
124
126
  建议:
125
- 5. **国家层级**(基于 `campaign-geo-matched`):各国家/地区在系列上的消耗/转化;高消耗低转化地域与降权/调价建议。
127
+ 5. **国家层级**(摘要;详述见 Sheet `国家` 表末分析):地域消耗/转化与调价。
126
128
  总结:
127
129
  建议:
128
130
 
@@ -132,45 +134,49 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
132
134
 
133
135
  ### Sheet `关键词`
134
136
 
135
- | 行 | 内容 |
136
- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
137
- | R1 | `搜索关键字报告` |
138
- | R2 | 统计区间 |
137
+ | 行 | 内容 |
138
+ | --- | --- |
139
+ | R1 | `搜索关键字报告` |
140
+ | R2 | 统计区间 |
139
141
  | R3(A→L,12 列) | `关键字` \| `匹配类型` \| `广告系列` \| `广告组` \| `费用` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `转化次数` \| `每次转化费用` \| `转化率` |
140
- | R4… | `keywords-*.json`:`keywordMatchType`→匹配类型;`campaignName` / `adGroupName` 等按 outline;`ctr` / `conversionRate` 已归一,直接写入 |
142
+ | R4… | `keywords-*.json`:`keywordMatchType`→匹配类型;`campaignName` / `adGroupName` 等按 outline;`ctr` / `conversionRate` 已归一,直接写入 |
143
+ | 表末「分析」 | **总结**:Top 消耗/转化关键词、高消耗低转化或零转化词、匹配类型(广泛/词组/完全)分布。**建议**:暂停/降价、提价扩量、改匹配或拓词(须写关键词 + 表中费用/转化/CPC),1~3 条 |
141
144
 
142
145
  ---
143
146
 
144
147
  ### Sheet `搜索词`
145
148
 
146
- | 行 | 内容 |
147
- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
148
- | R1 | `搜索字词报告` |
149
- | R2 | 统计区间 |
150
- | R3(A→N,14 列) | `搜索字词` \| `匹配类型` \| `已添加/已排除` \| `广告系列` \| `广告组` \| `关键字` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `费用` \| `转化次数` \| `每次转化费用` \| `转化率` |
151
- | R4… | `search-terms-*.json`:`queryTargetingStatusZh`(`Added`→已添加、`Excluded`→已排除、`None`→都没有);无该字段时读 `queryTargetingStatus` 按上表映射,仍无则「—」;`ctr` / `conversionRate` 已归一,直接写入 |
149
+ | 行 | 内容 |
150
+ | --- | --- |
151
+ | R1 | `搜索字词报告` |
152
+ | R2 | 统计区间 |
153
+ | R3(A→N,14 列) | `搜索字词` \| `匹配类型` \| `已添加/已排除` \| `广告系列` \| `广告组` \| `关键字` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `费用` \| `转化次数` \| `每次转化费用` \| `转化率` |
154
+ | R4… | `search-terms-*.json`:`queryTargetingStatusZh`(`Added`→已添加、`Excluded`→已排除、`None`→都没有);无该字段时读 `queryTargetingStatus` 按上表映射,仍无则「—」;`ctr` / `conversionRate` 已归一,直接写入 |
155
+ | 表末「分析」 | **总结**:高意向/高转化搜索词、高消耗但未加为关键词的词、应否定的无关词(对比「已添加/已排除」列)。**建议**:加词、否词、落地页或系列调整(须写搜索词 + 表中数据),1~3 条 |
152
156
 
153
157
  ---
154
158
 
155
159
  ### Sheet `设备`
156
160
 
157
- | 行 | 内容 |
158
- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
159
- | R1 | `设备报告` |
160
- | R2 | 统计区间 |
161
- | R3(A→M,13 列) | `设备` \| `级别` \| `广告系列` \| `出价调整` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `费用` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
162
- | R4… | `campaign-device-*.json`(`devices[]`):`deviceType`→设备;`campaignName` / `adGroupName`→「广告系列」等;`allConversions`→「所有转化次数」;`conversions`→「转化次数」;`ctr` / `conversionRate` 直接写入;**出价调整**:bidModifier (0.5 表示 > 降低50%, 1.1表示提高10%,在表格中分别为-50%,+10%);**级别**列按运营约定(如填 `广告系列` 或 `账户`,全文一致) |
161
+ | 行 | 内容 |
162
+ | --- | --- |
163
+ | R1 | `设备报告` |
164
+ | R2 | 统计区间 |
165
+ | R3(A→M,13 列) | `设备` \| `级别` \| `广告系列` \| `出价调整` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `费用` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
166
+ | R4… | `campaign-device-*.json`(`devices[]`):`deviceType`→设备;`campaignName` / `adGroupName`→「广告系列」等;`allConversions`→「所有转化次数」;`conversions`→「转化次数」;`ctr` / `conversionRate` 直接写入;**出价调整**:bidModifier(0.5 表示降低 50%,1.1 表示提高 10%,在表格中分别为 -50%、+10%);**级别**列按运营约定(如填 `广告系列` 或 `账户`,全文一致) |
167
+ | 表末「分析」 | **总结**:各设备(Mobile/Desktop/Tablet 等)消耗与转化占比、系列×设备高消耗组合、移动端 vs 桌面 CPA/CPC 差异。**建议**:设备出价系数调整或系列预算倾斜(须引用设备类型与表中费用/转化),1~3 条 |
163
168
 
164
169
  ---
165
170
 
166
171
  ### Sheet `国家`
167
172
 
168
- | 行 | 内容 |
169
- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
170
- | R1 | `地理位置报告` |
171
- | R2 | 统计区间 |
172
- | R3(A→M,13 列) | `地理位置` \| `广告系列` \| `展示次数` \| `互动次数` \| `互动率` \| `费用` \| `平均费用` \| `点击次数` \| `点击率` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
173
- | R4… | `campaign-geo-matched-*.json` 的 `items[]`(`schemaVersion 3` 行统一在 `items`):`countryOrRegion`→地理位置;`campaignName`→「广告系列」;`allConversions`→「所有转化次数」;`conversions`→「转化次数」;**互动次数**→`interactions`;**互动率**→`interactionRate`(字符串须解析)或 `interactions/impressions`,`interactions` 为 0 时填 `—`;**平均费用 必须** = `spend / interactions`,`interactions` 为 0 / null / undefined 时填 `—`;`ctr` / `conversionRate` 直接写入;其余列按 outline 映射 |
173
+ | 行 | 内容 |
174
+ | --- | --- |
175
+ | R1 | `地理位置报告` |
176
+ | R2 | 统计区间 |
177
+ | R3(A→M,13 列) | `地理位置` \| `广告系列` \| `展示次数` \| `互动次数` \| `互动率` \| `费用` \| `平均费用` \| `点击次数` \| `点击率` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
178
+ | R4… | `campaign-geo-matched-*.json` 的 `items[]`(`schemaVersion 3` 行统一在 `items`):`countryOrRegion`→地理位置;`campaignName`→「广告系列」;`allConversions`→「所有转化次数」;`conversions`→「转化次数」;**互动次数**→`interactions`;**互动率**→`interactionRate`(字符串须解析)或 `interactions/impressions`,`interactions` 为 0 时填 `—`;**平均费用 必须** = `spend / interactions`,`interactions` 为 0 / null / undefined 时填 `—`;`ctr` / `conversionRate` 直接写入;其余列按 outline 映射 |
179
+ | 表末「分析」 | **总结**:Top 国家/地区消耗与转化、高消耗低转化地域、系列×地域组合。**建议**:地域加价/降价、排除或单独系列(须写国家/地区名 + 表中费用/转化/CPA),1~3 条 |
174
180
 
175
181
  ---
176
182
 
@@ -228,10 +234,9 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
228
234
 
229
235
  ## 复盘小结(Excel「数据复盘」区块 / 对内补充)
230
236
 
231
- 基于同一批 JSON(`campaigns`、`keywords`、`search-terms`、`campaign-device`、`campaign-geo-matched`、`overview`)写入 Sheet `账户报告` 下方 **「数据复盘」**(5 条结构化要点,见上文 xlsx 规范);与对外话术 **第 6 条优化建议** 可呼应但 **Excel 内须更细**,不得与话术数字矛盾。
237
+ 每个 Sheet **数据表格末尾**须写当 Sheet **表末「分析」**(见上文各 Sheet 规范);Sheet `账户报告` 另含跨维度 **「数据复盘」** 总览。
232
238
 
233
- - **复盘**:3~6 bullet:消耗集中度、高消耗系列/词、地域与设备差异、CTR/CPC 相对水平。
234
- - **优化建议**:3~5 条可执行项(预算、否词、落地页、系列状态等),**不额外拉数**;缺数据则标 `[数据不可用:原因]`。
239
+ 基于同一批 JSON 撰写;与对外话术 **第 6 条优化建议** 可呼应但 **Excel 内须更细**,各 Sheet 数字不得矛盾。
235
240
 
236
241
  ---
237
242
 
@@ -1,7 +1,8 @@
1
1
  # Bing(Microsoft Advertising / BingV2)— 账户分析报告(模板纲要)
2
2
 
3
3
  > 统计区间:`{startDate}` ~ `{endDate}`(**不可包含今天或昨天**,见下文)
4
- > 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
4
+ > 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
5
+ > **默认交付**:**HTML**(Agent 脚本读落盘 JSON 生成;禁止仅 Markdown 摘要或纯 JSON 充当终稿)。用户指定 Excel 时 Agent 脚本写 xlsx,**无** CLI `render` 子命令。
5
6
 
6
7
  数据块:总览、设备、地域、受众(年龄/性别)、系列、广告组、广告、关键词、搜索字词。
7
8
 
@@ -15,38 +16,170 @@
15
16
 
16
17
  ---
17
18
 
19
+ ## 拉数(一次目录)
20
+
21
+ ```bash
22
+ mkdir -p ./snap-bing
23
+
24
+ siluzan-tso list-accounts -m BingV2 -k <mediaCustomerId> --json-out ./snap-bing
25
+
26
+ siluzan-tso report bing-overview -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
27
+ siluzan-tso balance -m BingV2 --accounts <mediaCustomerId> --json-out ./snap-bing
28
+ siluzan-tso report bing-campaigns -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
29
+ siluzan-tso report bing-device -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
30
+ siluzan-tso report bing-geographic -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
31
+ siluzan-tso report bing-audience-merged -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
32
+ siluzan-tso report bing-ad-groups -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
33
+ siluzan-tso report bing-ads -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
34
+ siluzan-tso report bing-keywords -a <mediaCustomerId> --start <S> --end <E> --limit 100 --json-out ./snap-bing
35
+ siluzan-tso report bing-search-terms -a <mediaCustomerId> --start <S> --end <E> --json-out ./snap-bing
36
+ ```
37
+
38
+ - 写脚本前先读各 `bing-*-<id>.outline.txt`,再读 `.json`(见 `references/core/agent-conventions.md` §三)。
39
+ - TopN、排序、汇总均在脚本内完成,禁止心算。
40
+ - 金额字段已为**元**;`ctr` / `conversionRate` 等为 **0~1 小数**,展示百分比用 `(v * 100).toFixed(2) + '%'`。
41
+
42
+ ---
43
+
44
+ ## 分析纪律(全章节强约束)
45
+
46
+ **每个 section 的数据表格/图表之后,必须紧跟该 section 的「分析」小节**;整份报告**禁止**只有数据、没有分析。
47
+
48
+ | 要求 | 说明 |
49
+ | --- | --- |
50
+ | **总结** | 引用**当 section 落盘 JSON** 中的数字(TopN、占比、合计、环比);可对比账户 KPI 或 section 内均值 |
51
+ | **建议** | 1~3 条可执行项(预算、出价、暂停、否词、设备/地域调价等),须点名系列/关键词/国家/设备并引用数据 |
52
+ | **禁止** | 编造数字、空分析、全章共用一段笼统话而不分 section |
53
+ | **环比** | 仅 **总览** `currentPeriod` vs **`previousPeriod`**(CLI 可能二次拉数回填);**previousPeriod 全 0 时不写 0% 环比** |
54
+ | **缺数据** | 某 section 拉数失败或 `items` 为空 → 分析写 `[数据不可用:<原因>]`,**仍须保留分析小节标题** |
55
+
56
+ 报告 HTML 建议结构:按下方章节顺序,每章 = **标题 → 数据表/指标卡 → 「分析」**(含总结 + 建议)。
57
+
58
+ ---
59
+
18
60
  ## 1. 执行摘要(总览)
19
61
 
20
62
  - **CLI**:`siluzan-tso report bing-overview -a <mediaCustomerId> [--start … --end …] --json-out <dir>`
21
- - **余额**:若 `OverviewSectionData` 误返 `remainingAccountBudget: 0`,CLI 会自动用 `GetMediaAccountInfo` 实时余额回填(与 Web 账户列表一致);**仍建议**单查余额用 `balance -m BingV2`。
22
- - **日均消耗 / 活跃天数**:Bing 网关常不返回 `averageDailyCost`、`activeDays`(或为 0)。CLI 落盘前会:① 缺 `totalCost` 时用 `currentPeriod.spend`;② 缺 `activeDays` 时用 `--start`~`--end` 含首尾日历天数;③ `averageDailyCost = totalCost / activeDays`(保留 2 位小数)。报告脚本直接读落盘字段即可。
23
63
 
24
- ## 2. 设备与地域
64
+ ### 余额(必读 · 勿读 overview 快照)
65
+
66
+ `OverviewSectionData` 里的 `balance` / `remainingAccountBudget` **不是实时余额**,是报告生成时的快照,**常为 0 或未同步**。
67
+
68
+ | 正确做法 | 错误做法 |
69
+ | --- | --- |
70
+ | 读落盘 JSON 的 **`remainingAccountBudget`** 或 **`balance`**(CLI 已用 `GetMediaAccountInfo` 回填) | 直接相信 overview 原始响应里的 0 |
71
+ | 字段 **`_balanceSource: "GetMediaAccountInfo"`** 表示已校正 | 把 `$0.00` 写进报告而不说明 |
72
+ | 仍须独立核对:`siluzan-tso balance -m BingV2 --accounts <id> --json-out <dir>` | 用 `stats` 或 overview 推断余额 |
73
+
74
+ CLI 落盘时会:overview 余额为 0 → 调 `GetMediaAccountInfo` → 写回 `remainingAccountBudget` 与 **`balance`**(与 Web `filterOverviewData` 一致)。
75
+
76
+ ### 环比(必读 · 勿读空 previousPeriod)
77
+
78
+ 网关内嵌的 **`previousPeriod` 常全为 0**。CLI 若检测到为空,会按 Web `calcPrePeriodTimeRange` 规则再请求**上一周期**的 `OverviewSectionData`,将其 **`currentPeriod`** 写入 `previousPeriod`,并标注 **`_previousPeriodSource: "OverviewSectionData-second-fetch"`**。
79
+
80
+ | 场景 | 报告写法 |
81
+ | --- | --- |
82
+ | `previousPeriod.spend > 0`(含二次拉数回填) | 可写消耗/点击/转化/CTR/CPC/CPA 环比 |
83
+ | `previousPeriod` 仍为空或全 0 | **不写环比为 0%**;写「上一周期数据不可用」 |
84
+ | 有 `previousPeriodDateRange` | 分析中注明对比区间 |
85
+
86
+ ### 日均消耗 / 活跃天数
87
+
88
+ Bing 网关常不返回 `averageDailyCost`、`activeDays`(或为 0)。CLI 落盘前会:① 缺 `totalCost` 时用 `currentPeriod.spend`;② 缺 `activeDays` 时用 `--start`~`--end` 含首尾日历天数;③ `averageDailyCost = totalCost / activeDays`(保留 2 位小数)。
89
+
90
+ **数据呈现**:区间消耗、展示、点击、转化、CTR、CPC、CPA、日均消耗、**实时余额**(`balance`)、优化分;**有效** `previousPeriod` 时展示环比。
91
+
92
+ **分析(必写)**:
93
+
94
+ - **总结**:本期 vs 上期消耗/点击/转化/CTR/CPC/CPA 变化(**仅当 previousPeriod 有数据**);展示份额(`searchImpressionShare`)及预算/排名丢失份额;余额与日均消耗可支撑天数(用 **`balance` / `remainingAccountBudget`**,非 overview 快照 0)。
95
+ - **建议**:账户级预算或投放节奏(引用具体百分比或金额);若展示份额丢失高 → 预算或出价方向;1~3 条。
96
+
97
+ ---
98
+
99
+ ## 2. 设备
100
+
101
+ - **CLI**:`report bing-device` → `DeviceSectionData`(行在 `devices[]`)
102
+
103
+ **数据呈现**:各 `deviceType` 的展示、点击、消耗、CTR、CPC、转化、CPA;建议附消耗占比。
25
104
 
26
- - 设备:`report bing-device` → `DeviceSectionData`
27
- - 地域:`report bing-geographic` → `GeographicSectionData`(响应中常见 `countries` 等字段,**以实际 JSON 为准**)
105
+ **分析(必写)**:
28
106
 
29
- ## 3. 受众
107
+ - **总结**:主消耗设备、各设备 CPA/CPC 差异、高消耗低转化设备。
108
+ - **建议**:设备出价调整或预算向高转化设备倾斜(写设备名 + 表中数字),1~3 条。
30
109
 
31
- - 年龄:`report bing-age-audience` → `AgeAudienceData`
32
- - 性别:`report bing-gender-audience` → `GenderAudienceData`
33
- - 合并输出:`report bing-audience-merged`(**固定输出合并 JSON**,含年龄+性别)
110
+ ---
111
+
112
+ ## 3. 地域
113
+
114
+ - **CLI**:`report bing-geographic` → `GeographicSectionData`(常见 `countries[]`,**以 outline 为准**)
115
+
116
+ **数据呈现**:Top 国家/地区(建议 Top 10)消耗、点击、转化、CTR、CPC、CPA。
117
+
118
+ **分析(必写)**:
119
+
120
+ - **总结**:地域消耗集中度、高转化 vs 高消耗低转化国家/地区。
121
+ - **建议**:地域加价/降价/排除或单独系列(写 `countryOrRegion` + 数据),1~3 条。
122
+
123
+ ---
124
+
125
+ ## 4. 受众
126
+
127
+ - **CLI**:推荐 `report bing-audience-merged`(年龄+性别合并 JSON);或分别 `bing-age-audience` / `bing-gender-audience`
128
+
129
+ **数据呈现**:年龄段、性别的展示、点击、消耗、CTR、CPC(按 `data.ageAudience.audience` / `data.genderAudience.audience`)。
130
+
131
+ **分析(必写)**:
132
+
133
+ - **总结**:主力年龄段/性别、转化或 CPA 更优的受众段、无效花费受众。
134
+ - **建议**:受众出价_modifier 或排除(引用 `audience` 字段 + 指标),1~3 条。
135
+
136
+ ---
34
137
 
35
- ## 4. 广告结构
138
+ ## 5. 广告结构(系列 / 广告组 / 广告)
36
139
 
37
- - 系列:`report bing-campaigns` `CampaignReport`
38
- - 广告组:`report bing-ad-groups` `AdGroupReport`
39
- - 广告:`report bing-ads` `AdReport`
140
+ | 层级 | CLI | 落盘 |
141
+ | --- | --- | --- |
142
+ | 系列 | `report bing-campaigns` | `bing-campaigns-*.json`(数组行) |
143
+ | 广告组 | `report bing-ad-groups` | `bing-ad-groups-*.json` |
144
+ | 广告 | `report bing-ads` | `bing-ads-*.json` |
40
145
 
41
- ## 5. 关键词与搜索字词
146
+ **数据呈现**:各表按消耗降序;系列含 `campaignStatus`;广告组可含质量分相关字段(以 outline 为准)。
42
147
 
43
- - 关键词:`report bing-keywords` → `KeywordReport`,Query 含 `limit`(默认 **100**)、`orderByCost=true`
44
- - 搜索字词:`report bing-search-terms` → `SearchQueryReport`,参数同上
148
+ **分析(必写,三个子块各写一段,不可合并为一句带过)**:
149
+
150
+ 1. **系列分析 — 总结**:Top 系列及费用占比、暂停/活跃系列效果差异、合计 CTR/CPC/CPA。**建议**:预算增减、暂停或放量具体 `campaignName`,1~3 条。
151
+ 2. **广告组分析 — 总结**:高消耗广告组、质量分或 CPA 异常组。**建议**:出价、暂停或结构优化(写 `adGroupName` + 数据),1~3 条。
152
+ 3. **广告分析 — 总结**:Top 消耗创意、CTR/转化表现分化。**建议**:暂停低效广告、复制高效创意方向,1~3 条。
153
+
154
+ ---
155
+
156
+ ## 6. 关键词与搜索字词
157
+
158
+ | 类型 | CLI | 说明 |
159
+ | --- | --- | --- |
160
+ | 关键词 | `report bing-keywords` | 默认 `limit=100`、`orderByCost=true` |
161
+ | 搜索字词 | `report bing-search-terms` | 同上 |
162
+
163
+ **数据呈现**:Top 关键词/搜索词表(消耗、CTR、CPC、转化、CPA、质量分/匹配类型等,以 outline 为准)。
164
+
165
+ **分析(必写,两块各写一段)**:
166
+
167
+ 1. **关键词 — 总结**:高转化词、高消耗低转化或零转化词、匹配类型分布。**建议**:暂停/降价/提价/改匹配(写 `keyword` + 数据),1~3 条。
168
+ 2. **搜索字词 — 总结**:高意向词、应加为关键词的词、应否定的无关词(对比触发 `keyword`)。**建议**:加词、否词(写 `searchQuery` + 数据),1~3 条。
169
+
170
+ ---
171
+
172
+ ## 7. 报告收尾(全账户)
173
+
174
+ 在以上各 section 分析之后,可增加 **「优化建议汇总」**(3~5 条),跨 section 归纳优先级,**须与前面各 section 分析一致、不得矛盾**;可呼应对外客户话术(若用户需要)。
175
+
176
+ ---
45
177
 
46
- ## 6. 附录
178
+ ## 附录
47
179
 
48
180
  - 鉴权:与 TSO 其他接口相同(`config show` 中 `tsoApiBaseUrl` / Token)。
49
181
  - 与 steward「优化报告」区别:见 `meta-period-report.md` 末节;此处为**实时分析 JSON**。
182
+ - 交付前自检:见 `references/core/agent-conventions.md` §七(币种、区间、章节齐全、数字来自脚本 stdout)。
50
183
 
51
184
  ---
52
185
 
@@ -91,6 +91,7 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
91
91
  - **R2(A2)**:统计区间,中文可读:`YYYY年M月D日 - YYYY年M月D日`。
92
92
  - **R3**:表头(列名、顺序须与下表一致)。
93
93
  - **R4 起**:数据行;列宽、自动筛选、首行冻结、降序排序为可选,**不得**因此改数。
94
+ - **表末「分析」**(强约束):数据区(含「合计」行若有)结束后 **留白 1~2 行**,必须写入 **「分析」** 标题区块;须含 **总结**(引用**当 Sheet 表格**中的数字,可 TopN / 占比 / 与合计行对比)+ **建议**(1~3 条可执行项);禁止编造、禁止留空;各 Sheet 具体分析要点见下表「表末「分析」」行。
94
95
  - **百分比列**:样表为 0~1 小数(如 7.95% 存 `0.08`);与运营约定二选一,**禁止**同一文件混用。
95
96
  - **金额**:与 `references/analytics/account-analytics.md` 一致;`campaigns.budgetAmountYuan`、`spend`、`averageCpc`、`costPerConversion` 均为元,可直接写入。
96
97
 
@@ -98,31 +99,32 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
98
99
 
99
100
  ### Sheet `账户报告`
100
101
 
101
- | 行 | 内容 |
102
- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
103
- | R1 | `广告系列报告`(如运营改名,可改文案但 Sheet 名仍为 `账户报告`) |
104
- | R2 | 统计区间 |
105
- | R3(A→K,11 列) | `广告系列` \| `预算` \| `费用` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
106
- | R4… | 来自 `campaigns-*.json` 的 `items[]`(`schemaVersion 3` 行统一在 `items`):`campaignName`、`budgetAmountYuan`(元)、`spend`、`impressions`、`clicks`、`ctr`(已归一直接写入「点击率」)、`averageCpc`、`allConversions`→「所有转化次数」、`conversions`→「转化次数」、`costPerConversion`、`conversionRate`(已归一直接写入「转化率」);缺 `allConversions` 时与「转化次数」同值或填 `0` / `—`,并在脚注说明 |
107
- | 末行「合计」 | **广告系列**列填 `总计`;**预算**列填 `--`;**展示 / 点击 / 所有转化次数 / 转化次数 / 费用** 做列求和;**点击率** = 合计点击 ÷ 合计展示;**转化率** = 合计转化 ÷ 合计点击(**禁止**用合计转化 ÷ 合计展示);**每次转化费用** = 合计费用 ÷ 合计转化(转化为 0 时填 `—`);禁止对各行比率取算术平均 |
102
+ | 行 | 内容 |
103
+ | --- | --- |
104
+ | R1 | `广告系列报告`(如运营改名,可改文案但 Sheet 名仍为 `账户报告`) |
105
+ | R2 | 统计区间 |
106
+ | R3(A→K,11 列) | `广告系列` \| `预算` \| `费用` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
107
+ | R4… | 来自 `campaigns-*.json` 的 `items[]`(`schemaVersion 3` 行统一在 `items`):`campaignName`、`budgetAmountYuan`(元)、`spend`、`impressions`、`clicks`、`ctr`(已归一直接写入「点击率」)、`averageCpc`、`allConversions`→「所有转化次数」、`conversions`→「转化次数」、`costPerConversion`、`conversionRate`(已归一直接写入「转化率」);缺 `allConversions` 时与「转化次数」同值或填 `0` / `—`,并在脚注说明 |
108
+ | 末行「合计」 | **广告系列**列填 `总计`;**预算**列填 `--`;**展示 / 点击 / 所有转化次数 / 转化次数 / 费用** 做列求和;**点击率** = 合计点击 ÷ 合计展示;**转化率** = 合计转化 ÷ 合计点击(**禁止**用合计转化 ÷ 合计展示);**每次转化费用** = 合计费用 ÷ 合计转化(转化为 0 时填 `—`);禁止对各行比率取算术平均 |
109
+ | 表末「分析」 | **总结**:Top 消耗系列及费用占比、合计/系列 CTR·CPC·CPA 水平、预算与费用对比(预算跑满/空耗/超支系列);可对照 stats/overview 账户级指标。**建议**:预算增减、暂停或放量具体系列(须写系列名 + 引用表中数字),1~3 条 |
108
110
 
109
- 表下**留白若干行**后写 **「数据复盘」**:
111
+ 表下**留白若干行**后写 **「数据复盘」**(账户 Sheet 汇总向,与各 Sheet 表末「分析」互补,数字须一致):
110
112
 
111
113
  - 首行标题:`{结束日所在月份}月份数据复盘:`(例:`12月份数据复盘:`)。
112
- - 5 条结构化要点:
114
+ - 5 条结构化要点(**账户 Sheet 总览**;关键词/搜索词/设备/国家细节以各 Sheet **表末「分析」** 为准,此处可摘要呼应、**不得矛盾**):
113
115
  1. **账户层级**:整体消耗、展示、点击、点击率与 3% 基准对比、询盘/转化数、平均询盘成本(数据来自 stats/overview 与上表合计)。
114
116
  总结:
115
117
  建议:
116
- 2. **关键词层级**:高转化词、高消耗低转化词、出价/竞争力建议。
118
+ 2. **关键词层级**(摘要;详述见 Sheet `关键词` 表末分析):高转化词、高消耗低转化词、出价/竞争力。
117
119
  总结:
118
120
  建议:
119
- 3. **搜索字词层级**:标红/高意向词、否词与加词建议。
121
+ 3. **搜索字词层级**(摘要;详述见 Sheet `搜索词` 表末分析):高意向词、否词与加词。
120
122
  总结:
121
123
  建议:
122
- 4. **设备层级**(基于 `campaign-device`):各设备在系列上的消耗/转化分布;可点出高消耗系列×设备组合。
124
+ 4. **设备层级**(摘要;详述见 Sheet `设备` 表末分析):设备消耗/转化分布。
123
125
  总结:
124
126
  建议:
125
- 5. **国家层级**(基于 `campaign-geo-matched`):各国家/地区在系列上的消耗/转化;高消耗低转化地域与降权/调价建议。
127
+ 5. **国家层级**(摘要;详述见 Sheet `国家` 表末分析):地域消耗/转化与调价。
126
128
  总结:
127
129
  建议:
128
130
 
@@ -132,45 +134,49 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
132
134
 
133
135
  ### Sheet `关键词`
134
136
 
135
- | 行 | 内容 |
136
- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
137
- | R1 | `搜索关键字报告` |
138
- | R2 | 统计区间 |
137
+ | 行 | 内容 |
138
+ | --- | --- |
139
+ | R1 | `搜索关键字报告` |
140
+ | R2 | 统计区间 |
139
141
  | R3(A→L,12 列) | `关键字` \| `匹配类型` \| `广告系列` \| `广告组` \| `费用` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `转化次数` \| `每次转化费用` \| `转化率` |
140
- | R4… | `keywords-*.json`:`keywordMatchType`→匹配类型;`campaignName` / `adGroupName` 等按 outline;`ctr` / `conversionRate` 已归一,直接写入 |
142
+ | R4… | `keywords-*.json`:`keywordMatchType`→匹配类型;`campaignName` / `adGroupName` 等按 outline;`ctr` / `conversionRate` 已归一,直接写入 |
143
+ | 表末「分析」 | **总结**:Top 消耗/转化关键词、高消耗低转化或零转化词、匹配类型(广泛/词组/完全)分布。**建议**:暂停/降价、提价扩量、改匹配或拓词(须写关键词 + 表中费用/转化/CPC),1~3 条 |
141
144
 
142
145
  ---
143
146
 
144
147
  ### Sheet `搜索词`
145
148
 
146
- | 行 | 内容 |
147
- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
148
- | R1 | `搜索字词报告` |
149
- | R2 | 统计区间 |
150
- | R3(A→N,14 列) | `搜索字词` \| `匹配类型` \| `已添加/已排除` \| `广告系列` \| `广告组` \| `关键字` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `费用` \| `转化次数` \| `每次转化费用` \| `转化率` |
151
- | R4… | `search-terms-*.json`:`queryTargetingStatusZh`(`Added`→已添加、`Excluded`→已排除、`None`→都没有);无该字段时读 `queryTargetingStatus` 按上表映射,仍无则「—」;`ctr` / `conversionRate` 已归一,直接写入 |
149
+ | 行 | 内容 |
150
+ | --- | --- |
151
+ | R1 | `搜索字词报告` |
152
+ | R2 | 统计区间 |
153
+ | R3(A→N,14 列) | `搜索字词` \| `匹配类型` \| `已添加/已排除` \| `广告系列` \| `广告组` \| `关键字` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `费用` \| `转化次数` \| `每次转化费用` \| `转化率` |
154
+ | R4… | `search-terms-*.json`:`queryTargetingStatusZh`(`Added`→已添加、`Excluded`→已排除、`None`→都没有);无该字段时读 `queryTargetingStatus` 按上表映射,仍无则「—」;`ctr` / `conversionRate` 已归一,直接写入 |
155
+ | 表末「分析」 | **总结**:高意向/高转化搜索词、高消耗但未加为关键词的词、应否定的无关词(对比「已添加/已排除」列)。**建议**:加词、否词、落地页或系列调整(须写搜索词 + 表中数据),1~3 条 |
152
156
 
153
157
  ---
154
158
 
155
159
  ### Sheet `设备`
156
160
 
157
- | 行 | 内容 |
158
- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
159
- | R1 | `设备报告` |
160
- | R2 | 统计区间 |
161
- | R3(A→M,13 列) | `设备` \| `级别` \| `广告系列` \| `出价调整` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `费用` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
162
- | R4… | `campaign-device-*.json`(`devices[]`):`deviceType`→设备;`campaignName` / `adGroupName`→「广告系列」等;`allConversions`→「所有转化次数」;`conversions`→「转化次数」;`ctr` / `conversionRate` 直接写入;**出价调整**:bidModifier (0.5 表示 > 降低50%, 1.1表示提高10%,在表格中分别为-50%,+10%);**级别**列按运营约定(如填 `广告系列` 或 `账户`,全文一致) |
161
+ | 行 | 内容 |
162
+ | --- | --- |
163
+ | R1 | `设备报告` |
164
+ | R2 | 统计区间 |
165
+ | R3(A→M,13 列) | `设备` \| `级别` \| `广告系列` \| `出价调整` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `费用` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
166
+ | R4… | `campaign-device-*.json`(`devices[]`):`deviceType`→设备;`campaignName` / `adGroupName`→「广告系列」等;`allConversions`→「所有转化次数」;`conversions`→「转化次数」;`ctr` / `conversionRate` 直接写入;**出价调整**:bidModifier(0.5 表示降低 50%,1.1 表示提高 10%,在表格中分别为 -50%、+10%);**级别**列按运营约定(如填 `广告系列` 或 `账户`,全文一致) |
167
+ | 表末「分析」 | **总结**:各设备(Mobile/Desktop/Tablet 等)消耗与转化占比、系列×设备高消耗组合、移动端 vs 桌面 CPA/CPC 差异。**建议**:设备出价系数调整或系列预算倾斜(须引用设备类型与表中费用/转化),1~3 条 |
163
168
 
164
169
  ---
165
170
 
166
171
  ### Sheet `国家`
167
172
 
168
- | 行 | 内容 |
169
- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
170
- | R1 | `地理位置报告` |
171
- | R2 | 统计区间 |
172
- | R3(A→M,13 列) | `地理位置` \| `广告系列` \| `展示次数` \| `互动次数` \| `互动率` \| `费用` \| `平均费用` \| `点击次数` \| `点击率` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
173
- | R4… | `campaign-geo-matched-*.json` 的 `items[]`(`schemaVersion 3` 行统一在 `items`):`countryOrRegion`→地理位置;`campaignName`→「广告系列」;`allConversions`→「所有转化次数」;`conversions`→「转化次数」;**互动次数**→`interactions`;**互动率**→`interactionRate`(字符串须解析)或 `interactions/impressions`,`interactions` 为 0 时填 `—`;**平均费用 必须** = `spend / interactions`,`interactions` 为 0 / null / undefined 时填 `—`;`ctr` / `conversionRate` 直接写入;其余列按 outline 映射 |
173
+ | 行 | 内容 |
174
+ | --- | --- |
175
+ | R1 | `地理位置报告` |
176
+ | R2 | 统计区间 |
177
+ | R3(A→M,13 列) | `地理位置` \| `广告系列` \| `展示次数` \| `互动次数` \| `互动率` \| `费用` \| `平均费用` \| `点击次数` \| `点击率` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
178
+ | R4… | `campaign-geo-matched-*.json` 的 `items[]`(`schemaVersion 3` 行统一在 `items`):`countryOrRegion`→地理位置;`campaignName`→「广告系列」;`allConversions`→「所有转化次数」;`conversions`→「转化次数」;**互动次数**→`interactions`;**互动率**→`interactionRate`(字符串须解析)或 `interactions/impressions`,`interactions` 为 0 时填 `—`;**平均费用 必须** = `spend / interactions`,`interactions` 为 0 / null / undefined 时填 `—`;`ctr` / `conversionRate` 直接写入;其余列按 outline 映射 |
179
+ | 表末「分析」 | **总结**:Top 国家/地区消耗与转化、高消耗低转化地域、系列×地域组合。**建议**:地域加价/降价、排除或单独系列(须写国家/地区名 + 表中费用/转化/CPA),1~3 条 |
174
180
 
175
181
  ---
176
182
 
@@ -228,10 +234,9 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
228
234
 
229
235
  ## 复盘小结(Excel「数据复盘」区块 / 对内补充)
230
236
 
231
- 基于同一批 JSON(`campaigns`、`keywords`、`search-terms`、`campaign-device`、`campaign-geo-matched`、`overview`)写入 Sheet `账户报告` 下方 **「数据复盘」**(5 条结构化要点,见上文 xlsx 规范);与对外话术 **第 6 条优化建议** 可呼应但 **Excel 内须更细**,不得与话术数字矛盾。
237
+ 每个 Sheet **数据表格末尾**须写当 Sheet **表末「分析」**(见上文各 Sheet 规范);Sheet `账户报告` 另含跨维度 **「数据复盘」** 总览。
232
238
 
233
- - **复盘**:3~6 bullet:消耗集中度、高消耗系列/词、地域与设备差异、CTR/CPC 相对水平。
234
- - **优化建议**:3~5 条可执行项(预算、否词、落地页、系列状态等),**不额外拉数**;缺数据则标 `[数据不可用:原因]`。
239
+ 基于同一批 JSON 撰写;与对外话术 **第 6 条优化建议** 可呼应但 **Excel 内须更细**,各 Sheet 数字不得矛盾。
235
240
 
236
241
  ---
237
242
 
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  $PKG_NAME = 'siluzan-tso-cli'
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- $PKG_VERSION = '1.1.34-beta.3'
12
+ $PKG_VERSION = '1.1.34-beta.4'
13
13
  $CLI_BIN = 'siluzan-tso'
14
14
  $SKILL_LABEL = 'Siluzan TSO'
15
15
  $INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  readonly PKG_NAME="siluzan-tso-cli"
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- readonly PKG_VERSION="1.1.34-beta.3"
12
+ readonly PKG_VERSION="1.1.34-beta.4"
13
13
  readonly CLI_BIN="siluzan-tso"
14
14
  readonly SKILL_LABEL="Siluzan TSO"
15
15
  readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
@@ -45,7 +45,7 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
45
45
  **任务**:
46
46
 
47
47
  1. Read `report-templates/okki-weekly-google-client.md` 中 xlsx 版式与 **§对外客户话术**(默认 7 条模板;主 Agent 若指定自定义话术则从其指示)。
48
- 2. 编写并执行 Node/Python 脚本,产出 `.xlsx` + 客户话术。
48
+ 2. 编写并执行 Node/Python 脚本,产出 `.xlsx` + 客户话术;**每个 Sheet 数据表末须写「分析」**(总结 + 建议,见模板各 Sheet「表末「分析」」行)。
49
49
  3. 设备/国家 Sheet 必须用 `campaign-device` / `campaign-geo-matched` 落盘文件。
50
50
 
51
51
  **returnSchema**: 产出文件路径列表 + 脚本 exitCode;勿贴 xlsx 二进制内容。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.34-beta.3",
3
+ "version": "1.1.34-beta.4",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",