siluzan-tso-cli 1.1.56 → 1.1.57-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +334 -26
  3. package/dist/skill/SKILL.md +2 -1
  4. package/dist/skill/_meta.json +2 -2
  5. package/dist/skill/references/accounts/accounts-balance-stats.md +23 -0
  6. package/dist/skill/references/accounts/accounts-list.md +1 -1
  7. package/dist/skill/references/accounts/accounts-permissions.md +2 -2
  8. package/dist/skill/references/accounts/finance.md +1 -1
  9. package/dist/skill/references/accounts/open-account-by-media.md +1 -1
  10. package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
  11. package/dist/skill/references/analytics/rag.md +1 -1
  12. package/dist/skill/references/analytics/reporting.md +5 -5
  13. package/dist/skill/references/core/agent-conventions.md +4 -1
  14. package/dist/skill/references/core/playbooks.md +5 -2
  15. package/dist/skill/references/core/setup.md +5 -5
  16. package/dist/skill/references/core/workflows.md +1 -1
  17. package/dist/skill/references/misc/tso-home.md +2 -2
  18. package/dist/skill/references/report-templates/bing-period-report.md +1 -1
  19. package/dist/skill/references/report-templates/google-period-report.md +1 -1
  20. package/dist/skill/references/report-templates/google-quarterly-plan.md +2 -0
  21. package/dist/skill/references/report-templates/google-quarterly-report.md +2 -0
  22. package/dist/skill/references/report-templates/meta-period-report.md +1 -1
  23. package/dist/skill/references/report-templates/tiktok-period-report.md +1 -1
  24. package/dist/skill/references/report-templates/yandex-period-report.md +1 -1
  25. package/dist/skill/report-templates/bing-period-report.html +14 -2
  26. package/dist/skill/report-templates/bing-period-report.md +1 -1
  27. package/dist/skill/report-templates/google-period-report.html +14 -2
  28. package/dist/skill/report-templates/google-period-report.md +1 -1
  29. package/dist/skill/report-templates/google-quarterly-plan.md +2 -0
  30. package/dist/skill/report-templates/google-quarterly-report.html +20 -10
  31. package/dist/skill/report-templates/google-quarterly-report.md +2 -0
  32. package/dist/skill/report-templates/meta-period-report.html +6 -2
  33. package/dist/skill/report-templates/meta-period-report.md +1 -1
  34. package/dist/skill/report-templates/tiktok-period-report.html +14 -2
  35. package/dist/skill/report-templates/tiktok-period-report.md +1 -1
  36. package/dist/skill/report-templates/yandex-period-report.html +15 -1
  37. package/dist/skill/report-templates/yandex-period-report.md +1 -1
  38. package/dist/skill/scripts/install.ps1 +3 -3
  39. package/dist/skill/scripts/install.sh +3 -3
  40. package/package.json +1 -1
@@ -98,6 +98,7 @@ siluzan-tso balance -m Google -a 6326027735 --json-out ./snap
98
98
  > - 口语「零询盘 / 转化成本」且**无 CRM 询盘附件** → 本命令(P3),**不是** P7。
99
99
  > - `conversions` / `cpa` 可能为 **null**(overview 未返回转化)→ 交付写「转化未返回」;脚本勿对 null 做数值比较。过滤用 `--max-cpa` / `--zero-conversions`,巡检交付用 `agentBrief.userDelivery`,勿手写脆弱 Python 重算 CTR/合计。
100
100
  > - **没有** `cpa-risk-scan`。`--max-cpa` 的零转化仅当消耗已超过阈值才命中。`meta.totals` **只合计 returned**,禁止写成全部账户。`ctr` 已是百分数。
101
+ > - **没有**按户循环 `ad campaigns` 来判断 Display / PMax 的做法。要系列类型就加 `--with-campaign-types`(仅 Google,只查消耗 > 0 的户)。
101
102
 
102
103
  ```bash
103
104
  siluzan-tso accounts-digest -m <媒体类型> [选项]
@@ -116,6 +117,9 @@ siluzan-tso accounts-digest -m <媒体类型> [选项]
116
117
  | `--skip-balance` | 只拉消耗、不拉余额(转化成本巡检推荐) | off |
117
118
  | `--page-size <n>` | 全量扫描时清单分页大小(上限 500) | `200` |
118
119
  | `--max-pages <n>` | 全量扫描时最多页数(上限 200) | `20` |
120
+ | `--login-customer-id <id>` | 只保留该 MCC(`ma.loginCustomerId`)下的账户。可带横杠。先拉清单再过滤,**不要**先 `list-accounts` 把 ID 拼进 `-a` | — |
121
+ | `--with-campaign-types` | **仅 Google**。对区间消耗 > 0 的账户并发请求 `campaigns/types-summary`,每行写入系列类型。四项巡检用这个,禁止逐户 `ad campaigns` | off |
122
+ | `--campaign-type-concurrency <n>` | 上面这个请求的并发(1–16) | `8` |
119
123
  | `--json-out <path>` | **Agent 推荐**:落盘目录或 `*.json` 文件;stdout 一行摘要(含 `outlineFile`、`writtenFiles`、`manifestFile`) | — |
120
124
 
121
125
  **`--json-out` 落盘**:
@@ -152,10 +156,29 @@ siluzan-tso accounts-digest -m Google --start 2026-07-14 --end 2026-07-20 \
152
156
  # 零转化 / 口语「零询盘」巡检(无 CRM 附件时用本命令,不是 P7)
153
157
  siluzan-tso accounts-digest -m Google --start 2026-07-20 --end 2026-07-20 \
154
158
  --zero-conversions --json-out ./snap-zero-conv
159
+
160
+ # Google 四项巡检:CPA、余额、零转化、有没有 Display/PMax。一条命令,不要再逐户拉系列
161
+ siluzan-tso accounts-digest -m Google --login-customer-id 397-889-3117 \
162
+ --with-campaign-types --start 2026-10-16 --end 2026-10-22 \
163
+ --json-out ./snap-inspect
155
164
  ```
156
165
 
166
+ 四项都要时不要加 `--max-cpa` / `--zero-conversions`(会丢掉其它行)。外层命令必须直接是 `siluzan-tso`,不要写进 Python 再调用。
167
+
157
168
  **`data.items[]` 主要字段**:`mediaCustomerId`、`name`、`advertiserName`、`currencyCode`、`balance`、`spend`(**区间合计**,同 `stats`)、`impressions`、`clicks`、`conversions`(可为 null)、`ctr`(%)、`cpc`、`cpa`(可为 null)。跨币种汇总见 `references/accounts/currency.md`(**禁止**对 `meta.totals` 跨币种直接当最终结论)。
