siluzan-tso-cli 1.1.26-beta.2 → 1.1.26-beta.3
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 +1 -1
- package/dist/skill/SKILL.md +1 -1
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/README.md +1 -1
- package/dist/skill/references/core/agent-conventions.md +2 -2
- package/dist/skill/references/core/deliverable-preflight.md +66 -67
- package/dist/skill/references/core/playbooks.md +2 -2
- package/dist/skill/report-templates/REPORT-WORKFLOW.md +4 -4
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/package.json +1 -1
- package/dist/skill/scripts/check-deliverable-snap.mjs +0 -170
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.26-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.26-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -17,7 +17,7 @@ allowed-tools: Bash(siluzan-tso:*) Read Write
|
|
|
17
17
|
|
|
18
18
|
> **Agent 纪律(每个新任务必读)**:Read `references/core/agent-conventions.md`,再按 `SKILL.md` 任务表 Read 域文档。禁止跨话题复用参数记忆;数据类任务一律 `--json-out` + **仅用代码**读落盘 JSON(见 `references/core/tips.md`)——**禁止**用 Read 工具打开 `writtenFiles` 里的完整 `*.json`。
|
|
19
19
|
>
|
|
20
|
-
> **报告/Excel 交付前**:Read `references/core/deliverable-preflight.md
|
|
20
|
+
> **报告/Excel 交付前**:Read `references/core/deliverable-preflight.md`,**Read 最终产物**并按自检表确认币种与章节完整;币种只认当次 `list-accounts` 的 `currencyCode`。
|
|
21
21
|
>
|
|
22
22
|
> **开户**:首次进入开户话题须先向用户罗列该媒体(或未指明媒体时六平台)**全部必填项**,见 `references/accounts/open-account-by-media.md` §「首次响应硬规范」。
|
|
23
23
|
>
|
package/dist/skill/_meta.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| `core/setup.md` | 安装、登录(手机验证码优先)、配置、更新 |
|
|
10
10
|
| `core/agent-conventions.md` | **Agent 必读**:加载纪律、硬规范、数据转换、时间/币种/批量 |
|
|
11
11
|
| `core/tips.md` | `--json-out` 协议、outline→脚本读 JSON |
|
|
12
|
-
| `core/deliverable-preflight.md` |
|
|
12
|
+
| `core/deliverable-preflight.md` | **交付前审阅最终产物**:Agent Read 报告文件并对照自检表(币种/章节) |
|
|
13
13
|
| `core/playbooks.md` | P1–P7 高频任务标准动作 |
|
|
14
14
|
| `core/workflows.md` | 多命令串联业务流程 |
|
|
15
15
|
| `core/skill-authoring.md` | Skill 文档维护最佳实践(维护者) |
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
2. 涉及写入/修改/删除的操作必须与用户确认
|
|
38
38
|
3. 按计划执行,说明每步意图
|
|
39
39
|
4. 用成对的读命令复核写入结果;异步任务每 5s 轮询直到完成
|
|
40
|
-
5. **交付前**(报告/Excel/含金额话术):Read `references/core/deliverable-preflight.md
|
|
40
|
+
5. **交付前**(报告/Excel/含金额话术):Read `references/core/deliverable-preflight.md`,**Read 最终产物文件**并按自检表 A/B/C 逐项确认后再发给用户
|
|
41
41
|
6. 全部完成后预测用户下一步操作
|
|
42
42
|
|
|
43
43
|
### 执行模式速查
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
- **符号**:`CNY` → **¥**;`USD` → **$**。多账户按 `currencyCode` 分表,**禁止**跨币种求和。
|
|
137
137
|
- **金额单位统一为「元」**:报告保留 2 位小数。
|
|
138
138
|
- **Google 广告诊断报告**:`report-templates/google-ads-diagnosis.md` § 撰写硬约束 — 每日趋势金额/CPA 必须 2 位小数;每个模块除表格外须有「分析」+「建议」,禁止只贴数据。
|
|
139
|
-
-
|
|
139
|
+
- **交付前审阅产物**:报告/Excel 交付前 Agent **Read 最终文件**并对照 `deliverable-preflight.md` 自检表;未审阅禁止交付。
|
|
140
140
|
- **品牌名优先级**:(1) 用户明确提供 → (2) `list-accounts.mag.advertiserName` → (3) 网址域名占位 `[待确认品牌名]`。**严禁**把英文域名翻译为虚构中文品牌。
|
|
141
141
|
|
|
142
142
|
---
|
|
@@ -1,107 +1,106 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 交付前审阅最终产物(Agent 自检)
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 用户反馈:产出物常出现**币种错误**、**报告缺章/缺数**。
|
|
4
|
+
> **不靠外部校验脚本**——由 **Agent 在交付用户前,亲自查看已生成的最终文件**(HTML / Markdown / Excel 等),对照清单结论后再发送。
|
|
4
5
|
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## 原则
|
|
8
9
|
|
|
9
|
-
|
|
|
10
|
-
| ---- |
|
|
11
|
-
|
|
|
12
|
-
|
|
|
10
|
+
| 必须 | 禁止 |
|
|
11
|
+
| ---- | ---- |
|
|
12
|
+
| **Read 最终交付文件**(你刚写出的 `.html` / `.md` 等) | 用 Read 打开 `--json-out` 落盘的业务 `*.json` 来「核对」(见 `tips.md`) |
|
|
13
|
+
| 对照当次 `report-templates/*.md` 章节逐项勾选 | 未打开产物就声称「报告已好」 |
|
|
14
|
+
| 币种与**当次对话**里 `list-accounts` / CLI 表格已确认的 `currencyCode` 一致 | 凭媒体猜 USD;CNY 用 `$` |
|
|
15
|
+
| 缺数据的章节写 `[ 数据不可用:… ]` | 空白章节、编造数字填坑 |
|
|
16
|
+
|
|
17
|
+
拉数阶段仍用 `--json-out` + **代码**读 JSON 生成产物;**审阅阶段**只看**最终产物** + 记忆中的账户元数据(账户 ID、区间、币种),不跑 `check-*.mjs` 类脚本。
|
|
13
18
|
|
|
14
19
|
---
|
|
15
20
|
|
|
16
|
-
##
|
|
21
|
+
## 审阅时机
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
在以下动作**之后、发给用户之前**:
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
1. 已用脚本/CLI 拉数并生成报告文件(或 Excel)。
|
|
26
|
+
2. 文件已写入磁盘路径(你知道路径)。
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
siluzan-tso list-accounts -m Google -k <mediaCustomerId> --json-out ./snap
|
|
24
|
-
node -e "
|
|
25
|
-
const d=require('./snap/list-accounts-google.json');
|
|
26
|
-
const ma=(d.items||[]).map(x=>x.ma||x).find(m=>String(m.mediaCustomerId)===process.argv[1]);
|
|
27
|
-
if(!ma){console.error('账户未找到');process.exit(1);}
|
|
28
|
-
console.log(ma.currencyCode, ma.mediaCustomerName);
|
|
29
|
-
" <mediaCustomerId>
|
|
30
|
-
```
|
|
28
|
+
**然后必须 Read 该文件**(或 Excel 时对用户说明各 Sheet 名称与首行摘要——若无法 Read 二进制 xlsx,须在对话中贴出**自检表**逐条勾选,并说明依据来自生成脚本的 stdout 摘要,而非猜)。
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
- 首行必须有:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:CNY|USD)`。
|
|
34
|
-
- 符号:**CNY → ¥**,**USD → $**。详见 `references/accounts/currency.md`。
|
|
30
|
+
---
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
## 自检表 A · 币种(每条必勾)
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
- 拉数后打开 **`manifest-<accountId>.json`**(用脚本 `JSON.parse`,勿 Read 整份若很大),核对 `artifacts[].section` 与模板一致。
|
|
34
|
+
对照 `references/accounts/currency.md`:
|
|
40
35
|
|
|
41
|
-
|
|
36
|
+
- [ ] 报告**封面/首行**含 `统计区间:…(货币:CNY)` 或 `(货币:USD)`(或等价英文标注)
|
|
37
|
+
- [ ] 代码与**一致**:`CNY` → 全文金额用 **¥**;`USD` → **$**;未混用
|
|
38
|
+
- [ ] 与当次 `list-accounts -k …` 得到的 **`currencyCode` 相同**(回顾对话中 CLI 表格或你记录的「币种」一行)
|
|
39
|
+
- [ ] 多账户报告:分币种分表,**无** CNY+USD 直接相加的一行「总计」
|
|
40
|
+
- [ ] 未出现「Google 账户所以是美金」类表述
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
**不通过** → 改产物文件后**重新 Read** 再审,不得交付。
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
---
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
## 自检表 B · 结构完整(对照当次模板)
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
node scripts/check-deliverable-snap.mjs ./snap --preset p4-google
|
|
51
|
-
# 或自定义:--required overview,campaigns,geographic
|
|
52
|
-
# 已知账户为 CNY 时:--expect-currency CNY
|
|
53
|
-
```
|
|
48
|
+
先确认当次用的是哪份模板(如 `google-period-report.md`、`okki-weekly-google-client.md`、`google-inquiry-analysis.md`),再逐项:
|
|
54
49
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| `--expect-currency CNY\|USD` | 与 `list-accounts` / `overview` 中 `currencyCode` 对照 |
|
|
50
|
+
- [ ] 模板要求的**每一章/Sheet**在产物里都存在(标题或 Sheet 名可对应)
|
|
51
|
+
- [ ] **无**整章空白(除非该章标注 `[ 数据不可用:… ]`)
|
|
52
|
+
- [ ] 执行摘要 / 概览:有消耗、展示、点击等**至少一项非空数字**(或明确写无数据)
|
|
53
|
+
- [ ] 周期报告默认维(P4):概览、日趋势、系列、设备、地域、关键词等**均有对应段落**(Meta 无接口的维度按模板写「未提供」)
|
|
54
|
+
- [ ] 优化建议:独立一节,**不是**只有表格没有文字
|
|
55
|
+
- [ ] Google 诊断:每模块除表格外有「分析」+「建议」(见 `google-ads-diagnosis.md`)
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
**不通过** → 补拉 CLI 维度、重跑生成逻辑、**重写产物**后再 Read 审阅。
|
|
64
58
|
|
|
65
|
-
|
|
59
|
+
---
|
|
66
60
|
|
|
67
|
-
|
|
61
|
+
## 自检表 C · 数字可信(抽样,不读大 JSON)
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
node -e "
|
|
71
|
-
const o=require('./snap/overview-<id>_<start>-<end>.json');
|
|
72
|
-
const r=o.record||{};
|
|
73
|
-
const p=r.currentPeriod||{};
|
|
74
|
-
console.log('spend',p.spend,'clicks',p.clicks,'currency',r.currencyCode);
|
|
75
|
-
"
|
|
76
|
-
```
|
|
63
|
+
不打开落盘 JSON,用以下**已掌握信息**对照产物:
|
|
77
64
|
|
|
78
|
-
|
|
65
|
+
- [ ] 报告总消耗/CPA 数量级与你在生成过程中 **CLI/脚本 stdout 打印的汇总**一致(同一会话里应有过一行 summary;若无,先补跑一次极小 `node -e` 只打印 totals 再对照——**仅用于核对,不把 JSON 贴进对话**)
|
|
66
|
+
- [ ] 账户 ID、账户名、统计区间与用户需求一致
|
|
67
|
+
- [ ] 无「示例账户」「占位 123456」等模板残留
|
|
68
|
+
- [ ] 表格行数与预期同一(如 P3 要求每个 `-a` ID 都有一行)
|
|
69
|
+
|
|
70
|
+
**不通过** → 修正产物或重新生成,禁止手改数字糊弄。
|
|
79
71
|
|
|
80
72
|
---
|
|
81
73
|
|
|
82
|
-
##
|
|
74
|
+
## 向用户交付时的格式(建议)
|
|
83
75
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
审阅通过后,消息中附带简短**自检结论**(证明你看过产物):
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
交付前自检(已通过):
|
|
80
|
+
- 产物:./out/report-xxx.html
|
|
81
|
+
- 币种:CNY(来自 list-accounts,与报告首行一致)
|
|
82
|
+
- 章节:8/8 默认维度齐全;关键词章 [ 数据不可用:接口超时 ] 已标注
|
|
83
|
+
- 区间:2026-04-01 ~ 2026-04-30
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
若某项未通过,**不要**写「已通过」,应说明正在补全。
|
|
89
87
|
|
|
90
88
|
---
|
|
91
89
|
|
|
92
|
-
##
|
|
90
|
+
## 与 Playbook 的关系
|
|
93
91
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
92
|
+
| Playbook | 审阅时重点 |
|
|
93
|
+
| -------- | ---------- |
|
|
94
|
+
| P1 | 画像是否含 stats + 系列要点;币种 |
|
|
95
|
+
| P4 | `google-period-report.md` 8 块是否齐 |
|
|
96
|
+
| P4-FB | 无接口维度是否写「未提供」 |
|
|
97
|
+
| P6 / P7 | 按各自 `report-templates/*.md` Sheet/章节表逐项 |
|
|
99
98
|
|
|
100
99
|
---
|
|
101
100
|
|
|
102
101
|
## 相关文档
|
|
103
102
|
|
|
104
103
|
- `references/accounts/currency.md`
|
|
105
|
-
- `report-templates/REPORT-WORKFLOW.md`
|
|
106
|
-
- `references/core/tips.md
|
|
104
|
+
- `report-templates/REPORT-WORKFLOW.md`
|
|
105
|
+
- `references/core/tips.md`(拉数阶段)
|
|
107
106
|
- `references/core/playbooks.md`
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
4. `ad campaigns -a <mediaCustomerId> --start <S> --end <D> --json-out ./snap-p1`
|
|
25
25
|
5. 诊断/画像报告:Read `report-templates/google-ads-diagnosis.md`(**硬约束:每日趋势 2 位小数、每模块必有分析**)+ `google-account-diagnosis-report.md`(章节与 CLI);`google-analysis --json-out` 须含 `daily-metrics`(按日趋势)。
|
|
26
26
|
6. 首行标注统计区间和货币;HTML 可对齐 `report-template.html` + `section-*` 区块。
|
|
27
|
-
7.
|
|
27
|
+
7. 交付前:**Read 最终产物**,按 `deliverable-preflight.md` 自检表确认币种与章节。
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
@@ -67,7 +67,7 @@ siluzan-tso accounts-digest -m Google -a id1,id2,... --start <S> --end <D> --jso
|
|
|
67
67
|
1. 确认时间范围;区间 > 3 个月时分段(季度/月)。
|
|
68
68
|
2. 按 `google-period-report.md` 默认维度执行 `google-analysis --json-out <dir>`。
|
|
69
69
|
3. 报告须含:账户概览、投放趋势、Top 关键词/系列/地区分布、优化建议。
|
|
70
|
-
4.
|
|
70
|
+
4. 交付前:**Read 最终 HTML/报告文件**,对照 `google-period-report.md` 与 `deliverable-preflight.md` 自检。
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
|
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
|
-
### 步骤 4b
|
|
54
|
+
### 步骤 4b:交付前审阅最终产物(**必做**)
|
|
55
55
|
|
|
56
|
-
- Read `references/core/deliverable-preflight.md
|
|
57
|
-
-
|
|
58
|
-
-
|
|
56
|
+
- Read `references/core/deliverable-preflight.md` 自检表 A/B/C。
|
|
57
|
+
- **Read 刚生成的报告文件**(HTML/Markdown 等),对照当次 `report-templates/*.md` 逐项确认章节与币种。
|
|
58
|
+
- 未通过:**修改产物或补拉数据后重新生成**,再次 Read 审阅;不得在未审阅时交付。
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
@@ -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.26-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.26-beta.3'
|
|
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.26-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.26-beta.3"
|
|
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"
|
package/package.json
CHANGED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 交付前快照自检(币种 + 缺文件/空数据)
|
|
4
|
-
*
|
|
5
|
-
* 用法:
|
|
6
|
-
* node scripts/check-deliverable-snap.mjs <snapDir> [--preset p4-google] [--required a,b]
|
|
7
|
-
* node scripts/check-deliverable-snap.mjs ./snap --expect-currency CNY
|
|
8
|
-
*
|
|
9
|
-
* 退出码:0 通过;1 有 error;2 用法错误
|
|
10
|
-
*/
|
|
11
|
-
import fs from "node:fs";
|
|
12
|
-
import path from "node:path";
|
|
13
|
-
|
|
14
|
-
const PRESETS = {
|
|
15
|
-
"p4-google": [
|
|
16
|
-
"overview",
|
|
17
|
-
"daily-metrics",
|
|
18
|
-
"dimension-summary",
|
|
19
|
-
"campaigns",
|
|
20
|
-
"devices",
|
|
21
|
-
"geographic",
|
|
22
|
-
"keywords",
|
|
23
|
-
],
|
|
24
|
-
"p1-google": ["list-accounts", "stats", "ad-campaigns"],
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function parseArgs(argv) {
|
|
28
|
-
const snapDir = argv[2];
|
|
29
|
-
if (!snapDir) return { error: "缺少 snapDir" };
|
|
30
|
-
let preset;
|
|
31
|
-
let required = [];
|
|
32
|
-
let expectCurrency;
|
|
33
|
-
for (let i = 3; i < argv.length; i++) {
|
|
34
|
-
if (argv[i] === "--preset" && argv[i + 1]) {
|
|
35
|
-
preset = argv[++i];
|
|
36
|
-
} else if (argv[i] === "--required" && argv[i + 1]) {
|
|
37
|
-
required = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
38
|
-
} else if (argv[i] === "--expect-currency" && argv[i + 1]) {
|
|
39
|
-
expectCurrency = argv[++i].toUpperCase();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (preset) {
|
|
43
|
-
const p = PRESETS[preset];
|
|
44
|
-
if (!p) return { error: `未知 preset:${preset},可选:${Object.keys(PRESETS).join(", ")}` };
|
|
45
|
-
required = [...new Set([...required, ...p])];
|
|
46
|
-
}
|
|
47
|
-
return { snapDir: path.resolve(snapDir), required, expectCurrency };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function readJson(filePath) {
|
|
51
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function listManifests(dir) {
|
|
55
|
-
return fs.readdirSync(dir).filter((f) => /^manifest(-.*)?\.json$/.test(f) || /^report-manifest(-.*)?\.json$/.test(f) || /^cli-manifest(-.*)?\.json$/.test(f));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function sectionMatches(artifactSection, required) {
|
|
59
|
-
return required.some((r) => artifactSection === r || artifactSection.startsWith(`${r}-`) || artifactSection.includes(r));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function itemCount(payload) {
|
|
63
|
-
if (!payload || typeof payload !== "object") return null;
|
|
64
|
-
if (Array.isArray(payload.items)) return payload.items.length;
|
|
65
|
-
if (Array.isArray(payload.data?.items)) return payload.data.items.length;
|
|
66
|
-
if (payload.record && typeof payload.record === "object") return 1;
|
|
67
|
-
if (Array.isArray(payload.data)) return payload.data.length;
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function extractCurrencyFromPayload(payload, section) {
|
|
72
|
-
if (!payload || typeof payload !== "object") return null;
|
|
73
|
-
const rec = payload.record ?? payload.data?.record;
|
|
74
|
-
if (rec?.currencyCode) return String(rec.currencyCode);
|
|
75
|
-
const items = payload.items ?? payload.data?.items;
|
|
76
|
-
if (Array.isArray(items) && items.length > 0) {
|
|
77
|
-
const ma = items[0].ma ?? items[0];
|
|
78
|
-
if (ma?.currencyCode) return String(ma.currencyCode);
|
|
79
|
-
if (items[0].currencyCode) return String(items[0].currencyCode);
|
|
80
|
-
}
|
|
81
|
-
if (section.includes("list-accounts") && Array.isArray(items) && items[0]?.ma?.currencyCode) {
|
|
82
|
-
return String(items[0].ma.currencyCode);
|
|
83
|
-
}
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function main() {
|
|
88
|
-
const parsed = parseArgs(process.argv);
|
|
89
|
-
if (parsed.error) {
|
|
90
|
-
console.error(parsed.error);
|
|
91
|
-
process.exit(2);
|
|
92
|
-
}
|
|
93
|
-
const { snapDir, required, expectCurrency } = parsed;
|
|
94
|
-
if (!fs.existsSync(snapDir)) {
|
|
95
|
-
console.error(`目录不存在:${snapDir}`);
|
|
96
|
-
process.exit(2);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const manifests = listManifests(snapDir);
|
|
100
|
-
if (manifests.length === 0) {
|
|
101
|
-
console.error(`未找到 manifest / report-manifest / cli-manifest:${snapDir}`);
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const errors = [];
|
|
106
|
-
const warnings = [];
|
|
107
|
-
let currencySeen = null;
|
|
108
|
-
|
|
109
|
-
for (const mf of manifests) {
|
|
110
|
-
const man = readJson(path.join(snapDir, mf));
|
|
111
|
-
const arts = man.artifacts ?? [];
|
|
112
|
-
console.log(`\n📋 ${mf}(${arts.length} 个 artifact)`);
|
|
113
|
-
|
|
114
|
-
if (required.length > 0) {
|
|
115
|
-
for (const req of required) {
|
|
116
|
-
const hit = arts.some((a) => sectionMatches(String(a.section ?? ""), [req]));
|
|
117
|
-
if (!hit) errors.push(`[${mf}] 缺少维度/命令:${req}`);
|
|
118
|
-
else console.log(` ✓ 已拉取:${req}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
for (const art of arts) {
|
|
123
|
-
const fp = path.join(snapDir, art.file);
|
|
124
|
-
if (!fs.existsSync(fp)) {
|
|
125
|
-
errors.push(`[${mf}] 文件缺失:${art.file}`);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
let payload;
|
|
129
|
-
try {
|
|
130
|
-
payload = readJson(fp);
|
|
131
|
-
} catch (e) {
|
|
132
|
-
errors.push(`[${mf}] JSON 解析失败:${art.file}`);
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const n = itemCount(payload);
|
|
136
|
-
const sec = String(art.section ?? "");
|
|
137
|
-
if (n === 0) warnings.push(`[${mf}] ${art.file} items 为空(章节可能写「无数据」)`);
|
|
138
|
-
const ccy = extractCurrencyFromPayload(payload, sec);
|
|
139
|
-
if (ccy) {
|
|
140
|
-
if (!currencySeen) currencySeen = ccy;
|
|
141
|
-
else if (currencySeen !== ccy) warnings.push(`[${mf}] 币种不一致:${ccy} vs 先前 ${currencySeen}`);
|
|
142
|
-
if (sec.includes("overview") || sec.includes("list-accounts")) {
|
|
143
|
-
console.log(` 💱 币种来源 ${art.file}:${ccy}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (expectCurrency) {
|
|
150
|
-
if (!currencySeen) warnings.push(`未从快照解析到 currencyCode,无法对照 --expect-currency ${expectCurrency}`);
|
|
151
|
-
else if (currencySeen !== expectCurrency) {
|
|
152
|
-
errors.push(`币种不符:快照为 ${currencySeen},期望 ${expectCurrency}(报告须用 ¥/ $ 与代码一致)`);
|
|
153
|
-
} else console.log(`\n✓ 币种与期望一致:${expectCurrency}`);
|
|
154
|
-
} else if (currencySeen) {
|
|
155
|
-
console.log(`\n💱 建议在报告首行写:统计区间:…(货币:${currencySeen})`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (warnings.length) {
|
|
159
|
-
console.log("\n⚠️ 警告:");
|
|
160
|
-
warnings.forEach((w) => console.log(` - ${w}`));
|
|
161
|
-
}
|
|
162
|
-
if (errors.length) {
|
|
163
|
-
console.log("\n❌ 错误:");
|
|
164
|
-
errors.forEach((e) => console.log(` - ${e}`));
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
console.log("\n✅ 交付前自检通过(仍须确认报告正文金额来自脚本、非手填)\n");
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
main();
|