siluzan-tso-cli 1.1.35 → 1.1.36-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3829,7 +3829,7 @@ var DEFAULT_API_BASE;
3829
3829
  var init_defaults = __esm({
3830
3830
  "src/config/defaults.ts"() {
3831
3831
  "use strict";
3832
- DEFAULT_API_BASE = "https://tso-api.siluzan.com";
3832
+ DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
3833
3833
  }
3834
3834
  });
3835
3835
 
@@ -106735,7 +106735,7 @@ async function writeReportAnalysisSnapshot(params) {
106735
106735
  );
106736
106736
  await fs8.writeFile(
106737
106737
  path12.join(absDir, outlineFileName),
106738
- `${formatOutlineFileBody(fileName, params.payload)}
106738
+ `${formatOutlineFileBody(fileName, params.payload, params.outlineExtraHints)}
106739
106739
  `,
106740
106740
  "utf8"
106741
106741
  );
@@ -113676,17 +113676,120 @@ init_cli_table();
113676
113676
  // src/commands/ad/campaign-batch-diff.ts
113677
113677
  init_auth();
113678
113678
  init_cli_json_snapshot();
113679
+
113680
+ // src/commands/ad/campaign-extension-remediate.ts
113681
+ function shellArg(value) {
113682
+ return /[\s"]/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
113683
+ }
113684
+ function pickString(...vals) {
113685
+ for (const v of vals) {
113686
+ if (typeof v === "string" && v.trim()) return v.trim();
113687
+ }
113688
+ return "";
113689
+ }
113679
113690
  function asRecord2(v) {
113680
113691
  return v && typeof v === "object" && !Array.isArray(v) ? v : null;
113681
113692
  }
113682
- function pickString(...vals) {
113693
+ function extensionType2(ext) {
113694
+ return pickString(ext["typeV2"], ext["TypeV2"], ext["AssetFieldType"], ext["type"]).toUpperCase();
113695
+ }
113696
+ function extensionLevel(ext) {
113697
+ const level = pickString(ext["level"], ext["Level"]) || "Campaign";
113698
+ return level.charAt(0).toUpperCase() + level.slice(1).toLowerCase();
113699
+ }
113700
+ function buildExtensionRemediateCommandLine(opts) {
113701
+ const { accountId, campaignId, ext } = opts;
113702
+ const jsonOut = shellArg(opts.jsonOutDir ?? "./snap");
113703
+ const type = extensionType2(ext);
113704
+ const level = extensionLevel(ext);
113705
+ const props = asRecord2(ext["Properties"]) ?? {};
113706
+ const levelFlag = `--level ${level}`;
113707
+ const campaignFlag = level === "Campaign" ? `--campaign-id ${campaignId}` : "";
113708
+ const base = ["siluzan-tso", "ad", "extension"];
113709
+ if (type === "SITELINK") {
113710
+ const text = pickString(props["Text"], props["LinkText"]);
113711
+ const url = pickString(props["DestinationUrl"]);
113712
+ if (!text || !url) return null;
113713
+ const parts = [
113714
+ ...base,
113715
+ "sitelink",
113716
+ `-a ${accountId}`,
113717
+ `--text ${shellArg(text)}`,
113718
+ `--url ${shellArg(url)}`
113719
+ ];
113720
+ const line2 = pickString(props["Line2"], props["Description1"]);
113721
+ const line3 = pickString(props["Line3"], props["Description2"]);
113722
+ if (line2) parts.push(`--line2 ${shellArg(line2)}`);
113723
+ if (line3) parts.push(`--line3 ${shellArg(line3)}`);
113724
+ parts.push(levelFlag);
113725
+ if (campaignFlag) parts.push(campaignFlag);
113726
+ parts.push(`--json-out ${jsonOut}`);
113727
+ return parts.join(" ");
113728
+ }
113729
+ if (type === "CALLOUT") {
113730
+ const text = pickString(props["Text"], props["CalloutText"]);
113731
+ if (!text) return null;
113732
+ const parts = [
113733
+ ...base,
113734
+ "callout",
113735
+ `-a ${accountId}`,
113736
+ `--text ${shellArg(text)}`,
113737
+ levelFlag
113738
+ ];
113739
+ if (campaignFlag) parts.push(campaignFlag);
113740
+ parts.push(`--json-out ${jsonOut}`);
113741
+ return parts.join(" ");
113742
+ }
113743
+ if (type === "CALL") {
113744
+ const code = pickString(props["ContryCode"], props["CountryCode"]);
113745
+ const phone = pickString(props["PhoneNumber"], props["phoneNumber"]);
113746
+ if (!code || !phone) return null;
113747
+ const parts = [
113748
+ ...base,
113749
+ "call",
113750
+ `-a ${accountId}`,
113751
+ `--country-code ${shellArg(code)}`,
113752
+ `--phone ${shellArg(phone)}`,
113753
+ levelFlag
113754
+ ];
113755
+ if (campaignFlag) parts.push(campaignFlag);
113756
+ parts.push(`--json-out ${jsonOut}`);
113757
+ return parts.join(" ");
113758
+ }
113759
+ if (type === "STRUCTURED_SNIPPET") {
113760
+ const ssv = asRecord2(ext["StructuredSnippetHeaderValue"]);
113761
+ const header = pickString(ssv?.["Key"]);
113762
+ const values = ssv?.["Value"];
113763
+ if (!header || !Array.isArray(values) || values.length === 0) return null;
113764
+ const valueStr = values.filter((v) => typeof v === "string" && v.trim().length > 0).join(",");
113765
+ if (!valueStr) return null;
113766
+ const parts = [
113767
+ ...base,
113768
+ "snippet",
113769
+ `-a ${accountId}`,
113770
+ `--header ${shellArg(header)}`,
113771
+ `--values ${shellArg(valueStr)}`,
113772
+ levelFlag
113773
+ ];
113774
+ if (campaignFlag) parts.push(campaignFlag);
113775
+ parts.push(`--json-out ${jsonOut}`);
113776
+ return parts.join(" ");
113777
+ }
113778
+ return null;
113779
+ }
113780
+
113781
+ // src/commands/ad/campaign-batch-diff.ts
113782
+ function asRecord3(v) {
113783
+ return v && typeof v === "object" && !Array.isArray(v) ? v : null;
113784
+ }
113785
+ function pickString2(...vals) {
113683
113786
  for (const v of vals) {
113684
113787
  if (typeof v === "string" && v.trim()) return v.trim();
113685
113788
  }
113686
113789
  return "";
113687
113790
  }
113688
113791
  function normMatchType(v) {
113689
- return pickString(v).toUpperCase() || "BROAD";
113792
+ return pickString2(v).toUpperCase() || "BROAD";
113690
113793
  }
113691
113794
  function keywordKey(core, matchType) {
113692
113795
  return `${normMatchType(matchType)}:${unwrapKeywordDisplayTextForEdit(core).trim().toLowerCase()}`;
@@ -113696,19 +113799,19 @@ function liveKeywordTexts(item) {
113696
113799
  if (Array.isArray(kt)) {
113697
113800
  return kt.filter((t2) => typeof t2 === "string" && t2.trim().length > 0);
113698
113801
  }
113699
- const t = pickString(item["text"], kt);
113802
+ const t = pickString2(item["text"], kt);
113700
113803
  return t ? [t] : [];
113701
113804
  }
113702
113805
  function belongsToCampaign(item, campaignId, campaignName) {
113703
- const cid = pickString(item["campaignId"], item["campaignid"], item["CampaignId"]);
113704
- const cname = pickString(item["campaignName"], item["campaign"], item["CampaignName"]);
113806
+ const cid = pickString2(item["campaignId"], item["campaignid"], item["CampaignId"]);
113807
+ const cname = pickString2(item["campaignName"], item["campaign"], item["CampaignName"]);
113705
113808
  if (campaignId && cid) return cid === campaignId;
113706
113809
  if (campaignName && cname) return cname === campaignName;
113707
113810
  return false;
113708
113811
  }
113709
113812
  function belongsToAdGroup(item, adGroupName, adGroupId) {
113710
- const gid = pickString(item["adGroupId"], item["adgroupId"], item["AdGroupId"]);
113711
- const gname = pickString(item["adGroupName"], item["adGroup"], item["AdGroupName"]);
113813
+ const gid = pickString2(item["adGroupId"], item["adgroupId"], item["AdGroupId"]);
113814
+ const gname = pickString2(item["adGroupName"], item["adGroup"], item["AdGroupName"]);
113712
113815
  if (adGroupId && gid) return gid === adGroupId;
113713
113816
  return gname === adGroupName;
113714
113817
  }
@@ -113729,10 +113832,10 @@ function liveAdHasHeadline(ad, headline) {
113729
113832
  }
113730
113833
  function formatExtensionPlanned(ext) {
113731
113834
  if (!ext) return "\u2014";
113732
- const type = pickString(ext["typeV2"], ext["AssetFieldType"], ext["type"]);
113733
- const props = asRecord2(ext["Properties"]);
113734
- const phone = props ? pickString(props["PhoneNumber"], props["phoneNumber"]) : "";
113735
- const code = props ? pickString(props["ContryCode"], props["CountryCode"]) : "";
113835
+ const type = pickString2(ext["typeV2"], ext["AssetFieldType"], ext["type"]);
113836
+ const props = asRecord3(ext["Properties"]);
113837
+ const phone = props ? pickString2(props["PhoneNumber"], props["phoneNumber"]) : "";
113838
+ const code = props ? pickString2(props["ContryCode"], props["CountryCode"]) : "";
113736
113839
  const parts = [type ? `\u7C7B\u578B: ${type}` : "", code || phone ? `\u7535\u8BDD: ${code}${phone}` : ""].filter(
113737
113840
  Boolean
113738
113841
  );
@@ -113743,15 +113846,15 @@ function listPlannedKeywords(campaign) {
113743
113846
  if (!Array.isArray(groups)) return [];
113744
113847
  const out = [];
113745
113848
  for (let gi = 0; gi < groups.length; gi++) {
113746
- const g = asRecord2(groups[gi]);
113849
+ const g = asRecord3(groups[gi]);
113747
113850
  if (!g) continue;
113748
- const groupName = pickString(g["Name"], g["name"]) || `AdGroupsForBatchJob[${gi}]`;
113851
+ const groupName = pickString2(g["Name"], g["name"]) || `AdGroupsForBatchJob[${gi}]`;
113749
113852
  const blocks = g["KeywordsForBatchJob"];
113750
113853
  if (!Array.isArray(blocks)) continue;
113751
113854
  for (let bi = 0; bi < blocks.length; bi++) {
113752
- const block = asRecord2(blocks[bi]);
113855
+ const block = asRecord3(blocks[bi]);
113753
113856
  if (!block) continue;
113754
- const matchTypeV2 = pickString(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
113857
+ const matchTypeV2 = pickString2(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
113755
113858
  const texts = block["KeywordText"];
113756
113859
  if (!Array.isArray(texts)) continue;
113757
113860
  for (let ki = 0; ki < texts.length; ki++) {
@@ -113773,9 +113876,9 @@ function listPlannedNegativeKeywords(campaign) {
113773
113876
  if (!Array.isArray(blocks)) return [];
113774
113877
  const out = [];
113775
113878
  for (let bi = 0; bi < blocks.length; bi++) {
113776
- const block = asRecord2(blocks[bi]);
113879
+ const block = asRecord3(blocks[bi]);
113777
113880
  if (!block) continue;
113778
- const matchTypeV2 = pickString(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
113881
+ const matchTypeV2 = pickString2(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
113779
113882
  const texts = block["KeywordText"];
113780
113883
  if (!Array.isArray(texts)) continue;
113781
113884
  for (let ki = 0; ki < texts.length; ki++) {
@@ -113793,7 +113896,7 @@ function listPlannedNegativeKeywords(campaign) {
113793
113896
  }
113794
113897
  function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113795
113898
  const campaign = cfg.campaign ?? {};
113796
- const campaignName = pickString(cfg.name, campaign["Name"], campaign["name"]);
113899
+ const campaignName = pickString2(cfg.name, campaign["Name"], campaign["name"]);
113797
113900
  const accountId = String(cfg.account ?? "");
113798
113901
  const missing = [];
113799
113902
  if (!live.campaignFound) {
@@ -113807,15 +113910,15 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113807
113910
  }
113808
113911
  const liveGroups = live.adGroups;
113809
113912
  const liveGroupNames = new Set(
113810
- liveGroups.map((g) => pickString(g["name"], g["Name"]).toLowerCase()).filter(Boolean)
113913
+ liveGroups.map((g) => pickString2(g["name"], g["Name"]).toLowerCase()).filter(Boolean)
113811
113914
  );
113812
113915
  const groups = campaign["AdGroupsForBatchJob"];
113813
113916
  const plannedGroupCount = Array.isArray(groups) ? groups.length : 0;
113814
113917
  if (Array.isArray(groups)) {
113815
113918
  for (let gi = 0; gi < groups.length; gi++) {
113816
- const g = asRecord2(groups[gi]);
113919
+ const g = asRecord3(groups[gi]);
113817
113920
  if (!g) continue;
113818
- const groupName = pickString(g["Name"], g["name"]) || `AdGroupsForBatchJob[${gi}]`;
113921
+ const groupName = pickString2(g["Name"], g["name"]) || `AdGroupsForBatchJob[${gi}]`;
113819
113922
  const groupPath = `campaign.AdGroupsForBatchJob[${gi}]`;
113820
113923
  if (!liveGroupNames.has(groupName.toLowerCase())) {
113821
113924
  missing.push({
@@ -113829,9 +113932,9 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113829
113932
  continue;
113830
113933
  }
113831
113934
  const liveGroup = liveGroups.find(
113832
- (lg) => pickString(lg["name"], lg["Name"]).toLowerCase() === groupName.toLowerCase()
113935
+ (lg) => pickString2(lg["name"], lg["Name"]).toLowerCase() === groupName.toLowerCase()
113833
113936
  );
113834
- const liveGroupId = liveGroup ? pickString(liveGroup["id"], liveGroup["Id"]) : "";
113937
+ const liveGroupId = liveGroup ? pickString2(liveGroup["id"], liveGroup["Id"]) : "";
113835
113938
  const liveKwInGroup = live.keywords.filter(
113836
113939
  (k) => belongsToCampaign(k, campaignId, campaignName) && belongsToAdGroup(k, groupName, liveGroupId)
113837
113940
  );
@@ -113844,9 +113947,9 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113844
113947
  const blocks = g["KeywordsForBatchJob"];
113845
113948
  if (Array.isArray(blocks)) {
113846
113949
  for (let bi = 0; bi < blocks.length; bi++) {
113847
- const block = asRecord2(blocks[bi]);
113950
+ const block = asRecord3(blocks[bi]);
113848
113951
  if (!block) continue;
113849
- const matchTypeV2 = pickString(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
113952
+ const matchTypeV2 = pickString2(block["MatchTypeV2"], block["matchTypeV2"]) || "BROAD";
113850
113953
  const texts = block["KeywordText"];
113851
113954
  if (!Array.isArray(texts)) continue;
113852
113955
  for (let ki = 0; ki < texts.length; ki++) {
@@ -113873,10 +113976,10 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113873
113976
  const ads = g["AdsForBatchJob"];
113874
113977
  if (Array.isArray(ads)) {
113875
113978
  for (let ai = 0; ai < ads.length; ai++) {
113876
- const ad = asRecord2(ads[ai]);
113979
+ const ad = asRecord3(ads[ai]);
113877
113980
  if (!ad) continue;
113878
113981
  const path34 = `${groupPath}.AdsForBatchJob[${ai}]`;
113879
- const primary = pickString(ad["headlinePart1"], ad["AdTitle"]);
113982
+ const primary = pickString2(ad["headlinePart1"], ad["AdTitle"]);
113880
113983
  if (!primary) {
113881
113984
  missing.push({
113882
113985
  layer: "ad",
@@ -113888,7 +113991,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113888
113991
  });
113889
113992
  continue;
113890
113993
  }
113891
- const finalUrl = pickString(ad["Finalurl"], ad["DestinationUrl"], ad["finalUrl"]);
113994
+ const finalUrl = pickString2(ad["Finalurl"], ad["DestinationUrl"], ad["finalUrl"]);
113892
113995
  const found = liveAdsInGroup.some((la) => liveAdHasHeadline(la, primary));
113893
113996
  if (!found) {
113894
113997
  missing.push({
@@ -113929,13 +114032,15 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113929
114032
  ).length;
113930
114033
  if (plannedExt > liveExt && Array.isArray(extBlocks)) {
113931
114034
  for (let ei = liveExt; ei < extBlocks.length; ei++) {
113932
- const ext = asRecord2(extBlocks[ei]);
114035
+ const ext = asRecord3(extBlocks[ei]);
114036
+ const remediateCommand = ext && accountId && campaignId ? buildExtensionRemediateCommandLine({ accountId, campaignId, ext }) ?? void 0 : void 0;
113933
114037
  missing.push({
113934
114038
  layer: "extension",
113935
114039
  path: `campaign.ExtensionsForBatchJob[${ei}]`,
113936
114040
  plannedContent: formatExtensionPlanned(ext),
113937
114041
  liveNote: `\u7CFB\u5217\u7EA7\u9644\u52A0\u4FE1\u606F\u8D26\u6237\u5185 ${liveExt}/${plannedExt} \u6761`,
113938
- summary: `\u9644\u52A0\u4FE1\u606F\u672A\u521B\u5EFA\uFF1A${formatExtensionPlanned(ext)}`
114042
+ summary: `\u9644\u52A0\u4FE1\u606F\u672A\u521B\u5EFA\uFF1A${formatExtensionPlanned(ext)}`,
114043
+ remediateCommand
113939
114044
  });
113940
114045
  }
113941
114046
  }
@@ -113959,7 +114064,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113959
114064
  plannedKeywords: plannedKeywords.length,
113960
114065
  liveKeywords: liveKwCount,
113961
114066
  plannedAds: Array.isArray(groups) ? groups.reduce((n, g) => {
113962
- const gr = asRecord2(g);
114067
+ const gr = asRecord3(g);
113963
114068
  const ads = gr?.["AdsForBatchJob"];
113964
114069
  return n + (Array.isArray(ads) ? ads.length : 0);
113965
114070
  }, 0) : 0,
@@ -114020,7 +114125,7 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
114020
114125
  )
114021
114126
  ]);
114022
114127
  const campaignFound = campaigns.some(
114023
- (c) => pickString(c["id"], c["Id"]) === campaignId || pickString(c["name"], c["Name"]) === campaignName
114128
+ (c) => pickString2(c["id"], c["Id"]) === campaignId || pickString2(c["name"], c["Name"]) === campaignName
114024
114129
  );
114025
114130
  return {
114026
114131
  campaignFound,
@@ -114035,8 +114140,8 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
114035
114140
  };
114036
114141
  }
114037
114142
  function resolveCampaignIdFromBatch(record) {
114038
- const campaign = asRecord2(record["campaign"]);
114039
- return pickString(campaign?.["Id"], campaign?.["id"], record["campaignId"]);
114143
+ const campaign = asRecord3(record["campaign"]);
114144
+ return pickString2(campaign?.["Id"], campaign?.["id"], record["campaignId"]);
114040
114145
  }
114041
114146
  function shellArgPath(p) {
114042
114147
  return /[\s"]/.test(p) ? `"${p.replace(/"/g, '\\"')}"` : p;
