siluzan-tso-cli 1.1.35-beta.3 → 1.1.35-beta.5
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 +198 -64
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/analytics/account-analytics.md +7 -0
- package/dist/skill/references/report-templates/google-ads-diagnosis.md +12 -3
- package/dist/skill/references/report-templates/google-period-report.md +13 -0
- package/dist/skill/report-templates/google-ads-diagnosis.md +12 -3
- package/dist/skill/report-templates/google-period-report.md +13 -0
- 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.35-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.35-beta.5),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -5471,7 +5471,8 @@ var init_google_analysis = __esm({
|
|
|
5471
5471
|
};
|
|
5472
5472
|
GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS = [
|
|
5473
5473
|
"// \u91D1\u989D\uFF1A`campaigns[].budgetAmountYuan` / `campaignTargetCpaYuan` / `maximizeConversionsTargetCpaYuan` \u5747\u4E3A**\u5143**\uFF08CLI \u51FA\u53E3\u7EDF\u4E00\uFF09\uFF1B\u540C\u884C `spend` / `averageCpc` / `costPerConversion` \u4E5F\u662F\u5143\u3002",
|
|
5474
|
-
"// \u65E7\u5B57\u6BB5 `budgetAmount`\uFF08\u5206\uFF09/ `*Micros`\uFF08\u5FAE\u5143\uFF09\u5DF2\u4E0D\u518D\u843D\u76D8\uFF0C**\u7981\u6B62**\u518D\u505A \xF7100 / \xF71_000_000 \u6362\u7B97\u3002"
|
|
5474
|
+
"// \u65E7\u5B57\u6BB5 `budgetAmount`\uFF08\u5206\uFF09/ `*Micros`\uFF08\u5FAE\u5143\uFF09\u5DF2\u4E0D\u518D\u843D\u76D8\uFF0C**\u7981\u6B62**\u518D\u505A \xF7100 / \xF71_000_000 \u6362\u7B97\u3002",
|
|
5475
|
+
"// \u72B6\u6001\uFF1A\u6BCF\u884C\u542B `campaignStatusDisplay`\uFF08\u6295\u653E\u4E2D/\u5DF2\u6682\u505C/\u5DF2\u79FB\u9664\uFF09\uFF1B\u62A5\u544A\u72B6\u6001\u5217**\u76F4\u63A5\u8BFB\u6B64\u5B57\u6BB5**\uFF0C\u7981\u6B62\u5BF9 `campaignStatus` \u5199 `MAP[s]||'\u5DF2\u79FB\u9664'`\u3002"
|
|
5475
5476
|
];
|
|
5476
5477
|
GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS = [
|
|
5477
5478
|
"// \u91D1\u989D\uFF1A`ads[].spend` / `averageCpc` / `costPerConversion` \u843D\u76D8\u524D\u5DF2\u7EDF\u4E00\u4E3A**\u5143**\uFF08`costPerConversion` \u7531\u7F51\u5173\u5FAE\u5143 \xF71e6\uFF09\uFF1B**\u7981\u6B62**\u518D\u5BF9 `costPerConversion` \u505A \xF71_000_000\u3002"
|
|
@@ -101445,6 +101446,38 @@ var init_city_to_country = __esm({
|
|
|
101445
101446
|
}
|
|
101446
101447
|
});
|
|
101447
101448
|
|
|
101449
|
+
// src/commands/google-analysis/format-campaign-status.ts
|
|
101450
|
+
function formatGoogleCampaignStatusDisplay(status) {
|
|
101451
|
+
if (status == null) return "\u2014";
|
|
101452
|
+
const raw = String(status).trim();
|
|
101453
|
+
if (raw === "") return "\u2014";
|
|
101454
|
+
switch (raw.toUpperCase()) {
|
|
101455
|
+
case "ENABLED":
|
|
101456
|
+
return "\u6295\u653E\u4E2D";
|
|
101457
|
+
case "PAUSED":
|
|
101458
|
+
return "\u5DF2\u6682\u505C";
|
|
101459
|
+
case "REMOVED":
|
|
101460
|
+
return "\u5DF2\u79FB\u9664";
|
|
101461
|
+
default:
|
|
101462
|
+
return "\u672A\u77E5";
|
|
101463
|
+
}
|
|
101464
|
+
}
|
|
101465
|
+
function readGoogleCampaignStatusRaw(row) {
|
|
101466
|
+
if (row.campaignStatusV2 !== void 0 && row.campaignStatusV2 !== null) {
|
|
101467
|
+
return row.campaignStatusV2;
|
|
101468
|
+
}
|
|
101469
|
+
return row.campaignStatus;
|
|
101470
|
+
}
|
|
101471
|
+
function annotateCampaignStatusDisplay(row) {
|
|
101472
|
+
if (row.campaignStatusDisplay !== void 0) return;
|
|
101473
|
+
row.campaignStatusDisplay = formatGoogleCampaignStatusDisplay(readGoogleCampaignStatusRaw(row));
|
|
101474
|
+
}
|
|
101475
|
+
var init_format_campaign_status = __esm({
|
|
101476
|
+
"src/commands/google-analysis/format-campaign-status.ts"() {
|
|
101477
|
+
"use strict";
|
|
101478
|
+
}
|
|
101479
|
+
});
|
|
101480
|
+
|
|
101448
101481
|
// src/commands/google-analysis/translate-fields.ts
|
|
101449
101482
|
function lookupZh(dict, key) {
|
|
101450
101483
|
if (typeof key !== "string" || key === "") return void 0;
|
|
@@ -101463,6 +101496,14 @@ function annotateArrayRows(rows, source, target, dict) {
|
|
|
101463
101496
|
}
|
|
101464
101497
|
}
|
|
101465
101498
|
}
|
|
101499
|
+
function annotateCampaignStatusDisplayRows(rows) {
|
|
101500
|
+
if (!Array.isArray(rows)) return;
|
|
101501
|
+
for (const r of rows) {
|
|
101502
|
+
if (r && typeof r === "object" && !Array.isArray(r)) {
|
|
101503
|
+
annotateCampaignStatusDisplay(r);
|
|
101504
|
+
}
|
|
101505
|
+
}
|
|
101506
|
+
}
|
|
101466
101507
|
function annotateCountryRollup(rows) {
|
|
101467
101508
|
if (!Array.isArray(rows)) return;
|
|
101468
101509
|
for (const r of rows) {
|
|
@@ -101483,9 +101524,15 @@ function annotateCountryRollup(rows) {
|
|
|
101483
101524
|
function annotateZhFields(payload, section) {
|
|
101484
101525
|
if (payload === null || typeof payload !== "object") return payload;
|
|
101485
101526
|
switch (section) {
|
|
101527
|
+
case "campaigns": {
|
|
101528
|
+
const rows = payload.campaigns;
|
|
101529
|
+
annotateCampaignStatusDisplayRows(rows);
|
|
101530
|
+
break;
|
|
101531
|
+
}
|
|
101486
101532
|
case "keywords": {
|
|
101487
101533
|
const rows = payload.keywords;
|
|
101488
101534
|
annotateArrayRows(rows, "keywordMatchType", "keywordMatchTypeZh", MATCH_TYPE_DICT);
|
|
101535
|
+
annotateCampaignStatusDisplayRows(rows);
|
|
101489
101536
|
break;
|
|
101490
101537
|
}
|
|
101491
101538
|
case "search-terms": {
|
|
@@ -101520,6 +101567,7 @@ var init_translate_fields = __esm({
|
|
|
101520
101567
|
init_match_type_en2zh();
|
|
101521
101568
|
init_geo_en2zh();
|
|
101522
101569
|
init_city_to_country();
|
|
101570
|
+
init_format_campaign_status();
|
|
101523
101571
|
MATCH_TYPE_DICT = match_type_en2zh_default;
|
|
101524
101572
|
GEO_DICT = geo_en2zh_default;
|
|
101525
101573
|
CITY_DICT = city_to_country_default.byName ?? {};
|
|
@@ -105346,12 +105394,12 @@ init_cli_json_snapshot();
|
|
|
105346
105394
|
|
|
105347
105395
|
// src/utils/list-json-envelope.ts
|
|
105348
105396
|
function wrapListJson(params) {
|
|
105349
|
-
const
|
|
105397
|
+
const itemCount2 = Array.isArray(params.items) ? params.items.length : 0;
|
|
105350
105398
|
return {
|
|
105351
105399
|
page: params.page,
|
|
105352
105400
|
pageSize: params.pageSize,
|
|
105353
105401
|
total: params.total === void 0 || params.total === null ? null : params.total,
|
|
105354
|
-
itemCount,
|
|
105402
|
+
itemCount: itemCount2,
|
|
105355
105403
|
items: params.items
|
|
105356
105404
|
};
|
|
105357
105405
|
}
|
|
@@ -124534,7 +124582,7 @@ function buildGoogleAdsDiagnosisCollectAgentHint(snapDir) {
|
|
|
124534
124582
|
"Google Ads \u8BCA\u65AD collect \u5DF2\u5B8C\u6210\uFF08\u4EC5\u4E8B\u5B9E\u6570\u636E\uFF0C\u4E0D\u542B\u5EFA\u8BAE\u6027\u6587\u6848\uFF09\u3002",
|
|
124535
124583
|
`1) Read ${dir}/${GOOGLE_ADS_DIAGNOSIS_COLLECT_BASENAME}\uFF08reportData + agentBrief\uFF09\uFF1B`,
|
|
124536
124584
|
"2) Read references/report-templates/google-ads-diagnosis.md\uFF0C\u7531 Agent \u64B0\u5199\u5168\u90E8 analysis/suggestions\u3001diagnosisOverview\u3001summary\u3001accountInfo.businessModel/industry\u3001budgetCompetitiveness[].strategy\u3001newFeatures[].optimizerRecommendation\u3001adCreativeOptimization.items[].suggestion \u7B49\uFF1B",
|
|
124537
|
-
`3) \u5C06 reportData \u4E0E\u53D9\u4E8B\u5B57\u6BB5\u5408\u5E76\u5199\u5165 ${dir}/${GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME}\uFF08**\u7981\u6B62\
|
|
124585
|
+
`3) \u5C06 reportData \u4E0E\u53D9\u4E8B\u5B57\u6BB5\u5408\u5E76\u5199\u5165 ${dir}/${GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME}\uFF08**\u7981\u6B62\u5199\u5165/\u8986\u76D6** campaigns/geographic/keywords \u7684 items \u884C\uFF0C\u53EA\u586B analysis/suggestions\uFF1Bitems \u4EE5 collect \u4E3A\u51C6\uFF0Crender \u4F1A\u81EA\u52A8\u8986\u76D6 Agent \u6539\u52A8\u7684\u5BF9\u6BD4\u8868\uFF09\uFF1B`,
|
|
124538
124586
|
`4) siluzan-tso google-ads-diagnosis render --data ${dir}/${GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME} --out ${dir}/google-ads-diagnosis-report.html\uFF08\u9ED8\u8BA4\u540C\u76EE\u5F55 collect \u81EA\u52A8\u6062\u590D\u88AB\u6539\u574F\u7684 items\uFF09\u3002`,
|
|
124539
124587
|
"\u7981\u6B62\u5728\u672A\u7ECF\u8FC7 Agent \u64B0\u5199\u7684\u60C5\u51B5\u4E0B\u76F4\u63A5 render collect \u4EA7\u7269\uFF1B\u7F3A\u4EFB\u4E00\u5EFA\u8BAE\u5B57\u6BB5 render \u4F1A\u5931\u8D25\u3002"
|
|
124540
124588
|
].join("\n");
|
|
@@ -124587,6 +124635,20 @@ function buildGoogleAdsDiagnosisAgentBrief(input) {
|
|
|
124587
124635
|
campaigns: input.campaignHints,
|
|
124588
124636
|
keywords: input.keywordHints
|
|
124589
124637
|
},
|
|
124638
|
+
comparisonTableItemCounts: input.comparisonTableItemCounts,
|
|
124639
|
+
agentRules: {
|
|
124640
|
+
doNotModifyItemRows: [
|
|
124641
|
+
"campaigns.items",
|
|
124642
|
+
"geographic.items",
|
|
124643
|
+
"keywords.items"
|
|
124644
|
+
],
|
|
124645
|
+
renderRestoresComparisonItemsFromCollect: "render \u9ED8\u8BA4\u540C\u76EE\u5F55 google-ads-diagnosis-collect.json \u4F1A\u4EE5 collect \u8986\u76D6\u4E0A\u8FF0 items\uFF08Agent \u53EA\u586B analysis/suggestions\uFF09",
|
|
124646
|
+
mayEditItemFields: [
|
|
124647
|
+
"biddingStrategy.items[].recommendationReason",
|
|
124648
|
+
"newFeatures.items[].optimizerRecommendation",
|
|
124649
|
+
"adCreativeOptimization.items[].suggestion"
|
|
124650
|
+
]
|
|
124651
|
+
},
|
|
124590
124652
|
agentDeliverable: {
|
|
124591
124653
|
outputFile: "google-ads-diagnosis.json",
|
|
124592
124654
|
structure: "\u4E0E reportData \u540C\u7ED3\u6784\u7684 sectionData\uFF1B\u987B\u586B diagnosisOverview\u3001metrics.analysis/suggestions\u3001\u5404\u6A21\u5757 analysis/suggestions\u3001summary.keyIssues\u3001summary.optimizationRoadmap[]\uFF08priority/focusArea/actionItems/expectedOutcome\uFF09\u3001budgetCompetitiveness[].strategy\u3001newFeatures[].optimizerRecommendation \u7B49",
|
|
@@ -125380,11 +125442,58 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
|
|
|
125380
125442
|
profile.industry,
|
|
125381
125443
|
...campaignCurrent.map((r) => String(r.campaignName ?? ""))
|
|
125382
125444
|
].filter(Boolean),
|
|
125383
|
-
keywordHints: zeroConvKw.slice(0, 5).map((r) => String(r.keyword ?? ""))
|
|
125445
|
+
keywordHints: zeroConvKw.slice(0, 5).map((r) => String(r.keyword ?? "")),
|
|
125446
|
+
comparisonTableItemCounts: {
|
|
125447
|
+
campaigns: campaignItems.length,
|
|
125448
|
+
geographic: geoItems.length,
|
|
125449
|
+
keywords: kwItems.length
|
|
125450
|
+
}
|
|
125384
125451
|
});
|
|
125385
125452
|
return { reportData: data, agentBrief };
|
|
125386
125453
|
}
|
|
125387
125454
|
|
|
125455
|
+
// src/commands/google-ads-diagnosis/collect-integrity.ts
|
|
125456
|
+
function asRecord5(value) {
|
|
125457
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
125458
|
+
return value;
|
|
125459
|
+
}
|
|
125460
|
+
return null;
|
|
125461
|
+
}
|
|
125462
|
+
function itemCount(block) {
|
|
125463
|
+
const items = asRecord5(block)?.items;
|
|
125464
|
+
return Array.isArray(items) ? items.length : 0;
|
|
125465
|
+
}
|
|
125466
|
+
function getCollectIntegrityWarnings(reportData) {
|
|
125467
|
+
const warnings = [];
|
|
125468
|
+
const kwItems = itemCount(reportData.keywords);
|
|
125469
|
+
const fullKwItems = itemCount(reportData.fullKeywords);
|
|
125470
|
+
const keywordCount = Number(asRecord5(reportData.structure)?.keywordCount ?? 0);
|
|
125471
|
+
if (kwItems === 0 && fullKwItems > 0) {
|
|
125472
|
+
warnings.push(
|
|
125473
|
+
`keywords.items \u5BF9\u6BD4\u8868\u4E3A\u7A7A\uFF0C\u4F46 fullKeywords.items \u6709 ${fullKwItems} \u884C\uFF1B\u8BF7\u786E\u8BA4 collect \u662F\u5426\u6210\u529F\u62C9\u53D6\u4E0A\u4E00\u5468\u671F keywords \u5FEB\u7167\uFF08\u73AF\u6BD4\u5217\u4F9D\u8D56 prev \u5468\u671F JSON\uFF09\u3002`
|
|
125474
|
+
);
|
|
125475
|
+
} else if (kwItems === 0 && keywordCount > 0) {
|
|
125476
|
+
warnings.push(
|
|
125477
|
+
`keywords.items \u5BF9\u6BD4\u8868\u4E3A\u7A7A\uFF0C\u4F46 structure.keywordCount=${keywordCount}\uFF1B\u8BF7\u786E\u8BA4\u4E0A\u4E00\u5468\u671F keywords \u5FEB\u7167\u662F\u5426\u62C9\u53D6\u6210\u529F\u3002`
|
|
125478
|
+
);
|
|
125479
|
+
}
|
|
125480
|
+
const campaignItems = itemCount(reportData.campaigns);
|
|
125481
|
+
const campaignCount = Number(asRecord5(reportData.structure)?.campaignCount ?? 0);
|
|
125482
|
+
if (campaignItems === 0 && campaignCount > 0) {
|
|
125483
|
+
warnings.push(
|
|
125484
|
+
`campaigns.items \u5BF9\u6BD4\u8868\u4E3A\u7A7A\uFF0C\u4F46 structure.campaignCount=${campaignCount}\uFF1B\u8BF7\u786E\u8BA4\u4E0A\u4E00\u5468\u671F campaigns \u5FEB\u7167\u662F\u5426\u62C9\u53D6\u6210\u529F\u3002`
|
|
125485
|
+
);
|
|
125486
|
+
}
|
|
125487
|
+
const geoItems = itemCount(reportData.geographic);
|
|
125488
|
+
const fullGeoItems = itemCount(reportData.fullGeographic);
|
|
125489
|
+
if (geoItems === 0 && fullGeoItems > 0) {
|
|
125490
|
+
warnings.push(
|
|
125491
|
+
`geographic.items \u5BF9\u6BD4\u8868\u4E3A\u7A7A\uFF0C\u4F46 fullGeographic.items \u6709 ${fullGeoItems} \u884C\uFF1B\u8BF7\u786E\u8BA4\u4E0A\u4E00\u5468\u671F geographic \u5FEB\u7167\u662F\u5426\u62C9\u53D6\u6210\u529F\u3002`
|
|
125492
|
+
);
|
|
125493
|
+
}
|
|
125494
|
+
return warnings;
|
|
125495
|
+
}
|
|
125496
|
+
|
|
125388
125497
|
// src/commands/google-ads-diagnosis/invoke-google-analysis.ts
|
|
125389
125498
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
125390
125499
|
function invokeGoogleAnalysisCli(opts) {
|
|
@@ -125527,6 +125636,12 @@ async function runGoogleAdsDiagnosisCollect(opts) {
|
|
|
125527
125636
|
website: websiteUrl || opts.website,
|
|
125528
125637
|
landingPage
|
|
125529
125638
|
});
|
|
125639
|
+
for (const warning of getCollectIntegrityWarnings(reportData)) {
|
|
125640
|
+
console.error(`
|
|
125641
|
+
\u26A0\uFE0F collect \u5B8C\u6574\u6027\uFF1A${warning}
|
|
125642
|
+
`);
|
|
125643
|
+
degraded = true;
|
|
125644
|
+
}
|
|
125530
125645
|
const collectPayload = {
|
|
125531
125646
|
kind: "siluzan-tso-google-ads-diagnosis-collect",
|
|
125532
125647
|
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -125567,7 +125682,7 @@ import fs18 from "fs/promises";
|
|
|
125567
125682
|
import path23 from "path";
|
|
125568
125683
|
|
|
125569
125684
|
// src/commands/google-ads-diagnosis/report-content.ts
|
|
125570
|
-
function
|
|
125685
|
+
function asRecord6(value) {
|
|
125571
125686
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
125572
125687
|
return value;
|
|
125573
125688
|
}
|
|
@@ -125576,7 +125691,7 @@ function asRecord5(value) {
|
|
|
125576
125691
|
function getNestedValue(root, path34) {
|
|
125577
125692
|
let cur = root;
|
|
125578
125693
|
for (const key of path34) {
|
|
125579
|
-
const rec =
|
|
125694
|
+
const rec = asRecord6(cur);
|
|
125580
125695
|
if (!rec) return void 0;
|
|
125581
125696
|
cur = rec[key];
|
|
125582
125697
|
}
|
|
@@ -125593,7 +125708,7 @@ function validateBudgetCompetitivenessStrategies(data) {
|
|
|
125593
125708
|
}
|
|
125594
125709
|
const warnings = [];
|
|
125595
125710
|
for (let i = 0; i < items.length; i++) {
|
|
125596
|
-
const row =
|
|
125711
|
+
const row = asRecord6(items[i]);
|
|
125597
125712
|
const name2 = String(row?.name ?? row?.metric ?? i);
|
|
125598
125713
|
if (!hasNarrativeContent(row?.strategy)) {
|
|
125599
125714
|
warnings.push(`budgetCompetitiveness[${name2}].strategy \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
|
|
@@ -125602,13 +125717,13 @@ function validateBudgetCompetitivenessStrategies(data) {
|
|
|
125602
125717
|
return warnings;
|
|
125603
125718
|
}
|
|
125604
125719
|
function validateNewFeatureRecommendations(data) {
|
|
125605
|
-
const nf =
|
|
125720
|
+
const nf = asRecord6(data.newFeatures);
|
|
125606
125721
|
if (!nf) return [];
|
|
125607
125722
|
const items = nf.items;
|
|
125608
125723
|
if (!Array.isArray(items)) return [];
|
|
125609
125724
|
const warnings = [];
|
|
125610
125725
|
for (let i = 0; i < items.length; i++) {
|
|
125611
|
-
const row =
|
|
125726
|
+
const row = asRecord6(items[i]);
|
|
125612
125727
|
const name2 = String(row?.strategyName ?? row?.feature ?? row?.strategy ?? i);
|
|
125613
125728
|
if (!hasNarrativeContent(row?.optimizerRecommendation)) {
|
|
125614
125729
|
warnings.push(`newFeatures.items[${name2}].optimizerRecommendation \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
|
|
@@ -125617,13 +125732,13 @@ function validateNewFeatureRecommendations(data) {
|
|
|
125617
125732
|
return warnings;
|
|
125618
125733
|
}
|
|
125619
125734
|
function validateAdCreativeItemSuggestions(data) {
|
|
125620
|
-
const creative =
|
|
125735
|
+
const creative = asRecord6(data.adCreativeOptimization);
|
|
125621
125736
|
if (!creative) return [];
|
|
125622
125737
|
const items = creative.items;
|
|
125623
125738
|
if (!Array.isArray(items) || items.length === 0) return [];
|
|
125624
125739
|
const warnings = [];
|
|
125625
125740
|
for (let i = 0; i < items.length; i++) {
|
|
125626
|
-
const row =
|
|
125741
|
+
const row = asRecord6(items[i]);
|
|
125627
125742
|
const title = String(row?.adTitle ?? i);
|
|
125628
125743
|
if (!hasNarrativeContent(row?.suggestion ?? row?.suggestions)) {
|
|
125629
125744
|
warnings.push(
|
|
@@ -125638,7 +125753,7 @@ function validateGoogleAdsDiagnosisContent(data) {
|
|
|
125638
125753
|
(key) => data[key] === void 0
|
|
125639
125754
|
);
|
|
125640
125755
|
const warnings = [];
|
|
125641
|
-
const accountInfo =
|
|
125756
|
+
const accountInfo = asRecord6(data.accountInfo);
|
|
125642
125757
|
if (accountInfo && !accountInfo.companyName && !accountInfo.period) {
|
|
125643
125758
|
warnings.push("accountInfo \u7F3A\u5C11 companyName \u4E0E period\uFF0C\u9875\u7709\u53EF\u80FD\u4E3A\u7A7A");
|
|
125644
125759
|
}
|
|
@@ -125686,7 +125801,7 @@ var GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES = [
|
|
|
125686
125801
|
GOOGLE_ADS_DIAGNOSIS_BIDDING_STRATEGY_MODULE,
|
|
125687
125802
|
GOOGLE_ADS_DIAGNOSIS_NEW_FEATURES_MODULE
|
|
125688
125803
|
];
|
|
125689
|
-
function
|
|
125804
|
+
function asRecord7(value) {
|
|
125690
125805
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
125691
125806
|
return value;
|
|
125692
125807
|
}
|
|
@@ -125697,7 +125812,7 @@ function isValidRateChange(value) {
|
|
|
125697
125812
|
return typeof value === "number" && Number.isFinite(value);
|
|
125698
125813
|
}
|
|
125699
125814
|
function isComparisonTableRowValid(row) {
|
|
125700
|
-
const rec =
|
|
125815
|
+
const rec = asRecord7(row);
|
|
125701
125816
|
if (!rec) return false;
|
|
125702
125817
|
if (!String(rec.title ?? "").trim()) return false;
|
|
125703
125818
|
if (!Number.isFinite(Number(rec.currentCost))) return false;
|
|
@@ -125709,12 +125824,12 @@ function isComparisonTableRowValid(row) {
|
|
|
125709
125824
|
return true;
|
|
125710
125825
|
}
|
|
125711
125826
|
function isNewFeatureRowValid(row) {
|
|
125712
|
-
const rec =
|
|
125827
|
+
const rec = asRecord7(row);
|
|
125713
125828
|
if (!rec) return false;
|
|
125714
125829
|
return Boolean(String(rec.strategyName ?? rec.feature ?? rec.strategy ?? "").trim());
|
|
125715
125830
|
}
|
|
125716
125831
|
function isBiddingStrategyRowValid(row) {
|
|
125717
|
-
const rec =
|
|
125832
|
+
const rec = asRecord7(row);
|
|
125718
125833
|
if (!rec) return false;
|
|
125719
125834
|
if (!String(rec.campaignName ?? "").trim()) return false;
|
|
125720
125835
|
if (!Number.isFinite(Number(rec.duration)) || Number(rec.duration) <= 0) return false;
|
|
@@ -125739,14 +125854,32 @@ function moduleItemsNeedRestore(items, validator) {
|
|
|
125739
125854
|
if (!Array.isArray(items) || items.length === 0) return false;
|
|
125740
125855
|
return items.some((row) => !validator(row));
|
|
125741
125856
|
}
|
|
125857
|
+
function isComparisonTableModule(mod) {
|
|
125858
|
+
return GOOGLE_ADS_DIAGNOSIS_COMPARISON_MODULES.includes(mod);
|
|
125859
|
+
}
|
|
125860
|
+
function comparisonItemsDifferFromCollect(finalItems, collectItems) {
|
|
125861
|
+
if (!Array.isArray(finalItems)) return true;
|
|
125862
|
+
if (finalItems.length !== collectItems.length) return true;
|
|
125863
|
+
return JSON.stringify(finalItems) !== JSON.stringify(collectItems);
|
|
125864
|
+
}
|
|
125742
125865
|
function getFactItemsErrors(data) {
|
|
125743
125866
|
const errors = [];
|
|
125744
125867
|
for (const mod of GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES) {
|
|
125745
125868
|
const validator = MODULE_ITEM_VALIDATORS[mod];
|
|
125746
125869
|
const hint = MODULE_ITEM_ERROR_HINT[mod];
|
|
125747
|
-
const block =
|
|
125870
|
+
const block = asRecord7(data[mod]);
|
|
125748
125871
|
const items = block?.items;
|
|
125749
|
-
if (!Array.isArray(items) || items.length === 0)
|
|
125872
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
125873
|
+
if (mod === "keywords") {
|
|
125874
|
+
const fullKw = asRecord7(data.fullKeywords)?.items;
|
|
125875
|
+
if (Array.isArray(fullKw) && fullKw.length > 0) {
|
|
125876
|
+
errors.push(
|
|
125877
|
+
"keywords.items \u4E3A\u7A7A\u4F46 fullKeywords.items \u6709\u6570\u636E\uFF1B\u8BF7\u4FDD\u7559\u540C\u76EE\u5F55 google-ads-diagnosis-collect.json \u540E\u91CD\u65B0 render\uFF08\u4F1A\u81EA\u52A8\u6062\u590D\u5BF9\u6BD4\u8868\uFF09"
|
|
125878
|
+
);
|
|
125879
|
+
}
|
|
125880
|
+
}
|
|
125881
|
+
continue;
|
|
125882
|
+
}
|
|
125750
125883
|
for (let i = 0; i < items.length; i++) {
|
|
125751
125884
|
if (!validator(items[i])) {
|
|
125752
125885
|
errors.push(`${mod}.items[${i}] ${hint}`);
|
|
@@ -125763,15 +125896,15 @@ var LANDING_PAGE_SPEED_KEYS = [
|
|
|
125763
125896
|
"speedIndex"
|
|
125764
125897
|
];
|
|
125765
125898
|
function isLandingPageSpeedValid(value) {
|
|
125766
|
-
const rec =
|
|
125899
|
+
const rec = asRecord7(value);
|
|
125767
125900
|
if (!rec) return false;
|
|
125768
125901
|
return LANDING_PAGE_SPEED_KEYS.every(
|
|
125769
125902
|
(key) => typeof rec[key] === "number" && Number.isFinite(rec[key])
|
|
125770
125903
|
);
|
|
125771
125904
|
}
|
|
125772
125905
|
function restoreLandingPageSpeedFromCollect(finalData, collectReportData) {
|
|
125773
|
-
const finalBlock =
|
|
125774
|
-
const collectBlock =
|
|
125906
|
+
const finalBlock = asRecord7(finalData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
|
|
125907
|
+
const collectBlock = asRecord7(collectReportData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
|
|
125775
125908
|
if (!collectBlock) return { data: finalData, restored: false };
|
|
125776
125909
|
const needsDesktop = !isLandingPageSpeedValid(finalBlock?.desktop) && isLandingPageSpeedValid(collectBlock.desktop);
|
|
125777
125910
|
const needsMobile = !isLandingPageSpeedValid(finalBlock?.mobile) && isLandingPageSpeedValid(collectBlock.mobile);
|
|
@@ -125789,12 +125922,13 @@ function mergeComparisonItemsFromCollect(finalData, collectReportData) {
|
|
|
125789
125922
|
const restoredModules = [];
|
|
125790
125923
|
for (const mod of GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES) {
|
|
125791
125924
|
const validator = MODULE_ITEM_VALIDATORS[mod];
|
|
125792
|
-
const finalBlock =
|
|
125793
|
-
const collectBlock =
|
|
125925
|
+
const finalBlock = asRecord7(data[mod]);
|
|
125926
|
+
const collectBlock = asRecord7(collectReportData[mod]);
|
|
125794
125927
|
const finalItems = finalBlock?.items;
|
|
125795
125928
|
const collectItems = collectBlock?.items;
|
|
125796
|
-
if (!moduleItemsNeedRestore(finalItems, validator)) continue;
|
|
125797
125929
|
if (!Array.isArray(collectItems) || collectItems.length === 0) continue;
|
|
125930
|
+
const needsRestore = isComparisonTableModule(mod) ? comparisonItemsDifferFromCollect(finalItems, collectItems) : moduleItemsNeedRestore(finalItems, validator);
|
|
125931
|
+
if (!needsRestore) continue;
|
|
125798
125932
|
data[mod] = {
|
|
125799
125933
|
...finalBlock ?? {},
|
|
125800
125934
|
items: collectItems
|
|
@@ -125805,7 +125939,7 @@ function mergeComparisonItemsFromCollect(finalData, collectReportData) {
|
|
|
125805
125939
|
}
|
|
125806
125940
|
|
|
125807
125941
|
// src/commands/google-ads-diagnosis/narrative-consistency-check.ts
|
|
125808
|
-
function
|
|
125942
|
+
function asRecord8(value) {
|
|
125809
125943
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
125810
125944
|
return value;
|
|
125811
125945
|
}
|
|
@@ -125813,14 +125947,14 @@ function asRecord7(value) {
|
|
|
125813
125947
|
}
|
|
125814
125948
|
function collectNarrativeTexts(data) {
|
|
125815
125949
|
const texts = [];
|
|
125816
|
-
const summary =
|
|
125950
|
+
const summary = asRecord8(data.summary);
|
|
125817
125951
|
if (Array.isArray(summary?.keyIssues)) {
|
|
125818
125952
|
for (const item of summary.keyIssues) texts.push(String(item ?? ""));
|
|
125819
125953
|
}
|
|
125820
|
-
const overview =
|
|
125954
|
+
const overview = asRecord8(data.diagnosisOverview);
|
|
125821
125955
|
if (Array.isArray(overview?.disadvantages)) {
|
|
125822
125956
|
for (const item of overview.disadvantages) {
|
|
125823
|
-
const rec =
|
|
125957
|
+
const rec = asRecord8(item);
|
|
125824
125958
|
if (rec) {
|
|
125825
125959
|
texts.push(String(rec.title ?? ""), String(rec.description ?? ""));
|
|
125826
125960
|
} else {
|
|
@@ -125831,16 +125965,16 @@ function collectNarrativeTexts(data) {
|
|
|
125831
125965
|
return texts.filter((t) => t.trim().length > 0);
|
|
125832
125966
|
}
|
|
125833
125967
|
function newFeatureAccountStatus(data, strategy) {
|
|
125834
|
-
const nf =
|
|
125968
|
+
const nf = asRecord8(data.newFeatures);
|
|
125835
125969
|
const items = nf?.items;
|
|
125836
125970
|
if (!Array.isArray(items)) return null;
|
|
125837
|
-
const row = items.find((it) =>
|
|
125838
|
-
const rec =
|
|
125971
|
+
const row = items.find((it) => asRecord8(it)?.strategy === strategy);
|
|
125972
|
+
const rec = asRecord8(row);
|
|
125839
125973
|
if (!rec || typeof rec.accountStatus !== "boolean") return null;
|
|
125840
125974
|
return rec.accountStatus;
|
|
125841
125975
|
}
|
|
125842
125976
|
function goldAccountFlag(data, key) {
|
|
125843
|
-
const gold =
|
|
125977
|
+
const gold = asRecord8(data.goldAccount);
|
|
125844
125978
|
const value = gold?.[key];
|
|
125845
125979
|
return typeof value === "boolean" ? value : null;
|
|
125846
125980
|
}
|
|
@@ -125871,7 +126005,7 @@ function getNarrativeConsistencyErrors(data) {
|
|
|
125871
126005
|
}
|
|
125872
126006
|
|
|
125873
126007
|
// src/commands/google-ads-diagnosis/render-report.ts
|
|
125874
|
-
function
|
|
126008
|
+
function asRecord9(value) {
|
|
125875
126009
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
125876
126010
|
return value;
|
|
125877
126011
|
}
|
|
@@ -125880,8 +126014,8 @@ function asRecord8(value) {
|
|
|
125880
126014
|
async function tryLoadCollectReportData(collectPath) {
|
|
125881
126015
|
try {
|
|
125882
126016
|
const raw = JSON.parse(await fs18.readFile(collectPath, "utf8"));
|
|
125883
|
-
const root =
|
|
125884
|
-
const reportData =
|
|
126017
|
+
const root = asRecord9(raw);
|
|
126018
|
+
const reportData = asRecord9(root?.reportData);
|
|
125885
126019
|
return reportData ?? root;
|
|
125886
126020
|
} catch {
|
|
125887
126021
|
return null;
|
|
@@ -125898,7 +126032,7 @@ async function runGoogleAdsDiagnosisRender(opts) {
|
|
|
125898
126032
|
`);
|
|
125899
126033
|
process.exit(1);
|
|
125900
126034
|
}
|
|
125901
|
-
let data =
|
|
126035
|
+
let data = asRecord9(dataRaw);
|
|
125902
126036
|
if (!opts.noMergeCollect) {
|
|
125903
126037
|
const collectPath = path23.resolve(
|
|
125904
126038
|
opts.collectFile ?? path23.join(path23.dirname(dataPath), GOOGLE_ADS_DIAGNOSIS_COLLECT_BASENAME)
|
|
@@ -126411,7 +126545,7 @@ function ensureScorecardFromSnapshot(healthDiagnosis, merged) {
|
|
|
126411
126545
|
}
|
|
126412
126546
|
|
|
126413
126547
|
// src/commands/facebook-analysis/merge-snapshot.ts
|
|
126414
|
-
function
|
|
126548
|
+
function asRecord10(value) {
|
|
126415
126549
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
126416
126550
|
return value;
|
|
126417
126551
|
}
|
|
@@ -126460,7 +126594,7 @@ async function readManifest(snapshotDir) {
|
|
|
126460
126594
|
async function loadSectionFile(snapshotDir, file) {
|
|
126461
126595
|
try {
|
|
126462
126596
|
const raw = await fs21.readFile(path28.join(snapshotDir, file), "utf8");
|
|
126463
|
-
return
|
|
126597
|
+
return asRecord10(JSON.parse(raw));
|
|
126464
126598
|
} catch {
|
|
126465
126599
|
return null;
|
|
126466
126600
|
}
|
|
@@ -126508,7 +126642,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
126508
126642
|
if (data) sectionMap.set(art.section, data);
|
|
126509
126643
|
}
|
|
126510
126644
|
const overview = sectionMap.get("overview");
|
|
126511
|
-
const current =
|
|
126645
|
+
const current = asRecord10(overview?.currentPeriod);
|
|
126512
126646
|
const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
|
|
126513
126647
|
const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
|
|
126514
126648
|
const kpis = { ...payload.kpis ?? {} };
|
|
@@ -126630,7 +126764,7 @@ function isFiniteNumber(value) {
|
|
|
126630
126764
|
function asArray2(value) {
|
|
126631
126765
|
return Array.isArray(value) ? value : [];
|
|
126632
126766
|
}
|
|
126633
|
-
function
|
|
126767
|
+
function asRecord11(value) {
|
|
126634
126768
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
126635
126769
|
return value;
|
|
126636
126770
|
}
|
|
@@ -126642,26 +126776,26 @@ function pushMissing(missing, id, chapter, dimension, hint) {
|
|
|
126642
126776
|
function countAdSetsWithSpend(tables) {
|
|
126643
126777
|
const rows = asArray2(tables?.adSets);
|
|
126644
126778
|
return rows.filter((row) => {
|
|
126645
|
-
const r =
|
|
126779
|
+
const r = asRecord11(row);
|
|
126646
126780
|
const spend = r?.spend;
|
|
126647
126781
|
return isFiniteNumber(spend) && spend > 0;
|
|
126648
126782
|
}).length;
|
|
126649
126783
|
}
|
|
126650
126784
|
function chartLabels(chart) {
|
|
126651
|
-
const c =
|
|
126785
|
+
const c = asRecord11(chart);
|
|
126652
126786
|
return asArray2(c?.labels).length;
|
|
126653
126787
|
}
|
|
126654
126788
|
function validateMetaPeriodReportContent(data) {
|
|
126655
126789
|
const missing = [];
|
|
126656
|
-
const meta =
|
|
126657
|
-
const kpis =
|
|
126658
|
-
const narrative =
|
|
126659
|
-
const tables =
|
|
126660
|
-
const charts =
|
|
126661
|
-
const sections =
|
|
126662
|
-
const health =
|
|
126663
|
-
const priorityPlan =
|
|
126664
|
-
const actionChecklist =
|
|
126790
|
+
const meta = asRecord11(data.meta) ?? {};
|
|
126791
|
+
const kpis = asRecord11(data.kpis) ?? {};
|
|
126792
|
+
const narrative = asRecord11(data.narrative) ?? {};
|
|
126793
|
+
const tables = asRecord11(data.tables);
|
|
126794
|
+
const charts = asRecord11(data.charts);
|
|
126795
|
+
const sections = asRecord11(data.sections);
|
|
126796
|
+
const health = asRecord11(data.healthDiagnosis);
|
|
126797
|
+
const priorityPlan = asRecord11(data.priorityPlan);
|
|
126798
|
+
const actionChecklist = asRecord11(data.actionChecklist);
|
|
126665
126799
|
const adSetsWithSpend2 = countAdSetsWithSpend(tables ?? void 0);
|
|
126666
126800
|
const regionalNarratives = asArray2(narrative.regional).length;
|
|
126667
126801
|
const recommendations = asArray2(narrative.recommendations);
|
|
@@ -126733,7 +126867,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126733
126867
|
"charts.audience.labels \u81F3\u5C11 1 \u9879"
|
|
126734
126868
|
);
|
|
126735
126869
|
}
|
|
126736
|
-
const funnel =
|
|
126870
|
+
const funnel = asRecord11(charts?.funnel);
|
|
126737
126871
|
if (!isFiniteNumber(funnel?.reach)) {
|
|
126738
126872
|
pushMissing(
|
|
126739
126873
|
missing,
|
|
@@ -126797,7 +126931,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126797
126931
|
);
|
|
126798
126932
|
} else {
|
|
126799
126933
|
for (let i = 0; i < regionalNarratives; i++) {
|
|
126800
|
-
const row =
|
|
126934
|
+
const row = asRecord11(asArray2(narrative.regional)[i]);
|
|
126801
126935
|
if (textLen2(row?.text) < 80) {
|
|
126802
126936
|
pushMissing(
|
|
126803
126937
|
missing,
|
|
@@ -126827,7 +126961,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126827
126961
|
"\u5FC5\u987B\u6070\u597D 4 \u6761\uFF08\u7B80\u5316\u8868\u5355/\u533A\u57DF\u8C03\u6574/\u9884\u7B97\u91CD\u6784/\u7D20\u6750\u5EFA\u8BAE\uFF09"
|
|
126828
126962
|
);
|
|
126829
126963
|
} else {
|
|
126830
|
-
const titles = recommendations.map((r) =>
|
|
126964
|
+
const titles = recommendations.map((r) => asRecord11(r)?.title);
|
|
126831
126965
|
for (const title of RECOMMENDATION_TITLES) {
|
|
126832
126966
|
if (!titles.includes(title)) {
|
|
126833
126967
|
pushMissing(
|
|
@@ -126840,7 +126974,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126840
126974
|
}
|
|
126841
126975
|
}
|
|
126842
126976
|
for (let i = 0; i < recommendations.length; i++) {
|
|
126843
|
-
const rec =
|
|
126977
|
+
const rec = asRecord11(recommendations[i]);
|
|
126844
126978
|
if (textLen2(rec?.content) < 150) {
|
|
126845
126979
|
pushMissing(
|
|
126846
126980
|
missing,
|
|
@@ -126862,7 +126996,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126862
126996
|
);
|
|
126863
126997
|
} else {
|
|
126864
126998
|
for (let i = 0; i < supplementary.length; i++) {
|
|
126865
|
-
const row =
|
|
126999
|
+
const row = asRecord11(supplementary[i]);
|
|
126866
127000
|
if (!textLen2(row?.dimension) || !textLen2(row?.issue)) {
|
|
126867
127001
|
pushMissing(
|
|
126868
127002
|
missing,
|
|
@@ -126944,7 +127078,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126944
127078
|
);
|
|
126945
127079
|
} else {
|
|
126946
127080
|
for (let i = 0; i < fourQuestions.length; i++) {
|
|
126947
|
-
const q =
|
|
127081
|
+
const q = asRecord11(fourQuestions[i]);
|
|
126948
127082
|
const evidence = asArray2(q?.evidence).filter((e) => textLen2(e) > 0);
|
|
126949
127083
|
if (evidence.length < 2) {
|
|
126950
127084
|
pushMissing(
|
|
@@ -126968,7 +127102,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
126968
127102
|
}
|
|
126969
127103
|
let scorecardValidRows = 0;
|
|
126970
127104
|
for (let i = 0; i < scorecard.length; i++) {
|
|
126971
|
-
const row =
|
|
127105
|
+
const row = asRecord11(scorecard[i]);
|
|
126972
127106
|
const itemOk = textLen2(row?.item) > 0;
|
|
126973
127107
|
const dataOk = textLen2(row?.data) > 0;
|
|
126974
127108
|
const signalOk = SCORECARD_SIGNALS.includes(row?.signal);
|
|
@@ -127010,7 +127144,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127010
127144
|
);
|
|
127011
127145
|
}
|
|
127012
127146
|
for (const key of ["platform", "country", "adSets"]) {
|
|
127013
|
-
const insight = textLen2(
|
|
127147
|
+
const insight = textLen2(asRecord11(sections?.[key])?.insight);
|
|
127014
127148
|
if (insight < 200) {
|
|
127015
127149
|
pushMissing(
|
|
127016
127150
|
missing,
|
|
@@ -127021,7 +127155,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127021
127155
|
);
|
|
127022
127156
|
}
|
|
127023
127157
|
}
|
|
127024
|
-
const audience =
|
|
127158
|
+
const audience = asRecord11(sections?.audience);
|
|
127025
127159
|
if (asArray2(audience?.goldenProfile).filter((s) => textLen2(s) > 0).length < 3) {
|
|
127026
127160
|
pushMissing(
|
|
127027
127161
|
missing,
|
|
@@ -127049,7 +127183,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
127049
127183
|
"\u2265150 \u5B57"
|
|
127050
127184
|
);
|
|
127051
127185
|
}
|
|
127052
|
-
const landingRows = asArray2(
|
|
127186
|
+
const landingRows = asArray2(asRecord11(sections?.landingPage)?.rows);
|
|
127053
127187
|
if (landingRows.length < 3) {
|
|
127054
127188
|
pushMissing(
|
|
127055
127189
|
missing,
|
|
@@ -127134,7 +127268,7 @@ async function readJsonFile2(filePath) {
|
|
|
127134
127268
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
127135
127269
|
}
|
|
127136
127270
|
}
|
|
127137
|
-
function
|
|
127271
|
+
function asRecord12(value) {
|
|
127138
127272
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
127139
127273
|
return value;
|
|
127140
127274
|
}
|
|
@@ -127149,7 +127283,7 @@ function injectReportData2(html, payload) {
|
|
|
127149
127283
|
}
|
|
127150
127284
|
async function runFacebookAnalysisRender(opts) {
|
|
127151
127285
|
const dataPath = path29.resolve(opts.dataFile);
|
|
127152
|
-
let data =
|
|
127286
|
+
let data = asRecord12(await readJsonFile2(dataPath));
|
|
127153
127287
|
if (opts.snapshotDir?.trim()) {
|
|
127154
127288
|
data = await mergeFacebookSnapshotIntoReport(data, path29.resolve(opts.snapshotDir));
|
|
127155
127289
|
}
|
package/dist/skill/_meta.json
CHANGED
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
|
|
43
43
|
报告中「系列状态列」不得来自账户接口(账户状态 ≠ 系列状态,见 `references/core/agent-conventions.md` §四):系列是否启用**必须**来自 `ad campaigns`(含 `statusDisplay`)或 `google-analysis --sections campaigns` 落盘的系列数据。
|
|
44
44
|
|
|
45
|
+
**报告脚本禁止自行映射 `campaignStatus` 枚举**:网关值为 PascalCase(`Enabled`/`Paused`/`Removed`),Agent 若写 `STATUS_MAP[s] || '已移除'` 且未归一大小写,会把「投放中」误显示为「已移除」。`campaigns-*.json` 的 `items[]` 已注入 **`campaignStatusDisplay`**(`投放中` / `已暂停` / `已移除` / `—` / `未知`),HTML/Excel 状态列**直接读该字段**。
|
|
46
|
+
|
|
45
47
|
### 金额单位
|
|
46
48
|
|
|
47
49
|
CLI 出口的所有 JSON / 表格金额已统一为**元**,关键字段:
|
|
@@ -197,6 +199,11 @@ const data = d.record ?? d.items; // record 非空=汇总维度;否则=列表
|
|
|
197
199
|
|
|
198
200
|
`items[]` 每行额外包含:`conversionsValue`、`conversionsValuePerCost`(`spend ≤ 0` 时为 0)、`campaignTargetCpaYuan`、`maximizeConversionsTargetCpaYuan`、`manualCpcEnhancedCpcEnabled`、`percentCpcEnhancedCpcEnabled`。所有金额字段(`*Yuan` 后缀)已统一为元,可直接展示,无需换算。
|
|
199
201
|
|
|
202
|
+
| 字段 | 含义 |
|
|
203
|
+
| ---- | ---- |
|
|
204
|
+
| `campaignStatus` / `campaignStatusV2` | 网关原始枚举(`Enabled` / `Paused` / `Removed`) |
|
|
205
|
+
| `campaignStatusDisplay` | CLI 注入的中文状态文案;**报告状态列读此字段** |
|
|
206
|
+
|
|
200
207
|
日预算字段为 `budgetAmountYuan`(元),脚本可直接 `row.budgetAmountYuan`;网关原始 `budgetAmount`(分)已不再落盘。
|
|
201
208
|
|
|
202
209
|
#### 竞争指标 `competitiveMetrics`
|
|
@@ -47,7 +47,7 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
47
47
|
- 若环比列全为 `null`,检查落盘目录是否存在上一周期文件(如 `campaigns-<id>_20260503-20260602.json`);不存在则**重新执行 collect**(勿用手动拼装或 Python 脚本绕过 CLI)。
|
|
48
48
|
|
|
49
49
|
- **render 自动合并(默认开启)**:若同目录存在 `google-ads-diagnosis-collect.json`,且 Agent 改坏了 `campaigns` / `geographic` / `keywords` 的 `items`(缺 `title`/`currentCost` 或环比写成字符串)或整体覆盖丢弃了 `landingPageAnalysis.desktop`/`mobile`,CLI **自动从 collect.reportData 恢复**,保留 Agent 的 `analysis`/`suggestions`。可用 `--no-merge-collect` 关闭;`--collect <file>` 指定 collect 路径。
|
|
50
|
-
- **render 硬校验**:合并后对比表仍不完整则 **exit 1**(避免 HTML 表体全空);`summary.keyIssues` / `diagnosisOverview.disadvantages` 与 `newFeatures`/`goldAccount` 等事实字段出现已知矛盾模式(见「撰写硬约束」第 5 条)时同样 **exit 1**。
|
|
50
|
+
- **render 硬校验**:合并后对比表仍不完整则 **exit 1**(避免 HTML 表体全空);`campaigns/geographic/keywords.items` 默认同目录 collect **整表覆盖** Agent 改动(Agent 只填 analysis/suggestions);`summary.keyIssues` / `diagnosisOverview.disadvantages` 与 `newFeatures`/`goldAccount` 等事实字段出现已知矛盾模式(见「撰写硬约束」第 5 条)时同样 **exit 1**。
|
|
51
51
|
|
|
52
52
|
- **禁止**在 `collect` 阶段生成或写入 `analysis` / `suggestions` / `diagnosisOverview` / `summary` 等建议性文案。
|
|
53
53
|
- **禁止**跳过 Agent 直接 `render` collect 产物(`render` 会校验全部叙事 / 建议字段;调试可加 `--lenient`)。
|
|
@@ -130,6 +130,13 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
130
130
|
- `render` 会在同目录存在 `google-ads-diagnosis-collect.json` 时自动从 collect 恢复缺失/损坏的 `desktop`/`mobile`(保留 Agent 写的 `analysis`/`suggestions`),但**不能依赖这个兜底**——终稿应本就保留完整字段。
|
|
131
131
|
- 若 `collect` 里 `desktop`/`mobile` 本身就是空/全 0(说明 Lighthouse 没有抓到数据),`analysis` 必须写「着陆页测速数据未获取」,**禁止**编造评分或分析文字。
|
|
132
132
|
|
|
133
|
+
### 7. 环比对比表 `campaigns/geographic/keywords.items` — Agent 禁止写入
|
|
134
|
+
|
|
135
|
+
- `collect` 已根据当期 + 上一周期快照生成对比行(`title`、`currentCost`/`previousCost`、`costRateChange`、`currentClicks`/`previousClicks`、`clicksRateChange`、`currentCvr`/`previousCvr`、`cvrRateChange`)。Agent 撰写终稿时**只填** `analysis` / `suggestions`,**禁止**写入、清空、删减或「手工重写」`items` 数组
|
|
136
|
+
- `agentBrief.comparisonTableItemCounts` 给出 collect 侧行数(如 `keywords: 15`);终稿 `keywords.items.length` 须与之一致。若 Agent 误改,`render` 会**始终以**同目录 `google-ads-diagnosis-collect.json` **覆盖**上述三个模块的 `items`(保留 Agent 的 narrative)。
|
|
137
|
+
- 分析文案须引用对比表中的具体词/系列/国家名与环比数字(如「fitness mat 消耗 $230.60,环比下降 39.6%」),**禁止**在 `items` 有数据时写「关键词对比表为空」类结论。
|
|
138
|
+
- `biddingStrategy.items` / `newFeatures.items` 行结构由 collect 生成,Agent 可在合法行上填写 `recommendationReason` / `optimizerRecommendation`,但勿篡改 `duration`(须为天数 number)、`biddingStrategyType`、`strategyName` 等事实字段。
|
|
139
|
+
|
|
133
140
|
---
|
|
134
141
|
|
|
135
142
|
## 页眉信息(报告头)
|
|
@@ -246,17 +253,19 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
246
253
|
- 指标列:花费(上期/本期/环比)、点击(上期/本期/环比)、转化率(上期/本期/环比)
|
|
247
254
|
- **数据**:`campaigns.items`(`title`、`previousCost`、`currentCost`、`costRateChange`、`previousClicks`、`currentClicks`、`clicksRateChange`、`previousCvr`、`currentCvr`、`cvrRateChange`)
|
|
248
255
|
- **分析 / 建议**:`campaigns.analysis`、`campaigns.suggestions`
|
|
249
|
-
- **Agent
|
|
256
|
+
- **Agent 禁止写入 `items` 行**(只填 `analysis`/`suggestions`);`render` 会以 collect 为准**自动覆盖** Agent 改动的对比表 items
|
|
250
257
|
|
|
251
258
|
#### 国家地区分析
|
|
252
259
|
|
|
253
260
|
- 维度列:**国家/地区**
|
|
254
261
|
- **数据**:`geographic.items`(同上结构)
|
|
262
|
+
- **Agent 禁止写入 `items` 行**;`render` 自动从 collect 恢复
|
|
255
263
|
|
|
256
264
|
#### 关键词分析
|
|
257
265
|
|
|
258
266
|
- 维度列:**关键词**
|
|
259
|
-
- **数据**:`keywords.items`
|
|
267
|
+
- **数据**:`keywords.items`(字段同上;行数见 `agentBrief.comparisonTableItemCounts.keywords`)
|
|
268
|
+
- **Agent 禁止写入/清空 `items`**;分析须引用表中具体词名与环比,勿写「对比表为空」
|
|
260
269
|
|
|
261
270
|
#### 转化成本 / 按日趋势(若页面已渲染)
|
|
262
271
|
|
|
@@ -58,3 +58,16 @@ siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-goog
|
|
|
58
58
|
如果用户**追加**了维度(按上文「可选追加」表),把它们加入 `--sections` 即可;要全 21 维则省略 `--sections`。
|
|
59
59
|
|
|
60
60
|
> 仅在调试单个维度或需要传 `all` 暂未透传的特殊参数时,才回落到逐条 `siluzan-tso google-analysis <子命令> -a <id> …` 调用。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 广告系列状态列(必读)
|
|
65
|
+
|
|
66
|
+
`campaigns-*.json` → `items[]` 每行含 CLI 注入的 **`campaignStatusDisplay`**(`投放中` / `已暂停` / `已移除`)。
|
|
67
|
+
|
|
68
|
+
| 正确 | 错误 |
|
|
69
|
+
| ---- | ---- |
|
|
70
|
+
| 直接展示 `row.campaignStatusDisplay` | 读 `campaignStatus` 后自行 `MAP[s] \|\| '已移除'`(`Enabled` 未命中键时会全变「已移除」) |
|
|
71
|
+
| 需要英文枚举时读 `campaignStatus` 或 `campaignStatusV2`,展示前 `.upper()` 再映射 | 用 `stats` / `list-accounts` 的账户 `status` 充当系列状态 |
|
|
72
|
+
|
|
73
|
+
原始枚举为 PascalCase(`Enabled`),与 `ad campaigns` 的 `statusDisplay`(有效/未投放/已结束使用)口径不同;周期报告「状态」列统一用 **`campaignStatusDisplay`**。
|
|
@@ -47,7 +47,7 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
47
47
|
- 若环比列全为 `null`,检查落盘目录是否存在上一周期文件(如 `campaigns-<id>_20260503-20260602.json`);不存在则**重新执行 collect**(勿用手动拼装或 Python 脚本绕过 CLI)。
|
|
48
48
|
|
|
49
49
|
- **render 自动合并(默认开启)**:若同目录存在 `google-ads-diagnosis-collect.json`,且 Agent 改坏了 `campaigns` / `geographic` / `keywords` 的 `items`(缺 `title`/`currentCost` 或环比写成字符串)或整体覆盖丢弃了 `landingPageAnalysis.desktop`/`mobile`,CLI **自动从 collect.reportData 恢复**,保留 Agent 的 `analysis`/`suggestions`。可用 `--no-merge-collect` 关闭;`--collect <file>` 指定 collect 路径。
|
|
50
|
-
- **render 硬校验**:合并后对比表仍不完整则 **exit 1**(避免 HTML 表体全空);`summary.keyIssues` / `diagnosisOverview.disadvantages` 与 `newFeatures`/`goldAccount` 等事实字段出现已知矛盾模式(见「撰写硬约束」第 5 条)时同样 **exit 1**。
|
|
50
|
+
- **render 硬校验**:合并后对比表仍不完整则 **exit 1**(避免 HTML 表体全空);`campaigns/geographic/keywords.items` 默认同目录 collect **整表覆盖** Agent 改动(Agent 只填 analysis/suggestions);`summary.keyIssues` / `diagnosisOverview.disadvantages` 与 `newFeatures`/`goldAccount` 等事实字段出现已知矛盾模式(见「撰写硬约束」第 5 条)时同样 **exit 1**。
|
|
51
51
|
|
|
52
52
|
- **禁止**在 `collect` 阶段生成或写入 `analysis` / `suggestions` / `diagnosisOverview` / `summary` 等建议性文案。
|
|
53
53
|
- **禁止**跳过 Agent 直接 `render` collect 产物(`render` 会校验全部叙事 / 建议字段;调试可加 `--lenient`)。
|
|
@@ -130,6 +130,13 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
130
130
|
- `render` 会在同目录存在 `google-ads-diagnosis-collect.json` 时自动从 collect 恢复缺失/损坏的 `desktop`/`mobile`(保留 Agent 写的 `analysis`/`suggestions`),但**不能依赖这个兜底**——终稿应本就保留完整字段。
|
|
131
131
|
- 若 `collect` 里 `desktop`/`mobile` 本身就是空/全 0(说明 Lighthouse 没有抓到数据),`analysis` 必须写「着陆页测速数据未获取」,**禁止**编造评分或分析文字。
|
|
132
132
|
|
|
133
|
+
### 7. 环比对比表 `campaigns/geographic/keywords.items` — Agent 禁止写入
|
|
134
|
+
|
|
135
|
+
- `collect` 已根据当期 + 上一周期快照生成对比行(`title`、`currentCost`/`previousCost`、`costRateChange`、`currentClicks`/`previousClicks`、`clicksRateChange`、`currentCvr`/`previousCvr`、`cvrRateChange`)。Agent 撰写终稿时**只填** `analysis` / `suggestions`,**禁止**写入、清空、删减或「手工重写」`items` 数组
|
|
136
|
+
- `agentBrief.comparisonTableItemCounts` 给出 collect 侧行数(如 `keywords: 15`);终稿 `keywords.items.length` 须与之一致。若 Agent 误改,`render` 会**始终以**同目录 `google-ads-diagnosis-collect.json` **覆盖**上述三个模块的 `items`(保留 Agent 的 narrative)。
|
|
137
|
+
- 分析文案须引用对比表中的具体词/系列/国家名与环比数字(如「fitness mat 消耗 $230.60,环比下降 39.6%」),**禁止**在 `items` 有数据时写「关键词对比表为空」类结论。
|
|
138
|
+
- `biddingStrategy.items` / `newFeatures.items` 行结构由 collect 生成,Agent 可在合法行上填写 `recommendationReason` / `optimizerRecommendation`,但勿篡改 `duration`(须为天数 number)、`biddingStrategyType`、`strategyName` 等事实字段。
|
|
139
|
+
|
|
133
140
|
---
|
|
134
141
|
|
|
135
142
|
## 页眉信息(报告头)
|
|
@@ -246,17 +253,19 @@ siluzan-tso google-ads-diagnosis render \
|
|
|
246
253
|
- 指标列:花费(上期/本期/环比)、点击(上期/本期/环比)、转化率(上期/本期/环比)
|
|
247
254
|
- **数据**:`campaigns.items`(`title`、`previousCost`、`currentCost`、`costRateChange`、`previousClicks`、`currentClicks`、`clicksRateChange`、`previousCvr`、`currentCvr`、`cvrRateChange`)
|
|
248
255
|
- **分析 / 建议**:`campaigns.analysis`、`campaigns.suggestions`
|
|
249
|
-
- **Agent
|
|
256
|
+
- **Agent 禁止写入 `items` 行**(只填 `analysis`/`suggestions`);`render` 会以 collect 为准**自动覆盖** Agent 改动的对比表 items
|
|
250
257
|
|
|
251
258
|
#### 国家地区分析
|
|
252
259
|
|
|
253
260
|
- 维度列:**国家/地区**
|
|
254
261
|
- **数据**:`geographic.items`(同上结构)
|
|
262
|
+
- **Agent 禁止写入 `items` 行**;`render` 自动从 collect 恢复
|
|
255
263
|
|
|
256
264
|
#### 关键词分析
|
|
257
265
|
|
|
258
266
|
- 维度列:**关键词**
|
|
259
|
-
- **数据**:`keywords.items`
|
|
267
|
+
- **数据**:`keywords.items`(字段同上;行数见 `agentBrief.comparisonTableItemCounts.keywords`)
|
|
268
|
+
- **Agent 禁止写入/清空 `items`**;分析须引用表中具体词名与环比,勿写「对比表为空」
|
|
260
269
|
|
|
261
270
|
#### 转化成本 / 按日趋势(若页面已渲染)
|
|
262
271
|
|
|
@@ -58,3 +58,16 @@ siluzan-tso google-analysis -a <id> --start <s> --end <e> --json-out ./snap-goog
|
|
|
58
58
|
如果用户**追加**了维度(按上文「可选追加」表),把它们加入 `--sections` 即可;要全 21 维则省略 `--sections`。
|
|
59
59
|
|
|
60
60
|
> 仅在调试单个维度或需要传 `all` 暂未透传的特殊参数时,才回落到逐条 `siluzan-tso google-analysis <子命令> -a <id> …` 调用。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 广告系列状态列(必读)
|
|
65
|
+
|
|
66
|
+
`campaigns-*.json` → `items[]` 每行含 CLI 注入的 **`campaignStatusDisplay`**(`投放中` / `已暂停` / `已移除`)。
|
|
67
|
+
|
|
68
|
+
| 正确 | 错误 |
|
|
69
|
+
| ---- | ---- |
|
|
70
|
+
| 直接展示 `row.campaignStatusDisplay` | 读 `campaignStatus` 后自行 `MAP[s] \|\| '已移除'`(`Enabled` 未命中键时会全变「已移除」) |
|
|
71
|
+
| 需要英文枚举时读 `campaignStatus` 或 `campaignStatusV2`,展示前 `.upper()` 再映射 | 用 `stats` / `list-accounts` 的账户 `status` 充当系列状态 |
|
|
72
|
+
|
|
73
|
+
原始枚举为 PascalCase(`Enabled`),与 `ad campaigns` 的 `statusDisplay`(有效/未投放/已结束使用)口径不同;周期报告「状态」列统一用 **`campaignStatusDisplay`**。
|
|
@@ -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.35-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.35-beta.5'
|
|
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.35-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.35-beta.5"
|
|
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"
|