158
169
 
170
+ **`--with-campaign-types` 额外字段**(只在打开开关时出现):
171
+
172
+ | 字段 | 含义 |
173
+ | --- | --- |
174
+ | `campaignTypesStatus` | `ok` 查到了;`skipped-no-spend` 区间消耗为 0,没请求;`error` 这一户失败 |
175
+ | `campaignTypes` | `ok` 时是计数 > 0 的类型名(如 `Search`、`Display`、`PerformanceMax`);否则 `null` |
176
+ | `hasDisplay` | `ok` 时才是布尔。`Display` 计数 > 0 为 true |
177
+ | `hasPmax` | `ok` 时才是布尔。`PerformanceMax` / `PERFORMANCE_MAX` 计数 > 0 为 true |
178
+ | `campaignTypesError` | `error` 时的短原因 |
179
+
180
+ 脚本规则:`campaignTypesStatus === "ok" && hasDisplay === false && hasPmax === false` 才算「无展示且无 PMax」。`error` 和 `skipped-no-spend` 单独列出,**禁止**当成「无」。`meta.campaignTypes` 有 `requested` / `ok` / `failed` / `skippedNoSpend`。
181
+
159
182
  ---
160
183
 
161
184
  ## stats — 查询投放消耗数据
@@ -170,7 +170,7 @@ siluzan-tso account-history --start 2026-03-01 --end 2026-03-31 --json-out ./sna
170
170
  | 状态 | 含义 | 下一步操作 |
171
171
  | ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
172
172
  | `Pending` | 审核中 | 等待,可反复运行此命令轮询;审核周期因媒体而异 |
173
- | `Approved` | 审核通过 | 运行 `list-accounts -m <媒体>` 确认账户已出现;引导用户充值激活(`config show` 取 `webUrl`,按 `finance.md` 打开对应媒体充值页;例如 Google 为 `https://www.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google`;Yandex 当前没有对应充值界面) |
173
+ | `Approved` | 审核通过 | 运行 `list-accounts -m <媒体>` 确认账户已出现;引导用户充值激活(`config show` 取 `webUrl`,按 `finance.md` 打开对应媒体充值页;例如 Google 为 `https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google`;Yandex 当前没有对应充值界面) |
174
174
  | `Rejected` | 被拒 | 查看 `--json-out` 落盘中的 `reason` 字段了解拒绝原因;修改资料后重新提交;若原因不明,引导用户联系丝路赞客服 |
175
175
 
176
176
  ---
@@ -490,7 +490,7 @@ siluzan-tso account email-deauth -c <mediaCustomerId> --invitation-id <id> --res
490
490
 
491
491
  | 功能 | 媒体 | 网页路径 |
492
492
  | --------------------------------------- | ------ | ------------------------------------------------- |
493
- | **账户激活**(邀请他人激活 / 充值激活) | Google | `https://www.siluzan.com/v3/foreign_trade/tso/manageAccounts` |
493
+ | **账户激活**(邀请他人激活 / 充值激活) | Google | `https://www-ci.siluzan.com/v3/foreign_trade/tso/manageAccounts` |
494
494
 
495
495
  **Agent 建议话术**:
496
496
 
@@ -499,5 +499,5 @@ siluzan-tso account email-deauth -c <mediaCustomerId> --invitation-id <id> --res
499
499
  siluzan-tso config show # 查看 webUrl 字段
500
500
 
501
501
  # 账户激活(Google)→ 引导至账户管理页
502
- # https://www.siluzan.com/v3/foreign_trade/tso/manageAccounts
502
+ # https://www-ci.siluzan.com/v3/foreign_trade/tso/manageAccounts
503
503
  ```
@@ -126,7 +126,7 @@ siluzan-tso config show
126
126
 
127
127
  ### 充值页链接(按媒体 × 类型)
128
128
 
129
- 链接模式:`https://www.siluzan.com/v3/foreign_trade/tso/recharge/<page>?mediaType=<mediaType>`;丝路赞钱包:`https://www.siluzan.com/v3/foreign_trade/tso/recharge/siluzanWallet`(无媒体参数)。
129
+ 链接模式:`https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/<page>?mediaType=<mediaType>`;丝路赞钱包:`https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/siluzanWallet`(无媒体参数)。
130
130
 
131
131
  | 充值类型 | `<page>` | 支持媒体(`mediaType` 参数) |
132
132
  | ------------------------- | --------------------- | ------------------------------------------- |
@@ -1,6 +1,6 @@
1
1
  # 各媒体开户
2
2
 
3
- > 网页链接:`https://www.siluzan.com/v3/foreign_trade/tso/accountOpeningHistory?tso=%2Fv3umijs%2Ftso%2FaccountOpeningHistory`
3
+ > 网页链接:`https://www-ci.siluzan.com/v3/foreign_trade/tso/accountOpeningHistory?tso=%2Fv3umijs%2Ftso%2FaccountOpeningHistory`
4
4
  > 多命令串联见 `references/core/workflows.md` § 流程一。
5
5
 
6
6
  ## Contents
@@ -65,7 +65,7 @@ siluzan-tso open-account google-wizard
65
65
 
66
66
  ```bash
67
67
  siluzan-tso account-history -m Google
68
- # 审核通过后:config show → https://www.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google
68
+ # 审核通过后:config show → https://www-ci.siluzan.com/v3/foreign_trade/tso/recharge/pay?mediaType=Google
69
69
  ```
70
70
 
71
71
  ---
@@ -1,6 +1,6 @@
1
1
  # rag:知识库检索(TSO 广告投放辅助)
2
2
 
3
- 知识库管理页面在 https://www.siluzan.com/knowledge-base/
3
+ 知识库管理页面在 https://www-ci.siluzan.com/knowledge-base/
4
4
 
5
5
  为 **广告投放、账户分析、拓词、诊断报告** 等 TSO 业务提供**企业已入库**的产品、行业、客户背景事实依据。
6
6
 
@@ -190,9 +190,9 @@ siluzan-tso report push receive-emails -m Google [--json-out ./snap]
190
190
 
191
191
  | 媒体 | 报告类型 | URL 模板 |
