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/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 asRecord18(value) {
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 = asRecord18(data.narrative) ?? {};
103137
+ const narrative = asRecord19(data.narrative) ?? {};
103138
103138
  const parts = [];
103139
- const sheetAnalysis = asRecord18(narrative.sheetAnalysis) ?? {};
103139
+ const sheetAnalysis = asRecord19(narrative.sheetAnalysis) ?? {};
103140
103140
  for (const block of Object.values(sheetAnalysis)) {
103141
- const rec = asRecord18(block);
103141
+ const rec = asRecord19(block);
103142
103142
  parts.push(...stringList(rec?.summary), ...stringList(rec?.suggestions));
103143
103143
  }
103144
- const review = asRecord18(narrative.review) ?? {};
103144
+ const review = asRecord19(narrative.review) ?? {};
103145
103145
  for (const block of Object.values(review)) {
103146
- const rec = asRecord18(block);
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 asRecord19(value) {
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) => asRecord19(row) !== null);
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 asRecord19(payload.meta)?.accountId === "string" ? asRecord19(payload.meta)?.accountId : void 0;
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(asRecord19(payload.meta) ?? void 0);
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 = asRecord19(overviewRecord?.currentPeriod);
103322
- const previousPeriod = asRecord19(overviewRecord?.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 asRecord20(value) {
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 = asRecord20(data.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 = asRecord20(data.snapshot?.sections) ?? {};
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 = asRecord20(data.narrative) ?? {};
103508
- const sheetAnalysis = asRecord20(narrative.sheetAnalysis) ?? {};
103509
- const review = asRecord20(narrative.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 = asRecord20(sheetAnalysis[key]);
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 = asRecord20(review[key]);
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) => asRecord20(row) !== null));
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) => asRecord20(row) !== null);
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 asRecord20(payload.meta)?.accountId === "string" ? asRecord20(payload.meta)?.accountId : void 0;
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(asRecord20(payload.meta) ?? void 0);
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 = asRecord20(overviewRecord?.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 = asRecord20(asRecord20(data.narrative)?.sheetAnalysis)?.[key];
103883
- const rec = asRecord20(block);
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 = asRecord20(asRecord20(data.narrative)?.review) ?? {};
103890
+ const review = asRecord21(asRecord21(data.narrative)?.review) ?? {};
103891
103891
  const lines = [];
103892
103892
  OKKI_REVIEW_KEYS.forEach((key, idx) => {
103893
- const block = asRecord20(review[key]) ?? {};
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 = asRecord20(data.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 asRecord21(value) {
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 = asRecord21(data.meta) ?? {};
104298
- const narrative = asRecord21(data.narrative) ?? {};
104299
- const sections = asRecord21(narrative.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 = asRecord21(data.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 = asRecord21(sections[key]);
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 = asRecord21(r);
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 asRecord22(value) {
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 = asRecord22(await readJsonFile3(dataPath));
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(url, config, { method: "POST", body: JSON.stringify(body) }, opts.verbose);
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
- \u2705 \u5DF2\u5728${negativeKeywordLevelLabel[level]}\u5C42\u7EA7\u6DFB\u52A0 ${keywords.length} \u4E2A\u5426\u5B9A\u5173\u952E\u8BCD
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
- console.log(`
114111
- \u2705 \u5173\u952E\u8BCD\u5DF2\u66F4\u65B0
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
- console.error(`
115765
- \u274C \u72B6\u6001\u66F4\u65B0\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
115766
- `);
115767
- process.exit(1);
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
- console.log(`
115770
- \u2705 \u5E7F\u544A\u7CFB\u5217 ${opts.id} \u5DF2\u8BBE\u7F6E\u4E3A ${opts.status}
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
- console.error(`
116076
- \u274C \u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
116077
- `);
116078
- process.exit(1);
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
- console.log(`
116089
- \u2705 \u5E7F\u544A\u7CFB\u5217 ${opts.id} \u5DF2\u66F4\u65B0${parts.length ? `\uFF08${parts.join("\uFF0C")}\uFF09` : ""}
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
- applyLeadAdTargetingAutomation(
125494
- targeting,
125495
- resolveAdvantageAudience({
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 retryMajor = cfg.campaign.dailyBudget ?? cfg.campaign.lifetimeBudget ?? cfg.adset.dailyBudget ?? cfg.adset.lifetimeBudget ?? 1;
126015
- if (!opts.jsonOut) {
126016
- console.warn(
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 \u8BE5\u6237\u8981\u6C42 bid cap\uFF0C\u6309\u65E5/\u603B\u9884\u7B97 ${retryMajor} ${currency} \u5E26 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4
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: "LOWEST_COST_WITH_BID_CAP" },
126741
+ adset: { ...baseAdset, bidStrategy: decision.bidStrategy },
126027
126742
  campaignId: progress.campaignId,
126028
126743
  pageId,
126029
126744
  ...money2,
126030
- bidAmountMinor: toMinorUnitString(retryMajor, currency)
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 retryMajor = daily ?? lifetime ?? 1;
127006
- if (!opts.jsonOut) {
127007
- console.warn(
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 \u8BE5\u6237\u8981\u6C42 bid cap\uFF0C\u6309\u65E5/\u603B\u9884\u7B97 ${retryMajor} ${currency} \u5E26 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4
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: "LOWEST_COST_WITH_BID_CAP" },
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(retryMajor, currency)
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
- countries,
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
- adsUrl(facebookApiUrl, digits, `/adsets/${encodeURIComponent(opts.id)}`),
127937
+ adsetUrl,
127106
127938
  config,
127107
127939
  body,
127108
127940
  opts.verbose,
127109
127941
  "\u6539\u5E7F\u544A\u7EC4\u5931\u8D25"
127110
127942
  );
127111
- const payload = { id: extractCreatedId(data) ?? opts.id, ...data };
127112
- if (await emitMetaJson(opts, "meta-ad-adset-edit", "meta-ad adset-edit", payload, digits)) return;
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
- \u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u66F4\u65B0
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("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "ACTIVE | PAUSED").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("\u6539\u5E7F\u544A\u7EC4\uFF08targeting \u6574\u4EFD\u66FF\u6362\uFF1A\u56FD\u5BB6/\u5E74\u9F84/\u6027\u522B/advantage_audience\uFF09").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").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(
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=\u7CBE\u786E\u5B9A\u5411 1=Advantage Audience",
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\uFF0C\u64B0\u5199\u5168\u90E8 analysis/suggestions\u3001diagnosisOverview.advantages/disadvantages\uFF08\u6BCF\u6761 {title, description}\uFF0C\u7981\u6B62\u7EAF\u5B57\u7B26\u4E32\uFF09\u3001summary\u3001accountInfo.businessModel/industry\u3001budgetCompetitiveness[].strategy\u3001newFeatures[].optimizerRecommendation\u3001adCreativeOptimization.items[].suggestion \u7B49\uFF1B",
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: ["campaigns.items", "geographic.items", "keywords.items"],
133489
- renderRestoresComparisonItemsFromCollect: "render \u9ED8\u8BA4\u540C\u76EE\u5F55 google-ads-diagnosis-collect.json \u4F1A\u4EE5 collect \u8986\u76D6\u4E0A\u8FF0 items\uFF08Agent \u53EA\u586B analysis/suggestions\uFF09",
133490
- mayEditItemFields: [
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\uFF1B\u987B\u586B diagnosisOverview.advantages/disadvantages\uFF08\u6BCF\u6761 {title, description}\uFF09\u3001metrics.analysis/suggestions\u3001\u5404\u6A21\u5757 analysis/suggestions\u3001summary.keyIssues\u3001summary.optimizationRoadmap[]\uFF08priority/focusArea/actionItems/expectedOutcome\uFF09\u3001budgetCompetitiveness[].strategy\u3001newFeatures[].optimizerRecommendation \u7B49",
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
- function hasSearchTextAds(gold) {
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 hasGa4Link(rows) {
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 hasAudienceSetup(rows) {
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 = { ...applyTextAdQualification(gold) };
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
- if (hasGa4Link(conversions)) next.gaConfigured = true;
133598
- if (hasEnabledExtension(extensions, "CALLOUT")) next.adsCalloutExtensionQualified = true;
133599
- if (hasEnabledExtension(extensions, "STRUCTURED_SNIPPET")) {
133600
- next.adsStructuredSnippetExtensionQualified = true;
133601
- }
133602
- if (hasAudienceSetup(audiences)) next.audienceQualified = true;
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 (displayIsServing(responsiveDisplay)) {
133605
- next.multiAssetResponsiveDisplayAdQualified = true;
133606
- } else if (responsiveDisplay.length > 0 && next.multiAssetResponsiveDisplayAdQualified !== true) {
133607
- const suggestions = asRecord7(next.goldItemSuggestions) ?? {};
133608
- next.goldItemSuggestions = {
133609
- ...suggestions,
133610
- \u81EA\u9002\u5E94\u5C55\u793A\u5E7F\u544A: DISPLAY_PAUSED_SUGGESTION
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
- if (snapDir && accountId) {
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\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
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\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
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\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
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\uFF1BAgent \u53EA\u586B optimizerRecommendation\uFF0C\u52FF\u6539\u7528 feature \u5B57\u6BB5\u66FF\u4EE3 strategyName\uFF09"
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/render-report.ts
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 = asRecord15(raw);
135271
- const reportData = asRecord15(root?.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 = asRecord15(dataRaw);
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\u5168\u90E8 analysis/suggestions \u53CA budgetCompetitiveness[].strategy \u7B49\u5EFA\u8BAE\u5B57\u6BB5\u540E\u518D render\uFF1B\u8C03\u8BD5\u53EF\u52A0 --lenient\u3002\n"
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 asRecord16(value) {
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 = asRecord16(data);
136637
- const narrative = asRecord16(root?.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 = asRecord16(narrative.sections);
137774
+ const sections = asRecord17(narrative.sections);
136644
137775
  for (const key of ["efficiency", "adType", "region", "summary"]) {
136645
- const block = asRecord16(sections?.[key]);
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 = asRecord16(narrative.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
- ...asRecord16(collect.meta) ?? {},
136662
- ...asRecord16(agentData.meta) ?? {}
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 asRecord17(value) {
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(asRecord17(agentRaw), asRecord17(collectRaw));
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 asRecord23(value) {
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(asRecord23(row));
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 asRecord23(JSON.parse(raw));
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 = asRecord23(overview?.currentPeriod);
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 asRecord24(value) {
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 = asRecord24(section);
137279
- const insight = asRecord24(rec?.insight) ?? rec;
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 = asRecord24(data.kpis) ?? {};
137289
- const meta = asRecord24(data.meta) ?? {};
137290
- const sections = asRecord24(data.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 = asRecord24(asArray3(data.fourQuestions)[0]);
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 = asRecord24(raw);
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 = asRecord24(data.meta) ?? {};
137328
- const kpis = asRecord24(data.kpis) ?? {};
137329
- const tables = asRecord24(data.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 = asRecord24(raw);
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 = asRecord24(raw);
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 = asRecord24(raw);
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 = asRecord24(raw);
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 asRecord25(value) {
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 = asRecord25(section);
137425
- const insight = asRecord25(rec?.insight) ?? rec;
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 = asRecord25(data.meta) ?? {};
137456
- const kpis = asRecord25(data.kpis) ?? {};
137457
- const tables = asRecord25(data.tables) ?? {};
137458
- const charts = asRecord25(data.charts) ?? {};
137459
- const sections = asRecord25(data.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 = asRecord25(charts.daily);
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 = asRecord25(charts.country);
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 = asRecord25(charts.audience);
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) => asRecord25(q)?.title);
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 = asRecord25(fourQuestions[i]);
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 = asRecord25(recommendations[i]);
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 asRecord26(value) {
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 = asRecord26(await readJsonFile4(dataPath));
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 asRecord27(value) {
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) => asRecord27(r) !== null) : [];
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 asRecord27(JSON.parse(raw));
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 asRecord27(payload.meta)?.accountId === "string" ? asRecord27(payload.meta)?.accountId : void 0;
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 = asRecord27(overview?.currentPeriod);
137897
- const previousPeriod = asRecord27(overview?.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 asRecord28(value) {
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 = asRecord28(data.meta) ?? {};
138043
- const narrative = asRecord28(data.narrative) ?? {};
138044
- const sections = asRecord28(narrative.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 = asRecord28(sections[key]);
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 = asRecord28(sections[key]);
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 = asRecord28(r);
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 asRecord29(value) {
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 = asRecord29(await readJsonFile5(dataPath));
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 asRecord30(value) {
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 = asRecord30(raw);
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 = asRecord30(pickProp(rec, "paging", "Paging"));
138412
- const cursors = paging ? asRecord30(pickProp(paging, "cursors", "Cursors")) : void 0;
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 = asRecord30(raw);
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 = asRecord30(item);
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 asRecord31(value) {
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) => asRecord31(r) !== null);
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 = asRecord31(payload);
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 = asRecord31(payload);
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 = asRecord31(payload);
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 = asRecord31(asRecord31(payload)?.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 asRecord31(payload.meta)?.accountId === "string" ? asRecord31(payload.meta)?.accountId : void 0;
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 = asRecord31(sectionMap.get("overview"));
140069
- const currentPeriod = asRecord31(overview?.currentPeriod);
140070
- const previousPeriod = asRecord31(overview?.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 asRecord32(value) {
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 = asRecord32(data.meta) ?? {};
140209
- const narrative = asRecord32(data.narrative) ?? {};
140210
- const sections = asRecord32(narrative.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 = asRecord32(sections[key]);
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 = asRecord32(r);
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 asRecord33(value) {
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 = asRecord33(await readJsonFile6(dataPath));
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 asRecord34(value) {
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 asRecord34(payload.meta)?.accountId === "string" ? asRecord34(payload.meta)?.accountId : void 0;
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 asRecord35(value) {
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 = asRecord35(data.meta) ?? {};
141106
- const narrative = asRecord35(data.narrative) ?? {};
141107
- const sections = asRecord35(narrative.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 = asRecord35(sections[key]);
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 = asRecord35(r);
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 asRecord36(value) {
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 = asRecord36(await readJsonFile7(dataPath));
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 asRecord37(value) {
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 = asRecord37(payload);
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) => asRecord37(r) !== null);
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 = asRecord37(payload) ?? {};
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 asRecord37(payload.meta)?.accountId === "string" ? asRecord37(payload.meta)?.accountId : void 0;
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 = asRecord37(sectionMap.get("overview"));
142567
- const totals = asRecord37(overview?.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 asRecord38(value) {
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 = asRecord38(item);
143933
+ const rec = asRecord39(item);
142803
143934
  if (!rec) return {};
142804
- return asRecord38(rec.match) ?? rec;
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 asRecord38(candidates[0]);
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 asRecord38(narrowed ?? candidates[0]);
143946
+ return asRecord39(narrowed ?? candidates[0]);
142816
143947
  }
142817
143948
  function topRowsBySpend(rows, limit) {
142818
- return rows.map((r) => asRecord38(r)).filter((r) => r != null).sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0)).slice(0, limit);
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 = asRecord38(data.meta) ?? {};
142834
- const narrative = asRecord38(data.narrative) ?? {};
142835
- const sections = asRecord38(narrative.sections) ?? {};
142836
- const kpis = asRecord38(data.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 = asRecord38(sections[key]);
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 = asRecord38(r);
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 = asRecord38(data.tables) ?? {};
142892
- const weeklyRows = asArray8(tables.weekly).map((r) => asRecord38(r)).filter((r) => r != null);
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 = asRecord38(narrative.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 asRecord39(value) {
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 = asRecord39(await readJsonFile8(dataPath));
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 asRecord40(raw) {
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 = asRecord40(result.raw);
144419
+ const row = asRecord41(result.raw);
143289
144420
  await emitOrTable(opts, {
143290
144421
  section: "yandex-account-status",
143291
144422
  commandLabel: "yandex-analysis account-status",