@@ -114083,7 +114188,9 @@ function printBatchDiffFollowUpHint(opts) {
114083
114188
  if (opts.status === "Successfully") {
114084
114189
  console.log(" \u6838\u5BF9\u8BA1\u5212\u4E0E\u8D26\u6237\u662F\u5426\u4E00\u81F4\uFF08\u65E0\u7F3A\u5931\u5E94\u663E\u793A \u2705\uFF09\u3002\n");
114085
114190
  } else {
114086
- console.log(" \u5217\u51FA\u7F3A\u5931\u9879\u540E\u6309\u8DEF\u5F84\u8865\u5EFA\u3002\n");
114191
+ console.log(
114192
+ " \u5217\u51FA\u7F3A\u5931\u9879\u540E\u81EA\u52A8\u8865\u5EFA\uFF08\u9644\u52A0\u4FE1\u606F\u7528 ad extension *\uFF1B\u7EC4/\u8BCD/RSA \u7528\u5BF9\u5E94 create \u547D\u4EE4\uFF09\u3002\n"
114193
+ );
114087
114194
  }
114088
114195
  console.log(` ${cmd}
114089
114196
  `);
@@ -114111,6 +114218,9 @@ function printMissingEntry(configFile, item) {
114111
114218
  if (item.liveNote) {
114112
114219
  console.log(` \u5B9E\u51B5: ${item.liveNote}`);
114113
114220
  }
114221
+ if (item.remediateCommand) {
114222
+ console.log(` \u8865\u5EFA: ${item.remediateCommand}`);
114223
+ }
114114
114224
  }
114115
114225
  function printMissingGroupByAdGroup(configFile, layer, items) {
114116
114226
  const byGroup = /* @__PURE__ */ new Map();
@@ -114178,6 +114288,11 @@ function formatBatchDiffGrouped(result) {
114178
114288
  for (const m of layerItems) {
114179
114289
  printMissingEntry(configFile, m);
114180
114290
  }
114291
+ if (layer === "extension" && layerItems.some((m) => m.remediateCommand)) {
114292
+ console.log(
114293
+ "\n \u{1F4A1} \u9644\u52A0\u4FE1\u606F BatchJob \u5E38\u5931\u8D25\uFF1AAgent \u5E94**\u76F4\u63A5\u6267\u884C**\u4E0A\u65B9\u300C\u8865\u5EFA\u300D\u547D\u4EE4\uFF08\u7528\u6237\u5DF2\u786E\u8BA4\u8FC7 JSON\uFF09\uFF0C\u52FF\u4EC5\u8BE2\u95EE\u662F\u5426\u8865\u5EFA\u3002"
114294
+ );
114295
+ }
114181
114296
  }
114182
114297
  }
114183
114298
  console.log();
@@ -114190,14 +114305,14 @@ async function runCampaignBatchDiff(opts) {
114190
114305
  `);
114191
114306
  process.exit(1);
114192
114307
  }
114193
- const accountId = pickString(opts.account, cfg.account);
114308
+ const accountId = pickString2(opts.account, cfg.account);
114194
114309
  if (!accountId) {
114195
114310
  console.error("\n\u274C \u7F3A\u5C11\u8D26\u6237 ID\uFF1A\u8BF7\u5728 JSON \u4E2D\u586B\u5199 account \u6216\u4F7F\u7528 -a\n");
114196
114311
  process.exit(1);
114197
114312
  }
114198
114313
  cfg.account = accountId;
114199
114314
  const config = loadConfig(opts.token);
114200
- let campaignId = pickString(opts.campaignId);
114315
+ let campaignId = pickString2(opts.campaignId);
114201
114316
  let batchStatus;
114202
114317
  if (opts.batchId) {
114203
114318
  const batchUrl = `${config.apiBaseUrl}/query/campaign-creation-record/campaign-batch-asyncs/${opts.batchId}`;
@@ -114210,7 +114325,7 @@ async function runCampaignBatchDiff(opts) {
114210
114325
  `);
114211
114326
  process.exit(1);
114212
114327
  }