192
192
  | ------ | ---------------- | ----------------------------------------------------------- |
193
- | Google | 日报(Daily) | `https://www.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
194
- | Google | 小时报(Hourly) | `https://www.siluzan.com/media-report/hour/{entityId}?culture=zh-CN` |
195
- | TikTok | 日报 | `https://www.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
193
+ | Google | 日报(Daily) | `https://www-ci.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
194
+ | Google | 小时报(Hourly) | `https://www-ci.siluzan.com/media-report/hour/{entityId}?culture=zh-CN` |
195
+ | TikTok | 日报 | `https://www-ci.siluzan.com/media-report/publish/{entityId}?culture=zh-CN` |
196
196
 
197
197
  `entityId` 来自 `siluzan-tso report list --json-out ./snap` 中每条记录的 `entityId` 字段。
198
198
 
@@ -205,8 +205,8 @@ siluzan-tso report list -m Google --json-out ./snap
205
205
 
206
206
  # 第二步:查看 webUrl
207
207
  siluzan-tso config show
208
- # webUrl: https://www.siluzan.com
208
+ # webUrl: https://www-ci.siluzan.com
209
209
 
210
210
  # 第三步:拼接链接(Google 日报)
211
- # https://www.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
211
+ # https://www-ci.siluzan.com/media-report/publish/rpt_abc123?culture=zh-CN
212
212
  ```
@@ -221,7 +221,9 @@
221
221
 
222
222
  ## 七、交付前自检(报告 / Excel / 含金额话术)
223
223
 
224
- > 在产物文件已写入磁盘**之后、发给用户之前**执行;不靠外部校验脚本,由 Agent **亲自 Read 最终产物文件**(HTML / Markdown 等;二进制 xlsx 无法 Read 时在对话贴自检表逐条勾选,依据为生成脚本的 stdout 摘要)。审阅阶段只看最终产物 + 已掌握的账户元数据,**不**回头 Read 落盘业务 JSON。
224
+ > CLI `render` 写出的 HTML(P1、P4、P4-FB、P8、P9、P10,以及 Bing / TikTok / Yandex / Meta 周期或诊断):命令成功并打印路径就是自检通过。**立刻交付该 HTML**。P10 与各媒体周期报告的「季度 / 月度」已按日期写进封面(对不上的周期报告仍写「周期」或「账户」),不要 Read、grep 或修改成品,不要为此再 render。用户没要 PDF 就不要转;要了也只转一次,不要用 pdftotext、PyMuPDF、截图或视觉模型读页面。下面 A/B/C 只用于 Agent 自己写的 Excel / Markdown。
225
+
226
+ > 在 Agent 自写产物已写入磁盘**之后、发给用户之前**执行;不靠外部校验脚本,由 Agent **亲自 Read 最终产物文件**(Markdown 等;二进制 xlsx 无法 Read 时在对话贴自检表逐条勾选,依据为生成脚本的 stdout 摘要)。审阅阶段只看最终产物 + 已掌握的账户元数据,**不**回头 Read 落盘业务 JSON。
225
227
 
226
228
  **A · 币种**:首行含 `统计区间:…(货币:CNY|USD)`;全文符号与 `currencyCode` 一致(CNY=¥、USD=$,未混用);与当次 `list-accounts -k` 结果相同;多账户分币种分表、无跨币种「总计」行。
227
229
 
@@ -249,6 +251,7 @@
249
251
  | -------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
250
252
  | 多账户余额 / 预算不足预警 | `balance-scan -m <媒体> --threshold-days 7`(用 **`dailySpend`** / `remainingDays`;交付 `agentBrief.userDelivery`) | 逐账户 `balance --accounts ...`;`account balance`;把 `stats` 多日 **`spend` 当「日消耗」** |
251
253
  | 多账户投放画像 / 转化成本巡检 | `accounts-digest -m <媒体> [-a ...] --start --end --json-out`(CPA 加 `--max-cpa --skip-balance`;**`spend`=区间合计**;交付 `agentBrief.userDelivery`) | 发明 `cpa-risk-scan`;逐账户 `stats`;把 `meta.totals` 说成全部账户;把 `ctr` 再乘 100 |
254
+ | Google 巡检要「有没有 Display / PMax」 | 同一条 `accounts-digest -m Google --with-campaign-types [--login-customer-id <MCC>]`。只对消耗 > 0 的户查类型 | 逐户 `ad campaigns` / `google-analysis --sections campaign-types`;把 `siluzan-tso` 写进 Python 再 subprocess;把 `campaignTypesStatus≠ok` 当成「无展示」 |
252
255
  | 单户按日 Excel | `stats … --by-day --json-out`(P4-DAILY) | 默认 `stats` 当按日 |
253
256
  | 多账户 × 多维度 Google 数据 | 全量:`google-analysis-batch run`(省略 `-a`);2~10:`google-analysis -a id1,id2,...` | 外层 for-loop;先 list 再拼 `-a` |
254
257
  | 多系列诊断 | `ad campaigns --json-out` + node 读文件过滤 | 逐系列 `ad campaign-get` |
@@ -59,8 +59,10 @@
59
59
  - `conversions`/`cpa` 为 null 时写「转化未返回」,**禁止**对 null 做 `>` 比较导致脚本崩溃。
60
60
  - **禁止**把 `meta.totals` 写成「全部账户」合计(`totalsScope=returned`,只合计命中行)。
61
61
  - **禁止**把 `ctr` 再 `.2%` / `*100`(已是百分数)。
62
+ - **Google 四项巡检**(CPA、余额、零转化、无 Display/PMax,常带 MCC):只跑下面这一条,**禁止**再逐户 `ad campaigns` / `google-analysis --sections campaign-types`,**禁止**把 `siluzan-tso` 写进脚本再 subprocess。
62
63
  - **步骤**:
63
- 1. 确认时间范围后执行 `accounts-digest -m <媒体> -a id1,id2,... --start <S> --end <D> --json-out ./snap-p3`(全量则省略 `-a`)。
64
+ 1. 确认时间范围后执行 `accounts-digest -m <媒体> -a id1,id2,... --start <S> --end <D> --json-out ./snap-p3`(全量则省略 `-a`)。指定 MCC 时加 `--login-customer-id <id>`,不要先 `list-accounts` 再把全部 ID 拼进 `-a`。
65
+ 1b. **要系列类型时**(仅 Google):同一条命令加 `--with-campaign-types`。四项都要时**不要**再加 `--max-cpa` / `--zero-conversions`(那两个开关会丢掉其它行)。脚本读 `data.items`:`cpa`、`balance`、`spend`+`conversions`,以及 `hasDisplay` / `hasPmax`。只有 `campaignTypesStatus=ok` 且两项都为 false 才写入「无展示且无 PMax」;`error` / `skipped-no-spend` 另列。
64
66
  2. **高 CPA / 转化成本巡检**:`--max-cpa <n> --skip-balance`(CPA > n,或零转化且消耗已 > n)。读盘后**只把 `agentBrief.userDelivery` 原样发给用户**(无命中即「全部正常」句;有命中只贴表)。
65
67
  3. **零转化 / 零询盘巡检**(要全部空耗户,含小额):加 `--zero-conversions`。
66
68
  4. 指定子集对比表:基于 `data.items`;**不要**再逐账户 `stats`。
@@ -80,7 +82,7 @@
80
82
  4. **门禁**:对每个 section Read `<section>-<accountId>_*.outline.txt` 后再写脚本。
81
83
  5. 脚本读盘写产物:默认 **HTML**(`report-template*.html` 版式参考);用户要 **Excel** → Agent 脚本写 xlsx(**禁止**宿主第三方 xlsx Skill;**无** CLI excel 子命令)。
82
84
  6. 默认 HTML 报告须含:账户概览、投放趋势、Top 关键词/系列/地区/受众分布、优化建议;用户定制 Excel 以用户 Sheet 清单为准。
83
- - **产物**:按 conventions §七 自检;`accountId` 须与用户给的 `mediaCustomerId` 一致。
85
+ - **产物**:按 conventions §七 自检;`accountId` 须与用户给的 `mediaCustomerId` 一致。HTML 封面按区间写成「季度」或「月度」,对不上则仍是「周期」。不要打开 HTML 改称呼。
84
86
 
85
87
  > 用户用「OKKI 周报」固定话术 → 改走 **P6**,不按默认 9 章追问。
86
88
  > 只要账户级按日 Excel(无多维章节)→ **P4-DAILY**。
@@ -205,4 +207,5 @@
205
207
  5. 读 `google-quarterly-collect.json`,只写 `narrative` 到 `google-quarterly.json`(禁止改 kpis / monthly / planDefaults 数字)。
206
208
  6. 下期规划按 `google-quarterly-plan.md` 写九章。预算默认三档(维持本期 / +30% / +50%)+ 建议档月度 30/35/35,标「基于历史数据」,不是已批预算;CPA/询盘不外推;禁止自动调价/暂停指令。
207
209
  7. `google-quarterly render --data ./snap-p10/google-quarterly.json --out ./snap-p10/google-quarterly-report.html`。
210
+ 8. **render 成功即停并交付**该 HTML。封面称呼已按区间写好:完整自然季是「季度」,从 1 号起的同一个月(含未过完的本月)是「月度」,其余是「本期」。**禁止**打开或修改 HTML,**禁止**再把「季度 / 月度」改一遍,**禁止**再 render,**禁止**未要求就转 PDF、截图或用视觉模型验收。
208
211
  - **产物**:HTML 路径;新探索(视频 / PMax / 发现 / 展示)全无量则模板自动隐藏该章。
@@ -10,7 +10,7 @@
10
10
  ## 安装 CLI
11
11
 
12
12
  ```bash
