siluzan-tso-cli 1.1.25-beta.1 → 1.1.25-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.
- package/README.md +1 -1
- package/dist/index.js +143 -20
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/accounts/currency.md +1 -1
- package/dist/skill/references/analytics/account-analytics.md +34 -20
- package/dist/skill/references/operations/hosted-automation-monitoring-json.md +1 -1
- package/dist/skill/references/operations/hosted-automation-optimize-scale.md +2 -2
- package/dist/skill/references/operations/hosted-automation-self-control.md +1 -1
- package/dist/skill/report-templates/google-inquiry-analysis.md +8 -8
- package/dist/skill/report-templates/google-period-report.md +1 -1
- package/dist/skill/report-templates/okki-weekly-google-client.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/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.25-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.25-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -4611,6 +4611,118 @@ var init_accounts_digest2 = __esm({
|
|
|
4611
4611
|
}
|
|
4612
4612
|
});
|
|
4613
4613
|
|
|
4614
|
+
// src/commands/google-analysis/wrap-snapshot-envelope.ts
|
|
4615
|
+
function coerceToRowArray(value) {
|
|
4616
|
+
if (Array.isArray(value)) return value;
|
|
4617
|
+
if (value === null || typeof value !== "object") return [];
|
|
4618
|
+
const o = value;
|
|
4619
|
+
const keys = Object.keys(o);
|
|
4620
|
+
if (keys.length === 0) return [];
|
|
4621
|
+
if (keys.every((k) => /^\d+$/.test(k))) {
|
|
4622
|
+
return keys.sort((a, b) => Number(a) - Number(b)).map((k) => o[k]);
|
|
4623
|
+
}
|
|
4624
|
+
return [];
|
|
4625
|
+
}
|
|
4626
|
+
function asRecord(value) {
|
|
4627
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
4628
|
+
return value;
|
|
4629
|
+
}
|
|
4630
|
+
function readGatewayList(bag) {
|
|
4631
|
+
const items = coerceToRowArray(bag.data);
|
|
4632
|
+
const meta = {};
|
|
4633
|
+
if (typeof bag.code === "number") meta.code = bag.code;
|
|
4634
|
+
if (typeof bag.message === "string") meta.message = bag.message;
|
|
4635
|
+
return { items, ...Object.keys(meta).length > 0 ? { meta } : {} };
|
|
4636
|
+
}
|
|
4637
|
+
function flattenMaterials(payload) {
|
|
4638
|
+
const bag = asRecord(payload);
|
|
4639
|
+
if (!bag) return [];
|
|
4640
|
+
const images = coerceToRowArray(bag.images).map(
|
|
4641
|
+
(row) => asRecord(row) ? { ...row, assetType: "image" } : row
|
|
4642
|
+
);
|
|
4643
|
+
const videos = coerceToRowArray(bag.videos).map(
|
|
4644
|
+
(row) => asRecord(row) ? { ...row, assetType: "video" } : row
|
|
4645
|
+
);
|
|
4646
|
+
return [...images, ...videos];
|
|
4647
|
+
}
|
|
4648
|
+
function wrapGoogleAnalysisSnapshotPayload(section, payload) {
|
|
4649
|
+
let items = [];
|
|
4650
|
+
let record = null;
|
|
4651
|
+
let meta;
|
|
4652
|
+
if (COMPOSITE_SECTIONS.has(section)) {
|
|
4653
|
+
items = flattenMaterials(payload);
|
|
4654
|
+
} else if (RECORD_ONLY_SECTIONS.has(section)) {
|
|
4655
|
+
record = asRecord(payload);
|
|
4656
|
+
} else if (GATEWAY_LIST_SECTIONS.has(section)) {
|
|
4657
|
+
const bag = asRecord(payload);
|
|
4658
|
+
if (bag) {
|
|
4659
|
+
const r = readGatewayList(bag);
|
|
4660
|
+
items = r.items;
|
|
4661
|
+
meta = r.meta;
|
|
4662
|
+
}
|
|
4663
|
+
} else if (ROOT_ARRAY_SECTIONS.has(section)) {
|
|
4664
|
+
items = coerceToRowArray(payload);
|
|
4665
|
+
} else {
|
|
4666
|
+
const rowKey = ROW_KEY_BY_SECTION[section];
|
|
4667
|
+
const bag = asRecord(payload);
|
|
4668
|
+
if (rowKey && bag) {
|
|
4669
|
+
items = coerceToRowArray(bag[rowKey]);
|
|
4670
|
+
} else {
|
|
4671
|
+
const arr = coerceToRowArray(payload);
|
|
4672
|
+
if (arr.length > 0) items = arr;
|
|
4673
|
+
else record = asRecord(payload);
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
return {
|
|
4677
|
+
schemaVersion: GOOGLE_ANALYSIS_FILE_SCHEMA_VERSION,
|
|
4678
|
+
section,
|
|
4679
|
+
itemCount: items.length,
|
|
4680
|
+
items,
|
|
4681
|
+
record,
|
|
4682
|
+
...meta !== void 0 ? { meta } : {}
|
|
4683
|
+
};
|
|
4684
|
+
}
|
|
4685
|
+
var GOOGLE_ANALYSIS_FILE_SCHEMA_VERSION, ROW_KEY_BY_SECTION, RECORD_ONLY_SECTIONS, COMPOSITE_SECTIONS, ROOT_ARRAY_SECTIONS, GATEWAY_LIST_SECTIONS;
|
|
4686
|
+
var init_wrap_snapshot_envelope = __esm({
|
|
4687
|
+
"src/commands/google-analysis/wrap-snapshot-envelope.ts"() {
|
|
4688
|
+
"use strict";
|
|
4689
|
+
GOOGLE_ANALYSIS_FILE_SCHEMA_VERSION = 3;
|
|
4690
|
+
ROW_KEY_BY_SECTION = {
|
|
4691
|
+
keywords: "keywords",
|
|
4692
|
+
campaigns: "campaigns",
|
|
4693
|
+
devices: "devices",
|
|
4694
|
+
geographic: "countries",
|
|
4695
|
+
"geo-matched": "countries",
|
|
4696
|
+
"campaign-geo": "countries",
|
|
4697
|
+
"campaign-geo-matched": "countries",
|
|
4698
|
+
"campaign-device": "devices",
|
|
4699
|
+
audience: "audience"
|
|
4700
|
+
};
|
|
4701
|
+
RECORD_ONLY_SECTIONS = /* @__PURE__ */ new Set([
|
|
4702
|
+
"overview",
|
|
4703
|
+
"resource-counts",
|
|
4704
|
+
"dimension-summary",
|
|
4705
|
+
"gold-account",
|
|
4706
|
+
"final-urls",
|
|
4707
|
+
"campaign-types",
|
|
4708
|
+
"ads-index"
|
|
4709
|
+
]);
|
|
4710
|
+
COMPOSITE_SECTIONS = /* @__PURE__ */ new Set(["materials"]);
|
|
4711
|
+
ROOT_ARRAY_SECTIONS = /* @__PURE__ */ new Set([
|
|
4712
|
+
"campaign-hour",
|
|
4713
|
+
"extensions",
|
|
4714
|
+
"asset-images",
|
|
4715
|
+
"videos",
|
|
4716
|
+
"daily-metrics",
|
|
4717
|
+
"conversion-actions"
|
|
4718
|
+
]);
|
|
4719
|
+
GATEWAY_LIST_SECTIONS = /* @__PURE__ */ new Set([
|
|
4720
|
+
"search-terms",
|
|
4721
|
+
"ads"
|
|
4722
|
+
]);
|
|
4723
|
+
}
|
|
4724
|
+
});
|
|
4725
|
+
|
|
4614
4726
|
// src/utils/snapshot/google-analysis.ts
|
|
4615
4727
|
import * as fs7 from "fs/promises";
|
|
4616
4728
|
import * as path11 from "path";
|
|
@@ -4618,7 +4730,7 @@ function googleAnalysisManifestFile(accountId) {
|
|
|
4618
4730
|
return `${applyIdSuffix("manifest", accountId)}.json`;
|
|
4619
4731
|
}
|
|
4620
4732
|
function buildOutlineHints(section) {
|
|
4621
|
-
const hints = [];
|
|
4733
|
+
const hints = [...GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS];
|
|
4622
4734
|
if (RATE_BEARING_SECTIONS.has(section)) {
|
|
4623
4735
|
hints.push(...GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS);
|
|
4624
4736
|
}
|
|
@@ -4656,7 +4768,8 @@ async function readManifestIfExists(absDir, accountId) {
|
|
|
4656
4768
|
try {
|
|
4657
4769
|
const raw = await fs7.readFile(manifestPath, "utf8");
|
|
4658
4770
|
const parsed = JSON.parse(raw);
|
|
4659
|
-
|
|
4771
|
+
const sv = parsed?.schemaVersion;
|
|
4772
|
+
if (sv !== 2 && sv !== SCHEMA_VERSION2 || !Array.isArray(parsed.artifacts)) {
|
|
4660
4773
|
continue;
|
|
4661
4774
|
}
|
|
4662
4775
|
return parsed;
|
|
@@ -4696,12 +4809,16 @@ async function writeGoogleAnalysisSnapshot(params) {
|
|
|
4696
4809
|
);
|
|
4697
4810
|
const relativeFile = fileName;
|
|
4698
4811
|
const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4699
|
-
const
|
|
4812
|
+
const wrapped = wrapGoogleAnalysisSnapshotPayload(
|
|
4813
|
+
params.section,
|
|
4814
|
+
params.payload
|
|
4815
|
+
);
|
|
4816
|
+
const body = JSON.stringify(wrapped, null, 2);
|
|
4700
4817
|
await fs7.writeFile(path11.join(absDir, fileName), `${body}
|
|
4701
4818
|
`, "utf8");
|
|
4702
4819
|
const outlineFileName = snapshotOutlineFileName(fileName);
|
|
4703
4820
|
const outlineExtra = buildOutlineHints(params.section);
|
|
4704
|
-
const outlineBody = formatOutlineFileBody(fileName,
|
|
4821
|
+
const outlineBody = formatOutlineFileBody(fileName, wrapped, outlineExtra);
|
|
4705
4822
|
await fs7.writeFile(path11.join(absDir, outlineFileName), `${outlineBody}
|
|
4706
4823
|
`, "utf8");
|
|
4707
4824
|
const existing = await readManifestIfExists(absDir, accountSlug || void 0);
|
|
@@ -4735,15 +4852,16 @@ async function writeGoogleAnalysisSnapshot(params) {
|
|
|
4735
4852
|
agentHint: OUTLINE_AGENT_HINT
|
|
4736
4853
|
};
|
|
4737
4854
|
}
|
|
4738
|
-
var LEGACY_MANIFEST_FILE, CLI_PACKAGE2, SCHEMA_VERSION2, DEFAULT_FIELD_GUIDE2, GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS, RATE_BEARING_SECTIONS, GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS, GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION;
|
|
4855
|
+
var LEGACY_MANIFEST_FILE, CLI_PACKAGE2, SCHEMA_VERSION2, DEFAULT_FIELD_GUIDE2, GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS, RATE_BEARING_SECTIONS, GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS, GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS, GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION;
|
|
4739
4856
|
var init_google_analysis = __esm({
|
|
4740
4857
|
"src/utils/snapshot/google-analysis.ts"() {
|
|
4741
4858
|
"use strict";
|
|
4742
4859
|
init_cli_json();
|
|
4860
|
+
init_wrap_snapshot_envelope();
|
|
4743
4861
|
init_dir();
|
|
4744
4862
|
LEGACY_MANIFEST_FILE = "manifest.json";
|
|
4745
4863
|
CLI_PACKAGE2 = "siluzan-tso-cli";
|
|
4746
|
-
SCHEMA_VERSION2 =
|
|
4864
|
+
SCHEMA_VERSION2 = 3;
|
|
4747
4865
|
DEFAULT_FIELD_GUIDE2 = {
|
|
4748
4866
|
markdownRefs: ["references/accounts/currency.md", "references/analytics/account-analytics.md"],
|
|
4749
4867
|
tsTypesModule: "tso-cli/src/types/google-analysis-api.ts"
|
|
@@ -4782,6 +4900,10 @@ var init_google_analysis = __esm({
|
|
|
4782
4900
|
"// \u6982\u7387\u5B57\u6BB5\uFF1A`ctr` / `conversionRate` \u81EA manifest schemaVersion 2\uFF082026-05\uFF09\u8D77\u5DF2\u7531 CLI \u7EDF\u4E00\u5F52\u4E00\u4E3A **0~1 \u5C0F\u6570**\uFF08\u5982 `0.0964` = 9.64%\uFF09\u3002",
|
|
4783
4901
|
"// \u5199 Excel 0~1 \u5C0F\u6570\u5217\uFF1A\u76F4\u63A5\u5199\u5165\uFF1B\u5199\u300Cx%\u300D\u6587\u6848\uFF1A`(v * 100).toFixed(2) + '%'`\uFF1B**\u7981\u6B62**\u518D \xF7100\u3002`interactionRate` \u4ECD\u662F\u5B57\u7B26\u4E32 0~1 \u5C0F\u6570\uFF0C`parseFloat` \u540E\u4F7F\u7528\u3002"
|
|
4784
4902
|
];
|
|
4903
|
+
GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS = [
|
|
4904
|
+
"// \u7EDF\u4E00\u8BBF\u95EE\u5C42\uFF08manifest / \u6587\u4EF6\u5185\u5747\u4E3A `schemaVersion: 3`\uFF09\uFF1A\u5224\u522B\u53EA\u6709\u4E00\u6761\u2014\u2014`record` \u975E null \u5373**\u6C47\u603B\u7EF4\u5EA6**\uFF08overview/resource-counts/gold-account/ads-index/dimension-summary/final-urls/campaign-types\uFF09\uFF0C\u8BFB `record`\uFF1B\u5426\u5219\u8BFB `items[]`\uFF08\u6240\u6709\u5217\u8868\u7EF4\u5EA6\uFF0C\u542B materials\uFF0C\u884C\u5DF2\u7EDF\u4E00\u642C\u5165\uFF0C`itemCount` = `items.length`\uFF09\u3002",
|
|
4905
|
+
"// \u7EF4\u5EA6\u4E13\u5C5E\u539F\u952E\u540D\uFF08keywords/campaigns/countries/data \u7B49\uFF09\u5DF2**\u79FB\u9664**\uFF0C\u4E0D\u8981\u518D\u6309\u7EF4\u5EA6\u731C\u952E\u540D\uFF1B`search-terms`/`ads` \u7684\u7F51\u5173 `code`/`message` \u5728 `meta`\uFF08\u4E0E\u6570\u636E\u65E0\u5173\uFF09\uFF1Bmaterials \u884C\u5E26 `assetType: 'image'|'video'`\u3002"
|
|
4906
|
+
];
|
|
4785
4907
|
GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION = {
|
|
4786
4908
|
keywords: [
|
|
4787
4909
|
"// \u4E2D\u6587\u8BD1\u540D\uFF1A`keywordMatchTypeZh`\uFF08\u7531 `keywordMatchType` \u7ECF match-type-en2zh.json \u7FFB\u8BD1\uFF0C\u8986\u76D6 BROAD/PHRASE/EXACT \u4E0E Broad/Phrase/Exact \u5927\u5C0F\u5199\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
|
|
@@ -102574,6 +102696,7 @@ var init_google_analysis2 = __esm({
|
|
|
102574
102696
|
init_normalize_rates();
|
|
102575
102697
|
init_normalize_impression_shares();
|
|
102576
102698
|
init_translate_fields();
|
|
102699
|
+
init_wrap_snapshot_envelope();
|
|
102577
102700
|
init_register_cli();
|
|
102578
102701
|
}
|
|
102579
102702
|
});
|
|
@@ -112931,7 +113054,7 @@ init_cli_table();
|
|
|
112931
113054
|
// src/commands/ad/campaign-batch-diff.ts
|
|
112932
113055
|
init_auth();
|
|
112933
113056
|
init_cli_json_snapshot();
|
|
112934
|
-
function
|
|
113057
|
+
function asRecord2(v) {
|
|
112935
113058
|
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
112936
113059
|
}
|
|
112937
113060
|
function pickString(...vals) {
|
|
@@ -112985,7 +113108,7 @@ function liveAdHasHeadline(ad, headline) {
|
|
|
112985
113108
|
function formatExtensionPlanned(ext) {
|
|
112986
113109
|
if (!ext) return "\u2014";
|
|
112987
113110
|
const type = pickString(ext["typeV2"], ext["AssetFieldType"], ext["type"]);
|
|
112988
|
-
const props =
|
|
113111
|
+
const props = asRecord2(ext["Properties"]);
|
|
112989
113112
|
const phone = props ? pickString(props["PhoneNumber"], props["phoneNumber"]) : "";
|
|
112990
113113
|
const code = props ? pickString(props["ContryCode"], props["CountryCode"]) : "";
|
|
112991
113114
|
const parts = [type ? `\u7C7B\u578B: ${type}` : "", code || phone ? `\u7535\u8BDD: ${code}${phone}` : ""].filter(Boolean);
|
|
@@ -112996,13 +113119,13 @@ function listPlannedKeywords(campaign) {
|
|
|
112996
113119
|
if (!Array.isArray(groups)) return [];
|
|
112997
113120
|
const out = [];
|
|
112998
113121
|
for (let gi = 0; gi < groups.length; gi++) {
|
|
112999
|
-
const g =
|
|
113122
|
+
const g = asRecord2(groups[gi]);
|
|
113000
113123
|
if (!g) continue;
|
|
113001
113124
|
const groupName = pickString(g["Name"], g["name"]) || `AdGroupsForBatchJob[${gi}]`;
|
|
113002
113125
|
const blocks = g["KeywordsForBatchJob"];
|
|
113003
113126
|
if (!Array.isArray(blocks)) continue;
|
|
113004
113127
|
for (let bi = 0; bi < blocks.length; bi++) {
|
|
113005
|
-
const block =
|
|
113128
|
+
const block = asRecord2(blocks[bi]);
|
|
113006
113129
|
if (!block) continue;
|
|
113007
113130
|
const matchTypeV2 = pickString(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
|
|
113008
113131
|
const texts = block["KeywordText"];
|
|
@@ -113026,7 +113149,7 @@ function listPlannedNegativeKeywords(campaign) {
|
|
|
113026
113149
|
if (!Array.isArray(blocks)) return [];
|
|
113027
113150
|
const out = [];
|
|
113028
113151
|
for (let bi = 0; bi < blocks.length; bi++) {
|
|
113029
|
-
const block =
|
|
113152
|
+
const block = asRecord2(blocks[bi]);
|
|
113030
113153
|
if (!block) continue;
|
|
113031
113154
|
const matchTypeV2 = pickString(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
|
|
113032
113155
|
const texts = block["KeywordText"];
|
|
@@ -113066,7 +113189,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113066
113189
|
const plannedGroupCount = Array.isArray(groups) ? groups.length : 0;
|
|
113067
113190
|
if (Array.isArray(groups)) {
|
|
113068
113191
|
for (let gi = 0; gi < groups.length; gi++) {
|
|
113069
|
-
const g =
|
|
113192
|
+
const g = asRecord2(groups[gi]);
|
|
113070
113193
|
if (!g) continue;
|
|
113071
113194
|
const groupName = pickString(g["Name"], g["name"]) || `AdGroupsForBatchJob[${gi}]`;
|
|
113072
113195
|
const groupPath = `campaign.AdGroupsForBatchJob[${gi}]`;
|
|
@@ -113097,7 +113220,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113097
113220
|
const blocks = g["KeywordsForBatchJob"];
|
|
113098
113221
|
if (Array.isArray(blocks)) {
|
|
113099
113222
|
for (let bi = 0; bi < blocks.length; bi++) {
|
|
113100
|
-
const block =
|
|
113223
|
+
const block = asRecord2(blocks[bi]);
|
|
113101
113224
|
if (!block) continue;
|
|
113102
113225
|
const matchTypeV2 = pickString(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
|
|
113103
113226
|
const texts = block["KeywordText"];
|
|
@@ -113126,7 +113249,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113126
113249
|
const ads = g["AdsForBatchJob"];
|
|
113127
113250
|
if (Array.isArray(ads)) {
|
|
113128
113251
|
for (let ai = 0; ai < ads.length; ai++) {
|
|
113129
|
-
const ad =
|
|
113252
|
+
const ad = asRecord2(ads[ai]);
|
|
113130
113253
|
if (!ad) continue;
|
|
113131
113254
|
const path24 = `${groupPath}.AdsForBatchJob[${ai}]`;
|
|
113132
113255
|
const primary = pickString(ad["headlinePart1"], ad["AdTitle"]);
|
|
@@ -113182,7 +113305,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113182
113305
|
).length;
|
|
113183
113306
|
if (plannedExt > liveExt && Array.isArray(extBlocks)) {
|
|
113184
113307
|
for (let ei = liveExt; ei < extBlocks.length; ei++) {
|
|
113185
|
-
const ext =
|
|
113308
|
+
const ext = asRecord2(extBlocks[ei]);
|
|
113186
113309
|
missing.push({
|
|
113187
113310
|
layer: "extension",
|
|
113188
113311
|
path: `campaign.ExtensionsForBatchJob[${ei}]`,
|
|
@@ -113210,7 +113333,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113210
113333
|
plannedKeywords: plannedKeywords.length,
|
|
113211
113334
|
liveKeywords: liveKwCount,
|
|
113212
113335
|
plannedAds: Array.isArray(groups) ? groups.reduce((n, g) => {
|
|
113213
|
-
const gr =
|
|
113336
|
+
const gr = asRecord2(g);
|
|
113214
113337
|
const ads = gr?.["AdsForBatchJob"];
|
|
113215
113338
|
return n + (Array.isArray(ads) ? ads.length : 0);
|
|
113216
113339
|
}, 0) : 0,
|
|
@@ -113284,7 +113407,7 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
113284
113407
|
};
|
|
113285
113408
|
}
|
|
113286
113409
|
function resolveCampaignIdFromBatch(record) {
|
|
113287
|
-
const campaign =
|
|
113410
|
+
const campaign = asRecord2(record["campaign"]);
|
|
113288
113411
|
return pickString(campaign?.["Id"], campaign?.["id"], record["campaignId"]);
|
|
113289
113412
|
}
|
|
113290
113413
|
function shellArgPath(p) {
|
|
@@ -121390,7 +121513,7 @@ function readJsonFile(filePath) {
|
|
|
121390
121513
|
}
|
|
121391
121514
|
});
|
|
121392
121515
|
}
|
|
121393
|
-
function
|
|
121516
|
+
function asRecord3(value) {
|
|
121394
121517
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
121395
121518
|
return value;
|
|
121396
121519
|
}
|
|
@@ -121416,10 +121539,10 @@ function injectReportData(html, payload) {
|
|
|
121416
121539
|
async function runWebsiteDiagnosisRender(opts) {
|
|
121417
121540
|
const dataPath = path17.resolve(opts.dataFile);
|
|
121418
121541
|
const dataRaw = await readJsonFile(dataPath);
|
|
121419
|
-
let data =
|
|
121542
|
+
let data = asRecord3(dataRaw);
|
|
121420
121543
|
if (opts.collectFile) {
|
|
121421
121544
|
const collectRaw = await readJsonFile(path17.resolve(opts.collectFile));
|
|
121422
|
-
data = mergeCollectLighthouse(data,
|
|
121545
|
+
data = mergeCollectLighthouse(data, asRecord3(collectRaw));
|
|
121423
121546
|
}
|
|
121424
121547
|
const templatePath = websiteDiagnosisReportTemplatePath();
|
|
121425
121548
|
let html;
|
package/dist/skill/_meta.json
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
1. **`list-accounts`**(推荐第一步):`items[].ma.currencyCode`;表格有 **「币种」** 列。
|
|
35
35
|
2. **`balance` / `balance-scan` / `accounts-digest`**:`items[].currencyCode` 或行内已格式化的金额(含代码)。
|
|
36
36
|
3. **`stats`**:`items[].currencyCode`(Google 含今天窗口时可能无币种,见 `accounts/accounts.md` 时效性说明)。
|
|
37
|
-
4. **`google-analysis --sections overview`**:`overview-*.json`
|
|
37
|
+
4. **`google-analysis --sections overview`**:`overview-*.json` 的 `record.currencyCode`(汇总维度,`schemaVersion 3` 起整块在 `record`)。
|
|
38
38
|
5. **`ad campaigns` / `ad groups`**:JSON 内 `currencyCode`(与账户主币种一致)。
|
|
39
39
|
6. **发票/充值**:`invoice billable` 的 `currencyCode`;人民币订单与美金订单开票类型不同,见 `accounts/finance.md`。
|
|
40
40
|
|
|
@@ -140,7 +140,7 @@ siluzan-tso google-analysis -a <id> --exclude materials,gold-account --json-out
|
|
|
140
140
|
| `keywords` | 关键词;默认 `costGreater=0`(仅有消耗)、`limit=0`(不封顶)、`orderByCost=true`;可用 `--limit` 限制 Top N |
|
|
141
141
|
| `search-terms` | 搜索词;默认 `limit=0`(不封顶)、`orderByCost=true`;落盘前过滤 `spend>0`;含 `queryTargetingStatus` / `queryTargetingStatusZh`(已添加/已排除/都没有);可用 `--limit` 限制 Top N |
|
|
142
142
|
| `campaigns` | 广告系列 |
|
|
143
|
-
| `campaign-hour` |
|
|
143
|
+
| `campaign-hour` | 系列按小时(行在 `items[]`) |
|
|
144
144
|
| `ads` | 广告;与 `ad list` 同源 |
|
|
145
145
|
| `extensions` | 附加信息;可选 `--level` |
|
|
146
146
|
| `devices` | 设备分布(账户级 `DeviceSectionData`) |
|
|
@@ -152,7 +152,7 @@ siluzan-tso google-analysis -a <id> --exclude materials,gold-account --json-out
|
|
|
152
152
|
| `audience` | 受众;可选 `--audience-type` |
|
|
153
153
|
| `asset-images` | 图片素材 |
|
|
154
154
|
| `videos` | 视频 |
|
|
155
|
-
| `materials` |
|
|
155
|
+
| `materials` | 图片+视频拍平进 `items[]`,每行带 `assetType: 'image' \| 'video'` |
|
|
156
156
|
| `resource-counts` | 结构统计 |
|
|
157
157
|
| `conversion-actions` | 转化动作 |
|
|
158
158
|
| `daily-metrics` | 按日指标(主平台 `GET …/report/media-account/google/account-daily-reports`,`--json-out` 落盘为按日数组)。**广告诊断报告**中:金额/CPA **保留 2 位小数**,转化/点击/展示为整数;须配趋势**分析**段落(见 `google-ads-diagnosis.md`) |
|
|
@@ -162,6 +162,33 @@ siluzan-tso google-analysis -a <id> --exclude materials,gold-account --json-out
|
|
|
162
162
|
| `dimension-summary` | 账户汇总 |
|
|
163
163
|
| `campaign-types` | 系列类型(实时,可查当天;不传 `--start`/`--end`) |
|
|
164
164
|
|
|
165
|
+
### 落盘 JSON
|
|
166
|
+
|
|
167
|
+
| 字段 | 含义 |
|
|
168
|
+
| ---- | ---- |
|
|
169
|
+
| `schemaVersion` | 文件内固定 `3` |
|
|
170
|
+
| `section` | 维度名(与文件名 stem 一致;仅标识,读数据用不到) |
|
|
171
|
+
| `itemCount` | `items.length` |
|
|
172
|
+
| `items` | **所有列表维度的行数据**统一入口(含 `materials`);汇总维度为 `[]` |
|
|
173
|
+
| `record` | **汇总维度**的整块对象;列表维度为 `null` |
|
|
174
|
+
| `meta` | 仅 `search-terms` / `ads`:网关 `code`/`message`(与数据无关,可忽略) |
|
|
175
|
+
|
|
176
|
+
**唯一判别规则**:`record` 非 `null` → 读 `record`(汇总维度);否则 → 读 `items[]`(列表维度)。
|
|
177
|
+
|
|
178
|
+
| 桶 | 维度 | 读法 |
|
|
179
|
+
| -- | ---- | ---- |
|
|
180
|
+
| **列表** | `keywords`、`search-terms`、`campaigns`、`campaign-hour`、`ads`、`extensions`、`devices`、`geographic`、`geo-matched`、`campaign-geo`、`campaign-geo-matched`、`campaign-device`、`audience`、`asset-images`、`videos`、`materials`、`daily-metrics`、`conversion-actions` | `d.items[]` |
|
|
181
|
+
| **汇总** | `overview`、`resource-counts`、`dimension-summary`、`gold-account`、`ads-index`、`final-urls`、`campaign-types` | `d.record` |
|
|
182
|
+
|
|
183
|
+
脚本示例(Node)—— 所有维度同一套读法:
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
const d = require("./snap/campaigns-9526903813_20260401-20260430.json");
|
|
187
|
+
const data = d.record ?? d.items; // record 非空=汇总维度;否则=列表行
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
旧快照(manifest `schemaVersion` 1/2,无 `items`/`record`)须按 `*.outline.txt` 最后一行类型读取。
|
|
191
|
+
|
|
165
192
|
### stdout 摘要
|
|
166
193
|
|
|
167
194
|
```json
|
|
@@ -196,24 +223,11 @@ siluzan-tso google-analysis -a <id> --exclude materials,gold-account --json-out
|
|
|
196
223
|
| 平均点击成本 | `averageCpc` |
|
|
197
224
|
| 转化成本 | `costPerConversion` |
|
|
198
225
|
|
|
199
|
-
### 中文化字段(En→Zh 字典补充)
|
|
200
|
-
|
|
201
|
-
CLI 在落盘前为以下维度自动补「中文译名字段」(**原英文字段保留**,便于排错;字典未命中时该字段缺省):
|
|
202
|
-
|
|
203
|
-
| 维度 | 源字段(英文) | 新增字段(中文) | 字典源 |
|
|
204
|
-
| -------------- | ------------------ | -------------------- | --------------------------------------------------------------- |
|
|
205
|
-
| `keywords` | `keywordMatchType` | `keywordMatchTypeZh` | `match-type-en2zh.json`(覆盖 `BROAD/PHRASE/EXACT` 大小写写法) |
|
|
206
|
-
| `search-terms` | `matchType` | `matchTypeZh` | `match-type-en2zh.json` |
|
|
207
|
-
| `search-terms` | `queryTargetingStatus` | `queryTargetingStatusZh` | 网关 `Added`→已添加、`Excluded`→已排除、`None`→都没有(写 Excel「已添加/已排除」列用此字段) |
|
|
208
|
-
| `campaign-geo` | `countryOrRegion` | `countryOrRegionZh` | `geo-en2zh.json`(覆盖 105 个国家/地区) |
|
|
209
|
-
| `geo-matched` | `countryOrRegion` | `countryOrRegionZh` | `geo-en2zh.json` |
|
|
210
|
-
| `campaign-geo-matched` | `countryOrRegion` | `countryOrRegionZh` | `geo-en2zh.json` |
|
|
211
|
-
|
|
212
|
-
写 Excel / 报表脚本可直接读 `keywordMatchTypeZh` / `matchTypeZh` / `countryOrRegionZh` 输出中文,**不要**自己维护字典或在线翻译。判断覆盖率用 `if (row.xxxZh)` 即可。
|
|
213
|
-
|
|
214
226
|
### CampaignSectionData 关键字段
|
|
215
227
|
|
|
216
|
-
`campaigns[]`
|
|
228
|
+
> `campaigns` 维度的系列行在 `items[]`(`schemaVersion 3` 起统一信封;本节下文的 `campaigns[]` 即指 `items[]`)。
|
|
229
|
+
|
|
230
|
+
`items[]` 每行额外包含:`conversionsValue`、`conversionsValuePerCost`(`spend ≤ 0` 时为 0)、`campaignTargetCpaYuan`、`maximizeConversionsTargetCpaYuan`、`manualCpcEnhancedCpcEnabled`、`percentCpcEnhancedCpcEnabled`。所有金额字段(`*Yuan` 后缀)已统一为元,可直接展示,无需换算。
|
|
217
231
|
|
|
218
232
|
日预算字段为 `budgetAmountYuan`(元),脚本可直接 `row.budgetAmountYuan`;网关原始 `budgetAmount`(分)已不再落盘。
|
|
219
233
|
|
|
@@ -239,11 +253,11 @@ CLI 在落盘前为以下维度自动补「中文译名字段」(**原英文
|
|
|
239
253
|
|
|
240
254
|
#### 行顶 legacy 份额(与 `competitiveMetrics` 同名 3 项)
|
|
241
255
|
|
|
242
|
-
`campaigns
|
|
256
|
+
`campaigns` / `keywords` 维度的 `items[]` 行顶仍保留 `searchImpressionShare`、`searchBudgetLostImpressionShare`、`searchRankLostImpressionShare`。CLI 落盘前已统一为 **0~1 小数**(与 `competitiveMetrics` 同名项一致;有嵌套时以 `competitiveMetrics` 为准)。Top/AbsoluteTop/Content/ClickShare 等扩展项仅存在于 `competitiveMetrics`。展示为百分比:`(v * 100).toFixed(2) + '%'`。
|
|
243
257
|
|
|
244
258
|
### campaign-hour 字段
|
|
245
259
|
|
|
246
|
-
`campaignId`、`campaignName`、`date`、`hour`、`spend`、`impressions`、`clicks`、`conversions
|
|
260
|
+
`items[]` 每行:`campaignId`、`campaignName`、`date`、`hour`、`spend`、`impressions`、`clicks`、`conversions`(`schemaVersion 3` 起行统一在 `items[]`,不再是裸数组根)。
|
|
247
261
|
|
|
248
262
|
---
|
|
249
263
|
|
|
@@ -71,7 +71,7 @@ mkdir -p ./snap-monitor && siluzan-tso google-analysis -a <mediaCustomerId> --se
|
|
|
71
71
|
mkdir -p ./snap-monitor && siluzan-tso google-analysis -a <mediaCustomerId> --sections final-urls --json-out ./snap-monitor
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
读 **`./snap-monitor/final-urls.json
|
|
74
|
+
读 **`./snap-monitor/final-urls.json`**。`final-urls` 为**汇总维度**,整块对象在 **`record`**(`schemaVersion 3` 起;`items` 为 `[]`):`record` 的**键名**为网关返回的资源标识(以当次 JSON 为准),**值为字符串数组**(每个元素是一条最终到达网址)。CLI **不**代发 HTTP 请求判断 4xx/5xx;死链判定须在宿主对 URL 执行 HEAD/GET(注意频率与 robots/合规)。
|
|
75
75
|
|
|
76
76
|
**按创意行拉数**(可与拒审、启停共用一轮数据):
|
|
77
77
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
mkdir -p ./snap-scale && siluzan-tso google-analysis -a <mediaCustomerId> --sections campaigns --start <YYYY-MM-DD> --end <YYYY-MM-DD> --json-out ./snap-scale
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
读 **`./snap-scale/campaigns-<accountId>.json`**(具体路径见 stdout 摘要的 `writtenFiles[0]` 或 `manifest-<accountId>.json` 的 `artifacts
|
|
21
|
+
读 **`./snap-scale/campaigns-<accountId>.json`**(具体路径见 stdout 摘要的 `writtenFiles[0]` 或 `manifest-<accountId>.json` 的 `artifacts`)。系列行在 **`items[]`**(`schemaVersion 3` 统一信封),单行关注(键名以当次落盘 JSON 为准):
|
|
22
22
|
|
|
23
23
|
- **`conversionsValuePerCost`**(与 Google「转化价值/费用」语义一致,作 ROAS 代理)
|
|
24
24
|
- **`searchBudgetLostImpressionShare`**、**`searchRankLostImpressionShare`**、**`searchImpressionShare`**
|
|
@@ -40,7 +40,7 @@ siluzan-tso ad campaigns -a <mediaCustomerId> --start <YYYY-MM-DD> --end <YYYY-M
|
|
|
40
40
|
mkdir -p ./snap-scale && siluzan-tso google-analysis -a <mediaCustomerId> --sections campaign-hour --start <YYYY-MM-DD> --end <YYYY-MM-DD> --json-out ./snap-scale
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
读 **`./snap-scale/campaign-hour.json
|
|
43
|
+
读 **`./snap-scale/campaign-hour.json`**。行在 **`items[]`**:**`campaignId`**、**`date`**、**`hour`**、**`spend`**。宿主可做「近若干小时花费 vs 预期」的辅助条件。
|
|
44
44
|
|
|
45
45
|
### 4. 条件示例(仅示意)
|
|
46
46
|
|
|
@@ -125,7 +125,7 @@ siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --max-cpc <主
|
|
|
125
125
|
|
|
126
126
|
见 **`references/google-ads/google-ads.md`**「广告组编辑」。
|
|
127
127
|
|
|
128
|
-
写前**必须**先 **`ad groups --json-out ./snap` / `ad campaigns --json-out ./snap`** 取当前值,**读取主币种金额**:组侧读 `maxCPCAmountYuan` / `targetCpaAmountYuan`(元);系列列表侧 `ad campaigns` 的 `budget` 也是元(与写参 `--budget` 一致);`google-analysis campaigns-*.json` 的 `budgetAmountYuan` 同。在宿主内按主币种算新值(如下调 12%:`newYuan = round(oldYuan * 0.88 * 100) / 100`),再以主币种金额作为 `--target-cpa` / `--max-cpc` / `--budget` 传回。
|
|
128
|
+
写前**必须**先 **`ad groups --json-out ./snap` / `ad campaigns --json-out ./snap`** 取当前值,**读取主币种金额**:组侧读 `maxCPCAmountYuan` / `targetCpaAmountYuan`(元);系列列表侧 `ad campaigns` 的 `budget` 也是元(与写参 `--budget` 一致);`google-analysis campaigns-*.json` 的 `items[]` 行 `budgetAmountYuan` 同。在宿主内按主币种算新值(如下调 12%:`newYuan = round(oldYuan * 0.88 * 100) / 100`),再以主币种金额作为 `--target-cpa` / `--max-cpc` / `--budget` 传回。
|
|
129
129
|
**严禁** 自己再做 `÷100` / `÷1_000_000` 换算——`*Yuan` 字段已经是元,再换算就是错的。
|
|
130
130
|
|
|
131
131
|
### 写后复核
|
|
@@ -89,7 +89,7 @@ for month in <S月, S+1月, E月>:
|
|
|
89
89
|
**说明**:
|
|
90
90
|
|
|
91
91
|
- **禁止**用 `daily-metrics` 填 Sheet 4 上区:该维度走 TSO 主平台 `account-daily-reports`,与 Google 前台 / Web 分析(`CampaignSectionData`)**不同数据源**,产品已确认存在不可接受偏差。
|
|
92
|
-
- **Sheet 4 上区(账户月汇总)**:读 `./<YYYY-MM>/campaigns-*.json`,对 `
|
|
92
|
+
- **Sheet 4 上区(账户月汇总)**:读 `./<YYYY-MM>/campaigns-*.json`,对 `items[]`(`schemaVersion 3` 行统一在 `items`)**全系列求和**得到当月账户级点击/展示/费用/转化,再算 CTR/CPC/CPA/CVR(公式见下文 Sheet 4);与 Google「广告系列」页同区间加总口径一致。
|
|
93
93
|
- `./snap-inquiry/campaigns-<accountId>_<S>-<E>.json`(步骤 2,整段 S~E):仅用于 **Sheet 5** 系列明细;**不得**用它按月拆行填 Sheet 4。
|
|
94
94
|
- `geographic`:账户级国家聚合;Sheet 6 与 Sheet 4 **下区**(重点国切片)按月目录读取。
|
|
95
95
|
- `keywords` / `search-terms`:Sheet 7 / 8;默认拉取**全部有消耗**行(`keywords` 网关 `costGreater=0`,`limit=0` 不封顶);需 TOP N 时加 `--limit <n>`。
|
|
@@ -186,7 +186,7 @@ Sheet 5 整段系列明细读:`./snap-inquiry/campaigns-<accountId>_<S>-<E>.js
|
|
|
186
186
|
### 数值格式(强约束)
|
|
187
187
|
|
|
188
188
|
- **金额、CPL / CPA / CPC / 平均费用**:保留 **2 位小数**,写明货币代码(如 `¥123.45 CNY`)。
|
|
189
|
-
- **点击率 / 转化率 / 互动率**(`ctr` / `conversionRate` / `interactionRate`):CLI 已归一为 **0~1 小数**(schemaVersion
|
|
189
|
+
- **点击率 / 转化率 / 互动率**(`ctr` / `conversionRate` / `interactionRate`):CLI 已归一为 **0~1 小数**(schemaVersion ≥ 2),写入 Excel 时**直接写 0~1**(与运营样表对齐,Excel 单元格用百分比格式自动渲染)。话术 `x%` 用 `(v * 100).toFixed(2) + "%"`。
|
|
190
190
|
- **空值**:`interactions === 0` 时「平均费用」「互动率」填 `—`,**禁止**除零。
|
|
191
191
|
|
|
192
192
|
### 分析输出区位置(**全局硬约束**)
|
|
@@ -317,14 +317,14 @@ Sheet 5 整段系列明细读:`./snap-inquiry/campaigns-<accountId>_<S>-<E>.js
|
|
|
317
317
|
| 项 | 约定 |
|
|
318
318
|
| --- | --- |
|
|
319
319
|
| 数据源 | `./snap-inquiry/<YYYY-MM>/campaigns-<accountId>_<月1号>-<月末>.json`(每月单独拉 `campaigns`;见「落盘防覆盖」) |
|
|
320
|
-
| 聚合范围 | 该月内 **全部** `
|
|
320
|
+
| 聚合范围 | 该月内 **全部** `items[]` 行(`schemaVersion 3` 行统一在 `items`;含已暂停但在区间内有消耗的系列;**禁止**只取 Top N 或手工筛系列) |
|
|
321
321
|
| 数据口径 | `google-analysis campaigns` 与账户月表一致;账户月表 = 各系列在当月起止日内指标之和 |
|
|
322
322
|
| 禁止 | `daily-metrics` / `stats` / `overview` 填上区投放列(询盘列仍来自 `inquiries.json`) |
|
|
323
323
|
|
|
324
324
|
**脚本聚合(每月一份 `campaigns-*.json`)**:
|
|
325
325
|
|
|
326
326
|
```text
|
|
327
|
-
rows = payload.
|
|
327
|
+
rows = payload.items ?? []
|
|
328
328
|
spend = Σ rows[].spend
|
|
329
329
|
clicks = Σ rows[].clicks
|
|
330
330
|
impressions = Σ rows[].impressions
|
|
@@ -339,7 +339,7 @@ CPL = 询盘个数 > 0 ? spend / 询盘个数 : —
|
|
|
339
339
|
|
|
340
340
|
`时间` 列写 `YYYY-MM` 或 `YYYY年M月`(与样表一致即可)。金额列用 `list-accounts` 的 `currencyCode`。
|
|
341
341
|
|
|
342
|
-
**可选校验(不写入 Excel,仅日志)**:同月再拉 `overview`,对比 `currentPeriod.spend`
|
|
342
|
+
**可选校验(不写入 Excel,仅日志)**:同月再拉 `overview`,对比 `record.currentPeriod.spend`(汇总维度在 `record`)与系列 `spend` 之和;偏差 >1% 时在 Agent 交付说明中脚注差异,**仍以系列加总为准**。
|
|
343
343
|
|
|
344
344
|
**上区**(B→L,跳过 A 列保持与运营样表一致)3 行月汇总:
|
|
345
345
|
|
|
@@ -347,7 +347,7 @@ CPL = 询盘个数 > 0 ? spend / 询盘个数 : —
|
|
|
347
347
|
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
348
348
|
| R1 | (A 列空)`汇总` |
|
|
349
349
|
| R2 | `时间` \| `点击次数` \| `展示次数` \| `点击率` \| `平均每次点击费用` \| `费用` \| `转化次数` \| `每次转化费用` \| `转化率` \| `询盘个数` \| `CPL` |
|
|
350
|
-
| R3..R5 | 倒序写 3 个月(**E 月在上**,S 月在下):脚本读 `./<YYYY-MM>/campaigns-*.json` 按上表对 `
|
|
350
|
+
| R3..R5 | 倒序写 3 个月(**E 月在上**,S 月在下):脚本读 `./<YYYY-MM>/campaigns-*.json` 按上表对 `items[]` 全量求和;`询盘个数` = `inquiries.json` 按月 count;`CPL` = `费用 / 询盘个数`(询盘 0 填 `—`) |
|
|
351
351
|
|
|
352
352
|
**下区**(B→G)3 月 × **重点国家 / 非重点国** 切片(共 6 行):
|
|
353
353
|
|
|
@@ -381,7 +381,7 @@ CPL = 询盘个数 > 0 ? spend / 询盘个数 : —
|
|
|
381
381
|
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
382
382
|
| R1 | `广告系列表现明细 · 账户 <mediaCustomerId(带连字符)> · <数据开始月>-<数据结束月>` |
|
|
383
383
|
| R2(A→M,13 列) | `系列名称` \| `系列ID` \| `策略` \| `开始日期` \| `消耗 (¥)` \| `展示` \| `点击` \| `CTR` \| `平均CPC` \| `转化` \| `CPA (¥)` \| `转化率` \| `消耗占比` |
|
|
384
|
-
| R3… | 读 `campaigns-*.json`:`campaignName` / `campaignId` / `biddingStrategyTypeV2`(中文化:`MAXIMIZE_CONVERSIONS` → `Max转化`、`TARGET_SPEND` → `目标支出`、`MANUAL_CPC` → `手动CPC` 等;未命中保留原文)/ `startDate` / `spend` / `impressions` / `clicks` / `ctr` / `averageCpc` / `conversions` / `costPerConversion` / `conversionRate` / **`消耗占比` = `spend` / `sum(spend)`**(脚本算,写 0~1 小数) |
|
|
384
|
+
| R3… | 读 `campaigns-*.json` 的 `items[]`:`campaignName` / `campaignId` / `biddingStrategyTypeV2`(中文化:`MAXIMIZE_CONVERSIONS` → `Max转化`、`TARGET_SPEND` → `目标支出`、`MANUAL_CPC` → `手动CPC` 等;未命中保留原文)/ `startDate` / `spend` / `impressions` / `clicks` / `ctr` / `averageCpc` / `conversions` / `costPerConversion` / `conversionRate` / **`消耗占比` = `spend` / `sum(spend)`**(脚本算,写 0~1 小数) |
|
|
385
385
|
| 末行 | `合计`:A 列写 `合计`;金额、展示、点击、转化 求和;CTR/CVR/CPA 按合计反推(**禁止**对各行比率求平均) |
|
|
386
386
|
|
|
387
387
|
**下区**:
|
|
@@ -427,7 +427,7 @@ CPL = 询盘个数 > 0 ? spend / 询盘个数 : —
|
|
|
427
427
|
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
428
428
|
| R1 | `关键词表现 · 账户 <id> · <分析月份>` |
|
|
429
429
|
| R2(A→J,10 列) | `关键词` \| `匹配方式` \| `系列` \| `消耗 (¥)` \| `展示` \| `点击` \| `CTR` \| `平均CPC` \| `转化` \| `CPA (¥)` |
|
|
430
|
-
| R3… | 读 `keywords-*.json
|
|
430
|
+
| R3… | 读 `keywords-*.json` 的 `items[]`:`keyword` / `keywordMatchTypeZh`(中文匹配方式:`Broad` / `Phrase` / `Exact`)/ `campaignName` / `spend` / `impressions` / `clicks` / `ctr` / `averageCpc` / `conversions` / `costPerConversion` |
|
|
431
431
|
|
|
432
432
|
按 `转化` 降序、`消耗` 降序双排序。
|
|
433
433
|
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
| 维度 | CLI | 备注 |
|
|
30
30
|
| ------------- | ----------------------------------------------- | ------------------------------------------------------------ |
|
|
31
|
-
| 系列按小时 | `google-analysis --sections campaign-hour` | `
|
|
31
|
+
| 系列按小时 | `google-analysis --sections campaign-hour` | 行在 `items[]`,含 `date`/`hour`/消耗与效果 |
|
|
32
32
|
| 受众分布 | `google-analysis --sections audience` | 可分 `SystemDefined` / `UserDefined` |
|
|
33
33
|
| 搜索词报告 | `google-analysis --sections search-terms` | 高消耗搜索词;`queryTargetingStatusZh` 列(已添加/已排除/都没有) |
|
|
34
34
|
| 广告创意表现 | `google-analysis --sections ads` | 广告标题/类型/到达网址 |
|
|
@@ -79,7 +79,7 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
|
|
|
79
79
|
|
|
80
80
|
### 点击率 / 转化率 / 互动率
|
|
81
81
|
|
|
82
|
-
> **2026-05 起 CLI 已统一归一**(manifest `schemaVersion
|
|
82
|
+
> **2026-05 起 CLI 已统一归一**(manifest `schemaVersion ≥ 2`):所有 `<section>-*.json` 中的 `ctr` / `conversionRate` 一律为 **0~1 小数**(如 `0.10` = 10.00%)。`schemaVersion 3` 起行数据统一在 `items[]`、汇总在 `record`。详见 `references/analytics/account-analytics.md`「指标字段对照」「落盘 JSON 统一信封」。
|
|
83
83
|
|
|
84
84
|
| 场景 | 处理 |
|
|
85
85
|
| ------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
@@ -116,7 +116,7 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
|
|
|
116
116
|
| R1 | `广告系列报告`(如运营改名,可改文案但 Sheet 名仍为 `账户报告`) |
|
|
117
117
|
| R2 | 统计区间 |
|
|
118
118
|
| R3(A→K,11 列) | `广告系列` \| `预算` \| `费用` \| `展示次数` \| `点击次数` \| `点击率` \| `平均每次点击费用` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
|
|
119
|
-
| R4… | 来自 `campaigns-*.json
|
|
119
|
+
| R4… | 来自 `campaigns-*.json` 的 `items[]`(`schemaVersion 3` 行统一在 `items`):`campaignName`、`budgetAmountYuan`(元)、`spend`、`impressions`、`clicks`、`ctr`(已归一直接写入「点击率」)、`averageCpc`、`allConversions`→「所有转化次数」、`conversions`→「转化次数」、`costPerConversion`、`conversionRate`(已归一直接写入「转化率」);缺 `allConversions` 时与「转化次数」同值或填 `0` / `—`,并在脚注说明 |
|
|
120
120
|
| 末行「合计」 | **广告系列**列填 `总计`;**预算**列填 `--`;**展示 / 点击 / 所有转化次数 / 转化次数 / 费用** 做列求和;**点击率** = 合计点击 ÷ 合计展示;**转化率** = 合计转化 ÷ 合计点击(**禁止**用合计转化 ÷ 合计展示);**每次转化费用** = 合计费用 ÷ 合计转化(转化为 0 时填 `—`);禁止对各行比率取算术平均 |
|
|
121
121
|
|
|
122
122
|
表下**留白若干行**后写 **「数据复盘」**:
|
|
@@ -183,7 +183,7 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
|
|
|
183
183
|
| R1 | `地理位置报告` |
|
|
184
184
|
| R2 | 统计区间 |
|
|
185
185
|
| R3(A→M,13 列) | `地理位置` \| `广告系列` \| `展示次数` \| `互动次数` \| `互动率` \| `费用` \| `平均费用` \| `点击次数` \| `点击率` \| `所有转化次数` \| `转化次数` \| `每次转化费用` \| `转化率` |
|
|
186
|
-
| R4… | `campaign-geo-matched-*.json
|
|
186
|
+
| 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 映射 |
|
|
187
187
|
|
|
188
188
|
---
|
|
189
189
|
|
|
@@ -207,7 +207,7 @@ siluzan-tso google-analysis -a <mediaCustomerId> --start <S> --end <E> --json-ou
|
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
- **第 1 条「平均每天」**:`区间总消耗 / 区间日历天数`(含起止日),脚本计算,禁止手填。
|
|
210
|
-
- **第 3 条**:CLI 返回的若是账户整体 CTR,写「整体点击率」而非「搜索点击率」;可区分时再写「搜索点击率」。`overview-*.json
|
|
210
|
+
- **第 3 条**:CLI 返回的若是账户整体 CTR,写「整体点击率」而非「搜索点击率」;可区分时再写「搜索点击率」。`overview-*.json`(汇总维度,`schemaVersion 3` 起整块在 `record`)的 `record.ctr` 已是 0~1 小数(如 `0.10`),话术 `x%` 用 `(ctr * 100).toFixed(2) + "%"`,**禁止**直接打印数值得到 "0.10%"。
|
|
211
211
|
- **所有金额、百分比** 一律 **2 位小数**。
|
|
212
212
|
|
|
213
213
|
---
|
|
@@ -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.25-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.25-beta.2'
|
|
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.25-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.25-beta.2"
|
|
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"
|