114213
- batchStatus = pickString(batch["status"]);
114328
+ batchStatus = pickString2(batch["status"]);
114214
114329
  if (batchStatus === "Creating") {
114215
114330
  console.error("\n\u274C \u4EFB\u52A1\u4ECD\u5728\u521B\u5EFA\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u6267\u884C ad batch diff\n");
114216
114331
  process.exit(1);
@@ -114228,7 +114343,7 @@ async function runCampaignBatchDiff(opts) {
114228
114343
  }
114229
114344
  }
114230
114345
  const campaign = cfg.campaign ?? {};
114231
- const campaignName = pickString(cfg.name, campaign["Name"], campaign["name"]);
114346
+ const campaignName = pickString2(cfg.name, campaign["Name"], campaign["name"]);
114232
114347
  if (!campaignId && campaignName) {
114233
114348
  const googleApiUrl = requireGoogleApi(config);
114234
114349
  const params = new URLSearchParams();
@@ -114240,10 +114355,10 @@ async function runCampaignBatchDiff(opts) {
114240
114355
  opts.verbose
114241
114356
  );
114242
114357
  const hit = campaigns.find(
114243
- (c) => pickString(c["name"], c["Name"]).toLowerCase() === campaignName.toLowerCase()
114358
+ (c) => pickString2(c["name"], c["Name"]).toLowerCase() === campaignName.toLowerCase()
114244
114359
  );
114245
114360
  if (hit) {
114246
- campaignId = pickString(hit["id"], hit["Id"]);
114361
+ campaignId = pickString2(hit["id"], hit["Id"]);
114247
114362
  }
114248
114363
  }