13
- npm install -g siluzan-tso-cli
13
+ npm install -g siluzan-tso-cli@beta
14
14
  ```
15
15
 
16
16
  ---
@@ -64,7 +64,7 @@ siluzan-tso config set --api-key <Key> # 或 config 直接写入
64
64
  siluzan-tso config set --token <Token> # 备用:设置 JWT Token
65
65
  ```
66
66
 
67
- API Key 获取入口:`https://www.siluzan.com/v3/foreign_trade/settings/apiKeyManagement`
67
+ API Key 获取入口:`https://www-ci.siluzan.com/v3/foreign_trade/settings/apiKeyManagement`
68
68
 
69
69
  ```bash
70
70
  # 第 1 步:让用户报出手机号后,立刻发码(命令立即返回,不会等待输入)
@@ -127,9 +127,9 @@ siluzan-tso config show
127
127
 
128
128
  ```
129
129
  构建环境 : production
130
- apiBaseUrl : https://tso-api.siluzan.com
131
- googleApiUrl : https://googleapi.mysiluzan.com
132
- webUrl : https://www.siluzan.com
130
+ apiBaseUrl : https://tso-api-ci.siluzan.com
131
+ googleApiUrl : https://googleapi-ci.mysiluzan.com
132
+ webUrl : https://www-ci.siluzan.com
133
133
  apiKey : abcd****1234
134
134
  ```
135
135
 
@@ -213,7 +213,7 @@
213
213
  - **必读**:`references/accounts/accounts-balance-stats.md`;首页看板口径见 `references/misc/tso-home.md`。Google 超预算/空耗熔断另读 `references/operations/guard.md`。Bing / Yandex / TikTok / Facebook 只读巡检读 `hosted-automation-bing.md` / `hosted-automation-yandex.md` / `hosted-automation-tiktok.md` / `hosted-automation-facebook.md`。
214
214
  - **步骤**:
215
215
  1. 余额:`list-accounts -m <媒体> --json-out ./snap` → `balance -m <媒体> -a <mediaCustomerId,...>`(多户续航预警走 **P2**)。
216
- 2. 消耗:`stats -m <媒体> -a <id> --start <昨天/上周一> --end <昨天/上周日>`(多户汇总走 **P3**;Bing/Yandex **今天**不要用 `stats`)。
216
+ 2. 消耗:多户汇总走 **P3** `accounts-digest`(Bing/Yandex **今天**不要用 `stats`)。Google 还要判断有没有 Display / PMax 时,同一条命令加 `--with-campaign-types`(可加 `--login-customer-id`),**禁止**逐户 `ad campaigns` 或把 CLI 塞进 Python。
217
217
  3. Google 当日超预算/空耗熔断(若用户要求):`guard budget-circuit` / `guard zero-conv -m Google --json-out`(**禁止**逐户 for-loop)。
218
218
  4. Bing 巡检(若用户要求):按 `hosted-automation-bing.md` 跑封禁/拒审/当日超预算或空耗预警(只告警,不暂停)。
219
219
  5. Yandex 巡检(若用户要求):按 `hosted-automation-yandex.md` 跑归档/拒审/当日超预算或空耗预警(只告警,不暂停)。
@@ -6,7 +6,7 @@
6
6
  siluzan-tso config show # 取 webUrl
7
7
  ```
