siluzan-tso-cli 1.1.50-beta.2 → 1.1.50

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
@@ -258,9 +258,9 @@ var require_semver = __commonJS({
258
258
  } else {
259
259
  this.prerelease = m[4].split(".").map((id) => {
260
260
  if (/^[0-9]+$/.test(id)) {
261
- const num11 = +id;
262
- if (num11 >= 0 && num11 < MAX_SAFE_INTEGER) {
263
- return num11;
261
+ const num12 = +id;
262
+ if (num12 >= 0 && num12 < MAX_SAFE_INTEGER) {
263
+ return num12;
264
264
  }
265
265
  }
266
266
  return id;
@@ -4018,7 +4018,7 @@ var DEFAULT_API_BASE;
4018
4018
  var init_defaults = __esm({
4019
4019
  "src/config/defaults.ts"() {
4020
4020
  "use strict";
4021
- DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
4021
+ DEFAULT_API_BASE = "https://tso-api.siluzan.com";
4022
4022
  }
4023
4023
  });
4024
4024
 
@@ -102833,20 +102833,138 @@ var init_fetch = __esm({
102833
102833
  }
102834
102834
  });
102835
102835
 
102836
+ // src/commands/google-analysis/okki-display.ts
102837
+ function num2(value) {
102838
+ const n = typeof value === "number" ? value : Number(value);
102839
+ return Number.isFinite(n) ? n : 0;
102840
+ }
102841
+ function round22(value) {
102842
+ return Math.round(value * 100) / 100;
102843
+ }
102844
+ function share(part, total) {
102845
+ return total > 0 ? part / total : 0;
102846
+ }
102847
+ function unwrapOkkiKeywordDisplay(raw) {
102848
+ const t = raw.trim();
102849
+ if (t.length < 2) return t;
102850
+ const first = t[0];
102851
+ const last = t[t.length - 1];
102852
+ if (first === "[" && last === "]") return t.slice(1, -1).trim();
102853
+ if (first === '"' && last === '"') return t.slice(1, -1).trim();
102854
+ if ((first === "\u201C" || first === "\u201D") && (last === "\u201C" || last === "\u201D")) {
102855
+ return t.slice(1, -1).trim();
102856
+ }
102857
+ return t;
102858
+ }
102859
+ function classifyOkkiMatchKind(matchType) {
102860
+ const raw = typeof matchType === "string" ? matchType.trim() : "";
102861
+ if (!raw) return "unknown";
102862
+ const lower = raw.toLowerCase();
102863
+ if (raw.includes("\u5E7F\u6CDB") || lower.includes("broad")) return "broad";
102864
+ if (raw.includes("\u8BCD\u7EC4") || lower.includes("phrase")) return "phrase";
102865
+ if (raw.includes("\u7CBE\u786E") || raw.includes("\u7CBE\u51C6") || raw.includes("\u5B8C\u5168") || lower.includes("exact")) {
102866
+ return "exact";
102867
+ }
102868
+ return "unknown";
102869
+ }
102870
+ function formatOkkiKeywordDisplay(keyword, matchType) {
102871
+ const raw = typeof keyword === "string" ? keyword.trim() : keyword == null ? "" : String(keyword).trim();
102872
+ if (!raw) return raw;
102873
+ const kind = classifyOkkiMatchKind(matchType);
102874
+ if (kind === "unknown") return raw;
102875
+ const core = unwrapOkkiKeywordDisplay(raw);
102876
+ if (kind === "exact") return `[${core}]`;
102877
+ if (kind === "phrase") return `"${core}"`;
102878
+ return core;
102879
+ }
102880
+ function classifyDeviceKind(value) {
102881
+ const raw = typeof value === "string" ? value.trim() : "";
102882
+ const lower = raw.toLowerCase();
102883
+ if (raw.includes("\u79FB\u52A8") || lower.includes("mobile")) return "mobile";
102884
+ if (raw.includes("\u5E73\u677F") || lower.includes("tablet")) return "tablet";
102885
+ if (raw.includes("\u8BA1\u7B97\u673A") || raw.includes("\u7535\u8111") || lower.includes("desktop") || lower.includes("computer")) {
102886
+ return "desktop";
102887
+ }
102888
+ return "other";
102889
+ }
102890
+ function okkiGeoInteractionCount(row) {
102891
+ const interactions = num2(row.interactions);
102892
+ if (interactions > 0) return interactions;
102893
+ const clicks = num2(row.clicks);
102894
+ return clicks > 0 ? clicks : 0;
102895
+ }
102896
+ function summarizeOkkiDeviceSpend(rows) {
102897
+ const acc = { desktop: 0, mobile: 0, tablet: 0, other: 0 };
102898
+ for (const row of rows) {
102899
+ acc[classifyDeviceKind(row.deviceType)] += num2(row.spend);
102900
+ }
102901
+ const desktop = round22(acc.desktop);
102902
+ const mobile = round22(acc.mobile);
102903
+ const tablet = round22(acc.tablet);
102904
+ const other = round22(acc.other);
102905
+ const total = round22(desktop + mobile + tablet + other);
102906
+ return {
102907
+ desktop,
102908
+ mobile,
102909
+ tablet,
102910
+ other,
102911
+ total,
102912
+ desktopShare: share(desktop, total),
102913
+ mobileShare: share(mobile, total),
102914
+ desktopLeads: desktop > mobile,
102915
+ mobileLeads: mobile > desktop
102916
+ };
102917
+ }
102918
+ function narrativeClaimsMobileSpendLeads(text2) {
102919
+ const t = text2.replace(/\s+/g, "");
102920
+ if (!t.includes("\u79FB\u52A8")) return false;
102921
+ return /移动(?:端|设备)?(?:为|是)主要消耗/.test(t) || /移动(?:端|设备)?为主要(?:消耗)?设备/.test(t) || /移动(?:端|设备)?(?:消耗|花费|费用|流量)?占比高/.test(t);
102922
+ }
102923
+ function asRecord11(value) {
102924
+ if (value && typeof value === "object" && !Array.isArray(value)) return value;
102925
+ return null;
102926
+ }
102927
+ function stringList(value) {
102928
+ return (Array.isArray(value) ? value : []).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
102929
+ }
102930
+ function collectOkkiNarrativeText(data) {
102931
+ const narrative = asRecord11(data.narrative) ?? {};
102932
+ const parts = [];
102933
+ const sheetAnalysis = asRecord11(narrative.sheetAnalysis) ?? {};
102934
+ for (const block of Object.values(sheetAnalysis)) {
102935
+ const rec = asRecord11(block);
102936
+ parts.push(...stringList(rec?.summary), ...stringList(rec?.suggestions));
102937
+ }
102938
+ const review = asRecord11(narrative.review) ?? {};
102939
+ for (const block of Object.values(review)) {
102940
+ const rec = asRecord11(block);
102941
+ for (const key of ["overview", "summary", "suggestion"]) {
102942
+ const value = rec?.[key];
102943
+ if (typeof value === "string" && value.trim()) parts.push(value.trim());
102944
+ }
102945
+ }
102946
+ return parts.join("\n");
102947
+ }
102948
+ var init_okki_display = __esm({
102949
+ "src/commands/google-analysis/okki-display.ts"() {
102950
+ "use strict";
102951
+ }
102952
+ });
102953
+
102836
102954
  // src/commands/google-analysis/merge-snapshot.ts
102837
102955
  import * as fs22 from "fs/promises";
102838
102956
  import * as path27 from "path";
102839
- function asRecord11(value) {
102957
+ function asRecord12(value) {
102840
102958
  if (value && typeof value === "object" && !Array.isArray(value)) {
102841
102959
  return value;
102842
102960
  }
102843
102961
  return null;
102844
102962
  }
102845
- function num2(value) {
102963
+ function num3(value) {
102846
102964
  const n = Number(value);
102847
102965
  return Number.isFinite(n) ? n : void 0;
102848
102966
  }
102849
- function round22(value) {
102967
+ function round23(value) {
102850
102968
  return value == null ? void 0 : Math.round(value * 100) / 100;
102851
102969
  }
102852
102970
  async function readManifest(snapshotDir, accountId) {
@@ -102921,31 +103039,31 @@ async function recoverMissingReportSections(sectionMap, absDir, accountId, prefe
102921
103039
  function periodMetricsFromRow(row) {
102922
103040
  if (!row) return null;
102923
103041
  return {
102924
- cost: num2(row.spend),
102925
- impressions: num2(row.impressions),
102926
- clicks: num2(row.clicks),
102927
- conversions: num2(row.conversions),
102928
- ctr: num2(row.ctr),
102929
- averageCpc: num2(row.averageCpc),
102930
- costPerConversion: num2(row.costPerConversion)
103042
+ cost: num3(row.spend),
103043
+ impressions: num3(row.impressions),
103044
+ clicks: num3(row.clicks),
103045
+ conversions: num3(row.conversions),
103046
+ ctr: num3(row.ctr),
103047
+ averageCpc: num3(row.averageCpc),
103048
+ costPerConversion: num3(row.costPerConversion)
102931
103049
  };
102932
103050
  }
102933
103051
  function items(envelope) {
102934
103052
  if (!envelope || !Array.isArray(envelope.items)) return [];
102935
- return envelope.items.filter((row) => asRecord11(row) !== null);
103053
+ return envelope.items.filter((row) => asRecord12(row) !== null);
102936
103054
  }
102937
103055
  function record(envelope) {
102938
103056
  return envelope?.record ?? null;
102939
103057
  }
102940
103058
  function pickMetricRow(row, extraFields) {
102941
103059
  const base = {
102942
- spend: num2(row.spend) ?? 0,
102943
- impressions: num2(row.impressions) ?? 0,
102944
- clicks: num2(row.clicks) ?? 0,
102945
- ctr: num2(row.ctr),
102946
- averageCpc: num2(row.averageCpc),
102947
- conversions: num2(row.conversions) ?? 0,
102948
- costPerConversion: num2(row.costPerConversion)
103060
+ spend: num3(row.spend) ?? 0,
103061
+ impressions: num3(row.impressions) ?? 0,
103062
+ clicks: num3(row.clicks) ?? 0,
103063
+ ctr: num3(row.ctr),
103064
+ averageCpc: num3(row.averageCpc),
103065
+ conversions: num3(row.conversions) ?? 0,
103066
+ costPerConversion: num3(row.costPerConversion)
102949
103067
  };
102950
103068
  for (const f of extraFields) {
102951
103069
  if (row[f] !== void 0) base[f] = row[f];
@@ -102959,34 +103077,34 @@ function sumCampaignRowsToPeriodKpis(rows) {
102959
103077
  let clicks = 0;
102960
103078
  let conversions = 0;
102961
103079
  for (const row of rows) {
102962
- cost += num2(row.spend) ?? 0;
102963
- impressions += num2(row.impressions) ?? 0;
102964
- clicks += num2(row.clicks) ?? 0;
102965
- conversions += num2(row.conversions) ?? 0;
103080
+ cost += num3(row.spend) ?? 0;
103081
+ impressions += num3(row.impressions) ?? 0;
103082
+ clicks += num3(row.clicks) ?? 0;
103083
+ conversions += num3(row.conversions) ?? 0;
102966
103084
  }
102967
103085
  const ctr = impressions > 0 ? clicks / impressions : void 0;
102968
103086
  const averageCpc = clicks > 0 ? cost / clicks : void 0;
102969
103087
  const costPerConversion = conversions > 0 ? cost / conversions : void 0;
102970
103088
  return {
102971
- cost: round22(cost),
103089
+ cost: round23(cost),
102972
103090
  impressions: Math.round(impressions),
102973
103091
  clicks: Math.round(clicks),
102974
- conversions: round22(conversions),
103092
+ conversions: round23(conversions),
102975
103093
  ctr,
102976
- averageCpc: round22(averageCpc),
102977
- costPerConversion: round22(costPerConversion)
103094
+ averageCpc: round23(averageCpc),
103095
+ costPerConversion: round23(costPerConversion)
102978
103096
  };
102979
103097
  }
102980
103098
  async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102981
103099
  const absDir = path27.resolve(snapshotDir);
102982
- const metaAccountId = typeof asRecord11(payload.meta)?.accountId === "string" ? asRecord11(payload.meta)?.accountId : void 0;
103100
+ const metaAccountId = typeof asRecord12(payload.meta)?.accountId === "string" ? asRecord12(payload.meta)?.accountId : void 0;
102983
103101
  const manifest = await readManifest(snapshotDir, metaAccountId);
102984
103102
  if (!manifest) {
102985
103103
  throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C google-analysis --json-out`);
102986
103104
  }
102987
103105
  const accountId = manifest.googleAnalysis?.accountId ?? metaAccountId;
102988
103106
  const dateRange = manifest.googleAnalysis?.dateRange;
102989
- const preferredDateTag = dateTagFromRange(dateRange) ?? dateTagFromMeta(asRecord11(payload.meta) ?? void 0);
103107
+ const preferredDateTag = dateTagFromRange(dateRange) ?? dateTagFromMeta(asRecord12(payload.meta) ?? void 0);
102990
103108
  const sectionMap = /* @__PURE__ */ new Map();
102991
103109
  for (const art of manifest.artifacts ?? []) {
102992
103110
  const data = await loadSectionFile(absDir, art.file);
@@ -102994,8 +103112,8 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102994
103112
  }
102995
103113
  await recoverMissingReportSections(sectionMap, absDir, accountId, preferredDateTag);
102996
103114
  const overviewRecord = record(sectionMap.get("overview"));
102997
- const currentPeriod = asRecord11(overviewRecord?.currentPeriod);
102998
- const previousPeriod = asRecord11(overviewRecord?.previousPeriod);
103115
+ const currentPeriod = asRecord12(overviewRecord?.currentPeriod);
103116
+ const previousPeriod = asRecord12(overviewRecord?.previousPeriod);
102999
103117
  const meta = { ...payload.meta ?? {} };
103000
103118
  if (!meta.accountId && accountId) meta.accountId = accountId;
103001
103119
  if (!meta.currency) {
@@ -103022,20 +103140,20 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
103022
103140
  }
103023
103141
  }
103024
103142
  if (overviewRecord) {
103025
- const activeDays = num2(overviewRecord.activeDays);
103143
+ const activeDays = num3(overviewRecord.activeDays);
103026
103144
  if (activeDays != null) kpis.activeDays = Math.round(activeDays);
103027
- const averageDailyCost = num2(overviewRecord.averageDailyCost);
103028
- if (averageDailyCost != null) kpis.averageDailyCost = round22(averageDailyCost);
103145
+ const averageDailyCost = num3(overviewRecord.averageDailyCost);
103146
+ if (averageDailyCost != null) kpis.averageDailyCost = round23(averageDailyCost);
103029
103147
  }
103030
103148
  if (previousPeriod) {
103031
103149
  kpis.previousPeriod = {
103032
- cost: num2(previousPeriod.spend),
103033
- impressions: num2(previousPeriod.impressions),
103034
- clicks: num2(previousPeriod.clicks),
103035
- conversions: num2(previousPeriod.conversions),
103036
- ctr: num2(previousPeriod.ctr),
103037
- averageCpc: num2(previousPeriod.averageCpc),
103038
- costPerConversion: num2(previousPeriod.costPerConversion)
103150
+ cost: num3(previousPeriod.spend),
103151
+ impressions: num3(previousPeriod.impressions),
103152
+ clicks: num3(previousPeriod.clicks),
103153
+ conversions: num3(previousPeriod.conversions),
103154
+ ctr: num3(previousPeriod.ctr),
103155
+ averageCpc: num3(previousPeriod.averageCpc),
103156
+ costPerConversion: num3(previousPeriod.costPerConversion)
103039
103157
  };
103040
103158
  }
103041
103159
  const charts = { ...payload.charts ?? {} };
@@ -103047,25 +103165,25 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
103047
103165
  );
103048
103166
  charts.dailyTrend = {
103049
103167
  dates: sorted.map((r) => String(r.date ?? "\u2014")),
103050
- cost: sorted.map((r) => round22(num2(r.spend)) ?? 0),
103051
- conversions: sorted.map((r) => Math.round(num2(r.conversions) ?? 0)),
103052
- cpa: sorted.map((r) => round22(num2(r.costPerConversion)) ?? 0)
103168
+ cost: sorted.map((r) => round23(num3(r.spend)) ?? 0),
103169
+ conversions: sorted.map((r) => Math.round(num3(r.conversions) ?? 0)),
103170
+ cpa: sorted.map((r) => round23(num3(r.costPerConversion)) ?? 0)
103053
103171
  };
103054
103172
  }
103055
103173
  }
103056
103174
  const tables = { ...payload.tables ?? {} };
103057
103175
  if (dimensionSummaryRecord) {
103058
103176
  tables.dimensionSummary = {
103059
- cost: num2(dimensionSummaryRecord.spend),
103060
- impressions: num2(dimensionSummaryRecord.impressions),
103061
- clicks: num2(dimensionSummaryRecord.clicks),
103062
- conversions: num2(dimensionSummaryRecord.conversions),
103063
- ctr: num2(dimensionSummaryRecord.ctr),
103064
- averageCpc: num2(dimensionSummaryRecord.averageCpc),
103065
- costPerConversion: num2(dimensionSummaryRecord.costPerConversion),
103066
- searchImpressionShare: num2(dimensionSummaryRecord.searchImpressionShare),
103067
- searchBudgetLostImpressionShare: num2(dimensionSummaryRecord.searchBudgetLostImpressionShare),
103068
- searchRankLostImpressionShare: num2(dimensionSummaryRecord.searchRankLostImpressionShare)
103177
+ cost: num3(dimensionSummaryRecord.spend),
103178
+ impressions: num3(dimensionSummaryRecord.impressions),
103179
+ clicks: num3(dimensionSummaryRecord.clicks),
103180
+ conversions: num3(dimensionSummaryRecord.conversions),
103181
+ ctr: num3(dimensionSummaryRecord.ctr),
103182
+ averageCpc: num3(dimensionSummaryRecord.averageCpc),
103183
+ costPerConversion: num3(dimensionSummaryRecord.costPerConversion),
103184
+ searchImpressionShare: num3(dimensionSummaryRecord.searchImpressionShare),
103185
+ searchBudgetLostImpressionShare: num3(dimensionSummaryRecord.searchBudgetLostImpressionShare),
103186
+ searchRankLostImpressionShare: num3(dimensionSummaryRecord.searchRankLostImpressionShare)
103069
103187
  };
103070
103188
  }
103071
103189
  if (campaignRows3.length > 0) {
@@ -103128,13 +103246,13 @@ function textLen(value) {
103128
103246
  function asArray(value) {
103129
103247
  return Array.isArray(value) ? value : [];
103130
103248
  }
103131
- function asRecord12(value) {
103249
+ function asRecord13(value) {
103132
103250
  if (value && typeof value === "object" && !Array.isArray(value)) {
103133
103251
  return value;
103134
103252
  }
103135
103253
  return null;
103136
103254
  }
103137
- function stringList(value) {
103255
+ function stringList2(value) {
103138
103256
  return asArray(value).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
103139
103257
  }
103140
103258
  function pushMissing(missing, id, chapter, dimension, hint) {
@@ -103142,7 +103260,7 @@ function pushMissing(missing, id, chapter, dimension, hint) {
103142
103260
  }
103143
103261
  function validateOkkiWeeklyReportContent(data) {
103144
103262
  const missing = [];
103145
- const meta = asRecord12(data.meta) ?? {};
103263
+ const meta = asRecord13(data.meta) ?? {};
103146
103264
  if (!textLen(meta.accountId)) {
103147
103265
  pushMissing(
103148
103266
  missing,
@@ -103152,7 +103270,7 @@ function validateOkkiWeeklyReportContent(data) {
103152
103270
  "meta.accountId \u5FC5\u586B\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 overview/manifest"
103153
103271
  );
103154
103272
  }
103155
- const snapshotSections = asRecord12(data.snapshot?.sections) ?? {};
103273
+ const snapshotSections = asRecord13(data.snapshot?.sections) ?? {};
103156
103274
  const snapshotPresent = {};
103157
103275
  for (const section of OKKI_SNAPSHOT_SECTIONS) {
103158
103276
  const present = snapshotSections[section] === true;
@@ -103167,14 +103285,14 @@ function validateOkkiWeeklyReportContent(data) {
103167
103285
  );
103168
103286
  }
103169
103287
  }
103170
- const narrative = asRecord12(data.narrative) ?? {};
103171
- const sheetAnalysis = asRecord12(narrative.sheetAnalysis) ?? {};
103172
- const review = asRecord12(narrative.review) ?? {};
103288
+ const narrative = asRecord13(data.narrative) ?? {};
103289
+ const sheetAnalysis = asRecord13(narrative.sheetAnalysis) ?? {};
103290
+ const review = asRecord13(narrative.review) ?? {};
103173
103291
  const sheetAnalysisCounts = {};
103174
103292
  for (const key of OKKI_SHEET_KEYS) {
103175
- const block = asRecord12(sheetAnalysis[key]);
103176
- const summary = stringList(block?.summary);
103177
- const suggestions = stringList(block?.suggestions);
103293
+ const block = asRecord13(sheetAnalysis[key]);
103294
+ const summary = stringList2(block?.summary);
103295
+ const suggestions = stringList2(block?.suggestions);
103178
103296
  sheetAnalysisCounts[key] = { summary: summary.length, suggestions: suggestions.length };
103179
103297
  const label = SHEET_LABELS[key];
103180
103298
  if (summary.length < 1) {
@@ -103198,7 +103316,7 @@ function validateOkkiWeeklyReportContent(data) {
103198
103316
  }
103199
103317
  const reviewFilled = {};
103200
103318
  for (const key of OKKI_REVIEW_KEYS) {
103201
- const block = asRecord12(review[key]);
103319
+ const block = asRecord13(review[key]);
103202
103320
  const overview = textLen(block?.overview);
103203
103321
  const summary = textLen(block?.summary);
103204
103322
  const suggestion = textLen(block?.suggestion);
@@ -103232,6 +103350,17 @@ function validateOkkiWeeklyReportContent(data) {
103232
103350
  );
103233
103351
  }
103234
103352
  }
103353
+ const deviceRows2 = Array.isArray(data.tables?.devices) ? data.tables.devices : [];
103354
+ const deviceSpend = summarizeOkkiDeviceSpend(deviceRows2.filter((row) => asRecord13(row) !== null));
103355
+ if (deviceSpend.total > 0 && deviceSpend.desktop >= deviceSpend.mobile && narrativeClaimsMobileSpendLeads(collectOkkiNarrativeText(data))) {
103356
+ pushMissing(
103357
+ missing,
103358
+ "narrative-devices-spend-lead",
103359
+ "\u8868\u672B\u5206\u6790 \xB7 \u8BBE\u5907",
103360
+ "narrative \u8BBE\u5907\u6D88\u8017\u4E3B\u6B21",
103361
+ `\u6587\u6848\u5199\u79FB\u52A8\u7AEF\u6D88\u8017/\u5360\u6BD4\u66F4\u9AD8\uFF0C\u4F46\u8868\u5185\u684C\u9762 \xA5${deviceSpend.desktop.toFixed(2)}\uFF08${(deviceSpend.desktopShare * 100).toFixed(1)}%\uFF09\u2265 \u79FB\u52A8 \xA5${deviceSpend.mobile.toFixed(2)}\uFF08${(deviceSpend.mobileShare * 100).toFixed(1)}%\uFF09\u3002\u8BF7\u6309\u8868\u5185\u8D39\u7528\u5408\u8BA1\u6539\u5199\uFF0C\u7981\u6B62\u628A\u884C\u6570\u6216\u5C55\u793A\u5F53\u6210\u6D88\u8017\u5360\u6BD4\u3002`
103362
+ );
103363
+ }
103235
103364
  return {
103236
103365
  ok: missing.length === 0,
103237
103366
  missing,
@@ -103253,6 +103382,7 @@ var OKKI_SHEET_KEYS, OKKI_REVIEW_KEYS, OKKI_SNAPSHOT_SECTIONS, SHEET_LABELS, REV
103253
103382
  var init_okki_report_content = __esm({
103254
103383
  "src/commands/google-analysis/okki-report-content.ts"() {
103255
103384
  "use strict";
103385
+ init_okki_display();
103256
103386
  OKKI_SHEET_KEYS = [
103257
103387
  "campaigns",
103258
103388
  "keywords",
@@ -103301,16 +103431,16 @@ var init_okki_report_content = __esm({
103301
103431
  // src/commands/google-analysis/okki-merge-snapshot.ts
103302
103432
  import * as fs23 from "fs/promises";
103303
103433
  import * as path28 from "path";
103304
- function num3(value) {
103434
+ function num4(value) {
103305
103435
  const n = Number(value);
103306
103436
  return Number.isFinite(n) ? n : void 0;
103307
103437
  }
103308
- function round23(value) {
103438
+ function round24(value) {
103309
103439
  return value == null ? void 0 : Math.round(value * 100) / 100;
103310
103440
  }
103311
103441
  function items2(envelope) {
103312
103442
  if (!envelope || !Array.isArray(envelope.items)) return [];
103313
- return envelope.items.filter((row) => asRecord12(row) !== null);
103443
+ return envelope.items.filter((row) => asRecord13(row) !== null);
103314
103444
  }
103315
103445
  function dateTagFromRange2(dateRange) {
103316
103446
  if (!dateRange || dateRange.mode !== "range") return void 0;
@@ -103354,14 +103484,14 @@ async function loadSectionFile2(snapshotDir, file) {
103354
103484
  }
103355
103485
  async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
103356
103486
  const absDir = path28.resolve(snapshotDir);
103357
- const metaAccountId = typeof asRecord12(payload.meta)?.accountId === "string" ? asRecord12(payload.meta)?.accountId : void 0;
103487
+ const metaAccountId = typeof asRecord13(payload.meta)?.accountId === "string" ? asRecord13(payload.meta)?.accountId : void 0;
103358
103488
  const manifest = await readManifest2(absDir, metaAccountId);
103359
103489
  if (!manifest) {
103360
103490
  throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C google-analysis --json-out`);
103361
103491
  }
103362
103492
  const accountId = manifest.googleAnalysis?.accountId ?? metaAccountId;
103363
103493
  const dateRange = manifest.googleAnalysis?.dateRange;
103364
- const preferredDateTag = dateTagFromRange2(dateRange) ?? dateTagFromMeta2(asRecord12(payload.meta) ?? void 0);
103494
+ const preferredDateTag = dateTagFromRange2(dateRange) ?? dateTagFromMeta2(asRecord13(payload.meta) ?? void 0);
103365
103495
  const sectionMap = /* @__PURE__ */ new Map();
103366
103496
  for (const art of manifest.artifacts ?? []) {
103367
103497
  const data = await loadSectionFile2(absDir, art.file);
@@ -103387,7 +103517,7 @@ async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
103387
103517
  snapshotPresent[section] = sectionMap.has(section);
103388
103518
  }
103389
103519
  const overviewRecord = sectionMap.get("overview")?.record ?? null;
103390
- const currentPeriod = asRecord12(overviewRecord?.currentPeriod);
103520
+ const currentPeriod = asRecord13(overviewRecord?.currentPeriod);
103391
103521
  const meta = { ...payload.meta ?? {} };
103392
103522
  if (!meta.accountId && accountId) meta.accountId = accountId;
103393
103523
  if (!meta.currency) {
@@ -103397,16 +103527,17 @@ async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
103397
103527
  if (!meta.startDate && dateRange && dateRange.mode === "range") meta.startDate = dateRange.start;
103398
103528
  if (!meta.endDate && dateRange && dateRange.mode === "range") meta.endDate = dateRange.end;
103399
103529
  const campaignRows3 = items2(sectionMap.get("campaigns"));
103530
+ const deviceRows2 = items2(sectionMap.get("campaign-device"));
103400
103531
  const campaignKpis = sumCampaignRowsToPeriodKpis(campaignRows3);
103401
103532
  const kpis = { ...payload.kpis ?? {} };
103402
103533
  const kpiSource = campaignKpis ?? (currentPeriod ? {
103403
- cost: num3(currentPeriod.spend),
103404
- impressions: num3(currentPeriod.impressions),
103405
- clicks: num3(currentPeriod.clicks),
103406
- conversions: num3(currentPeriod.conversions),
103407
- ctr: num3(currentPeriod.ctr),
103408
- averageCpc: num3(currentPeriod.averageCpc),
103409
- costPerConversion: num3(currentPeriod.costPerConversion)
103534
+ cost: num4(currentPeriod.spend),
103535
+ impressions: num4(currentPeriod.impressions),
103536
+ clicks: num4(currentPeriod.clicks),
103537
+ conversions: num4(currentPeriod.conversions),
103538
+ ctr: num4(currentPeriod.ctr),
103539
+ averageCpc: num4(currentPeriod.averageCpc),
103540
+ costPerConversion: num4(currentPeriod.costPerConversion)
103410
103541
  } : null);
103411
103542
  if (kpiSource) {
103412
103543
  if (kpiSource.cost != null) kpis.cost = kpiSource.cost;
@@ -103418,10 +103549,10 @@ async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
103418
103549
  if (kpiSource.costPerConversion != null) kpis.costPerConversion = kpiSource.costPerConversion;
103419
103550
  }
103420
103551
  if (overviewRecord) {
103421
- const activeDays = num3(overviewRecord.activeDays);
103552
+ const activeDays = num4(overviewRecord.activeDays);
103422
103553
  if (activeDays != null) kpis.activeDays = Math.round(activeDays);
103423
- const averageDailyCost = num3(overviewRecord.averageDailyCost);
103424
- if (averageDailyCost != null) kpis.averageDailyCost = round23(averageDailyCost);
103554
+ const averageDailyCost = num4(overviewRecord.averageDailyCost);
103555
+ if (averageDailyCost != null) kpis.averageDailyCost = round24(averageDailyCost);
103425
103556
  }
103426
103557
  return {
103427
103558
  ...payload,
@@ -103431,9 +103562,12 @@ async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
103431
103562
  campaigns: campaignRows3,
103432
103563
  keywords: items2(sectionMap.get("keywords")),
103433
103564
  searchTerms: items2(sectionMap.get("search-terms")),
103434
- devices: items2(sectionMap.get("campaign-device")),
103565
+ devices: deviceRows2,
103435
103566
  geo: items2(sectionMap.get("campaign-geo-matched"))
103436
103567
  },
103568
+ facts: {
103569
+ devices: summarizeOkkiDeviceSpend(deviceRows2)
103570
+ },
103437
103571
  snapshot: { sections: snapshotPresent }
103438
103572
  };
103439
103573
  }
@@ -103441,37 +103575,38 @@ var init_okki_merge_snapshot = __esm({
103441
103575
  "src/commands/google-analysis/okki-merge-snapshot.ts"() {
103442
103576
  "use strict";
103443
103577
  init_google_analysis();
103578
+ init_okki_display();
103444
103579
  init_merge_snapshot();
103445
103580
  init_okki_report_content();
103446
103581
  }
103447
103582
  });
103448
103583
 
103449
103584
  // src/commands/google-analysis/okki-render-excel.ts
103450
- function num4(value) {
103585
+ function num5(value) {
103451
103586
  if (value == null || value === "") return void 0;
103452
103587
  const n = typeof value === "number" ? value : Number(value);
103453
103588
  return Number.isFinite(n) ? n : void 0;
103454
103589
  }
103455
- function round24(value) {
103590
+ function round25(value) {
103456
103591
  return Math.round(value * 100) / 100;
103457
103592
  }
103458
103593
  function money(value) {
103459
- return round24(num4(value) ?? 0);
103594
+ return round25(num5(value) ?? 0);
103460
103595
  }
103461
103596
  function count(value) {
103462
- const n = num4(value) ?? 0;
103463
- return Number.isInteger(n) ? n : round24(n);
103597
+ const n = num5(value) ?? 0;
103598
+ return Number.isInteger(n) ? n : round25(n);
103464
103599
  }
103465
103600
  function rate(value) {
103466
- return num4(value) ?? 0;
103601
+ return num5(value) ?? 0;
103467
103602
  }
103468
103603
  function dashOrRate(numer, denom) {
103469
103604
  if (denom <= 0) return "\u2014";
103470
- return round24(numer / denom);
103605
+ return round25(numer / denom);
103471
103606
  }
103472
103607
  function dashOrMoney(numer, denom) {
103473
103608
  if (denom <= 0) return "\u2014";
103474
- return round24(numer / denom);
103609
+ return round25(numer / denom);
103475
103610
  }
103476
103611
  function str(value, fallback = "\u2014") {
103477
103612
  if (typeof value === "string" && value.trim()) return value.trim();
@@ -103505,18 +103640,18 @@ function targetingStatus(row) {
103505
103640
  return TARGETING_STATUS_ZH[raw] ?? (raw || "\u2014");
103506
103641
  }
103507
103642
  function conversionsPair(row) {
103508
- const conv = num4(row.conversions) ?? 0;
103509
- const all = num4(row.allConversions);
103643
+ const conv = num5(row.conversions) ?? 0;
103644
+ const all = num5(row.allConversions);
103510
103645
  return { all: all ?? conv, conv };
103511
103646
  }
103512
103647
  function interactionRateOf(row, impressions, interactions) {
103513
103648
  if (interactions <= 0) return "\u2014";
103514
- const parsed = typeof row.interactionRate === "string" ? Number.parseFloat(row.interactionRate) : num4(row.interactionRate);
103649
+ const parsed = typeof row.interactionRate === "string" ? Number.parseFloat(row.interactionRate) : num5(row.interactionRate);
103515
103650
  if (parsed != null) return parsed;
103516
103651
  return dashOrRate(interactions, impressions);
103517
103652
  }
103518
103653
  function sortBySpendDesc(rows) {
103519
- return [...rows].sort((a, b) => (num4(b.spend) ?? 0) - (num4(a.spend) ?? 0));
103654
+ return [...rows].sort((a, b) => (num5(b.spend) ?? 0) - (num5(a.spend) ?? 0));
103520
103655
  }
103521
103656
  function prefixLines(items3, firstPrefix) {
103522
103657
  return items3.map((text2, idx) => {
@@ -103525,18 +103660,18 @@ function prefixLines(items3, firstPrefix) {
103525
103660
  });
103526
103661
  }
103527
103662
  function analysisLines(data, key) {
103528
- const block = asRecord12(asRecord12(data.narrative)?.sheetAnalysis)?.[key];
103529
- const rec = asRecord12(block);
103663
+ const block = asRecord13(asRecord13(data.narrative)?.sheetAnalysis)?.[key];
103664
+ const rec = asRecord13(block);
103530
103665
  return [
103531
- ...prefixLines(stringList(rec?.summary), "\u603B\u7ED3\uFF1A"),
103532
- ...prefixLines(stringList(rec?.suggestions), "\u5EFA\u8BAE\uFF1A")
103666
+ ...prefixLines(stringList2(rec?.summary), "\u603B\u7ED3\uFF1A"),
103667
+ ...prefixLines(stringList2(rec?.suggestions), "\u5EFA\u8BAE\uFF1A")
103533
103668
  ];
103534
103669
  }
103535
103670
  function reviewLines(data) {
103536
- const review = asRecord12(asRecord12(data.narrative)?.review) ?? {};
103671
+ const review = asRecord13(asRecord13(data.narrative)?.review) ?? {};
103537
103672
  const lines = [];
103538
103673
  OKKI_REVIEW_KEYS.forEach((key, idx) => {
103539
- const block = asRecord12(review[key]) ?? {};
103674
+ const block = asRecord13(review[key]) ?? {};
103540
103675
  lines.push(`${idx + 1}\u3001${REVIEW_LABELS2[key]}\uFF1A${str(block.overview, "")}`);
103541
103676
  lines.push(` \u603B\u7ED3\uFF1A${str(block.summary, "")}`);
103542
103677
  lines.push(` \u5EFA\u8BAE\uFF1A${str(block.suggestion, "")}`);
@@ -103552,9 +103687,9 @@ function campaignRows(data) {
103552
103687
  let conv = 0;
103553
103688
  const body = rows.map((row) => {
103554
103689
  const pair = conversionsPair(row);
103555
- spend += num4(row.spend) ?? 0;
103556
- impressions += num4(row.impressions) ?? 0;
103557
- clicks += num4(row.clicks) ?? 0;
103690
+ spend += num5(row.spend) ?? 0;
103691
+ impressions += num5(row.impressions) ?? 0;
103692
+ clicks += num5(row.clicks) ?? 0;
103558
103693
  allConv += pair.all;
103559
103694
  conv += pair.conv;
103560
103695
  return [
@@ -103567,7 +103702,7 @@ function campaignRows(data) {
103567
103702
  money(row.averageCpc),
103568
103703
  count(pair.all),
103569
103704
  count(pair.conv),
103570
- num4(row.costPerConversion) == null && pair.conv <= 0 ? "\u2014" : money(row.costPerConversion),
103705
+ num5(row.costPerConversion) == null && pair.conv <= 0 ? "\u2014" : money(row.costPerConversion),
103571
103706
  rate(row.conversionRate)
103572
103707
  ];
103573
103708
  });
@@ -103576,7 +103711,7 @@ function campaignRows(data) {
103576
103711
  total: [
103577
103712
  "\u603B\u8BA1",
103578
103713
  "--",
103579
- round24(spend),
103714
+ round25(spend),
103580
103715
  Math.round(impressions),
103581
103716
  Math.round(clicks),
103582
103717
  dashOrRate(clicks, impressions),
@@ -103590,7 +103725,7 @@ function campaignRows(data) {
103590
103725
  }
103591
103726
  function keywordRows(data) {
103592
103727
  return sortBySpendDesc(data.tables?.keywords ?? []).map((row) => [
103593
- str(row.keyword),
103728
+ formatOkkiKeywordDisplay(row.keyword, row.keywordMatchTypeZh || row.keywordMatchType),
103594
103729
  str(row.keywordMatchTypeZh || row.keywordMatchType),
103595
103730
  str(row.campaignName),
103596
103731
  str(row.adGroupName),
@@ -103600,7 +103735,7 @@ function keywordRows(data) {
103600
103735
  rate(row.ctr),
103601
103736
  money(row.averageCpc),
103602
103737
  count(row.conversions),
103603
- num4(row.costPerConversion) == null && (num4(row.conversions) ?? 0) <= 0 ? "\u2014" : money(row.costPerConversion),
103738
+ num5(row.costPerConversion) == null && (num5(row.conversions) ?? 0) <= 0 ? "\u2014" : money(row.costPerConversion),
103604
103739
  rate(row.conversionRate)
103605
103740
  ]);
103606
103741
  }
@@ -103611,14 +103746,17 @@ function searchTermRows(data) {
103611
103746
  targetingStatus(row),
103612
103747
  str(row.campaignName),
103613
103748
  str(row.adGroupName),
103614
- str(row.keywordText || row.keyword),
103749
+ formatOkkiKeywordDisplay(
103750
+ row.keywordText || row.keyword,
103751
+ row.keywordMatchTypeZh || row.keywordMatchType || row.matchTypeZh || row.matchType
103752
+ ),
103615
103753
  count(row.impressions),
103616
103754
  count(row.clicks),
103617
103755
  rate(row.ctr),
103618
103756
  money(row.averageCpc),
103619
103757
  money(row.spend),
103620
103758
  count(row.conversions),
103621
- num4(row.costPerConversion) == null && (num4(row.conversions) ?? 0) <= 0 ? "\u2014" : money(row.costPerConversion),
103759
+ num5(row.costPerConversion) == null && (num5(row.conversions) ?? 0) <= 0 ? "\u2014" : money(row.costPerConversion),
103622
103760
  rate(row.conversionRate)
103623
103761
  ]);
103624
103762
  }
@@ -103637,7 +103775,7 @@ function deviceRows(data) {
103637
103775
  money(row.spend),
103638
103776
  count(pair.all),
103639
103777
  count(pair.conv),
103640
- num4(row.costPerConversion) == null && pair.conv <= 0 ? "\u2014" : money(row.costPerConversion),
103778
+ num5(row.costPerConversion) == null && pair.conv <= 0 ? "\u2014" : money(row.costPerConversion),
103641
103779
  rate(row.conversionRate)
103642
103780
  ];
103643
103781
  });
@@ -103645,8 +103783,8 @@ function deviceRows(data) {
103645
103783
  function geoRows(data) {
103646
103784
  return sortBySpendDesc(data.tables?.geo ?? []).map((row) => {
103647
103785
  const pair = conversionsPair(row);
103648
- const impressions = num4(row.impressions) ?? 0;
103649
- const interactions = num4(row.interactions) ?? 0;
103786
+ const impressions = num5(row.impressions) ?? 0;
103787
+ const interactions = okkiGeoInteractionCount(row);
103650
103788
  return [
103651
103789
  str(row.countryOrRegionZh || row.countryNameZh || row.countryOrRegion),
103652
103790
  str(row.campaignName),
@@ -103654,18 +103792,18 @@ function geoRows(data) {
103654
103792
  interactions > 0 ? count(interactions) : 0,
103655
103793
  interactionRateOf(row, impressions, interactions),
103656
103794
  money(row.spend),
103657
- dashOrMoney(num4(row.spend) ?? 0, interactions),
103795
+ dashOrMoney(num5(row.spend) ?? 0, interactions),
103658
103796
  count(row.clicks),
103659
103797
  rate(row.ctr),
103660
103798
  count(pair.all),
103661
103799
  count(pair.conv),
103662
- num4(row.costPerConversion) == null && pair.conv <= 0 ? "\u2014" : money(row.costPerConversion),
103800
+ num5(row.costPerConversion) == null && pair.conv <= 0 ? "\u2014" : money(row.costPerConversion),
103663
103801
  rate(row.conversionRate)
103664
103802
  ];
103665
103803
  });
103666
103804
  }
103667
103805
  function buildOkkiWeeklySheetDrafts(data) {
103668
- const meta = asRecord12(data.meta) ?? {};
103806
+ const meta = asRecord13(data.meta) ?? {};
103669
103807
  const period = formatOkkiZhDateRange(str(meta.startDate, ""), str(meta.endDate, ""));
103670
103808
  const campaigns = campaignRows(data);
103671
103809
  const drafts = {
@@ -103823,6 +103961,7 @@ var init_okki_render_excel = __esm({
103823
103961
  "src/commands/google-analysis/okki-render-excel.ts"() {
103824
103962
  "use strict";
103825
103963
  init_xlsx_style_engine();
103964
+ init_okki_display();
103826
103965
  init_okki_report_content();
103827
103966
  REVIEW_LABELS2 = {
103828
103967
  account: "\u8D26\u6237\u5C42\u7EA7",
@@ -103925,7 +104064,7 @@ function textLen2(value) {
103925
104064
  function asArray2(value) {
103926
104065
  return Array.isArray(value) ? value : [];
103927
104066
  }
103928
- function asRecord13(value) {
104067
+ function asRecord14(value) {
103929
104068
  if (value && typeof value === "object" && !Array.isArray(value)) {
103930
104069
  return value;
103931
104070
  }
@@ -103936,9 +104075,9 @@ function pushMissing2(missing, id, chapter, dimension, hint) {
103936
104075
  }
103937
104076
  function validateGooglePeriodReportContent(data) {
103938
104077
  const missing = [];
103939
- const meta = asRecord13(data.meta) ?? {};
103940
- const narrative = asRecord13(data.narrative) ?? {};
103941
- const sections = asRecord13(narrative.sections) ?? {};
104078
+ const meta = asRecord14(data.meta) ?? {};
104079
+ const narrative = asRecord14(data.narrative) ?? {};
104080
+ const sections = asRecord14(narrative.sections) ?? {};
103942
104081
  if (!textLen2(meta.accountId)) {
103943
104082
  pushMissing2(
103944
104083
  missing,
@@ -103958,7 +104097,7 @@ function validateGooglePeriodReportContent(data) {
103958
104097
  "\u22651 \u6BB5\u6267\u884C\u6458\u8981\uFF0C\u6982\u8FF0\u672C\u671F\u6D88\u8017/\u8F6C\u5316/CTR/CPA \u8D70\u52BF"
103959
104098
  );
103960
104099
  }
103961
- const kpis = asRecord13(data.kpis);
104100
+ const kpis = asRecord14(data.kpis);
103962
104101
  const hasCoreKpi = ["cost", "impressions", "clicks"].some(
103963
104102
  (key) => Number.isFinite(Number(kpis?.[key]))
103964
104103
  );
@@ -103974,7 +104113,7 @@ function validateGooglePeriodReportContent(data) {
103974
104113
  const sectionAnalysisCounts = {};
103975
104114
  const sectionSuggestionCounts = {};
103976
104115
  for (const key of SECTION_KEYS) {
103977
- const section = asRecord13(sections[key]);
104116
+ const section = asRecord14(sections[key]);
103978
104117
  const analysis = asArray2(section?.analysis).filter((s) => textLen2(s) > 0);
103979
104118
  const suggestions = asArray2(section?.suggestions).filter((s) => textLen2(s) > 0);
103980
104119
  sectionAnalysisCounts[key] = analysis.length;
@@ -104000,7 +104139,7 @@ function validateGooglePeriodReportContent(data) {
104000
104139
  }
104001
104140
  const recommendations = asArray2(narrative.recommendations).filter((r) => {
104002
104141
  if (typeof r === "string") return textLen2(r) > 0;
104003
- const rec = asRecord13(r);
104142
+ const rec = asRecord14(r);
104004
104143
  return textLen2(rec?.content) > 0;
104005
104144
  });
104006
104145
  if (recommendations.length < 3) {
@@ -104087,7 +104226,7 @@ async function readJsonFile3(filePath) {
104087
104226
  throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
104088
104227
  }
104089
104228
  }
104090
- function asRecord14(value) {
104229
+ function asRecord15(value) {
104091
104230
  if (value && typeof value === "object" && !Array.isArray(value)) {
104092
104231
  return value;
104093
104232
  }
@@ -104102,7 +104241,7 @@ function injectReportData2(html, payload) {
104102
104241
  }
104103
104242
  async function runGoogleAnalysisRender(opts) {
104104
104243
  const dataPath = path30.resolve(opts.dataFile);
104105
- let data = asRecord14(await readJsonFile3(dataPath));
104244
+ let data = asRecord15(await readJsonFile3(dataPath));
104106
104245
  if (opts.snapshotDir?.trim()) {
104107
104246
  data = await mergeGoogleAnalysisSnapshotIntoReport(data, path30.resolve(opts.snapshotDir));
104108
104247
  }
@@ -130793,7 +130932,105 @@ function buildDiagnosisScaffold(signals) {
130793
130932
  }
130794
130933
 
130795
130934
  // src/commands/website-diagnosis/lighthouse-with-fallback.ts
130935
+ init_dist();
130796
130936
  init_auth();
130937
+ var LIGHTHOUSE_MAX_ATTEMPTS = 3;
130938
+ var LIGHTHOUSE_RETRY_DELAYS_MS = [2e3, 4e3];
130939
+ function lighthouseHasScores2(lh) {
130940
+ if (!lh || typeof lh !== "object") return false;
130941
+ const d = lh.desktop?.score;
130942
+ const m = lh.mobile?.score;
130943
+ return typeof d === "number" && Number.isFinite(d) || typeof m === "number" && Number.isFinite(m);
130944
+ }
130945
+ function isLighthouseRetryableError(error) {
130946
+ const status = parseHttpStatusFromError(new Error(error));
130947
+ if (status === 401) return false;
130948
+ return true;
130949
+ }
130950
+ function defaultSleep(ms) {
130951
+ return new Promise((resolve25) => {
130952
+ setTimeout(resolve25, ms);
130953
+ });
130954
+ }
130955
+ async function retryLighthouseFetch(opts) {
130956
+ const maxAttempts = opts.maxAttempts ?? LIGHTHOUSE_MAX_ATTEMPTS;
130957
+ const delays = opts.delaysMs ?? LIGHTHOUSE_RETRY_DELAYS_MS;
130958
+ const sleep4 = opts.sleep ?? defaultSleep;
130959
+ let lastError = "TSO Lighthouse API \u5931\u8D25";
130960
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
130961
+ const result = await opts.fetchOnce();
130962
+ if (result.ok) return { ...result, attempts: attempt };
130963
+ lastError = result.error;
130964
+ const canRetry = isLighthouseRetryableError(result.error) && attempt < maxAttempts;
130965
+ if (!canRetry) return { ok: false, error: lastError, attempts: attempt };
130966
+ const delayMs = delays[attempt - 1] ?? delays[delays.length - 1] ?? 2e3;
130967
+ opts.onRetry?.({ attempt, delayMs, error: result.error });
130968
+ await sleep4(delayMs);
130969
+ }
130970
+ return { ok: false, error: lastError, attempts: maxAttempts };
130971
+ }
130972
+ async function tryTsoLighthouseApi(config, websiteUrl, verbose) {
130973
+ try {
130974
+ const lighthouse = await apiFetch2(
130975
+ `${config.apiBaseUrl}/api/WebsiteDiagnosisReports/performance?url=${encodeURIComponent(websiteUrl)}`,
130976
+ config,
130977
+ {},
130978
+ verbose
130979
+ );
130980
+ if (!lighthouseHasScores2(lighthouse)) {
130981
+ return { ok: false, error: "TSO Lighthouse API \u8FD4\u56DE\u7A7A\u5206\u6570" };
130982
+ }
130983
+ return { ok: true, lighthouse };
130984
+ } catch (err) {
130985
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
130986
+ }
130987
+ }
130988
+ async function resolveLighthouseWithFallback(opts) {
130989
+ const websiteUrl = opts.url;
130990
+ const api = await retryLighthouseFetch({
130991
+ fetchOnce: () => tryTsoLighthouseApi(opts.config, websiteUrl, opts.verbose),
130992
+ onRetry: ({ attempt, delayMs, error }) => {
130993
+ console.error(
130994
+ ` \u26A0\uFE0F Lighthouse \u7B2C ${attempt} \u6B21\u5931\u8D25\uFF0C${(delayMs / 1e3).toFixed(0)}s \u540E\u91CD\u8BD5\uFF1A${error}
130995
+ `
130996
+ );
130997
+ }
130998
+ });
130999
+ if (api.ok) {
131000
+ return {
131001
+ source: "api",
131002
+ lighthouse: api.lighthouse,
131003
+ attempts: api.attempts
131004
+ };
131005
+ }
131006
+ return {
131007
+ source: "none",
131008
+ lighthouse: {},
131009
+ lighthouseWarning: `Lighthouse API \u91CD\u8BD5 ${api.attempts} \u6B21\u4ECD\u5931\u8D25\uFF1A${api.error}`,
131010
+ attempts: api.attempts
131011
+ };
131012
+ }
131013
+ function attachLandingPageMeta(lighthouse, resolved) {
131014
+ const desktop = lighthouse.desktop ?? {};
131015
+ const mobile = lighthouse.mobile ?? {};
131016
+ return {
131017
+ desktop: {
131018
+ score: desktop.score ?? 0,
131019
+ firstContentfulPaint: desktop.firstContentfulPaint ?? 0,
131020
+ firstMeaningfulPaint: desktop.firstMeaningfulPaint ?? 0,
131021
+ speedIndex: desktop.speedIndex ?? 0
131022
+ },
131023
+ mobile: {
131024
+ score: mobile.score ?? 0,
131025
+ firstContentfulPaint: mobile.firstContentfulPaint ?? 0,
131026
+ firstMeaningfulPaint: mobile.firstMeaningfulPaint ?? 0,
131027
+ speedIndex: mobile.speedIndex ?? 0
131028
+ },
131029
+ source: resolved.source,
131030
+ ...resolved.lighthouseWarning ? { lighthouseWarning: resolved.lighthouseWarning } : {},
131031
+ lighthouseOk: resolved.source === "api"
131032
+ };
131033
+ }
130797
131034
 
130798
131035
  // src/commands/website-diagnosis/shared.ts
130799
131036
  init_dist();
@@ -130858,134 +131095,6 @@ async function fetchWebsiteHtml(config, websiteUrl, verbose) {
130858
131095
  return picked.value;
130859
131096
  }
130860
131097
 
130861
- // src/commands/website-diagnosis/lighthouse-with-fallback.ts
130862
- function lighthouseHasScores2(lh) {
130863
- if (!lh || typeof lh !== "object") return false;
130864
- const d = lh.desktop?.score;
130865
- const m = lh.mobile?.score;
130866
- return typeof d === "number" && Number.isFinite(d) || typeof m === "number" && Number.isFinite(m);
130867
- }
130868
- function toSimple(signals, htmlAsset) {
130869
- const fetchMs = typeof htmlAsset?.downloadTime === "number" && Number.isFinite(htmlAsset.downloadTime) ? htmlAsset.downloadTime : null;
130870
- const contentLength = typeof htmlAsset?.contentLength === "number" && Number.isFinite(htmlAsset.contentLength) ? htmlAsset.contentLength : signals.htmlChars || null;
130871
- return {
130872
- fetchMs,
130873
- contentLength,
130874
- finalUrl: htmlAsset?.finalUrl ?? signals.url,
130875
- https: signals.https,
130876
- title: signals.title,
130877
- hasViewportMeta: signals.hasViewportMeta,
130878
- hasNav: signals.hasNav,
130879
- hasForm: signals.hasForm,
130880
- hasMailto: signals.hasMailto,
130881
- hasTel: signals.hasTel,
130882
- analyticsHints: [...signals.analyticsHints],
130883
- socialLinkCount: signals.socialLinks.length,
130884
- ctaHintCount: signals.ctaHints.length,
130885
- note: "TSO Lighthouse API \u4E0D\u53EF\u7528\uFF0C\u5DF2\u964D\u7EA7\u4E3A CLI \u7B80\u6613\u8BCA\u65AD\uFF08HTML \u7ED3\u6784\u4FE1\u53F7 + \u4E0B\u8F7D\u8017\u65F6\uFF09\u3002\u4E0D\u542B\u5B98\u65B9 Lighthouse score/FCP\uFF1B\u7981\u6B62\u628A fetchMs \u5199\u6210 FCP \u6216\u6027\u80FD\u6307\u6570\u3002"
130886
- };
130887
- }
130888
- async function tryTsoLighthouseApi(config, websiteUrl, verbose) {
130889
- try {
130890
- const lighthouse = await apiFetch2(
130891
- `${config.apiBaseUrl}/api/WebsiteDiagnosisReports/performance?url=${encodeURIComponent(websiteUrl)}`,
130892
- config,
130893
- {},
130894
- verbose
130895
- );
130896
- if (!lighthouseHasScores2(lighthouse)) {
130897
- return { ok: false, error: "TSO Lighthouse API \u8FD4\u56DE\u7A7A\u5206\u6570" };
130898
- }
130899
- return { ok: true, lighthouse };
130900
- } catch (err) {
130901
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
130902
- }
130903
- }
130904
- async function resolveLighthouseWithFallback(opts) {
130905
- const websiteUrl = opts.url;
130906
- const api = await tryTsoLighthouseApi(opts.config, websiteUrl, opts.verbose);
130907
- if (api.ok) {
130908
- let signals2;
130909
- let htmlAsset2 = opts.htmlAsset;
130910
- const htmlContent2 = opts.htmlContent ?? htmlAsset2?.content;
130911
- if (htmlContent2) {
130912
- signals2 = extractWebsiteDiagnosisSignals({
130913
- url: websiteUrl,
130914
- html: String(htmlContent2),
130915
- lighthouse: api.lighthouse
130916
- });
130917
- }
130918
- return {
130919
- source: "api",
130920
- lighthouse: api.lighthouse,
130921
- signals: signals2,
130922
- htmlAsset: htmlAsset2
130923
- };
130924
- }
130925
- const apiError = api.error;
130926
- if (opts.skipSimpleHtml) {
130927
- return {
130928
- source: "none",
130929
- lighthouse: {},
130930
- lighthouseWarning: `Lighthouse API \u5931\u8D25\u4E14\u5DF2\u8DF3\u8FC7\u7B80\u6613\u8BCA\u65AD: ${apiError}`
130931
- };
130932
- }
130933
- let htmlAsset = opts.htmlAsset;
130934
- let htmlContent = opts.htmlContent ?? (htmlAsset?.content ? String(htmlAsset.content) : "");
130935
- let htmlError;
130936
- if (!htmlContent) {
130937
- try {
130938
- htmlAsset = await fetchWebsiteHtml(opts.config, websiteUrl, opts.verbose);
130939
- htmlContent = String(htmlAsset.content ?? "");
130940
- } catch (err) {
130941
- htmlError = err instanceof Error ? err.message : String(err);
130942
- return {
130943
- source: "none",
130944
- lighthouse: {},
130945
- lighthouseWarning: `Lighthouse API \u5931\u8D25\uFF08${apiError}\uFF09\uFF1B\u7B80\u6613\u8BCA\u65AD HTML \u4EA6\u5931\u8D25\uFF08${htmlError}\uFF09`
130946
- };
130947
- }
130948
- }
130949
- const signals = extractWebsiteDiagnosisSignals({
130950
- url: websiteUrl,
130951
- html: htmlContent,
130952
- lighthouse: {},
130953
- lighthouseWarning: `Lighthouse \u6027\u80FD\u6570\u636E\u83B7\u53D6\u5931\u8D25: ${apiError}`,
130954
- htmlError
130955
- });
130956
- const simple = toSimple(signals, htmlAsset);
130957
- return {
130958
- source: "simple",
130959
- lighthouse: {},
130960
- lighthouseWarning: `Lighthouse API \u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u7B80\u6613\u8BCA\u65AD: ${apiError}`,
130961
- simple,
130962
- signals,
130963
- htmlAsset
130964
- };
130965
- }
130966
- function attachLandingPageMeta(lighthouse, resolved) {
130967
- const desktop = lighthouse.desktop ?? {};
130968
- const mobile = lighthouse.mobile ?? {};
130969
- return {
130970
- desktop: {
130971
- score: desktop.score ?? 0,
130972
- firstContentfulPaint: desktop.firstContentfulPaint ?? 0,
130973
- firstMeaningfulPaint: desktop.firstMeaningfulPaint ?? 0,
130974
- speedIndex: desktop.speedIndex ?? 0
130975
- },
130976
- mobile: {
130977
- score: mobile.score ?? 0,
130978
- firstContentfulPaint: mobile.firstContentfulPaint ?? 0,
130979
- firstMeaningfulPaint: mobile.firstMeaningfulPaint ?? 0,
130980
- speedIndex: mobile.speedIndex ?? 0
130981
- },
130982
- source: resolved.source,
130983
- ...resolved.lighthouseWarning ? { lighthouseWarning: resolved.lighthouseWarning } : {},
130984
- ...resolved.simple ? { simpleDiagnosis: resolved.simple } : {},
130985
- lighthouseOk: resolved.source === "api"
130986
- };
130987
- }
130988
-
130989
131098
  // src/commands/website-diagnosis/run.ts
130990
131099
  async function runWebsiteDiagnosisPerformance(opts) {
130991
131100
  const config = loadConfig(opts.token);
@@ -131004,9 +131113,8 @@ async function runWebsiteDiagnosisPerformance(opts) {
131004
131113
  source: resolved.source,
131005
131114
  lighthouse: resolved.lighthouse,
131006
131115
  ...resolved.lighthouseWarning ? { warning: resolved.lighthouseWarning } : {},
131007
- ...resolved.simple ? { simpleDiagnosis: resolved.simple } : {},
131008
- ...resolved.signals ? { signals: resolved.signals } : {},
131009
- agentHint: resolved.source === "api" ? "TSO Lighthouse API \u6210\u529F\u3002" : resolved.source === "simple" ? "\u5DF2\u964D\u7EA7 CLI \u7B80\u6613\u8BCA\u65AD\uFF1A\u7528 simpleDiagnosis/signals \u5199\u7ED3\u8BBA\uFF0C\u7981\u6B62\u7F16\u9020 score/FCP\u3002" : "Lighthouse \u4E0E\u7B80\u6613\u8BCA\u65AD\u5747\u5931\u8D25\uFF0C\u6027\u80FD\u76F8\u5173\u7ED3\u8BBA\u987B\u6807\u660E\u6570\u636E\u4E0D\u53EF\u7528\u3002"
131116
+ ...resolved.attempts != null ? { attempts: resolved.attempts } : {},
131117
+ agentHint: resolved.source === "api" ? "TSO Lighthouse API \u6210\u529F\u3002" : "Lighthouse API \u5931\u8D25\uFF1A\u6027\u80FD\u76F8\u5173\u7ED3\u8BBA\u987B\u6807\u660E\u6570\u636E\u4E0D\u53EF\u7528\uFF0C\u7981\u6B62\u7F16\u9020 score/FCP\u3002"
131010
131118
  };
131011
131119
  if (await emitCliJsonOrSnapshot(
131012
131120
  { jsonOut: opts.jsonOut },
@@ -131085,34 +131193,17 @@ async function runWebsiteDiagnosisCollect(opts) {
131085
131193
  htmlError = err instanceof Error ? err.message : String(err);
131086
131194
  }
131087
131195
  }
131088
- const htmlForSignals = htmlFull || htmlPreview || "";
131089
131196
  const resolved = await resolveLighthouseWithFallback({
131090
131197
  config,
131091
131198
  url: websiteUrl,
131092
- verbose: opts.verbose,
131093
- htmlContent: htmlForSignals || void 0,
131094
- htmlAsset,
131095
- skipSimpleHtml: opts.skipHtml && !htmlForSignals
131199
+ verbose: opts.verbose
131096
131200
  });
131097
- if (!htmlAsset && resolved.htmlAsset) {
131098
- htmlAsset = resolved.htmlAsset;
131099
- htmlMeta = {
131100
- originUrl: htmlAsset.originUrl,
131101
- finalUrl: htmlAsset.finalUrl,
131102
- mimeType: htmlAsset.mimeType,
131103
- contentLength: htmlAsset.contentLength,
131104
- downloadTime: htmlAsset.downloadTime
131105
- };
131106
- const content = String(htmlAsset.content ?? "");
131107
- htmlPreview = content.slice(0, HTML_PREVIEW_CHARS);
131108
- if (opts.includeHtml) htmlFull = content;
131109
- }
131110
- const htmlBody = htmlFull || htmlPreview || String(resolved.htmlAsset?.content ?? "");
131111
- const signals = resolved.signals ?? extractWebsiteDiagnosisSignals({
131201
+ const htmlBody = String(htmlAsset?.content ?? htmlFull ?? htmlPreview ?? "");
131202
+ const signals = extractWebsiteDiagnosisSignals({
131112
131203
  url: websiteUrl,
131113
131204
  collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
131114
131205
  html: htmlBody,
131115
- htmlTruncated: !htmlFull && Boolean(htmlPreview || htmlBody),
131206
+ htmlTruncated: Boolean(htmlAsset?.content) ? false : !htmlFull && Boolean(htmlPreview || htmlBody),
131116
131207
  lighthouse: resolved.lighthouse,
131117
131208
  lighthouseWarning: resolved.lighthouseWarning,
131118
131209
  htmlError,
@@ -131124,14 +131215,14 @@ async function runWebsiteDiagnosisCollect(opts) {
131124
131215
  lighthouseSource: resolved.source,
131125
131216
  lighthouse: resolved.lighthouse,
131126
131217
  ...resolved.lighthouseWarning ? { lighthouseWarning: resolved.lighthouseWarning } : {},
131127
- ...resolved.simple ? { simpleDiagnosis: resolved.simple } : {},
131218
+ ...resolved.attempts != null ? { lighthouseAttempts: resolved.attempts } : {},
131128
131219
  html: htmlMeta,
131129
131220
  htmlPreview,
131130
131221
  ...htmlFull ? { htmlContent: htmlFull } : {},
131131
131222
  ...htmlError ? { htmlError } : {},
131132
131223
  signals,
131133
131224
  dataAvailability: signals.dataAvailability,
131134
- agentHint: `lighthouseSource=${resolved.source}\u3002` + (resolved.source === "simple" ? "\u5DF2\u964D\u7EA7\u7B80\u6613\u8BCA\u65AD\uFF1Am2i1/m5i1 \u6807 Absent\uFF1B\u7528 signals/simpleDiagnosis\uFF0C\u7981\u6B62\u7F16\u9020 Lighthouse \u5206\u3002" : resolved.source === "none" ? "Lighthouse \u4E0E\u7B80\u6613\u8BCA\u65AD\u5747\u5931\u8D25\uFF1A\u6027\u80FD\u9879\u6807\u4E0D\u53EF\u7528\u3002" : "TSO Lighthouse \u53EF\u7528\u3002") + ` 1) \u8BFB signals + dataAvailability\uFF1B2) website-diagnosis prepare\uFF1B3) \u8865\u5168 diagnosis\uFF1B4) render\u3002 ${signals.dataAvailability.agentHint}`
131225
+ agentHint: `lighthouseSource=${resolved.source}\u3002` + (resolved.source === "none" ? "Lighthouse API \u5931\u8D25\uFF1Am2i1/m5i1 \u987B status=Absent\u3001score=0\uFF0C\u7981\u6B62\u7F16\u9020\u6027\u80FD\u5206\u3002" : "TSO Lighthouse \u53EF\u7528\u3002") + ` \u8868\u5355/\u5BFC\u822A\u4EE5 signals \u4E3A\u51C6\uFF08\u5DF2\u6309\u5B8C\u6574 HTML \u62BD\u53D6\uFF09\uFF1B\u7981\u6B62\u7528 htmlPreview \u5224\u65AD\u6709\u65E0\u8868\u5355\u3002 1) \u8BFB signals + dataAvailability\uFF1B2) website-diagnosis prepare\uFF1B3) \u8865\u5168 diagnosis\uFF1B4) render\u3002 ${signals.dataAvailability.agentHint}`
131135
131226
  };
131136
131227
  if (await emitCliJsonOrSnapshot(
131137
131228
  { jsonOut: opts.jsonOut },
@@ -131343,7 +131434,7 @@ function registerWebsiteDiagnosisCommands(program2) {
131343
131434
  "\u7F51\u7AD9\u8BCA\u65AD\uFF1ALighthouse \u6027\u80FD\u3001HTML \u91C7\u96C6\u3001ARIT \u5386\u53F2\u5F97\u5206\uFF1B\u9ED8\u8BA4\u4EA4\u4ED8 HTML \u62A5\u544A\uFF08render \u5B50\u547D\u4EE4\uFF09"
131344
131435
  );
131345
131436
  root.command("performance").description(
131346
- "\u62C9\u53D6 Lighthouse\uFF1A\u5148 TSO API\uFF0C\u5931\u8D25\u5219\u964D\u7EA7 CLI \u7B80\u6613\u8BCA\u65AD\uFF08HTML signals\uFF0C\u4E0D\u7F16\u9020 score/FCP\uFF09"
131437
+ "\u62C9\u53D6 Lighthouse\uFF1A\u8BF7\u6C42 TSO performance API\uFF08\u5931\u8D25\u81EA\u52A8\u91CD\u8BD5\uFF0C\u4E0D\u7F16\u9020 score/FCP\uFF09"
131347
131438
  ).requiredOption("--url <url>", "\u7F51\u7AD9 URL\uFF08\u53EF\u7701\u7565 https://\uFF0C\u81EA\u52A8\u8865\u5168\uFF09").option("--token <token>", "JWT\uFF08\u9ED8\u8BA4\u8BFB config / \u73AF\u5883\u53D8\u91CF\uFF09").option("--verbose", "\u6253\u5370\u8BF7\u6C42\u8BE6\u60C5").option("--json-out <dir>", "\u843D\u76D8 cli-manifest + JSON").action(async (opts) => {
131348
131439
  await runWebsiteDiagnosisPerformance({
131349
131440
  url: opts.url,
@@ -133149,7 +133240,7 @@ async function runGoogleAdsDiagnosisCollect(opts) {
133149
133240
  let landingPageResolve;
133150
133241
  if (!opts.skipLandingPage && websiteUrl) {
133151
133242
  console.error(`
133152
- \u{1F4E5} \u7740\u9646\u9875\uFF1A\u5148 TSO Lighthouse\uFF0C\u5931\u8D25\u5219 CLI \u7B80\u6613\u8BCA\u65AD\uFF1A${websiteUrl}
133243
+ \u{1F4E5} \u7740\u9646\u9875\uFF1ATSO Lighthouse\uFF08\u5931\u8D25\u81EA\u52A8\u91CD\u8BD5\uFF09\uFF1A${websiteUrl}
133153
133244
  `);
133154
133245
  const config = loadConfig(opts.token);
133155
133246
  landingPageResolve = await resolveLighthouseWithFallback({
@@ -133159,12 +133250,8 @@ async function runGoogleAdsDiagnosisCollect(opts) {
133159
133250
  });
133160
133251
  if (landingPageResolve.source === "api") {
133161
133252
  console.error(" \u2713 TSO Lighthouse API \u6210\u529F\n");
133162
- } else if (landingPageResolve.source === "simple") {
133163
- console.error(` \u26A0\uFE0F ${landingPageResolve.lighthouseWarning ?? "\u5DF2\u964D\u7EA7\u7B80\u6613\u8BCA\u65AD"}
133164
- `);
133165
- degraded = true;
133166
133253
  } else {
133167
- console.error(` \u26A0\uFE0F ${landingPageResolve.lighthouseWarning ?? "\u7740\u9646\u9875\u6570\u636E\u4E0D\u53EF\u7528"}
133254
+ console.error(` \u26A0\uFE0F ${landingPageResolve.lighthouseWarning ?? "\u7740\u9646\u9875\u6D4B\u901F\u4E0D\u53EF\u7528"}
133168
133255
  `);
133169
133256
  degraded = true;
133170
133257
  }
@@ -133452,7 +133539,7 @@ function restoreLandingPageSpeedFromCollect(finalData, collectReportData) {
133452
133539
  ...finalBlock ?? {},
133453
133540
  desktop: needsDesktop ? collectBlock.desktop : finalBlock?.desktop,
133454
133541
  mobile: needsMobile ? collectBlock.mobile : finalBlock?.mobile,
133455
- // 保留 collect 的降级元数据(source / simpleDiagnosis),避免 Agent 覆盖后丢失
133542
+ // 保留 collect 的测速元数据(source / lighthouseWarning);旧 JSON 的 simpleDiagnosis 仅透传
133456
133543
  ...collectBlock.source != null && finalBlock?.source == null ? { source: collectBlock.source } : {},
133457
133544
  ...collectBlock.simpleDiagnosis != null && finalBlock?.simpleDiagnosis == null ? { simpleDiagnosis: collectBlock.simpleDiagnosis } : {},
133458
133545
  ...collectBlock.lighthouseWarning != null && finalBlock?.lighthouseWarning == null ? { lighthouseWarning: collectBlock.lighthouseWarning } : {},
@@ -133547,7 +133634,7 @@ var NARRATIVE_CONSISTENCY_RULES = [
133547
133634
  id: "landing-page-missing-as-core-issue",
133548
133635
  textPattern: /(着陆页|落地页).{0,24}(性能|测速|Lighthouse|打开速度|加载).{0,24}(缺失|未获取|无法评估|数据缺失)/,
133549
133636
  isContradicted: () => true,
133550
- hint: "\u7740\u9646\u9875\u6D4B\u901F\u672A\u83B7\u53D6\u662F\u91C7\u96C6\u964D\u7EA7/\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=simple/none \u4E0E simpleDiagnosis \u4FE1\u53F7\u5373\u53EF\u3002"
133637
+ 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"
133551
133638
  }
133552
133639
  ];
133553
133640
  function findIdenticalTexts(texts, minCount) {
@@ -133592,12 +133679,12 @@ function getLandingPageTopicErrors(data) {
133592
133679
  const analysis = String(lp.analysis ?? "");
133593
133680
  if (!analysis.trim()) return [];
133594
133681
  const talksKeywords = /关键词|搜索词|匹配类型|广泛匹配/.test(analysis);
133595
- const talksLanding = /着陆页|落地页|Lighthouse|加载|打开速度|FCP|性能指数|HTTPS|表单|测速|简易诊断|fetchMs|下载耗时/.test(
133682
+ const talksLanding = /着陆页|落地页|Lighthouse|加载|打开速度|FCP|性能指数|HTTPS|表单|测速|未测到|下载耗时/.test(
133596
133683
  analysis
133597
133684
  );
133598
133685
  if (talksKeywords && !talksLanding) {
133599
133686
  return [
133600
- "landingPageAnalysis.analysis \u5199\u6210\u4E86\u5173\u952E\u8BCD/\u641C\u7D22\u8BCD\u6D1E\u5BDF\uFF0C\u4E0E \xA707 \u7740\u9646\u9875\u6A21\u5757\u4E0D\u7B26\u3002\u8BF7\u53EA\u4F9D\u636E landingPageAnalysis.source / desktop / mobile / simpleDiagnosis \u5199\u6D4B\u901F\u6216\u7B80\u6613\u8BCA\u65AD\u7ED3\u8BBA\uFF1B\u5173\u952E\u8BCD\u5206\u6790\u653E\u5230 \xA708\u3002"
133687
+ "landingPageAnalysis.analysis \u5199\u6210\u4E86\u5173\u952E\u8BCD/\u641C\u7D22\u8BCD\u6D1E\u5BDF\uFF0C\u4E0E \xA707 \u7740\u9646\u9875\u6A21\u5757\u4E0D\u7B26\u3002\u8BF7\u53EA\u4F9D\u636E landingPageAnalysis.source / desktop / mobile \u5199\u6D4B\u901F\u7ED3\u8BBA\uFF08source=none \u5219\u5199\u672A\u6D4B\u5230\uFF09\uFF1B\u5173\u952E\u8BCD\u5206\u6790\u653E\u5230 \xA708\u3002"
133601
133688
  ];
133602
133689
  }
133603
133690
  return [];
@@ -133765,7 +133852,7 @@ function registerGoogleAdsDiagnosisCommands(program2) {
133765
133852
  ).requiredOption("-a, --account <id>", "Google mediaCustomerId").requiredOption("--start <date>", "\u8BCA\u65AD\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").requiredOption("--end <date>", "\u8BCA\u65AD\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--company-name <name>", "\u8D26\u6237\u540D\u79F0\uFF08\u9ED8\u8BA4\u4ECE list-accounts \u5FEB\u7167\u8BFB\u53D6\uFF09").option("--website <url>", "\u5B98\u7F51 URL\uFF08\u9ED8\u8BA4\u4ECE ads \u5FEB\u7167 finalurl \u8BFB\u53D6\uFF09").option(
133766
133853
  "--json-out <dir>",
133767
133854
  "\u843D\u76D8\u76EE\u5F55\uFF08google-analysis \u5FEB\u7167 + google-ads-diagnosis-collect.json\uFF09"
133768
- ).option("--skip-landing-page", "\u4E0D\u62C9\u53D6\u7740\u9646\u9875\uFF08\u8DF3\u8FC7 TSO Lighthouse \u4E0E CLI \u7B80\u6613\u8BCA\u65AD\u964D\u7EA7\uFF09").option("--no-fetch-previous-period", "\u4E0D\u62C9\u4E0A\u4E00\u5468\u671F campaigns/geographic/keywords \u5BF9\u6BD4\u6570\u636E").option("--token <token>", "JWT").option("--verbose", "\u6253\u5370\u8BF7\u6C42\u8BE6\u60C5").action(
133855
+ ).option("--skip-landing-page", "\u4E0D\u62C9\u53D6\u7740\u9646\u9875\uFF08\u8DF3\u8FC7 TSO Lighthouse\uFF09").option("--no-fetch-previous-period", "\u4E0D\u62C9\u4E0A\u4E00\u5468\u671F campaigns/geographic/keywords \u5BF9\u6BD4\u6570\u636E").option("--token <token>", "JWT").option("--verbose", "\u6253\u5370\u8BF7\u6C42\u8BE6\u60C5").action(
133769
133856
  async (opts) => {
133770
133857
  if (!opts.jsonOut?.trim()) {
133771
133858
  console.error("\n\u274C collect \u987B\u6307\u5B9A --json-out <\u76EE\u5F55>\u3002\n");
@@ -133896,13 +133983,13 @@ import * as fs28 from "fs/promises";
133896
133983
  import * as path35 from "path";
133897
133984
 
133898
133985
  // src/commands/facebook-analysis/report-metrics.ts
133899
- function asRecord15(value) {
133986
+ function asRecord16(value) {
133900
133987
  if (value && typeof value === "object" && !Array.isArray(value)) {
133901
133988
  return value;
133902
133989
  }
133903
133990
  return null;
133904
133991
  }
133905
- function num5(value) {
133992
+ function num6(value) {
133906
133993
  const n = Number(value);
133907
133994
  return Number.isFinite(n) ? n : void 0;
133908
133995
  }
@@ -133927,14 +134014,14 @@ function normalizeInsightDate(value) {
133927
134014
  }
133928
134015
  function pickMetricFields(row) {
133929
134016
  return {
133930
- reach: num5(row.reach),
133931
- impressions: num5(row.impressions),
133932
- frequency: num5(row.frequency),
134017
+ reach: num6(row.reach),
134018
+ impressions: num6(row.impressions),
134019
+ frequency: num6(row.frequency),
133933
134020
  attributionSetting: str2(row.attributionSetting) || void 0,
133934
134021
  resultType: str2(row.resultType) || void 0,
133935
- results: num5(row.results),
133936
- spend: num5(row.spend),
133937
- costPerResult: num5(row.costPerResult)
134022
+ results: num6(row.results),
134023
+ spend: num6(row.spend),
134024
+ costPerResult: num6(row.costPerResult)
133938
134025
  };
133939
134026
  }
133940
134027
  function audienceLabel(age, gender) {
@@ -134033,13 +134120,13 @@ function localizeFacebookReportLabels(payload) {
134033
134120
  for (const rows of Object.values(tables)) {
134034
134121
  if (!Array.isArray(rows)) continue;
134035
134122
  for (const row of rows) {
134036
- localizeMetricLabels(asRecord15(row));
134123
+ localizeMetricLabels(asRecord16(row));
134037
134124
  }
134038
134125
  }
134039
134126
  return payload;
134040
134127
  }
134041
134128
  function hasResults(row) {
134042
- const n = num5(row.results);
134129
+ const n = num6(row.results);
134043
134130
  return n != null && n > 0;
134044
134131
  }
134045
134132
 
@@ -134066,7 +134153,7 @@ async function readManifest3(snapshotDir) {
134066
134153
  async function loadSectionFile3(snapshotDir, file) {
134067
134154
  try {
134068
134155
  const raw = await fs28.readFile(path35.join(snapshotDir, file), "utf8");
134069
- return asRecord15(JSON.parse(raw));
134156
+ return asRecord16(JSON.parse(raw));
134070
134157
  } catch {
134071
134158
  return null;
134072
134159
  }
@@ -134122,7 +134209,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
134122
134209
  if (data) sectionMap.set(art.section, data);
134123
134210
  }
134124
134211
  const overview = sectionMap.get("overview");
134125
- const current = asRecord15(overview?.currentPeriod);
134212
+ const current = asRecord16(overview?.currentPeriod);
134126
134213
  const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
134127
134214
  const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
134128
134215
  const kpis = { ...payload.kpis ?? {} };
@@ -134259,7 +134346,7 @@ function writeSimpleXlsx(filePath, sheets, opts = {}) {
134259
134346
  }
134260
134347
 
134261
134348
  // src/commands/facebook-analysis/render-excel.ts
134262
- function asRecord16(value) {
134349
+ function asRecord17(value) {
134263
134350
  if (value && typeof value === "object" && !Array.isArray(value)) {
134264
134351
  return value;
134265
134352
  }
@@ -134285,30 +134372,30 @@ function metricHeaders(currency, firstCol) {
134285
134372
  ];
134286
134373
  }
134287
134374
  function moneyText(value) {
134288
- const n = num5(value);
134375
+ const n = num6(value);
134289
134376
  if (n == null) return "\u2014";
134290
134377
  return `$${n.toFixed(2)}`;
134291
134378
  }
134292
134379
  function metricCells(row) {
134293
- const results = num5(row.results);
134380
+ const results = num6(row.results);
134294
134381
  const showResults = hasResults({ results });
134295
134382
  return [
134296
- num5(row.reach) ?? "",
134297
- num5(row.impressions) ?? "",
134298
- num5(row.frequency) ?? "",
134383
+ num6(row.reach) ?? "",
134384
+ num6(row.impressions) ?? "",
134385
+ num6(row.frequency) ?? "",
134299
134386
  displayAttribution(row.attributionSetting),
134300
134387
  showResults ? displayResultType(row.resultType) : "",
134301
134388
  showResults ? results ?? "" : "",
134302
- num5(row.spend) ?? "",
134303
- showResults ? num5(row.costPerResult) ?? "" : ""
134389
+ num6(row.spend) ?? "",
134390
+ showResults ? num6(row.costPerResult) ?? "" : ""
134304
134391
  ];
134305
134392
  }
134306
134393
  function bullets(value) {
134307
134394
  return asArray3(value).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
134308
134395
  }
134309
134396
  function insightLines(section) {
134310
- const rec = asRecord16(section);
134311
- const insight = asRecord16(rec?.insight) ?? rec;
134397
+ const rec = asRecord17(section);
134398
+ const insight = asRecord17(rec?.insight) ?? rec;
134312
134399
  return [...bullets(insight?.analysis), ...bullets(insight?.advice)];
134313
134400
  }
134314
134401
  function numberedSection(index, title, lines) {
@@ -134317,19 +134404,19 @@ function numberedSection(index, title, lines) {
134317
134404
  ${body}`;
134318
134405
  }
134319
134406
  function buildSummaryNarrative(data) {
134320
- const kpis = asRecord16(data.kpis) ?? {};
134321
- const meta = asRecord16(data.meta) ?? {};
134322
- const sections = asRecord16(data.sections) ?? {};
134407
+ const kpis = asRecord17(data.kpis) ?? {};
134408
+ const meta = asRecord17(data.meta) ?? {};
134409
+ const sections = asRecord17(data.sections) ?? {};
134323
134410
  const period = str2(meta.periodLabel) || `${str2(meta.startDate)} ~ ${str2(meta.endDate)}`.trim();
134324
134411
  const account = str2(meta.accountName) || "Facebook \u5E7F\u544A\u8D26\u6237";
134325
134412
  const campaign = str2(meta.campaignName);
134326
134413
  const resultLabel = displayResultType(kpis.resultType ?? meta.resultType) || "\u6210\u6548";
134327
134414
  const resultNoun = /潜在客户|lead/i.test(resultLabel) ? "\u4E2A\u6F5C\u5728\u5BA2\u6237\u8868\u5355" : `\u4E2A${resultLabel}`;
134328
- const firstQuestion = asRecord16(asArray3(data.fourQuestions)[0]);
134415
+ const firstQuestion = asRecord17(asArray3(data.fourQuestions)[0]);
134329
134416
  const extra = str2(firstQuestion?.verdict);
134330
134417
  const spendLine = [
134331
134418
  campaign ? `\uFF08\u5E7F\u544A\u7CFB\u5217\uFF1A${campaign}\uFF09` : "",
134332
- `\u5171\u6D88\u8017 ${moneyText(kpis.spend)}\uFF0C\u83B7\u53D6 ${num5(kpis.results) ?? "\u2014"} ${resultNoun}\uFF0C\u5E73\u5747\u5355\u6B21\u83B7\u5BA2\u6210\u672C ${moneyText(kpis.costPerResult)}\u3002`,
134419
+ `\u5171\u6D88\u8017 ${moneyText(kpis.spend)}\uFF0C\u83B7\u53D6 ${num6(kpis.results) ?? "\u2014"} ${resultNoun}\uFF0C\u5E73\u5747\u5355\u6B21\u83B7\u5BA2\u6210\u672C ${moneyText(kpis.costPerResult)}\u3002`,
134333
134420
  extra
134334
134421
  ].filter(Boolean).join("");
134335
134422
  const partOne = [`\u4E00\u3001${account}${period ? ` ${period}` : ""} Facebook \u5E7F\u544A\u8D26\u6237`, spendLine].join(
@@ -134345,7 +134432,7 @@ function buildSummaryNarrative(data) {
134345
134432
  numberedSection(3, "\u5E7F\u544A\u9891\u6B21\u4E0E\u65E5\u8D8B\u52BF", insightLines(sections.daily))
134346
134433
  ].join("\n");
134347
134434
  const recBlocks = asArray3(data.recommendations).map((raw, i) => {
134348
- const rec = asRecord16(raw);
134435
+ const rec = asRecord17(raw);
134349
134436
  if (!rec) return "";
134350
134437
  const title = [str2(rec.title), str2(rec.tag)].filter(Boolean).join(" \xB7 ");
134351
134438
  return numberedSection(i + 1, title || "\u4F18\u5316\u5EFA\u8BAE", bullets(rec.items));
@@ -134356,9 +134443,9 @@ function buildSummaryNarrative(data) {
134356
134443
  return [partOne, partTwo, partThree];
134357
134444
  }
134358
134445
  function buildMetaPeriodExcelSheets(data) {
134359
- const meta = asRecord16(data.meta) ?? {};
134360
- const kpis = asRecord16(data.kpis) ?? {};
134361
- const tables = asRecord16(data.tables) ?? {};
134446
+ const meta = asRecord17(data.meta) ?? {};
134447
+ const kpis = asRecord17(data.kpis) ?? {};
134448
+ const tables = asRecord17(data.tables) ?? {};
134362
134449
  const currency = str2(meta.currency) || "USD";
134363
134450
  const resultType = displayResultType(kpis.resultType ?? meta.resultType);
134364
134451
  const attribution = displayAttribution(kpis.attributionSetting ?? meta.attributionSetting);
@@ -134377,19 +134464,19 @@ function buildMetaPeriodExcelSheets(data) {
134377
134464
  ];
134378
134465
  const dailyRows = [metricHeaders(currency, "\u5355\u65E5")];
134379
134466
  for (const raw of asArray3(tables.daily)) {
134380
- const row = asRecord16(raw);
134467
+ const row = asRecord17(raw);
134381
134468
  if (!row) continue;
134382
134469
  dailyRows.push([normalizeInsightDate(row.date), ...metricCells(row)]);
134383
134470
  }
134384
134471
  const countryRows = [metricHeaders(currency, "\u56FD\u5BB6/\u5730\u533A")];
134385
134472
  for (const raw of asArray3(tables.countries)) {
134386
- const row = asRecord16(raw);
134473
+ const row = asRecord17(raw);
134387
134474
  if (!row) continue;
134388
134475
  countryRows.push([str2(row.countryOrRegion), ...metricCells(row)]);
134389
134476
  }
134390
134477
  const campaignRows3 = [metricHeaders(currency, "\u5E7F\u544A\u7CFB\u5217\u540D\u79F0")];
134391
134478
  for (const raw of asArray3(tables.campaigns)) {
134392
- const row = asRecord16(raw);
134479
+ const row = asRecord17(raw);
134393
134480
  if (!row) continue;
134394
134481
  campaignRows3.push([str2(row.campaignName), ...metricCells(row)]);
134395
134482
  }
@@ -134408,7 +134495,7 @@ function buildMetaPeriodExcelSheets(data) {
134408
134495
  ]
134409
134496
  ];
134410
134497
  for (const raw of asArray3(tables.audiences)) {
134411
- const row = asRecord16(raw);
134498
+ const row = asRecord17(raw);
134412
134499
  if (!row) continue;
134413
134500
  audienceRows.push([str2(row.age), str2(row.gender), ...metricCells(row)]);
134414
134501
  }
@@ -134440,7 +134527,7 @@ function isFiniteNumber(value) {
134440
134527
  function asArray4(value) {
134441
134528
  return Array.isArray(value) ? value : [];
134442
134529
  }
134443
- function asRecord17(value) {
134530
+ function asRecord18(value) {
134444
134531
  if (value && typeof value === "object" && !Array.isArray(value)) {
134445
134532
  return value;
134446
134533
  }
@@ -134449,15 +134536,15 @@ function asRecord17(value) {
134449
134536
  function pushMissing3(missing, id, chapter, dimension, hint) {
134450
134537
  missing.push({ id, chapter, dimension, hint });
134451
134538
  }
134452
- function stringList2(value) {
134539
+ function stringList3(value) {
134453
134540
  return asArray4(value).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
134454
134541
  }
134455
134542
  function insightLists(section) {
134456
- const rec = asRecord17(section);
134457
- const insight = asRecord17(rec?.insight) ?? rec;
134543
+ const rec = asRecord18(section);
134544
+ const insight = asRecord18(rec?.insight) ?? rec;
134458
134545
  return {
134459
- analysis: stringList2(insight?.analysis),
134460
- advice: stringList2(insight?.advice)
134546
+ analysis: stringList3(insight?.analysis),
134547
+ advice: stringList3(insight?.advice)
134461
134548
  };
134462
134549
  }
134463
134550
  function requireInsight(missing, key, chapter, section, required) {
@@ -134484,11 +134571,11 @@ function requireInsight(missing, key, chapter, section, required) {
134484
134571
  }
134485
134572
  function validateMetaPeriodReportContent(data) {
134486
134573
  const missing = [];
134487
- const meta = asRecord17(data.meta) ?? {};
134488
- const kpis = asRecord17(data.kpis) ?? {};
134489
- const tables = asRecord17(data.tables) ?? {};
134490
- const charts = asRecord17(data.charts) ?? {};
134491
- const sections = asRecord17(data.sections) ?? {};
134574
+ const meta = asRecord18(data.meta) ?? {};
134575
+ const kpis = asRecord18(data.kpis) ?? {};
134576
+ const tables = asRecord18(data.tables) ?? {};
134577
+ const charts = asRecord18(data.charts) ?? {};
134578
+ const sections = asRecord18(data.sections) ?? {};
134492
134579
  const fourQuestions = asArray4(data.fourQuestions);
134493
134580
  const recommendations = asArray4(data.recommendations);
134494
134581
  if (!textLen3(meta.accountName) && !textLen3(meta.accountId)) {
@@ -134567,7 +134654,7 @@ function validateMetaPeriodReportContent(data) {
134567
134654
  "\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 []"
134568
134655
  );
134569
134656
  }
134570
- const dailyChart = asRecord17(charts.daily);
134657
+ const dailyChart = asRecord18(charts.daily);
134571
134658
  if (!Array.isArray(dailyChart?.labels)) {
134572
134659
  pushMissing3(
134573
134660
  missing,
@@ -134577,7 +134664,7 @@ function validateMetaPeriodReportContent(data) {
134577
134664
  "\u987B\u6709 charts.daily.labels \u6570\u7EC4\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 daily\u3002\u672C\u671F\u65E0\u6D88\u8017\u53EF\u4E3A []"
134578
134665
  );
134579
134666
  }
134580
- const countryChart = asRecord17(charts.country);
134667
+ const countryChart = asRecord18(charts.country);
134581
134668
  if (!Array.isArray(countryChart?.labels)) {
134582
134669
  pushMissing3(
134583
134670
  missing,
@@ -134587,7 +134674,7 @@ function validateMetaPeriodReportContent(data) {
134587
134674
  "\u987B\u6709 charts.country.labels \u6570\u7EC4\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 country\u3002\u672C\u671F\u65E0\u6D88\u8017\u53EF\u4E3A []"
134588
134675
  );
134589
134676
  }
134590
- const audienceChart = asRecord17(charts.audience);
134677
+ const audienceChart = asRecord18(charts.audience);
134591
134678
  if (!Array.isArray(audienceChart?.labels)) {
134592
134679
  pushMissing3(
134593
134680
  missing,
@@ -134606,7 +134693,7 @@ function validateMetaPeriodReportContent(data) {
134606
134693
  "\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"
134607
134694
  );
134608
134695
  } else {
134609
- const titles = fourQuestions.map((q) => asRecord17(q)?.title);
134696
+ const titles = fourQuestions.map((q) => asRecord18(q)?.title);
134610
134697
  for (const title of FOUR_QUESTION_TITLES) {
134611
134698
  if (!titles.includes(title)) {
134612
134699
  pushMissing3(
@@ -134619,7 +134706,7 @@ function validateMetaPeriodReportContent(data) {
134619
134706
  }
134620
134707
  }
134621
134708
  for (let i = 0; i < fourQuestions.length; i++) {
134622
- const q = asRecord17(fourQuestions[i]);
134709
+ const q = asRecord18(fourQuestions[i]);
134623
134710
  if (textLen3(q?.verdict) < 8) {
134624
134711
  pushMissing3(
134625
134712
  missing,
@@ -134629,7 +134716,7 @@ function validateMetaPeriodReportContent(data) {
134629
134716
  "\u4E00\u53E5\u8BDD\u7ED3\u8BBA\uFF0C\u5F15\u7528\u5F53\u6B21\u56FD\u5BB6/CPL/\u4EBA\u7FA4"
134630
134717
  );
134631
134718
  }
134632
- if (stringList2(q?.bullets).length < 1) {
134719
+ if (stringList3(q?.bullets).length < 1) {
134633
134720
  pushMissing3(
134634
134721
  missing,
134635
134722
  `four-questions-bullets-${i}`,
@@ -134659,7 +134746,7 @@ function validateMetaPeriodReportContent(data) {
134659
134746
  );
134660
134747
  } else {
134661
134748
  for (let i = 0; i < recommendations.length; i++) {
134662
- const rec = asRecord17(recommendations[i]);
134749
+ const rec = asRecord18(recommendations[i]);
134663
134750
  if (textLen3(rec?.title) < 2) {
134664
134751
  pushMissing3(
134665
134752
  missing,
@@ -134669,7 +134756,7 @@ function validateMetaPeriodReportContent(data) {
134669
134756
  "\u987B\u6709\u6807\u9898"
134670
134757
  );
134671
134758
  }
134672
- if (stringList2(rec?.items).length < 1) {
134759
+ if (stringList3(rec?.items).length < 1) {
134673
134760
  pushMissing3(
134674
134761
  missing,
134675
134762
  `recommendations-items-${i}`,
@@ -134752,7 +134839,7 @@ async function readJsonFile4(filePath) {
134752
134839
  throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
134753
134840
  }
134754
134841
  }
134755
- function asRecord18(value) {
134842
+ function asRecord19(value) {
134756
134843
  if (value && typeof value === "object" && !Array.isArray(value)) {
134757
134844
  return value;
134758
134845
  }
@@ -134781,7 +134868,7 @@ function defaultOutPath(dataPath, format, out) {
134781
134868
  }
134782
134869
  async function runFacebookAnalysisRender(opts) {
134783
134870
  const dataPath = path36.resolve(opts.dataFile);
134784
- let data = asRecord18(await readJsonFile4(dataPath));
134871
+ let data = asRecord19(await readJsonFile4(dataPath));
134785
134872
  if (opts.snapshotDir?.trim()) {
134786
134873
  data = await mergeFacebookSnapshotIntoReport(data, path36.resolve(opts.snapshotDir));
134787
134874
  }
@@ -134845,31 +134932,31 @@ import { fileURLToPath as fileURLToPath8 } from "url";
134845
134932
  // src/commands/facebook-analysis/diagnosis-merge-snapshot.ts
134846
134933
  import * as fs30 from "fs/promises";
134847
134934
  import * as path37 from "path";
134848
- function asRecord19(value) {
134935
+ function asRecord20(value) {
134849
134936
  if (value && typeof value === "object" && !Array.isArray(value)) {
134850
134937
  return value;
134851
134938
  }
134852
134939
  return null;
134853
134940
  }
134854
- function num6(value) {
134941
+ function num7(value) {
134855
134942
  const n = Number(value);
134856
134943
  return Number.isFinite(n) ? n : void 0;
134857
134944
  }
134858
134945
  function asRows(value) {
134859
- return Array.isArray(value) ? value.filter((r) => asRecord19(r) !== null) : [];
134946
+ return Array.isArray(value) ? value.filter((r) => asRecord20(r) !== null) : [];
134860
134947
  }
134861
134948
  function pickMetricExtras(row, extras) {
134862
134949
  const out = {
134863
- spend: num6(row.spend) ?? 0,
134864
- impressions: num6(row.impressions) ?? 0,
134865
- clicks: num6(row.clicks) ?? 0,
134866
- results: num6(row.results),
134867
- costPerResult: num6(row.costPerResult),
134868
- reach: num6(row.reach),
134869
- frequency: num6(row.frequency),
134870
- ctr: num6(row.ctr),
134871
- conversions: num6(row.conversions),
134872
- costPerConversion: num6(row.costPerConversion)
134950
+ spend: num7(row.spend) ?? 0,
134951
+ impressions: num7(row.impressions) ?? 0,
134952
+ clicks: num7(row.clicks) ?? 0,
134953
+ results: num7(row.results),
134954
+ costPerResult: num7(row.costPerResult),
134955
+ reach: num7(row.reach),
134956
+ frequency: num7(row.frequency),
134957
+ ctr: num7(row.ctr),
134958
+ conversions: num7(row.conversions),
134959
+ costPerConversion: num7(row.costPerConversion)
134873
134960
  };
134874
134961
  for (const f of extras) {
134875
134962
  if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
@@ -134905,14 +134992,14 @@ async function readManifest4(snapshotDir, accountId) {
134905
134992
  async function loadSectionFile4(snapshotDir, file) {
134906
134993
  try {
134907
134994
  const raw = await fs30.readFile(path37.join(snapshotDir, file), "utf8");
134908
- return asRecord19(JSON.parse(raw));
134995
+ return asRecord20(JSON.parse(raw));
134909
134996
  } catch {
134910
134997
  return null;
134911
134998
  }
134912
134999
  }
134913
135000
  async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
134914
135001
  const absDir = path37.resolve(snapshotDir);
134915
- const metaAccountId = typeof asRecord19(payload.meta)?.accountId === "string" ? asRecord19(payload.meta)?.accountId : void 0;
135002
+ const metaAccountId = typeof asRecord20(payload.meta)?.accountId === "string" ? asRecord20(payload.meta)?.accountId : void 0;
134916
135003
  const manifest = await readManifest4(snapshotDir, metaAccountId);
134917
135004
  if (!manifest) {
134918
135005
  throw new Error(
@@ -134925,8 +135012,8 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
134925
135012
  if (data) sectionMap.set(art.section, data);
134926
135013
  }
134927
135014
  const overview = sectionMap.get("overview");
134928
- const currentPeriod = asRecord19(overview?.currentPeriod);
134929
- const previousPeriod = asRecord19(overview?.previousPeriod);
135015
+ const currentPeriod = asRecord20(overview?.currentPeriod);
135016
+ const previousPeriod = asRecord20(overview?.previousPeriod);
134930
135017
  const accountId = manifest.accountId ?? metaAccountId;
134931
135018
  const meta = { ...payload.meta ?? {} };
134932
135019
  if (!meta.accountId && accountId) meta.accountId = accountId;
@@ -134938,15 +135025,15 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
134938
135025
  if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
134939
135026
  const kpis = { ...payload.kpis ?? {} };
134940
135027
  if (currentPeriod) {
134941
- const spend = num6(currentPeriod.spend);
134942
- const impressions = num6(currentPeriod.impressions);
134943
- const clicks = num6(currentPeriod.clicks);
134944
- const results = num6(currentPeriod.results);
134945
- const costPerResult = num6(currentPeriod.costPerResult);
134946
- const ctr = num6(currentPeriod.ctr);
134947
- const cpc = num6(currentPeriod.cpc ?? currentPeriod.averageCpc);
134948
- const reach = num6(currentPeriod.reach);
134949
- const frequency = num6(currentPeriod.frequency);
135028
+ const spend = num7(currentPeriod.spend);
135029
+ const impressions = num7(currentPeriod.impressions);
135030
+ const clicks = num7(currentPeriod.clicks);
135031
+ const results = num7(currentPeriod.results);
135032
+ const costPerResult = num7(currentPeriod.costPerResult);
135033
+ const ctr = num7(currentPeriod.ctr);
135034
+ const cpc = num7(currentPeriod.cpc ?? currentPeriod.averageCpc);
135035
+ const reach = num7(currentPeriod.reach);
135036
+ const frequency = num7(currentPeriod.frequency);
134950
135037
  if (spend != null) kpis.spend = spend;
134951
135038
  if (impressions != null) kpis.impressions = impressions;
134952
135039
  if (clicks != null) kpis.clicks = clicks;
@@ -134959,15 +135046,15 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
134959
135046
  }
134960
135047
  if (previousPeriod) {
134961
135048
  kpis.previousPeriod = {
134962
- spend: num6(previousPeriod.spend),
134963
- impressions: num6(previousPeriod.impressions),
134964
- clicks: num6(previousPeriod.clicks),
134965
- results: num6(previousPeriod.results),
134966
- costPerResult: num6(previousPeriod.costPerResult),
134967
- ctr: num6(previousPeriod.ctr),
134968
- cpc: num6(previousPeriod.cpc ?? previousPeriod.averageCpc),
134969
- reach: num6(previousPeriod.reach),
134970
- frequency: num6(previousPeriod.frequency)
135049
+ spend: num7(previousPeriod.spend),
135050
+ impressions: num7(previousPeriod.impressions),
135051
+ clicks: num7(previousPeriod.clicks),
135052
+ results: num7(previousPeriod.results),
135053
+ costPerResult: num7(previousPeriod.costPerResult),
135054
+ ctr: num7(previousPeriod.ctr),
135055
+ cpc: num7(previousPeriod.cpc ?? previousPeriod.averageCpc),
135056
+ reach: num7(previousPeriod.reach),
135057
+ frequency: num7(previousPeriod.frequency)
134971
135058
  };
134972
135059
  }
134973
135060
  const tables = { ...payload.tables ?? {} };
@@ -135060,7 +135147,7 @@ function textLen4(value) {
135060
135147
  function asArray5(value) {
135061
135148
  return Array.isArray(value) ? value : [];
135062
135149
  }
135063
- function asRecord20(value) {
135150
+ function asRecord21(value) {
135064
135151
  if (value && typeof value === "object" && !Array.isArray(value)) {
135065
135152
  return value;
135066
135153
  }
@@ -135071,9 +135158,9 @@ function pushMissing4(missing, id, chapter, dimension, hint) {
135071
135158
  }
135072
135159
  function validateMetaDiagnosisReportContent(data) {
135073
135160
  const missing = [];
135074
- const meta = asRecord20(data.meta) ?? {};
135075
- const narrative = asRecord20(data.narrative) ?? {};
135076
- const sections = asRecord20(narrative.sections) ?? {};
135161
+ const meta = asRecord21(data.meta) ?? {};
135162
+ const narrative = asRecord21(data.narrative) ?? {};
135163
+ const sections = asRecord21(narrative.sections) ?? {};
135077
135164
  if (!textLen4(meta.accountId)) {
135078
135165
  pushMissing4(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
135079
135166
  }
@@ -135091,7 +135178,7 @@ function validateMetaDiagnosisReportContent(data) {
135091
135178
  const sectionSuggestionCounts = {};
135092
135179
  const sectionNotAvailable = {};
135093
135180
  for (const key of NORMAL_SECTION_KEYS) {
135094
- const section = asRecord20(sections[key]);
135181
+ const section = asRecord21(sections[key]);
135095
135182
  const analysis = asArray5(section?.analysis).filter((s) => textLen4(s) > 0);
135096
135183
  const suggestions = asArray5(section?.suggestions).filter((s) => textLen4(s) > 0);
135097
135184
  sectionAnalysisCounts[key] = analysis.length;
@@ -135117,7 +135204,7 @@ function validateMetaDiagnosisReportContent(data) {
135117
135204
  }
135118
135205
  }
135119
135206
  for (const key of NA_SECTION_KEYS) {
135120
- const section = asRecord20(sections[key]);
135207
+ const section = asRecord21(sections[key]);
135121
135208
  const analysis = asArray5(section?.analysis).filter((s) => textLen4(s) > 0);
135122
135209
  const suggestions = asArray5(section?.suggestions).filter((s) => textLen4(s) > 0);
135123
135210
  const notAvailable = section?.notAvailable === true;
@@ -135139,7 +135226,7 @@ function validateMetaDiagnosisReportContent(data) {
135139
135226
  }
135140
135227
  const recommendations = asArray5(narrative.recommendations).filter((r) => {
135141
135228
  if (typeof r === "string") return textLen4(r) > 0;
135142
- const rec = asRecord20(r);
135229
+ const rec = asRecord21(r);
135143
135230
  return textLen4(rec?.content) > 0;
135144
135231
  });
135145
135232
  if (recommendations.length < 3) {
@@ -135212,7 +135299,7 @@ async function readJsonFile5(filePath) {
135212
135299
  throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
135213
135300
  }
135214
135301
  }
135215
- function asRecord21(value) {
135302
+ function asRecord22(value) {
135216
135303
  if (value && typeof value === "object" && !Array.isArray(value)) {
135217
135304
  return value;
135218
135305
  }
@@ -135227,7 +135314,7 @@ function injectReportData4(html, payload) {
135227
135314
  }
135228
135315
  async function runFacebookAnalysisDiagnosisRender(opts) {
135229
135316
  const dataPath = path38.resolve(opts.dataFile);
135230
- let data = asRecord21(await readJsonFile5(dataPath));
135317
+ let data = asRecord22(await readJsonFile5(dataPath));
135231
135318
  if (opts.snapshotDir?.trim()) {
135232
135319
  data = await mergeMetaDiagnosisSnapshotIntoReport(data, path38.resolve(opts.snapshotDir));
135233
135320
  }
@@ -135426,7 +135513,7 @@ function resolveFacebookPageSize(raw, fallback = FB_ENTITY_PAGE_SIZE_DEFAULT) {
135426
135513
  if (!Number.isFinite(n) || n <= 0) return fallback;
135427
135514
  return Math.min(Math.max(Math.floor(n), 1), FB_ENTITY_PAGE_SIZE_MAX);
135428
135515
  }
135429
- function asRecord22(value) {
135516
+ function asRecord23(value) {
135430
135517
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
135431
135518
  }
135432
135519
  function pickProp(obj, ...keys) {
@@ -135436,12 +135523,12 @@ function pickProp(obj, ...keys) {
135436
135523
  return void 0;
135437
135524
  }
135438
135525
  function unwrapFacebookPaged(raw) {
135439
- const rec = asRecord22(raw);
135526
+ const rec = asRecord23(raw);
135440
135527
  if (!rec) return { items: [] };
135441
135528
  const data = pickProp(rec, "data", "Data");
135442
135529
  const items3 = Array.isArray(data) ? data : [];
135443
- const paging = asRecord22(pickProp(rec, "paging", "Paging"));
135444
- const cursors = paging ? asRecord22(pickProp(paging, "cursors", "Cursors")) : void 0;
135530
+ const paging = asRecord23(pickProp(rec, "paging", "Paging"));
135531
+ const cursors = paging ? asRecord23(pickProp(paging, "cursors", "Cursors")) : void 0;
135445
135532
  const afterRaw = cursors ? pickProp(cursors, "after", "After") : void 0;
135446
135533
  const after = typeof afterRaw === "string" && afterRaw.trim() ? afterRaw.trim() : void 0;
135447
135534
  return { items: items3, after };
@@ -135589,7 +135676,7 @@ async function fetchFacebookInsights(config, digits, level, opts) {
135589
135676
  opts.verbose
135590
135677
  );
135591
135678
  if (Array.isArray(raw)) return raw;
135592
- const rec = asRecord22(raw);
135679
+ const rec = asRecord23(raw);
135593
135680
  const data = rec ? pickProp(rec, "data", "Data") : void 0;
135594
135681
  return Array.isArray(data) ? data : [];
135595
135682
  }
@@ -135598,7 +135685,7 @@ function sumActionArray(raw) {
135598
135685
  let sum = 0;
135599
135686
  let hit = false;
135600
135687
  for (const item of raw) {
135601
- const rec = asRecord22(item);
135688
+ const rec = asRecord23(item);
135602
135689
  if (!rec) continue;
135603
135690
  const n = Number(rec.value);
135604
135691
  if (!Number.isFinite(n)) continue;
@@ -136134,7 +136221,7 @@ init_auth();
136134
136221
  init_balance();
136135
136222
 
136136
136223
  // src/commands/bing-analysis/normalize-bing-overview.ts
136137
- function num7(v) {
136224
+ function num8(v) {
136138
136225
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
136139
136226
  }
136140
136227
  function roundMoney(v) {
@@ -136155,19 +136242,19 @@ function countInclusiveCalendarDays(startDate, endDate) {
136155
136242
  }
136156
136243
  function periodSpend(period) {
136157
136244
  if (!period || typeof period !== "object") return void 0;
136158
- return num7(period.spend);
136245
+ return num8(period.spend);
136159
136246
  }
136160
136247
  function enrichBingOverviewDerivedMetrics(record2, startDate, endDate) {
136161
136248
  const out = { ...record2 };
136162
136249
  const currentSpend = periodSpend(out.currentPeriod);
136163
- let totalCost = num7(out.totalCost);
136250
+ let totalCost = num8(out.totalCost);
136164
136251
  if (totalCost == null || totalCost <= 0) {
136165
136252
  if (currentSpend != null && currentSpend > 0) {
136166
136253
  out.totalCost = currentSpend;
136167
136254
  totalCost = currentSpend;
136168
136255
  }
136169
136256
  }
136170
- let activeDays = num7(out.activeDays);
136257
+ let activeDays = num8(out.activeDays);
136171
136258
  if (activeDays == null || activeDays <= 0) {
136172
136259
  const calendarDays = countInclusiveCalendarDays(startDate, endDate);
136173
136260
  if (calendarDays > 0) {
@@ -136175,7 +136262,7 @@ function enrichBingOverviewDerivedMetrics(record2, startDate, endDate) {
136175
136262
  activeDays = calendarDays;
136176
136263
  }
136177
136264
  }
136178
- const averageDailyCost = num7(out.averageDailyCost);
136265
+ const averageDailyCost = num8(out.averageDailyCost);
136179
136266
  if ((averageDailyCost == null || averageDailyCost <= 0) && totalCost != null && totalCost > 0 && activeDays != null && activeDays > 0) {
136180
136267
  out.averageDailyCost = roundMoney(totalCost / activeDays);
136181
136268
  }
@@ -136207,16 +136294,16 @@ function calcBingPreviousPeriodRange(startDate, endDate) {
136207
136294
  function isBingPeriodBlockEmpty(period) {
136208
136295
  if (!period || typeof period !== "object") return true;
136209
136296
  const p = period;
136210
- const spend = num7(p.spend) ?? 0;
136211
- const impressions = num7(p.impressions) ?? 0;
136212
- const clicks = num7(p.clicks) ?? 0;
136297
+ const spend = num8(p.spend) ?? 0;
136298
+ const impressions = num8(p.impressions) ?? 0;
136299
+ const clicks = num8(p.clicks) ?? 0;
136213
136300
  return spend <= 0 && impressions <= 0 && clicks <= 0;
136214
136301
  }
136215
136302
  function syncOverviewBalanceFields(record2) {
136216
136303
  const out = { ...record2 };
136217
- const budget = num7(out.remainingAccountBudget);
136304
+ const budget = num8(out.remainingAccountBudget);
136218
136305
  if (budget != null && budget >= 0) {
136219
- const balanceField = num7(out.balance);
136306
+ const balanceField = num8(out.balance);
136220
136307
  if (balanceField == null || balanceField <= 0 && budget > 0) {
136221
136308
  out.balance = budget;
136222
136309
  }
@@ -136877,22 +136964,22 @@ import { fileURLToPath as fileURLToPath9 } from "url";
136877
136964
  // src/commands/bing-analysis/merge-snapshot.ts
136878
136965
  import * as fs32 from "fs/promises";
136879
136966
  import * as path41 from "path";
136880
- function asRecord23(value) {
136967
+ function asRecord24(value) {
136881
136968
  if (value && typeof value === "object" && !Array.isArray(value)) {
136882
136969
  return value;
136883
136970
  }
136884
136971
  return null;
136885
136972
  }
136886
- function num8(value) {
136973
+ function num9(value) {
136887
136974
  const n = Number(value);
136888
136975
  return Number.isFinite(n) ? n : void 0;
136889
136976
  }
136890
- function round25(value) {
136977
+ function round26(value) {
136891
136978
  return value == null ? void 0 : Math.round(value * 100) / 100;
136892
136979
  }
136893
136980
  function asRows2(value) {
136894
136981
  if (Array.isArray(value)) {
136895
- return value.filter((r) => asRecord23(r) !== null);
136982
+ return value.filter((r) => asRecord24(r) !== null);
136896
136983
  }
136897
136984
  return [];
136898
136985
  }
@@ -136929,14 +137016,14 @@ async function readManifest5(snapshotDir, accountId) {
136929
137016
  }
136930
137017
  function pickMetrics(row, extras = []) {
136931
137018
  const out = {
136932
- spend: num8(row.spend) ?? 0,
136933
- impressions: num8(row.impressions) ?? 0,
136934
- clicks: num8(row.clicks) ?? 0,
136935
- ctr: num8(row.ctr),
136936
- averageCpc: num8(row.averageCpc),
136937
- conversions: num8(row.conversions) ?? 0,
136938
- conversionRate: num8(row.conversionRate),
136939
- costPerConversion: num8(row.costPerConversion)
137019
+ spend: num9(row.spend) ?? 0,
137020
+ impressions: num9(row.impressions) ?? 0,
137021
+ clicks: num9(row.clicks) ?? 0,
137022
+ ctr: num9(row.ctr),
137023
+ averageCpc: num9(row.averageCpc),
137024
+ conversions: num9(row.conversions) ?? 0,
137025
+ conversionRate: num9(row.conversionRate),
137026
+ costPerConversion: num9(row.costPerConversion)
136940
137027
  };
136941
137028
  for (const f of extras) {
136942
137029
  if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
@@ -136950,19 +137037,19 @@ function sumBingCampaignRowsToPeriodKpis(rows) {
136950
137037
  let clicks = 0;
136951
137038
  let conversions = 0;
136952
137039
  for (const row of rows) {
136953
- spend += num8(row.spend) ?? 0;
136954
- impressions += num8(row.impressions) ?? 0;
136955
- clicks += num8(row.clicks) ?? 0;
136956
- conversions += num8(row.conversions) ?? 0;
137040
+ spend += num9(row.spend) ?? 0;
137041
+ impressions += num9(row.impressions) ?? 0;
137042
+ clicks += num9(row.clicks) ?? 0;
137043
+ conversions += num9(row.conversions) ?? 0;
136957
137044
  }
136958
137045
  return {
136959
- spend: round25(spend),
137046
+ spend: round26(spend),
136960
137047
  impressions: Math.round(impressions),
136961
137048
  clicks: Math.round(clicks),
136962
- conversions: round25(conversions),
137049
+ conversions: round26(conversions),
136963
137050
  ctr: impressions > 0 ? clicks / impressions : void 0,
136964
- averageCpc: clicks > 0 ? round25(spend / clicks) : void 0,
136965
- costPerConversion: conversions > 0 ? round25(spend / conversions) : void 0
137051
+ averageCpc: clicks > 0 ? round26(spend / clicks) : void 0,
137052
+ costPerConversion: conversions > 0 ? round26(spend / conversions) : void 0
136966
137053
  };
136967
137054
  }
136968
137055
  function campaignStatusDisplay(status) {
@@ -136974,17 +137061,17 @@ function campaignStatusDisplay(status) {
136974
137061
  return status == null || status === "" ? "\u2014" : String(status);
136975
137062
  }
136976
137063
  function mapDeviceRows(payload) {
136977
- const obj = asRecord23(payload);
137064
+ const obj = asRecord24(payload);
136978
137065
  return asRows2(obj?.devices).map((r) => pickMetrics(r, ["deviceType"]));
136979
137066
  }
136980
137067
  function mapGeographicRows(payload) {
136981
- const obj = asRecord23(payload);
137068
+ const obj = asRecord24(payload);
136982
137069
  return asRows2(obj?.countries).map(
136983
137070
  (r) => pickMetrics(r, ["countryOrRegion", "countryCriteriaId", "regionName", "cityName"])
136984
137071
  );
136985
137072
  }
136986
137073
  function mapAudienceLabelRows(payload, labelKey) {
136987
- const obj = asRecord23(payload);
137074
+ const obj = asRecord24(payload);
136988
137075
  return asRows2(obj?.audience).map((r) => {
136989
137076
  const m = pickMetrics(r, ["audience", "bidModifier"]);
136990
137077
  m[labelKey] = r.audience;
@@ -136992,7 +137079,7 @@ function mapAudienceLabelRows(payload, labelKey) {
136992
137079
  });
136993
137080
  }
136994
137081
  function mapAudienceFromMerged(payload) {
136995
- const data = asRecord23(asRecord23(payload)?.data);
137082
+ const data = asRecord24(asRecord24(payload)?.data);
136996
137083
  return {
136997
137084
  audienceAge: mapAudienceLabelRows(data?.ageAudience, "ageRange"),
136998
137085
  audienceGender: mapAudienceLabelRows(data?.genderAudience, "gender")
@@ -137087,7 +137174,7 @@ function mapSearchTermRows(payload) {
137087
137174
  }
137088
137175
  async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
137089
137176
  const absDir = path41.resolve(snapshotDir);
137090
- const metaAccountId = typeof asRecord23(payload.meta)?.accountId === "string" ? asRecord23(payload.meta)?.accountId : void 0;
137177
+ const metaAccountId = typeof asRecord24(payload.meta)?.accountId === "string" ? asRecord24(payload.meta)?.accountId : void 0;
137091
137178
  const manifest = await readManifest5(snapshotDir, metaAccountId);
137092
137179
  if (!manifest) {
137093
137180
  throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 report-manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C bing-analysis --json-out`);
@@ -137097,9 +137184,9 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
137097
137184
  const data = await loadSectionPayload(absDir, art.file);
137098
137185
  if (data != null) sectionMap.set(art.section, data);
137099
137186
  }
137100
- const overview = asRecord23(sectionMap.get("overview"));
137101
- const currentPeriod = asRecord23(overview?.currentPeriod);
137102
- const previousPeriod = asRecord23(overview?.previousPeriod);
137187
+ const overview = asRecord24(sectionMap.get("overview"));
137188
+ const currentPeriod = asRecord24(overview?.currentPeriod);
137189
+ const previousPeriod = asRecord24(overview?.previousPeriod);
137103
137190
  const accountId = manifest.accountId ?? metaAccountId;
137104
137191
  const campaignRows3 = mapCampaignRows2(sectionMap.get("campaigns"));
137105
137192
  const campaignKpis = sumBingCampaignRowsToPeriodKpis(campaignRows3);
@@ -137118,13 +137205,13 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
137118
137205
  if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
137119
137206
  const kpis = { ...payload.kpis ?? {} };
137120
137207
  const currentKpiSource = campaignKpis ?? (currentPeriod ? {
137121
- spend: num8(currentPeriod.spend),
137122
- impressions: num8(currentPeriod.impressions),
137123
- clicks: num8(currentPeriod.clicks),
137124
- conversions: num8(currentPeriod.conversions),
137125
- ctr: num8(currentPeriod.ctr),
137126
- averageCpc: num8(currentPeriod.averageCpc),
137127
- costPerConversion: num8(currentPeriod.costPerConversion)
137208
+ spend: num9(currentPeriod.spend),
137209
+ impressions: num9(currentPeriod.impressions),
137210
+ clicks: num9(currentPeriod.clicks),
137211
+ conversions: num9(currentPeriod.conversions),
137212
+ ctr: num9(currentPeriod.ctr),
137213
+ averageCpc: num9(currentPeriod.averageCpc),
137214
+ costPerConversion: num9(currentPeriod.costPerConversion)
137128
137215
  } : null);
137129
137216
  if (currentKpiSource) {
137130
137217
  if (currentKpiSource.spend != null) kpis.spend = currentKpiSource.spend;
@@ -137138,24 +137225,24 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
137138
137225
  }
137139
137226
  }
137140
137227
  if (overview) {
137141
- const avgDaily = num8(overview.averageDailyCost);
137228
+ const avgDaily = num9(overview.averageDailyCost);
137142
137229
  if (avgDaily != null) kpis.averageDailyCost = avgDaily;
137143
- const bal = num8(overview.balance);
137230
+ const bal = num9(overview.balance);
137144
137231
  if (bal != null) kpis.balance = bal;
137145
- const rem = num8(overview.remainingAccountBudget);
137232
+ const rem = num9(overview.remainingAccountBudget);
137146
137233
  if (rem != null) kpis.remainingAccountBudget = rem;
137147
- const opt = num8(overview.optimizationScore);
137234
+ const opt = num9(overview.optimizationScore);
137148
137235
  if (opt != null) kpis.optimizationScore = opt;
137149
137236
  }
137150
137237
  if (previousPeriod) {
137151
137238
  kpis.previousPeriod = {
137152
- spend: num8(previousPeriod.spend),
137153
- impressions: num8(previousPeriod.impressions),
137154
- clicks: num8(previousPeriod.clicks),
137155
- conversions: num8(previousPeriod.conversions),
137156
- ctr: num8(previousPeriod.ctr),
137157
- averageCpc: num8(previousPeriod.averageCpc),
137158
- costPerConversion: num8(previousPeriod.costPerConversion)
137239
+ spend: num9(previousPeriod.spend),
137240
+ impressions: num9(previousPeriod.impressions),
137241
+ clicks: num9(previousPeriod.clicks),
137242
+ conversions: num9(previousPeriod.conversions),
137243
+ ctr: num9(previousPeriod.ctr),
137244
+ averageCpc: num9(previousPeriod.averageCpc),
137245
+ costPerConversion: num9(previousPeriod.costPerConversion)
137159
137246
  };
137160
137247
  }
137161
137248
  const tables = { ...payload.tables ?? {} };
@@ -137226,7 +137313,7 @@ function textLen5(value) {
137226
137313
  function asArray6(value) {
137227
137314
  return Array.isArray(value) ? value : [];
137228
137315
  }
137229
- function asRecord24(value) {
137316
+ function asRecord25(value) {
137230
137317
  if (value && typeof value === "object" && !Array.isArray(value)) {
137231
137318
  return value;
137232
137319
  }
@@ -137237,9 +137324,9 @@ function pushMissing5(missing, id, chapter, dimension, hint) {
137237
137324
  }
137238
137325
  function validateBingPeriodReportContent(data) {
137239
137326
  const missing = [];
137240
- const meta = asRecord24(data.meta) ?? {};
137241
- const narrative = asRecord24(data.narrative) ?? {};
137242
- const sections = asRecord24(narrative.sections) ?? {};
137327
+ const meta = asRecord25(data.meta) ?? {};
137328
+ const narrative = asRecord25(data.narrative) ?? {};
137329
+ const sections = asRecord25(narrative.sections) ?? {};
137243
137330
  if (!textLen5(meta.accountId)) {
137244
137331
  pushMissing5(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
137245
137332
  }
@@ -137256,7 +137343,7 @@ function validateBingPeriodReportContent(data) {
137256
137343
  const sectionAnalysisCounts = {};
137257
137344
  const sectionSuggestionCounts = {};
137258
137345
  for (const key of SECTION_KEYS2) {
137259
- const section = asRecord24(sections[key]);
137346
+ const section = asRecord25(sections[key]);
137260
137347
  const analysis = asArray6(section?.analysis).filter((s) => textLen5(s) > 0);
137261
137348
  const suggestions = asArray6(section?.suggestions).filter((s) => textLen5(s) > 0);
137262
137349
  sectionAnalysisCounts[key] = analysis.length;
@@ -137282,7 +137369,7 @@ function validateBingPeriodReportContent(data) {
137282
137369
  }
137283
137370
  const recommendations = asArray6(narrative.recommendations).filter((r) => {
137284
137371
  if (typeof r === "string") return textLen5(r) > 0;
137285
- const rec = asRecord24(r);
137372
+ const rec = asRecord25(r);
137286
137373
  return textLen5(rec?.content) > 0;
137287
137374
  });
137288
137375
  if (recommendations.length < 3) {
@@ -137343,7 +137430,7 @@ async function readJsonFile6(filePath) {
137343
137430
  throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
137344
137431
  }
137345
137432
  }
137346
- function asRecord25(value) {
137433
+ function asRecord26(value) {
137347
137434
  if (value && typeof value === "object" && !Array.isArray(value)) {
137348
137435
  return value;
137349
137436
  }
@@ -137358,7 +137445,7 @@ function injectReportData5(html, payload) {
137358
137445
  }
137359
137446
  async function runBingAnalysisRender(opts) {
137360
137447
  const dataPath = path42.resolve(opts.dataFile);
137361
- let data = asRecord25(await readJsonFile6(dataPath));
137448
+ let data = asRecord26(await readJsonFile6(dataPath));
137362
137449
  if (opts.snapshotDir?.trim()) {
137363
137450
  data = await mergeBingAnalysisSnapshotIntoReport(data, path42.resolve(opts.snapshotDir));
137364
137451
  }
@@ -137831,17 +137918,17 @@ import { fileURLToPath as fileURLToPath10 } from "url";
137831
137918
  // src/commands/tiktok-analysis/merge-snapshot.ts
137832
137919
  import * as fs34 from "fs/promises";
137833
137920
  import * as path44 from "path";
137834
- function asRecord26(value) {
137921
+ function asRecord27(value) {
137835
137922
  if (value && typeof value === "object" && !Array.isArray(value)) {
137836
137923
  return value;
137837
137924
  }
137838
137925
  return null;
137839
137926
  }
137840
- function num9(value) {
137927
+ function num10(value) {
137841
137928
  const n = Number(value);
137842
137929
  return Number.isFinite(n) ? n : void 0;
137843
137930
  }
137844
- function round26(value) {
137931
+ function round27(value) {
137845
137932
  return value == null ? void 0 : Math.round(value * 100) / 100;
137846
137933
  }
137847
137934
  async function loadSectionPayload2(snapshotDir, file) {
@@ -137876,16 +137963,16 @@ async function readManifest6(snapshotDir, accountId) {
137876
137963
  return null;
137877
137964
  }
137878
137965
  function normalizeTikTokRate(value) {
137879
- const n = num9(value);
137966
+ const n = num10(value);
137880
137967
  if (n == null) return void 0;
137881
137968
  return n > 1 ? n / 100 : n;
137882
137969
  }
137883
137970
  function normalizeTikTokMetrics(metrics) {
137884
137971
  const m = metrics ?? {};
137885
- const spend = num9(m.spend);
137886
- const impressions = num9(m.impressions);
137887
- const clicks = num9(m.clicks);
137888
- const conversions = num9(m.conversion);
137972
+ const spend = num10(m.spend);
137973
+ const impressions = num10(m.impressions);
137974
+ const clicks = num10(m.clicks);
137975
+ const conversions = num10(m.conversion);
137889
137976
  const ctrFromCounts = impressions != null && impressions > 0 && clicks != null ? clicks / impressions : void 0;
137890
137977
  const cvrFromCounts = clicks != null && clicks > 0 && conversions != null ? conversions / clicks : void 0;
137891
137978
  return {
@@ -137894,8 +137981,8 @@ function normalizeTikTokMetrics(metrics) {
137894
137981
  clicks: clicks ?? 0,
137895
137982
  conversions: conversions == null ? 0 : Math.round(conversions),
137896
137983
  ctr: ctrFromCounts ?? normalizeTikTokRate(m.ctr),
137897
- averageCpc: num9(m.cpc),
137898
- costPerConversion: num9(m.cost_per_conversion),
137984
+ averageCpc: num10(m.cpc),
137985
+ costPerConversion: num10(m.cost_per_conversion),
137899
137986
  conversionRate: cvrFromCounts ?? normalizeTikTokRate(m.conversion_rate)
137900
137987
  };
137901
137988
  }
@@ -137915,19 +138002,19 @@ function sumTikTokCampaignRowsToPeriodKpis(rows) {
137915
138002
  let clicks = 0;
137916
138003
  let conversions = 0;
137917
138004
  for (const row of rows) {
137918
- spend += num9(row.spend) ?? 0;
137919
- impressions += num9(row.impressions) ?? 0;
137920
- clicks += num9(row.clicks) ?? 0;
137921
- conversions += num9(row.conversions) ?? 0;
138005
+ spend += num10(row.spend) ?? 0;
138006
+ impressions += num10(row.impressions) ?? 0;
138007
+ clicks += num10(row.clicks) ?? 0;
138008
+ conversions += num10(row.conversions) ?? 0;
137922
138009
  }
137923
138010
  return {
137924
- spend: round26(spend),
138011
+ spend: round27(spend),
137925
138012
  impressions: Math.round(impressions),
137926
138013
  clicks: Math.round(clicks),
137927
- conversions: round26(conversions),
138014
+ conversions: round27(conversions),
137928
138015
  ctr: impressions > 0 ? clicks / impressions : void 0,
137929
- averageCpc: clicks > 0 ? round26(spend / clicks) : void 0,
137930
- costPerConversion: conversions > 0 ? round26(spend / conversions) : void 0
138016
+ averageCpc: clicks > 0 ? round27(spend / clicks) : void 0,
138017
+ costPerConversion: conversions > 0 ? round27(spend / conversions) : void 0
137931
138018
  };
137932
138019
  }
137933
138020
  function mapCampaignTable(rows) {
@@ -137935,13 +138022,13 @@ function mapCampaignTable(rows) {
137935
138022
  campaignId: r.campaignId,
137936
138023
  campaignName: r.campaignName,
137937
138024
  status: r.campaignStatus,
137938
- spend: num9(r.spend) ?? 0,
137939
- impressions: num9(r.impressions) ?? 0,
137940
- clicks: num9(r.clicks) ?? 0,
137941
- conversions: num9(r.conversions) ?? 0,
137942
- ctr: num9(r.ctr),
137943
- averageCpc: num9(r.averageCpc),
137944
- costPerConversion: num9(r.costPerConversion)
138025
+ spend: num10(r.spend) ?? 0,
138026
+ impressions: num10(r.impressions) ?? 0,
138027
+ clicks: num10(r.clicks) ?? 0,
138028
+ conversions: num10(r.conversions) ?? 0,
138029
+ ctr: num10(r.ctr),
138030
+ averageCpc: num10(r.averageCpc),
138031
+ costPerConversion: num10(r.costPerConversion)
137945
138032
  }));
137946
138033
  }
137947
138034
  function pickTikTokName(item, metricKey, dimensionKey) {
@@ -138009,7 +138096,7 @@ function mapAudienceMerged(payload) {
138009
138096
  }
138010
138097
  async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
138011
138098
  const absDir = path44.resolve(snapshotDir);
138012
- const metaAccountId = typeof asRecord26(payload.meta)?.accountId === "string" ? asRecord26(payload.meta)?.accountId : void 0;
138099
+ const metaAccountId = typeof asRecord27(payload.meta)?.accountId === "string" ? asRecord27(payload.meta)?.accountId : void 0;
138013
138100
  const manifest = await readManifest6(snapshotDir, metaAccountId);
138014
138101
  if (!manifest) {
138015
138102
  throw new Error(
@@ -138038,13 +138125,13 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
138038
138125
  if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
138039
138126
  const kpis = { ...payload.kpis ?? {} };
138040
138127
  const currentKpiSource = campaignKpis ?? (currentPeriod ? {
138041
- spend: num9(currentPeriod.spend),
138042
- impressions: num9(currentPeriod.impressions),
138043
- clicks: num9(currentPeriod.clicks),
138044
- conversions: num9(currentPeriod.conversions),
138045
- ctr: num9(currentPeriod.ctr),
138046
- averageCpc: num9(currentPeriod.averageCpc),
138047
- costPerConversion: num9(currentPeriod.costPerConversion)
138128
+ spend: num10(currentPeriod.spend),
138129
+ impressions: num10(currentPeriod.impressions),
138130
+ clicks: num10(currentPeriod.clicks),
138131
+ conversions: num10(currentPeriod.conversions),
138132
+ ctr: num10(currentPeriod.ctr),
138133
+ averageCpc: num10(currentPeriod.averageCpc),
138134
+ costPerConversion: num10(currentPeriod.costPerConversion)
138048
138135
  } : null);
138049
138136
  if (currentKpiSource) {
138050
138137
  if (currentKpiSource.spend != null) kpis.spend = currentKpiSource.spend;
@@ -138059,13 +138146,13 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
138059
138146
  }
138060
138147
  if (previousPeriod) {
138061
138148
  kpis.previousPeriod = {
138062
- spend: num9(previousPeriod.spend),
138063
- impressions: num9(previousPeriod.impressions),
138064
- clicks: num9(previousPeriod.clicks),
138065
- conversions: num9(previousPeriod.conversions),
138066
- ctr: num9(previousPeriod.ctr),
138067
- averageCpc: num9(previousPeriod.averageCpc),
138068
- costPerConversion: num9(previousPeriod.costPerConversion)
138149
+ spend: num10(previousPeriod.spend),
138150
+ impressions: num10(previousPeriod.impressions),
138151
+ clicks: num10(previousPeriod.clicks),
138152
+ conversions: num10(previousPeriod.conversions),
138153
+ ctr: num10(previousPeriod.ctr),
138154
+ averageCpc: num10(previousPeriod.averageCpc),
138155
+ costPerConversion: num10(previousPeriod.costPerConversion)
138069
138156
  };
138070
138157
  }
138071
138158
  const tables = { ...payload.tables ?? {} };
@@ -138123,7 +138210,7 @@ function textLen6(value) {
138123
138210
  function asArray7(value) {
138124
138211
  return Array.isArray(value) ? value : [];
138125
138212
  }
138126
- function asRecord27(value) {
138213
+ function asRecord28(value) {
138127
138214
  if (value && typeof value === "object" && !Array.isArray(value)) {
138128
138215
  return value;
138129
138216
  }
@@ -138134,9 +138221,9 @@ function pushMissing6(missing, id, chapter, dimension, hint) {
138134
138221
  }
138135
138222
  function validateTikTokPeriodReportContent(data) {
138136
138223
  const missing = [];
138137
- const meta = asRecord27(data.meta) ?? {};
138138
- const narrative = asRecord27(data.narrative) ?? {};
138139
- const sections = asRecord27(narrative.sections) ?? {};
138224
+ const meta = asRecord28(data.meta) ?? {};
138225
+ const narrative = asRecord28(data.narrative) ?? {};
138226
+ const sections = asRecord28(narrative.sections) ?? {};
138140
138227
  if (!textLen6(meta.accountId)) {
138141
138228
  pushMissing6(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
138142
138229
  }
@@ -138153,7 +138240,7 @@ function validateTikTokPeriodReportContent(data) {
138153
138240
  const sectionAnalysisCounts = {};
138154
138241
  const sectionSuggestionCounts = {};
138155
138242
  for (const key of SECTION_KEYS3) {
138156
- const section = asRecord27(sections[key]);
138243
+ const section = asRecord28(sections[key]);
138157
138244
  const analysis = asArray7(section?.analysis).filter((s) => textLen6(s) > 0);
138158
138245
  const suggestions = asArray7(section?.suggestions).filter((s) => textLen6(s) > 0);
138159
138246
  sectionAnalysisCounts[key] = analysis.length;
@@ -138179,7 +138266,7 @@ function validateTikTokPeriodReportContent(data) {
138179
138266
  }
138180
138267
  const recommendations = asArray7(narrative.recommendations).filter((r) => {
138181
138268
  if (typeof r === "string") return textLen6(r) > 0;
138182
- const rec = asRecord27(r);
138269
+ const rec = asRecord28(r);
138183
138270
  return textLen6(rec?.content) > 0;
138184
138271
  });
138185
138272
  if (recommendations.length < 3) {
@@ -138240,7 +138327,7 @@ async function readJsonFile7(filePath) {
138240
138327
  throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
138241
138328
  }
138242
138329
  }
138243
- function asRecord28(value) {
138330
+ function asRecord29(value) {
138244
138331
  if (value && typeof value === "object" && !Array.isArray(value)) {
138245
138332
  return value;
138246
138333
  }
@@ -138255,7 +138342,7 @@ function injectReportData6(html, payload) {
138255
138342
  }
138256
138343
  async function runTikTokAnalysisRender(opts) {
138257
138344
  const dataPath = path45.resolve(opts.dataFile);
138258
- let data = asRecord28(await readJsonFile7(dataPath));
138345
+ let data = asRecord29(await readJsonFile7(dataPath));
138259
138346
  if (opts.snapshotDir?.trim()) {
138260
138347
  data = await mergeTikTokAnalysisSnapshotIntoReport(data, path45.resolve(opts.snapshotDir));
138261
138348
  }
@@ -139354,46 +139441,46 @@ function endpointHintForYandexSection(def) {
139354
139441
  // src/commands/yandex-analysis/merge-snapshot.ts
139355
139442
  import * as fs36 from "fs/promises";
139356
139443
  import * as path46 from "path";
139357
- function asRecord29(value) {
139444
+ function asRecord30(value) {
139358
139445
  if (value && typeof value === "object" && !Array.isArray(value)) {
139359
139446
  return value;
139360
139447
  }
139361
139448
  return null;
139362
139449
  }
139363
- function num10(value) {
139450
+ function num11(value) {
139364
139451
  if (value === null || value === void 0 || value === "") return void 0;
139365
139452
  const n = Number(value);
139366
139453
  return Number.isFinite(n) ? n : void 0;
139367
139454
  }
139368
- function round27(value) {
139455
+ function round28(value) {
139369
139456
  return value == null ? void 0 : Math.round(value * 100) / 100;
139370
139457
  }
139371
139458
  function normalizeYandexRoi(value) {
139372
139459
  if (typeof value !== "number" && typeof value !== "string") return void 0;
139373
- const r = num10(value);
139460
+ const r = num11(value);
139374
139461
  return r === -1 ? void 0 : r;
139375
139462
  }
139376
139463
  function asItems(payload) {
139377
- const obj = asRecord29(payload);
139464
+ const obj = asRecord30(payload);
139378
139465
  if (!obj) return [];
139379
139466
  const items3 = obj.items;
139380
139467
  if (!Array.isArray(items3)) return [];
139381
- return items3.filter((r) => asRecord29(r) !== null);
139468
+ return items3.filter((r) => asRecord30(r) !== null);
139382
139469
  }
139383
139470
  function pickMetrics2(row, extras = []) {
139384
- const spend = num10(row.spend) ?? 0;
139385
- const impressions = num10(row.impressions) ?? 0;
139386
- const clicks = num10(row.clicks) ?? 0;
139387
- const conversions = num10(row.conversions) ?? 0;
139471
+ const spend = num11(row.spend) ?? 0;
139472
+ const impressions = num11(row.impressions) ?? 0;
139473
+ const clicks = num11(row.clicks) ?? 0;
139474
+ const conversions = num11(row.conversions) ?? 0;
139388
139475
  const out = {
139389
139476
  spend,
139390
139477
  impressions,
139391
139478
  clicks,
139392
139479
  conversions,
139393
- ctr: num10(row.ctr) ?? (impressions > 0 ? clicks / impressions : void 0),
139394
- averageCpc: num10(row.averageCpc) ?? (clicks > 0 ? round27(spend / clicks) : void 0),
139395
- conversionRate: num10(row.conversionRate) ?? (clicks > 0 ? conversions / clicks : void 0),
139396
- costPerConversion: num10(row.costPerConversion) ?? (conversions > 0 ? round27(spend / conversions) : void 0),
139480
+ ctr: num11(row.ctr) ?? (impressions > 0 ? clicks / impressions : void 0),
139481
+ averageCpc: num11(row.averageCpc) ?? (clicks > 0 ? round28(spend / clicks) : void 0),
139482
+ conversionRate: num11(row.conversionRate) ?? (clicks > 0 ? conversions / clicks : void 0),
139483
+ costPerConversion: num11(row.costPerConversion) ?? (conversions > 0 ? round28(spend / conversions) : void 0),
139397
139484
  roi: normalizeYandexRoi(row.roi)
139398
139485
  };
139399
139486
  for (const f of extras) {
@@ -139489,10 +139576,10 @@ function aggregateDailyToWeekly(dailyRows) {
139489
139576
  map.set(key, acc);
139490
139577
  }
139491
139578
  acc.days += 1;
139492
- acc.spend += num10(row.spend) ?? 0;
139493
- acc.impressions += num10(row.impressions) ?? 0;
139494
- acc.clicks += num10(row.clicks) ?? 0;
139495
- acc.conversions += num10(row.conversions) ?? 0;
139579
+ acc.spend += num11(row.spend) ?? 0;
139580
+ acc.impressions += num11(row.impressions) ?? 0;
139581
+ acc.clicks += num11(row.clicks) ?? 0;
139582
+ acc.conversions += num11(row.conversions) ?? 0;
139496
139583
  }
139497
139584
  return [...map.values()].sort((a, b) => a.weekStart.getTime() - b.weekStart.getTime()).map((acc) => {
139498
139585
  const startDate = formatYmd2(acc.weekStart);
@@ -139503,18 +139590,18 @@ function aggregateDailyToWeekly(dailyRows) {
139503
139590
  startDate,
139504
139591
  endDate,
139505
139592
  days: acc.days,
139506
- spend: round27(acc.spend),
139593
+ spend: round28(acc.spend),
139507
139594
  impressions: Math.round(acc.impressions),
139508
139595
  clicks: Math.round(acc.clicks),
139509
- conversions: round27(acc.conversions),
139596
+ conversions: round28(acc.conversions),
139510
139597
  ctr: acc.impressions > 0 ? acc.clicks / acc.impressions : void 0,
139511
- averageCpc: acc.clicks > 0 ? round27(acc.spend / acc.clicks) : void 0,
139512
- costPerConversion: acc.conversions > 0 ? round27(acc.spend / acc.conversions) : void 0
139598
+ averageCpc: acc.clicks > 0 ? round28(acc.spend / acc.clicks) : void 0,
139599
+ costPerConversion: acc.conversions > 0 ? round28(acc.spend / acc.conversions) : void 0
139513
139600
  };
139514
139601
  });
139515
139602
  }
139516
139603
  function prepareYandexDailyPayload(payload) {
139517
- const obj = asRecord29(payload) ?? {};
139604
+ const obj = asRecord30(payload) ?? {};
139518
139605
  const items3 = asItems(obj).map((r) => {
139519
139606
  const date = normalizeYandexDailyDate(r.date);
139520
139607
  return date ? { ...r, date } : r;
@@ -139583,7 +139670,7 @@ function mapDeviceRows2(payload) {
139583
139670
  }
139584
139671
  async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
139585
139672
  const absDir = path46.resolve(snapshotDir);
139586
- const metaAccountId = typeof asRecord29(payload.meta)?.accountId === "string" ? asRecord29(payload.meta)?.accountId : void 0;
139673
+ const metaAccountId = typeof asRecord30(payload.meta)?.accountId === "string" ? asRecord30(payload.meta)?.accountId : void 0;
139587
139674
  const manifest = await readManifest7(snapshotDir, metaAccountId);
139588
139675
  if (!manifest) {
139589
139676
  throw new Error(
@@ -139595,8 +139682,8 @@ async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
139595
139682
  const data = await loadSectionPayload3(absDir, art.file);
139596
139683
  if (data != null) sectionMap.set(art.section, data);
139597
139684
  }
139598
- const overview = asRecord29(sectionMap.get("overview"));
139599
- const totals = asRecord29(overview?.totals);
139685
+ const overview = asRecord30(sectionMap.get("overview"));
139686
+ const totals = asRecord30(overview?.totals);
139600
139687
  const accountId = manifest.accountId ?? metaAccountId;
139601
139688
  const meta = { ...payload.meta ?? {} };
139602
139689
  if (!meta.accountId && accountId) meta.accountId = accountId;
@@ -139607,24 +139694,24 @@ async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
139607
139694
  if (!meta.platform) meta.platform = "Yandex Direct";
139608
139695
  const kpis = { ...payload.kpis ?? {} };
139609
139696
  if (totals) {
139610
- if (num10(totals.spend) != null) kpis.spend = round27(num10(totals.spend));
139611
- if (num10(totals.impressions) != null) kpis.impressions = Math.round(num10(totals.impressions));
139612
- if (num10(totals.clicks) != null) kpis.clicks = Math.round(num10(totals.clicks));
139613
- if (num10(totals.conversions) != null) kpis.conversions = round27(num10(totals.conversions));
139614
- if (num10(totals.ctr) != null) kpis.ctr = num10(totals.ctr);
139615
- if (num10(totals.averageCpc) != null) kpis.averageCpc = round27(num10(totals.averageCpc));
139616
- if (num10(totals.costPerConversion) != null) {
139617
- kpis.costPerConversion = round27(num10(totals.costPerConversion));
139618
- }
139619
- if (num10(totals.conversionRate) != null) kpis.conversionRate = num10(totals.conversionRate);
139697
+ if (num11(totals.spend) != null) kpis.spend = round28(num11(totals.spend));
139698
+ if (num11(totals.impressions) != null) kpis.impressions = Math.round(num11(totals.impressions));
139699
+ if (num11(totals.clicks) != null) kpis.clicks = Math.round(num11(totals.clicks));
139700
+ if (num11(totals.conversions) != null) kpis.conversions = round28(num11(totals.conversions));
139701
+ if (num11(totals.ctr) != null) kpis.ctr = num11(totals.ctr);
139702
+ if (num11(totals.averageCpc) != null) kpis.averageCpc = round28(num11(totals.averageCpc));
139703
+ if (num11(totals.costPerConversion) != null) {
139704
+ kpis.costPerConversion = round28(num11(totals.costPerConversion));
139705
+ }
139706
+ if (num11(totals.conversionRate) != null) kpis.conversionRate = num11(totals.conversionRate);
139620
139707
  const overviewRoi = normalizeYandexRoi(totals.roi);
139621
139708
  if (overviewRoi != null) kpis.roi = overviewRoi;
139622
139709
  }
139623
139710
  if (overview) {
139624
- if (num10(overview.balance) != null) kpis.balance = round27(num10(overview.balance));
139625
- if (num10(overview.avgDailySpend) != null)
139626
- kpis.avgDailySpend = round27(num10(overview.avgDailySpend));
139627
- if (num10(overview.activeDays) != null) kpis.activeDays = num10(overview.activeDays);
139711
+ if (num11(overview.balance) != null) kpis.balance = round28(num11(overview.balance));
139712
+ if (num11(overview.avgDailySpend) != null)
139713
+ kpis.avgDailySpend = round28(num11(overview.avgDailySpend));
139714
+ if (num11(overview.activeDays) != null) kpis.activeDays = num11(overview.activeDays);
139628
139715
  if (overview.balanceAsOf != null) kpis.balanceAsOf = overview.balanceAsOf;
139629
139716
  }
139630
139717
  if (kpis.balance != null && kpis.avgDailySpend != null && Number(kpis.avgDailySpend) > 0) {
@@ -139818,7 +139905,7 @@ function textLen7(value) {
139818
139905
  function asArray8(value) {
139819
139906
  return Array.isArray(value) ? value : [];
139820
139907
  }
139821
- function asRecord30(value) {
139908
+ function asRecord31(value) {
139822
139909
  if (value && typeof value === "object" && !Array.isArray(value)) {
139823
139910
  return value;
139824
139911
  }
@@ -139831,23 +139918,23 @@ function normKey(value) {
139831
139918
  return String(value ?? "").trim().toLowerCase();
139832
139919
  }
139833
139920
  function insightMatchRecord(item) {
139834
- const rec = asRecord30(item);
139921
+ const rec = asRecord31(item);
139835
139922
  if (!rec) return {};
139836
- return asRecord30(rec.match) ?? rec;
139923
+ return asRecord31(rec.match) ?? rec;
139837
139924
  }
139838
139925
  function findRowInsight(list, primaryField, primaryValue, row) {
139839
139926
  const target = normKey(primaryValue);
139840
139927
  if (!target) return null;
139841
139928
  const candidates = list.filter((it) => normKey(insightMatchRecord(it)[primaryField]) === target);
139842
- if (candidates.length <= 1) return asRecord30(candidates[0]);
139929
+ if (candidates.length <= 1) return asRecord31(candidates[0]);
139843
139930
  const campaign = normKey(row.campaignName);
139844
139931
  const narrowed = candidates.find(
139845
139932
  (it) => normKey(insightMatchRecord(it).campaignName) === campaign
139846
139933
  );
139847
- return asRecord30(narrowed ?? candidates[0]);
139934
+ return asRecord31(narrowed ?? candidates[0]);
139848
139935
  }
139849
139936
  function topRowsBySpend(rows, limit) {
139850
- return rows.map((r) => asRecord30(r)).filter((r) => r != null).sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0)).slice(0, limit);
139937
+ return rows.map((r) => asRecord31(r)).filter((r) => r != null).sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0)).slice(0, limit);
139851
139938
  }
139852
139939
  var ROW_INSIGHT_SPECS = [
139853
139940
  {
@@ -139862,17 +139949,17 @@ var ROW_INSIGHT_SPECS = [
139862
139949
  ];
139863
139950
  function validateYandexPeriodReportContent(data) {
139864
139951
  const missing = [];
139865
- const meta = asRecord30(data.meta) ?? {};
139866
- const narrative = asRecord30(data.narrative) ?? {};
139867
- const sections = asRecord30(narrative.sections) ?? {};
139868
- const kpis = asRecord30(data.kpis) ?? {};
139952
+ const meta = asRecord31(data.meta) ?? {};
139953
+ const narrative = asRecord31(data.narrative) ?? {};
139954
+ const sections = asRecord31(narrative.sections) ?? {};
139955
+ const kpis = asRecord31(data.kpis) ?? {};
139869
139956
  if (!textLen7(meta.accountId)) {
139870
139957
  pushMissing7(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
139871
139958
  }
139872
139959
  const sectionAnalysisCounts = {};
139873
139960
  const sectionSuggestionCounts = {};
139874
139961
  for (const key of SECTION_KEYS4) {
139875
- const section = asRecord30(sections[key]);
139962
+ const section = asRecord31(sections[key]);
139876
139963
  const analysis = asArray8(section?.analysis).filter((s) => textLen7(s) > 0);
139877
139964
  const suggestions = asArray8(section?.suggestions).filter((s) => textLen7(s) > 0);
139878
139965
  sectionAnalysisCounts[key] = analysis.length;
@@ -139898,7 +139985,7 @@ function validateYandexPeriodReportContent(data) {
139898
139985
  }
139899
139986
  const recommendations = asArray8(narrative.recommendations).filter((r) => {
139900
139987
  if (typeof r === "string") return textLen7(r) > 0;
139901
- const rec = asRecord30(r);
139988
+ const rec = asRecord31(r);
139902
139989
  return textLen7(rec?.content) > 0 || textLen7(rec?.text) > 0;
139903
139990
  });
139904
139991
  if (recommendations.length < 3) {
@@ -139920,8 +140007,8 @@ function validateYandexPeriodReportContent(data) {
139920
140007
  "\u81F3\u5C11\u4E00\u9879\u6709\u6548\u6570\u5B57\uFF08\u8BF7\u4F20 --snapshot-dir \u7531 CLI \u4ECE overview \u5408\u5E76\uFF09"
139921
140008
  );
139922
140009
  }
139923
- const tables = asRecord30(data.tables) ?? {};
139924
- const weeklyRows = asArray8(tables.weekly).map((r) => asRecord30(r)).filter((r) => r != null);
140010
+ const tables = asRecord31(data.tables) ?? {};
140011
+ const weeklyRows = asArray8(tables.weekly).map((r) => asRecord31(r)).filter((r) => r != null);
139925
140012
  for (const [i, row] of weeklyRows.entries()) {
139926
140013
  const label = `${row.weekLabel ?? ""} ${row.startDate ?? ""} ${row.endDate ?? ""}`;
139927
140014
  if (/nan/i.test(label) || !normalizeYandexDailyDate(row.startDate) || !normalizeYandexDailyDate(row.endDate)) {
@@ -139934,7 +140021,7 @@ function validateYandexPeriodReportContent(data) {
139934
140021
  );
139935
140022
  }
139936
140023
  }
139937
- const rowInsights = asRecord30(narrative.rowInsights) ?? {};
140024
+ const rowInsights = asRecord31(narrative.rowInsights) ?? {};
139938
140025
  const rowInsightCoverage = {};
139939
140026
  for (const spec of ROW_INSIGHT_SPECS) {
139940
140027
  const shown = topRowsBySpend(asArray8(tables[spec.tableKey]), spec.limit).filter(
@@ -140014,7 +140101,7 @@ async function readJsonFile8(filePath) {
140014
140101
  throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
140015
140102
  }
140016
140103
  }
140017
- function asRecord31(value) {
140104
+ function asRecord32(value) {
140018
140105
  if (value && typeof value === "object" && !Array.isArray(value)) {
140019
140106
  return value;
140020
140107
  }
@@ -140042,7 +140129,7 @@ function injectReportData7(html, payload) {
140042
140129
  }
140043
140130
  async function runYandexAnalysisRender(opts) {
140044
140131
  const dataPath = path48.resolve(opts.dataFile);
140045
- let data = asRecord31(await readJsonFile8(dataPath));
140132
+ let data = asRecord32(await readJsonFile8(dataPath));
140046
140133
  if (opts.snapshotDir?.trim()) {
140047
140134
  data = await mergeYandexAnalysisSnapshotIntoReport(data, path48.resolve(opts.snapshotDir));
140048
140135
  }
@@ -140238,7 +140325,7 @@ function unwrapYandexItems(raw) {
140238
140325
  }
140239
140326
  return [];
140240
140327
  }
140241
- function asRecord32(raw) {
140328
+ function asRecord33(raw) {
140242
140329
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
140243
140330
  }
140244
140331
 
@@ -140317,7 +140404,7 @@ async function runYandexAccountStatus(opts) {
140317
140404
  } catch (err) {
140318
140405
  fail3("\u67E5\u8BE2 Yandex \u8D26\u6237\u603B\u89C8\u5931\u8D25", err);
140319
140406
  }
140320
- const row = asRecord32(result.raw);
140407
+ const row = asRecord33(result.raw);
140321
140408
  await emitOrTable(opts, {
140322
140409
  section: "yandex-account-status",
140323
140410
  commandLabel: "yandex-analysis account-status",