siluzan-tso-cli 1.1.56-beta.2 → 1.1.56-beta.4
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 +1407 -276
- package/dist/skill/SKILL.md +1 -1
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/meta-lead-create-template.json +2 -1
- package/dist/skill/assets/meta-lead-create-template.md +3 -2
- package/dist/skill/references/core/agent-conventions.md +2 -1
- package/dist/skill/references/core/workflows.md +2 -1
- package/dist/skill/references/google-ads/google-ads-write.md +5 -1
- package/dist/skill/references/meta-ads/meta-ads-write.md +7 -6
- package/dist/skill/references/meta-ads/meta-ads.md +2 -2
- package/dist/skill/references/report-templates/google-ads-diagnosis.md +22 -18
- package/dist/skill/report-templates/GoogleAdsDiagnosisReport.html +1 -15
- package/dist/skill/report-templates/google-ads-diagnosis.md +22 -18
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -103126,7 +103126,7 @@ function narrativeClaimsMobileSpendLeads(text2) {
|
|
|
103126
103126
|
if (!t.includes("\u79FB\u52A8")) return false;
|
|
103127
103127
|
return /移动(?:端|设备)?(?:为|是)主要消耗/.test(t) || /移动(?:端|设备)?为主要(?:消耗)?设备/.test(t) || /移动(?:端|设备)?(?:消耗|花费|费用|流量)?占比高/.test(t);
|
|
103128
103128
|
}
|
|
103129
|
-
function
|
|
103129
|
+
function asRecord19(value) {
|
|
103130
103130
|
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
103131
103131
|
return null;
|
|
103132
103132
|
}
|
|
@@ -103134,16 +103134,16 @@ function stringList(value) {
|
|
|
103134
103134
|
return (Array.isArray(value) ? value : []).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
103135
103135
|
}
|
|
103136
103136
|
function collectOkkiNarrativeText(data) {
|
|
103137
|
-
const narrative =
|
|
103137
|
+
const narrative = asRecord19(data.narrative) ?? {};
|
|
103138
103138
|
const parts = [];
|
|
103139
|
-
const sheetAnalysis =
|
|
103139
|
+
const sheetAnalysis = asRecord19(narrative.sheetAnalysis) ?? {};
|
|
103140
103140
|
for (const block of Object.values(sheetAnalysis)) {
|
|
103141
|
-
const rec =
|
|
103141
|
+
const rec = asRecord19(block);
|
|
103142
103142
|
parts.push(...stringList(rec?.summary), ...stringList(rec?.suggestions));
|
|
103143
103143
|
}
|
|
103144
|
-
const review =
|
|
103144
|
+
const review = asRecord19(narrative.review) ?? {};
|
|
103145
103145
|
for (const block of Object.values(review)) {
|
|
103146
|
-
const rec =
|
|
103146
|
+
const rec = asRecord19(block);
|
|
103147
103147
|
for (const key of ["overview", "summary", "suggestion"]) {
|
|
103148
103148
|
const value = rec?.[key];
|
|
103149
103149
|
if (typeof value === "string" && value.trim()) parts.push(value.trim());
|
|
@@ -103160,7 +103160,7 @@ var init_okki_display = __esm({
|
|
|
103160
103160
|
// src/commands/google-analysis/merge-snapshot.ts
|
|
103161
103161
|
import * as fs27 from "fs/promises";
|
|
103162
103162
|
import * as path32 from "path";
|
|
103163
|
-
function
|
|
103163
|
+
function asRecord20(value) {
|
|
103164
103164
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
103165
103165
|
return value;
|
|
103166
103166
|
}
|
|
@@ -103256,7 +103256,7 @@ function periodMetricsFromRow(row) {
|
|
|
103256
103256
|
}
|
|
103257
103257
|
function items(envelope) {
|
|
103258
103258
|
if (!envelope || !Array.isArray(envelope.items)) return [];
|
|
103259
|
-
return envelope.items.filter((row) =>
|
|
103259
|
+
return envelope.items.filter((row) => asRecord20(row) !== null);
|
|
103260
103260
|
}
|
|
103261
103261
|
function record(envelope) {
|
|
103262
103262
|
return envelope?.record ?? null;
|
|
@@ -103303,14 +103303,14 @@ function sumCampaignRowsToPeriodKpis(rows) {
|
|
|
103303
103303
|
}
|
|
103304
103304
|
async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
103305
103305
|
const absDir = path32.resolve(snapshotDir);
|
|
103306
|
-
const metaAccountId = typeof
|
|
103306
|
+
const metaAccountId = typeof asRecord20(payload.meta)?.accountId === "string" ? asRecord20(payload.meta)?.accountId : void 0;
|
|
103307
103307
|
const manifest = await readManifest(snapshotDir, metaAccountId);
|
|
103308
103308
|
if (!manifest) {
|
|
103309
103309
|
throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C google-analysis --json-out`);
|
|
103310
103310
|
}
|
|
103311
103311
|
const accountId = manifest.googleAnalysis?.accountId ?? metaAccountId;
|
|
103312
103312
|
const dateRange = manifest.googleAnalysis?.dateRange;
|
|
103313
|
-
const preferredDateTag = dateTagFromRange(dateRange) ?? dateTagFromMeta(
|
|
103313
|
+
const preferredDateTag = dateTagFromRange(dateRange) ?? dateTagFromMeta(asRecord20(payload.meta) ?? void 0);
|
|
103314
103314
|
const sectionMap = /* @__PURE__ */ new Map();
|
|
103315
103315
|
for (const art of manifest.artifacts ?? []) {
|
|
103316
103316
|
const data = await loadSectionFile(absDir, art.file);
|
|
@@ -103318,8 +103318,8 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
103318
103318
|
}
|
|
103319
103319
|
await recoverMissingReportSections(sectionMap, absDir, accountId, preferredDateTag);
|
|
103320
103320
|
const overviewRecord = record(sectionMap.get("overview"));
|
|
103321
|
-
const currentPeriod =
|
|
103322
|
-
const previousPeriod =
|
|
103321
|
+
const currentPeriod = asRecord20(overviewRecord?.currentPeriod);
|
|
103322
|
+
const previousPeriod = asRecord20(overviewRecord?.previousPeriod);
|
|
103323
103323
|
const meta = { ...payload.meta ?? {} };
|
|
103324
103324
|
if (!meta.accountId && accountId) meta.accountId = accountId;
|
|
103325
103325
|
if (!meta.currency) {
|
|
@@ -103465,7 +103465,7 @@ function textLen(value) {
|
|
|
103465
103465
|
function asArray(value) {
|
|
103466
103466
|
return Array.isArray(value) ? value : [];
|
|
103467
103467
|
}
|
|
103468
|
-
function
|
|
103468
|
+
function asRecord21(value) {
|
|
103469
103469
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
103470
103470
|
return value;
|
|
103471
103471
|
}
|
|
@@ -103479,7 +103479,7 @@ function pushMissing(missing, id, chapter, dimension, hint) {
|
|
|
103479
103479
|
}
|
|
103480
103480
|
function validateOkkiWeeklyReportContent(data) {
|
|
103481
103481
|
const missing = [];
|
|
103482
|
-
const meta =
|
|
103482
|
+
const meta = asRecord21(data.meta) ?? {};
|
|
103483
103483
|
if (!textLen(meta.accountId)) {
|
|
103484
103484
|
pushMissing(
|
|
103485
103485
|
missing,
|
|
@@ -103489,7 +103489,7 @@ function validateOkkiWeeklyReportContent(data) {
|
|
|
103489
103489
|
"meta.accountId \u5FC5\u586B\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 overview/manifest"
|
|
103490
103490
|
);
|
|
103491
103491
|
}
|
|
103492
|
-
const snapshotSections =
|
|
103492
|
+
const snapshotSections = asRecord21(data.snapshot?.sections) ?? {};
|
|
103493
103493
|
const snapshotPresent = {};
|
|
103494
103494
|
for (const section of OKKI_SNAPSHOT_SECTIONS) {
|
|
103495
103495
|
const present = snapshotSections[section] === true;
|
|
@@ -103504,12 +103504,12 @@ function validateOkkiWeeklyReportContent(data) {
|
|
|
103504
103504
|
);
|
|
103505
103505
|
}
|
|
103506
103506
|
}
|
|
103507
|
-
const narrative =
|
|
103508
|
-
const sheetAnalysis =
|
|
103509
|
-
const review =
|
|
103507
|
+
const narrative = asRecord21(data.narrative) ?? {};
|
|
103508
|
+
const sheetAnalysis = asRecord21(narrative.sheetAnalysis) ?? {};
|
|
103509
|
+
const review = asRecord21(narrative.review) ?? {};
|
|
103510
103510
|
const sheetAnalysisCounts = {};
|
|
103511
103511
|
for (const key of OKKI_SHEET_KEYS) {
|
|
103512
|
-
const block =
|
|
103512
|
+
const block = asRecord21(sheetAnalysis[key]);
|
|
103513
103513
|
const summary = stringList2(block?.summary);
|
|
103514
103514
|
const suggestions = stringList2(block?.suggestions);
|
|
103515
103515
|
sheetAnalysisCounts[key] = { summary: summary.length, suggestions: suggestions.length };
|
|
@@ -103535,7 +103535,7 @@ function validateOkkiWeeklyReportContent(data) {
|
|
|
103535
103535
|
}
|
|
103536
103536
|
const reviewFilled = {};
|
|
103537
103537
|
for (const key of OKKI_REVIEW_KEYS) {
|
|
103538
|
-
const block =
|
|
103538
|
+
const block = asRecord21(review[key]);
|
|
103539
103539
|
const overview = textLen(block?.overview);
|
|
103540
103540
|
const summary = textLen(block?.summary);
|
|
103541
103541
|
const suggestion = textLen(block?.suggestion);
|
|
@@ -103570,7 +103570,7 @@ function validateOkkiWeeklyReportContent(data) {
|
|
|
103570
103570
|
}
|
|
103571
103571
|
}
|
|
103572
103572
|
const deviceRows2 = Array.isArray(data.tables?.devices) ? data.tables.devices : [];
|
|
103573
|
-
const deviceSpend = summarizeOkkiDeviceSpend(deviceRows2.filter((row) =>
|
|
103573
|
+
const deviceSpend = summarizeOkkiDeviceSpend(deviceRows2.filter((row) => asRecord21(row) !== null));
|
|
103574
103574
|
if (deviceSpend.total > 0 && deviceSpend.desktop >= deviceSpend.mobile && narrativeClaimsMobileSpendLeads(collectOkkiNarrativeText(data))) {
|
|
103575
103575
|
pushMissing(
|
|
103576
103576
|
missing,
|
|
@@ -103659,7 +103659,7 @@ function round25(value) {
|
|
|
103659
103659
|
}
|
|
103660
103660
|
function items2(envelope) {
|
|
103661
103661
|
if (!envelope || !Array.isArray(envelope.items)) return [];
|
|
103662
|
-
return envelope.items.filter((row) =>
|
|
103662
|
+
return envelope.items.filter((row) => asRecord21(row) !== null);
|
|
103663
103663
|
}
|
|
103664
103664
|
function dateTagFromRange2(dateRange) {
|
|
103665
103665
|
if (!dateRange || dateRange.mode !== "range") return void 0;
|
|
@@ -103703,14 +103703,14 @@ async function loadSectionFile2(snapshotDir, file) {
|
|
|
103703
103703
|
}
|
|
103704
103704
|
async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
|
|
103705
103705
|
const absDir = path33.resolve(snapshotDir);
|
|
103706
|
-
const metaAccountId = typeof
|
|
103706
|
+
const metaAccountId = typeof asRecord21(payload.meta)?.accountId === "string" ? asRecord21(payload.meta)?.accountId : void 0;
|
|
103707
103707
|
const manifest = await readManifest2(absDir, metaAccountId);
|
|
103708
103708
|
if (!manifest) {
|
|
103709
103709
|
throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C google-analysis --json-out`);
|
|
103710
103710
|
}
|
|
103711
103711
|
const accountId = manifest.googleAnalysis?.accountId ?? metaAccountId;
|
|
103712
103712
|
const dateRange = manifest.googleAnalysis?.dateRange;
|
|
103713
|
-
const preferredDateTag = dateTagFromRange2(dateRange) ?? dateTagFromMeta2(
|
|
103713
|
+
const preferredDateTag = dateTagFromRange2(dateRange) ?? dateTagFromMeta2(asRecord21(payload.meta) ?? void 0);
|
|
103714
103714
|
const sectionMap = /* @__PURE__ */ new Map();
|
|
103715
103715
|
for (const art of manifest.artifacts ?? []) {
|
|
103716
103716
|
const data = await loadSectionFile2(absDir, art.file);
|
|
@@ -103736,7 +103736,7 @@ async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
|
|
|
103736
103736
|
snapshotPresent[section] = sectionMap.has(section);
|
|
103737
103737
|
}
|
|
103738
103738
|
const overviewRecord = sectionMap.get("overview")?.record ?? null;
|
|
103739
|
-
const currentPeriod =
|
|
103739
|
+
const currentPeriod = asRecord21(overviewRecord?.currentPeriod);
|
|
103740
103740
|
const meta = { ...payload.meta ?? {} };
|
|
103741
103741
|
if (!meta.accountId && accountId) meta.accountId = accountId;
|
|
103742
103742
|
if (!meta.currency) {
|
|
@@ -103879,18 +103879,18 @@ function prefixLines(items3, firstPrefix) {
|
|
|
103879
103879
|
});
|
|
103880
103880
|
}
|
|
103881
103881
|
function analysisLines(data, key) {
|
|
103882
|
-
const block =
|
|
103883
|
-
const rec =
|
|
103882
|
+
const block = asRecord21(asRecord21(data.narrative)?.sheetAnalysis)?.[key];
|
|
103883
|
+
const rec = asRecord21(block);
|
|
103884
103884
|
return [
|
|
103885
103885
|
...prefixLines(stringList2(rec?.summary), "\u603B\u7ED3\uFF1A"),
|
|
103886
103886
|
...prefixLines(stringList2(rec?.suggestions), "\u5EFA\u8BAE\uFF1A")
|
|
103887
103887
|
];
|
|
103888
103888
|
}
|
|
103889
103889
|
function reviewLines(data) {
|
|
103890
|
-
const review =
|
|
103890
|
+
const review = asRecord21(asRecord21(data.narrative)?.review) ?? {};
|
|
103891
103891
|
const lines = [];
|
|
103892
103892
|
OKKI_REVIEW_KEYS.forEach((key, idx) => {
|
|
103893
|
-
const block =
|
|
103893
|
+
const block = asRecord21(review[key]) ?? {};
|
|
103894
103894
|
lines.push(`${idx + 1}\u3001${REVIEW_LABELS2[key]}\uFF1A${str(block.overview, "")}`);
|
|
103895
103895
|
lines.push(` \u603B\u7ED3\uFF1A${str(block.summary, "")}`);
|
|
103896
103896
|
lines.push(` \u5EFA\u8BAE\uFF1A${str(block.suggestion, "")}`);
|
|
@@ -104022,7 +104022,7 @@ function geoRows(data) {
|
|
|
104022
104022
|
});
|
|
104023
104023
|
}
|
|
104024
104024
|
function buildOkkiWeeklySheetDrafts(data) {
|
|
104025
|
-
const meta =
|
|
104025
|
+
const meta = asRecord21(data.meta) ?? {};
|
|
104026
104026
|
const period = formatOkkiZhDateRange(str(meta.startDate, ""), str(meta.endDate, ""));
|
|
104027
104027
|
const campaigns = campaignRows(data);
|
|
104028
104028
|
const drafts = {
|
|
@@ -104283,7 +104283,7 @@ function textLen2(value) {
|
|
|
104283
104283
|
function asArray2(value) {
|
|
104284
104284
|
return Array.isArray(value) ? value : [];
|
|
104285
104285
|
}
|
|
104286
|
-
function
|
|
104286
|
+
function asRecord22(value) {
|
|
104287
104287
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
104288
104288
|
return value;
|
|
104289
104289
|
}
|
|
@@ -104294,9 +104294,9 @@ function pushMissing2(missing, id, chapter, dimension, hint) {
|
|
|
104294
104294
|
}
|
|
104295
104295
|
function validateGooglePeriodReportContent(data) {
|
|
104296
104296
|
const missing = [];
|
|
104297
|
-
const meta =
|
|
104298
|
-
const narrative =
|
|
104299
|
-
const sections =
|
|
104297
|
+
const meta = asRecord22(data.meta) ?? {};
|
|
104298
|
+
const narrative = asRecord22(data.narrative) ?? {};
|
|
104299
|
+
const sections = asRecord22(narrative.sections) ?? {};
|
|
104300
104300
|
if (!textLen2(meta.accountId)) {
|
|
104301
104301
|
pushMissing2(
|
|
104302
104302
|
missing,
|
|
@@ -104316,7 +104316,7 @@ function validateGooglePeriodReportContent(data) {
|
|
|
104316
104316
|
"\u22651 \u6BB5\u6267\u884C\u6458\u8981\uFF0C\u6982\u8FF0\u672C\u671F\u6D88\u8017/\u8F6C\u5316/CTR/CPA \u8D70\u52BF"
|
|
104317
104317
|
);
|
|
104318
104318
|
}
|
|
104319
|
-
const kpis =
|
|
104319
|
+
const kpis = asRecord22(data.kpis);
|
|
104320
104320
|
const hasCoreKpi = ["cost", "impressions", "clicks"].some(
|
|
104321
104321
|
(key) => Number.isFinite(Number(kpis?.[key]))
|
|
104322
104322
|
);
|
|
@@ -104332,7 +104332,7 @@ function validateGooglePeriodReportContent(data) {
|
|
|
104332
104332
|
const sectionAnalysisCounts = {};
|
|
104333
104333
|
const sectionSuggestionCounts = {};
|
|
104334
104334
|
for (const key of SECTION_KEYS) {
|
|
104335
|
-
const section =
|
|
104335
|
+
const section = asRecord22(sections[key]);
|
|
104336
104336
|
const analysis = asArray2(section?.analysis).filter((s) => textLen2(s) > 0);
|
|
104337
104337
|
const suggestions = asArray2(section?.suggestions).filter((s) => textLen2(s) > 0);
|
|
104338
104338
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -104358,7 +104358,7 @@ function validateGooglePeriodReportContent(data) {
|
|
|
104358
104358
|
}
|
|
104359
104359
|
const recommendations = asArray2(narrative.recommendations).filter((r) => {
|
|
104360
104360
|
if (typeof r === "string") return textLen2(r) > 0;
|
|
104361
|
-
const rec =
|
|
104361
|
+
const rec = asRecord22(r);
|
|
104362
104362
|
return textLen2(rec?.content) > 0;
|
|
104363
104363
|
});
|
|
104364
104364
|
if (recommendations.length < 3) {
|
|
@@ -104447,7 +104447,7 @@ async function readJsonFile3(filePath) {
|
|
|
104447
104447
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
104448
104448
|
}
|
|
104449
104449
|
}
|
|
104450
|
-
function
|
|
104450
|
+
function asRecord23(value) {
|
|
104451
104451
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
104452
104452
|
return value;
|
|
104453
104453
|
}
|
|
@@ -104462,7 +104462,7 @@ function injectReportData2(html, payload) {
|
|
|
104462
104462
|
}
|
|
104463
104463
|
async function runGoogleAnalysisRender(opts) {
|
|
104464
104464
|
const dataPath = path35.resolve(opts.dataFile);
|
|
104465
|
-
let data =
|
|
104465
|
+
let data = asRecord23(await readJsonFile3(dataPath));
|
|
104466
104466
|
if (opts.snapshotDir?.trim()) {
|
|
104467
104467
|
data = await mergeGoogleAnalysisSnapshotIntoReport(data, path35.resolve(opts.snapshotDir));
|
|
104468
104468
|
}
|
|
@@ -113620,6 +113620,192 @@ import { readFileSync as readFileSync4 } from "fs";
|
|
|
113620
113620
|
init_cli_json_snapshot();
|
|
113621
113621
|
init_strip_legacy_google_fields();
|
|
113622
113622
|
init_cli_table();
|
|
113623
|
+
|
|
113624
|
+
// src/commands/ad/write-verification.ts
|
|
113625
|
+
var CAMPAIGN_WRITE_SCOPE = "gateway-full-put";
|
|
113626
|
+
function sameWriteValue(expected, actual) {
|
|
113627
|
+
if (expected === actual) return true;
|
|
113628
|
+
if (typeof expected === "string" && typeof actual === "string") {
|
|
113629
|
+
return expected.trim() === actual.trim();
|
|
113630
|
+
}
|
|
113631
|
+
if (typeof expected === "number" && typeof actual === "number") return expected === actual;
|
|
113632
|
+
if (typeof expected === "boolean" && typeof actual === "boolean") return expected === actual;
|
|
113633
|
+
if (Array.isArray(expected) && Array.isArray(actual)) {
|
|
113634
|
+
if (expected.length !== actual.length) return false;
|
|
113635
|
+
return expected.every((item, i) => sameWriteValue(item, actual[i]));
|
|
113636
|
+
}
|
|
113637
|
+
return false;
|
|
113638
|
+
}
|
|
113639
|
+
function fieldCheck(field, expected, actual, present) {
|
|
113640
|
+
if (!present) return { field, expected, actual: void 0, state: "unread" };
|
|
113641
|
+
return {
|
|
113642
|
+
field,
|
|
113643
|
+
expected,
|
|
113644
|
+
actual,
|
|
113645
|
+
state: sameWriteValue(expected, actual) ? "match" : "mismatch"
|
|
113646
|
+
};
|
|
113647
|
+
}
|
|
113648
|
+
function classifyFieldChecks(checks, readFailed) {
|
|
113649
|
+
if (readFailed) return "unknown";
|
|
113650
|
+
if (checks.length === 0) return "submitted";
|
|
113651
|
+
if (checks.some((c) => c.state === "unread")) return "unknown";
|
|
113652
|
+
if (checks.every((c) => c.state === "match")) return "verified";
|
|
113653
|
+
return "partial";
|
|
113654
|
+
}
|
|
113655
|
+
function describeUnmatchedChecks(checks) {
|
|
113656
|
+
return checks.filter((c) => c.state !== "match").map((c) => {
|
|
113657
|
+
const actual = c.state === "unread" ? "\uFF08\u672A\u8BFB\u5230\uFF09" : JSON.stringify(c.actual);
|
|
113658
|
+
return `${c.field}\uFF1A\u671F\u671B ${JSON.stringify(c.expected)}\uFF0C\u8BFB\u5230 ${actual}`;
|
|
113659
|
+
}).join("\uFF1B");
|
|
113660
|
+
}
|
|
113661
|
+
function writeVerificationHeadline(verification, detail) {
|
|
113662
|
+
if (verification === "verified") return `\u2705 ${detail}`;
|
|
113663
|
+
if (verification === "partial") return `\u26A0\uFE0F \u90E8\u5206\u751F\u6548\uFF1A${detail}`;
|
|
113664
|
+
return `\u26A0\uFE0F \u5DF2\u63D0\u4EA4\uFF0C\u5C1A\u672A\u6838\u9A8C\uFF1A${detail}`;
|
|
113665
|
+
}
|
|
113666
|
+
function readBudgetSharedFlag(campaign) {
|
|
113667
|
+
const raw = campaign["budgetShared"] ?? campaign["BudgetShared"];
|
|
113668
|
+
if (typeof raw === "boolean") return raw;
|
|
113669
|
+
return void 0;
|
|
113670
|
+
}
|
|
113671
|
+
function readCampaignBudgetCents(campaign) {
|
|
113672
|
+
const raw = campaign["budget"] ?? campaign["Budget"];
|
|
113673
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
113674
|
+
return void 0;
|
|
113675
|
+
}
|
|
113676
|
+
function readKey(obj, keys) {
|
|
113677
|
+
for (const key of keys) {
|
|
113678
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) return { present: true, value: obj[key] };
|
|
113679
|
+
}
|
|
113680
|
+
return { present: false, value: void 0 };
|
|
113681
|
+
}
|
|
113682
|
+
function evaluateCampaignReadback(after, expected, readFailed) {
|
|
113683
|
+
if (readFailed || !after) return { verification: "unknown", checks: [] };
|
|
113684
|
+
const checks = [];
|
|
113685
|
+
const push = (field, expectedValue, keys) => {
|
|
113686
|
+
if (expectedValue === void 0) return;
|
|
113687
|
+
const found = readKey(after, keys);
|
|
113688
|
+
checks.push(fieldCheck(field, expectedValue, found.value, found.present));
|
|
113689
|
+
};
|
|
113690
|
+
push("name", expected.name, ["name", "Name"]);
|
|
113691
|
+
push("statusV2", expected.statusV2, ["statusV2", "StatusV2"]);
|
|
113692
|
+
push("biddingStrategyTypeV2", expected.biddingStrategyTypeV2, [
|
|
113693
|
+
"biddingStrategyTypeV2",
|
|
113694
|
+
"BiddingStrategyTypeV2"
|
|
113695
|
+
]);
|
|
113696
|
+
push("targetSpend_BidCeilingAmount", expected.targetSpendBidCeilingCents, [
|
|
113697
|
+
"targetSpend_BidCeilingAmount",
|
|
113698
|
+
"TargetSpend_BidCeilingAmount"
|
|
113699
|
+
]);
|
|
113700
|
+
push("targetCpa_BidingAmount", expected.targetCpaCents, [
|
|
113701
|
+
"targetCpa_BidingAmount",
|
|
113702
|
+
"TargetCpa_BidingAmount"
|
|
113703
|
+
]);
|
|
113704
|
+
push("targetRoas", expected.targetRoas, ["targetRoas", "TargetRoas"]);
|
|
113705
|
+
push("manualCpc_EnhancedCpcEnabled", expected.manualCpcEnhanced, [
|
|
113706
|
+
"manualCpc_EnhancedCpcEnabled",
|
|
113707
|
+
"ManualCpc_EnhancedCpcEnabled"
|
|
113708
|
+
]);
|
|
113709
|
+
push("targetSearchNetwork", expected.targetSearchNetwork, [
|
|
113710
|
+
"targetSearchNetwork",
|
|
113711
|
+
"TargetSearchNetwork"
|
|
113712
|
+
]);
|
|
113713
|
+
push("targetContentNetwork", expected.targetContentNetwork, [
|
|
113714
|
+
"targetContentNetwork",
|
|
113715
|
+
"TargetContentNetwork"
|
|
113716
|
+
]);
|
|
113717
|
+
if (expected.budgetWasRequested) {
|
|
113718
|
+
push("budget", expected.budgetCents, ["budget", "Budget"]);
|
|
113719
|
+
} else if (expected.budgetCentsBefore !== void 0) {
|
|
113720
|
+
const found = readKey(after, ["budget", "Budget"]);
|
|
113721
|
+
checks.push(fieldCheck("budget", expected.budgetCentsBefore, found.value, found.present));
|
|
113722
|
+
}
|
|
113723
|
+
return { verification: classifyFieldChecks(checks, false), checks };
|
|
113724
|
+
}
|
|
113725
|
+
function judgeBudgetAfterFailedPut(input) {
|
|
113726
|
+
if (input.readFailed || input.budgetCentsBefore === void 0 || input.budgetCentsAfter === void 0) {
|
|
113727
|
+
const present = !input.readFailed && input.budgetCentsAfter !== void 0;
|
|
113728
|
+
const checks = input.budgetCentsBefore === void 0 ? [] : [fieldCheck("budget", input.budgetCentsBefore, input.budgetCentsAfter, present)];
|
|
113729
|
+
return { kind: "unknown", verification: "unknown", checks };
|
|
113730
|
+
}
|
|
113731
|
+
if (input.budgetCentsBefore !== input.budgetCentsAfter) {
|
|
113732
|
+
return {
|
|
113733
|
+
kind: "partial",
|
|
113734
|
+
verification: "partial",
|
|
113735
|
+
checks: [fieldCheck("budget", input.budgetCentsBefore, input.budgetCentsAfter, true)]
|
|
113736
|
+
};
|
|
113737
|
+
}
|
|
113738
|
+
return { kind: "unchanged" };
|
|
113739
|
+
}
|
|
113740
|
+
function coreKeyword(text2) {
|
|
113741
|
+
const t = text2.trim();
|
|
113742
|
+
if (t.length >= 2 && (t.startsWith('"') && t.endsWith('"') || t.startsWith("[") && t.endsWith("]"))) {
|
|
113743
|
+
return t.slice(1, -1).trim();
|
|
113744
|
+
}
|
|
113745
|
+
return t;
|
|
113746
|
+
}
|
|
113747
|
+
function collectKeywordCores(value) {
|
|
113748
|
+
if (typeof value === "string") {
|
|
113749
|
+
const core = coreKeyword(value);
|
|
113750
|
+
return core ? [core] : [];
|
|
113751
|
+
}
|
|
113752
|
+
if (!Array.isArray(value)) return [];
|
|
113753
|
+
const cores = [];
|
|
113754
|
+
for (const item of value) {
|
|
113755
|
+
if (typeof item !== "string") continue;
|
|
113756
|
+
const core = coreKeyword(item);
|
|
113757
|
+
if (core) cores.push(core);
|
|
113758
|
+
}
|
|
113759
|
+
return cores;
|
|
113760
|
+
}
|
|
113761
|
+
function isRecord(value) {
|
|
113762
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
113763
|
+
}
|
|
113764
|
+
function recordsFromResponse(response) {
|
|
113765
|
+
if (Array.isArray(response)) return response.filter(isRecord);
|
|
113766
|
+
if (!isRecord(response)) return [];
|
|
113767
|
+
for (const key of ["data", "items", "keywords"]) {
|
|
113768
|
+
const nested = response[key];
|
|
113769
|
+
if (Array.isArray(nested)) return nested.filter(isRecord);
|
|
113770
|
+
}
|
|
113771
|
+
const cores = [
|
|
113772
|
+
...collectKeywordCores(response["keywordText"]),
|
|
113773
|
+
...collectKeywordCores(response["KeywordText"]),
|
|
113774
|
+
...collectKeywordCores(response["text"])
|
|
113775
|
+
];
|
|
113776
|
+
if (cores.length > 0 || typeof response["id"] === "string") return [response];
|
|
113777
|
+
return [];
|
|
113778
|
+
}
|
|
113779
|
+
function summarizeNegativeKeywordCreate(requested, response, listItems) {
|
|
113780
|
+
const responseRecords = recordsFromResponse(response);
|
|
113781
|
+
const pools = listItems == null ? responseRecords : [...responseRecords, ...listItems];
|
|
113782
|
+
const matched = [];
|
|
113783
|
+
const pending = [];
|
|
113784
|
+
for (const text2 of requested) {
|
|
113785
|
+
const core = coreKeyword(text2);
|
|
113786
|
+
const hit = pools.find((item) => {
|
|
113787
|
+
const cores = [
|
|
113788
|
+
...collectKeywordCores(item["keywordText"]),
|
|
113789
|
+
...collectKeywordCores(item["KeywordText"]),
|
|
113790
|
+
...collectKeywordCores(item["text"])
|
|
113791
|
+
];
|
|
113792
|
+
return cores.includes(core);
|
|
113793
|
+
});
|
|
113794
|
+
if (!hit) {
|
|
113795
|
+
pending.push(text2);
|
|
113796
|
+
continue;
|
|
113797
|
+
}
|
|
113798
|
+
const idRaw = hit["id"] ?? hit["Id"];
|
|
113799
|
+
matched.push({ text: text2, id: typeof idRaw === "string" ? idRaw : void 0 });
|
|
113800
|
+
}
|
|
113801
|
+
let verification;
|
|
113802
|
+
if (requested.length > 0 && matched.length === requested.length) verification = "verified";
|
|
113803
|
+
else if (matched.length > 0) verification = "partial";
|
|
113804
|
+
else verification = "unknown";
|
|
113805
|
+
return { verification, matchedCount: matched.length, matched, pending };
|
|
113806
|
+
}
|
|
113807
|
+
|
|
113808
|
+
// src/commands/ad/keyword.ts
|
|
113623
113809
|
function unwrapKeywordDisplayTextForEdit(raw) {
|
|
113624
113810
|
const t = raw.trim();
|
|
113625
113811
|
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
|
|
@@ -113952,17 +114138,77 @@ async function runAdNegativeKeywordCreate(opts) {
|
|
|
113952
114138
|
}
|
|
113953
114139
|
const body = buildNegativeKeywordCreateBody(level, keywords, opts);
|
|
113954
114140
|
const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}`;
|
|
114141
|
+
let response;
|
|
113955
114142
|
try {
|
|
113956
|
-
await apiFetch2(
|
|
114143
|
+
response = await apiFetch2(
|
|
114144
|
+
url,
|
|
114145
|
+
config,
|
|
114146
|
+
{ method: "POST", body: JSON.stringify(body) },
|
|
114147
|
+
opts.verbose
|
|
114148
|
+
);
|
|
113957
114149
|
} catch (err) {
|
|
113958
114150
|
console.error(`
|
|
113959
114151
|
\u274C \u6DFB\u52A0\u5426\u5B9A\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113960
114152
|
`);
|
|
113961
114153
|
process.exit(1);
|
|
113962
114154
|
}
|
|
114155
|
+
const dateParams = new URLSearchParams();
|
|
114156
|
+
dateParams.set("startDate", toGoogleDate(void 0, -30));
|
|
114157
|
+
dateParams.set("endDate", toGoogleDate(void 0, 0));
|
|
114158
|
+
const listUrl = `${googleApiUrl}/negativekeywordmanagement/v2/list/${opts.account}?${dateParams}`;
|
|
114159
|
+
const readList = async () => {
|
|
114160
|
+
try {
|
|
114161
|
+
const listed = await apiFetch2(
|
|
114162
|
+
listUrl,
|
|
114163
|
+
config,
|
|
114164
|
+
{},
|
|
114165
|
+
opts.verbose
|
|
114166
|
+
);
|
|
114167
|
+
return listed.data ?? [];
|
|
114168
|
+
} catch {
|
|
114169
|
+
return null;
|
|
114170
|
+
}
|
|
114171
|
+
};
|
|
114172
|
+
let listItems = await readList();
|
|
114173
|
+
let summary = summarizeNegativeKeywordCreate(keywords, response, listItems);
|
|
114174
|
+
if (summary.verification !== "verified") {
|
|
114175
|
+
await new Promise((resolve25) => setTimeout(resolve25, 1e3));
|
|
114176
|
+
listItems = await readList();
|
|
114177
|
+
summary = summarizeNegativeKeywordCreate(keywords, response, listItems);
|
|
114178
|
+
}
|
|
114179
|
+
const nextQuery = `siluzan-tso ad keywords -a ${opts.account} --negative --json-out ./snap`;
|
|
114180
|
+
const detail = summary.verification === "verified" ? `\u5DF2\u6838\u9A8C\u6DFB\u52A0 ${summary.matchedCount} \u4E2A\u5426\u5B9A\u5173\u952E\u8BCD\uFF08${negativeKeywordLevelLabel[level]}\uFF09` : `\u8BF7\u6C42 ${keywords.length} \u4E2A\uFF0C\u5BF9\u4E0A ${summary.matchedCount} \u4E2A${summary.pending.length ? `\uFF0C\u5C1A\u672A\u6838\u9A8C\uFF1A${summary.pending.join("\u3001")}` : ""}`;
|
|
114181
|
+
const headline = writeVerificationHeadline(summary.verification, detail);
|
|
114182
|
+
if (summary.verification !== "verified") {
|
|
114183
|
+
console.error(`
|
|
114184
|
+
${headline}
|
|
114185
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}
|
|
114186
|
+
`);
|
|
114187
|
+
}
|
|
114188
|
+
const payload = {
|
|
114189
|
+
verification: summary.verification,
|
|
114190
|
+
requestedCount: keywords.length,
|
|
114191
|
+
matchedCount: summary.matchedCount,
|
|
114192
|
+
matched: summary.matched,
|
|
114193
|
+
pending: summary.pending,
|
|
114194
|
+
nextQuery,
|
|
114195
|
+
level,
|
|
114196
|
+
response
|
|
114197
|
+
};
|
|
114198
|
+
if (await emitCliJsonOrSnapshot(opts, {
|
|
114199
|
+
section: `ad-keyword-negative-create-${opts.account}`,
|
|
114200
|
+
commandLabel: "ad keyword-negative-create",
|
|
114201
|
+
commandHint: opts.account,
|
|
114202
|
+
payload,
|
|
114203
|
+
idSuffix: opts.account,
|
|
114204
|
+
summaryExtras: { verification: summary.verification, matchedCount: summary.matchedCount }
|
|
114205
|
+
})) {
|
|
114206
|
+
return;
|
|
114207
|
+
}
|
|
113963
114208
|
console.log(
|
|
113964
114209
|
`
|
|
113965
|
-
|
|
114210
|
+
${headline}${summary.verification === "verified" ? "" : `
|
|
114211
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}`}
|
|
113966
114212
|
`
|
|
113967
114213
|
);
|
|
113968
114214
|
}
|
|
@@ -114058,7 +114304,7 @@ async function runAdKeywordEdit(opts) {
|
|
|
114058
114304
|
currentId = returnedId;
|
|
114059
114305
|
console.log(
|
|
114060
114306
|
`
|
|
114061
|
-
\u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2\u8BF7\u4F7F\u7528\u65B0 id\u3002
|
|
114307
|
+
\u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF0CchangeKind=replace\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2\u8BF7\u4F7F\u7528\u65B0 id\u3002
|
|
114062
114308
|
`
|
|
114063
114309
|
);
|
|
114064
114310
|
}
|
|
@@ -114107,9 +114353,28 @@ ${mismatches.map((m) => ` - ${m}`).join("\n")}
|
|
|
114107
114353
|
process.exit(1);
|
|
114108
114354
|
}
|
|
114109
114355
|
}
|
|
114110
|
-
|
|
114111
|
-
|
|
114356
|
+
const verifiedFields = [];
|
|
114357
|
+
if (opts.finalUrl !== void 0) verifiedFields.push("finalURL");
|
|
114358
|
+
if (opts.maxCpc !== void 0) verifiedFields.push("maxCPC");
|
|
114359
|
+
if (opts.status !== void 0) verifiedFields.push("userStatusV2");
|
|
114360
|
+
const changeKind = currentId !== opts.id ? "replace" : "update";
|
|
114361
|
+
if (verifiedFields.length > 0) {
|
|
114362
|
+
console.log(
|
|
114363
|
+
`
|
|
114364
|
+
\u2705 \u5173\u952E\u8BCD\u5DF2\u6838\u9A8C\uFF08${verifiedFields.join("\u3001")}\uFF1BchangeKind=${changeKind}${changeKind === "replace" ? `\uFF0C\u65B0 id ${currentId}` : ""}\uFF09
|
|
114365
|
+
`
|
|
114366
|
+
);
|
|
114367
|
+
} else if (changeKind === "replace") {
|
|
114368
|
+
console.log(
|
|
114369
|
+
`
|
|
114370
|
+
\u2705 \u5173\u952E\u8BCD\u5DF2\u63D0\u4EA4\uFF08changeKind=replace\uFF0C\u65B0 id ${currentId}\uFF09\u3002\u8BCD\u9762\u6216\u5339\u914D\u7C7B\u578B\u4F1A\u65B0\u5EFA\u5E76\u79FB\u9664\u65E7\u8BCD\uFF0C\u8BF7\u7528 ad keywords \u6838\u5BF9\u65B0 id\u3002
|
|
114371
|
+
`
|
|
114372
|
+
);
|
|
114373
|
+
} else {
|
|
114374
|
+
console.log(`
|
|
114375
|
+
\u2705 \u5173\u952E\u8BCD\u5DF2\u63D0\u4EA4\uFF08changeKind=update\uFF09\u3002\u8BF7\u7528 ad keywords \u6838\u5BF9\u8BCD\u9762\u3002
|
|
114112
114376
|
`);
|
|
114377
|
+
}
|
|
114113
114378
|
}
|
|
114114
114379
|
async function runAdKeywordStatus(opts) {
|
|
114115
114380
|
opts = withResolvedAdAccount(opts);
|
|
@@ -115626,6 +115891,7 @@ async function resolveCampaignCustomerName(cfg, config, verbose) {
|
|
|
115626
115891
|
}
|
|
115627
115892
|
|
|
115628
115893
|
// src/commands/ad/campaign.ts
|
|
115894
|
+
var CAMPAIGN_READBACK_WAIT_MS = 1e3;
|
|
115629
115895
|
function formatGoogleCampaignListStatus(row) {
|
|
115630
115896
|
const raw = row.statusV2;
|
|
115631
115897
|
if (raw == null || String(raw).trim() === "") return "-";
|
|
@@ -115735,6 +116001,108 @@ async function runAdCampaigns(opts) {
|
|
|
115735
116001
|
printCliTable(rows, cols);
|
|
115736
116002
|
console.log();
|
|
115737
116003
|
}
|
|
116004
|
+
function unwrapCampaign(res) {
|
|
116005
|
+
if (res && typeof res === "object" && "data" in res && res.data && typeof res.data === "object") {
|
|
116006
|
+
return res.data;
|
|
116007
|
+
}
|
|
116008
|
+
return res;
|
|
116009
|
+
}
|
|
116010
|
+
async function fetchCampaignRecord(detailUrl, config, verbose) {
|
|
116011
|
+
const res = await apiFetch2(
|
|
116012
|
+
detailUrl,
|
|
116013
|
+
config,
|
|
116014
|
+
{},
|
|
116015
|
+
verbose
|
|
116016
|
+
);
|
|
116017
|
+
return unwrapCampaign(res);
|
|
116018
|
+
}
|
|
116019
|
+
async function verifyCampaignAfterPut(detailUrl, config, expected, verbose) {
|
|
116020
|
+
const readOnce = async () => {
|
|
116021
|
+
try {
|
|
116022
|
+
const after = await fetchCampaignRecord(detailUrl, config, verbose);
|
|
116023
|
+
return evaluateCampaignReadback(after, expected, false);
|
|
116024
|
+
} catch {
|
|
116025
|
+
return evaluateCampaignReadback(void 0, expected, true);
|
|
116026
|
+
}
|
|
116027
|
+
};
|
|
116028
|
+
let result = await readOnce();
|
|
116029
|
+
if (result.verification !== "verified") {
|
|
116030
|
+
await new Promise((resolve25) => setTimeout(resolve25, CAMPAIGN_READBACK_WAIT_MS));
|
|
116031
|
+
result = await readOnce();
|
|
116032
|
+
}
|
|
116033
|
+
return result;
|
|
116034
|
+
}
|
|
116035
|
+
async function reportFailedCampaignPut(opts) {
|
|
116036
|
+
const message = opts.err instanceof Error ? opts.err.message : String(opts.err);
|
|
116037
|
+
let budgetCentsAfter;
|
|
116038
|
+
let readFailed = false;
|
|
116039
|
+
try {
|
|
116040
|
+
const after = await fetchCampaignRecord(opts.detailUrl, opts.config, opts.verbose);
|
|
116041
|
+
budgetCentsAfter = readCampaignBudgetCents(after);
|
|
116042
|
+
} catch {
|
|
116043
|
+
readFailed = true;
|
|
116044
|
+
}
|
|
116045
|
+
const judged = judgeBudgetAfterFailedPut({
|
|
116046
|
+
readFailed,
|
|
116047
|
+
budgetCentsBefore: opts.budgetCentsBefore,
|
|
116048
|
+
budgetCentsAfter
|
|
116049
|
+
});
|
|
116050
|
+
if (judged.kind === "unchanged") {
|
|
116051
|
+
console.error(`
|
|
116052
|
+
\u274C ${opts.failureLabel}\uFF1A${message}
|
|
116053
|
+
`);
|
|
116054
|
+
process.exit(1);
|
|
116055
|
+
}
|
|
116056
|
+
const hint = judged.kind === "partial" ? "\u56DE\u8BFB\u9884\u7B97\u5DF2\u76F8\u5BF9\u63D0\u4EA4\u524D\u53D8\u5316\u3002\u9884\u7B97\u53EF\u80FD\u5DF2\u5199\u5165\uFF0C\u7CFB\u5217\u5176\u4F59\u66F4\u65B0\u672A\u5B8C\u6210\u3002\u4E0D\u8981\u6574\u5305\u91CD\u63D0\u3002" : "\u56DE\u8BFB\u5931\u8D25\u6216\u65E0\u6CD5\u5BF9\u6BD4\u9884\u7B97\uFF0C\u5199\u5165\u7ED3\u679C\u672A\u77E5\u3002\u4E0D\u8981\u6574\u5305\u91CD\u63D0\u3002";
|
|
116057
|
+
await reportCampaignWrite({
|
|
116058
|
+
jsonOut: opts.jsonOut,
|
|
116059
|
+
account: opts.account,
|
|
116060
|
+
id: opts.id,
|
|
116061
|
+
section: opts.section,
|
|
116062
|
+
commandLabel: opts.commandLabel,
|
|
116063
|
+
verification: judged.verification,
|
|
116064
|
+
checks: judged.checks,
|
|
116065
|
+
detail: `${opts.failureLabel}\uFF1A${message}\u3002${hint}`
|
|
116066
|
+
});
|
|
116067
|
+
}
|
|
116068
|
+
async function reportCampaignWrite(opts) {
|
|
116069
|
+
const nextQuery = `siluzan-tso ad campaigns -a ${opts.account} --json-out ./snap`;
|
|
116070
|
+
const unmatched = describeUnmatchedChecks(opts.checks);
|
|
116071
|
+
const text2 = opts.verification === "verified" ? opts.detail : unmatched || opts.detail;
|
|
116072
|
+
const headline = writeVerificationHeadline(opts.verification, text2);
|
|
116073
|
+
if (opts.verification !== "verified") {
|
|
116074
|
+
console.error(`
|
|
116075
|
+
${headline}
|
|
116076
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}
|
|
116077
|
+
`);
|
|
116078
|
+
}
|
|
116079
|
+
const payload = {
|
|
116080
|
+
id: opts.id,
|
|
116081
|
+
writeScope: CAMPAIGN_WRITE_SCOPE,
|
|
116082
|
+
verification: opts.verification,
|
|
116083
|
+
nextQuery,
|
|
116084
|
+
checks: opts.checks
|
|
116085
|
+
};
|
|
116086
|
+
if (await emitCliJsonOrSnapshot(
|
|
116087
|
+
{ jsonOut: opts.jsonOut },
|
|
116088
|
+
{
|
|
116089
|
+
section: opts.section,
|
|
116090
|
+
commandLabel: opts.commandLabel,
|
|
116091
|
+
commandHint: opts.account,
|
|
116092
|
+
payload,
|
|
116093
|
+
idSuffix: `${opts.account}-${opts.id}`,
|
|
116094
|
+
summaryExtras: { verification: opts.verification, writeScope: CAMPAIGN_WRITE_SCOPE }
|
|
116095
|
+
}
|
|
116096
|
+
)) {
|
|
116097
|
+
return;
|
|
116098
|
+
}
|
|
116099
|
+
console.log(
|
|
116100
|
+
`
|
|
116101
|
+
${headline}${opts.verification === "verified" ? "" : `
|
|
116102
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}`}
|
|
116103
|
+
`
|
|
116104
|
+
);
|
|
116105
|
+
}
|
|
115738
116106
|
async function runAdCampaignStatus(opts) {
|
|
115739
116107
|
opts = withResolvedAdAccount(opts);
|
|
115740
116108
|
const config = loadConfig(opts.token);
|
|
@@ -115761,14 +116129,41 @@ async function runAdCampaignStatus(opts) {
|
|
|
115761
116129
|
try {
|
|
115762
116130
|
await apiFetch2(detailUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
115763
116131
|
} catch (err) {
|
|
115764
|
-
|
|
115765
|
-
|
|
115766
|
-
|
|
115767
|
-
|
|
116132
|
+
await reportFailedCampaignPut({
|
|
116133
|
+
err,
|
|
116134
|
+
detailUrl,
|
|
116135
|
+
config,
|
|
116136
|
+
verbose: opts.verbose,
|
|
116137
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116138
|
+
jsonOut: opts.jsonOut,
|
|
116139
|
+
account: opts.account,
|
|
116140
|
+
id: opts.id,
|
|
116141
|
+
section: "ad-campaign-status",
|
|
116142
|
+
commandLabel: "ad campaign-status",
|
|
116143
|
+
failureLabel: "\u72B6\u6001\u66F4\u65B0\u5931\u8D25"
|
|
116144
|
+
});
|
|
116145
|
+
return;
|
|
115768
116146
|
}
|
|
115769
|
-
|
|
115770
|
-
|
|
115771
|
-
|
|
116147
|
+
const judged = await verifyCampaignAfterPut(
|
|
116148
|
+
detailUrl,
|
|
116149
|
+
config,
|
|
116150
|
+
{
|
|
116151
|
+
statusV2: opts.status,
|
|
116152
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116153
|
+
budgetWasRequested: false
|
|
116154
|
+
},
|
|
116155
|
+
opts.verbose
|
|
116156
|
+
);
|
|
116157
|
+
await reportCampaignWrite({
|
|
116158
|
+
jsonOut: opts.jsonOut,
|
|
116159
|
+
account: opts.account,
|
|
116160
|
+
id: opts.id,
|
|
116161
|
+
section: "ad-campaign-status",
|
|
116162
|
+
commandLabel: "ad campaign-status",
|
|
116163
|
+
verification: judged.verification,
|
|
116164
|
+
checks: judged.checks,
|
|
116165
|
+
detail: `\u5E7F\u544A\u7CFB\u5217 ${opts.id} \u72B6\u6001\u5DF2\u6838\u9A8C\u4E3A ${opts.status}`
|
|
116166
|
+
});
|
|
115772
116167
|
}
|
|
115773
116168
|
async function runAdCampaignDelete(opts) {
|
|
115774
116169
|
opts = withResolvedAdAccount(opts);
|
|
@@ -116069,13 +116464,29 @@ async function runAdCampaignEdit(opts) {
|
|
|
116069
116464
|
console.error("\n\u274C \u8BF7\u81F3\u5C11\u6307\u5B9A\u4E00\u4E2A\u4FEE\u6539\u9879\uFF08--name / --budget / --bidding / \u2026\uFF09\n");
|
|
116070
116465
|
process.exit(1);
|
|
116071
116466
|
}
|
|
116467
|
+
if (opts.budget !== void 0 && readBudgetSharedFlag(campaign) === true) {
|
|
116468
|
+
console.error(
|
|
116469
|
+
"\n\u274C \u8BE5\u7CFB\u5217\u6807\u8BB0\u4E3A\u5171\u4EAB\u9884\u7B97\uFF08budgetShared=true\uFF09\u3002campaign-edit \u4E0D\u4F1A\u53EA\u6539\u672C\u7CFB\u5217\u7684\u5171\u4EAB\u9884\u7B97\u91D1\u989D\uFF0C\u672C\u6B21\u672A\u63D0\u4EA4\u3002\n"
|
|
116470
|
+
);
|
|
116471
|
+
process.exit(1);
|
|
116472
|
+
}
|
|
116072
116473
|
try {
|
|
116073
116474
|
await apiFetch2(detailUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
116074
116475
|
} catch (err) {
|
|
116075
|
-
|
|
116076
|
-
|
|
116077
|
-
|
|
116078
|
-
|
|
116476
|
+
await reportFailedCampaignPut({
|
|
116477
|
+
err,
|
|
116478
|
+
detailUrl,
|
|
116479
|
+
config,
|
|
116480
|
+
verbose: opts.verbose,
|
|
116481
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116482
|
+
jsonOut: opts.jsonOut,
|
|
116483
|
+
account: opts.account,
|
|
116484
|
+
id: opts.id,
|
|
116485
|
+
section: "ad-campaign-edit",
|
|
116486
|
+
commandLabel: "ad campaign-edit",
|
|
116487
|
+
failureLabel: "\u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\u5931\u8D25"
|
|
116488
|
+
});
|
|
116489
|
+
return;
|
|
116079
116490
|
}
|
|
116080
116491
|
const parts = [];
|
|
116081
116492
|
if (opts.name !== void 0) parts.push(`\u540D\u79F0\u2192${opts.name}`);
|
|
@@ -116085,9 +116496,34 @@ async function runAdCampaignEdit(opts) {
|
|
|
116085
116496
|
if (opts.targetSpendBidCeiling !== void 0) parts.push(`CPC\u4E0A\u9650\u2192${opts.targetSpendBidCeiling}`);
|
|
116086
116497
|
if (opts.targetCpa !== void 0) parts.push(`\u76EE\u6807CPA\u2192${opts.targetCpa}`);
|
|
116087
116498
|
if (opts.targetRoas !== void 0) parts.push(`\u76EE\u6807ROAS\u2192${opts.targetRoas}`);
|
|
116088
|
-
|
|
116089
|
-
|
|
116090
|
-
|
|
116499
|
+
const judged = await verifyCampaignAfterPut(
|
|
116500
|
+
detailUrl,
|
|
116501
|
+
config,
|
|
116502
|
+
{
|
|
116503
|
+
name: opts.name,
|
|
116504
|
+
budgetCents: opts.budget !== void 0 ? toCentAmount(opts.budget) : void 0,
|
|
116505
|
+
budgetWasRequested: opts.budget !== void 0,
|
|
116506
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116507
|
+
biddingStrategyTypeV2: opts.biddingStrategy?.toUpperCase(),
|
|
116508
|
+
targetSpendBidCeilingCents: opts.targetSpendBidCeiling !== void 0 ? toCentAmount(opts.targetSpendBidCeiling) : void 0,
|
|
116509
|
+
targetCpaCents: opts.targetCpa !== void 0 ? toCentAmount(opts.targetCpa) : void 0,
|
|
116510
|
+
targetRoas: opts.targetRoas,
|
|
116511
|
+
manualCpcEnhanced: opts.manualCpcEnhanced,
|
|
116512
|
+
targetSearchNetwork: opts.targetSearchNetwork,
|
|
116513
|
+
targetContentNetwork: opts.targetContentNetwork
|
|
116514
|
+
},
|
|
116515
|
+
opts.verbose
|
|
116516
|
+
);
|
|
116517
|
+
await reportCampaignWrite({
|
|
116518
|
+
jsonOut: opts.jsonOut,
|
|
116519
|
+
account: opts.account,
|
|
116520
|
+
id: opts.id,
|
|
116521
|
+
section: "ad-campaign-edit",
|
|
116522
|
+
commandLabel: "ad campaign-edit",
|
|
116523
|
+
verification: judged.verification,
|
|
116524
|
+
checks: judged.checks,
|
|
116525
|
+
detail: `\u5E7F\u544A\u7CFB\u5217 ${opts.id} \u5DF2\u6838\u9A8C${parts.length ? `\uFF08${parts.join("\uFF0C")}\uFF09` : ""}`
|
|
116526
|
+
});
|
|
116091
116527
|
}
|
|
116092
116528
|
|
|
116093
116529
|
// src/commands/ad/adgroup.ts
|
|
@@ -123928,7 +124364,7 @@ function register21(program2) {
|
|
|
123928
124364
|
});
|
|
123929
124365
|
}
|
|
123930
124366
|
);
|
|
123931
|
-
adCmd.command("campaign-status").description("\u5207\u6362\u5E7F\u544A\u7CFB\u5217\u72B6\u6001\uFF08Enabled / Paused\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001\uFF1AEnabled | Paused").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124367
|
+
adCmd.command("campaign-status").description("\u5207\u6362\u5E7F\u544A\u7CFB\u5217\u72B6\u6001\uFF08Enabled / Paused\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001\uFF1AEnabled | Paused").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=gateway-full-put\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
123932
124368
|
async (opts) => {
|
|
123933
124369
|
if (opts.status !== "Enabled" && opts.status !== "Paused") {
|
|
123934
124370
|
console.error("\n\u274C --status \u53EA\u63A5\u53D7 Enabled \u6216 Paused\n");
|
|
@@ -123939,6 +124375,7 @@ function register21(program2) {
|
|
|
123939
124375
|
account: opts.account,
|
|
123940
124376
|
id: opts.id,
|
|
123941
124377
|
status: opts.status,
|
|
124378
|
+
jsonOut: opts.jsonOut,
|
|
123942
124379
|
verbose: opts.verbose
|
|
123943
124380
|
});
|
|
123944
124381
|
}
|
|
@@ -124124,7 +124561,7 @@ function register21(program2) {
|
|
|
124124
124561
|
).option(
|
|
124125
124562
|
"--adgroup-id <id>",
|
|
124126
124563
|
"\u5E7F\u544A\u7EC4 ID\uFF08level=AdGroup \u5FC5\u4F20\uFF1B\u672A\u4F20 --level \u65F6\u6307\u5B9A\u5373\u89C6\u4E3A AdGroup \u5C42\u7EA7\uFF09"
|
|
124127
|
-
).option("--adgroup-name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124564
|
+
).option("--adgroup-name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08\u6210\u529F\u6761\u6570\u6765\u81EA\u56DE\u8BFB\uFF0C\u4E0D\u662F\u8BF7\u6C42\u6761\u6570\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124128
124565
|
async (opts) => {
|
|
124129
124566
|
if (opts.level && !["Account", "Campaign", "AdGroup"].includes(opts.level)) {
|
|
124130
124567
|
console.error("\n\u274C --level \u53EA\u63A5\u53D7 Account | Campaign | AdGroup\n");
|
|
@@ -124141,6 +124578,7 @@ function register21(program2) {
|
|
|
124141
124578
|
keywords: opts.keywords,
|
|
124142
124579
|
keywordsFile: opts.keywordsFile,
|
|
124143
124580
|
matchType: parseOptionalMatchType(opts.matchType),
|
|
124581
|
+
jsonOut: opts.jsonOut,
|
|
124144
124582
|
verbose: opts.verbose
|
|
124145
124583
|
});
|
|
124146
124584
|
}
|
|
@@ -124455,7 +124893,7 @@ function register21(program2) {
|
|
|
124455
124893
|
"--search-network <bool>",
|
|
124456
124894
|
"\u6295\u653E\u5230 Google \u641C\u7D22\uFF1Atrue | false",
|
|
124457
124895
|
(v) => v === "true"
|
|
124458
|
-
).option("--content-network <bool>", "\u6295\u653E\u5230\u5C55\u793A\u7F51\u7EDC\uFF1Atrue | false", (v) => v === "true").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124896
|
+
).option("--content-network <bool>", "\u6295\u653E\u5230\u5C55\u793A\u7F51\u7EDC\uFF1Atrue | false", (v) => v === "true").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=gateway-full-put\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124459
124897
|
async (opts) => {
|
|
124460
124898
|
await runAdCampaignEdit({
|
|
124461
124899
|
token: opts.token,
|
|
@@ -124470,6 +124908,7 @@ function register21(program2) {
|
|
|
124470
124908
|
manualCpcEnhanced: opts.manualEcpc,
|
|
124471
124909
|
targetSearchNetwork: opts.searchNetwork,
|
|
124472
124910
|
targetContentNetwork: opts.contentNetwork,
|
|
124911
|
+
jsonOut: opts.jsonOut,
|
|
124473
124912
|
verbose: opts.verbose
|
|
124474
124913
|
});
|
|
124475
124914
|
}
|
|
@@ -125193,13 +125632,14 @@ function failMetaAd(action, err) {
|
|
|
125193
125632
|
`);
|
|
125194
125633
|
process.exit(1);
|
|
125195
125634
|
}
|
|
125196
|
-
async function emitMetaJson(opts, section, commandLabel, payload, idSuffix) {
|
|
125635
|
+
async function emitMetaJson(opts, section, commandLabel, payload, idSuffix, summaryExtras) {
|
|
125197
125636
|
return emitCliJsonOrSnapshot(opts, {
|
|
125198
125637
|
section,
|
|
125199
125638
|
commandLabel,
|
|
125200
125639
|
commandHint: idSuffix,
|
|
125201
125640
|
payload,
|
|
125202
|
-
idSuffix
|
|
125641
|
+
idSuffix,
|
|
125642
|
+
summaryExtras
|
|
125203
125643
|
});
|
|
125204
125644
|
}
|
|
125205
125645
|
function isHttpUrl(value) {
|
|
@@ -125480,6 +125920,10 @@ function buildUpdateAdSetBody(opts) {
|
|
|
125480
125920
|
if (opts.startTime != null) body.start_time = opts.startTime;
|
|
125481
125921
|
if (opts.endTime != null) body.end_time = opts.endTime;
|
|
125482
125922
|
if (opts.status != null) body.status = opts.status;
|
|
125923
|
+
if (opts.targeting && Object.keys(opts.targeting).length > 0) {
|
|
125924
|
+
body.targeting = opts.targeting;
|
|
125925
|
+
return body;
|
|
125926
|
+
}
|
|
125483
125927
|
const hasTargeting = opts.countries != null || opts.ageMin != null || opts.ageMax != null || opts.genders != null || opts.flexibleSpec != null || opts.advantageAudience != null;
|
|
125484
125928
|
if (hasTargeting) {
|
|
125485
125929
|
const targeting = {};
|
|
@@ -125490,17 +125934,221 @@ function buildUpdateAdSetBody(opts) {
|
|
|
125490
125934
|
if (opts.ageMax != null) targeting.age_max = opts.ageMax;
|
|
125491
125935
|
if (opts.genders) targeting.genders = opts.genders;
|
|
125492
125936
|
if (opts.flexibleSpec != null) targeting.flexible_spec = opts.flexibleSpec;
|
|
125493
|
-
|
|
125494
|
-
targeting,
|
|
125495
|
-
|
|
125496
|
-
advantageAudience: opts.advantageAudience,
|
|
125497
|
-
flexibleSpec: opts.flexibleSpec
|
|
125498
|
-
})
|
|
125499
|
-
);
|
|
125937
|
+
if (opts.advantageAudience === 0 || opts.advantageAudience === 1) {
|
|
125938
|
+
applyLeadAdTargetingAutomation(targeting, opts.advantageAudience);
|
|
125939
|
+
}
|
|
125500
125940
|
body.targeting = targeting;
|
|
125501
125941
|
}
|
|
125502
125942
|
return body;
|
|
125503
125943
|
}
|
|
125944
|
+
var ADSET_TARGETING_TOP_KEYS = /* @__PURE__ */ new Set([
|
|
125945
|
+
"geo_locations",
|
|
125946
|
+
"age_min",
|
|
125947
|
+
"age_max",
|
|
125948
|
+
"genders",
|
|
125949
|
+
"flexible_spec",
|
|
125950
|
+
"targeting_automation"
|
|
125951
|
+
]);
|
|
125952
|
+
function isPlainObject(value) {
|
|
125953
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
125954
|
+
}
|
|
125955
|
+
function readFiniteNumber(value) {
|
|
125956
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
125957
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
125958
|
+
const n = Number(value);
|
|
125959
|
+
if (Number.isFinite(n)) return n;
|
|
125960
|
+
}
|
|
125961
|
+
return void 0;
|
|
125962
|
+
}
|
|
125963
|
+
function upperUnique(list) {
|
|
125964
|
+
const seen = /* @__PURE__ */ new Set();
|
|
125965
|
+
const out = [];
|
|
125966
|
+
for (const raw of list) {
|
|
125967
|
+
const code = raw.trim().toUpperCase();
|
|
125968
|
+
if (!code || seen.has(code)) continue;
|
|
125969
|
+
seen.add(code);
|
|
125970
|
+
out.push(code);
|
|
125971
|
+
}
|
|
125972
|
+
return out;
|
|
125973
|
+
}
|
|
125974
|
+
function readCountries(targeting) {
|
|
125975
|
+
const geo = targeting?.["geo_locations"];
|
|
125976
|
+
if (!isPlainObject(geo) || !Array.isArray(geo["countries"])) return void 0;
|
|
125977
|
+
const countries = upperUnique(geo["countries"].map((c) => String(c)));
|
|
125978
|
+
return countries.length > 0 ? countries : void 0;
|
|
125979
|
+
}
|
|
125980
|
+
function readAdvantage(targeting) {
|
|
125981
|
+
const auto = targeting?.["targeting_automation"];
|
|
125982
|
+
if (!isPlainObject(auto)) return void 0;
|
|
125983
|
+
const value = readFiniteNumber(auto["advantage_audience"]);
|
|
125984
|
+
if (value === 0 || value === 1) return value;
|
|
125985
|
+
return void 0;
|
|
125986
|
+
}
|
|
125987
|
+
function unsupportedTargetingKeys(current) {
|
|
125988
|
+
if (!current) return [];
|
|
125989
|
+
const keys = [];
|
|
125990
|
+
for (const key of Object.keys(current)) {
|
|
125991
|
+
if (!ADSET_TARGETING_TOP_KEYS.has(key)) keys.push(key);
|
|
125992
|
+
}
|
|
125993
|
+
const geo = current["geo_locations"];
|
|
125994
|
+
if (isPlainObject(geo)) {
|
|
125995
|
+
for (const key of Object.keys(geo)) {
|
|
125996
|
+
if (key !== "countries") keys.push(`geo_locations.${key}`);
|
|
125997
|
+
}
|
|
125998
|
+
}
|
|
125999
|
+
const auto = current["targeting_automation"];
|
|
126000
|
+
if (isPlainObject(auto)) {
|
|
126001
|
+
for (const key of Object.keys(auto)) {
|
|
126002
|
+
if (key !== "advantage_audience") keys.push(`targeting_automation.${key}`);
|
|
126003
|
+
}
|
|
126004
|
+
}
|
|
126005
|
+
return keys;
|
|
126006
|
+
}
|
|
126007
|
+
function snapshotSupported(targeting) {
|
|
126008
|
+
const snap = {};
|
|
126009
|
+
if (!targeting) return snap;
|
|
126010
|
+
const countries = readCountries(targeting);
|
|
126011
|
+
if (countries) snap.countries = countries;
|
|
126012
|
+
const ageMin = readFiniteNumber(targeting["age_min"]);
|
|
126013
|
+
const ageMax = readFiniteNumber(targeting["age_max"]);
|
|
126014
|
+
if (ageMin !== void 0) snap.age_min = ageMin;
|
|
126015
|
+
if (ageMax !== void 0) snap.age_max = ageMax;
|
|
126016
|
+
if (Array.isArray(targeting["genders"])) snap.genders = targeting["genders"];
|
|
126017
|
+
if (targeting["flexible_spec"] !== void 0) snap.flexible_spec = targeting["flexible_spec"];
|
|
126018
|
+
const advantage = readAdvantage(targeting);
|
|
126019
|
+
if (advantage !== void 0) snap.advantage_audience = advantage;
|
|
126020
|
+
return snap;
|
|
126021
|
+
}
|
|
126022
|
+
function diffSnapshots(before, after) {
|
|
126023
|
+
const fields = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
126024
|
+
const changes = [];
|
|
126025
|
+
for (const field of fields) {
|
|
126026
|
+
const from = before[field];
|
|
126027
|
+
const to = after[field];
|
|
126028
|
+
const changed = JSON.stringify(from) !== JSON.stringify(to);
|
|
126029
|
+
changes.push({ field, from: from ?? null, to: to ?? null, changed });
|
|
126030
|
+
}
|
|
126031
|
+
return changes;
|
|
126032
|
+
}
|
|
126033
|
+
function mergeAdSetTargeting(current, patch, opts) {
|
|
126034
|
+
const source = current ?? void 0;
|
|
126035
|
+
const unsupported = unsupportedTargetingKeys(source);
|
|
126036
|
+
if (unsupported.length > 0 && !opts?.allowDropUnsupported) {
|
|
126037
|
+
return { ok: false, reason: "unsupported", unsupportedKeys: unsupported };
|
|
126038
|
+
}
|
|
126039
|
+
const before = snapshotSupported(source);
|
|
126040
|
+
const next = {};
|
|
126041
|
+
let countries = readCountries(source);
|
|
126042
|
+
if (patch.countries) {
|
|
126043
|
+
const incoming = upperUnique(patch.countries);
|
|
126044
|
+
countries = (patch.countriesMode ?? "replace") === "append" && countries ? upperUnique([...countries, ...incoming]) : incoming;
|
|
126045
|
+
}
|
|
126046
|
+
if (countries && countries.length > 0) {
|
|
126047
|
+
next.geo_locations = { countries };
|
|
126048
|
+
}
|
|
126049
|
+
const ageMin = patch.ageMin != null ? patch.ageMin : readFiniteNumber(source?.["age_min"]);
|
|
126050
|
+
const ageMax = patch.ageMax != null ? patch.ageMax : readFiniteNumber(source?.["age_max"]);
|
|
126051
|
+
if (ageMin !== void 0) next.age_min = ageMin;
|
|
126052
|
+
if (ageMax !== void 0) next.age_max = ageMax;
|
|
126053
|
+
const genders = patch.genders ?? source?.["genders"];
|
|
126054
|
+
if (Array.isArray(genders)) next.genders = genders;
|
|
126055
|
+
const flexible = patch.flexibleSpec ?? source?.["flexible_spec"];
|
|
126056
|
+
if (flexible !== void 0 && Array.isArray(flexible)) next.flexible_spec = flexible;
|
|
126057
|
+
const explicitAdvantage = patch.advantageAudience === 0 || patch.advantageAudience === 1 ? patch.advantageAudience : void 0;
|
|
126058
|
+
const advantage = explicitAdvantage ?? readAdvantage(source);
|
|
126059
|
+
if (advantage === void 0) {
|
|
126060
|
+
return { ok: false, reason: "advantage_unset" };
|
|
126061
|
+
}
|
|
126062
|
+
next.targeting_automation = { advantage_audience: advantage };
|
|
126063
|
+
return {
|
|
126064
|
+
ok: true,
|
|
126065
|
+
targeting: next,
|
|
126066
|
+
changes: diffSnapshots(before, snapshotSupported(next)),
|
|
126067
|
+
droppedKeys: opts?.allowDropUnsupported ? unsupported : []
|
|
126068
|
+
};
|
|
126069
|
+
}
|
|
126070
|
+
function sortedCountries(value) {
|
|
126071
|
+
if (!Array.isArray(value)) return [];
|
|
126072
|
+
return upperUnique(value.map((c) => String(c))).sort();
|
|
126073
|
+
}
|
|
126074
|
+
function targetingReadbackChecks(expected, actual) {
|
|
126075
|
+
const checks = [];
|
|
126076
|
+
const parentRead = actual !== void 0;
|
|
126077
|
+
if (expected["geo_locations"] !== void 0) {
|
|
126078
|
+
const exp = sortedCountries(
|
|
126079
|
+
isPlainObject(expected["geo_locations"]) ? expected["geo_locations"]["countries"] : []
|
|
126080
|
+
);
|
|
126081
|
+
const act = sortedCountries(
|
|
126082
|
+
actual && isPlainObject(actual["geo_locations"]) ? actual["geo_locations"]["countries"] : []
|
|
126083
|
+
);
|
|
126084
|
+
checks.push(fieldCheck("targeting.countries", exp, parentRead ? act : void 0, parentRead));
|
|
126085
|
+
}
|
|
126086
|
+
for (const key of ["age_min", "age_max"]) {
|
|
126087
|
+
if (expected[key] === void 0) continue;
|
|
126088
|
+
const raw = actual?.[key];
|
|
126089
|
+
checks.push(
|
|
126090
|
+
fieldCheck(
|
|
126091
|
+
`targeting.${key}`,
|
|
126092
|
+
expected[key],
|
|
126093
|
+
parentRead ? readFiniteNumber(raw) : void 0,
|
|
126094
|
+
parentRead
|
|
126095
|
+
)
|
|
126096
|
+
);
|
|
126097
|
+
}
|
|
126098
|
+
if (expected["genders"] !== void 0) {
|
|
126099
|
+
const exp = Array.isArray(expected["genders"]) ? [...expected["genders"]].sort() : [];
|
|
126100
|
+
const act = actual && Array.isArray(actual["genders"]) ? [...actual["genders"]].sort() : [];
|
|
126101
|
+
checks.push(fieldCheck("targeting.genders", exp, parentRead ? act : void 0, parentRead));
|
|
126102
|
+
}
|
|
126103
|
+
if (expected["flexible_spec"] !== void 0) {
|
|
126104
|
+
const exp = expected["flexible_spec"];
|
|
126105
|
+
const act = actual?.["flexible_spec"];
|
|
126106
|
+
const match = JSON.stringify(exp) === JSON.stringify(act);
|
|
126107
|
+
checks.push({
|
|
126108
|
+
field: "targeting.flexible_spec",
|
|
126109
|
+
expected: exp,
|
|
126110
|
+
actual: parentRead ? act : void 0,
|
|
126111
|
+
state: !parentRead ? "unread" : match ? "match" : "mismatch"
|
|
126112
|
+
});
|
|
126113
|
+
}
|
|
126114
|
+
if (isPlainObject(expected["targeting_automation"])) {
|
|
126115
|
+
const exp = expected["targeting_automation"]["advantage_audience"];
|
|
126116
|
+
const act = isPlainObject(actual?.["targeting_automation"]) ? readFiniteNumber(actual["targeting_automation"]["advantage_audience"]) : void 0;
|
|
126117
|
+
checks.push(fieldCheck("targeting.advantage_audience", exp, act, parentRead));
|
|
126118
|
+
}
|
|
126119
|
+
return checks;
|
|
126120
|
+
}
|
|
126121
|
+
var ADSET_SCALAR_READBACK_KEYS = [
|
|
126122
|
+
"name",
|
|
126123
|
+
"status",
|
|
126124
|
+
"daily_budget",
|
|
126125
|
+
"lifetime_budget",
|
|
126126
|
+
"optimization_goal",
|
|
126127
|
+
"bid_strategy",
|
|
126128
|
+
"bid_amount"
|
|
126129
|
+
];
|
|
126130
|
+
function evaluateAdSetEditReadback(sentBody, actual, readFailed) {
|
|
126131
|
+
if (readFailed || !actual) return { verification: "unknown", checks: [] };
|
|
126132
|
+
const checks = [];
|
|
126133
|
+
for (const key of ADSET_SCALAR_READBACK_KEYS) {
|
|
126134
|
+
if (sentBody[key] === void 0) continue;
|
|
126135
|
+
const present = Object.prototype.hasOwnProperty.call(actual, key);
|
|
126136
|
+
const money2 = key === "daily_budget" || key === "lifetime_budget" || key === "bid_amount";
|
|
126137
|
+
const expected = money2 ? String(sentBody[key]) : sentBody[key];
|
|
126138
|
+
const actualValue = money2 && actual[key] != null && actual[key] !== "" ? String(actual[key]) : actual[key];
|
|
126139
|
+
checks.push(fieldCheck(key, expected, actualValue, present));
|
|
126140
|
+
}
|
|
126141
|
+
if (isPlainObject(sentBody["targeting"])) {
|
|
126142
|
+
const actualTargeting = isPlainObject(actual["targeting"]) ? actual["targeting"] : void 0;
|
|
126143
|
+
checks.push(
|
|
126144
|
+
...targetingReadbackChecks(
|
|
126145
|
+
sentBody["targeting"],
|
|
126146
|
+
Object.prototype.hasOwnProperty.call(actual, "targeting") ? actualTargeting : void 0
|
|
126147
|
+
)
|
|
126148
|
+
);
|
|
126149
|
+
}
|
|
126150
|
+
return { verification: classifyFieldChecks(checks, false), checks };
|
|
126151
|
+
}
|
|
125504
126152
|
function buildUpdateAdBody(opts) {
|
|
125505
126153
|
const body = {};
|
|
125506
126154
|
if (opts.name != null) body.name = opts.name;
|
|
@@ -125713,6 +126361,9 @@ function runMetaLeadValidation(cfg) {
|
|
|
125713
126361
|
if (adset.bidAmount != null && !(Number.isFinite(adset.bidAmount) && adset.bidAmount > 0)) {
|
|
125714
126362
|
errors.push("adset.bidAmount \u987B\u4E3A\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09");
|
|
125715
126363
|
}
|
|
126364
|
+
if (adset.bidAmountOnRequired != null && !(Number.isFinite(adset.bidAmountOnRequired) && adset.bidAmountOnRequired > 0)) {
|
|
126365
|
+
errors.push("adset.bidAmountOnRequired \u987B\u4E3A\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09");
|
|
126366
|
+
}
|
|
125716
126367
|
if (adset.advantageAudience != null && adset.advantageAudience !== 0 && adset.advantageAudience !== 1) {
|
|
125717
126368
|
errors.push("adset.advantageAudience \u53EA\u5141\u8BB8 0 \u6216 1\uFF08targeting_automation.advantage_audience\uFF09");
|
|
125718
126369
|
}
|
|
@@ -125845,6 +126496,46 @@ async function runMetaAdValidate(opts) {
|
|
|
125845
126496
|
console.log("\n\u2705 Meta Lead \u914D\u7F6E\u6821\u9A8C\u901A\u8FC7\uFF08\u672C\u5730\uFF0C\u672A\u8C03\u7F51\u5173\uFF09\n");
|
|
125846
126497
|
}
|
|
125847
126498
|
|
|
126499
|
+
// src/commands/meta-ad/bid-recovery.ts
|
|
126500
|
+
function decideBidAmountRecovery(input) {
|
|
126501
|
+
if (input.bidAmountAlreadySet) return { action: "abort" };
|
|
126502
|
+
const authorized = input.authorizedBidAmount;
|
|
126503
|
+
if (typeof authorized === "number" && Number.isFinite(authorized) && authorized > 0) {
|
|
126504
|
+
return {
|
|
126505
|
+
action: "retry",
|
|
126506
|
+
bidStrategy: "LOWEST_COST_WITH_BID_CAP",
|
|
126507
|
+
bidAmount: authorized,
|
|
126508
|
+
recovery: {
|
|
126509
|
+
applied: true,
|
|
126510
|
+
fromStrategy: input.originalBidStrategy,
|
|
126511
|
+
toStrategy: "LOWEST_COST_WITH_BID_CAP",
|
|
126512
|
+
bidAmount: authorized
|
|
126513
|
+
}
|
|
126514
|
+
};
|
|
126515
|
+
}
|
|
126516
|
+
const budgetNote = [
|
|
126517
|
+
input.dailyBudget != null ? `\u65E5\u9884\u7B97 ${input.dailyBudget}` : "",
|
|
126518
|
+
input.lifetimeBudget != null ? `\u603B\u9884\u7B97 ${input.lifetimeBudget}` : "",
|
|
126519
|
+
input.currency ? input.currency : ""
|
|
126520
|
+
].filter(Boolean).join(" ");
|
|
126521
|
+
return {
|
|
126522
|
+
action: "needs_decision",
|
|
126523
|
+
needsDecision: {
|
|
126524
|
+
code: "BID_AMOUNT_REQUIRED",
|
|
126525
|
+
status: "needs_decision",
|
|
126526
|
+
campaignId: input.campaignId,
|
|
126527
|
+
originalBidStrategy: input.originalBidStrategy,
|
|
126528
|
+
proposedBidStrategy: "LOWEST_COST_WITH_BID_CAP",
|
|
126529
|
+
message: "\u8BE5\u6237\u8981\u6C42\u51FA\u4EF7\u4E0A\u9650\uFF08LOWEST_COST_WITH_BID_CAP\uFF09\u3002\u9884\u7B97\u91D1\u989D\u4E0D\u80FD\u5F53\u4F5C bid cap\u3002\u8BF7\u5355\u72EC\u586B\u5199\u51FA\u4EF7\u540E\u518D\u7EE7\u7EED\uFF1Acreate JSON \u7528 adset.bidAmountOnRequired\uFF0C\u6216 adset-create \u7528 --bid-amount-on-required\u3002" + (budgetNote ? ` \u5F53\u524D\u9884\u7B97\u4EC5\u4F5C\u4E0A\u4E0B\u6587\uFF08${budgetNote}\uFF09\uFF0C\u4E0D\u4F1A\u5199\u5165 bid_amount\u3002` : ""),
|
|
126530
|
+
budgetContext: {
|
|
126531
|
+
dailyBudget: input.dailyBudget,
|
|
126532
|
+
lifetimeBudget: input.lifetimeBudget,
|
|
126533
|
+
currency: input.currency
|
|
126534
|
+
}
|
|
126535
|
+
}
|
|
126536
|
+
};
|
|
126537
|
+
}
|
|
126538
|
+
|
|
125848
126539
|
// src/commands/meta-ad/create.ts
|
|
125849
126540
|
function resolveBesideConfig(configFile, p) {
|
|
125850
126541
|
if (isAbsolute7(p)) return p;
|
|
@@ -126011,23 +126702,47 @@ async function runMetaAdCreate(opts) {
|
|
|
126011
126702
|
);
|
|
126012
126703
|
} catch (firstErr) {
|
|
126013
126704
|
if (!isBidAmountRequired(firstErr) || cfg.adset.bidAmount != null) throw firstErr;
|
|
126014
|
-
const
|
|
126015
|
-
|
|
126016
|
-
|
|
126705
|
+
const decision = decideBidAmountRecovery({
|
|
126706
|
+
bidAmountAlreadySet: false,
|
|
126707
|
+
authorizedBidAmount: cfg.adset.bidAmountOnRequired,
|
|
126708
|
+
originalBidStrategy: baseAdset.bidStrategy,
|
|
126709
|
+
dailyBudget: cfg.campaign.dailyBudget ?? cfg.adset.dailyBudget,
|
|
126710
|
+
lifetimeBudget: cfg.campaign.lifetimeBudget ?? cfg.adset.lifetimeBudget,
|
|
126711
|
+
currency,
|
|
126712
|
+
campaignId: progress.campaignId
|
|
126713
|
+
});
|
|
126714
|
+
if (decision.action === "needs_decision") {
|
|
126715
|
+
progress.failedStep = "adset";
|
|
126716
|
+
progress.needsDecision = decision.needsDecision;
|
|
126717
|
+
progress.error = decision.needsDecision.message;
|
|
126718
|
+
await emitProgress();
|
|
126719
|
+
console.error(
|
|
126017
126720
|
`
|
|
126018
|
-
\u26A0\uFE0F
|
|
126721
|
+
\u26A0\uFE0F ${decision.needsDecision.message}
|
|
126722
|
+
\u5DF2\u5EFA\u6210\uFF1A${summarizeProgress(progress)}
|
|
126723
|
+
\u539F\u7B56\u7565\uFF1A${decision.needsDecision.originalBidStrategy ?? "\uFF08\u672A\u6307\u5B9A\uFF09"}
|
|
126724
|
+
\u62DF\u8BAE\u7B56\u7565\uFF1A${decision.needsDecision.proposedBidStrategy}
|
|
126725
|
+
\u672C\u6B21\u672A\u6539\u51FA\u4EF7\uFF0C\u672A\u518D\u6B21\u5EFA\u7EC4\u3002
|
|
126019
126726
|
`
|
|
126020
126727
|
);
|
|
126728
|
+
process.exit(2);
|
|
126021
126729
|
}
|
|
126730
|
+
if (decision.action !== "retry") throw firstErr;
|
|
126731
|
+
console.warn(
|
|
126732
|
+
`
|
|
126733
|
+
\u26A0\uFE0F \u5DF2\u6309\u4E8B\u524D\u6388\u6743\u7684 bid cap ${decision.bidAmount} ${currency} \u4EE5 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4\uFF08\u539F\u7B56\u7565 ${decision.recovery.fromStrategy ?? "\u672A\u6307\u5B9A"}\uFF09
|
|
126734
|
+
`
|
|
126735
|
+
);
|
|
126736
|
+
progress.recovery = decision.recovery;
|
|
126022
126737
|
data = await postJson(
|
|
126023
126738
|
adsetUrl,
|
|
126024
126739
|
config,
|
|
126025
126740
|
buildCreateAdSetBody({
|
|
126026
|
-
adset: { ...baseAdset, bidStrategy:
|
|
126741
|
+
adset: { ...baseAdset, bidStrategy: decision.bidStrategy },
|
|
126027
126742
|
campaignId: progress.campaignId,
|
|
126028
126743
|
pageId,
|
|
126029
126744
|
...money2,
|
|
126030
|
-
bidAmountMinor: toMinorUnitString(
|
|
126745
|
+
bidAmountMinor: toMinorUnitString(decision.bidAmount, currency)
|
|
126031
126746
|
}),
|
|
126032
126747
|
opts.verbose
|
|
126033
126748
|
);
|
|
@@ -126856,6 +127571,28 @@ async function runMetaAdCampaignStatus(opts) {
|
|
|
126856
127571
|
// src/commands/meta-ad/adset.ts
|
|
126857
127572
|
init_cli_table();
|
|
126858
127573
|
init_auth();
|
|
127574
|
+
var READBACK_WAIT_MS = 1e3;
|
|
127575
|
+
function parseCountriesMode(raw) {
|
|
127576
|
+
if (!raw || raw === "replace") return "replace";
|
|
127577
|
+
if (raw === "append") return "append";
|
|
127578
|
+
throw new Error("--countries-mode \u53EA\u5141\u8BB8 append \u6216 replace");
|
|
127579
|
+
}
|
|
127580
|
+
function isRecord2(value) {
|
|
127581
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
127582
|
+
}
|
|
127583
|
+
function formatTargetingChange(change) {
|
|
127584
|
+
const from = change.from == null ? "\uFF08\u65E0\uFF09" : JSON.stringify(change.from);
|
|
127585
|
+
const to = change.to == null ? "\uFF08\u65E0\uFF09" : JSON.stringify(change.to);
|
|
127586
|
+
return change.changed ? `${change.field}\uFF1A${from} \u2192 ${to}` : `${change.field}\uFF1A${to}\uFF08\u4FDD\u6301\uFF09`;
|
|
127587
|
+
}
|
|
127588
|
+
async function readAdSetForVerify(url, config, verbose) {
|
|
127589
|
+
try {
|
|
127590
|
+
const raw = await apiFetch2(url, config, {}, verbose);
|
|
127591
|
+
return { adset: raw, readFailed: false };
|
|
127592
|
+
} catch {
|
|
127593
|
+
return { readFailed: true };
|
|
127594
|
+
}
|
|
127595
|
+
}
|
|
126859
127596
|
function parseGenders(raw) {
|
|
126860
127597
|
if (!raw?.trim()) return void 0;
|
|
126861
127598
|
const nums = raw.split(",").map((s) => Number(s.trim()));
|
|
@@ -127002,14 +127739,37 @@ async function runMetaAdAdsetCreate(opts) {
|
|
|
127002
127739
|
if (!isBidAmountRequired(firstErr) || adset.bidAmount != null) {
|
|
127003
127740
|
failMetaAd("\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25", firstErr);
|
|
127004
127741
|
}
|
|
127005
|
-
const
|
|
127006
|
-
|
|
127007
|
-
|
|
127742
|
+
const decision = decideBidAmountRecovery({
|
|
127743
|
+
bidAmountAlreadySet: false,
|
|
127744
|
+
authorizedBidAmount: opts.bidAmountOnRequired,
|
|
127745
|
+
originalBidStrategy: adset.bidStrategy,
|
|
127746
|
+
dailyBudget: daily,
|
|
127747
|
+
lifetimeBudget: lifetime,
|
|
127748
|
+
currency,
|
|
127749
|
+
campaignId: opts.campaignId.trim()
|
|
127750
|
+
});
|
|
127751
|
+
if (decision.action === "needs_decision") {
|
|
127752
|
+
const payload3 = { ...decision.needsDecision, currency };
|
|
127753
|
+
console.error(
|
|
127008
127754
|
`
|
|
127009
|
-
\u26A0\uFE0F
|
|
127755
|
+
\u26A0\uFE0F ${decision.needsDecision.message}
|
|
127756
|
+
\u539F\u7B56\u7565\uFF1A${decision.needsDecision.originalBidStrategy ?? "\uFF08\u672A\u6307\u5B9A\uFF09"}
|
|
127757
|
+
\u62DF\u8BAE\u7B56\u7565\uFF1A${decision.needsDecision.proposedBidStrategy}
|
|
127758
|
+
campaignId\uFF1A${opts.campaignId.trim()}
|
|
127759
|
+
\u672C\u6B21\u672A\u6539\u51FA\u4EF7\uFF0C\u672A\u518D\u6B21\u5EFA\u7EC4\u3002
|
|
127010
127760
|
`
|
|
127011
127761
|
);
|
|
127762
|
+
await emitMetaJson(opts, "meta-ad-adset-create", "meta-ad adset-create", payload3, digits, {
|
|
127763
|
+
status: "needs_decision"
|
|
127764
|
+
});
|
|
127765
|
+
process.exit(2);
|
|
127012
127766
|
}
|
|
127767
|
+
if (decision.action !== "retry") failMetaAd("\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25", firstErr);
|
|
127768
|
+
console.warn(
|
|
127769
|
+
`
|
|
127770
|
+
\u26A0\uFE0F \u5DF2\u6309\u4E8B\u524D\u6388\u6743\u7684 bid cap ${decision.bidAmount} ${currency} \u4EE5 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4\uFF08\u539F\u7B56\u7565 ${decision.recovery.fromStrategy ?? "\u672A\u6307\u5B9A"}\uFF09
|
|
127771
|
+
`
|
|
127772
|
+
);
|
|
127013
127773
|
try {
|
|
127014
127774
|
data = await apiFetch2(
|
|
127015
127775
|
adsetUrl,
|
|
@@ -127018,12 +127778,12 @@ async function runMetaAdAdsetCreate(opts) {
|
|
|
127018
127778
|
method: "POST",
|
|
127019
127779
|
body: JSON.stringify(
|
|
127020
127780
|
buildCreateAdSetBody({
|
|
127021
|
-
adset: { ...adset, bidStrategy:
|
|
127781
|
+
adset: { ...adset, bidStrategy: decision.bidStrategy },
|
|
127022
127782
|
campaignId: opts.campaignId.trim(),
|
|
127023
127783
|
pageId: opts.pageId.trim(),
|
|
127024
127784
|
dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
|
|
127025
127785
|
lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0,
|
|
127026
|
-
bidAmountMinor: toMinorUnitString(
|
|
127786
|
+
bidAmountMinor: toMinorUnitString(decision.bidAmount, currency)
|
|
127027
127787
|
})
|
|
127028
127788
|
)
|
|
127029
127789
|
},
|
|
@@ -127032,6 +127792,15 @@ async function runMetaAdAdsetCreate(opts) {
|
|
|
127032
127792
|
} catch (retryErr) {
|
|
127033
127793
|
failMetaAd("\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25", retryErr);
|
|
127034
127794
|
}
|
|
127795
|
+
const id2 = extractCreatedId(data);
|
|
127796
|
+
const payload2 = { ...data, id: id2, currency, recovery: decision.recovery };
|
|
127797
|
+
if (await emitMetaJson(opts, "meta-ad-adset-create", "meta-ad adset-create", payload2, digits)) {
|
|
127798
|
+
return;
|
|
127799
|
+
}
|
|
127800
|
+
console.log(`
|
|
127801
|
+
\u2705 \u5E7F\u544A\u7EC4\u5DF2\u521B\u5EFA\uFF1A${id2 ?? JSON.stringify(data)}
|
|
127802
|
+
`);
|
|
127803
|
+
return;
|
|
127035
127804
|
}
|
|
127036
127805
|
const id = extractCreatedId(data);
|
|
127037
127806
|
const payload = { ...data, id, currency };
|
|
@@ -127053,10 +127822,12 @@ async function runMetaAdAdsetEdit(opts) {
|
|
|
127053
127822
|
let countries;
|
|
127054
127823
|
let genders;
|
|
127055
127824
|
let advantageAudience;
|
|
127825
|
+
let countriesMode;
|
|
127056
127826
|
try {
|
|
127057
127827
|
if (opts.countries != null) countries = assertCountries(opts.countries);
|
|
127058
127828
|
genders = parseGenders(opts.genders);
|
|
127059
127829
|
advantageAudience = parseAdvantageAudience(opts.advantageAudience);
|
|
127830
|
+
countriesMode = parseCountriesMode(opts.countriesMode);
|
|
127060
127831
|
} catch (err) {
|
|
127061
127832
|
console.error(`
|
|
127062
127833
|
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
@@ -127070,26 +127841,87 @@ async function runMetaAdAdsetEdit(opts) {
|
|
|
127070
127841
|
process.exit(1);
|
|
127071
127842
|
}
|
|
127072
127843
|
}
|
|
127844
|
+
const ageMin = parseOptionalNumber(opts.ageMin);
|
|
127845
|
+
const ageMax = parseOptionalNumber(opts.ageMax);
|
|
127846
|
+
const wantsTargeting = countries != null || ageMin != null || ageMax != null || genders != null || advantageAudience != null;
|
|
127073
127847
|
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
127074
127848
|
const currency = await fetchAccountCurrency(facebookApiUrl, digits, config, opts.verbose);
|
|
127075
127849
|
const bidAmount = parseOptionalNumber(opts.bidAmount);
|
|
127850
|
+
const adsetUrl = adsUrl(facebookApiUrl, digits, `/adsets/${encodeURIComponent(opts.id)}`);
|
|
127851
|
+
let targeting;
|
|
127852
|
+
let targetingChanges;
|
|
127853
|
+
let droppedKeys = [];
|
|
127854
|
+
if (wantsTargeting) {
|
|
127855
|
+
let currentRaw;
|
|
127856
|
+
try {
|
|
127857
|
+
currentRaw = await apiFetch2(adsetUrl, config, {}, opts.verbose);
|
|
127858
|
+
} catch (err) {
|
|
127859
|
+
console.error(
|
|
127860
|
+
`
|
|
127861
|
+
\u274C \u8BFB\u53D6\u5F53\u524D\u5B9A\u5411\u5931\u8D25\uFF0C\u672A\u63D0\u4EA4\u4FEE\u6539\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
127862
|
+
`
|
|
127863
|
+
);
|
|
127864
|
+
process.exit(1);
|
|
127865
|
+
}
|
|
127866
|
+
const currentTargeting = isRecord2(currentRaw["targeting"]) ? currentRaw["targeting"] : void 0;
|
|
127867
|
+
const merged = mergeAdSetTargeting(
|
|
127868
|
+
currentTargeting,
|
|
127869
|
+
{
|
|
127870
|
+
countries,
|
|
127871
|
+
countriesMode: countries ? countriesMode : "replace",
|
|
127872
|
+
ageMin,
|
|
127873
|
+
ageMax,
|
|
127874
|
+
genders,
|
|
127875
|
+
advantageAudience
|
|
127876
|
+
},
|
|
127877
|
+
{ allowDropUnsupported: opts.targetingReplace === true }
|
|
127878
|
+
);
|
|
127879
|
+
if (!merged.ok) {
|
|
127880
|
+
const payload2 = merged.reason === "advantage_unset" ? {
|
|
127881
|
+
status: "needs_decision",
|
|
127882
|
+
code: "ADVANTAGE_AUDIENCE_UNSET",
|
|
127883
|
+
id: opts.id,
|
|
127884
|
+
message: "\u5F53\u524D\u7EC4\u8BFB\u4E0D\u5230 advantage_audience\uFF080 \u6216 1\uFF09\u3002\u7F51\u5173\u4F1A\u5728\u8FD9\u6B21\u5B9A\u5411\u66F4\u65B0\u91CC\u628A\u5B83\u5199\u6210 1\u3002\u8BF7\u663E\u5F0F\u4F20\u5165 --advantage-audience 0 \u6216 1 \u540E\u518D\u63D0\u4EA4\u3002\u672C\u6B21\u672A\u63D0\u4EA4\u4FEE\u6539\u3002"
|
|
127885
|
+
} : {
|
|
127886
|
+
status: "needs_decision",
|
|
127887
|
+
code: "TARGETING_UNSUPPORTED",
|
|
127888
|
+
id: opts.id,
|
|
127889
|
+
unsupportedKeys: merged.unsupportedKeys,
|
|
127890
|
+
message: "\u5F53\u524D\u5B9A\u5411\u542B\u672C\u547D\u4EE4\u4E0D\u80FD\u5B89\u5168\u5199\u56DE\u7684\u5B57\u6BB5\u3002\u6574\u4EFD\u66FF\u6362\u4F1A\u4E22\u6389\u5B83\u4EEC\u3002\u786E\u8BA4\u53EA\u4FDD\u7559\u56FD\u5BB6/\u5E74\u9F84/\u6027\u522B/flexible_spec/advantage_audience \u540E\uFF0C\u52A0\u4E0A --targeting-replace \u518D\u63D0\u4EA4\u3002\u672C\u6B21\u672A\u63D0\u4EA4\u4FEE\u6539\u3002"
|
|
127891
|
+
};
|
|
127892
|
+
const extra = merged.reason === "unsupported" ? `
|
|
127893
|
+
\u4E0D\u80FD\u5199\u56DE\uFF1A${merged.unsupportedKeys.join("\u3001")}` : "";
|
|
127894
|
+
console.error(`
|
|
127895
|
+
\u26A0\uFE0F ${payload2.message}${extra}
|
|
127896
|
+
`);
|
|
127897
|
+
await emitMetaJson(opts, "meta-ad-adset-edit", "meta-ad adset-edit", payload2, digits, {
|
|
127898
|
+
status: "needs_decision"
|
|
127899
|
+
});
|
|
127900
|
+
process.exit(2);
|
|
127901
|
+
}
|
|
127902
|
+
targeting = merged.targeting;
|
|
127903
|
+
targetingChanges = merged.changes;
|
|
127904
|
+
droppedKeys = merged.droppedKeys;
|
|
127905
|
+
const lines = merged.changes.map(formatTargetingChange);
|
|
127906
|
+
if (droppedKeys.length > 0) lines.push(`\u5C06\u4E22\u5F03\uFF1A${droppedKeys.join("\u3001")}`);
|
|
127907
|
+
console.error(`
|
|
127908
|
+
\u5B9A\u5411\u5DEE\u5F02\uFF1A
|
|
127909
|
+
${lines.join("\n ")}
|
|
127910
|
+
`);
|
|
127911
|
+
}
|
|
127076
127912
|
let body;
|
|
127077
127913
|
try {
|
|
127078
127914
|
body = buildUpdateAdSetBody({
|
|
127079
127915
|
name: opts.name?.trim() || void 0,
|
|
127080
127916
|
dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
|
|
127081
127917
|
lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0,
|
|
127082
|
-
|
|
127083
|
-
ageMin: parseOptionalNumber(opts.ageMin),
|
|
127084
|
-
ageMax: parseOptionalNumber(opts.ageMax),
|
|
127085
|
-
genders,
|
|
127918
|
+
targeting,
|
|
127086
127919
|
optimizationGoal: opts.optimizationGoal?.trim().toUpperCase(),
|
|
127087
127920
|
bidStrategy: opts.bidStrategy,
|
|
127088
127921
|
bidAmountMinor: bidAmount != null ? toMinorUnitString(bidAmount, currency) : void 0,
|
|
127089
127922
|
startTime: opts.startTime,
|
|
127090
127923
|
endTime: opts.endTime,
|
|
127091
|
-
status: opts.status
|
|
127092
|
-
advantageAudience
|
|
127924
|
+
status: opts.status
|
|
127093
127925
|
});
|
|
127094
127926
|
} catch (err) {
|
|
127095
127927
|
console.error(`
|
|
@@ -127102,16 +127934,47 @@ async function runMetaAdAdsetEdit(opts) {
|
|
|
127102
127934
|
process.exit(1);
|
|
127103
127935
|
}
|
|
127104
127936
|
const data = await metaAdPost(
|
|
127105
|
-
|
|
127937
|
+
adsetUrl,
|
|
127106
127938
|
config,
|
|
127107
127939
|
body,
|
|
127108
127940
|
opts.verbose,
|
|
127109
127941
|
"\u6539\u5E7F\u544A\u7EC4\u5931\u8D25"
|
|
127110
127942
|
);
|
|
127111
|
-
|
|
127112
|
-
|
|
127943
|
+
let read = await readAdSetForVerify(adsetUrl, config, opts.verbose);
|
|
127944
|
+
let judged = evaluateAdSetEditReadback(body, read.adset, read.readFailed);
|
|
127945
|
+
if (judged.verification !== "verified") {
|
|
127946
|
+
await new Promise((r) => setTimeout(r, READBACK_WAIT_MS));
|
|
127947
|
+
read = await readAdSetForVerify(adsetUrl, config, opts.verbose);
|
|
127948
|
+
judged = evaluateAdSetEditReadback(body, read.adset, read.readFailed);
|
|
127949
|
+
}
|
|
127950
|
+
const verification = judged.verification;
|
|
127951
|
+
const nextQuery = `siluzan-tso meta-ad adset -a ${digits} --id ${opts.id} --json-out ./snap`;
|
|
127952
|
+
const unmatched = describeUnmatchedChecks(judged.checks);
|
|
127953
|
+
const detail = verification === "verified" ? `\u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u6838\u9A8C` : unmatched || `\u5E7F\u544A\u7EC4 ${opts.id} \u7684\u5199\u5165\u7ED3\u679C\u5C1A\u672A\u6838\u9A8C`;
|
|
127954
|
+
const headline = writeVerificationHeadline(verification, detail);
|
|
127955
|
+
if (verification !== "verified") {
|
|
127956
|
+
console.error(`
|
|
127957
|
+
${headline}
|
|
127958
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}
|
|
127959
|
+
`);
|
|
127960
|
+
}
|
|
127961
|
+
const payload = {
|
|
127962
|
+
id: extractCreatedId(data) ?? opts.id,
|
|
127963
|
+
verification,
|
|
127964
|
+
nextQuery,
|
|
127965
|
+
targetingDiff: targetingChanges,
|
|
127966
|
+
droppedKeys,
|
|
127967
|
+
checks: judged.checks,
|
|
127968
|
+
...data
|
|
127969
|
+
};
|
|
127970
|
+
if (await emitMetaJson(opts, "meta-ad-adset-edit", "meta-ad adset-edit", payload, digits, {
|
|
127971
|
+
verification
|
|
127972
|
+
})) {
|
|
127973
|
+
return;
|
|
127974
|
+
}
|
|
127113
127975
|
console.log(`
|
|
127114
|
-
|
|
127976
|
+
${headline}${verification === "verified" ? "" : `
|
|
127977
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}`}
|
|
127115
127978
|
`);
|
|
127116
127979
|
}
|
|
127117
127980
|
async function runMetaAdAdsetStatus(opts) {
|
|
@@ -127408,7 +128271,11 @@ function register22(program2) {
|
|
|
127408
128271
|
);
|
|
127409
128272
|
addCommon(
|
|
127410
128273
|
addAccount(
|
|
127411
|
-
meta.command("adset-create").description("\u5EFA\u7EBF\u7D22\u5E7F\u544A\u7EC4\uFF08destination=ON_AD\uFF1B\u9ED8\u8BA4 LEAD_GENERATION\uFF09").requiredOption("--campaign-id <id>", "\u7CFB\u5217 ID").requiredOption("--page-id <id>", "\u6295\u653E\u4E3B\u9875 ID").requiredOption("--name <name>", "\u7EC4\u540D\u79F0").requiredOption("--countries <list>", "ISO \u56FD\u5BB6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u5982 US,GB\uFF09").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143\uFF08ABO\uFF09", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--age-min <n>", "\u6700\u5C0F\u5E74\u9F84", parseInt).option("--age-max <n>", "\u6700\u5927\u5E74\u9F84", parseInt).option("--genders <list>", "1=\u7537,2=\u5973\uFF0C\u9017\u53F7\u5206\u9694").option("--optimization-goal <g>", "LEAD_GENERATION | QUALITY_LEAD").option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--bid-amount <amount>", "\u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option(
|
|
128274
|
+
meta.command("adset-create").description("\u5EFA\u7EBF\u7D22\u5E7F\u544A\u7EC4\uFF08destination=ON_AD\uFF1B\u9ED8\u8BA4 LEAD_GENERATION\uFF09").requiredOption("--campaign-id <id>", "\u7CFB\u5217 ID").requiredOption("--page-id <id>", "\u6295\u653E\u4E3B\u9875 ID").requiredOption("--name <name>", "\u7EC4\u540D\u79F0").requiredOption("--countries <list>", "ISO \u56FD\u5BB6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u5982 US,GB\uFF09").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143\uFF08ABO\uFF09", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--age-min <n>", "\u6700\u5C0F\u5E74\u9F84", parseInt).option("--age-max <n>", "\u6700\u5927\u5E74\u9F84", parseInt).option("--genders <list>", "1=\u7537,2=\u5973\uFF0C\u9017\u53F7\u5206\u9694").option("--optimization-goal <g>", "LEAD_GENERATION | QUALITY_LEAD").option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--bid-amount <amount>", "\u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option(
|
|
128275
|
+
"--bid-amount-on-required <amount>",
|
|
128276
|
+
"\u4EC5\u5F53\u5EFA\u7EC4\u8FD4\u56DE BID_AMOUNT_REQUIRED \u65F6\u4F7F\u7528\u7684\u51FA\u4EF7\u4E0A\u9650\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09\u3002\u4E0D\u4F20\u5219\u505C\u4E0B\uFF0C\u4E0D\u4F1A\u7528\u9884\u7B97\u4EE3\u66FF",
|
|
128277
|
+
parseFloat
|
|
128278
|
+
).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "ACTIVE | PAUSED").option(
|
|
127412
128279
|
"--advantage-audience <n>",
|
|
127413
128280
|
"0=\u7CBE\u786E\u5B9A\u5411 1=Advantage Audience\uFF08\u9ED8\u8BA4 1\uFF09",
|
|
127414
128281
|
(v) => Number.parseInt(v, 10)
|
|
@@ -127421,11 +128288,13 @@ function register22(program2) {
|
|
|
127421
128288
|
);
|
|
127422
128289
|
addCommon(
|
|
127423
128290
|
addAccount(
|
|
127424
|
-
meta.command("adset-edit").description(
|
|
128291
|
+
meta.command("adset-edit").description(
|
|
128292
|
+
"\u6539\u5E7F\u544A\u7EC4\u3002\u56FD\u5BB6\u9ED8\u8BA4\u6574\u8868\u66FF\u6362\uFF1B\u8FFD\u52A0\u7528 --countries-mode append\u3002\u672A\u4F20 --advantage-audience \u65F6\u4FDD\u7559\u5F53\u524D\u503C\uFF0C\u4E0D\u5957\u521B\u5EFA\u9ED8\u8BA4"
|
|
128293
|
+
).requiredOption("--id <adSetId>", "\u5E7F\u544A\u7EC4 ID").option("--name <name>", "\u65B0\u540D\u79F0").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--countries <list>", "ISO \u56FD\u5BB6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u9ED8\u8BA4\u66FF\u6362\u6574\u4EFD\u56FD\u5BB6\u5217\u8868\uFF09").option("--countries-mode <mode>", "replace\uFF08\u9ED8\u8BA4\uFF09| append\uFF08\u5E76\u4E0A\u5F53\u524D\u56FD\u5BB6\uFF09").option("--age-min <n>", "\u6700\u5C0F\u5E74\u9F84", parseInt).option("--age-max <n>", "\u6700\u5927\u5E74\u9F84", parseInt).option("--genders <list>", "1=\u7537,2=\u5973").option("--optimization-goal <g>", "LEAD_GENERATION | QUALITY_LEAD").option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--bid-amount <amount>", "\u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "\u72B6\u6001").option(
|
|
127425
128294
|
"--advantage-audience <n>",
|
|
127426
|
-
"0
|
|
128295
|
+
"0 \u6216 1\u3002\u4E0D\u4F20\u5219\u4FDD\u7559\u5F53\u524D\u503C\uFF0C\u4E0D\u4F1A\u9ED8\u8BA4\u6210 1",
|
|
127427
128296
|
(v) => Number.parseInt(v, 10)
|
|
127428
|
-
)
|
|
128297
|
+
).option("--targeting-replace", "\u786E\u8BA4\u4E22\u6389\u5F53\u524D\u5B9A\u5411\u91CC\u672C\u547D\u4EE4\u4E0D\u80FD\u5199\u56DE\u7684\u5B57\u6BB5\u540E\u518D\u63D0\u4EA4", false)
|
|
127429
128298
|
)
|
|
127430
128299
|
).action(
|
|
127431
128300
|
async (opts) => {
|
|
@@ -133428,11 +134297,12 @@ function buildGoogleAdsDiagnosisCollectAgentHint(snapDir) {
|
|
|
133428
134297
|
"Google Ads \u8BCA\u65AD collect \u5DF2\u5B8C\u6210\uFF08\u4EC5\u4E8B\u5B9E\u6570\u636E\uFF0C\u4E0D\u542B\u5EFA\u8BAE\u6027\u6587\u6848\uFF09\u3002",
|
|
133429
134298
|
`1) Read ${dir}/${GOOGLE_ADS_DIAGNOSIS_DIGEST_BASENAME}\uFF1A\u5404 section \u7ED3\u6784\u3001\u884C\u6570\u3001\u5F85\u586B\u5B57\u6BB5\u6E05\u5355\u90FD\u5728\u91CC\u9762\uFF1B`,
|
|
133430
134299
|
`2) ${dir}/${GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME} \u5DF2\u662F\u7EC8\u7A3F\u9AA8\u67B6\uFF08items \u4E8B\u5B9E\u884C\u5DF2\u586B\u597D\u3001\u53D9\u4E8B\u5B57\u6BB5\u7559\u7A7A\uFF09\uFF0C\u76F4\u63A5\u5728\u5B83\u4E0A\u9762\u586B\u7A7A\uFF1B`,
|
|
133431
|
-
"3) Read references/report-templates/google-ads-diagnosis.md\
|
|
134300
|
+
"3) Read references/report-templates/google-ads-diagnosis.md\u3002\u53EA\u5199\u4E3B\u89C2\u5206\u6790\uFF1AdiagnosisOverview.advantages/disadvantages\uFF08\u6BCF\u6761 {title, description}\uFF09\u3001\u5404\u6A21\u5757 analysis/suggestions\u3001summary\u3001accountInfo.businessModel/industry\u3002",
|
|
133432
134301
|
`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`,
|
|
133433
134302
|
`\u7981\u6B62\u7528 node -e \u9010\u6BB5 dump ${GOOGLE_ADS_DIAGNOSIS_COLLECT_BASENAME} \u6765\u6478\u7ED3\u6784\u2014\u2014\u7ED3\u6784\u770B digest\u3001\u6570\u5B57\u770B\u9AA8\u67B6\u6587\u4EF6\u5373\u53EF\u3002`,
|
|
133434
134303
|
"\u7981\u6B62\u5199\u5165/\u8986\u76D6 campaigns/geographic/keywords \u7684 items \u884C\uFF0C\u53EA\u586B analysis/suggestions\u3002",
|
|
133435
|
-
"\u7981\u6B62\u5728\u672A\u7ECF\u8FC7 Agent \u64B0\u5199\u7684\u60C5\u51B5\u4E0B\u76F4\u63A5 render\uFF1B\u7F3A\u4EFB\u4E00\u5EFA\u8BAE\u5B57\u6BB5 render \u4F1A\u5931\u8D25\u3002"
|
|
134304
|
+
"\u7981\u6B62\u5728\u672A\u7ECF\u8FC7 Agent \u64B0\u5199\u7684\u60C5\u51B5\u4E0B\u76F4\u63A5 render\uFF1B\u7F3A\u4EFB\u4E00\u5EFA\u8BAE\u5B57\u6BB5 render \u4F1A\u5931\u8D25\u3002",
|
|
134305
|
+
"\u9EC4\u91D1\u8D26\u6237\u8868\u3001\u9884\u7B97\u7ADE\u4E89\u529B\u3001\u51FA\u4EF7\u539F\u56E0\u3001\u65B0\u4EA7\u54C1\u72B6\u6001\u3001\u521B\u610F\u884C\u5EFA\u8BAE\u7531 render \u6309\u6570\u636E\u5199\u5165\u3002Agent \u4E0D\u8981\u586B\u8FD9\u4E9B\u5B57\u6BB5\uFF0C\u4E5F\u4E0D\u8981\u628A\u672A\u8FBE\u6807\u5199\u6210\u300C\u8BF7\u521B\u5EFA/\u6CA1\u6709\u300D\u3002"
|
|
133436
134306
|
].join("\n");
|
|
133437
134307
|
}
|
|
133438
134308
|
|
|
@@ -133485,17 +134355,21 @@ function buildGoogleAdsDiagnosisAgentBrief(input) {
|
|
|
133485
134355
|
},
|
|
133486
134356
|
comparisonTableItemCounts: input.comparisonTableItemCounts,
|
|
133487
134357
|
agentRules: {
|
|
133488
|
-
doNotModifyItemRows: [
|
|
133489
|
-
|
|
133490
|
-
|
|
134358
|
+
doNotModifyItemRows: [
|
|
134359
|
+
"campaigns.items",
|
|
134360
|
+
"geographic.items",
|
|
134361
|
+
"keywords.items",
|
|
134362
|
+
"budgetCompetitiveness[].strategy",
|
|
133491
134363
|
"biddingStrategy.items[].recommendationReason",
|
|
133492
134364
|
"newFeatures.items[].optimizerRecommendation",
|
|
133493
134365
|
"adCreativeOptimization.items[].suggestion"
|
|
133494
|
-
]
|
|
134366
|
+
],
|
|
134367
|
+
renderRestoresComparisonItemsFromCollect: "render \u9ED8\u8BA4\u540C\u76EE\u5F55 google-ads-diagnosis-collect.json \u4F1A\u4EE5 collect \u8986\u76D6\u4E0A\u8FF0 items\u3002\u9EC4\u91D1\u8D26\u6237\u8868\u3001\u9884\u7B97\u7ADE\u4E89\u529B strategy\u3001\u51FA\u4EF7 recommendationReason\u3001\u65B0\u4EA7\u54C1 optimizerRecommendation\u3001\u521B\u610F\u884C suggestion \u4E5F\u7531 render \u6309\u884C\u6570\u636E\u91CD\u5199\u3002Agent \u53EA\u586B\u4E3B\u89C2\u5206\u6790\u3002",
|
|
134368
|
+
mayEditItemFields: []
|
|
133495
134369
|
},
|
|
133496
134370
|
agentDeliverable: {
|
|
133497
134371
|
outputFile: "google-ads-diagnosis.json",
|
|
133498
|
-
structure: "\u4E0E reportData \u540C\u7ED3\u6784\u7684 sectionData\
|
|
134372
|
+
structure: "\u4E0E reportData \u540C\u7ED3\u6784\u7684 sectionData\u3002\u53EA\u586B\u4E3B\u89C2\u5206\u6790\uFF1AdiagnosisOverview.advantages/disadvantages\uFF08\u6BCF\u6761 {title, description}\uFF09\u3001\u5404\u6A21\u5757 analysis/suggestions\u3001summary.keyIssues\u3001summary.optimizationRoadmap[]\uFF08priority/focusArea/actionItems/expectedOutcome\uFF09\u3001accountInfo.businessModel/industry\u3002\u4E0D\u8981\u586B\u9EC4\u91D1\u8D26\u6237\u8868\u3001budgetCompetitiveness[].strategy\u3001biddingStrategy[].recommendationReason\u3001newFeatures[].optimizerRecommendation\u3001adCreativeOptimization.items[].suggestion",
|
|
133499
134373
|
thenRun: "siluzan-tso google-ads-diagnosis render --data ./<snap>/google-ads-diagnosis.json --out ./<snap>/google-ads-diagnosis-report.html"
|
|
133500
134374
|
}
|
|
133501
134375
|
};
|
|
@@ -133549,14 +134423,7 @@ function asRecord7(value) {
|
|
|
133549
134423
|
}
|
|
133550
134424
|
return null;
|
|
133551
134425
|
}
|
|
133552
|
-
|
|
133553
|
-
return gold.expandedTextAdQualified === true || gold.textAdQualified === true || gold.responsiveSearchAdQualified === true;
|
|
133554
|
-
}
|
|
133555
|
-
function applyTextAdQualification(gold) {
|
|
133556
|
-
if (!hasSearchTextAds(gold) || gold.expandedTextAdQualified === true) return gold;
|
|
133557
|
-
return { ...gold, expandedTextAdQualified: true };
|
|
133558
|
-
}
|
|
133559
|
-
var DISPLAY_PAUSED_SUGGESTION = "\u5DF2\u6709\u81EA\u9002\u5E94\u5C55\u793A\u5E7F\u544A\uFF0C\u6240\u5728\u5C55\u793A\u7CFB\u5217\u5DF2\u6682\u505C\uFF0C\u672C\u671F\u65E0\u6D88\u8017";
|
|
134426
|
+
var NOT_QUALIFIED = "\u672A\u8FBE\u5230\u8BE5\u9879\u9EC4\u91D1\u8D26\u6237\u6807\u51C6";
|
|
133560
134427
|
function statusOf(row) {
|
|
133561
134428
|
return String(row.statusV2 ?? row.status ?? "").toUpperCase();
|
|
133562
134429
|
}
|
|
@@ -133566,7 +134433,7 @@ function isEnabled(row) {
|
|
|
133566
134433
|
function hasEnabledExtension(rows, type) {
|
|
133567
134434
|
return rows.some((row) => String(row.typeV2 ?? "") === type && isEnabled(row));
|
|
133568
134435
|
}
|
|
133569
|
-
function
|
|
134436
|
+
function hasGa4Conversion(rows) {
|
|
133570
134437
|
return rows.some((row) => {
|
|
133571
134438
|
if (!isEnabled(row)) return false;
|
|
133572
134439
|
const type = String(row.type ?? "");
|
|
@@ -133574,12 +134441,25 @@ function hasGa4Link(rows) {
|
|
|
133574
134441
|
return type.startsWith("GoogleAnalytics") || /GA4/i.test(name2);
|
|
133575
134442
|
});
|
|
133576
134443
|
}
|
|
133577
|
-
function
|
|
134444
|
+
function hasAudienceRows(rows) {
|
|
133578
134445
|
return rows.some((row) => {
|
|
133579
134446
|
const type = String(row.criterionType ?? "");
|
|
133580
134447
|
return type === "UserList" || type === "CustomAudience" || type === "UserInterest" || type === "CustomIntent" || type === "CombinedAudience" || type === "CustomAffinity" || Number(row.impressions) > 0;
|
|
133581
134448
|
});
|
|
133582
134449
|
}
|
|
134450
|
+
function hasSearchTextAdRows(rows) {
|
|
134451
|
+
return rows.some((row) => {
|
|
134452
|
+
const type = String(row.typeV2 ?? "");
|
|
134453
|
+
return type === "RESPONSIVE_SEARCH_AD" || type === "EXPANDED_TEXT_AD";
|
|
134454
|
+
});
|
|
134455
|
+
}
|
|
134456
|
+
function campaignsUseBidding(rows) {
|
|
134457
|
+
return rows.some(
|
|
134458
|
+
(row) => /MAXIMIZE_|TARGET_|MANUAL_CPC|ENHANCED_CPC|COMMISSION/i.test(
|
|
134459
|
+
String(row.biddingStrategyTypeV2 ?? row.biddingStrategyType ?? "")
|
|
134460
|
+
)
|
|
134461
|
+
);
|
|
134462
|
+
}
|
|
133583
134463
|
function displayAds(rows) {
|
|
133584
134464
|
return rows.filter((row) => String(row.typeV2 ?? "") === "RESPONSIVE_DISPLAY_AD");
|
|
133585
134465
|
}
|
|
@@ -133588,28 +134468,109 @@ function displayIsServing(rows) {
|
|
|
133588
134468
|
(row) => isEnabled(row) && statusOf({ statusV2: row.parentCampaignStatusV2 }) === "ENABLED"
|
|
133589
134469
|
);
|
|
133590
134470
|
}
|
|
134471
|
+
function noteUnqualified(gold, suggestions, flag, itemName, present, sentence) {
|
|
134472
|
+
if (!present || gold[flag] !== false) return;
|
|
134473
|
+
suggestions[itemName] = sentence;
|
|
134474
|
+
}
|
|
133591
134475
|
function reconcileGoldAccount(gold, evidence = {}) {
|
|
133592
|
-
const next = { ...
|
|
134476
|
+
const next = { ...gold };
|
|
134477
|
+
const suggestions = { ...asRecord7(gold.goldItemSuggestions) ?? {} };
|
|
134478
|
+
const notes = {};
|
|
133593
134479
|
const conversions = evidence.conversionActions ?? [];
|
|
133594
134480
|
const extensions = evidence.extensions ?? [];
|
|
133595
134481
|
const audiences = evidence.audiences ?? [];
|
|
133596
134482
|
const ads = evidence.ads ?? [];
|
|
133597
|
-
|
|
133598
|
-
|
|
133599
|
-
if (
|
|
133600
|
-
|
|
133601
|
-
|
|
133602
|
-
|
|
134483
|
+
const presentBecause = (sentence) => `${NOT_QUALIFIED}\u3002${sentence}\u662F\u5426\u8FBE\u6807\u4E0D\u7531\u8BE5\u5BF9\u8C61\u662F\u5426\u5B58\u5728\u51B3\u5B9A\u3002`;
|
|
134484
|
+
const textAdsPresent = hasSearchTextAdRows(ads) || gold.responsiveSearchAdQualified === true || gold.textAdQualified === true || gold.expandedTextAdQualified === true;
|
|
134485
|
+
if (textAdsPresent) notes.textAds = true;
|
|
134486
|
+
noteUnqualified(
|
|
134487
|
+
gold,
|
|
134488
|
+
suggestions,
|
|
134489
|
+
"expandedTextAdQualified",
|
|
134490
|
+
"\u6587\u5B57\u5E7F\u544A",
|
|
134491
|
+
textAdsPresent,
|
|
134492
|
+
presentBecause("\u8D26\u6237\u91CC\u5DF2\u6709\u641C\u7D22\u6587\u5B57\u5E7F\u544A\uFF0C")
|
|
134493
|
+
);
|
|
134494
|
+
noteUnqualified(
|
|
134495
|
+
gold,
|
|
134496
|
+
suggestions,
|
|
134497
|
+
"responsiveSearchAdQualified",
|
|
134498
|
+
"\u81EA\u9002\u5E94\u641C\u7D22\u5E7F\u544A",
|
|
134499
|
+
hasSearchTextAdRows(ads),
|
|
134500
|
+
presentBecause("\u8D26\u6237\u91CC\u5DF2\u6709\u81EA\u9002\u5E94\u641C\u7D22\u5E7F\u544A\uFF0C")
|
|
134501
|
+
);
|
|
133603
134502
|
const responsiveDisplay = displayAds(ads);
|
|
133604
|
-
if (
|
|
133605
|
-
|
|
133606
|
-
|
|
133607
|
-
|
|
133608
|
-
|
|
133609
|
-
|
|
133610
|
-
|
|
133611
|
-
|
|
133612
|
-
|
|
134503
|
+
if (responsiveDisplay.length > 0) {
|
|
134504
|
+
notes.displayAds = true;
|
|
134505
|
+
if (gold.multiAssetResponsiveDisplayAdQualified === false) {
|
|
134506
|
+
suggestions["\u81EA\u9002\u5E94\u5C55\u793A\u5E7F\u544A"] = displayIsServing(responsiveDisplay) ? presentBecause("\u8D26\u6237\u91CC\u5DF2\u6709\u81EA\u9002\u5E94\u5C55\u793A\u5E7F\u544A\uFF0C") : `${NOT_QUALIFIED}\u3002\u8D26\u6237\u91CC\u5DF2\u6709\u81EA\u9002\u5E94\u5C55\u793A\u5E7F\u544A\uFF0C\u6240\u5728\u5C55\u793A\u7CFB\u5217\u5DF2\u6682\u505C\uFF0C\u672C\u671F\u65E0\u6D88\u8017\u3002`;
|
|
134507
|
+
}
|
|
134508
|
+
}
|
|
134509
|
+
if (hasGa4Conversion(conversions)) notes.ga4 = true;
|
|
134510
|
+
noteUnqualified(
|
|
134511
|
+
gold,
|
|
134512
|
+
suggestions,
|
|
134513
|
+
"gaConfigured",
|
|
134514
|
+
"G A\u4E0E\u5E7F\u544A\u8D26\u6237\u8054\u7ED3",
|
|
134515
|
+
hasGa4Conversion(conversions),
|
|
134516
|
+
presentBecause("\u8D26\u6237\u91CC\u5DF2\u6709\u542F\u7528\u7684 Google Analytics \u8F6C\u5316\uFF0C")
|
|
134517
|
+
);
|
|
134518
|
+
if (conversions.some((row) => isEnabled(row))) notes.conversion = true;
|
|
134519
|
+
noteUnqualified(
|
|
134520
|
+
gold,
|
|
134521
|
+
suggestions,
|
|
134522
|
+
"conversionTrackingQualified",
|
|
134523
|
+
"\u8F6C\u5316\u8FFD\u8E2A\u8BBE\u7F6E",
|
|
134524
|
+
conversions.some((row) => isEnabled(row)),
|
|
134525
|
+
presentBecause("\u8D26\u6237\u91CC\u5DF2\u6709\u542F\u7528\u7684\u8F6C\u5316\u52A8\u4F5C\uFF0C")
|
|
134526
|
+
);
|
|
134527
|
+
const extensionNote = (type, flag, itemName, noteKey, found) => {
|
|
134528
|
+
if (!hasEnabledExtension(extensions, type)) return;
|
|
134529
|
+
notes[noteKey] = true;
|
|
134530
|
+
noteUnqualified(gold, suggestions, flag, itemName, true, presentBecause(found));
|
|
134531
|
+
};
|
|
134532
|
+
extensionNote(
|
|
134533
|
+
"CALLOUT",
|
|
134534
|
+
"adsCalloutExtensionQualified",
|
|
134535
|
+
"\u9644\u52A0\u5BA3\u4F20\u4FE1\u606F",
|
|
134536
|
+
"callout",
|
|
134537
|
+
"\u8D26\u6237\u91CC\u5DF2\u6709\u542F\u7528\u7684\u5BA3\u4F20\u4FE1\u606F\uFF0C"
|
|
134538
|
+
);
|
|
134539
|
+
extensionNote(
|
|
134540
|
+
"STRUCTURED_SNIPPET",
|
|
134541
|
+
"adsStructuredSnippetExtensionQualified",
|
|
134542
|
+
"\u9644\u52A0\u7ED3\u6784\u5316\u4FE1\u606F\u6458\u8981",
|
|
134543
|
+
"snippet",
|
|
134544
|
+
"\u8D26\u6237\u91CC\u5DF2\u6709\u542F\u7528\u7684\u7ED3\u6784\u5316\u4FE1\u606F\u6458\u8981\uFF0C"
|
|
134545
|
+
);
|
|
134546
|
+
extensionNote(
|
|
134547
|
+
"SITELINK",
|
|
134548
|
+
"adsSitelinkExtensionQualified",
|
|
134549
|
+
"\u9644\u52A0\u94FE\u63A5",
|
|
134550
|
+
"sitelink",
|
|
134551
|
+
"\u8D26\u6237\u91CC\u5DF2\u6709\u542F\u7528\u7684\u9644\u52A0\u94FE\u63A5\uFF0C"
|
|
134552
|
+
);
|
|
134553
|
+
extensionNote("CALL", "adsCallExtensionQualified", "\u9644\u52A0\u7535\u8BDD\u4FE1\u606F", "call", "\u8D26\u6237\u91CC\u5DF2\u6709\u542F\u7528\u7684\u7535\u8BDD\u4FE1\u606F\uFF0C");
|
|
134554
|
+
if (hasAudienceRows(audiences)) notes.audience = true;
|
|
134555
|
+
noteUnqualified(
|
|
134556
|
+
gold,
|
|
134557
|
+
suggestions,
|
|
134558
|
+
"audienceQualified",
|
|
134559
|
+
"\u53D7\u4F17\u7FA4\u4F53\u8BBE\u7F6E",
|
|
134560
|
+
hasAudienceRows(audiences),
|
|
134561
|
+
presentBecause("\u8D26\u6237\u91CC\u5DF2\u6709\u53D7\u4F17\uFF0C")
|
|
134562
|
+
);
|
|
134563
|
+
if (campaignsUseBidding(evidence.campaigns ?? [])) notes.bidding = true;
|
|
134564
|
+
noteUnqualified(
|
|
134565
|
+
gold,
|
|
134566
|
+
suggestions,
|
|
134567
|
+
"biddingPolicyQualified",
|
|
134568
|
+
"\u51FA\u4EF7\u7B56\u7565",
|
|
134569
|
+
campaignsUseBidding(evidence.campaigns ?? []),
|
|
134570
|
+
presentBecause("\u7CFB\u5217\u5DF2\u8BBE\u7F6E\u51FA\u4EF7\u7B56\u7565\uFF0C")
|
|
134571
|
+
);
|
|
134572
|
+
if (Object.keys(suggestions).length > 0) next.goldItemSuggestions = suggestions;
|
|
134573
|
+
if (Object.keys(notes).length > 0) next.goldAssetNotes = notes;
|
|
133613
134574
|
return next;
|
|
133614
134575
|
}
|
|
133615
134576
|
function parsePeriod(period) {
|
|
@@ -133617,24 +134578,23 @@ function parsePeriod(period) {
|
|
|
133617
134578
|
if (!matched) return {};
|
|
133618
134579
|
return { start: matched[1], end: matched[2] };
|
|
133619
134580
|
}
|
|
134581
|
+
function loadEvidence(snapDir, accountId, start, end) {
|
|
134582
|
+
const items3 = (section) => sectionItems(loadSectionSnapshot(snapDir, section, accountId, start, end));
|
|
134583
|
+
return {
|
|
134584
|
+
ads: items3("ads"),
|
|
134585
|
+
extensions: items3("extensions"),
|
|
134586
|
+
conversionActions: items3("conversion-actions"),
|
|
134587
|
+
audiences: items3("audience"),
|
|
134588
|
+
campaigns: items3("campaigns")
|
|
134589
|
+
};
|
|
134590
|
+
}
|
|
133620
134591
|
function reconcileGoldAccountInReport(data, snapDir) {
|
|
133621
134592
|
const gold = asRecord7(data.goldAccount) ?? {};
|
|
133622
134593
|
const account = asRecord7(data.accountInfo);
|
|
133623
134594
|
const accountId = String(account?.companyId ?? "").replace(/\D/g, "");
|
|
133624
134595
|
const { start, end } = parsePeriod(String(account?.period ?? ""));
|
|
133625
|
-
const evidence = {};
|
|
133626
|
-
|
|
133627
|
-
evidence.conversionActions = sectionItems(
|
|
133628
|
-
loadSectionSnapshot(snapDir, "conversion-actions", accountId, start, end)
|
|
133629
|
-
);
|
|
133630
|
-
evidence.extensions = sectionItems(
|
|
133631
|
-
loadSectionSnapshot(snapDir, "extensions", accountId, start, end)
|
|
133632
|
-
);
|
|
133633
|
-
evidence.audiences = sectionItems(loadSectionSnapshot(snapDir, "audience", accountId, start, end));
|
|
133634
|
-
evidence.ads = sectionItems(loadSectionSnapshot(snapDir, "ads", accountId, start, end));
|
|
133635
|
-
}
|
|
133636
|
-
const next = reconcileGoldAccount(gold, evidence);
|
|
133637
|
-
return { ...data, goldAccount: next };
|
|
134596
|
+
const evidence = snapDir && accountId ? loadEvidence(snapDir, accountId, start, end) : {};
|
|
134597
|
+
return { ...data, goldAccount: reconcileGoldAccount(gold, evidence) };
|
|
133638
134598
|
}
|
|
133639
134599
|
|
|
133640
134600
|
// src/commands/google-ads-diagnosis/default-section-data.ts
|
|
@@ -134281,7 +135241,8 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
|
|
|
134281
135241
|
conversionActions: conversionRows,
|
|
134282
135242
|
extensions: extensionRows,
|
|
134283
135243
|
audiences: audienceRows,
|
|
134284
|
-
ads: adRows
|
|
135244
|
+
ads: adRows,
|
|
135245
|
+
campaigns: campaignCurrent
|
|
134285
135246
|
});
|
|
134286
135247
|
data.goldAccount = goldRecord;
|
|
134287
135248
|
data.fullAudience = {
|
|
@@ -134426,6 +135387,7 @@ function narrativeChecklist(shapes) {
|
|
|
134426
135387
|
const lines = [
|
|
134427
135388
|
"- `accountInfo.businessModel`\u3001`accountInfo.industry`",
|
|
134428
135389
|
'- `diagnosisOverview.advantages`\u3001`diagnosisOverview.disadvantages`\uFF1A\u6BCF\u6761 `{"title":"\u77ED\u6807\u9898","description":"\u4F9D\u636E"}`\u3002\u7EAF\u5B57\u7B26\u4E32\u4F1A\u5728 render \u65F6\u6309\u7B2C\u4E00\u4E2A\u300C\uFF1A\u300D\u62C6\u5F00\uFF1B\u7F3A title \u6216 description \u5219\u62D2\u7EDD\u51FA HTML\uFF08\u5426\u5219\u9875\u9762\u663E\u793A\u300C\u672A\u586B\u5199\u300D\uFF09',
|
|
135390
|
+
"- \u9EC4\u91D1\u8D26\u6237\u8868\u3001\u9884\u7B97\u7ADE\u4E89\u529B strategy\u3001\u51FA\u4EF7 recommendationReason\u3001\u65B0\u4EA7\u54C1 optimizerRecommendation\u3001\u521B\u610F\u884C suggestion \u7531 render \u6309\u6570\u636E\u5199\u5165\uFF0CAgent \u4E0D\u8981\u586B",
|
|
134429
135391
|
"- `summary.keyIssues`\u3001`summary.optimizationRoadmap`"
|
|
134430
135392
|
];
|
|
134431
135393
|
for (const shape of shapes) {
|
|
@@ -134433,10 +135395,6 @@ function narrativeChecklist(shapes) {
|
|
|
134433
135395
|
lines.push(`- \`${shape.section}.analysis\`\u3001\`${shape.section}.suggestions\``);
|
|
134434
135396
|
}
|
|
134435
135397
|
}
|
|
134436
|
-
lines.push("- `budgetCompetitiveness.items[].strategy`");
|
|
134437
|
-
lines.push("- `biddingStrategy.items[].recommendationReason`");
|
|
134438
|
-
lines.push("- `newFeatures.items[].optimizerRecommendation`");
|
|
134439
|
-
lines.push("- `adCreativeOptimization.items[].suggestion`\uFF08\u6BCF\u884C\u72EC\u7ACB\uFF0C\u7981\u6B62\u590D\u5236\u540C\u4E00\u53E5\uFF09");
|
|
134440
135398
|
return lines;
|
|
134441
135399
|
}
|
|
134442
135400
|
function buildCollectDigest(reportData, meta) {
|
|
@@ -134806,7 +135764,7 @@ function validateBudgetCompetitivenessStrategies(data) {
|
|
|
134806
135764
|
const row = asRecord11(items3[i]);
|
|
134807
135765
|
const name2 = String(row?.name ?? row?.metric ?? i);
|
|
134808
135766
|
if (!hasNarrativeContent(row?.strategy)) {
|
|
134809
|
-
warnings.push(`budgetCompetitiveness[${name2}].strategy \u4E3A\u7A7A\
|
|
135767
|
+
warnings.push(`budgetCompetitiveness[${name2}].strategy \u4E3A\u7A7A\uFF08render \u4F1A\u6309\u672C\u671F\u6570\u503C\u4E0E\u6807\u51C6\u751F\u6210\uFF0C\u4E0D\u5E94\u7531 Agent \u586B\u5199\uFF09`);
|
|
134810
135768
|
}
|
|
134811
135769
|
}
|
|
134812
135770
|
return warnings;
|
|
@@ -134821,7 +135779,7 @@ function validateNewFeatureRecommendations(data) {
|
|
|
134821
135779
|
const row = asRecord11(items3[i]);
|
|
134822
135780
|
const name2 = String(row?.strategyName ?? row?.feature ?? row?.strategy ?? i);
|
|
134823
135781
|
if (!hasNarrativeContent(row?.optimizerRecommendation)) {
|
|
134824
|
-
warnings.push(`newFeatures.items[${name2}].optimizerRecommendation \u4E3A\u7A7A\
|
|
135782
|
+
warnings.push(`newFeatures.items[${name2}].optimizerRecommendation \u4E3A\u7A7A\uFF08render \u4F1A\u6309\u542F\u7528\u72B6\u6001\u751F\u6210\uFF0C\u4E0D\u5E94\u7531 Agent \u586B\u5199\uFF09`);
|
|
134825
135783
|
}
|
|
134826
135784
|
}
|
|
134827
135785
|
return warnings;
|
|
@@ -134856,7 +135814,7 @@ function validateAdCreativeItemSuggestions(data) {
|
|
|
134856
135814
|
const row = asRecord11(items3[i]);
|
|
134857
135815
|
const title = String(row?.adTitle ?? i);
|
|
134858
135816
|
if (!hasNarrativeContent(row?.suggestion ?? row?.suggestions)) {
|
|
134859
|
-
warnings.push(`adCreativeOptimization.items[${title}].suggestion \u4E3A\u7A7A\
|
|
135817
|
+
warnings.push(`adCreativeOptimization.items[${title}].suggestion \u4E3A\u7A7A\uFF08render \u4F1A\u6309\u6807\u9898/\u63CF\u8FF0\u6761\u6570\u751F\u6210\uFF0C\u4E0D\u5E94\u7531 Agent \u586B\u5199\uFF09`);
|
|
134860
135818
|
}
|
|
134861
135819
|
}
|
|
134862
135820
|
return warnings;
|
|
@@ -134960,7 +135918,7 @@ var MODULE_ITEM_ERROR_HINT = {
|
|
|
134960
135918
|
geographic: "\u7F3A\u5C11 title/currentCost/currentClicks \u6216\u73AF\u6BD4\u5B57\u6BB5\u88AB\u5199\u6210\u5B57\u7B26\u4E32\uFF08\u987B\u4FDD\u7559 collect \u4E8B\u5B9E\u884C\uFF0C\u4EC5\u7531 Agent \u586B\u5199 analysis/suggestions\uFF09",
|
|
134961
135919
|
keywords: "\u7F3A\u5C11 title/currentCost/currentClicks \u6216\u73AF\u6BD4\u5B57\u6BB5\u88AB\u5199\u6210\u5B57\u7B26\u4E32\uFF08\u987B\u4FDD\u7559 collect \u4E8B\u5B9E\u884C\uFF0C\u4EC5\u7531 Agent \u586B\u5199 analysis/suggestions\uFF09",
|
|
134962
135920
|
biddingStrategy: "\u7F3A\u5C11 campaignName/duration(\u5929\u6570)/biddingStrategyType\uFF08Agent \u52FF\u628A duration \u5199\u6210\u6587\u6848\u5B57\u7B26\u4E32\uFF0C\u52FF\u7528 currentStrategy \u66FF\u4EE3 biddingStrategyType\uFF09",
|
|
134963
|
-
newFeatures: "\u7F3A\u5C11 strategyName/strategy\uFF08collect \u4E8B\u5B9E\u884C\
|
|
135921
|
+
newFeatures: "\u7F3A\u5C11 strategyName/strategy\uFF08collect \u4E8B\u5B9E\u884C\uFF1BoptimizerRecommendation \u7531 render \u6309 accountStatus \u751F\u6210\uFF0C\u52FF\u6539\u7528 feature \u5B57\u6BB5\u66FF\u4EE3 strategyName\uFF09"
|
|
134964
135922
|
};
|
|
134965
135923
|
function moduleItemsNeedRestore(items3, validator) {
|
|
134966
135924
|
if (!Array.isArray(items3) || items3.length === 0) return false;
|
|
@@ -135124,6 +136082,61 @@ var NARRATIVE_CONSISTENCY_RULES = [
|
|
|
135124
136082
|
hint: "\u7740\u9646\u9875\u6D4B\u901F\u672A\u83B7\u53D6\u662F Lighthouse API \u5931\u8D25\uFF0C\u4E0D\u662F\u8D26\u6237\u6295\u653E\u6838\u5FC3\u95EE\u9898\u3002\u7981\u6B62\u5199\u5165 keyIssues/disadvantages\uFF1B\u4EC5\u5728 \xA707 landingPageAnalysis.analysis \u8BF4\u660E source=none\u3002"
|
|
135125
136083
|
}
|
|
135126
136084
|
];
|
|
136085
|
+
function goldAssetNoted(data, key) {
|
|
136086
|
+
const notes = asRecord13(asRecord13(data.goldAccount)?.goldAssetNotes);
|
|
136087
|
+
return notes?.[key] === true;
|
|
136088
|
+
}
|
|
136089
|
+
var GOLD_CLAIM_RULES = [
|
|
136090
|
+
{
|
|
136091
|
+
id: "ga-already-linked",
|
|
136092
|
+
textPattern: /(请将|请把|未将).{0,40}(Google Analytics|GA4|GA).{0,24}关联|(Google Analytics|GA4|GA).{0,12}未关联|未关联.{0,12}(Google Analytics|GA4|GA)/,
|
|
136093
|
+
isContradicted: (data) => goldAccountFlag(data, "gaConfigured") === true || goldAssetNoted(data, "ga4"),
|
|
136094
|
+
hint: "GA \u8054\u7ED3\u5DF2\u8FBE\u6807\uFF0C\u6216\u8F6C\u5316\u52A8\u4F5C\u91CC\u5DF2\u6709\u542F\u7528\u7684 Google Analytics \u8F6C\u5316\u3002\u7981\u6B62\u5199\u6210\u300C\u8BF7\u5173\u8054 / \u672A\u5173\u8054\u300D\u3002\u672A\u8FBE\u6807\u4E0D\u7B49\u4E8E\u8D26\u6237\u91CC\u6CA1\u6709\u8FD9\u4E9B\u8F6C\u5316\u3002"
|
|
136095
|
+
},
|
|
136096
|
+
{
|
|
136097
|
+
id: "text-ads-already-exist",
|
|
136098
|
+
textPattern: /请创建文字广告|没有文字广告|缺少文字广告|未创建文字广告/,
|
|
136099
|
+
isContradicted: (data) => goldAssetNoted(data, "textAds") || goldAccountFlag(data, "expandedTextAdQualified") === true || goldAccountFlag(data, "responsiveSearchAdQualified") === true,
|
|
136100
|
+
hint: "\u8D26\u6237\u5DF2\u6709\u641C\u7D22\u6587\u5B57\u5E7F\u544A\u3002\u6587\u5B57\u5E7F\u544A\u9879\u672A\u8FBE\u6807\u4E0D\u7B49\u4E8E\u6CA1\u6709\u5E7F\u544A\uFF0C\u7981\u6B62\u5199\u300C\u8BF7\u521B\u5EFA\u6587\u5B57\u5E7F\u544A\u300D\u3002"
|
|
136101
|
+
},
|
|
136102
|
+
{
|
|
136103
|
+
id: "callout-already-exists",
|
|
136104
|
+
textPattern: /请添加宣传信息|缺少宣传信息|没有宣传信息|未添加宣传信息/,
|
|
136105
|
+
isContradicted: (data) => goldAssetNoted(data, "callout") || goldAccountFlag(data, "adsCalloutExtensionQualified") === true,
|
|
136106
|
+
hint: "\u5DF2\u6709\u542F\u7528\u7684\u5BA3\u4F20\u4FE1\u606F\uFF0C\u6216\u8BE5\u9879\u5DF2\u8FBE\u6807\u3002\u7981\u6B62\u5199\u300C\u8BF7\u6DFB\u52A0\u5BA3\u4F20\u4FE1\u606F\u300D\u3002\u672A\u8FBE\u6807\u4E0D\u8868\u793A\u8D26\u6237\u91CC\u6CA1\u6709\u3002"
|
|
136107
|
+
},
|
|
136108
|
+
{
|
|
136109
|
+
id: "snippet-already-exists",
|
|
136110
|
+
textPattern: /请添加结构化信息摘要|缺少结构化信息摘要|没有结构化摘要|未添加结构化/,
|
|
136111
|
+
isContradicted: (data) => goldAssetNoted(data, "snippet") || goldAccountFlag(data, "adsStructuredSnippetExtensionQualified") === true,
|
|
136112
|
+
hint: "\u5DF2\u6709\u542F\u7528\u7684\u7ED3\u6784\u5316\u4FE1\u606F\u6458\u8981\uFF0C\u6216\u8BE5\u9879\u5DF2\u8FBE\u6807\u3002\u7981\u6B62\u5199\u300C\u8BF7\u6DFB\u52A0\u7ED3\u6784\u5316\u4FE1\u606F\u6458\u8981\u300D\u3002"
|
|
136113
|
+
},
|
|
136114
|
+
{
|
|
136115
|
+
id: "audience-already-exists",
|
|
136116
|
+
textPattern: /请设置受众|未设置受众|没有受众群体|缺少受众群体/,
|
|
136117
|
+
isContradicted: (data) => goldAssetNoted(data, "audience") || goldAccountFlag(data, "audienceQualified") === true,
|
|
136118
|
+
hint: "\u53D7\u4F17\u62A5\u8868\u91CC\u5DF2\u6709\u53D7\u4F17\uFF0C\u6216\u8BE5\u9879\u5DF2\u8FBE\u6807\u3002\u7981\u6B62\u5199\u300C\u8BF7\u8BBE\u7F6E\u53D7\u4F17\u7FA4\u4F53\u300D\u3002"
|
|
136119
|
+
},
|
|
136120
|
+
{
|
|
136121
|
+
id: "display-ads-already-exist",
|
|
136122
|
+
textPattern: /请使用自适应展示广告|没有自适应展示广告|未创建展示广告/,
|
|
136123
|
+
isContradicted: (data) => goldAssetNoted(data, "displayAds") || goldAccountFlag(data, "multiAssetResponsiveDisplayAdQualified") === true,
|
|
136124
|
+
hint: "\u5DF2\u6709\u81EA\u9002\u5E94\u5C55\u793A\u5E7F\u544A\u3002\u672A\u8FBE\u6807\u4E0D\u7B49\u4E8E\u6CA1\u6709\u5E7F\u544A\uFF1B\u7CFB\u5217\u6682\u505C\u65F6\u5199\u300C\u5C55\u793A\u7CFB\u5217\u5DF2\u6682\u505C\uFF0C\u672C\u671F\u65E0\u6D88\u8017\u300D\uFF0C\u7981\u6B62\u5199\u300C\u8BF7\u4F7F\u7528\u300D\u3002"
|
|
136125
|
+
}
|
|
136126
|
+
];
|
|
136127
|
+
function collectGoldClaimTexts(data) {
|
|
136128
|
+
const texts = collectNarrativeTexts(data);
|
|
136129
|
+
const overview = asRecord13(data.diagnosisOverview);
|
|
136130
|
+
for (const key of ["advantages", "disadvantages"]) {
|
|
136131
|
+
const items3 = overview?.[key];
|
|
136132
|
+
if (!Array.isArray(items3)) continue;
|
|
136133
|
+
for (const item of items3) {
|
|
136134
|
+
const rec = asRecord13(item);
|
|
136135
|
+
if (rec) texts.push(String(rec.title ?? ""), String(rec.description ?? ""));
|
|
136136
|
+
}
|
|
136137
|
+
}
|
|
136138
|
+
return texts.filter((text2) => text2.trim().length > 0);
|
|
136139
|
+
}
|
|
135127
136140
|
function findIdenticalTexts(texts, minCount) {
|
|
135128
136141
|
const counts = /* @__PURE__ */ new Map();
|
|
135129
136142
|
for (const t of texts) {
|
|
@@ -135185,6 +136198,13 @@ function getNarrativeConsistencyErrors(data) {
|
|
|
135185
136198
|
errors.push(`\u53D9\u4E8B\u77DB\u76FE\uFF08${rule.id}\uFF09\uFF1A\u300C${hitText}\u300D\u2014\u2014 ${rule.hint}`);
|
|
135186
136199
|
}
|
|
135187
136200
|
}
|
|
136201
|
+
const goldClaimTexts = collectGoldClaimTexts(data);
|
|
136202
|
+
for (const rule of GOLD_CLAIM_RULES) {
|
|
136203
|
+
const hitText = goldClaimTexts.find((t) => rule.textPattern.test(t));
|
|
136204
|
+
if (hitText && rule.isContradicted(data)) {
|
|
136205
|
+
errors.push(`\u53D9\u4E8B\u77DB\u76FE\uFF08${rule.id}\uFF09\uFF1A\u300C${hitText}\u300D\u2014\u2014 ${rule.hint}`);
|
|
136206
|
+
}
|
|
136207
|
+
}
|
|
135188
136208
|
errors.push(...getRepetitiveNarrativeErrors(data));
|
|
135189
136209
|
errors.push(...getLandingPageTopicErrors(data));
|
|
135190
136210
|
return errors;
|
|
@@ -135257,8 +136277,118 @@ function normalizeDiagnosisOverview(data) {
|
|
|
135257
136277
|
};
|
|
135258
136278
|
}
|
|
135259
136279
|
|
|
135260
|
-
// src/commands/google-ads-diagnosis/
|
|
136280
|
+
// src/commands/google-ads-diagnosis/data-rendered-copy.ts
|
|
135261
136281
|
function asRecord15(value) {
|
|
136282
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
136283
|
+
return value;
|
|
136284
|
+
}
|
|
136285
|
+
return null;
|
|
136286
|
+
}
|
|
136287
|
+
function compareToBenchmark(reportValue, benchmark) {
|
|
136288
|
+
const value = Number(reportValue.replace(/[^\d.]/g, ""));
|
|
136289
|
+
const matched = benchmark.match(/(≥|>=|≤|<=|>|<)\s*([\d.]+)/);
|
|
136290
|
+
if (!Number.isFinite(value) || !matched) return "\u5BF9\u7167\u6807\u51C6\u89C1\u672C\u884C";
|
|
136291
|
+
const bar = Number(matched[2]);
|
|
136292
|
+
const op = matched[1];
|
|
136293
|
+
const reached = op === "\u2265" || op === ">=" ? value >= bar : op === ">" ? value > bar : op === "\u2264" || op === "<=" ? value <= bar : value < bar;
|
|
136294
|
+
return reached ? "\u8FBE\u5230\u672C\u884C\u6807\u51C6" : "\u672A\u8FBE\u5230\u672C\u884C\u6807\u51C6";
|
|
136295
|
+
}
|
|
136296
|
+
function budgetStrategyFromRow(row) {
|
|
136297
|
+
const name2 = String(row.name ?? row.metric ?? "\u8BE5\u9879");
|
|
136298
|
+
const reportValue = String(row.reportValue ?? "\u2014");
|
|
136299
|
+
const benchmark = String(row.benchmark ?? "\u2014");
|
|
136300
|
+
return `${name2}\u672C\u671F\u4E3A ${reportValue}\uFF0C\u6807\u51C6 ${benchmark}\uFF0C${compareToBenchmark(reportValue, benchmark)}\u3002`;
|
|
136301
|
+
}
|
|
136302
|
+
function biddingReasonFromRow(row) {
|
|
136303
|
+
const name2 = String(row.campaignName ?? "\u8BE5\u7CFB\u5217");
|
|
136304
|
+
const current = String(row.biddingStrategyType ?? row.currentStrategy ?? "\u672A\u77E5");
|
|
136305
|
+
const recommended = String(row.recommendedStrategy ?? "\u2014");
|
|
136306
|
+
if (row.isCorrect === true) {
|
|
136307
|
+
return `${name2}\u5F53\u524D\u51FA\u4EF7\u7B56\u7565\u4E3A ${current}\uFF0C\u4E0E\u672C\u671F\u5EFA\u8BAE ${recommended} \u4E00\u81F4\u3002`;
|
|
136308
|
+
}
|
|
136309
|
+
return `${name2}\u5F53\u524D\u51FA\u4EF7\u7B56\u7565\u4E3A ${current}\uFF0C\u672C\u671F\u5BF9\u7167\u5EFA\u8BAE\u4E3A ${recommended}\u3002`;
|
|
136310
|
+
}
|
|
136311
|
+
function featureRecommendationFromRow(row) {
|
|
136312
|
+
const name2 = String(row.strategyName ?? row.strategy ?? "\u8BE5\u80FD\u529B");
|
|
136313
|
+
if (row.accountStatus === true) return `${name2}\uFF1A\u8D26\u6237\u5DF2\u542F\u7528\u3002`;
|
|
136314
|
+
return `${name2}\uFF1A\u672C\u671F\u672A\u68C0\u6D4B\u5230\u5BF9\u5E94\u7CFB\u5217\u3002`;
|
|
136315
|
+
}
|
|
136316
|
+
function creativeSuggestionFromRow(row) {
|
|
136317
|
+
const title = String(row.adTitle ?? "\u8BE5\u5E7F\u544A");
|
|
136318
|
+
const headlines = row.headlinesCount ?? "\u2014";
|
|
136319
|
+
const descriptions = row.descriptionsCount ?? "\u2014";
|
|
136320
|
+
const headlinesStatus = String(row.headlinesStatus ?? "\u2014");
|
|
136321
|
+
const descriptionsStatus = String(row.descriptionsStatus ?? "\u2014");
|
|
136322
|
+
return `${title}\uFF1A\u6807\u9898 ${headlines} \u6761\uFF08${headlinesStatus}\uFF09\uFF0C\u63CF\u8FF0 ${descriptions} \u6761\uFF08${descriptionsStatus}\uFF09\u3002`;
|
|
136323
|
+
}
|
|
136324
|
+
function distinctCopies(rows, key) {
|
|
136325
|
+
const counts = /* @__PURE__ */ new Map();
|
|
136326
|
+
for (const row of rows) {
|
|
136327
|
+
const text2 = String(row[key] ?? "");
|
|
136328
|
+
counts.set(text2, (counts.get(text2) ?? 0) + 1);
|
|
136329
|
+
}
|
|
136330
|
+
const seen = /* @__PURE__ */ new Map();
|
|
136331
|
+
return rows.map((row) => {
|
|
136332
|
+
const text2 = String(row[key] ?? "");
|
|
136333
|
+
if ((counts.get(text2) ?? 0) < 3) return row;
|
|
136334
|
+
const n = (seen.get(text2) ?? 0) + 1;
|
|
136335
|
+
seen.set(text2, n);
|
|
136336
|
+
return { ...row, [key]: `${text2}\uFF08\u540C\u7ED3\u6784\u7B2C ${n} \u6761\uFF09` };
|
|
136337
|
+
});
|
|
136338
|
+
}
|
|
136339
|
+
function mapRows(rows, write) {
|
|
136340
|
+
if (!Array.isArray(rows)) return rows;
|
|
136341
|
+
const written = rows.map((item) => {
|
|
136342
|
+
const row = asRecord15(item);
|
|
136343
|
+
return row ? write(row) : item;
|
|
136344
|
+
});
|
|
136345
|
+
return written;
|
|
136346
|
+
}
|
|
136347
|
+
function applyDataRenderedCopy(data) {
|
|
136348
|
+
const next = { ...data };
|
|
136349
|
+
const budgetRows = mapRows(data.budgetCompetitiveness, (row) => ({
|
|
136350
|
+
...row,
|
|
136351
|
+
strategy: budgetStrategyFromRow(row)
|
|
136352
|
+
}));
|
|
136353
|
+
if (Array.isArray(budgetRows)) {
|
|
136354
|
+
next.budgetCompetitiveness = distinctCopies(budgetRows, "strategy");
|
|
136355
|
+
}
|
|
136356
|
+
const bidding = asRecord15(data.biddingStrategy);
|
|
136357
|
+
if (bidding) {
|
|
136358
|
+
next.biddingStrategy = {
|
|
136359
|
+
...bidding,
|
|
136360
|
+
items: mapRows(bidding.items, (row) => ({
|
|
136361
|
+
...row,
|
|
136362
|
+
recommendationReason: biddingReasonFromRow(row)
|
|
136363
|
+
}))
|
|
136364
|
+
};
|
|
136365
|
+
}
|
|
136366
|
+
const features = asRecord15(data.newFeatures);
|
|
136367
|
+
if (features) {
|
|
136368
|
+
next.newFeatures = {
|
|
136369
|
+
...features,
|
|
136370
|
+
items: mapRows(features.items, (row) => ({
|
|
136371
|
+
...row,
|
|
136372
|
+
optimizerRecommendation: featureRecommendationFromRow(row)
|
|
136373
|
+
}))
|
|
136374
|
+
};
|
|
136375
|
+
}
|
|
136376
|
+
const creative = asRecord15(data.adCreativeOptimization);
|
|
136377
|
+
if (creative) {
|
|
136378
|
+
const creativeRows = mapRows(creative.items, (row) => ({
|
|
136379
|
+
...row,
|
|
136380
|
+
suggestion: creativeSuggestionFromRow(row)
|
|
136381
|
+
}));
|
|
136382
|
+
next.adCreativeOptimization = {
|
|
136383
|
+
...creative,
|
|
136384
|
+
items: Array.isArray(creativeRows) ? distinctCopies(creativeRows, "suggestion") : creativeRows
|
|
136385
|
+
};
|
|
136386
|
+
}
|
|
136387
|
+
return next;
|
|
136388
|
+
}
|
|
136389
|
+
|
|
136390
|
+
// src/commands/google-ads-diagnosis/render-report.ts
|
|
136391
|
+
function asRecord16(value) {
|
|
135262
136392
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
135263
136393
|
return value;
|
|
135264
136394
|
}
|
|
@@ -135267,8 +136397,8 @@ function asRecord15(value) {
|
|
|
135267
136397
|
async function tryLoadCollectReportData(collectPath) {
|
|
135268
136398
|
try {
|
|
135269
136399
|
const raw = JSON.parse(await fs22.readFile(collectPath, "utf8"));
|
|
135270
|
-
const root =
|
|
135271
|
-
const reportData =
|
|
136400
|
+
const root = asRecord16(raw);
|
|
136401
|
+
const reportData = asRecord16(root?.reportData);
|
|
135272
136402
|
return reportData ?? root;
|
|
135273
136403
|
} catch {
|
|
135274
136404
|
return null;
|
|
@@ -135285,7 +136415,7 @@ async function runGoogleAdsDiagnosisRender(opts) {
|
|
|
135285
136415
|
`);
|
|
135286
136416
|
process.exit(1);
|
|
135287
136417
|
}
|
|
135288
|
-
let data =
|
|
136418
|
+
let data = asRecord16(dataRaw);
|
|
135289
136419
|
if (!opts.noMergeCollect) {
|
|
135290
136420
|
const collectPath = path26.resolve(
|
|
135291
136421
|
opts.collectFile ?? path26.join(path26.dirname(dataPath), GOOGLE_ADS_DIAGNOSIS_COLLECT_BASENAME)
|
|
@@ -135318,6 +136448,7 @@ async function runGoogleAdsDiagnosisRender(opts) {
|
|
|
135318
136448
|
}
|
|
135319
136449
|
}
|
|
135320
136450
|
data = reconcileGoldAccountInReport(data, path26.dirname(dataPath));
|
|
136451
|
+
data = applyDataRenderedCopy(data);
|
|
135321
136452
|
const overview = normalizeDiagnosisOverview(data);
|
|
135322
136453
|
data = overview.data;
|
|
135323
136454
|
if (overview.coerced.length > 0) {
|
|
@@ -135365,7 +136496,7 @@ async function runGoogleAdsDiagnosisRender(opts) {
|
|
|
135365
136496
|
\u274C ${formatGoogleAdsDiagnosisContentErrors(contentCheck)}
|
|
135366
136497
|
`);
|
|
135367
136498
|
console.error(
|
|
135368
|
-
" \u987B\u5148\u7531 Agent \u8865\u5168\
|
|
136499
|
+
" \u987B\u5148\u7531 Agent \u8865\u5168\u4E3B\u89C2\u5206\u6790\uFF08\u5404\u6A21\u5757 analysis/suggestions\u3001\u8BCA\u65AD\u6982\u89C8\u3001\u603B\u7ED3\u3001\u4E1A\u52A1\u6A21\u5F0F\u4E0E\u884C\u4E1A\uFF09\u540E\u518D render\uFF1B\u8C03\u8BD5\u53EF\u52A0 --lenient\u3002\n"
|
|
135369
136500
|
);
|
|
135370
136501
|
process.exit(1);
|
|
135371
136502
|
}
|
|
@@ -136626,26 +137757,26 @@ import fs26 from "fs/promises";
|
|
|
136626
137757
|
import path31 from "path";
|
|
136627
137758
|
|
|
136628
137759
|
// src/commands/google-quarterly/report-content.ts
|
|
136629
|
-
function
|
|
137760
|
+
function asRecord17(value) {
|
|
136630
137761
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
136631
137762
|
}
|
|
136632
137763
|
function asStringArray(value) {
|
|
136633
137764
|
return Array.isArray(value) ? value.map((x) => String(x).trim()).filter(Boolean) : [];
|
|
136634
137765
|
}
|
|
136635
137766
|
function validateQuarterlyNarrative(data) {
|
|
136636
|
-
const root =
|
|
136637
|
-
const narrative =
|
|
137767
|
+
const root = asRecord17(data);
|
|
137768
|
+
const narrative = asRecord17(root?.narrative);
|
|
136638
137769
|
const missing = [];
|
|
136639
137770
|
if (!narrative) {
|
|
136640
137771
|
return { ok: false, missing: ["narrative"] };
|
|
136641
137772
|
}
|
|
136642
137773
|
if (asStringArray(narrative.executiveSummary).length < 1) missing.push("narrative.executiveSummary");
|
|
136643
|
-
const sections =
|
|
137774
|
+
const sections = asRecord17(narrative.sections);
|
|
136644
137775
|
for (const key of ["efficiency", "adType", "region", "summary"]) {
|
|
136645
|
-
const block =
|
|
137776
|
+
const block = asRecord17(sections?.[key]);
|
|
136646
137777
|
if (asStringArray(block?.analysis).length < 1) missing.push(`narrative.sections.${key}.analysis`);
|
|
136647
137778
|
}
|
|
136648
|
-
const plan =
|
|
137779
|
+
const plan = asRecord17(narrative.plan);
|
|
136649
137780
|
if (asStringArray(plan?.analysis ?? plan?.goals).length < 1 && !String(plan?.text ?? "").trim()) {
|
|
136650
137781
|
missing.push("narrative.plan");
|
|
136651
137782
|
}
|
|
@@ -136658,8 +137789,8 @@ function mergeCollectFacts(agentData, collect) {
|
|
|
136658
137789
|
...facts,
|
|
136659
137790
|
narrative: agentData.narrative ?? collect.narrative,
|
|
136660
137791
|
meta: {
|
|
136661
|
-
...
|
|
136662
|
-
...
|
|
137792
|
+
...asRecord17(collect.meta) ?? {},
|
|
137793
|
+
...asRecord17(agentData.meta) ?? {}
|
|
136663
137794
|
}
|
|
136664
137795
|
};
|
|
136665
137796
|
}
|
|
@@ -136671,7 +137802,7 @@ import { fileURLToPath as fileURLToPath8 } from "url";
|
|
|
136671
137802
|
var TSO_CLI_ROOT = path30.join(path30.dirname(fileURLToPath8(import.meta.url)), "../..");
|
|
136672
137803
|
|
|
136673
137804
|
// src/commands/google-quarterly/render-report.ts
|
|
136674
|
-
function
|
|
137805
|
+
function asRecord18(value) {
|
|
136675
137806
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
136676
137807
|
return value;
|
|
136677
137808
|
}
|
|
@@ -136700,7 +137831,7 @@ async function runGoogleQuarterlyRender(opts) {
|
|
|
136700
137831
|
`);
|
|
136701
137832
|
process.exit(1);
|
|
136702
137833
|
}
|
|
136703
|
-
const merged = mergeCollectFacts(
|
|
137834
|
+
const merged = mergeCollectFacts(asRecord18(agentRaw), asRecord18(collectRaw));
|
|
136704
137835
|
const check = validateQuarterlyNarrative(merged);
|
|
136705
137836
|
if (!check.ok && !opts.lenient) {
|
|
136706
137837
|
console.error("\n\u274C narrative \u4E0D\u5B8C\u6574\uFF0C\u62D2\u7EDD\u51FA HTML\uFF1A\n");
|
|
@@ -136864,7 +137995,7 @@ import * as fs33 from "fs/promises";
|
|
|
136864
137995
|
import * as path40 from "path";
|
|
136865
137996
|
|
|
136866
137997
|
// src/commands/facebook-analysis/report-metrics.ts
|
|
136867
|
-
function
|
|
137998
|
+
function asRecord24(value) {
|
|
136868
137999
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
136869
138000
|
return value;
|
|
136870
138001
|
}
|
|
@@ -137001,7 +138132,7 @@ function localizeFacebookReportLabels(payload) {
|
|
|
137001
138132
|
for (const rows of Object.values(tables)) {
|
|
137002
138133
|
if (!Array.isArray(rows)) continue;
|
|
137003
138134
|
for (const row of rows) {
|
|
137004
|
-
localizeMetricLabels(
|
|
138135
|
+
localizeMetricLabels(asRecord24(row));
|
|
137005
138136
|
}
|
|
137006
138137
|
}
|
|
137007
138138
|
return payload;
|
|
@@ -137034,7 +138165,7 @@ async function readManifest3(snapshotDir) {
|
|
|
137034
138165
|
async function loadSectionFile3(snapshotDir, file) {
|
|
137035
138166
|
try {
|
|
137036
138167
|
const raw = await fs33.readFile(path40.join(snapshotDir, file), "utf8");
|
|
137037
|
-
return
|
|
138168
|
+
return asRecord24(JSON.parse(raw));
|
|
137038
138169
|
} catch {
|
|
137039
138170
|
return null;
|
|
137040
138171
|
}
|
|
@@ -137090,7 +138221,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
137090
138221
|
if (data) sectionMap.set(art.section, data);
|
|
137091
138222
|
}
|
|
137092
138223
|
const overview = sectionMap.get("overview");
|
|
137093
|
-
const current =
|
|
138224
|
+
const current = asRecord24(overview?.currentPeriod);
|
|
137094
138225
|
const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
|
|
137095
138226
|
const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
|
|
137096
138227
|
const kpis = { ...payload.kpis ?? {} };
|
|
@@ -137227,7 +138358,7 @@ function writeSimpleXlsx(filePath, sheets, opts = {}) {
|
|
|
137227
138358
|
}
|
|
137228
138359
|
|
|
137229
138360
|
// src/commands/facebook-analysis/render-excel.ts
|
|
137230
|
-
function
|
|
138361
|
+
function asRecord25(value) {
|
|
137231
138362
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
137232
138363
|
return value;
|
|
137233
138364
|
}
|
|
@@ -137275,8 +138406,8 @@ function bullets(value) {
|
|
|
137275
138406
|
return asArray3(value).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
137276
138407
|
}
|
|
137277
138408
|
function insightLines(section) {
|
|
137278
|
-
const rec =
|
|
137279
|
-
const insight =
|
|
138409
|
+
const rec = asRecord25(section);
|
|
138410
|
+
const insight = asRecord25(rec?.insight) ?? rec;
|
|
137280
138411
|
return [...bullets(insight?.analysis), ...bullets(insight?.advice)];
|
|
137281
138412
|
}
|
|
137282
138413
|
function numberedSection(index, title, lines) {
|
|
@@ -137285,15 +138416,15 @@ function numberedSection(index, title, lines) {
|
|
|
137285
138416
|
${body}`;
|
|
137286
138417
|
}
|
|
137287
138418
|
function buildSummaryNarrative(data) {
|
|
137288
|
-
const kpis =
|
|
137289
|
-
const meta =
|
|
137290
|
-
const sections =
|
|
138419
|
+
const kpis = asRecord25(data.kpis) ?? {};
|
|
138420
|
+
const meta = asRecord25(data.meta) ?? {};
|
|
138421
|
+
const sections = asRecord25(data.sections) ?? {};
|
|
137291
138422
|
const period = str2(meta.periodLabel) || `${str2(meta.startDate)} ~ ${str2(meta.endDate)}`.trim();
|
|
137292
138423
|
const account = str2(meta.accountName) || "Facebook \u5E7F\u544A\u8D26\u6237";
|
|
137293
138424
|
const campaign = str2(meta.campaignName);
|
|
137294
138425
|
const resultLabel = displayResultType(kpis.resultType ?? meta.resultType) || "\u6210\u6548";
|
|
137295
138426
|
const resultNoun = /潜在客户|lead/i.test(resultLabel) ? "\u4E2A\u6F5C\u5728\u5BA2\u6237\u8868\u5355" : `\u4E2A${resultLabel}`;
|
|
137296
|
-
const firstQuestion =
|
|
138427
|
+
const firstQuestion = asRecord25(asArray3(data.fourQuestions)[0]);
|
|
137297
138428
|
const extra = str2(firstQuestion?.verdict);
|
|
137298
138429
|
const spendLine = [
|
|
137299
138430
|
campaign ? `\uFF08\u5E7F\u544A\u7CFB\u5217\uFF1A${campaign}\uFF09` : "",
|
|
@@ -137313,7 +138444,7 @@ function buildSummaryNarrative(data) {
|
|
|
137313
138444
|
numberedSection(3, "\u5E7F\u544A\u9891\u6B21\u4E0E\u65E5\u8D8B\u52BF", insightLines(sections.daily))
|
|
137314
138445
|
].join("\n");
|
|
137315
138446
|
const recBlocks = asArray3(data.recommendations).map((raw, i) => {
|
|
137316
|
-
const rec =
|
|
138447
|
+
const rec = asRecord25(raw);
|
|
137317
138448
|
if (!rec) return "";
|
|
137318
138449
|
const title = [str2(rec.title), str2(rec.tag)].filter(Boolean).join(" \xB7 ");
|
|
137319
138450
|
return numberedSection(i + 1, title || "\u4F18\u5316\u5EFA\u8BAE", bullets(rec.items));
|
|
@@ -137324,9 +138455,9 @@ function buildSummaryNarrative(data) {
|
|
|
137324
138455
|
return [partOne, partTwo, partThree];
|
|
137325
138456
|
}
|
|
137326
138457
|
function buildMetaPeriodExcelSheets(data) {
|
|
137327
|
-
const meta =
|
|
137328
|
-
const kpis =
|
|
137329
|
-
const tables =
|
|
138458
|
+
const meta = asRecord25(data.meta) ?? {};
|
|
138459
|
+
const kpis = asRecord25(data.kpis) ?? {};
|
|
138460
|
+
const tables = asRecord25(data.tables) ?? {};
|
|
137330
138461
|
const currency = str2(meta.currency) || "USD";
|
|
137331
138462
|
const resultType = displayResultType(kpis.resultType ?? meta.resultType);
|
|
137332
138463
|
const attribution = displayAttribution(kpis.attributionSetting ?? meta.attributionSetting);
|
|
@@ -137345,19 +138476,19 @@ function buildMetaPeriodExcelSheets(data) {
|
|
|
137345
138476
|
];
|
|
137346
138477
|
const dailyRows = [metricHeaders(currency, "\u5355\u65E5")];
|
|
137347
138478
|
for (const raw of asArray3(tables.daily)) {
|
|
137348
|
-
const row =
|
|
138479
|
+
const row = asRecord25(raw);
|
|
137349
138480
|
if (!row) continue;
|
|
137350
138481
|
dailyRows.push([normalizeInsightDate(row.date), ...metricCells(row)]);
|
|
137351
138482
|
}
|
|
137352
138483
|
const countryRows = [metricHeaders(currency, "\u56FD\u5BB6/\u5730\u533A")];
|
|
137353
138484
|
for (const raw of asArray3(tables.countries)) {
|
|
137354
|
-
const row =
|
|
138485
|
+
const row = asRecord25(raw);
|
|
137355
138486
|
if (!row) continue;
|
|
137356
138487
|
countryRows.push([str2(row.countryOrRegion), ...metricCells(row)]);
|
|
137357
138488
|
}
|
|
137358
138489
|
const campaignRows3 = [metricHeaders(currency, "\u5E7F\u544A\u7CFB\u5217\u540D\u79F0")];
|
|
137359
138490
|
for (const raw of asArray3(tables.campaigns)) {
|
|
137360
|
-
const row =
|
|
138491
|
+
const row = asRecord25(raw);
|
|
137361
138492
|
if (!row) continue;
|
|
137362
138493
|
campaignRows3.push([str2(row.campaignName), ...metricCells(row)]);
|
|
137363
138494
|
}
|
|
@@ -137376,7 +138507,7 @@ function buildMetaPeriodExcelSheets(data) {
|
|
|
137376
138507
|
]
|
|
137377
138508
|
];
|
|
137378
138509
|
for (const raw of asArray3(tables.audiences)) {
|
|
137379
|
-
const row =
|
|
138510
|
+
const row = asRecord25(raw);
|
|
137380
138511
|
if (!row) continue;
|
|
137381
138512
|
audienceRows.push([str2(row.age), str2(row.gender), ...metricCells(row)]);
|
|
137382
138513
|
}
|
|
@@ -137408,7 +138539,7 @@ function isFiniteNumber(value) {
|
|
|
137408
138539
|
function asArray4(value) {
|
|
137409
138540
|
return Array.isArray(value) ? value : [];
|
|
137410
138541
|
}
|
|
137411
|
-
function
|
|
138542
|
+
function asRecord26(value) {
|
|
137412
138543
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
137413
138544
|
return value;
|
|
137414
138545
|
}
|
|
@@ -137421,8 +138552,8 @@ function stringList3(value) {
|
|
|
137421
138552
|
return asArray4(value).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
137422
138553
|
}
|
|
137423
138554
|
function insightLists(section) {
|
|
137424
|
-
const rec =
|
|
137425
|
-
const insight =
|
|
138555
|
+
const rec = asRecord26(section);
|
|
138556
|
+
const insight = asRecord26(rec?.insight) ?? rec;
|
|
137426
138557
|
return {
|
|
137427
138558
|
analysis: stringList3(insight?.analysis),
|
|
137428
138559
|
advice: stringList3(insight?.advice)
|
|
@@ -137452,11 +138583,11 @@ function requireInsight(missing, key, chapter, section, required) {
|
|
|
137452
138583
|
}
|
|
137453
138584
|
function validateMetaPeriodReportContent(data) {
|
|
137454
138585
|
const missing = [];
|
|
137455
|
-
const meta =
|
|
137456
|
-
const kpis =
|
|
137457
|
-
const tables =
|
|
137458
|
-
const charts =
|
|
137459
|
-
const sections =
|
|
138586
|
+
const meta = asRecord26(data.meta) ?? {};
|
|
138587
|
+
const kpis = asRecord26(data.kpis) ?? {};
|
|
138588
|
+
const tables = asRecord26(data.tables) ?? {};
|
|
138589
|
+
const charts = asRecord26(data.charts) ?? {};
|
|
138590
|
+
const sections = asRecord26(data.sections) ?? {};
|
|
137460
138591
|
const fourQuestions = asArray4(data.fourQuestions);
|
|
137461
138592
|
const recommendations = asArray4(data.recommendations);
|
|
137462
138593
|
if (!textLen3(meta.accountName) && !textLen3(meta.accountId)) {
|
|
@@ -137535,7 +138666,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
137535
138666
|
"\u987B\u6709 tables.audiences \u6570\u7EC4\uFF1B\u62C9\u6570\u52A0 --sections audience\uFF0Crender \u65F6\u4F20 --snapshot-dir\u3002\u672C\u671F\u65E0\u6D88\u8017\u53EF\u4E3A []"
|
|
137536
138667
|
);
|
|
137537
138668
|
}
|
|
137538
|
-
const dailyChart =
|
|
138669
|
+
const dailyChart = asRecord26(charts.daily);
|
|
137539
138670
|
if (!Array.isArray(dailyChart?.labels)) {
|
|
137540
138671
|
pushMissing3(
|
|
137541
138672
|
missing,
|
|
@@ -137545,7 +138676,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
137545
138676
|
"\u987B\u6709 charts.daily.labels \u6570\u7EC4\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 daily\u3002\u672C\u671F\u65E0\u6D88\u8017\u53EF\u4E3A []"
|
|
137546
138677
|
);
|
|
137547
138678
|
}
|
|
137548
|
-
const countryChart =
|
|
138679
|
+
const countryChart = asRecord26(charts.country);
|
|
137549
138680
|
if (!Array.isArray(countryChart?.labels)) {
|
|
137550
138681
|
pushMissing3(
|
|
137551
138682
|
missing,
|
|
@@ -137555,7 +138686,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
137555
138686
|
"\u987B\u6709 charts.country.labels \u6570\u7EC4\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 country\u3002\u672C\u671F\u65E0\u6D88\u8017\u53EF\u4E3A []"
|
|
137556
138687
|
);
|
|
137557
138688
|
}
|
|
137558
|
-
const audienceChart =
|
|
138689
|
+
const audienceChart = asRecord26(charts.audience);
|
|
137559
138690
|
if (!Array.isArray(audienceChart?.labels)) {
|
|
137560
138691
|
pushMissing3(
|
|
137561
138692
|
missing,
|
|
@@ -137574,7 +138705,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
137574
138705
|
"\u5FC5\u987B\u6070\u597D 4 \u95EE\uFF1A\u94B1\u82B1\u5F97\u503C\u4E0D\u503C / \u8C01\u771F\u7684\u60F3\u4E70 / \u5E7F\u544A\u8FD8\u65B0\u9C9C\u5417 / \u7528\u6237\u4E3A\u4EC0\u4E48\u4E0D\u7559\u8D44"
|
|
137575
138706
|
);
|
|
137576
138707
|
} else {
|
|
137577
|
-
const titles = fourQuestions.map((q) =>
|
|
138708
|
+
const titles = fourQuestions.map((q) => asRecord26(q)?.title);
|
|
137578
138709
|
for (const title of FOUR_QUESTION_TITLES) {
|
|
137579
138710
|
if (!titles.includes(title)) {
|
|
137580
138711
|
pushMissing3(
|
|
@@ -137587,7 +138718,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
137587
138718
|
}
|
|
137588
138719
|
}
|
|
137589
138720
|
for (let i = 0; i < fourQuestions.length; i++) {
|
|
137590
|
-
const q =
|
|
138721
|
+
const q = asRecord26(fourQuestions[i]);
|
|
137591
138722
|
if (textLen3(q?.verdict) < 8) {
|
|
137592
138723
|
pushMissing3(
|
|
137593
138724
|
missing,
|
|
@@ -137627,7 +138758,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
137627
138758
|
);
|
|
137628
138759
|
} else {
|
|
137629
138760
|
for (let i = 0; i < recommendations.length; i++) {
|
|
137630
|
-
const rec =
|
|
138761
|
+
const rec = asRecord26(recommendations[i]);
|
|
137631
138762
|
if (textLen3(rec?.title) < 2) {
|
|
137632
138763
|
pushMissing3(
|
|
137633
138764
|
missing,
|
|
@@ -137720,7 +138851,7 @@ async function readJsonFile4(filePath) {
|
|
|
137720
138851
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
137721
138852
|
}
|
|
137722
138853
|
}
|
|
137723
|
-
function
|
|
138854
|
+
function asRecord27(value) {
|
|
137724
138855
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
137725
138856
|
return value;
|
|
137726
138857
|
}
|
|
@@ -137749,7 +138880,7 @@ function defaultOutPath(dataPath, format, out) {
|
|
|
137749
138880
|
}
|
|
137750
138881
|
async function runFacebookAnalysisRender(opts) {
|
|
137751
138882
|
const dataPath = path41.resolve(opts.dataFile);
|
|
137752
|
-
let data =
|
|
138883
|
+
let data = asRecord27(await readJsonFile4(dataPath));
|
|
137753
138884
|
if (opts.snapshotDir?.trim()) {
|
|
137754
138885
|
data = await mergeFacebookSnapshotIntoReport(data, path41.resolve(opts.snapshotDir));
|
|
137755
138886
|
}
|
|
@@ -137813,7 +138944,7 @@ import { fileURLToPath as fileURLToPath11 } from "url";
|
|
|
137813
138944
|
// src/commands/facebook-analysis/diagnosis-merge-snapshot.ts
|
|
137814
138945
|
import * as fs35 from "fs/promises";
|
|
137815
138946
|
import * as path42 from "path";
|
|
137816
|
-
function
|
|
138947
|
+
function asRecord28(value) {
|
|
137817
138948
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
137818
138949
|
return value;
|
|
137819
138950
|
}
|
|
@@ -137824,7 +138955,7 @@ function num8(value) {
|
|
|
137824
138955
|
return Number.isFinite(n) ? n : void 0;
|
|
137825
138956
|
}
|
|
137826
138957
|
function asRows(value) {
|
|
137827
|
-
return Array.isArray(value) ? value.filter((r) =>
|
|
138958
|
+
return Array.isArray(value) ? value.filter((r) => asRecord28(r) !== null) : [];
|
|
137828
138959
|
}
|
|
137829
138960
|
function pickMetricExtras(row, extras) {
|
|
137830
138961
|
const out = {
|
|
@@ -137873,14 +139004,14 @@ async function readManifest4(snapshotDir, accountId) {
|
|
|
137873
139004
|
async function loadSectionFile4(snapshotDir, file) {
|
|
137874
139005
|
try {
|
|
137875
139006
|
const raw = await fs35.readFile(path42.join(snapshotDir, file), "utf8");
|
|
137876
|
-
return
|
|
139007
|
+
return asRecord28(JSON.parse(raw));
|
|
137877
139008
|
} catch {
|
|
137878
139009
|
return null;
|
|
137879
139010
|
}
|
|
137880
139011
|
}
|
|
137881
139012
|
async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
|
|
137882
139013
|
const absDir = path42.resolve(snapshotDir);
|
|
137883
|
-
const metaAccountId = typeof
|
|
139014
|
+
const metaAccountId = typeof asRecord28(payload.meta)?.accountId === "string" ? asRecord28(payload.meta)?.accountId : void 0;
|
|
137884
139015
|
const manifest = await readManifest4(snapshotDir, metaAccountId);
|
|
137885
139016
|
if (!manifest) {
|
|
137886
139017
|
throw new Error(
|
|
@@ -137893,8 +139024,8 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
137893
139024
|
if (data) sectionMap.set(art.section, data);
|
|
137894
139025
|
}
|
|
137895
139026
|
const overview = sectionMap.get("overview");
|
|
137896
|
-
const currentPeriod =
|
|
137897
|
-
const previousPeriod =
|
|
139027
|
+
const currentPeriod = asRecord28(overview?.currentPeriod);
|
|
139028
|
+
const previousPeriod = asRecord28(overview?.previousPeriod);
|
|
137898
139029
|
const accountId = manifest.accountId ?? metaAccountId;
|
|
137899
139030
|
const meta = { ...payload.meta ?? {} };
|
|
137900
139031
|
if (!meta.accountId && accountId) meta.accountId = accountId;
|
|
@@ -138028,7 +139159,7 @@ function textLen4(value) {
|
|
|
138028
139159
|
function asArray5(value) {
|
|
138029
139160
|
return Array.isArray(value) ? value : [];
|
|
138030
139161
|
}
|
|
138031
|
-
function
|
|
139162
|
+
function asRecord29(value) {
|
|
138032
139163
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
138033
139164
|
return value;
|
|
138034
139165
|
}
|
|
@@ -138039,9 +139170,9 @@ function pushMissing4(missing, id, chapter, dimension, hint) {
|
|
|
138039
139170
|
}
|
|
138040
139171
|
function validateMetaDiagnosisReportContent(data) {
|
|
138041
139172
|
const missing = [];
|
|
138042
|
-
const meta =
|
|
138043
|
-
const narrative =
|
|
138044
|
-
const sections =
|
|
139173
|
+
const meta = asRecord29(data.meta) ?? {};
|
|
139174
|
+
const narrative = asRecord29(data.narrative) ?? {};
|
|
139175
|
+
const sections = asRecord29(narrative.sections) ?? {};
|
|
138045
139176
|
if (!textLen4(meta.accountId)) {
|
|
138046
139177
|
pushMissing4(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
|
|
138047
139178
|
}
|
|
@@ -138059,7 +139190,7 @@ function validateMetaDiagnosisReportContent(data) {
|
|
|
138059
139190
|
const sectionSuggestionCounts = {};
|
|
138060
139191
|
const sectionNotAvailable = {};
|
|
138061
139192
|
for (const key of NORMAL_SECTION_KEYS) {
|
|
138062
|
-
const section =
|
|
139193
|
+
const section = asRecord29(sections[key]);
|
|
138063
139194
|
const analysis = asArray5(section?.analysis).filter((s) => textLen4(s) > 0);
|
|
138064
139195
|
const suggestions = asArray5(section?.suggestions).filter((s) => textLen4(s) > 0);
|
|
138065
139196
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -138085,7 +139216,7 @@ function validateMetaDiagnosisReportContent(data) {
|
|
|
138085
139216
|
}
|
|
138086
139217
|
}
|
|
138087
139218
|
for (const key of NA_SECTION_KEYS) {
|
|
138088
|
-
const section =
|
|
139219
|
+
const section = asRecord29(sections[key]);
|
|
138089
139220
|
const analysis = asArray5(section?.analysis).filter((s) => textLen4(s) > 0);
|
|
138090
139221
|
const suggestions = asArray5(section?.suggestions).filter((s) => textLen4(s) > 0);
|
|
138091
139222
|
const notAvailable = section?.notAvailable === true;
|
|
@@ -138107,7 +139238,7 @@ function validateMetaDiagnosisReportContent(data) {
|
|
|
138107
139238
|
}
|
|
138108
139239
|
const recommendations = asArray5(narrative.recommendations).filter((r) => {
|
|
138109
139240
|
if (typeof r === "string") return textLen4(r) > 0;
|
|
138110
|
-
const rec =
|
|
139241
|
+
const rec = asRecord29(r);
|
|
138111
139242
|
return textLen4(rec?.content) > 0;
|
|
138112
139243
|
});
|
|
138113
139244
|
if (recommendations.length < 3) {
|
|
@@ -138180,7 +139311,7 @@ async function readJsonFile5(filePath) {
|
|
|
138180
139311
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
138181
139312
|
}
|
|
138182
139313
|
}
|
|
138183
|
-
function
|
|
139314
|
+
function asRecord30(value) {
|
|
138184
139315
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
138185
139316
|
return value;
|
|
138186
139317
|
}
|
|
@@ -138195,7 +139326,7 @@ function injectReportData4(html, payload) {
|
|
|
138195
139326
|
}
|
|
138196
139327
|
async function runFacebookAnalysisDiagnosisRender(opts) {
|
|
138197
139328
|
const dataPath = path43.resolve(opts.dataFile);
|
|
138198
|
-
let data =
|
|
139329
|
+
let data = asRecord30(await readJsonFile5(dataPath));
|
|
138199
139330
|
if (opts.snapshotDir?.trim()) {
|
|
138200
139331
|
data = await mergeMetaDiagnosisSnapshotIntoReport(data, path43.resolve(opts.snapshotDir));
|
|
138201
139332
|
}
|
|
@@ -138394,7 +139525,7 @@ function resolveFacebookPageSize(raw, fallback = FB_ENTITY_PAGE_SIZE_DEFAULT) {
|
|
|
138394
139525
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
138395
139526
|
return Math.min(Math.max(Math.floor(n), 1), FB_ENTITY_PAGE_SIZE_MAX);
|
|
138396
139527
|
}
|
|
138397
|
-
function
|
|
139528
|
+
function asRecord31(value) {
|
|
138398
139529
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
138399
139530
|
}
|
|
138400
139531
|
function pickProp(obj, ...keys) {
|
|
@@ -138404,12 +139535,12 @@ function pickProp(obj, ...keys) {
|
|
|
138404
139535
|
return void 0;
|
|
138405
139536
|
}
|
|
138406
139537
|
function unwrapFacebookPaged(raw) {
|
|
138407
|
-
const rec =
|
|
139538
|
+
const rec = asRecord31(raw);
|
|
138408
139539
|
if (!rec) return { items: [] };
|
|
138409
139540
|
const data = pickProp(rec, "data", "Data");
|
|
138410
139541
|
const items3 = Array.isArray(data) ? data : [];
|
|
138411
|
-
const paging =
|
|
138412
|
-
const cursors = paging ?
|
|
139542
|
+
const paging = asRecord31(pickProp(rec, "paging", "Paging"));
|
|
139543
|
+
const cursors = paging ? asRecord31(pickProp(paging, "cursors", "Cursors")) : void 0;
|
|
138413
139544
|
const afterRaw = cursors ? pickProp(cursors, "after", "After") : void 0;
|
|
138414
139545
|
const after = typeof afterRaw === "string" && afterRaw.trim() ? afterRaw.trim() : void 0;
|
|
138415
139546
|
return { items: items3, after };
|
|
@@ -138557,7 +139688,7 @@ async function fetchFacebookInsights(config, digits, level, opts) {
|
|
|
138557
139688
|
opts.verbose
|
|
138558
139689
|
);
|
|
138559
139690
|
if (Array.isArray(raw)) return raw;
|
|
138560
|
-
const rec =
|
|
139691
|
+
const rec = asRecord31(raw);
|
|
138561
139692
|
const data = rec ? pickProp(rec, "data", "Data") : void 0;
|
|
138562
139693
|
return Array.isArray(data) ? data : [];
|
|
138563
139694
|
}
|
|
@@ -138566,7 +139697,7 @@ function sumActionArray(raw) {
|
|
|
138566
139697
|
let sum = 0;
|
|
138567
139698
|
let hit = false;
|
|
138568
139699
|
for (const item of raw) {
|
|
138569
|
-
const rec =
|
|
139700
|
+
const rec = asRecord31(item);
|
|
138570
139701
|
if (!rec) continue;
|
|
138571
139702
|
const n = Number(rec.value);
|
|
138572
139703
|
if (!Number.isFinite(n)) continue;
|
|
@@ -139845,7 +140976,7 @@ import { fileURLToPath as fileURLToPath12 } from "url";
|
|
|
139845
140976
|
// src/commands/bing-analysis/merge-snapshot.ts
|
|
139846
140977
|
import * as fs37 from "fs/promises";
|
|
139847
140978
|
import * as path46 from "path";
|
|
139848
|
-
function
|
|
140979
|
+
function asRecord32(value) {
|
|
139849
140980
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
139850
140981
|
return value;
|
|
139851
140982
|
}
|
|
@@ -139860,7 +140991,7 @@ function round27(value) {
|
|
|
139860
140991
|
}
|
|
139861
140992
|
function asRows2(value) {
|
|
139862
140993
|
if (Array.isArray(value)) {
|
|
139863
|
-
return value.filter((r) =>
|
|
140994
|
+
return value.filter((r) => asRecord32(r) !== null);
|
|
139864
140995
|
}
|
|
139865
140996
|
return [];
|
|
139866
140997
|
}
|
|
@@ -139942,17 +141073,17 @@ function campaignStatusDisplay(status) {
|
|
|
139942
141073
|
return status == null || status === "" ? "\u2014" : String(status);
|
|
139943
141074
|
}
|
|
139944
141075
|
function mapDeviceRows(payload) {
|
|
139945
|
-
const obj =
|
|
141076
|
+
const obj = asRecord32(payload);
|
|
139946
141077
|
return asRows2(obj?.devices).map((r) => pickMetrics(r, ["deviceType"]));
|
|
139947
141078
|
}
|
|
139948
141079
|
function mapGeographicRows(payload) {
|
|
139949
|
-
const obj =
|
|
141080
|
+
const obj = asRecord32(payload);
|
|
139950
141081
|
return asRows2(obj?.countries).map(
|
|
139951
141082
|
(r) => pickMetrics(r, ["countryOrRegion", "countryCriteriaId", "regionName", "cityName"])
|
|
139952
141083
|
);
|
|
139953
141084
|
}
|
|
139954
141085
|
function mapAudienceLabelRows(payload, labelKey) {
|
|
139955
|
-
const obj =
|
|
141086
|
+
const obj = asRecord32(payload);
|
|
139956
141087
|
return asRows2(obj?.audience).map((r) => {
|
|
139957
141088
|
const m = pickMetrics(r, ["audience", "bidModifier"]);
|
|
139958
141089
|
m[labelKey] = r.audience;
|
|
@@ -139960,7 +141091,7 @@ function mapAudienceLabelRows(payload, labelKey) {
|
|
|
139960
141091
|
});
|
|
139961
141092
|
}
|
|
139962
141093
|
function mapAudienceFromMerged(payload) {
|
|
139963
|
-
const data =
|
|
141094
|
+
const data = asRecord32(asRecord32(payload)?.data);
|
|
139964
141095
|
return {
|
|
139965
141096
|
audienceAge: mapAudienceLabelRows(data?.ageAudience, "ageRange"),
|
|
139966
141097
|
audienceGender: mapAudienceLabelRows(data?.genderAudience, "gender")
|
|
@@ -140055,7 +141186,7 @@ function mapSearchTermRows(payload) {
|
|
|
140055
141186
|
}
|
|
140056
141187
|
async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
140057
141188
|
const absDir = path46.resolve(snapshotDir);
|
|
140058
|
-
const metaAccountId = typeof
|
|
141189
|
+
const metaAccountId = typeof asRecord32(payload.meta)?.accountId === "string" ? asRecord32(payload.meta)?.accountId : void 0;
|
|
140059
141190
|
const manifest = await readManifest5(snapshotDir, metaAccountId);
|
|
140060
141191
|
if (!manifest) {
|
|
140061
141192
|
throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 report-manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C bing-analysis --json-out`);
|
|
@@ -140065,9 +141196,9 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
140065
141196
|
const data = await loadSectionPayload(absDir, art.file);
|
|
140066
141197
|
if (data != null) sectionMap.set(art.section, data);
|
|
140067
141198
|
}
|
|
140068
|
-
const overview =
|
|
140069
|
-
const currentPeriod =
|
|
140070
|
-
const previousPeriod =
|
|
141199
|
+
const overview = asRecord32(sectionMap.get("overview"));
|
|
141200
|
+
const currentPeriod = asRecord32(overview?.currentPeriod);
|
|
141201
|
+
const previousPeriod = asRecord32(overview?.previousPeriod);
|
|
140071
141202
|
const accountId = manifest.accountId ?? metaAccountId;
|
|
140072
141203
|
const campaignRows3 = mapCampaignRows2(sectionMap.get("campaigns"));
|
|
140073
141204
|
const campaignKpis = sumBingCampaignRowsToPeriodKpis(campaignRows3);
|
|
@@ -140194,7 +141325,7 @@ function textLen5(value) {
|
|
|
140194
141325
|
function asArray6(value) {
|
|
140195
141326
|
return Array.isArray(value) ? value : [];
|
|
140196
141327
|
}
|
|
140197
|
-
function
|
|
141328
|
+
function asRecord33(value) {
|
|
140198
141329
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
140199
141330
|
return value;
|
|
140200
141331
|
}
|
|
@@ -140205,9 +141336,9 @@ function pushMissing5(missing, id, chapter, dimension, hint) {
|
|
|
140205
141336
|
}
|
|
140206
141337
|
function validateBingPeriodReportContent(data) {
|
|
140207
141338
|
const missing = [];
|
|
140208
|
-
const meta =
|
|
140209
|
-
const narrative =
|
|
140210
|
-
const sections =
|
|
141339
|
+
const meta = asRecord33(data.meta) ?? {};
|
|
141340
|
+
const narrative = asRecord33(data.narrative) ?? {};
|
|
141341
|
+
const sections = asRecord33(narrative.sections) ?? {};
|
|
140211
141342
|
if (!textLen5(meta.accountId)) {
|
|
140212
141343
|
pushMissing5(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
|
|
140213
141344
|
}
|
|
@@ -140224,7 +141355,7 @@ function validateBingPeriodReportContent(data) {
|
|
|
140224
141355
|
const sectionAnalysisCounts = {};
|
|
140225
141356
|
const sectionSuggestionCounts = {};
|
|
140226
141357
|
for (const key of SECTION_KEYS2) {
|
|
140227
|
-
const section =
|
|
141358
|
+
const section = asRecord33(sections[key]);
|
|
140228
141359
|
const analysis = asArray6(section?.analysis).filter((s) => textLen5(s) > 0);
|
|
140229
141360
|
const suggestions = asArray6(section?.suggestions).filter((s) => textLen5(s) > 0);
|
|
140230
141361
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -140250,7 +141381,7 @@ function validateBingPeriodReportContent(data) {
|
|
|
140250
141381
|
}
|
|
140251
141382
|
const recommendations = asArray6(narrative.recommendations).filter((r) => {
|
|
140252
141383
|
if (typeof r === "string") return textLen5(r) > 0;
|
|
140253
|
-
const rec =
|
|
141384
|
+
const rec = asRecord33(r);
|
|
140254
141385
|
return textLen5(rec?.content) > 0;
|
|
140255
141386
|
});
|
|
140256
141387
|
if (recommendations.length < 3) {
|
|
@@ -140311,7 +141442,7 @@ async function readJsonFile6(filePath) {
|
|
|
140311
141442
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
140312
141443
|
}
|
|
140313
141444
|
}
|
|
140314
|
-
function
|
|
141445
|
+
function asRecord34(value) {
|
|
140315
141446
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
140316
141447
|
return value;
|
|
140317
141448
|
}
|
|
@@ -140326,7 +141457,7 @@ function injectReportData5(html, payload) {
|
|
|
140326
141457
|
}
|
|
140327
141458
|
async function runBingAnalysisRender(opts) {
|
|
140328
141459
|
const dataPath = path47.resolve(opts.dataFile);
|
|
140329
|
-
let data =
|
|
141460
|
+
let data = asRecord34(await readJsonFile6(dataPath));
|
|
140330
141461
|
if (opts.snapshotDir?.trim()) {
|
|
140331
141462
|
data = await mergeBingAnalysisSnapshotIntoReport(data, path47.resolve(opts.snapshotDir));
|
|
140332
141463
|
}
|
|
@@ -140799,7 +141930,7 @@ import { fileURLToPath as fileURLToPath13 } from "url";
|
|
|
140799
141930
|
// src/commands/tiktok-analysis/merge-snapshot.ts
|
|
140800
141931
|
import * as fs39 from "fs/promises";
|
|
140801
141932
|
import * as path49 from "path";
|
|
140802
|
-
function
|
|
141933
|
+
function asRecord35(value) {
|
|
140803
141934
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
140804
141935
|
return value;
|
|
140805
141936
|
}
|
|
@@ -140977,7 +142108,7 @@ function mapAudienceMerged(payload) {
|
|
|
140977
142108
|
}
|
|
140978
142109
|
async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
140979
142110
|
const absDir = path49.resolve(snapshotDir);
|
|
140980
|
-
const metaAccountId = typeof
|
|
142111
|
+
const metaAccountId = typeof asRecord35(payload.meta)?.accountId === "string" ? asRecord35(payload.meta)?.accountId : void 0;
|
|
140981
142112
|
const manifest = await readManifest6(snapshotDir, metaAccountId);
|
|
140982
142113
|
if (!manifest) {
|
|
140983
142114
|
throw new Error(
|
|
@@ -141091,7 +142222,7 @@ function textLen6(value) {
|
|
|
141091
142222
|
function asArray7(value) {
|
|
141092
142223
|
return Array.isArray(value) ? value : [];
|
|
141093
142224
|
}
|
|
141094
|
-
function
|
|
142225
|
+
function asRecord36(value) {
|
|
141095
142226
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
141096
142227
|
return value;
|
|
141097
142228
|
}
|
|
@@ -141102,9 +142233,9 @@ function pushMissing6(missing, id, chapter, dimension, hint) {
|
|
|
141102
142233
|
}
|
|
141103
142234
|
function validateTikTokPeriodReportContent(data) {
|
|
141104
142235
|
const missing = [];
|
|
141105
|
-
const meta =
|
|
141106
|
-
const narrative =
|
|
141107
|
-
const sections =
|
|
142236
|
+
const meta = asRecord36(data.meta) ?? {};
|
|
142237
|
+
const narrative = asRecord36(data.narrative) ?? {};
|
|
142238
|
+
const sections = asRecord36(narrative.sections) ?? {};
|
|
141108
142239
|
if (!textLen6(meta.accountId)) {
|
|
141109
142240
|
pushMissing6(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
|
|
141110
142241
|
}
|
|
@@ -141121,7 +142252,7 @@ function validateTikTokPeriodReportContent(data) {
|
|
|
141121
142252
|
const sectionAnalysisCounts = {};
|
|
141122
142253
|
const sectionSuggestionCounts = {};
|
|
141123
142254
|
for (const key of SECTION_KEYS3) {
|
|
141124
|
-
const section =
|
|
142255
|
+
const section = asRecord36(sections[key]);
|
|
141125
142256
|
const analysis = asArray7(section?.analysis).filter((s) => textLen6(s) > 0);
|
|
141126
142257
|
const suggestions = asArray7(section?.suggestions).filter((s) => textLen6(s) > 0);
|
|
141127
142258
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -141147,7 +142278,7 @@ function validateTikTokPeriodReportContent(data) {
|
|
|
141147
142278
|
}
|
|
141148
142279
|
const recommendations = asArray7(narrative.recommendations).filter((r) => {
|
|
141149
142280
|
if (typeof r === "string") return textLen6(r) > 0;
|
|
141150
|
-
const rec =
|
|
142281
|
+
const rec = asRecord36(r);
|
|
141151
142282
|
return textLen6(rec?.content) > 0;
|
|
141152
142283
|
});
|
|
141153
142284
|
if (recommendations.length < 3) {
|
|
@@ -141208,7 +142339,7 @@ async function readJsonFile7(filePath) {
|
|
|
141208
142339
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
141209
142340
|
}
|
|
141210
142341
|
}
|
|
141211
|
-
function
|
|
142342
|
+
function asRecord37(value) {
|
|
141212
142343
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
141213
142344
|
return value;
|
|
141214
142345
|
}
|
|
@@ -141223,7 +142354,7 @@ function injectReportData6(html, payload) {
|
|
|
141223
142354
|
}
|
|
141224
142355
|
async function runTikTokAnalysisRender(opts) {
|
|
141225
142356
|
const dataPath = path50.resolve(opts.dataFile);
|
|
141226
|
-
let data =
|
|
142357
|
+
let data = asRecord37(await readJsonFile7(dataPath));
|
|
141227
142358
|
if (opts.snapshotDir?.trim()) {
|
|
141228
142359
|
data = await mergeTikTokAnalysisSnapshotIntoReport(data, path50.resolve(opts.snapshotDir));
|
|
141229
142360
|
}
|
|
@@ -142322,7 +143453,7 @@ function endpointHintForYandexSection(def) {
|
|
|
142322
143453
|
// src/commands/yandex-analysis/merge-snapshot.ts
|
|
142323
143454
|
import * as fs41 from "fs/promises";
|
|
142324
143455
|
import * as path51 from "path";
|
|
142325
|
-
function
|
|
143456
|
+
function asRecord38(value) {
|
|
142326
143457
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
142327
143458
|
return value;
|
|
142328
143459
|
}
|
|
@@ -142342,11 +143473,11 @@ function normalizeYandexRoi(value) {
|
|
|
142342
143473
|
return r === -1 ? void 0 : r;
|
|
142343
143474
|
}
|
|
142344
143475
|
function asItems(payload) {
|
|
142345
|
-
const obj =
|
|
143476
|
+
const obj = asRecord38(payload);
|
|
142346
143477
|
if (!obj) return [];
|
|
142347
143478
|
const items3 = obj.items;
|
|
142348
143479
|
if (!Array.isArray(items3)) return [];
|
|
142349
|
-
return items3.filter((r) =>
|
|
143480
|
+
return items3.filter((r) => asRecord38(r) !== null);
|
|
142350
143481
|
}
|
|
142351
143482
|
function pickMetrics2(row, extras = []) {
|
|
142352
143483
|
const spend = num12(row.spend) ?? 0;
|
|
@@ -142482,7 +143613,7 @@ function aggregateDailyToWeekly(dailyRows) {
|
|
|
142482
143613
|
});
|
|
142483
143614
|
}
|
|
142484
143615
|
function prepareYandexDailyPayload(payload) {
|
|
142485
|
-
const obj =
|
|
143616
|
+
const obj = asRecord38(payload) ?? {};
|
|
142486
143617
|
const items3 = asItems(obj).map((r) => {
|
|
142487
143618
|
const date = normalizeYandexDailyDate(r.date);
|
|
142488
143619
|
return date ? { ...r, date } : r;
|
|
@@ -142551,7 +143682,7 @@ function mapDeviceRows2(payload) {
|
|
|
142551
143682
|
}
|
|
142552
143683
|
async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
142553
143684
|
const absDir = path51.resolve(snapshotDir);
|
|
142554
|
-
const metaAccountId = typeof
|
|
143685
|
+
const metaAccountId = typeof asRecord38(payload.meta)?.accountId === "string" ? asRecord38(payload.meta)?.accountId : void 0;
|
|
142555
143686
|
const manifest = await readManifest7(snapshotDir, metaAccountId);
|
|
142556
143687
|
if (!manifest) {
|
|
142557
143688
|
throw new Error(
|
|
@@ -142563,8 +143694,8 @@ async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
142563
143694
|
const data = await loadSectionPayload3(absDir, art.file);
|
|
142564
143695
|
if (data != null) sectionMap.set(art.section, data);
|
|
142565
143696
|
}
|
|
142566
|
-
const overview =
|
|
142567
|
-
const totals =
|
|
143697
|
+
const overview = asRecord38(sectionMap.get("overview"));
|
|
143698
|
+
const totals = asRecord38(overview?.totals);
|
|
142568
143699
|
const accountId = manifest.accountId ?? metaAccountId;
|
|
142569
143700
|
const meta = { ...payload.meta ?? {} };
|
|
142570
143701
|
if (!meta.accountId && accountId) meta.accountId = accountId;
|
|
@@ -142786,7 +143917,7 @@ function textLen7(value) {
|
|
|
142786
143917
|
function asArray8(value) {
|
|
142787
143918
|
return Array.isArray(value) ? value : [];
|
|
142788
143919
|
}
|
|
142789
|
-
function
|
|
143920
|
+
function asRecord39(value) {
|
|
142790
143921
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
142791
143922
|
return value;
|
|
142792
143923
|
}
|
|
@@ -142799,23 +143930,23 @@ function normKey(value) {
|
|
|
142799
143930
|
return String(value ?? "").trim().toLowerCase();
|
|
142800
143931
|
}
|
|
142801
143932
|
function insightMatchRecord(item) {
|
|
142802
|
-
const rec =
|
|
143933
|
+
const rec = asRecord39(item);
|
|
142803
143934
|
if (!rec) return {};
|
|
142804
|
-
return
|
|
143935
|
+
return asRecord39(rec.match) ?? rec;
|
|
142805
143936
|
}
|
|
142806
143937
|
function findRowInsight(list, primaryField, primaryValue, row) {
|
|
142807
143938
|
const target = normKey(primaryValue);
|
|
142808
143939
|
if (!target) return null;
|
|
142809
143940
|
const candidates = list.filter((it) => normKey(insightMatchRecord(it)[primaryField]) === target);
|
|
142810
|
-
if (candidates.length <= 1) return
|
|
143941
|
+
if (candidates.length <= 1) return asRecord39(candidates[0]);
|
|
142811
143942
|
const campaign = normKey(row.campaignName);
|
|
142812
143943
|
const narrowed = candidates.find(
|
|
142813
143944
|
(it) => normKey(insightMatchRecord(it).campaignName) === campaign
|
|
142814
143945
|
);
|
|
142815
|
-
return
|
|
143946
|
+
return asRecord39(narrowed ?? candidates[0]);
|
|
142816
143947
|
}
|
|
142817
143948
|
function topRowsBySpend(rows, limit) {
|
|
142818
|
-
return rows.map((r) =>
|
|
143949
|
+
return rows.map((r) => asRecord39(r)).filter((r) => r != null).sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0)).slice(0, limit);
|
|
142819
143950
|
}
|
|
142820
143951
|
var ROW_INSIGHT_SPECS = [
|
|
142821
143952
|
{
|
|
@@ -142830,17 +143961,17 @@ var ROW_INSIGHT_SPECS = [
|
|
|
142830
143961
|
];
|
|
142831
143962
|
function validateYandexPeriodReportContent(data) {
|
|
142832
143963
|
const missing = [];
|
|
142833
|
-
const meta =
|
|
142834
|
-
const narrative =
|
|
142835
|
-
const sections =
|
|
142836
|
-
const kpis =
|
|
143964
|
+
const meta = asRecord39(data.meta) ?? {};
|
|
143965
|
+
const narrative = asRecord39(data.narrative) ?? {};
|
|
143966
|
+
const sections = asRecord39(narrative.sections) ?? {};
|
|
143967
|
+
const kpis = asRecord39(data.kpis) ?? {};
|
|
142837
143968
|
if (!textLen7(meta.accountId)) {
|
|
142838
143969
|
pushMissing7(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
|
|
142839
143970
|
}
|
|
142840
143971
|
const sectionAnalysisCounts = {};
|
|
142841
143972
|
const sectionSuggestionCounts = {};
|
|
142842
143973
|
for (const key of SECTION_KEYS4) {
|
|
142843
|
-
const section =
|
|
143974
|
+
const section = asRecord39(sections[key]);
|
|
142844
143975
|
const analysis = asArray8(section?.analysis).filter((s) => textLen7(s) > 0);
|
|
142845
143976
|
const suggestions = asArray8(section?.suggestions).filter((s) => textLen7(s) > 0);
|
|
142846
143977
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -142866,7 +143997,7 @@ function validateYandexPeriodReportContent(data) {
|
|
|
142866
143997
|
}
|
|
142867
143998
|
const recommendations = asArray8(narrative.recommendations).filter((r) => {
|
|
142868
143999
|
if (typeof r === "string") return textLen7(r) > 0;
|
|
142869
|
-
const rec =
|
|
144000
|
+
const rec = asRecord39(r);
|
|
142870
144001
|
return textLen7(rec?.content) > 0 || textLen7(rec?.text) > 0;
|
|
142871
144002
|
});
|
|
142872
144003
|
if (recommendations.length < 3) {
|
|
@@ -142888,8 +144019,8 @@ function validateYandexPeriodReportContent(data) {
|
|
|
142888
144019
|
"\u81F3\u5C11\u4E00\u9879\u6709\u6548\u6570\u5B57\uFF08\u8BF7\u4F20 --snapshot-dir \u7531 CLI \u4ECE overview \u5408\u5E76\uFF09"
|
|
142889
144020
|
);
|
|
142890
144021
|
}
|
|
142891
|
-
const tables =
|
|
142892
|
-
const weeklyRows = asArray8(tables.weekly).map((r) =>
|
|
144022
|
+
const tables = asRecord39(data.tables) ?? {};
|
|
144023
|
+
const weeklyRows = asArray8(tables.weekly).map((r) => asRecord39(r)).filter((r) => r != null);
|
|
142893
144024
|
for (const [i, row] of weeklyRows.entries()) {
|
|
142894
144025
|
const label2 = `${row.weekLabel ?? ""} ${row.startDate ?? ""} ${row.endDate ?? ""}`;
|
|
142895
144026
|
if (/nan/i.test(label2) || !normalizeYandexDailyDate(row.startDate) || !normalizeYandexDailyDate(row.endDate)) {
|
|
@@ -142902,7 +144033,7 @@ function validateYandexPeriodReportContent(data) {
|
|
|
142902
144033
|
);
|
|
142903
144034
|
}
|
|
142904
144035
|
}
|
|
142905
|
-
const rowInsights =
|
|
144036
|
+
const rowInsights = asRecord39(narrative.rowInsights) ?? {};
|
|
142906
144037
|
const rowInsightCoverage = {};
|
|
142907
144038
|
for (const spec of ROW_INSIGHT_SPECS) {
|
|
142908
144039
|
const shown = topRowsBySpend(asArray8(tables[spec.tableKey]), spec.limit).filter(
|
|
@@ -142982,7 +144113,7 @@ async function readJsonFile8(filePath) {
|
|
|
142982
144113
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
142983
144114
|
}
|
|
142984
144115
|
}
|
|
142985
|
-
function
|
|
144116
|
+
function asRecord40(value) {
|
|
142986
144117
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
142987
144118
|
return value;
|
|
142988
144119
|
}
|
|
@@ -143010,7 +144141,7 @@ function injectReportData7(html, payload) {
|
|
|
143010
144141
|
}
|
|
143011
144142
|
async function runYandexAnalysisRender(opts) {
|
|
143012
144143
|
const dataPath = path53.resolve(opts.dataFile);
|
|
143013
|
-
let data =
|
|
144144
|
+
let data = asRecord40(await readJsonFile8(dataPath));
|
|
143014
144145
|
if (opts.snapshotDir?.trim()) {
|
|
143015
144146
|
data = await mergeYandexAnalysisSnapshotIntoReport(data, path53.resolve(opts.snapshotDir));
|
|
143016
144147
|
}
|
|
@@ -143206,7 +144337,7 @@ function unwrapYandexItems(raw) {
|
|
|
143206
144337
|
}
|
|
143207
144338
|
return [];
|
|
143208
144339
|
}
|
|
143209
|
-
function
|
|
144340
|
+
function asRecord41(raw) {
|
|
143210
144341
|
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
143211
144342
|
}
|
|
143212
144343
|
|
|
@@ -143285,7 +144416,7 @@ async function runYandexAccountStatus(opts) {
|
|
|
143285
144416
|
} catch (err) {
|
|
143286
144417
|
fail3("\u67E5\u8BE2 Yandex \u8D26\u6237\u603B\u89C8\u5931\u8D25", err);
|
|
143287
144418
|
}
|
|
143288
|
-
const row =
|
|
144419
|
+
const row = asRecord41(result.raw);
|
|
143289
144420
|
await emitOrTable(opts, {
|
|
143290
144421
|
section: "yandex-account-status",
|
|
143291
144422
|
commandLabel: "yandex-analysis account-status",
|