siluzan-tso-cli 1.1.53-beta.5 → 1.1.53-beta.7

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.53-beta.5),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.53-beta.7),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -103326,6 +103326,18 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
103326
103326
  );
103327
103327
  }
103328
103328
  }
103329
+ {
103330
+ const rows = items(sectionMap.get("audience"));
103331
+ if (rows.length > 0) {
103332
+ tables.audience = rows.map((r) => {
103333
+ const mapped = pickMetricRow(r, ["audience", "audienceName", "audienceType", "campaignName"]);
103334
+ if (mapped.audience == null && mapped.audienceName != null) {
103335
+ mapped.audience = mapped.audienceName;
103336
+ }
103337
+ return mapped;
103338
+ });
103339
+ }
103340
+ }
103329
103341
  {
103330
103342
  const rows = items(sectionMap.get("keywords"));
103331
103343
  if (rows.length > 0) {
@@ -103354,6 +103366,7 @@ var init_merge_snapshot = __esm({
103354
103366
  "campaigns",
103355
103367
  "devices",
103356
103368
  "geographic",
103369
+ "audience",
103357
103370
  "keywords"
103358
103371
  ];
103359
103372
  }
@@ -104266,7 +104279,7 @@ function validateGooglePeriodReportContent(data) {
104266
104279
  pushMissing2(
104267
104280
  missing,
104268
104281
  "narrative-recommendations",
104269
- "\u2467\u4F18\u5316\u5EFA\u8BAE",
104282
+ "\u2468\u4F18\u5316\u5EFA\u8BAE",
104270
104283
  "narrative.recommendations",
104271
104284
  `\u22653 \u6761\u8DE8\u7EF4\u5EA6\u4F18\u5316\u5EFA\u8BAE\uFF08\u5F53\u524D ${recommendations.length} \u6761\uFF09`
104272
104285
  );
@@ -104286,7 +104299,7 @@ function formatGooglePeriodReportErrors(result) {
104286
104299
  if (result.ok) return "";
104287
104300
  const lines = result.missing.map((m) => ` - [${m.chapter}] ${m.dimension}\uFF1A${m.hint}`);
104288
104301
  return [
104289
- `\u62A5\u544A JSON \u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 google-period-report.md 8 \u7EF4\u5EA6\uFF09\uFF1A`,
104302
+ `\u62A5\u544A JSON \u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 google-period-report.md 9 \u7AE0\uFF09\uFF1A`,
104290
104303
  ...lines,
104291
104304
  "",
104292
104305
  "\u8BF7\u8865\u5168 narrative.sections.<dimension>.{analysis,suggestions} \u4E0E narrative.recommendations \u540E\u91CD\u65B0\u6267\u884C render\u3002",
@@ -104304,6 +104317,7 @@ var init_report_content = __esm({
104304
104317
  "campaigns",
104305
104318
  "devices",
104306
104319
  "geographic",
104320
+ "audience",
104307
104321
  "keywords"
104308
104322
  ];
104309
104323
  SECTION_LABELS = {
@@ -104313,7 +104327,8 @@ var init_report_content = __esm({
104313
104327
  campaigns: "\u2463\u5E7F\u544A\u7CFB\u5217\u8868\u73B0",
104314
104328
  devices: "\u2464\u8BBE\u5907\u5206\u5E03",
104315
104329
  geographic: "\u2465\u5730\u57DF\u5206\u5E03",
104316
- keywords: "\u2466\u5173\u952E\u8BCD\u8868\u73B0"
104330
+ audience: "\u2466\u53D7\u4F17\u5206\u5E03",
104331
+ keywords: "\u2467\u5173\u952E\u8BCD\u8868\u73B0"
104317
104332
  };
104318
104333
  }
104319
104334
  });
@@ -104400,7 +104415,7 @@ async function runGoogleAnalysisRender(opts) {
104400
104415
  \u2705 Google Ads \u5468\u671F\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
104401
104416
  `);
104402
104417
  console.log(
104403
- ` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u6267\u884C\u6458\u8981 ${stats.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats.recommendationCount} \u6761\uFF5C7 \u7EF4\u5EA6\u5206\u6790+\u5EFA\u8BAE\u5747\u5DF2\u9F50\u5168
104418
+ ` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u6267\u884C\u6458\u8981 ${stats.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats.recommendationCount} \u6761\uFF5C8 \u4E2A\u6570\u636E\u7EF4\u5206\u6790+\u5EFA\u8BAE\u5747\u5DF2\u9F50\u5168
104404
104419
  `
104405
104420
  );
104406
104421
  console.log(
@@ -105919,7 +105934,7 @@ function registerGoogleAnalysisCommands(program2) {
105919
105934
  );
105920
105935
  root.addCommand(registerRunCommand(), { isDefault: true });
105921
105936
  root.command("render").description(
105922
- "\u6839\u636E Agent \u64B0\u5199\u7684\u62A5\u544A JSON \u751F\u6210 Google Ads \u5468\u671F\u5206\u6790 HTML \u7EC8\u7A3F\uFF08\u6821\u9A8C 8 \u7EF4\u5EA6\u5FC5\u542B\u5B57\u6BB5\uFF0C\u7F3A\u9879\u62D2\u7EDD\u6E32\u67D3\uFF09"
105937
+ "\u6839\u636E Agent \u64B0\u5199\u7684\u62A5\u544A JSON \u751F\u6210 Google Ads \u5468\u671F\u5206\u6790 HTML \u7EC8\u7A3F\uFF08\u6821\u9A8C 9 \u7AE0\u5FC5\u542B\u5B57\u6BB5\uFF0C\u542B\u53D7\u4F17\uFF0C\u7F3A\u9879\u62D2\u7EDD\u6E32\u67D3\uFF09"
105923
105938
  ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 google-period-report.json").option(
105924
105939
  "--snapshot-dir <dir>",
105925
105940
  "\u53EF\u9009\uFF1Agoogle-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u5408\u5E76 KPI / \u56FE\u8868 / \u8868\u683C"
@@ -120,7 +120,7 @@ siluzan-tso -h
120
120
  | 用户意图(关键词) | 工作流 | 必读文档 |
121
121
  | ------------------ | ------ | -------- |
122
122
  | Google 广告账户诊断 / 健康检查(**含账户 ID**) | **P1** | `references/analytics/account-analytics.md` + `report-templates/google-ads-diagnosis.md`(可选对照 `report-templates/google-account-diagnosis-report.md`) |
123
- | Google 账户周期 **数据汇总**(只要花费/转化/8 维/Excel;**无**规划/客户版/大族) | **P4** | `report-templates/google-period-report.md` + `references/analytics/account-analytics.md`;Excel 加 `google-period-report-excel.md` |
123
+ | Google 账户周期 **数据汇总**(只要花费/转化/9 章/Excel;**无**规划/客户版/大族) | **P4** | `report-templates/google-period-report.md` + `references/analytics/account-analytics.md`;Excel 加 `google-period-report-excel.md` |
124
124
  | Google/谷歌 **月度报告** / **月总结与规划** / **季度报告** / **Q3 汇总** / **Q4 规划** / **季度汇报** / **投放季报** / 客户版 / 大族模板 / 下月规划(效率+区域询盘+国家明细) | **P10** | `report-templates/google-quarterly-report.md` + **`report-templates/google-quarterly-plan.md`**(**未带询盘表须先问并等待**,禁止不问就 collect;有表须先转 JSON 再 `--inquiries`,禁止把 xlsx 丢给 collect;`collect`+`render`;规划九章;花费三档基于本期、不是已批预算;**不做**行业数据) |
125
125
  | Meta/Facebook 周期或诊断 | **P4-FB** | `report-templates/meta-period-report.md` + `assets/meta-period-report-rules.md` + `references/analytics/facebook-analysis-guide.md` |
126
126
  | TikTok / Bing 周期报告 | **P4** | `references/analytics/account-analytics.md` + 对应 `report-templates/*-period-report.md` |
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.53-beta.5",
4
- "publishedAt": 1789876126053
3
+ "version": "1.1.53-beta.7",
4
+ "publishedAt": 1789961917566
5
5
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "siluzan-tso/google-period-report.schema.json",
4
4
  "title": "GooglePeriodReport",
5
- "description": "Google Ads 周期分析报告 JSON(对齐 google-period-report.md 8 默认维度;供 google-analysis render 注入 HTML)。Agent 只需撰写 narrative;kpis/charts/tables 由 --snapshot-dir 从 google-analysis 快照合并。render 校验要求合并后 kpis.cost/impressions/clicks 至少一项为有效数字,否则拒绝出 HTML(避免执行摘要卡片全「—」)。",
5
+ "description": "Google Ads 周期分析报告 JSON(对齐 google-period-report.md 9 章:8 个数据维 + 优化建议;供 google-analysis render 注入 HTML)。Agent 只需撰写 narrative;kpis/charts/tables 由 --snapshot-dir 从 google-analysis 快照合并。render 校验要求合并后 kpis.cost/impressions/clicks 至少一项为有效数字,否则拒绝出 HTML(避免执行摘要卡片全「—」)。",
6
6
  "type": "object",
7
7
  "required": ["meta", "narrative"],
8
8
  "properties": {
@@ -92,6 +92,7 @@
92
92
  "campaigns": { "$ref": "#/$defs/campaignRows" },
93
93
  "devices": { "$ref": "#/$defs/deviceRows" },
94
94
  "geographic": { "$ref": "#/$defs/geographicRows" },
95
+ "audience": { "$ref": "#/$defs/audienceRows" },
95
96
  "keywords": { "$ref": "#/$defs/keywordRows" }
96
97
  }
97
98
  },
@@ -108,7 +109,7 @@
108
109
  },
109
110
  "sections": {
110
111
  "type": "object",
111
- "description": "7 个数据维度各自的分析+建议(对应报告 §1~§7);每维度 analysis ≥2 条、suggestions ≥1 条",
112
+ "description": "8 个数据维度各自的分析+建议(对应报告 §1~§8);每维度 analysis ≥2 条、suggestions ≥1 条",
112
113
  "required": [
113
114
  "overview",
114
115
  "dailyTrend",
@@ -116,6 +117,7 @@
116
117
  "campaigns",
117
118
  "devices",
118
119
  "geographic",
120
+ "audience",
119
121
  "keywords"
120
122
  ],
121
123
  "properties": {
@@ -125,12 +127,13 @@
125
127
  "campaigns": { "$ref": "#/$defs/sectionNarrative" },
126
128
  "devices": { "$ref": "#/$defs/sectionNarrative" },
127
129
  "geographic": { "$ref": "#/$defs/sectionNarrative" },
130
+ "audience": { "$ref": "#/$defs/sectionNarrative" },
128
131
  "keywords": { "$ref": "#/$defs/sectionNarrative" }
129
132
  }
130
133
  },
131
134
  "recommendations": {
132
135
  "type": "array",
133
- "description": "报告 §8 优化建议;≥3 条,可跨维度综合",
136
+ "description": "报告 §9 优化建议;≥3 条,可跨维度综合",
134
137
  "minItems": 3,
135
138
  "items": {
136
139
  "oneOf": [
@@ -215,6 +218,23 @@
215
218
  }
216
219
  },
217
220
  "geographicRows": { "type": "array", "items": { "$ref": "#/$defs/geographicRow" } },
221
+ "audienceRow": {
222
+ "type": "object",
223
+ "properties": {
224
+ "audience": { "type": "string", "description": "受众名称(audienceName 回填到此字段)" },
225
+ "audienceName": { "type": "string" },
226
+ "audienceType": { "type": "string", "description": "SystemDefined / UserDefined 等,有则保留" },
227
+ "campaignName": { "type": "string" },
228
+ "spend": { "type": "number" },
229
+ "impressions": { "type": "number" },
230
+ "clicks": { "type": "number" },
231
+ "ctr": { "type": "number" },
232
+ "averageCpc": { "type": "number" },
233
+ "conversions": { "type": "number" },
234
+ "costPerConversion": { "type": "number" }
235
+ }
236
+ },
237
+ "audienceRows": { "type": "array", "items": { "$ref": "#/$defs/audienceRow" } },
218
238
  "keywordRow": {
219
239
  "type": "object",
220
240
  "properties": {
@@ -44,7 +44,7 @@
44
44
  ## 工作流入口(线性步骤见 playbooks)
45
45
 
46
46
  > 线性步骤见 `playbooks.md`:单户画像 **P1**、Google 周期 **P4**、Meta 周期 **P4-FB**、多账户批处理 **P5**、OKKI **P6**、询盘 **P7**。
47
- > 「使用 okki 周报模板」→ **P6**(勿按默认 8 维追问;终稿 `google-analysis okki-render`,禁止 Agent 手写 xlsx)。Google 周期 Excel(非 OKKI/询盘)→ **P4** + `report-templates/google-period-report-excel.md`。
47
+ > 「使用 okki 周报模板」→ **P6**(勿按默认 9 章追问;终稿 `google-analysis okki-render`,禁止 Agent 手写 xlsx)。Google 周期 Excel(非 OKKI/询盘)→ **P4** + `report-templates/google-period-report-excel.md`。
48
48
 
49
49
  拉数落盘:`google-analysis … --json-out <dir>`(Google)或 `yandex-analysis` / `bing-analysis` / `facebook-analysis` / `tiktok-analysis`;目录内生成 `<section>-<accountId>.json` + `manifest-<accountId>.json`(Meta/TikTok/Bing/Yandex 为 `report-manifest-<accountId>.json`)。读盘协议、交付自检与报告首行标注统一见 `references/core/agent-conventions.md` §三、§七。
50
50
 
@@ -84,9 +84,9 @@ CLI 出口的所有 JSON / 表格金额已统一为**元**,关键字段:
84
84
  # 单维度
85
85
  siluzan-tso google-analysis -a <id> --sections overview --json-out <dir>
86
86
 
87
- # 多维度(撰写完整报告推荐 5-8 个维度)
87
+ # 多维度(周期报告默认 8 个拉数维,含 audience)
88
88
  siluzan-tso google-analysis -a <id> --start YYYY-MM-DD --end YYYY-MM-DD \
89
- --sections overview,campaigns,devices,geographic,keywords,daily-metrics --json-out <dir>
89
+ --sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords --json-out <dir>
90
90
 
91
91
  # 全量 25 个(省略 --sections)
92
92
  siluzan-tso google-analysis -a <id> --json-out <dir>
@@ -267,7 +267,7 @@
267
267
  | 用户话术(含子串即可) | 工作流 | 备注 |
268
268
  | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | --------------------------------------------------------- |
269
269
  | **网址 / URL / 域名 / 官网 / 落地页** + **诊断 / 检测 / 监测 / 评估 / 体检 / 报告 / 符合投放要求 / 能不能投**(含「网络诊断」「网络检测」) | **P8** | 与 **§零** 同义;**优先 §零**;必 `website-diagnosis` CLI |
270
- | `OKKI 周报` / `okki 周报` / `使用 okki 周报模板` | **P6** | **不按 P4 默认 8 维**;写 JSON 后 `okki-render` 出 xlsx;「表格形式」= Excel,禁止对话 Markdown 表 / Agent 手写 xlsx |
270
+ | `OKKI 周报` / `okki 周报` / `使用 okki 周报模板` | **P6** | **不按 P4 默认 9 章**;写 JSON 后 `okki-render` 出 xlsx;「表格形式」= Excel,禁止对话 Markdown 表 / Agent 手写 xlsx |
271
271
  | `Google 账户询盘分析` / `分析 … Google 账号的询盘效果`(**且有询盘附件/字段表**) | **P7** | 严格 3 个月窗口;无 CRM → 改 **§零·D / P3** |
272
272
  | `转化成本监控` / `CPA 监控` / `谷歌ADS转化成本` / `低余额账户转化成本` | **P3(§零·D)** | `accounts-digest --max-cpa --skip-balance`;贴 `agentBrief.userDelivery` |
273
273
  | `昨日零询盘` / `零询盘账户` / `0 转化` / `有消耗无转化`(**无 CRM 附件**) | **P3(§零·D)** | `--zero-conversions`;**不是 P7** |
@@ -9,7 +9,7 @@
9
9
  | P1 | 单账户投放画像 | 单户拉数 + 诊断画像 |
10
10
  | P2 | 多账户余额扫描 | 续航不足/充值预警巡检 |
11
11
  | P3 | 多账户投放画像汇总 | accounts-digest 多户对比表 |
12
- | P4 | Google 账户周期报告 | 默认 8 维周期汇总 |
12
+ | P4 | Google 账户周期报告 | 默认 9 章周期汇总(含受众) |
13
13
  | P4-FB | Meta/Facebook 周期报告 | facebook-analysis 默认 5 维,默认 HTML |
14
14
  | P5 | 多账户多维度批处理 | google-analysis-batch,禁外层 for-loop |
15
15
  | P6 | OKKI 周报 | 写 JSON → okki-render 出 5 Sheet xlsx |
@@ -70,19 +70,19 @@
70
70
 
71
71
  ## P4 · Google 账户周期报告
72
72
 
73
- - **触发**:Google 账户某区间的周期**数据汇总**(只要花费/转化/8 维,或用户列出 Sheet 要 **Excel**)。非 OKKI / 询盘 / **非**客户版经营报告。
73
+ - **触发**:Google 账户某区间的周期**数据汇总**(只要花费/转化/9 章,或用户列出 Sheet 要 **Excel**)。非 OKKI / 询盘 / **非**客户版经营报告。
74
74
  - **不是 P10**:出现「月度报告 / 月总结与规划 / 季度报告 / Q3 汇总 / Q4 规划 / 季度汇报 / 投放季报 / 客户版 / 大族模板 / 下月规划 / 效率+区域询盘+国家明细」→ **P10**(即使带「行业参考」也不改 P9)。
75
75
  - **必读**:`report-templates/google-period-report.md` + `references/analytics/account-analytics.md`;**要 Excel** 加 `report-templates/google-period-report-excel.md`(全文)。
76
76
  - **步骤**:
77
77
  1. **账户核验**:`list-accounts -m Google -k <mediaCustomerId> --json-out <dir>`;无记录则停止并告知用户。
78
78
  2. 确认时间范围;区间 > 3 个月时分段(季度/月)。
79
- 3. **拉数**:用户已指定 Sheet/维度 → 只拉对应 `--sections`(见 `google-period-report-excel.md` 映射表);未指定 → 按 `google-period-report.md` 默认 8 维,并可并行追问可选追加。
79
+ 3. **拉数**:用户已指定 Sheet/维度 → 只拉对应 `--sections`(见 `google-period-report-excel.md` 映射表);未指定 → 按 `google-period-report.md` 默认 8 个拉数维(含 `audience`),并可并行追问可选追加。
80
80
  4. **门禁**:对每个 section Read `<section>-<accountId>_*.outline.txt` 后再写脚本。
81
81
  5. 脚本读盘写产物:默认 **HTML**(`report-template*.html` 版式参考);用户要 **Excel** → Agent 脚本写 xlsx(**禁止**宿主第三方 xlsx Skill;**无** CLI excel 子命令)。
82
- 6. 默认 HTML 报告须含:账户概览、投放趋势、Top 关键词/系列/地区分布、优化建议;用户定制 Excel 以用户 Sheet 清单为准。
82
+ 6. 默认 HTML 报告须含:账户概览、投放趋势、Top 关键词/系列/地区/受众分布、优化建议;用户定制 Excel 以用户 Sheet 清单为准。
83
83
  - **产物**:按 conventions §七 自检;`accountId` 须与用户给的 `mediaCustomerId` 一致。
84
84
 
85
- > 用户用「OKKI 周报」固定话术 → 改走 **P6**,不按默认 8 维追问。
85
+ > 用户用「OKKI 周报」固定话术 → 改走 **P6**,不按默认 9 章追问。
86
86
  > 只要账户级按日 Excel(无多维章节)→ **P4-DAILY**。
87
87
 
88
88
  ---
@@ -143,7 +143,7 @@
143
143
  4. 脚本读盘 → 只写 `okki-weekly-report.json` 的 `meta` + `narrative`(5 Sheet 表末分析 + 账户 Sheet 固定 5 维数据复盘;表为空也须写)。`deviceType` 是 `Desktop`/`Mobile`,禁止 `.get("DESKTOP")`,按大小写不敏感分类累加后再写设备金额。
144
144
  5. `google-analysis okki-render --data ./okki-weekly-report.json --snapshot-dir ./snap-okki --out <xlsx>`。缺分析 / 缺 5 维复盘 / 缺 `campaign-device` 或 `campaign-geo-matched` 会拒绝出文件;按 stderr 补 JSON 后重跑。**禁止** Agent 手写 xlsx。
145
145
  6. 按 `okki-weekly-google-client.md` **默认客户话术**填数交付(用户另有话术/增删条目时从其自定义);数值与 xlsx 一致。
146
- - **产物**:`okki-render` 写出的 `.xlsx` 路径(缺文件 = 未完成)+ 对话内客户话术;**不**按 P4 默认 8 维追加。仅用户明确「只要话术 / 不要文件」时才省略 xlsx。
146
+ - **产物**:`okki-render` 写出的 `.xlsx` 路径(缺文件 = 未完成)+ 对话内客户话术;**不**按 P4 默认 9 章追加。仅用户明确「只要话术 / 不要文件」时才省略 xlsx。
147
147
 
148
148
  ---
149
149
 
@@ -39,6 +39,7 @@
39
39
  | 关键字 / 关键词报告 | `keywords` | `items[]` |
40
40
  | 搜索字词 / 搜索词报告 | `search-terms` | `items[]` |
41
41
  | 国家 / 地域 / 地区报告 | `geographic` | `items[]` |
42
+ | 受众 / 受众分布 | `audience` | `items[]`(`audience` / `audienceName`) |
42
43
  | 广告系列(若用户要) | `campaigns` | `items[]` |
43
44
  | 设备(若用户要) | `devices` 或 `campaign-device` | `items[]`;OKKI 口径用后者 |
44
45
 
@@ -15,13 +15,13 @@
15
15
  | ---------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
16
16
  | **1. 拉数** | Agent 调 CLI | `google-analysis -a <id> --start <s> --end <e> --json-out ./snap-google --sections <见下方默认维度对应 CLI>` |
17
17
  | **2. 分析** | Agent | 用 **node/python 脚本**读落盘 JSON(勿用 Read 打开业务 `*.json`),完成筛选、聚合、排序与洞察 |
18
- | **3. 写 JSON** | Agent | 按 `assets/google-period-report.schema.json` 撰写 `google-period-report.json`:只写 `meta` + `narrative`(8 维度叙事);`kpis`/`charts`/`tables` 留给步骤 4 的 `--snapshot-dir` 合并(合并后缺 KPI 会拒绝出 HTML) |
19
- | **4. 渲染 HTML** | CLI | `google-analysis render` — **校验 narrative 8 维度必含字段**,缺项报错不生成 HTML;**禁止** Agent 手写/拼接 HTML |
18
+ | **3. 写 JSON** | Agent | 按 `assets/google-period-report.schema.json` 撰写 `google-period-report.json`:只写 `meta` + `narrative`(8 个数据维 + 优化建议);`kpis`/`charts`/`tables` 留给步骤 4 的 `--snapshot-dir` 合并(合并后缺 KPI 会拒绝出 HTML) |
19
+ | **4. 渲染 HTML** | CLI | `google-analysis render` — **校验 narrative 9 章必含字段**(含受众),缺项报错不生成 HTML;**禁止** Agent 手写/拼接 HTML |
20
20
 
21
21
  ```bash
22
22
  # 步骤 1
23
23
  siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-google \
24
- --sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,keywords
24
+ --sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords
25
25
 
26
26
  # 步骤 4(步骤 2~3 完成后)
27
27
  siluzan-tso google-analysis render \
@@ -35,11 +35,11 @@ siluzan-tso google-analysis render \
35
35
  - `meta`:`accountId`(必填)/ `accountName` / `currency` / `startDate` / `endDate`;可省略由 `--snapshot-dir` 从 `overview` + manifest 自动合并。
36
36
  - `kpis`:账户级 KPI(消耗/展示/点击/转化/CTR/CPC/CPA,含可选 `previousPeriod` 环比;另含 `activeDays` / `averageDailyCost`);Agent 可省略,但 **render 合并后** `cost`/`impressions`/`clicks` 至少一项须为有效数字,否则拒绝出 HTML(避免执行摘要卡片全「—」)。本期 KPI 由 CLI 优先对 `campaigns` 全系列累加并重算比率;无 campaigns 时回退 `overview.currentPeriod`,再回退 `dimension-summary`。`activeDays` / `averageDailyCost` 来自 `overview.record` 根(**不是** `currentPeriod`);环比仍来自 `overview.previousPeriod`(上期无活跃天数,不编造)。
37
37
  - `charts.dailyTrend`:每日趋势(`dates`/`cost`/`conversions`/`cpa`);可省略由 `daily-metrics` 自动合并。
38
- - `tables.{dimensionSummary,campaigns,devices,geographic,keywords}`:对应 §3~§7 表格数据;可省略由同名 `google-analysis` section 自动合并。
38
+ - `tables.{dimensionSummary,campaigns,devices,geographic,audience,keywords}`:对应 §3~§8 表格数据;可省略由同名 `google-analysis` section 自动合并。
39
39
  - `narrative`(**Agent 必填,唯一由 Agent 撰写的内容**):
40
40
  - `executiveSummary[]`:≥1 段执行摘要
41
- - `sections.{overview,dailyTrend,dimensionSummary,campaigns,devices,geographic,keywords}`:每个维度各 `{analysis[](≥2 条), suggestions[](≥1 条)}`,对应报告 §1~§7
42
- - `recommendations[]`:≥3 条跨维度优化建议,对应报告 §8
41
+ - `sections.{overview,dailyTrend,dimensionSummary,campaigns,devices,geographic,audience,keywords}`:每个维度各 `{analysis[](≥2 条), suggestions[](≥1 条)}`,对应报告 §1~§8
42
+ - `recommendations[]`:≥3 条跨维度优化建议,对应报告 §9
43
43
 
44
44
  完整字段定义见 `assets/google-period-report.schema.json`。
45
45
 
@@ -53,13 +53,13 @@ siluzan-tso google-analysis render \
53
53
  模板源码:`report-templates/google-period-report.html`(结构与样式,ECharts 渲染逻辑已内联)。
54
54
 
55
55
  `render` 会向输出目录写入 HTML,并注入 `window.__GOOGLE_PERIOD_REPORT__`。
56
- 传 `--snapshot-dir` 时 CLI 自动从快照合并 KPI、每日趋势图表、周期汇总/系列/设备/地域/关键词表格:**数值一律以 CLI 快照覆盖**(`meta` 仅补空)。本期 KPI 优先 `campaigns` 累加。若 manifest 缺条目但磁盘上仍有对应 `*.json`,CLI 会按文件名自愈补齐。Agent 只写 `narrative`,勿自填 `kpis`/`tables`。
56
+ 传 `--snapshot-dir` 时 CLI 自动从快照合并 KPI、每日趋势图表、周期汇总/系列/设备/地域/受众/关键词表格:**数值一律以 CLI 快照覆盖**(`meta` 仅补空)。本期 KPI 优先 `campaigns` 累加。若 manifest 缺条目但磁盘上仍有对应 `*.json`,CLI 会按文件名自愈补齐。Agent 只写 `narrative`,勿自填 `kpis`/`tables`。
57
57
 
58
58
  ---
59
59
 
60
60
  ## 默认报告维度
61
61
 
62
- 生成报告时,**默认包含**以下 8 个维度:
62
+ 生成报告时,**默认包含**以下 9 章(8 个拉数维 + 优化建议):
63
63
 
64
64
  | # | 维度 | CLI |
65
65
  | --- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
@@ -69,8 +69,9 @@ siluzan-tso google-analysis render \
69
69
  | 4 | 广告系列表现(预算/出价策略/各系列消耗与效果) | `google-analysis --sections campaigns` |
70
70
  | 5 | 设备分布(PC/移动/平板 消耗/点击/转化) | `google-analysis --sections devices` |
71
71
  | 6 | 地域分布(国家/地区 消耗占比) | `google-analysis --sections geographic` |
72
- | 7 | 关键词表现(词/消耗/CTR/CPC 排行) | `google-analysis --sections keywords`(这个命令获取的关键词数据会来自多个系列,不能合并去重) |
73
- | 8 | 优化建议(根据以上数据给出可执行改进建议) | 不额外拉数,基于已有数据撰写 |
72
+ | 7 | 受众分布(意向/兴趣/再营销/客户匹配等列表表现) | `google-analysis --sections audience`(默认不传 `--audience-type`,系统+自定义一起拉) |
73
+ | 8 | 关键词表现(词/消耗/CTR/CPC 排行) | `google-analysis --sections keywords`(这个命令获取的关键词数据会来自多个系列,不能合并去重) |
74
+ | 9 | 优化建议(根据以上数据给出可执行改进建议) | 不额外拉数,基于已有数据撰写 |
74
75
 
75
76
  **在执行任何数据拉取之前**,先向用户展示以下可选维度,询问是否需要追加:
76
77
 
@@ -81,7 +82,6 @@ siluzan-tso google-analysis render \
81
82
  | 维度 | CLI | 备注 |
82
83
  | ------------- | ----------------------------------------------- | ----------------------------------------------------------------- |
83
84
  | 系列按小时 | `google-analysis --sections campaign-hour` | 行在 `items[]`,含 `date`/`hour`/消耗与效果 |
84
- | 受众分布 | `google-analysis --sections audience` | 可分 `SystemDefined` / `UserDefined` |
85
85
  | 搜索词报告 | `google-analysis --sections search-terms` | 高消耗搜索词;`queryTargetingStatusZh` 列(已添加/已排除/都没有) |
86
86
  | 广告创意表现 | `google-analysis --sections ads` | 广告标题/类型/到达网址 |
87
87
  | 附加信息 | `google-analysis --sections extensions` | 附加链接/电话/宣传信息等状态 |
@@ -95,14 +95,14 @@ siluzan-tso google-analysis render \
95
95
 
96
96
  ---
97
97
 
98
- ## 拉数顺序(默认 7 个维度)
98
+ ## 拉数顺序(默认 8 个维度)
99
99
 
100
100
  **首选:用 `google-analysis --sections` 一次拉齐**(单进程复用 keep-alive,比逐个 spawn 快 3-7×;详见 `references/analytics/account-analytics.md` 的 `all` 子命令说明):
101
101
 
102
102
  ```bash
103
103
  mkdir -p ./snap-google
104
104
  siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-google \
105
- --sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,keywords
105
+ --sections overview,daily-metrics,dimension-summary,campaigns,devices,geographic,audience,keywords
106
106
  ```
107
107
 
108
108
  如果用户**追加**了维度(按上文「可选追加」表),把它们加入 `--sections` 即可;要全 21 维则省略 `--sections`。
@@ -121,3 +121,16 @@ siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-goog
121
121
  | 需要英文枚举时读 `campaignStatus` 或 `campaignStatusV2`,展示前 `.upper()` 再映射 | 用 `stats` / `list-accounts` 的账户 `status` 充当系列状态 |
122
122
 
123
123
  原始枚举为 PascalCase(`Enabled`),与 `ad campaigns` 的 `statusDisplay`(如 `Enabled·有效` / `Enabled·投放期已结束`)口径不同;周期报告「状态」列统一用 **`campaignStatusDisplay`**。
124
+
125
+ ---
126
+
127
+ ## 受众分析(必写)
128
+
129
+ `audience-*.json` → `items[]`。标签字段优先 `audienceName`,否则 `audience`。这是广告组受众列表表现(In-market / Affinity / 再营销 / Customer Match 等),**不是** Meta/Bing 那种年龄×性别交叉。
130
+
131
+ - **呈现**:`tables.audience[]`(`audience` / `audienceType` / `campaignName` / 消耗/点击/转化/CPA)。默认不传 `--audience-type`;用户只要系统或自定义列表时再加 `SystemDefined` / `UserDefined`。
132
+ - **分析** → `narrative.sections.audience.{analysis,suggestions}`(≥2 / ≥1):
133
+ - 写清高消耗、高转化、空耗(高花费低转化或零转化)的受众名称 + 数字。
134
+ - 区分系统意向/兴趣 vs 再营销/客户匹配;空耗列表建议缩小或排除,高效列表建议观察后再加码。
135
+ - 无行时写「本期间无受众行 / 未挂受众列表」,**禁止**编造年龄段或兴趣饼。
136
+ - 未确认前不要写自动改出价/排除命令。
@@ -24,6 +24,8 @@
24
24
 
25
25
  先结论,再表,最后路线图和风险。每条建议至少挂一项依据:目标 / 历史表现 / 业务战略 / 资源约束。缺数据的市场、类型、受众、词 → 写**验证计划**,禁止直接扩量。
26
26
 
27
+ render 会把九章铺成看板,不必为版式再写一份:`goals` → 五卡/目标分层;`budget` → 配比纪律;`structure`+`countries` → 加/守/砍与搜索结构;`keywords`/`creative` → 搜索打法;`exploreAds` → 非搜索四卡;`audience` → 受众步骤;`roadmap` → 月度阶梯 + 三月路线图;`risks` → 周节奏页脚注。金额与国家动作由 collect 算,Agent 只写判断。某章没写就标「待业务确认」,**不要**用通用运营清单填满版式。周节奏表是通用巡检。
28
+
27
29
  | 序 | 键 | 章节 | 最低要写 |
28
30
  | -- | -- | ---- | -------- |
29
31
  | 1 | `analysis` | 季度投放结论 | 3~5 条:主攻方向、重点市场、核心广告类型、主要建设任务 |
@@ -1,7 +1,7 @@
1
1
  # Google 季度经营报告
2
2
 
3
- > collect 只出事实;Agent 只写 `narrative`;`render` 注入 `google-quarterly-report.html`(大族版式 + ECharts)。
4
- > **禁止** Read HTML 模板。行业数据不做。新探索(视频 / PMax / 发现 / 展示)全无量则整章隐藏。
3
+ > collect 只出事实;Agent 只写 `narrative`;`render` 注入 `google-quarterly-report.html`(大族分页版式 + ECharts)。
4
+ > **禁止** Read HTML 模板。行业数据不做。展会 / 蜜月期服务页不自动出。新探索(视频 / PMax / 发现 / 展示)全无量则整章隐藏。
5
5
  >
6
6
  > **下期规划**:写 `narrative.plan` 前必读 `report-templates/google-quarterly-plan.md`(九章 + Q01–Q30)。规划只消费 collect,不编造预算/CPA/询盘,不写自动调价/暂停。
7
7
  >
@@ -120,3 +120,7 @@ Agent JSON Schema:`assets/google-quarterly-report.schema.json`。
120
120
  ```
121
121
 
122
122
  缺 `executiveSummary` / `sections.*.analysis` / `plan.analysis`(或 `plan.goals` / `plan.text`)时 render 失败。九章缺章不拒渲染,但规划不合格。
123
+
124
+ HTML 章节(render 自动分页,勿手写 HTML):封面 → 浏览逻辑 → 核心结论 → ①效率 ②类型 ③区域 ④国家 ⑤下钻 ⑥账户 → 新探索 tabs → 总结 → 规划核心(五卡 + 月度阶梯)→ 预算配比 → 国家加/守/砍 → 搜索打法 → 非搜索清单 → 受众 → 周节奏 → 谢谢。
125
+
126
+ 规划页数字只来自 collect:`planDefaults` 三档与月拆、`countries` 诊断出加/守/砍、`spendByChannel` 出本期类型占比。`narrative.plan` 九章填目标分层、执行要点、路线图;**禁止**为填满版式编造询盘/CPA/行业数。九章缺章标「待业务确认」,不塞通用运营话术。周节奏表是通用巡检,不是本户规划。`narrative.agenda` 可覆盖浏览逻辑四步说明。
@@ -23,7 +23,7 @@
23
23
 
24
24
  ---
25
25
 
26
- 识别到上述意图时:**不要**再走 `google-period-report.md` 的「默认 8 维 + 追问追加」流程;按本文**固定维度 + 默认客户话术(可用户自定义)+ `okki-render` 出 5 Sheet `.xlsx`** 交付。**先 `okki-render` 再发话术**;没有 `.xlsx` 路径不得收口。
26
+ 识别到上述意图时:**不要**再走 `google-period-report.md` 的「默认 9 章 + 追问追加」流程;按本文**固定维度 + 默认客户话术(可用户自定义)+ `okki-render` 出 5 Sheet `.xlsx`** 交付。**先 `okki-render` 再发话术**;没有 `.xlsx` 路径不得收口。
27
27
 
28
28
  ---
29
29
 
@@ -39,6 +39,7 @@
39
39
  | 关键字 / 关键词报告 | `keywords` | `items[]` |
40
40
  | 搜索字词 / 搜索词报告 | `search-terms` | `items[]` |
41
41
  | 国家 / 地域 / 地区报告 | `geographic` | `items[]` |
42
+ | 受众 / 受众分布 | `audience` | `items[]`(`audience` / `audienceName`) |
42
43
  | 广告系列(若用户要) | `campaigns` | `items[]` |
43
44
  | 设备(若用户要) | `devices` 或 `campaign-device` | `items[]`;OKKI 口径用后者 |
44
45
 
@@ -799,6 +799,7 @@
799
799
  <a href="#sec-campaigns">系列表现</a>
800
800
  <a href="#sec-devices">设备分布</a>
801
801
  <a href="#sec-geographic">地域分布</a>
802
+ <a href="#sec-audience">受众分布</a>
802
803
  <a href="#sec-keywords">关键词</a>
803
804
  <a href="#sec-recommendations">优化建议</a>
804
805
  </div>
@@ -1001,11 +1002,58 @@
1001
1002
  </section>`;
1002
1003
  }
1003
1004
 
1005
+ function renderAudience(data) {
1006
+ const rows = Array.isArray(data.tables?.audience) ? data.tables.audience : [];
1007
+ const currency = data.meta?.currency || "CNY";
1008
+ const section = data.narrative?.sections?.audience;
1009
+ const head = sectionHead(
1010
+ "07",
1011
+ "受众分布(Top 20)",
1012
+ "AdGroupAudienceData:意向/兴趣/再营销/客户匹配等列表表现,不是年龄×性别",
1013
+ );
1014
+ const sorted = [...rows]
1015
+ .sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0))
1016
+ .slice(0, 20);
1017
+ const body = sorted
1018
+ .map(
1019
+ (r) => `
1020
+ <tr>
1021
+ <td>${escapeHtml(r.audienceName || r.audience || "—")}</td>
1022
+ <td>${escapeHtml(r.audienceType || "—")}</td>
1023
+ <td>${escapeHtml(r.campaignName || "—")}</td>
1024
+ <td class="num">${money(r.spend, currency)}</td>
1025
+ <td class="num">${int(r.impressions)}</td>
1026
+ <td class="num">${int(r.clicks)}</td>
1027
+ <td class="num">${pct(r.ctr)}</td>
1028
+ <td class="num">${int(r.conversions)}</td>
1029
+ <td class="num">${money(r.costPerConversion, currency)}</td>
1030
+ </tr>`,
1031
+ )
1032
+ .join("");
1033
+ const table =
1034
+ sorted.length > 0
1035
+ ? `<div id="chart-audience" class="chart-box" style="height:320px"></div>
1036
+ <div class="table-wrap"><table>
1037
+ <thead><tr><th>受众</th><th>类型</th><th>广告系列</th><th class="num">消耗</th><th class="num">展示</th><th class="num">点击</th><th class="num">CTR</th><th class="num">转化</th><th class="num">CPA</th></tr></thead>
1038
+ <tbody>${body}</tbody>
1039
+ </table></div>
1040
+ <div class="card-title-row" style="margin-top:10px"><span class="hint">共 ${rows.length} 个受众,表格展示消耗 Top 20。默认不筛 SystemDefined / UserDefined。</span></div>`
1041
+ : '<div class="empty-note">数据不可用:audience 未拉取或本期间无受众行</div>';
1042
+ return `
1043
+ <section id="sec-audience">
1044
+ ${head}
1045
+ <div class="card">
1046
+ ${table}
1047
+ ${analysisBlock(section)}
1048
+ </div>
1049
+ </section>`;
1050
+ }
1051
+
1004
1052
  function renderKeywords(data) {
1005
1053
  const rows = Array.isArray(data.tables?.keywords) ? data.tables.keywords : [];
1006
1054
  const currency = data.meta?.currency || "CNY";
1007
1055
  const section = data.narrative?.sections?.keywords;
1008
- const head = sectionHead("07", "关键词表现(Top 50)", "跨系列合并展示,按消耗排序");
1056
+ const head = sectionHead("08", "关键词表现(Top 50)", "跨系列合并展示,按消耗排序");
1009
1057
  const sorted = [...rows]
1010
1058
  .sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0))
1011
1059
  .slice(0, 50);
@@ -1062,7 +1110,7 @@
1062
1110
  : '<div class="empty-note">(待 Agent 撰写优化建议)</div>';
1063
1111
  return `
1064
1112
  <section id="sec-recommendations">
1065
- ${sectionHead("08", "优化建议", "综合以上数据的可执行改进建议")}
1113
+ ${sectionHead("09", "优化建议", "综合以上数据的可执行改进建议")}
1066
1114
  <div class="card">${body}</div>
1067
1115
  </section>`;
1068
1116
  }
@@ -1255,6 +1303,44 @@
1255
1303
  });
1256
1304
  }
1257
1305
 
1306
+ const audienceEl = document.getElementById("chart-audience");
1307
+ const audience = Array.isArray(data.tables?.audience) ? data.tables.audience : [];
1308
+ if (audienceEl && audience.length > 0) {
1309
+ const top = [...audience]
1310
+ .sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0))
1311
+ .slice(0, 10)
1312
+ .reverse();
1313
+ const chart = echarts.init(audienceEl);
1314
+ chart.setOption({
1315
+ tooltip: Object.assign(
1316
+ { trigger: "axis", formatter: (p) => money(p[0].value, currency) },
1317
+ TOOLTIP_STYLE,
1318
+ ),
1319
+ grid: { left: 160, right: 30, top: 16, bottom: 20 },
1320
+ xAxis: styleAxis({ type: "value" }),
1321
+ yAxis: styleAxis({
1322
+ type: "category",
1323
+ data: top.map((c) => c.audienceName || c.audience || "—"),
1324
+ axisLabel: { color: AXIS_COLOR, width: 140, overflow: "truncate" },
1325
+ splitLine: { show: false },
1326
+ }),
1327
+ series: [
1328
+ {
1329
+ type: "bar",
1330
+ data: top.map((c) => Number(c.spend) || 0),
1331
+ barMaxWidth: 18,
1332
+ itemStyle: {
1333
+ borderRadius: [0, 6, 6, 0],
1334
+ color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
1335
+ { offset: 0, color: "rgba(245,158,11,0.55)" },
1336
+ { offset: 1, color: PALETTE[4] },
1337
+ ]),
1338
+ },
1339
+ },
1340
+ ],
1341
+ });
1342
+ }
1343
+
1258
1344
  window.addEventListener("resize", () => {
1259
1345
  document
1260
1346
  .querySelectorAll(".chart-box")
@@ -1297,6 +1383,7 @@
1297
1383
  renderCampaigns(data),
1298
1384
  renderDevices(data),
1299
1385
  renderGeographic(data),
1386
+ renderAudience(data),
1300
1387
  renderKeywords(data),
1301
1388
  renderRecommendations(data),
1302
1389
  renderFooter(data),