114249
114364
  if (!campaignId) {
@@ -123718,7 +123833,7 @@ function readJsonFile(filePath) {
123718
123833
  }
123719
123834
  });
123720
123835
  }
123721
- function asRecord3(value) {
123836
+ function asRecord4(value) {
123722
123837
  if (value && typeof value === "object" && !Array.isArray(value)) {
123723
123838
  return value;
123724
123839
  }
@@ -123744,10 +123859,10 @@ function injectReportData(html, payload) {
123744
123859
  async function runWebsiteDiagnosisRender(opts) {
123745
123860
  const dataPath = path17.resolve(opts.dataFile);
123746
123861
  const dataRaw = await readJsonFile(dataPath);
123747
- let data = asRecord3(dataRaw);
123862
+ let data = asRecord4(dataRaw);
123748
123863
  if (opts.collectFile) {
123749
123864
  const collectRaw = await readJsonFile(path17.resolve(opts.collectFile));
123750
- data = mergeCollectLighthouse(data, asRecord3(collectRaw));
123865
+ data = mergeCollectLighthouse(data, asRecord4(collectRaw));
123751
123866
  }
123752
123867
  const templatePath = websiteDiagnosisReportTemplatePath();
123753
123868
  let html;
@@ -124388,7 +124503,7 @@ function formatMarketReportContentErrors(result) {
124388
124503
  }
124389
124504
 
124390
124505
  // src/commands/market-analysis/render-report.ts
124391
- function asRecord4(value) {
124506
+ function asRecord5(value) {
124392
124507
  if (value && typeof value === "object" && !Array.isArray(value)) {
124393
124508
  return value;
124394
124509
  }
@@ -124418,7 +124533,7 @@ async function runMarketAnalysisRender(opts) {
124418
124533
  }
124419
124534
  let html;
124420
124535
  try {
124421
- html = resolveMarketAnalysisHtml(asRecord4(dataRaw));
124536
+ html = resolveMarketAnalysisHtml(asRecord5(dataRaw));
124422
124537
  } catch (err) {
124423
124538
  const message = err instanceof Error ? err.message : String(err);
124424
124539
  console.error(`
@@ -124430,7 +124545,7 @@ async function runMarketAnalysisRender(opts) {
124430
124545
  console.error("\n\u274C \u62A5\u544A HTML \u5185\u5BB9\u4E3A\u7A7A\n");
124431
124546
  process.exit(1);
124432
124547
  }
124433
- const reportData = asRecord4(dataRaw);
124548
+ const reportData = asRecord5(dataRaw);
124434
124549
  const contentCheck = validateMarketReportContent(html, validationContextFromReport(reportData));
124435
124550
  if (!contentCheck.ok) {
124436
124551
  console.error(`
@@ -125453,21 +125568,21 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125453
125568
  }
125454
125569
 
125455
125570
  // src/commands/google-ads-diagnosis/collect-integrity.ts
125456
- function asRecord5(value) {
125571
+ function asRecord6(value) {
125457
125572
  if (value && typeof value === "object" && !Array.isArray(value)) {
125458
125573
  return value;
125459
125574
  }
125460
125575
  return null;
125461
125576
  }
125462
125577
  function itemCount(block) {
125463
- const items = asRecord5(block)?.items;
125578
+ const items = asRecord6(block)?.items;
125464
125579
  return Array.isArray(items) ? items.length : 0;
125465
125580
  }
125466
125581
  function getCollectIntegrityWarnings(reportData) {
125467
125582
  const warnings = [];
125468
125583
  const kwItems = itemCount(reportData.keywords);
125469
125584
  const fullKwItems = itemCount(reportData.fullKeywords);
125470
- const keywordCount = Number(asRecord5(reportData.structure)?.keywordCount ?? 0);
125585
+ const keywordCount = Number(asRecord6(reportData.structure)?.keywordCount ?? 0);
125471
125586
  if (kwItems === 0 && fullKwItems > 0) {
125472
125587
  warnings.push(
125473
125588
  `keywords.items \u5BF9\u6BD4\u8868\u4E3A\u7A7A\uFF0C\u4F46 fullKeywords.items \u6709 ${fullKwItems} \u884C\uFF1B\u8BF7\u786E\u8BA4 collect \u662F\u5426\u6210\u529F\u62C9\u53D6\u4E0A\u4E00\u5468\u671F keywords \u5FEB\u7167\uFF08\u73AF\u6BD4\u5217\u4F9D\u8D56 prev \u5468\u671F JSON\uFF09\u3002`
@@ -125478,7 +125593,7 @@ function getCollectIntegrityWarnings(reportData) {
125478
125593
  );
125479
125594
  }
125480
125595
  const campaignItems = itemCount(reportData.campaigns);
125481
- const campaignCount = Number(asRecord5(reportData.structure)?.campaignCount ?? 0);
125596
+ const campaignCount = Number(asRecord6(reportData.structure)?.campaignCount ?? 0);
125482
125597
  if (campaignItems === 0 && campaignCount > 0) {
125483
125598
  warnings.push(
125484
125599
  `campaigns.items \u5BF9\u6BD4\u8868\u4E3A\u7A7A\uFF0C\u4F46 structure.campaignCount=${campaignCount}\uFF1B\u8BF7\u786E\u8BA4\u4E0A\u4E00\u5468\u671F campaigns \u5FEB\u7167\u662F\u5426\u62C9\u53D6\u6210\u529F\u3002`
@@ -125682,7 +125797,7 @@ import fs18 from "fs/promises";
125682
125797
  import path23 from "path";
125683
125798
 
125684
125799
  // src/commands/google-ads-diagnosis/report-content.ts
125685
- function asRecord6(value) {
125800
+ function asRecord7(value) {
125686
125801
  if (value && typeof value === "object" && !Array.isArray(value)) {
125687
125802
  return value;
125688
125803
  }
@@ -125691,7 +125806,7 @@ function asRecord6(value) {
125691
125806
  function getNestedValue(root, path34) {
125692
125807
  let cur = root;
125693
125808
  for (const key of path34) {
125694
- const rec = asRecord6(cur);
125809
+ const rec = asRecord7(cur);
125695
125810
  if (!rec) return void 0;
125696
125811
  cur = rec[key];
125697
125812
  }
@@ -125708,7 +125823,7 @@ function validateBudgetCompetitivenessStrategies(data) {
125708
125823
  }
125709
125824
  const warnings = [];
125710
125825
  for (let i = 0; i < items.length; i++) {
125711
- const row = asRecord6(items[i]);
125826
+ const row = asRecord7(items[i]);
125712
125827
  const name2 = String(row?.name ?? row?.metric ?? i);
125713
125828
  if (!hasNarrativeContent(row?.strategy)) {
125714
125829
  warnings.push(`budgetCompetitiveness[${name2}].strategy \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
@@ -125717,13 +125832,13 @@ function validateBudgetCompetitivenessStrategies(data) {
125717
125832
  return warnings;
125718
125833
  }
125719
125834
  function validateNewFeatureRecommendations(data) {
125720
- const nf = asRecord6(data.newFeatures);
125835
+ const nf = asRecord7(data.newFeatures);
125721
125836
  if (!nf) return [];
125722
125837
  const items = nf.items;
125723
125838
  if (!Array.isArray(items)) return [];
125724
125839
  const warnings = [];
125725
125840
  for (let i = 0; i < items.length; i++) {
125726
- const row = asRecord6(items[i]);
125841
+ const row = asRecord7(items[i]);
125727
125842
  const name2 = String(row?.strategyName ?? row?.feature ?? row?.strategy ?? i);
125728
125843
  if (!hasNarrativeContent(row?.optimizerRecommendation)) {
125729
125844
  warnings.push(`newFeatures.items[${name2}].optimizerRecommendation \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
@@ -125732,13 +125847,13 @@ function validateNewFeatureRecommendations(data) {
125732
125847
  return warnings;
125733
125848
  }
125734
125849
  function validateAdCreativeItemSuggestions(data) {
125735
- const creative = asRecord6(data.adCreativeOptimization);
125850
+ const creative = asRecord7(data.adCreativeOptimization);
125736
125851
  if (!creative) return [];
125737
125852
  const items = creative.items;
125738
125853
  if (!Array.isArray(items) || items.length === 0) return [];
125739
125854
  const warnings = [];
125740
125855
  for (let i = 0; i < items.length; i++) {
125741
- const row = asRecord6(items[i]);
125856
+ const row = asRecord7(items[i]);
125742
125857
  const title = String(row?.adTitle ?? i);
125743
125858
  if (!hasNarrativeContent(row?.suggestion ?? row?.suggestions)) {
125744
125859
  warnings.push(
@@ -125753,7 +125868,7 @@ function validateGoogleAdsDiagnosisContent(data) {
125753
125868
  (key) => data[key] === void 0
125754
125869
  );
125755
125870
  const warnings = [];
125756
- const accountInfo = asRecord6(data.accountInfo);
125871
+ const accountInfo = asRecord7(data.accountInfo);
125757
125872
  if (accountInfo && !accountInfo.companyName && !accountInfo.period) {
125758
125873
  warnings.push("accountInfo \u7F3A\u5C11 companyName \u4E0E period\uFF0C\u9875\u7709\u53EF\u80FD\u4E3A\u7A7A");
125759
125874
  }
@@ -125801,7 +125916,7 @@ var GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES = [
125801
125916
  GOOGLE_ADS_DIAGNOSIS_BIDDING_STRATEGY_MODULE,
125802
125917
  GOOGLE_ADS_DIAGNOSIS_NEW_FEATURES_MODULE
125803
125918
  ];
125804
- function asRecord7(value) {
125919
+ function asRecord8(value) {
125805
125920
  if (value && typeof value === "object" && !Array.isArray(value)) {
125806
125921
  return value;
125807
125922
  }
@@ -125812,7 +125927,7 @@ function isValidRateChange(value) {
125812
125927
  return typeof value === "number" && Number.isFinite(value);
125813
125928
  }
125814
125929
  function isComparisonTableRowValid(row) {
125815
- const rec = asRecord7(row);
125930
+ const rec = asRecord8(row);
125816
125931
  if (!rec) return false;
125817
125932
  if (!String(rec.title ?? "").trim()) return false;
125818
125933
  if (!Number.isFinite(Number(rec.currentCost))) return false;
@@ -125824,12 +125939,12 @@ function isComparisonTableRowValid(row) {
125824
125939
  return true;
125825
125940
  }
125826
125941
  function isNewFeatureRowValid(row) {
125827
- const rec = asRecord7(row);
125942
+ const rec = asRecord8(row);
125828
125943
  if (!rec) return false;
125829
125944
  return Boolean(String(rec.strategyName ?? rec.feature ?? rec.strategy ?? "").trim());
125830
125945
  }
125831
125946
  function isBiddingStrategyRowValid(row) {
125832
- const rec = asRecord7(row);
125947
+ const rec = asRecord8(row);
125833
125948
  if (!rec) return false;
125834
125949
  if (!String(rec.campaignName ?? "").trim()) return false;
125835
125950
  if (!Number.isFinite(Number(rec.duration)) || Number(rec.duration) <= 0) return false;
@@ -125867,11 +125982,11 @@ function getFactItemsErrors(data) {
125867
125982
  for (const mod of GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES) {
125868
125983
  const validator = MODULE_ITEM_VALIDATORS[mod];
125869
125984
  const hint = MODULE_ITEM_ERROR_HINT[mod];
125870
- const block = asRecord7(data[mod]);
125985
+ const block = asRecord8(data[mod]);
125871
125986
  const items = block?.items;
125872
125987
  if (!Array.isArray(items) || items.length === 0) {
125873
125988
  if (mod === "keywords") {
125874
- const fullKw = asRecord7(data.fullKeywords)?.items;
125989
+ const fullKw = asRecord8(data.fullKeywords)?.items;
125875
125990
  if (Array.isArray(fullKw) && fullKw.length > 0) {
125876
125991
  errors.push(
125877
125992
  "keywords.items \u4E3A\u7A7A\u4F46 fullKeywords.items \u6709\u6570\u636E\uFF1B\u8BF7\u4FDD\u7559\u540C\u76EE\u5F55 google-ads-diagnosis-collect.json \u540E\u91CD\u65B0 render\uFF08\u4F1A\u81EA\u52A8\u6062\u590D\u5BF9\u6BD4\u8868\uFF09"
@@ -125896,15 +126011,15 @@ var LANDING_PAGE_SPEED_KEYS = [
125896
126011
  "speedIndex"
125897
126012
  ];
125898
126013
  function isLandingPageSpeedValid(value) {
125899
- const rec = asRecord7(value);
126014
+ const rec = asRecord8(value);
125900
126015
  if (!rec) return false;
125901
126016
  return LANDING_PAGE_SPEED_KEYS.every(
125902
126017
  (key) => typeof rec[key] === "number" && Number.isFinite(rec[key])
125903
126018
  );
125904
126019
  }
125905
126020
  function restoreLandingPageSpeedFromCollect(finalData, collectReportData) {
125906
- const finalBlock = asRecord7(finalData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
125907
- const collectBlock = asRecord7(collectReportData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
126021
+ const finalBlock = asRecord8(finalData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
126022
+ const collectBlock = asRecord8(collectReportData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
125908
126023
  if (!collectBlock) return { data: finalData, restored: false };
125909
126024
  const needsDesktop = !isLandingPageSpeedValid(finalBlock?.desktop) && isLandingPageSpeedValid(collectBlock.desktop);
125910
126025
  const needsMobile = !isLandingPageSpeedValid(finalBlock?.mobile) && isLandingPageSpeedValid(collectBlock.mobile);
@@ -125922,8 +126037,8 @@ function mergeComparisonItemsFromCollect(finalData, collectReportData) {
125922
126037
  const restoredModules = [];
125923
126038
  for (const mod of GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES) {
125924
126039
  const validator = MODULE_ITEM_VALIDATORS[mod];
125925
- const finalBlock = asRecord7(data[mod]);
125926
- const collectBlock = asRecord7(collectReportData[mod]);
126040
+ const finalBlock = asRecord8(data[mod]);
126041
+ const collectBlock = asRecord8(collectReportData[mod]);
125927
126042
  const finalItems = finalBlock?.items;
125928
126043
  const collectItems = collectBlock?.items;
125929
126044
  if (!Array.isArray(collectItems) || collectItems.length === 0) continue;
@@ -125939,7 +126054,7 @@ function mergeComparisonItemsFromCollect(finalData, collectReportData) {
125939
126054
  }
125940
126055
 
125941
126056
  // src/commands/google-ads-diagnosis/narrative-consistency-check.ts
125942
- function asRecord8(value) {
126057
+ function asRecord9(value) {
125943
126058
  if (value && typeof value === "object" && !Array.isArray(value)) {
125944
126059
  return value;
125945
126060
  }
@@ -125947,14 +126062,14 @@ function asRecord8(value) {
125947
126062
  }
125948
126063
  function collectNarrativeTexts(data) {
125949
126064
  const texts = [];
125950
- const summary = asRecord8(data.summary);
126065
+ const summary = asRecord9(data.summary);
125951
126066
  if (Array.isArray(summary?.keyIssues)) {
125952
126067
  for (const item of summary.keyIssues) texts.push(String(item ?? ""));
125953
126068
  }
125954
- const overview = asRecord8(data.diagnosisOverview);
126069
+ const overview = asRecord9(data.diagnosisOverview);
125955
126070
  if (Array.isArray(overview?.disadvantages)) {
125956
126071
  for (const item of overview.disadvantages) {
125957
- const rec = asRecord8(item);
126072
+ const rec = asRecord9(item);
125958
126073
  if (rec) {
125959
126074
  texts.push(String(rec.title ?? ""), String(rec.description ?? ""));
125960
126075
  } else {
@@ -125965,16 +126080,16 @@ function collectNarrativeTexts(data) {
125965
126080
  return texts.filter((t) => t.trim().length > 0);
125966
126081
  }
125967
126082
  function newFeatureAccountStatus(data, strategy) {
125968
- const nf = asRecord8(data.newFeatures);
126083
+ const nf = asRecord9(data.newFeatures);
125969
126084
  const items = nf?.items;
125970
126085
  if (!Array.isArray(items)) return null;
125971
- const row = items.find((it) => asRecord8(it)?.strategy === strategy);
125972
- const rec = asRecord8(row);
126086
+ const row = items.find((it) => asRecord9(it)?.strategy === strategy);
126087
+ const rec = asRecord9(row);
125973
126088
  if (!rec || typeof rec.accountStatus !== "boolean") return null;
125974
126089
  return rec.accountStatus;
125975
126090
  }
125976
126091
  function goldAccountFlag(data, key) {
125977
- const gold = asRecord8(data.goldAccount);
126092
+ const gold = asRecord9(data.goldAccount);
125978
126093
  const value = gold?.[key];
125979
126094
  return typeof value === "boolean" ? value : null;
125980
126095
  }
@@ -126005,7 +126120,7 @@ function getNarrativeConsistencyErrors(data) {
126005
126120
  }
126006
126121
 
126007
126122
  // src/commands/google-ads-diagnosis/render-report.ts
126008
- function asRecord9(value) {
126123
+ function asRecord10(value) {
126009
126124
  if (value && typeof value === "object" && !Array.isArray(value)) {
126010
126125
  return value;
126011
126126
  }
@@ -126014,8 +126129,8 @@ function asRecord9(value) {
126014
126129
  async function tryLoadCollectReportData(collectPath) {
126015
126130
  try {
126016
126131
  const raw = JSON.parse(await fs18.readFile(collectPath, "utf8"));
126017
- const root = asRecord9(raw);
126018
- const reportData = asRecord9(root?.reportData);
126132
+ const root = asRecord10(raw);
126133
+ const reportData = asRecord10(root?.reportData);
126019
126134
  return reportData ?? root;
126020
126135
  } catch {
126021
126136
  return null;
@@ -126032,7 +126147,7 @@ async function runGoogleAdsDiagnosisRender(opts) {
126032
126147
  `);
126033
126148
  process.exit(1);
126034
126149
  }
126035
- let data = asRecord9(dataRaw);
126150
+ let data = asRecord10(dataRaw);
126036
126151
  if (!opts.noMergeCollect) {
126037
126152
  const collectPath = path23.resolve(
126038
126153
  opts.collectFile ?? path23.join(path23.dirname(dataPath), GOOGLE_ADS_DIAGNOSIS_COLLECT_BASENAME)
@@ -126545,7 +126660,7 @@ function ensureScorecardFromSnapshot(healthDiagnosis, merged) {
126545
126660
  }
126546
126661
 
126547
126662
  // src/commands/facebook-analysis/merge-snapshot.ts
126548
- function asRecord10(value) {
126663
+ function asRecord11(value) {
126549
126664
  if (value && typeof value === "object" && !Array.isArray(value)) {
126550
126665
  return value;
126551
126666
  }
@@ -126594,7 +126709,7 @@ async function readManifest(snapshotDir) {
126594
126709
  async function loadSectionFile(snapshotDir, file) {
126595
126710
  try {
126596
126711
  const raw = await fs21.readFile(path28.join(snapshotDir, file), "utf8");
126597
- return asRecord10(JSON.parse(raw));
126712
+ return asRecord11(JSON.parse(raw));
126598
126713
  } catch {
126599
126714
  return null;
126600
126715
  }
@@ -126642,7 +126757,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126642
126757
  if (data) sectionMap.set(art.section, data);
126643
126758
  }
126644
126759
  const overview = sectionMap.get("overview");
126645
- const current = asRecord10(overview?.currentPeriod);
126760
+ const current = asRecord11(overview?.currentPeriod);
126646
126761
  const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
126647
126762
  const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
126648
126763
  const kpis = { ...payload.kpis ?? {} };
@@ -126764,7 +126879,7 @@ function isFiniteNumber(value) {
126764
126879
  function asArray2(value) {
126765
126880
  return Array.isArray(value) ? value : [];
126766
126881
  }
126767
- function asRecord11(value) {
126882
+ function asRecord12(value) {
126768
126883
  if (value && typeof value === "object" && !Array.isArray(value)) {
126769
126884
  return value;
126770
126885
  }
@@ -126776,26 +126891,26 @@ function pushMissing(missing, id, chapter, dimension, hint) {
126776
126891
  function countAdSetsWithSpend(tables) {
126777
126892
  const rows = asArray2(tables?.adSets);
126778
126893
  return rows.filter((row) => {
126779
- const r = asRecord11(row);
126894
+ const r = asRecord12(row);
126780
126895
  const spend = r?.spend;
126781
126896
  return isFiniteNumber(spend) && spend > 0;
126782
126897
  }).length;
126783
126898
  }
126784
126899
  function chartLabels(chart) {
126785
- const c = asRecord11(chart);
126900
+ const c = asRecord12(chart);
126786
126901
  return asArray2(c?.labels).length;
126787
126902
  }
126788
126903
  function validateMetaPeriodReportContent(data) {
126789
126904
  const missing = [];
126790
- const meta = asRecord11(data.meta) ?? {};
126791
- const kpis = asRecord11(data.kpis) ?? {};
126792
- const narrative = asRecord11(data.narrative) ?? {};
126793
- const tables = asRecord11(data.tables);
126794
- const charts = asRecord11(data.charts);
126795
- const sections = asRecord11(data.sections);
126796
- const health = asRecord11(data.healthDiagnosis);
126797
- const priorityPlan = asRecord11(data.priorityPlan);
126798
- const actionChecklist = asRecord11(data.actionChecklist);
126905
+ const meta = asRecord12(data.meta) ?? {};
126906
+ const kpis = asRecord12(data.kpis) ?? {};
126907
+ const narrative = asRecord12(data.narrative) ?? {};
126908
+ const tables = asRecord12(data.tables);
126909
+ const charts = asRecord12(data.charts);
126910
+ const sections = asRecord12(data.sections);
126911
+ const health = asRecord12(data.healthDiagnosis);
126912
+ const priorityPlan = asRecord12(data.priorityPlan);
126913
+ const actionChecklist = asRecord12(data.actionChecklist);
126799
126914
  const adSetsWithSpend2 = countAdSetsWithSpend(tables ?? void 0);
126800
126915
  const regionalNarratives = asArray2(narrative.regional).length;
126801
126916
  const recommendations = asArray2(narrative.recommendations);
@@ -126867,7 +126982,7 @@ function validateMetaPeriodReportContent(data) {
126867
126982
  "charts.audience.labels \u81F3\u5C11 1 \u9879"
126868
126983
  );
126869
126984
  }
126870
- const funnel = asRecord11(charts?.funnel);
126985
+ const funnel = asRecord12(charts?.funnel);
126871
126986
  if (!isFiniteNumber(funnel?.reach)) {
126872
126987
  pushMissing(
126873
126988
  missing,
@@ -126931,7 +127046,7 @@ function validateMetaPeriodReportContent(data) {
126931
127046
  );
126932
127047
  } else {
126933
127048
  for (let i = 0; i < regionalNarratives; i++) {
126934
- const row = asRecord11(asArray2(narrative.regional)[i]);
127049
+ const row = asRecord12(asArray2(narrative.regional)[i]);
126935
127050
  if (textLen2(row?.text) < 80) {
126936
127051
  pushMissing(
126937
127052
  missing,
@@ -126961,7 +127076,7 @@ function validateMetaPeriodReportContent(data) {
126961
127076
  "\u5FC5\u987B\u6070\u597D 4 \u6761\uFF08\u7B80\u5316\u8868\u5355/\u533A\u57DF\u8C03\u6574/\u9884\u7B97\u91CD\u6784/\u7D20\u6750\u5EFA\u8BAE\uFF09"
126962
127077
  );
126963
127078
  } else {
126964
- const titles = recommendations.map((r) => asRecord11(r)?.title);
127079
+ const titles = recommendations.map((r) => asRecord12(r)?.title);
126965
127080
  for (const title of RECOMMENDATION_TITLES) {
126966
127081
  if (!titles.includes(title)) {
126967
127082
  pushMissing(
@@ -126974,7 +127089,7 @@ function validateMetaPeriodReportContent(data) {
126974
127089
  }
126975
127090
  }
126976
127091
  for (let i = 0; i < recommendations.length; i++) {
126977
- const rec = asRecord11(recommendations[i]);
127092
+ const rec = asRecord12(recommendations[i]);
126978
127093
  if (textLen2(rec?.content) < 150) {
126979
127094
  pushMissing(
126980
127095
  missing,
@@ -126996,7 +127111,7 @@ function validateMetaPeriodReportContent(data) {
126996
127111
  );
126997
127112
  } else {
126998
127113
  for (let i = 0; i < supplementary.length; i++) {
126999
- const row = asRecord11(supplementary[i]);
127114
+ const row = asRecord12(supplementary[i]);
127000
127115
  if (!textLen2(row?.dimension) || !textLen2(row?.issue)) {
127001
127116
  pushMissing(
127002
127117
  missing,
@@ -127078,7 +127193,7 @@ function validateMetaPeriodReportContent(data) {
127078
127193
  );
127079
127194
  } else {
127080
127195
  for (let i = 0; i < fourQuestions.length; i++) {
127081
- const q = asRecord11(fourQuestions[i]);
127196
+ const q = asRecord12(fourQuestions[i]);
127082
127197
  const evidence = asArray2(q?.evidence).filter((e) => textLen2(e) > 0);
127083
127198
  if (evidence.length < 2) {
127084
127199
  pushMissing(
@@ -127102,7 +127217,7 @@ function validateMetaPeriodReportContent(data) {
127102
127217
  }
127103
127218
  let scorecardValidRows = 0;
127104
127219
  for (let i = 0; i < scorecard.length; i++) {
127105
- const row = asRecord11(scorecard[i]);
127220
+ const row = asRecord12(scorecard[i]);
127106
127221
  const itemOk = textLen2(row?.item) > 0;
127107
127222
  const dataOk = textLen2(row?.data) > 0;
127108
127223
  const signalOk = SCORECARD_SIGNALS.includes(row?.signal);
@@ -127144,7 +127259,7 @@ function validateMetaPeriodReportContent(data) {
127144
127259
  );
127145
127260
  }
127146
127261
  for (const key of ["platform", "country", "adSets"]) {
127147
- const insight = textLen2(asRecord11(sections?.[key])?.insight);
127262
+ const insight = textLen2(asRecord12(sections?.[key])?.insight);
127148
127263
  if (insight < 200) {
127149
127264
  pushMissing(
127150
127265
  missing,
@@ -127155,7 +127270,7 @@ function validateMetaPeriodReportContent(data) {
127155
127270
  );
127156
127271
  }
127157
127272
  }
127158
- const audience = asRecord11(sections?.audience);
127273
+ const audience = asRecord12(sections?.audience);
127159
127274
  if (asArray2(audience?.goldenProfile).filter((s) => textLen2(s) > 0).length < 3) {
127160
127275
  pushMissing(
127161
127276
  missing,
@@ -127183,7 +127298,7 @@ function validateMetaPeriodReportContent(data) {
127183
127298
  "\u2265150 \u5B57"
127184
127299
  );
127185
127300
  }
127186
- const landingRows = asArray2(asRecord11(sections?.landingPage)?.rows);
127301
+ const landingRows = asArray2(asRecord12(sections?.landingPage)?.rows);
127187
127302
  if (landingRows.length < 3) {
127188
127303
  pushMissing(
127189
127304
  missing,
@@ -127268,7 +127383,7 @@ async function readJsonFile2(filePath) {
127268
127383
  throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
127269
127384
  }
127270
127385
  }
127271
- function asRecord12(value) {
127386
+ function asRecord13(value) {
127272
127387
  if (value && typeof value === "object" && !Array.isArray(value)) {
127273
127388
  return value;
127274
127389
  }
@@ -127283,7 +127398,7 @@ function injectReportData2(html, payload) {
127283
127398
  }
127284
127399
  async function runFacebookAnalysisRender(opts) {
127285
127400
  const dataPath = path29.resolve(opts.dataFile);
127286
- let data = asRecord12(await readJsonFile2(dataPath));
127401
+ let data = asRecord13(await readJsonFile2(dataPath));
127287
127402
  if (opts.snapshotDir?.trim()) {
127288
127403
  data = await mergeFacebookSnapshotIntoReport(data, path29.resolve(opts.snapshotDir));
127289
127404
  }
@@ -127692,6 +127807,55 @@ function syncOverviewBalanceFields(record) {
127692
127807
  return out;
127693
127808
  }
127694
127809
 
127810
+ // src/commands/bing-analysis/normalize-bing-rates.ts
127811
+ var RATE_FIELD_NAMES2 = /* @__PURE__ */ new Set(["ctr", "conversionRate"]);
127812
+ var BING_RATE_BEARING_SECTIONS = /* @__PURE__ */ new Set([
127813
+ "overview",
127814
+ "device",
127815
+ "geographic",
127816
+ "age-audience",
127817
+ "gender-audience",
127818
+ "audience-merged",
127819
+ "campaigns",
127820
+ "ad-groups",
127821
+ "ads",
127822
+ "keywords",
127823
+ "search-terms"
127824
+ ]);
127825
+ function divIfFiniteNumber2(v) {
127826
+ return typeof v === "number" && Number.isFinite(v) ? v / 100 : v;
127827
+ }
127828
+ function divideRatesInPlace2(value, seen) {
127829
+ if (value === null || typeof value !== "object") return;
127830
+ if (seen.has(value)) return;
127831
+ seen.add(value);
127832
+ if (Array.isArray(value)) {
127833
+ for (const item of value) divideRatesInPlace2(item, seen);
127834
+ return;
127835
+ }
127836
+ const obj = value;
127837
+ for (const k of Object.keys(obj)) {
127838
+ if (RATE_FIELD_NAMES2.has(k)) {
127839
+ obj[k] = divIfFiniteNumber2(obj[k]);
127840
+ } else {
127841
+ divideRatesInPlace2(obj[k], seen);
127842
+ }
127843
+ }
127844
+ }
127845
+ function normalizeBingRateScales(payload, section) {
127846
+ if (!BING_RATE_BEARING_SECTIONS.has(section)) return payload;
127847
+ divideRatesInPlace2(payload, /* @__PURE__ */ new WeakSet());
127848
+ return payload;
127849
+ }
127850
+ var BING_RATE_NORMALIZED_OUTLINE_HINTS = [
127851
+ "// \u6982\u7387\u5B57\u6BB5\uFF1A`ctr` / `conversionRate` \u5DF2\u7531 bing-analysis CLI \u4ECE\u7F51\u5173\u767E\u5206\u6570\u523B\u5EA6\u5F52\u4E00\u4E3A **0~1 \u5C0F\u6570**\uFF08\u5982 `0.0074` = 0.74%\uFF09\u3002",
127852
+ "// \u5199\u300Cx%\u300D\u6587\u6848\uFF1A`(v * 100).toFixed(2) + '%'`\uFF1B**\u7981\u6B62**\u518D \xF7100\uFF0C\u4E5F**\u7981\u6B62**\u628A JSON \u6570\u503C\u5F53\u300C\u5DF2\u662F\u767E\u5206\u6570\u300D\u76F4\u63A5\u52A0 `%`\uFF08\u4F1A \xD7100 \u9519\u8BEF\uFF0C\u5982 0.74% \u663E\u793A\u6210 74%\uFF09\u3002"
127853
+ ];
127854
+ function buildBingOutlineHints(section) {
127855
+ if (!BING_RATE_BEARING_SECTIONS.has(section)) return void 0;
127856
+ return BING_RATE_NORMALIZED_OUTLINE_HINTS;
127857
+ }
127858
+
127695
127859
  // src/commands/bing-analysis/fetch.ts
127696
127860
  var BING_KEYWORD_LIMIT_DEFAULT = 100;
127697
127861
  function stripLocalDate(d) {
@@ -127804,7 +127968,10 @@ async function fetchBingOverviewPayload(config, id, startDate, endDate, verbose)
127804
127968
  endDate,
127805
127969
  verbose
127806
127970
  );
127807
- return syncOverviewBalanceFields(withPrevious);
127971
+ return normalizeBingSectionPayload(
127972
+ syncOverviewBalanceFields(withPrevious),
127973
+ "overview"
127974
+ );
127808
127975
  }
127809
127976
  async function enrichBingOverviewPreviousPeriod(config, accountId, record, startDate, endDate, verbose) {
127810
127977
  if (!isBingPeriodBlockEmpty(record.previousPeriod)) {
@@ -127848,13 +128015,16 @@ async function fetchBingAudienceMergedPayload(config, id, startDate, endDate, ve
127848
128015
  fetchBingJson(config, ageUrl, verbose),
127849
128016
  fetchBingJson(config, genderUrl, verbose)
127850
128017
  ]);
127851
- return {
127852
- mergedParts: ["AgeAudienceData", "GenderAudienceData"],
127853
- startDate,
127854
- endDate,
127855
- account: id,
127856
- data: { ageAudience: ageData, genderAudience: genderData }
127857
- };
128018
+ return normalizeBingSectionPayload(
128019
+ {
128020
+ mergedParts: ["AgeAudienceData", "GenderAudienceData"],
128021
+ startDate,
128022
+ endDate,
128023
+ account: id,
128024
+ data: { ageAudience: ageData, genderAudience: genderData }
128025
+ },
128026
+ "audience-merged"
128027
+ );
127858
128028
  }
127859
128029
  async function fetchBingSectionPayload(def, opts, config, id) {
127860
128030
  const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
@@ -127871,7 +128041,12 @@ async function fetchBingSectionPayload(def, opts, config, id) {
127871
128041
  params.set("orderByCost", "true");
127872
128042
  }
127873
128043
  const url = reportingUrl(config, id, def.segment, params.toString());
127874
- return fetchBingJson(config, url, verbose);
128044
+ const raw = await fetchBingJson(config, url, verbose);
128045
+ return normalizeBingRateScales(raw, def.name);
128046
+ }
128047
+ function normalizeBingSectionPayload(payload, section) {
128048
+ if (payload === null || typeof payload !== "object") return payload;
128049
+ return normalizeBingRateScales(payload, section);
127875
128050
  }
127876
128051
  function endpointHintForBingSection(def) {
127877
128052
  return `GET \u2026/BingV2/{id}/${def.segment}`;
@@ -127930,7 +128105,8 @@ async function runAllBingSections(opts) {
127930
128105
  accountId: id,
127931
128106
  dateRange: { start: startDate, end: endDate },
127932
128107
  payload,
127933
- cliVersion
128108
+ cliVersion,
128109
+ outlineExtraHints: buildBingOutlineHints(def.name)
127934
128110
  });
127935
128111
  return {
127936
128112
  section: def.name,