8
8
 
9
- 首页地址:`https://www.siluzan.com/v3/foreign_trade/tso/home`
9
+ 首页地址:`https://www-ci.siluzan.com/v3/foreign_trade/tso/home`
10
10
 
11
11
  ---
12
12
 
@@ -25,7 +25,7 @@ siluzan-tso config show # 取 webUrl
25
25
 
26
26
  ## 推荐话术
27
27
 
28
- 1. **「和首页一样的总览」** → 打开 `https://www.siluzan.com/v3/foreign_trade/tso/home`。
28
+ 1. **「和首页一样的总览」** → 打开 `https://www-ci.siluzan.com/v3/foreign_trade/tso/home`。
29
29
  2. **「某个 Google 账户昨天花了多少」** → `list-accounts -m Google` + `stats -m Google -a <id>`。
30
30
  3. **「有待充值账户」** → 说明聚合数据在首页;CLI 可 `list-accounts` + `balance` 逐户排查,或引导充值页。
31
31
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 统计区间:`{startDate}` ~ `{endDate}`(**不可包含今天或昨天**,见下文)
4
4
  > 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
5
- > **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`bing-period-report.html`)。
5
+ > **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`bing-period-report.html`)。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
6
6
  > 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止** Agent 手写/拼接 HTML(语法错误会导致报告无法显示)。
7
7
  > **用户明确要求 Excel / xlsx** 时:拉数步骤不变,改为 Agent 脚本写 `.xlsx`;**不要**调用 `bing-analysis render`;禁止假设存在内置 excel 子命令。
8
8
 
@@ -52,7 +52,7 @@ siluzan-tso google-analysis render \
52
52
 
53
53
  模板源码:`report-templates/google-period-report.html`(结构与样式,ECharts 渲染逻辑已内联)。
54
54
 
55
- `render` 会向输出目录写入 HTML,并注入 `window.__GOOGLE_PERIOD_REPORT__`。
55
+ `render` 会向输出目录写入 HTML,并注入 `window.__GOOGLE_PERIOD_REPORT__`。封面按 `meta.startDate/endDate` 写成「季度分析」或「月度分析」;不是完整自然季、也不是从 1 号起的同一个月时,仍写「周期分析」。不要打开 HTML 改这几个字。
56
56
  传 `--snapshot-dir` 时 CLI 自动从快照合并 KPI、每日趋势图表、周期汇总/系列/设备/地域/受众/关键词表格:**数值一律以 CLI 快照覆盖**(`meta` 仅补空)。本期 KPI 优先 `campaigns` 累加。若 manifest 缺条目但磁盘上仍有对应 `*.json`,CLI 会按文件名自愈补齐。Agent 只写 `narrative`,勿自填 `kpis`/`tables`。
57
57
 
58
58
  ---
@@ -5,6 +5,8 @@
5
5
  > 数据只消费 `google-quarterly-collect.json`(及用户明确给的业务约束)。**禁止**再算一遍账户诊断,**禁止**编造预算 / CPA / 询盘 / 市场数。
6
6
  >
7
7
  > 编号 Q01–Q30 是规划条,**不是** playbook P10。
8
+ >
9
+ > 正文称呼跟区间走,和封面同一套:完整自然季写「季度」;从 1 号起的同一个月写「月度 / 本月」,路线图写「本月 / 次月 / 再次月」。封面由 `render` 生成,写完不要再改 HTML。
8
10
 
9
11
  ## 范围
10
12
 
@@ -63,6 +63,8 @@ siluzan-tso google-quarterly render \
63
63
  --out ./snap-p10/google-quarterly-report.html
64
64
  ```
65
65
 
66
+ `render` 打印 HTML 路径后**立刻交付该文件并结束**。封面和章节称呼由日期决定,不用改 HTML:完整自然季 →「Q3 季度…」;从 1 号起、起止在同一个月(含未过完的本月,如 10-01~10-22)→「10月 月度…」;跨月但不是完整自然季 →「本期」。**禁止**打开、grep 或改写 HTML,**禁止**为改「季度 / 月度」再跑一遍 render。用户没要 PDF 不要转换;要了也只转一次,不要读 PDF 正文、不要截图、不要用视觉模型验收。
67
+
66
68
  省略 `--start/--end` 时 collect 用上一完整自然季(上海日历)。
67
69
 
68
70
  可选:`--keyword-seeds "a,b"` 走 Keyword Planner(标注数据源,禁止写成行业指数)。**不做**频道订阅总数、订阅来源饼、视频类型(要 YouTube Analytics / 人工分类)。YouTube 广告能拿到的曝光 / TrueView / 完播 / CPV / 频道 ID /(若已关联)广告带来订阅,collect 写入 `exploreAds.types[video]` + `videosSummary`;PMax / 发现 / 展示并行写入 `exploreAds`。render 用 tab 切换,不编造 ABCD / 素材组 / 受众切分。
@@ -3,7 +3,7 @@
3
3
  > 统计区间:`{startDate}` ~ `{endDate}`
4
4
  > 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
5
5
 
6
- > **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`meta-period-report.html`)。
6
+ > **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`meta-period-report.html`)。封面按日期写成「季度复盘」或「月度复盘」,对不上仍是「周期复盘」。不要打开 HTML 改称呼。
7
7
  > 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止**仅 Markdown 摘要或纯 JSON 充当终稿。
8
8
  > 用户要 **表格 / Excel / xlsx** 时:步骤 1~3 相同,步骤 4 改 `--format xlsx`(见文末)。
9
9
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  数据块:总览、系列/组/广告、视频素材、受众(含性别/年龄/兴趣合并)(**以 `--json-out` 实际字段为准**)。
7
7
 
8
- **默认交付:HTML(`tiktok-analysis render` 注入 Agent JSON 生成;禁止手写/拼接 HTML)**。用户指定 Excel 时 Agent 脚本写 xlsx。
8
+ **默认交付:HTML(`tiktok-analysis render` 注入 Agent JSON 生成;禁止手写/拼接 HTML)**。用户指定 Excel 时 Agent 脚本写 xlsx。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
9
9
 
10
10
  ---
11
11
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 统计区间:`{startDate}` ~ `{endDate}`(**不能晚于今天**;省略默认近 7 天含今天)
4
4
  > 账户:`{mediaCustomerId}`(如 `porg-xxx`,与 `list-accounts -m Yandex` 一致)
5
- > **默认交付物**:一份可打开的 **HTML 文件**(`yandex-period-report.html`,样式对齐开发样例)。
5
+ > **默认交付物**:一份可打开的 **HTML 文件**(`yandex-period-report.html`,样式对齐开发样例)。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
6
6
  > 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止** Agent 手写/拼接 HTML。
7
7
  > **用户明确要求 Excel / xlsx** 时:拉数步骤不变,改为 Agent 脚本写 `.xlsx`;**不要**调用 `yandex-analysis render`;禁止假设存在内置 excel 子命令。见 `yandex-period-report-excel.md`。
8
8
 
@@ -609,6 +609,12 @@
609
609
  (function () {
610
610
  "use strict";
611
611
 
612
+ /** 只读 render 注入的 periodChrome,规则与 report-period.ts 相同。 */
613
+ function periodWord(data, neutral) {
614
+ const unit = data && data.periodChrome && data.periodChrome.unit;
615
+ return unit === "季度" || unit === "月度" ? unit : neutral;
616
+ }
617
+
612
618
  const PALETTE = [
613
619
  "#0ea5e9",
614
620
  "#6366f1",
@@ -772,7 +778,7 @@
772
778
  .join("");
773
779
  return `
774
780
  <div class="hero">
775
- <div class="eyebrow">BING ADS · 账户分析报告</div>
781
+ <div class="eyebrow">BING ADS · ${periodWord(data, "账户")}分析报告</div>
776
782
  <h1>${escapeHtml(meta.accountName || "Bing 广告账户")}</h1>
777
783
  <div class="meta-chips">
778
784
  <span class="meta-chip">📅 <b>${escapeHtml(period)}</b></span>
@@ -1159,7 +1165,7 @@ ${titleNotice}
1159
1165
 
1160
1166
  function renderFooter(data) {
1161
1167
  const generatedAt = data.meta?.generatedAt || new Date().toISOString();
1162
- return `<div class="footer"><span class="brand">siluzan-tso</span> · Bing 广告账户分析报告 · ${escapeHtml(generatedAt)} · 数据来源 bing-analysis</div>`;
1168
+ return `<div class="footer"><span class="brand">siluzan-tso</span> · Bing 广告${periodWord(data, "账户")}分析报告 · ${escapeHtml(generatedAt)} · 数据来源 bing-analysis</div>`;
1163
1169
  }
1164
1170
 
1165
1171
  function styleAxis(extra) {
@@ -1326,6 +1332,12 @@ ${titleNotice}
1326
1332
  '<div class="card">未找到报告数据(window.__BING_PERIOD_REPORT__ 为空),请通过 `bing-analysis render` 生成本文件。</div>';
1327
1333
  return;
1328
1334
  }
1335
+ const cover = periodWord(data, "账户");
1336
+ document.title =
1337
+ (data.meta && data.meta.accountName ? data.meta.accountName + " · " : "") +
1338
+ "Bing 广告" +
1339
+ cover +
1340
+ "分析报告";
1329
1341
  root.innerHTML = [
1330
1342
  renderHero(data),
1331
1343
  renderExecutiveSummary(data),
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 统计区间:`{startDate}` ~ `{endDate}`(**不可包含今天或昨天**,见下文)
4
4
  > 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
5
- > **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`bing-period-report.html`)。
5
+ > **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`bing-period-report.html`)。封面按日期写成「季度」或「月度」,对不上仍是「账户分析」。不要打开 HTML 改称呼。
6
6
  > 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止** Agent 手写/拼接 HTML(语法错误会导致报告无法显示)。
7
7
  > **用户明确要求 Excel / xlsx** 时:拉数步骤不变,改为 Agent 脚本写 `.xlsx`;**不要**调用 `bing-analysis render`;禁止假设存在内置 excel 子命令。
8
8
 
@@ -656,6 +656,12 @@
656
656
  (function () {
657
657
  "use strict";
658
658
 
659
+ /** 只读 render 注入的 periodChrome,规则与 report-period.ts 相同。 */
660
+ function periodWord(data, neutral) {
661
+ const unit = data && data.periodChrome && data.periodChrome.unit;
662
+ return unit === "季度" || unit === "月度" ? unit : neutral;
663
+ }
664
+
659
665
  const PALETTE = [
660
666
  "#3b82f6",
661
667
  "#8b5cf6",
@@ -785,7 +791,7 @@
785
791
  .join("");
786
792
  return `
787
793
  <div class="hero">
788
- <div class="eyebrow">GOOGLE ADS · 周期分析报告</div>
794
+ <div class="eyebrow">GOOGLE ADS · ${periodWord(data, "周期")}分析报告</div>
789
795
  <h1>${escapeHtml(meta.accountName || "Google Ads 账户")}</h1>
790
796
  <div class="meta-chips">
791
797
  <span class="meta-chip">📅 <b>${escapeHtml(period)}</b></span>
@@ -1117,7 +1123,7 @@
1117
1123
 
1118
1124
  function renderFooter(data) {
1119
1125
  const generatedAt = data.meta?.generatedAt || new Date().toISOString();
1120
- return `<div class="footer"><span class="brand">siluzan-tso</span> · Google Ads 周期分析报告 · ${escapeHtml(generatedAt)} · 数据来源 google-analysis</div>`;
1126
+ return `<div class="footer"><span class="brand">siluzan-tso</span> · Google Ads ${periodWord(data, "周期")}分析报告 · ${escapeHtml(generatedAt)} · 数据来源 google-analysis</div>`;
1121
1127
  }
1122
1128
 
1123
1129
  function styleAxis(extra) {
@@ -1375,6 +1381,12 @@
1375
1381
  '<div class="card">未找到报告数据(window.__GOOGLE_PERIOD_REPORT__ 为空),请通过 `google-analysis render` 生成本文件。</div>';
1376
1382
  return;
1377
1383
  }
1384
+ const cover = periodWord(data, "周期");
1385
+ document.title =
1386
+ (data.meta && data.meta.accountName ? data.meta.accountName + " · " : "") +
1387
+ "Google Ads " +
1388
+ cover +
1389
+ "分析报告";
1378
1390
  root.innerHTML = [
1379
1391
  renderHero(data),
1380
1392
  renderExecutiveSummary(data),
@@ -52,7 +52,7 @@ siluzan-tso google-analysis render \
52
52
 
53
53
  模板源码:`report-templates/google-period-report.html`(结构与样式,ECharts 渲染逻辑已内联)。
54
54
 
55
- `render` 会向输出目录写入 HTML,并注入 `window.__GOOGLE_PERIOD_REPORT__`。
55
+ `render` 会向输出目录写入 HTML,并注入 `window.__GOOGLE_PERIOD_REPORT__`。封面按 `meta.startDate/endDate` 写成「季度分析」或「月度分析」;不是完整自然季、也不是从 1 号起的同一个月时,仍写「周期分析」。不要打开 HTML 改这几个字。
56
56
  传 `--snapshot-dir` 时 CLI 自动从快照合并 KPI、每日趋势图表、周期汇总/系列/设备/地域/受众/关键词表格:**数值一律以 CLI 快照覆盖**(`meta` 仅补空)。本期 KPI 优先 `campaigns` 累加。若 manifest 缺条目但磁盘上仍有对应 `*.json`,CLI 会按文件名自愈补齐。Agent 只写 `narrative`,勿自填 `kpis`/`tables`。
57
57
 
58
58
  ---
@@ -5,6 +5,8 @@
5
5
  > 数据只消费 `google-quarterly-collect.json`(及用户明确给的业务约束)。**禁止**再算一遍账户诊断,**禁止**编造预算 / CPA / 询盘 / 市场数。
6
6
  >
7
7
  > 编号 Q01–Q30 是规划条,**不是** playbook P10。
8
+ >
9
+ > 正文称呼跟区间走,和封面同一套:完整自然季写「季度」;从 1 号起的同一个月写「月度 / 本月」,路线图写「本月 / 次月 / 再次月」。封面由 `render` 生成,写完不要再改 HTML。
8
10
 
9
11
  ## 范围
10
12
 
@@ -593,14 +593,24 @@
593
593
  const adminLabel = meta.drillAdminLabel || "一级行政区";
594
594
  const drillTitle = drillCountry ? drillCountry + "下钻" : "消耗最高国家下钻";
595
595
  const showDrill = meta.usGeoSource === "subdivisions" && (data.usStates || []).length > 0;
596
- document.title = name + " · Google 季度经营报告";
596
+ const chrome = data.periodChrome || {
597
+ current: quarterTag(meta.current) || "本期",
598
+ previous: quarterTag(meta.previous) || "上期",
599
+ next: nextQuarterTag(meta.current),
600
+ unit: "季度",
601
+ reportTitle: "Google 季度经营报告",
602
+ coverEn: "QUARTERLY REPORT",
603
+ };
604
+ const curQ = chrome.current || "本期";
605
+ const prevQ = chrome.previous || "上期";
606
+ const nextQ = chrome.next || "下期";
607
+ const unit = chrome.unit || "";
608
+ const periodHead = unit ? curQ + " " + unit : curQ;
609
+ document.title = name + " · " + (chrome.reportTitle || "Google 经营报告");
597
610
  document.getElementById("nav-brand").innerHTML = esc(name) + ' · <span>Google Ads</span>';
598
611
  document.getElementById("nav-explore").classList.toggle("hidden", hideSpecial);
599
612
 
600
613
  const exec = lines(narrative.executiveSummary);
601
- const curQ = quarterTag(meta.current) || "本期";
602
- const prevQ = quarterTag(meta.previous) || "上期";
603
- const nextQ = nextQuarterTag(meta.current);
604
614
  const prev = kpis.previous || {};
605
615
  const vsYoy = kpis.vsYoy || {};
606
616
  const months = data.monthly || [];
@@ -631,9 +641,9 @@
631
641
  const root = document.getElementById("report-root");
632
642
  root.innerHTML = `
633
643
  <header class="cover" id="cover">
634
- <div class="cover-tag">GOOGLE ADS · QUARTERLY REPORT</div>
644
+ <div class="cover-tag">GOOGLE ADS · ${esc(chrome.coverEn || "QUARTERLY REPORT")}</div>
635
645
  <h1>${esc(name)}</h1>
636
- <p class="subtitle">${esc(curQ)} 季度数据汇总 &amp; ${esc(nextQ)} 规划</p>
646
+ <p class="subtitle">${esc(periodHead)}数据汇总 &amp; ${esc(nextQ)} 规划</p>
637
647
  <div class="cover-meta">
638
648
  <span>账户:<strong>${esc(meta.accountId || "—")}</strong></span>
639
649
  <span>渠道:<strong>${esc(channelsLabel)}</strong></span>
@@ -677,7 +687,7 @@
677
687
  </section>
678
688
  <section class="chapter" id="s1">
679
689
  <p class="num">PART 01</p>
680
- <h2>${esc(curQ)} 季度数据表现</h2>
690
+ <h2>${esc(periodHead)}数据表现</h2>
681
691
  <p>先看效率是否变好,再看预算花在哪,最后看询盘从哪些国家来。</p>
682
692
  </section>
683
693
  <section class="page" id="efficiency">
@@ -1594,14 +1604,14 @@
1594
1604
  function planChapters(plan) {
1595
1605
  if (!plan || typeof plan !== "object") return analysisBlock(plan, "规划");
1596
1606
  const chapters = [
1597
- ["analysis", "季度投放结论"],
1598
- ["goals", "季度目标与 KPI"],
1607
+ ["analysis", "投放结论"],
1608
+ ["goals", "目标与 KPI"],
1599
1609
  ["budget", "预算与市场规划"],
1600
1610
  ["structure", "广告账户结构"],
1601
1611
  ["keywords", "关键词与搜索主题"],
1602
1612
  ["audience", "受众规划"],
1603
1613
  ["creative", "素材与落地页"],
1604
- ["roadmap", "季度执行路线图"],
1614
+ ["roadmap", "执行路线图"],
1605
1615
  ["risks", "风险与待确认"],
1606
1616
  ];
1607
1617
  const parts = chapters.map(([key, title]) => {
@@ -63,6 +63,8 @@ siluzan-tso google-quarterly render \
63
63
  --out ./snap-p10/google-quarterly-report.html
64
64
  ```
65
65
 
66
+ `render` 打印 HTML 路径后**立刻交付该文件并结束**。封面和章节称呼由日期决定,不用改 HTML:完整自然季 →「Q3 季度…」;从 1 号起、起止在同一个月(含未过完的本月,如 10-01~10-22)→「10月 月度…」;跨月但不是完整自然季 →「本期」。**禁止**打开、grep 或改写 HTML,**禁止**为改「季度 / 月度」再跑一遍 render。用户没要 PDF 不要转换;要了也只转一次,不要读 PDF 正文、不要截图、不要用视觉模型验收。
67
+
66
68
  省略 `--start/--end` 时 collect 用上一完整自然季(上海日历)。
67
69
 
68
70
  可选:`--keyword-seeds "a,b"` 走 Keyword Planner(标注数据源,禁止写成行业指数)。**不做**频道订阅总数、订阅来源饼、视频类型(要 YouTube Analytics / 人工分类)。YouTube 广告能拿到的曝光 / TrueView / 完播 / CPV / 频道 ID /(若已关联)广告带来订阅,collect 写入 `exploreAds.types[video]` + `videosSummary`;PMax / 发现 / 展示并行写入 `exploreAds`。render 用 tab 切换,不编造 ABCD / 素材组 / 受众切分。
@@ -126,7 +126,7 @@ table.data th,table.data td{white-space:nowrap;font-size:.8rem}
126
126
  </nav>
127
127
  <div class="wrap">
128
128
  <header class="hero">
129
- <div class="hero-badge">Facebook Ads · 周期复盘</div>
129
+ <div class="hero-badge" id="heroBadge">Facebook Ads · 周期复盘</div>
130
130
  <h1 id="heroTitle">Meta / Facebook 广告投放分析报告</h1>
131
131
  <p class="sub" id="heroSub"></p>
132
132
  </header>
@@ -269,7 +269,11 @@ window.__META_PERIOD_REPORT__ = window.__META_PERIOD_REPORT__ || null;
269
269
  var brand = meta.accountName || meta.brandName || "Facebook 广告账户";
270
270
  var period = meta.periodLabel || ((meta.startDate || "") + " ~ " + (meta.endDate || ""));
271
271
  var campaign = meta.campaignName || "";
272
- document.title = brand + " Facebook 广告报告" + (campaign ? " | " + campaign : "");
272
+ var coverUnit = data.periodChrome && data.periodChrome.unit;
273
+ var coverWord = coverUnit === "季度" || coverUnit === "月度" ? coverUnit : "周期";
274
+ document.title = brand + " · Facebook " + coverWord + "分析报告" + (campaign ? " | " + campaign : "");
275
+ var heroBadge = document.getElementById("heroBadge");
276
+ if (heroBadge) heroBadge.textContent = "Facebook Ads · " + coverWord + "复盘";
273
277
  document.getElementById("heroTitle").innerHTML = esc(brand) + " · Facebook 广告投放<br/>" + esc(period) + "分析报告";
274
278
  document.getElementById("heroSub").textContent =
275
279
  (campaign ? "广告系列:" + campaign + " | " : "") +
@@ -3,7 +3,7 @@
3
3
  > 统计区间:`{startDate}` ~ `{endDate}`
4
4
  > 账户:`{mediaCustomerId}`(`{mediaCustomerName}`)
5
5
 
6
- > **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`meta-period-report.html`)。
6
+ > **默认交付物**:一份可打开的、带 ECharts 图表的 **HTML 文件**(`meta-period-report.html`)。封面按日期写成「季度复盘」或「月度复盘」,对不上仍是「周期复盘」。不要打开 HTML 改称呼。
7
7
  > 用户**未指定格式**时一律走下方 **标准四步流程**;**禁止**仅 Markdown 摘要或纯 JSON 充当终稿。
8
8
  > 用户要 **表格 / Excel / xlsx** 时:步骤 1~3 相同,步骤 4 改 `--format xlsx`(见文末)。
9
9
 
@@ -639,6 +639,12 @@
639
639
  (function () {
640
640
  "use strict";
641
641
 
642
+ /** 只读 render 注入的 periodChrome,规则与 report-period.ts 相同。 */
643
+ function periodWord(data, neutral) {
644
+ const unit = data && data.periodChrome && data.periodChrome.unit;
645
+ return unit === "季度" || unit === "月度" ? unit : neutral;
646
+ }
647
+
642
648
  const PALETTE = [
643
649
  "#fe2c55",
644
650
  "#25f4ee",
@@ -765,7 +771,7 @@
765
771
  .join("");
766
772
  return `
767
773
  <div class="hero">
768
- <div class="eyebrow">TIKTOK ADS · 账户分析报告</div>
774
+ <div class="eyebrow">TIKTOK ADS · ${periodWord(data, "账户")}分析报告</div>
769
775
  <h1>${escapeHtml(meta.accountName || "TikTok 广告主账户")}</h1>
770
776
  <div class="meta-chips">
771
777
  <span class="meta-chip">📅 <b>${escapeHtml(period)}</b></span>
@@ -1031,7 +1037,7 @@
1031
1037
 
1032
1038
  function renderFooter(data) {
1033
1039
  const generatedAt = data.meta?.generatedAt || new Date().toISOString();
1034
- return `<div class="footer"><span class="brand">siluzan-tso</span> · TikTok 广告主账户分析报告 · ${escapeHtml(generatedAt)} · 数据来源 tiktok-analysis</div>`;
1040
+ return `<div class="footer"><span class="brand">siluzan-tso</span> · TikTok 广告主${periodWord(data, "账户")}分析报告 · ${escapeHtml(generatedAt)} · 数据来源 tiktok-analysis</div>`;
1035
1041
  }
1036
1042
 
1037
1043
  function styleAxis(extra) {
@@ -1122,6 +1128,12 @@
1122
1128
  '<div class="card">未找到报告数据(window.__TIKTOK_PERIOD_REPORT__ 为空),请通过 `tiktok-analysis render` 生成本文件。</div>';
1123
1129
  return;
1124
1130
  }
1131
+ const cover = periodWord(data, "账户");
1132
+ document.title =
1133
+ (data.meta && data.meta.accountName ? data.meta.accountName + " · " : "") +
1134
+ "TikTok 广告主" +
1135
+ cover +
1136
+ "分析报告";
1125
1137
  root.innerHTML = [
1126
1138
  renderHero(data),
1127
1139
  renderExecutiveSummary(data),