siluzan-tso-cli 1.1.38 → 1.1.39-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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 num10 = +id;
262
- if (num10 >= 0 && num10 < MAX_SAFE_INTEGER) {
263
- return num10;
261
+ const num11 = +id;
262
+ if (num11 >= 0 && num11 < MAX_SAFE_INTEGER) {
263
+ return num11;
264
264
  }
265
265
  }
266
266
  return id;
@@ -3218,6 +3218,16 @@ function getResolvedWriteAuditCommit() {
3218
3218
  const env = process.env.SILUZAN_AUDIT_NOTE?.trim() ?? "";
3219
3219
  return env !== "" ? truncateCommit(env) : "";
3220
3220
  }
3221
+ function isWriteAuditCommitRequiredSatisfied() {
3222
+ if (isSiluzanAgentEnv()) return true;
3223
+ return getResolvedWriteAuditCommit() !== "";
3224
+ }
3225
+ function resolveWriteAuditCommitForRecord() {
3226
+ const c = getResolvedWriteAuditCommit();
3227
+ if (c !== "") return c;
3228
+ if (isSiluzanAgentEnv()) return WRITE_AUDIT_AGENT_AUTO_COMMIT;
3229
+ return "";
3230
+ }
3221
3231
  function getWriteAuditRunId() {
3222
3232
  const r = process.env.SILUZAN_AUDIT_RUN_ID?.trim();
3223
3233
  return r !== void 0 && r !== "" ? r : void 0;
@@ -3805,7 +3815,7 @@ async function issueApiKeyWithPhoneCode(opts) {
3805
3815
  });
3806
3816
  return apiKey;
3807
3817
  }
3808
- var import_semver, SILUZAN_DIR, CONFIG_FILE, inProcessLockChain, ALLOWED_HOSTNAME_SUFFIXES, EXACT_ID_FIELD_NAMES, DEFAULT_TIMEOUT_MS, MAX_RESPONSE_BYTES, httpsAgent, httpAgent, PERF_PREFIX, DEFAULT_DETAIL_MAX, VERBOSE_DETAIL_MAX, ASCII_TABLE_CHARS, CLI_SNAPSHOT_MANIFEST_FILE, SCHEMA_VERSION, FALLBACK_FIELD_GUIDE_REFS, _cliPackage, _resolveCliVersion, _fieldGuideRefs, OUTLINE_AGENT_HINT, TS_LIKE_ARRAY_SAMPLE_MAX, MAX_INVOCATION_CHARS, currentInvocation, MAX_COMMIT_CHARS, cliCommitOverride, WRITE_AUDIT_SCHEMA_VERSION, MUTATING, BEIJING_TZ, AUDIT_LOG_FILENAME_RE, DEFAULT_MAX_FILE_MB, MIN_SEGMENT_BYTES, MAX_SEGMENT_BYTES, DEFAULT_RETENTION_DAYS, MIN_RETENTION_DAYS, MAX_RETENTION_DAYS, PRUNE_THROTTLE_MS, lastPruneAtMs, MAX_BODY_CHARS, MAX_ERROR_CHARS, DEFAULT_FIND_AUDIT_MAX_DAYS, MAX_SNAPSHOT_FILE_BYTES, API_KEY_SERVICE_VALUES;
3818
+ var import_semver, SILUZAN_DIR, CONFIG_FILE, inProcessLockChain, ALLOWED_HOSTNAME_SUFFIXES, EXACT_ID_FIELD_NAMES, DEFAULT_TIMEOUT_MS, MAX_RESPONSE_BYTES, httpsAgent, httpAgent, PERF_PREFIX, DEFAULT_DETAIL_MAX, VERBOSE_DETAIL_MAX, ASCII_TABLE_CHARS, CLI_SNAPSHOT_MANIFEST_FILE, SCHEMA_VERSION, FALLBACK_FIELD_GUIDE_REFS, _cliPackage, _resolveCliVersion, _fieldGuideRefs, OUTLINE_AGENT_HINT, TS_LIKE_ARRAY_SAMPLE_MAX, MAX_INVOCATION_CHARS, currentInvocation, MAX_COMMIT_CHARS, WRITE_AUDIT_AGENT_AUTO_COMMIT, cliCommitOverride, WRITE_AUDIT_SCHEMA_VERSION, MUTATING, BEIJING_TZ, AUDIT_LOG_FILENAME_RE, DEFAULT_MAX_FILE_MB, MIN_SEGMENT_BYTES, MAX_SEGMENT_BYTES, DEFAULT_RETENTION_DAYS, MIN_RETENTION_DAYS, MAX_RETENTION_DAYS, PRUNE_THROTTLE_MS, lastPruneAtMs, MAX_BODY_CHARS, MAX_ERROR_CHARS, DEFAULT_FIND_AUDIT_MAX_DAYS, MAX_SNAPSHOT_FILE_BYTES, API_KEY_SERVICE_VALUES;
3809
3819
  var init_dist = __esm({
3810
3820
  "../common/dist/index.js"() {
3811
3821
  "use strict";
@@ -3897,6 +3907,7 @@ var init_dist = __esm({
3897
3907
  TS_LIKE_ARRAY_SAMPLE_MAX = 8;
3898
3908
  MAX_INVOCATION_CHARS = 4e3;
3899
3909
  MAX_COMMIT_CHARS = 2e3;
3910
+ WRITE_AUDIT_AGENT_AUTO_COMMIT = "agent:auto";
3900
3911
  WRITE_AUDIT_SCHEMA_VERSION = 2;
3901
3912
  MUTATING = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
3902
3913
  BEIJING_TZ = "Asia/Shanghai";
@@ -3926,7 +3937,7 @@ var DEFAULT_API_BASE;
3926
3937
  var init_defaults = __esm({
3927
3938
  "src/config/defaults.ts"() {
3928
3939
  "use strict";
3929
- DEFAULT_API_BASE = "https://tso-api.siluzan.com";
3940
+ DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
3930
3941
  }
3931
3942
  });
3932
3943
 
@@ -4159,15 +4170,14 @@ function isTsoArgvExemptFromWriteCommit(argv) {
4159
4170
  }
4160
4171
  function ensureWriteCommitForMutatingHttp() {
4161
4172
  if (isTsoArgvExemptFromWriteCommit(process.argv.slice(2))) return;
4162
- const c = getResolvedWriteAuditCommit();
4163
- if (c !== "") return;
4173
+ if (isWriteAuditCommitRequiredSatisfied()) return;
4164
4174
  console.error('\n\u274C \u5199\u64CD\u4F5C\u5FC5\u987B\u63D0\u4F9B\u8BF4\u660E\uFF08commit\uFF09\uFF1A\u8BF7\u4F7F\u7528 --commit "\u2026"');
4165
4175
  process.exit(1);
4166
4176
  }
4167
4177
  async function withWriteAudit(params, execute) {
4168
4178
  ensureWriteCommitForMutatingHttp();
4169
4179
  const auditId = randomUUID();
4170
- const commit = getResolvedWriteAuditCommit();
4180
+ const commit = resolveWriteAuditCommitForRecord();
4171
4181
  const runId = getWriteAuditRunId();
4172
4182
  const snap = await tryWritePreSnapshot({
4173
4183
  namespace: TSO_WRITE_AUDIT_NS,
@@ -5407,335 +5417,6 @@ var init_accounts_digest2 = __esm({
5407
5417
  }
5408
5418
  });
5409
5419
 
5410
- // src/commands/google-analysis/wrap-snapshot-envelope.ts
5411
- function coerceToRowArray(value) {
5412
- if (Array.isArray(value)) return value;
5413
- if (value === null || typeof value !== "object") return [];
5414
- const o = value;
5415
- const keys = Object.keys(o);
5416
- if (keys.length === 0) return [];
5417
- if (keys.every((k) => /^\d+$/.test(k))) {
5418
- return keys.sort((a, b) => Number(a) - Number(b)).map((k) => o[k]);
5419
- }
5420
- return [];
5421
- }
5422
- function asRecord(value) {
5423
- if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
5424
- return value;
5425
- }
5426
- function readGatewayList(bag) {
5427
- const items2 = coerceToRowArray(bag.data);
5428
- const meta = {};
5429
- if (typeof bag.code === "number") meta.code = bag.code;
5430
- if (typeof bag.message === "string") meta.message = bag.message;
5431
- return { items: items2, ...Object.keys(meta).length > 0 ? { meta } : {} };
5432
- }
5433
- function flattenMaterials(payload) {
5434
- const bag = asRecord(payload);
5435
- if (!bag) return [];
5436
- const images = coerceToRowArray(bag.images).map(
5437
- (row) => asRecord(row) ? { ...row, assetType: "image" } : row
5438
- );
5439
- const videos = coerceToRowArray(bag.videos).map(
5440
- (row) => asRecord(row) ? { ...row, assetType: "video" } : row
5441
- );
5442
- return [...images, ...videos];
5443
- }
5444
- function wrapGoogleAnalysisSnapshotPayload(section, payload) {
5445
- let items2 = [];
5446
- let record2 = null;
5447
- let meta;
5448
- if (COMPOSITE_SECTIONS.has(section)) {
5449
- items2 = flattenMaterials(payload);
5450
- } else if (RECORD_ONLY_SECTIONS.has(section)) {
5451
- record2 = asRecord(payload);
5452
- } else if (GATEWAY_LIST_SECTIONS.has(section)) {
5453
- const bag = asRecord(payload);
5454
- if (bag) {
5455
- const r = readGatewayList(bag);
5456
- items2 = r.items;
5457
- meta = r.meta;
5458
- }
5459
- } else if (ROOT_ARRAY_SECTIONS.has(section)) {
5460
- items2 = coerceToRowArray(payload);
5461
- } else {
5462
- const rowKey = ROW_KEY_BY_SECTION[section];
5463
- const bag = asRecord(payload);
5464
- if (rowKey && bag) {
5465
- items2 = coerceToRowArray(bag[rowKey]);
5466
- } else {
5467
- const arr = coerceToRowArray(payload);
5468
- if (arr.length > 0) items2 = arr;
5469
- else record2 = asRecord(payload);
5470
- }
5471
- }
5472
- return {
5473
- schemaVersion: GOOGLE_ANALYSIS_FILE_SCHEMA_VERSION,
5474
- section,
5475
- itemCount: items2.length,
5476
- items: items2,
5477
- record: record2,
5478
- ...meta !== void 0 ? { meta } : {}
5479
- };
5480
- }
5481
- var GOOGLE_ANALYSIS_FILE_SCHEMA_VERSION, ROW_KEY_BY_SECTION, RECORD_ONLY_SECTIONS, COMPOSITE_SECTIONS, ROOT_ARRAY_SECTIONS, GATEWAY_LIST_SECTIONS;
5482
- var init_wrap_snapshot_envelope = __esm({
5483
- "src/commands/google-analysis/wrap-snapshot-envelope.ts"() {
5484
- "use strict";
5485
- GOOGLE_ANALYSIS_FILE_SCHEMA_VERSION = 3;
5486
- ROW_KEY_BY_SECTION = {
5487
- keywords: "keywords",
5488
- campaigns: "campaigns",
5489
- devices: "devices",
5490
- geographic: "countries",
5491
- "geo-matched": "countries",
5492
- "campaign-geo": "countries",
5493
- "campaign-geo-matched": "countries",
5494
- "campaign-device": "devices",
5495
- audience: "audience"
5496
- };
5497
- RECORD_ONLY_SECTIONS = /* @__PURE__ */ new Set([
5498
- "overview",
5499
- "resource-counts",
5500
- "dimension-summary",
5501
- "gold-account",
5502
- "final-urls",
5503
- "campaign-types",
5504
- "ads-index"
5505
- ]);
5506
- COMPOSITE_SECTIONS = /* @__PURE__ */ new Set(["materials"]);
5507
- ROOT_ARRAY_SECTIONS = /* @__PURE__ */ new Set([
5508
- "campaign-hour",
5509
- "extensions",
5510
- "asset-images",
5511
- "videos",
5512
- "daily-metrics",
5513
- "conversion-actions"
5514
- ]);
5515
- GATEWAY_LIST_SECTIONS = /* @__PURE__ */ new Set([
5516
- "search-terms",
5517
- "ads"
5518
- ]);
5519
- }
5520
- });
5521
-
5522
- // src/utils/snapshot/dir.ts
5523
- var init_dir = __esm({
5524
- "src/utils/snapshot/dir.ts"() {
5525
- "use strict";
5526
- init_dist();
5527
- }
5528
- });
5529
-
5530
- // src/utils/snapshot/google-analysis.ts
5531
- import * as fs7 from "fs/promises";
5532
- import * as path11 from "path";
5533
- function googleAnalysisManifestFile(accountId) {
5534
- return `${applyIdSuffix("manifest", accountId)}.json`;
5535
- }
5536
- function buildOutlineHints(section) {
5537
- const hints = [...GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS];
5538
- if (RATE_BEARING_SECTIONS.has(section)) {
5539
- hints.push(...GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS);
5540
- }
5541
- if (section === "campaigns") {
5542
- hints.push(
5543
- ...GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS,
5544
- ...GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS
5545
- );
5546
- }
5547
- if (section === "ads") {
5548
- hints.push(...GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS);
5549
- }
5550
- const zhHints = GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION[section];
5551
- if (zhHints) hints.push(...zhHints);
5552
- return hints.length > 0 ? hints : void 0;
5553
- }
5554
- function googleAnalysisSectionFilename(section, accountId, dateRange) {
5555
- if (!/^[a-z0-9-]+$/.test(section)) {
5556
- throw new Error(`\u975E\u6CD5\u5B50\u547D\u4EE4\u540D\uFF0C\u65E0\u6CD5\u751F\u6210\u6587\u4EF6\u540D\uFF1A${section}`);
5557
- }
5558
- let stem = applyIdSuffix(section, accountId);
5559
- if (dateRange?.mode === "range") {
5560
- const start = dateRange.start.replace(/-/g, "");
5561
- const end = dateRange.end.replace(/-/g, "");
5562
- stem = `${stem}_${start}-${end}`;
5563
- }
5564
- return `${stem}.json`;
5565
- }
5566
- async function readManifestIfExists(absDir, accountId) {
5567
- const candidates = [];
5568
- const suffixed = googleAnalysisManifestFile(accountId);
5569
- candidates.push(suffixed);
5570
- if (suffixed !== LEGACY_MANIFEST_FILE) {
5571
- candidates.push(LEGACY_MANIFEST_FILE);
5572
- }
5573
- for (const name2 of candidates) {
5574
- const manifestPath = path11.join(absDir, name2);
5575
- try {
5576
- const raw = await fs7.readFile(manifestPath, "utf8");
5577
- const parsed = JSON.parse(raw);
5578
- const sv = parsed?.schemaVersion;
5579
- if (sv !== 2 && sv !== SCHEMA_VERSION2 || !Array.isArray(parsed.artifacts)) {
5580
- continue;
5581
- }
5582
- return parsed;
5583
- } catch {
5584
- continue;
5585
- }
5586
- }
5587
- return null;
5588
- }
5589
- function mergeGoogleAnalysisManifest(existing, params) {
5590
- const prevArts = existing?.artifacts ?? [];
5591
- const without = prevArts.filter((a) => a.file !== params.newArtifact.file);
5592
- const artifacts = [...without, params.newArtifact];
5593
- const fieldGuide = existing?.fieldGuide ?? DEFAULT_FIELD_GUIDE;
5594
- return {
5595
- schemaVersion: SCHEMA_VERSION2,
5596
- cliPackage: CLI_PACKAGE,
5597
- cliVersion: params.cliVersion,
5598
- googleAnalysis: {
5599
- accountId: params.accountId,
5600
- dateRange: params.dateRange
5601
- },
5602
- fieldGuide: {
5603
- markdownRefs: [...fieldGuide.markdownRefs],
5604
- tsTypesModule: fieldGuide.tsTypesModule
5605
- },
5606
- artifacts
5607
- };
5608
- }
5609
- async function writeGoogleAnalysisSnapshot(params) {
5610
- const absDir = await ensureSnapshotDir(params.snapshotDir);
5611
- const accountSlug = slugifyIdSuffix(params.accountId);
5612
- const fileName = googleAnalysisSectionFilename(
5613
- params.section,
5614
- accountSlug || void 0,
5615
- params.dateRange
5616
- );
5617
- const relativeFile = fileName;
5618
- const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
5619
- const wrapped = wrapGoogleAnalysisSnapshotPayload(
5620
- params.section,
5621
- params.payload
5622
- );
5623
- const normalizedWrapped = stringifyIdsDeep(wrapped);
5624
- const body = JSON.stringify(normalizedWrapped, null, 2);
5625
- await fs7.writeFile(path11.join(absDir, fileName), `${body}
5626
- `, "utf8");
5627
- const outlineFileName = snapshotOutlineFileName(fileName);
5628
- const outlineExtra = buildOutlineHints(params.section);
5629
- const outlineBody = formatOutlineFileBody(fileName, normalizedWrapped, outlineExtra);
5630
- await fs7.writeFile(path11.join(absDir, outlineFileName), `${outlineBody}
5631
- `, "utf8");
5632
- const existing = await readManifestIfExists(absDir, accountSlug || void 0);
5633
- const newArtifact = {
5634
- section: params.section,
5635
- file: relativeFile,
5636
- writtenAt,
5637
- ...params.endpointHint !== void 0 ? { endpointHint: params.endpointHint } : {}
5638
- };
5639
- const merged = mergeGoogleAnalysisManifest(existing, {
5640
- accountId: params.accountId,
5641
- dateRange: params.dateRange,
5642
- cliVersion: params.cliVersion,
5643
- newArtifact
5644
- });
5645
- const manifestFile = googleAnalysisManifestFile(accountSlug || void 0);
5646
- await fs7.writeFile(
5647
- path11.join(absDir, manifestFile),
5648
- `${JSON.stringify(stringifyIdsDeep(merged), null, 2)}
5649
- `,
5650
- "utf8"
5651
- );
5652
- return {
5653
- kind: "siluzan-tso-google-analysis-snapshot",
5654
- schemaVersion: SCHEMA_VERSION2,
5655
- absoluteSnapshotDir: absDir,
5656
- manifestFile,
5657
- writtenFiles: [fileName, outlineFileName],
5658
- section: params.section,
5659
- outlineFile: outlineFileName,
5660
- agentHint: OUTLINE_AGENT_HINT
5661
- };
5662
- }
5663
- var LEGACY_MANIFEST_FILE, CLI_PACKAGE, SCHEMA_VERSION2, DEFAULT_FIELD_GUIDE, GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS, RATE_BEARING_SECTIONS, GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS, GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS, GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION;
5664
- var init_google_analysis = __esm({
5665
- "src/utils/snapshot/google-analysis.ts"() {
5666
- "use strict";
5667
- init_cli_json();
5668
- init_wrap_snapshot_envelope();
5669
- init_dir();
5670
- LEGACY_MANIFEST_FILE = "manifest.json";
5671
- CLI_PACKAGE = "siluzan-tso-cli";
5672
- SCHEMA_VERSION2 = 3;
5673
- DEFAULT_FIELD_GUIDE = {
5674
- markdownRefs: ["references/accounts/currency.md", "references/analytics/account-analytics.md"],
5675
- tsTypesModule: "tso-cli/src/types/google-analysis-api.ts"
5676
- };
5677
- GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS = [
5678
- "// \u91D1\u989D\uFF1A`campaigns[].budgetAmountYuan` / `campaignTargetCpaYuan` / `maximizeConversionsTargetCpaYuan` \u5747\u4E3A**\u5143**\uFF08CLI \u51FA\u53E3\u7EDF\u4E00\uFF09\uFF1B\u540C\u884C `spend` / `averageCpc` / `costPerConversion` \u4E5F\u662F\u5143\u3002",
5679
- "// \u65E7\u5B57\u6BB5 `budgetAmount`\uFF08\u5206\uFF09/ `*Micros`\uFF08\u5FAE\u5143\uFF09\u5DF2\u4E0D\u518D\u843D\u76D8\uFF0C**\u7981\u6B62**\u518D\u505A \xF7100 / \xF71_000_000 \u6362\u7B97\u3002",
5680
- "// \u72B6\u6001\uFF1A\u6BCF\u884C\u542B `campaignStatusDisplay`\uFF08\u6295\u653E\u4E2D/\u5DF2\u6682\u505C/\u5DF2\u79FB\u9664\uFF09\uFF1B\u62A5\u544A\u72B6\u6001\u5217**\u76F4\u63A5\u8BFB\u6B64\u5B57\u6BB5**\uFF0C\u7981\u6B62\u5BF9 `campaignStatus` \u5199 `MAP[s]||'\u5DF2\u79FB\u9664'`\u3002"
5681
- ];
5682
- GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS = [
5683
- "// \u91D1\u989D\uFF1A`ads[].spend` / `averageCpc` / `costPerConversion` \u843D\u76D8\u524D\u5DF2\u7EDF\u4E00\u4E3A**\u5143**\uFF08`costPerConversion` \u7531\u7F51\u5173\u5FAE\u5143 \xF71e6\uFF09\uFF1B**\u7981\u6B62**\u518D\u5BF9 `costPerConversion` \u505A \xF71_000_000\u3002"
5684
- ];
5685
- GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS = [
5686
- "// \u7ADE\u4E89\u6307\u6807\uFF1A`campaigns[].competitiveMetrics` \u4E3A **0~1 \u5C0F\u6570**\uFF08\u5982 `0.0999` = 9.99%\uFF09\uFF0C\u542B Top/AbsoluteTop/Content/ClickShare \u7B49\uFF1B\u65E0\u6570\u636E\u65F6\u4E3A `null`\u3002",
5687
- "// \u884C\u9876 `searchImpressionShare` / `searchBudgetLostImpressionShare` / `searchRankLostImpressionShare` \u843D\u76D8\u524D\u5DF2\u5F52\u4E00\u4E3A **0~1 \u5C0F\u6570**\uFF08\u4E0E `competitiveMetrics` \u540C\u540D\u9879\u4E00\u81F4\uFF09\uFF1B\u5C55\u793A\u4E3A % \u65F6 \xD7100\u3002",
5688
- "// `keywords[]` \u884C\u9876 3 \u9879\u4EFD\u989D\u540C\u6837\u4E3A 0~1 \u5C0F\u6570\uFF08\u65E0 `competitiveMetrics` \u65F6\u7531\u767E\u5206\u6570 \xF7100\uFF09\u3002"
5689
- ];
5690
- RATE_BEARING_SECTIONS = /* @__PURE__ */ new Set([
5691
- "overview",
5692
- "keywords",
5693
- "search-terms",
5694
- "campaigns",
5695
- "ads",
5696
- "extensions",
5697
- "devices",
5698
- "geographic",
5699
- "geo-matched",
5700
- "campaign-geo",
5701
- "campaign-geo-matched",
5702
- "campaign-device",
5703
- "audience",
5704
- "asset-images",
5705
- "videos",
5706
- "materials",
5707
- "daily-metrics",
5708
- "dimension-summary",
5709
- "ads-index"
5710
- ]);
5711
- GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS = [
5712
- "// \u6982\u7387\u5B57\u6BB5\uFF1A`ctr` / `conversionRate` \u81EA manifest schemaVersion 2\uFF082026-05\uFF09\u8D77\u5DF2\u7531 CLI \u7EDF\u4E00\u5F52\u4E00\u4E3A **0~1 \u5C0F\u6570**\uFF08\u5982 `0.0964` = 9.64%\uFF09\u3002",
5713
- "// \u5199 Excel 0~1 \u5C0F\u6570\u5217\uFF1A\u76F4\u63A5\u5199\u5165\uFF1B\u5199\u300Cx%\u300D\u6587\u6848\uFF1A`(v * 100).toFixed(2) + '%'`\uFF1B**\u7981\u6B62**\u518D \xF7100\u3002`interactionRate` \u4ECD\u662F\u5B57\u7B26\u4E32 0~1 \u5C0F\u6570\uFF0C`parseFloat` \u540E\u4F7F\u7528\u3002"
5714
- ];
5715
- GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS = [
5716
- "// \u7EDF\u4E00\u8BBF\u95EE\u5C42\uFF08manifest / \u6587\u4EF6\u5185\u5747\u4E3A `schemaVersion: 3`\uFF09\uFF1A\u5224\u522B\u53EA\u6709\u4E00\u6761\u2014\u2014`record` \u975E null \u5373**\u6C47\u603B\u7EF4\u5EA6**\uFF08overview/resource-counts/gold-account/ads-index/dimension-summary/final-urls/campaign-types\uFF09\uFF0C\u8BFB `record`\uFF1B\u5426\u5219\u8BFB `items[]`\uFF08\u6240\u6709\u5217\u8868\u7EF4\u5EA6\uFF0C\u542B materials\uFF0C\u884C\u5DF2\u7EDF\u4E00\u642C\u5165\uFF0C`itemCount` = `items.length`\uFF09\u3002",
5717
- "// \u7EF4\u5EA6\u4E13\u5C5E\u539F\u952E\u540D\uFF08keywords/campaigns/countries/data \u7B49\uFF09\u5DF2**\u79FB\u9664**\uFF0C\u4E0D\u8981\u518D\u6309\u7EF4\u5EA6\u731C\u952E\u540D\uFF1B`search-terms`/`ads` \u7684\u7F51\u5173 `code`/`message` \u5728 `meta`\uFF08\u4E0E\u6570\u636E\u65E0\u5173\uFF09\uFF1Bmaterials \u884C\u5E26 `assetType: 'image'|'video'`\u3002"
5718
- ];
5719
- GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION = {
5720
- keywords: [
5721
- "// \u4E2D\u6587\u8BD1\u540D\uFF1A`keywordMatchTypeZh`\uFF08\u7531 `keywordMatchType` \u7ECF match-type-en2zh.json \u7FFB\u8BD1\uFF0C\u8986\u76D6 BROAD/PHRASE/EXACT \u4E0E Broad/Phrase/Exact \u5927\u5C0F\u5199\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
5722
- ],
5723
- "search-terms": [
5724
- "// \u4E2D\u6587\u8BD1\u540D\uFF1A`matchTypeZh`\uFF08\u7531 `matchType` \u7ECF match-type-en2zh.json \u7FFB\u8BD1\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
5725
- ],
5726
- "campaign-geo": [
5727
- "// \u4E2D\u6587\u8BD1\u540D\uFF1A`countryOrRegionZh`\uFF08\u7531 `countryOrRegion` \u7ECF geo-en2zh.json \u7FFB\u8BD1\uFF0C\u76EE\u524D\u8986\u76D6 105 \u4E2A\u56FD\u5BB6/\u5730\u533A\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
5728
- ],
5729
- "campaign-geo-matched": [
5730
- "// \u4E2D\u6587\u8BD1\u540D\uFF1A`countryOrRegionZh`\uFF08\u7531 `countryOrRegion` \u7ECF geo-en2zh.json \u7FFB\u8BD1\uFF0C\u76EE\u524D\u8986\u76D6 105 \u4E2A\u56FD\u5BB6/\u5730\u533A\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
5731
- ],
5732
- "geo-matched": [
5733
- "// \u4E2D\u6587\u8BD1\u540D\uFF1A`countryOrRegionZh`\uFF08\u7531 `countryOrRegion` \u7ECF geo-en2zh.json \u7FFB\u8BD1\uFF0C\u76EE\u524D\u8986\u76D6 105 \u4E2A\u56FD\u5BB6/\u5730\u533A\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
5734
- ]
5735
- };
5736
- }
5737
- });
5738
-
5739
5420
  // src/utils/strip-legacy-google-fields.ts
5740
5421
  function stripLegacyGoogleFieldsIfV2Present(value) {
5741
5422
  return walk(value);
@@ -5773,11 +5454,147 @@ var init_strip_legacy_google_fields = __esm({
5773
5454
  }
5774
5455
  });
5775
5456
 
5776
- // src/utils/google-analysis-snapshot.ts
5777
- var init_google_analysis_snapshot = __esm({
5778
- "src/utils/google-analysis-snapshot.ts"() {
5457
+ // src/utils/http-retry.ts
5458
+ function classifyError(err) {
5459
+ const status = parseHttpStatusFromError(err);
5460
+ if (status === 401) return { class: "abort", status };
5461
+ if (status === void 0) {
5462
+ return { class: "retryable" };
5463
+ }
5464
+ if (status === 408 || status === 429) return { class: "retryable", status };
5465
+ if (status >= 500 && status <= 599) return { class: "retryable", status };
5466
+ if (status >= 400 && status <= 499) return { class: "permanent", status };
5467
+ return { class: "permanent", status };
5468
+ }
5469
+ function computeBackoffMs(attempt, policy) {
5470
+ const exp = policy.baseMs * 2 ** Math.max(0, attempt - 1);
5471
+ const capped = Math.min(exp, policy.maxBackoffMs);
5472
+ const jitter = Math.floor(Math.random() * policy.baseMs);
5473
+ return capped + jitter;
5474
+ }
5475
+ function sleep2(ms, signal) {
5476
+ return new Promise((resolve21, reject) => {
5477
+ if (signal?.aborted) {
5478
+ reject(new Error("retry sleep aborted"));
5479
+ return;
5480
+ }
5481
+ const timer = setTimeout(() => {
5482
+ signal?.removeEventListener("abort", onAbort);
5483
+ resolve21();
5484
+ }, ms);
5485
+ const onAbort = () => {
5486
+ clearTimeout(timer);
5487
+ reject(new Error("retry sleep aborted"));
5488
+ };
5489
+ signal?.addEventListener("abort", onAbort, { once: true });
5490
+ });
5491
+ }
5492
+ async function withTaskTimeout(factory, timeoutMs, label) {
5493
+ if (!timeoutMs || timeoutMs <= 0) return factory();
5494
+ let timer;
5495
+ try {
5496
+ return await Promise.race([
5497
+ factory(),
5498
+ new Promise((_, reject) => {
5499
+ timer = setTimeout(() => {
5500
+ reject(new Error(`\u8BF7\u6C42\u8D85\u65F6\uFF08${(timeoutMs / 1e3).toFixed(0)} \u79D2\uFF09\uFF1A${label}`));
5501
+ }, timeoutMs);
5502
+ })
5503
+ ]);
5504
+ } finally {
5505
+ if (timer) clearTimeout(timer);
5506
+ }
5507
+ }
5508
+ async function fetchWithRetry(factory, options) {
5509
+ const policy = { ...DEFAULT_RETRY_POLICY, ...options.policy ?? {} };
5510
+ let lastErr;
5511
+ let lastStatus;
5512
+ for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
5513
+ if (options.signal?.aborted) {
5514
+ throw new AbortRunError({
5515
+ message: `\u4EFB\u52A1\u88AB\u5916\u90E8\u4FE1\u53F7\u4E2D\u6B62\uFF1A${options.label}`
5516
+ });
5517
+ }
5518
+ try {
5519
+ return await withTaskTimeout(factory, policy.taskTimeoutMs, options.label);
5520
+ } catch (raw) {
5521
+ const err = raw instanceof Error ? raw : new Error(String(raw));
5522
+ const cls = classifyError(err);
5523
+ lastErr = err;
5524
+ lastStatus = cls.status;
5525
+ if (cls.class === "abort") {
5526
+ throw new AbortRunError({
5527
+ message: `Token \u5931\u6548\u6216\u81F4\u547D\u9519\u8BEF\uFF08${options.label}\uFF09\uFF1A${err.message}`,
5528
+ status: cls.status,
5529
+ cause: err
5530
+ });
5531
+ }
5532
+ if (cls.class === "permanent" || attempt >= policy.maxAttempts) {
5533
+ throw new HttpRetryError({
5534
+ message: cls.class === "permanent" ? `\u6C38\u4E45\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}` : `\u91CD\u8BD5 ${attempt} \u6B21\u4ECD\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}`,
5535
+ class: cls.class === "permanent" ? "permanent" : "retryable",
5536
+ status: cls.status,
5537
+ attempts: attempt,
5538
+ cause: err
5539
+ });
5540
+ }
5541
+ const backoffMs = computeBackoffMs(attempt, policy);
5542
+ options.onRetry?.({ attempt, backoffMs, status: cls.status, error: err });
5543
+ try {
5544
+ await sleep2(backoffMs, options.signal);
5545
+ } catch {
5546
+ throw new AbortRunError({
5547
+ message: `\u91CD\u8BD5\u7B49\u5F85\u88AB\u4E2D\u6B62\uFF1A${options.label}`,
5548
+ cause: err
5549
+ });
5550
+ }
5551
+ }
5552
+ }
5553
+ throw new HttpRetryError({
5554
+ message: `\u672A\u77E5\u91CD\u8BD5\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${lastErr?.message ?? "no error"}`,
5555
+ class: "retryable",
5556
+ status: lastStatus,
5557
+ attempts: policy.maxAttempts,
5558
+ cause: lastErr
5559
+ });
5560
+ }
5561
+ var HttpRetryError, AbortRunError, DEFAULT_RETRY_POLICY;
5562
+ var init_http_retry = __esm({
5563
+ "src/utils/http-retry.ts"() {
5779
5564
  "use strict";
5780
- init_google_analysis();
5565
+ init_dist();
5566
+ HttpRetryError = class extends Error {
5567
+ class;
5568
+ status;
5569
+ attempts;
5570
+ cause;
5571
+ constructor(opts) {
5572
+ super(opts.message);
5573
+ this.name = "HttpRetryError";
5574
+ this.class = opts.class;
5575
+ this.status = opts.status;
5576
+ this.attempts = opts.attempts;
5577
+ this.cause = opts.cause;
5578
+ }
5579
+ };
5580
+ AbortRunError = class extends HttpRetryError {
5581
+ constructor(opts) {
5582
+ super({
5583
+ message: opts.message,
5584
+ class: "abort",
5585
+ status: opts.status,
5586
+ attempts: 1,
5587
+ cause: opts.cause
5588
+ });
5589
+ this.name = "AbortRunError";
5590
+ }
5591
+ };
5592
+ DEFAULT_RETRY_POLICY = {
5593
+ maxAttempts: 3,
5594
+ baseMs: 1e3,
5595
+ maxBackoffMs: 3e4,
5596
+ taskTimeoutMs: 6e4
5597
+ };
5781
5598
  }
5782
5599
  });
5783
5600
 
@@ -5952,150 +5769,6 @@ var init_sections = __esm({
5952
5769
  }
5953
5770
  });
5954
5771
 
5955
- // src/utils/http-retry.ts
5956
- function classifyError(err) {
5957
- const status = parseHttpStatusFromError(err);
5958
- if (status === 401) return { class: "abort", status };
5959
- if (status === void 0) {
5960
- return { class: "retryable" };
5961
- }
5962
- if (status === 408 || status === 429) return { class: "retryable", status };
5963
- if (status >= 500 && status <= 599) return { class: "retryable", status };
5964
- if (status >= 400 && status <= 499) return { class: "permanent", status };
5965
- return { class: "permanent", status };
5966
- }
5967
- function computeBackoffMs(attempt, policy) {
5968
- const exp = policy.baseMs * 2 ** Math.max(0, attempt - 1);
5969
- const capped = Math.min(exp, policy.maxBackoffMs);
5970
- const jitter = Math.floor(Math.random() * policy.baseMs);
5971
- return capped + jitter;
5972
- }
5973
- function sleep3(ms, signal) {
5974
- return new Promise((resolve21, reject) => {
5975
- if (signal?.aborted) {
5976
- reject(new Error("retry sleep aborted"));
5977
- return;
5978
- }
5979
- const timer = setTimeout(() => {
5980
- signal?.removeEventListener("abort", onAbort);
5981
- resolve21();
5982
- }, ms);
5983
- const onAbort = () => {
5984
- clearTimeout(timer);
5985
- reject(new Error("retry sleep aborted"));
5986
- };
5987
- signal?.addEventListener("abort", onAbort, { once: true });
5988
- });
5989
- }
5990
- async function withTaskTimeout(factory, timeoutMs, label) {
5991
- if (!timeoutMs || timeoutMs <= 0) return factory();
5992
- let timer;
5993
- try {
5994
- return await Promise.race([
5995
- factory(),
5996
- new Promise((_, reject) => {
5997
- timer = setTimeout(() => {
5998
- reject(new Error(`\u8BF7\u6C42\u8D85\u65F6\uFF08${(timeoutMs / 1e3).toFixed(0)} \u79D2\uFF09\uFF1A${label}`));
5999
- }, timeoutMs);
6000
- })
6001
- ]);
6002
- } finally {
6003
- if (timer) clearTimeout(timer);
6004
- }
6005
- }
6006
- async function fetchWithRetry(factory, options) {
6007
- const policy = { ...DEFAULT_RETRY_POLICY, ...options.policy ?? {} };
6008
- let lastErr;
6009
- let lastStatus;
6010
- for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
6011
- if (options.signal?.aborted) {
6012
- throw new AbortRunError({
6013
- message: `\u4EFB\u52A1\u88AB\u5916\u90E8\u4FE1\u53F7\u4E2D\u6B62\uFF1A${options.label}`
6014
- });
6015
- }
6016
- try {
6017
- return await withTaskTimeout(factory, policy.taskTimeoutMs, options.label);
6018
- } catch (raw) {
6019
- const err = raw instanceof Error ? raw : new Error(String(raw));
6020
- const cls = classifyError(err);
6021
- lastErr = err;
6022
- lastStatus = cls.status;
6023
- if (cls.class === "abort") {
6024
- throw new AbortRunError({
6025
- message: `Token \u5931\u6548\u6216\u81F4\u547D\u9519\u8BEF\uFF08${options.label}\uFF09\uFF1A${err.message}`,
6026
- status: cls.status,
6027
- cause: err
6028
- });
6029
- }
6030
- if (cls.class === "permanent" || attempt >= policy.maxAttempts) {
6031
- throw new HttpRetryError({
6032
- message: cls.class === "permanent" ? `\u6C38\u4E45\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}` : `\u91CD\u8BD5 ${attempt} \u6B21\u4ECD\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}`,
6033
- class: cls.class === "permanent" ? "permanent" : "retryable",
6034
- status: cls.status,
6035
- attempts: attempt,
6036
- cause: err
6037
- });
6038
- }
6039
- const backoffMs = computeBackoffMs(attempt, policy);
6040
- options.onRetry?.({ attempt, backoffMs, status: cls.status, error: err });
6041
- try {
6042
- await sleep3(backoffMs, options.signal);
6043
- } catch {
6044
- throw new AbortRunError({
6045
- message: `\u91CD\u8BD5\u7B49\u5F85\u88AB\u4E2D\u6B62\uFF1A${options.label}`,
6046
- cause: err
6047
- });
6048
- }
6049
- }
6050
- }
6051
- throw new HttpRetryError({
6052
- message: `\u672A\u77E5\u91CD\u8BD5\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${lastErr?.message ?? "no error"}`,
6053
- class: "retryable",
6054
- status: lastStatus,
6055
- attempts: policy.maxAttempts,
6056
- cause: lastErr
6057
- });
6058
- }
6059
- var HttpRetryError, AbortRunError, DEFAULT_RETRY_POLICY;
6060
- var init_http_retry = __esm({
6061
- "src/utils/http-retry.ts"() {
6062
- "use strict";
6063
- init_dist();
6064
- HttpRetryError = class extends Error {
6065
- class;
6066
- status;
6067
- attempts;
6068
- cause;
6069
- constructor(opts) {
6070
- super(opts.message);
6071
- this.name = "HttpRetryError";
6072
- this.class = opts.class;
6073
- this.status = opts.status;
6074
- this.attempts = opts.attempts;
6075
- this.cause = opts.cause;
6076
- }
6077
- };
6078
- AbortRunError = class extends HttpRetryError {
6079
- constructor(opts) {
6080
- super({
6081
- message: opts.message,
6082
- class: "abort",
6083
- status: opts.status,
6084
- attempts: 1,
6085
- cause: opts.cause
6086
- });
6087
- this.name = "AbortRunError";
6088
- }
6089
- };
6090
- DEFAULT_RETRY_POLICY = {
6091
- maxAttempts: 3,
6092
- baseMs: 1e3,
6093
- maxBackoffMs: 3e4,
6094
- taskTimeoutMs: 6e4
6095
- };
6096
- }
6097
- });
6098
-
6099
5772
  // src/commands/google-analysis/normalize-impression-shares.ts
6100
5773
  function normalizeShareValue(value, competitiveMetrics, field) {
6101
5774
  const cmVal = competitiveMetrics?.[field];
@@ -101811,7 +101484,7 @@ var init_daily_metrics_range = __esm({
101811
101484
  });
101812
101485
 
101813
101486
  // src/commands/google-analysis/fetch.ts
101814
- function defaultDateRange3() {
101487
+ function defaultDateRange2() {
101815
101488
  const end = /* @__PURE__ */ new Date();
101816
101489
  end.setDate(end.getDate() - 1);
101817
101490
  const start = new Date(end);
@@ -101821,7 +101494,7 @@ function defaultDateRange3() {
101821
101494
  }
101822
101495
  function resolveDateRange(start, end) {
101823
101496
  if (start && end) return { startDate: start, endDate: end };
101824
- if (!start && !end) return defaultDateRange3();
101497
+ if (!start && !end) return defaultDateRange2();
101825
101498
  console.error("\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u8FD1 7 \u5929\uFF08\u622A\u81F3\u6628\u5929\uFF09\u3002\n");
101826
101499
  process.exit(1);
101827
101500
  }
@@ -102132,6 +101805,343 @@ var init_fetch = __esm({
102132
101805
  }
102133
101806
  });
102134
101807
 
101808
+ // src/commands/google-analysis/wrap-snapshot-envelope.ts
101809
+ function coerceToRowArray(value) {
101810
+ if (Array.isArray(value)) return value;
101811
+ if (value === null || typeof value !== "object") return [];
101812
+ const o = value;
101813
+ const keys = Object.keys(o);
101814
+ if (keys.length === 0) return [];
101815
+ if (keys.every((k) => /^\d+$/.test(k))) {
101816
+ return keys.sort((a, b) => Number(a) - Number(b)).map((k) => o[k]);
101817
+ }
101818
+ return [];
101819
+ }
101820
+ function asRecord(value) {
101821
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
101822
+ return value;
101823
+ }
101824
+ function readGatewayList(bag) {
101825
+ const items2 = coerceToRowArray(bag.data);
101826
+ const meta = {};
101827
+ if (typeof bag.code === "number") meta.code = bag.code;
101828
+ if (typeof bag.message === "string") meta.message = bag.message;
101829
+ return { items: items2, ...Object.keys(meta).length > 0 ? { meta } : {} };
101830
+ }
101831
+ function flattenMaterials(payload) {
101832
+ const bag = asRecord(payload);
101833
+ if (!bag) return [];
101834
+ const images = coerceToRowArray(bag.images).map(
101835
+ (row) => asRecord(row) ? { ...row, assetType: "image" } : row
101836
+ );
101837
+ const videos = coerceToRowArray(bag.videos).map(
101838
+ (row) => asRecord(row) ? { ...row, assetType: "video" } : row
101839
+ );
101840
+ return [...images, ...videos];
101841
+ }
101842
+ function wrapGoogleAnalysisSnapshotPayload(section, payload) {
101843
+ let items2 = [];
101844
+ let record2 = null;
101845
+ let meta;
101846
+ if (COMPOSITE_SECTIONS.has(section)) {
101847
+ items2 = flattenMaterials(payload);
101848
+ } else if (RECORD_ONLY_SECTIONS.has(section)) {
101849
+ record2 = asRecord(payload);
101850
+ } else if (GATEWAY_LIST_SECTIONS.has(section)) {
101851
+ const bag = asRecord(payload);
101852
+ if (bag) {
101853
+ const r = readGatewayList(bag);
101854
+ items2 = r.items;
101855
+ meta = r.meta;
101856
+ }
101857
+ } else if (ROOT_ARRAY_SECTIONS.has(section)) {
101858
+ items2 = coerceToRowArray(payload);
101859
+ } else {
101860
+ const rowKey = ROW_KEY_BY_SECTION[section];
101861
+ const bag = asRecord(payload);
101862
+ if (rowKey && bag) {
101863
+ items2 = coerceToRowArray(bag[rowKey]);
101864
+ } else {
101865
+ const arr = coerceToRowArray(payload);
101866
+ if (arr.length > 0) items2 = arr;
101867
+ else record2 = asRecord(payload);
101868
+ }
101869
+ }
101870
+ return {
101871
+ schemaVersion: GOOGLE_ANALYSIS_FILE_SCHEMA_VERSION,
101872
+ section,
101873
+ itemCount: items2.length,
101874
+ items: items2,
101875
+ record: record2,
101876
+ ...meta !== void 0 ? { meta } : {}
101877
+ };
101878
+ }
101879
+ var GOOGLE_ANALYSIS_FILE_SCHEMA_VERSION, ROW_KEY_BY_SECTION, RECORD_ONLY_SECTIONS, COMPOSITE_SECTIONS, ROOT_ARRAY_SECTIONS, GATEWAY_LIST_SECTIONS;
101880
+ var init_wrap_snapshot_envelope = __esm({
101881
+ "src/commands/google-analysis/wrap-snapshot-envelope.ts"() {
101882
+ "use strict";
101883
+ GOOGLE_ANALYSIS_FILE_SCHEMA_VERSION = 3;
101884
+ ROW_KEY_BY_SECTION = {
101885
+ keywords: "keywords",
101886
+ campaigns: "campaigns",
101887
+ devices: "devices",
101888
+ geographic: "countries",
101889
+ "geo-matched": "countries",
101890
+ "campaign-geo": "countries",
101891
+ "campaign-geo-matched": "countries",
101892
+ "campaign-device": "devices",
101893
+ audience: "audience"
101894
+ };
101895
+ RECORD_ONLY_SECTIONS = /* @__PURE__ */ new Set([
101896
+ "overview",
101897
+ "resource-counts",
101898
+ "dimension-summary",
101899
+ "gold-account",
101900
+ "final-urls",
101901
+ "campaign-types",
101902
+ "ads-index"
101903
+ ]);
101904
+ COMPOSITE_SECTIONS = /* @__PURE__ */ new Set(["materials"]);
101905
+ ROOT_ARRAY_SECTIONS = /* @__PURE__ */ new Set([
101906
+ "campaign-hour",
101907
+ "extensions",
101908
+ "asset-images",
101909
+ "videos",
101910
+ "daily-metrics",
101911
+ "conversion-actions"
101912
+ ]);
101913
+ GATEWAY_LIST_SECTIONS = /* @__PURE__ */ new Set([
101914
+ "search-terms",
101915
+ "ads"
101916
+ ]);
101917
+ }
101918
+ });
101919
+
101920
+ // src/utils/snapshot/dir.ts
101921
+ var init_dir = __esm({
101922
+ "src/utils/snapshot/dir.ts"() {
101923
+ "use strict";
101924
+ init_dist();
101925
+ }
101926
+ });
101927
+
101928
+ // src/utils/snapshot/google-analysis.ts
101929
+ import * as fs7 from "fs/promises";
101930
+ import * as path11 from "path";
101931
+ function googleAnalysisManifestFile(accountId) {
101932
+ return `${applyIdSuffix("manifest", accountId)}.json`;
101933
+ }
101934
+ function buildOutlineHints(section) {
101935
+ const hints = [...GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS];
101936
+ if (RATE_BEARING_SECTIONS.has(section)) {
101937
+ hints.push(...GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS);
101938
+ }
101939
+ if (section === "campaigns") {
101940
+ hints.push(
101941
+ ...GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS,
101942
+ ...GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS
101943
+ );
101944
+ }
101945
+ if (section === "ads") {
101946
+ hints.push(...GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS);
101947
+ }
101948
+ const zhHints = GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION[section];
101949
+ if (zhHints) hints.push(...zhHints);
101950
+ return hints.length > 0 ? hints : void 0;
101951
+ }
101952
+ function googleAnalysisSectionFilename(section, accountId, dateRange) {
101953
+ if (!/^[a-z0-9-]+$/.test(section)) {
101954
+ throw new Error(`\u975E\u6CD5\u5B50\u547D\u4EE4\u540D\uFF0C\u65E0\u6CD5\u751F\u6210\u6587\u4EF6\u540D\uFF1A${section}`);
101955
+ }
101956
+ let stem = applyIdSuffix(section, accountId);
101957
+ if (dateRange?.mode === "range") {
101958
+ const start = dateRange.start.replace(/-/g, "");
101959
+ const end = dateRange.end.replace(/-/g, "");
101960
+ stem = `${stem}_${start}-${end}`;
101961
+ }
101962
+ return `${stem}.json`;
101963
+ }
101964
+ async function readManifestIfExists(absDir, accountId) {
101965
+ const candidates = [];
101966
+ const suffixed = googleAnalysisManifestFile(accountId);
101967
+ candidates.push(suffixed);
101968
+ if (suffixed !== LEGACY_MANIFEST_FILE) {
101969
+ candidates.push(LEGACY_MANIFEST_FILE);
101970
+ }
101971
+ for (const name2 of candidates) {
101972
+ const manifestPath = path11.join(absDir, name2);
101973
+ try {
101974
+ const raw = await fs7.readFile(manifestPath, "utf8");
101975
+ const parsed = JSON.parse(raw);
101976
+ const sv = parsed?.schemaVersion;
101977
+ if (sv !== 2 && sv !== SCHEMA_VERSION2 || !Array.isArray(parsed.artifacts)) {
101978
+ continue;
101979
+ }
101980
+ return parsed;
101981
+ } catch {
101982
+ continue;
101983
+ }
101984
+ }
101985
+ return null;
101986
+ }
101987
+ function mergeGoogleAnalysisManifest(existing, params) {
101988
+ const prevArts = existing?.artifacts ?? [];
101989
+ const without = prevArts.filter((a) => a.file !== params.newArtifact.file);
101990
+ const artifacts = [...without, params.newArtifact];
101991
+ const fieldGuide = existing?.fieldGuide ?? DEFAULT_FIELD_GUIDE;
101992
+ return {
101993
+ schemaVersion: SCHEMA_VERSION2,
101994
+ cliPackage: CLI_PACKAGE,
101995
+ cliVersion: params.cliVersion,
101996
+ googleAnalysis: {
101997
+ accountId: params.accountId,
101998
+ dateRange: params.dateRange
101999
+ },
102000
+ fieldGuide: {
102001
+ markdownRefs: [...fieldGuide.markdownRefs],
102002
+ tsTypesModule: fieldGuide.tsTypesModule
102003
+ },
102004
+ artifacts
102005
+ };
102006
+ }
102007
+ async function writeGoogleAnalysisSnapshot(params) {
102008
+ const absDir = await ensureSnapshotDir(params.snapshotDir);
102009
+ const accountSlug = slugifyIdSuffix(params.accountId);
102010
+ const fileName = googleAnalysisSectionFilename(
102011
+ params.section,
102012
+ accountSlug || void 0,
102013
+ params.dateRange
102014
+ );
102015
+ const relativeFile = fileName;
102016
+ const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
102017
+ const wrapped = wrapGoogleAnalysisSnapshotPayload(
102018
+ params.section,
102019
+ params.payload
102020
+ );
102021
+ const normalizedWrapped = stringifyIdsDeep(wrapped);
102022
+ const body = JSON.stringify(normalizedWrapped, null, 2);
102023
+ await fs7.writeFile(path11.join(absDir, fileName), `${body}
102024
+ `, "utf8");
102025
+ const outlineFileName = snapshotOutlineFileName(fileName);
102026
+ const outlineExtra = buildOutlineHints(params.section);
102027
+ const outlineBody = formatOutlineFileBody(fileName, normalizedWrapped, outlineExtra);
102028
+ await fs7.writeFile(path11.join(absDir, outlineFileName), `${outlineBody}
102029
+ `, "utf8");
102030
+ const existing = await readManifestIfExists(absDir, accountSlug || void 0);
102031
+ const newArtifact = {
102032
+ section: params.section,
102033
+ file: relativeFile,
102034
+ writtenAt,
102035
+ ...params.endpointHint !== void 0 ? { endpointHint: params.endpointHint } : {}
102036
+ };
102037
+ const merged = mergeGoogleAnalysisManifest(existing, {
102038
+ accountId: params.accountId,
102039
+ dateRange: params.dateRange,
102040
+ cliVersion: params.cliVersion,
102041
+ newArtifact
102042
+ });
102043
+ const manifestFile = googleAnalysisManifestFile(accountSlug || void 0);
102044
+ await fs7.writeFile(
102045
+ path11.join(absDir, manifestFile),
102046
+ `${JSON.stringify(stringifyIdsDeep(merged), null, 2)}
102047
+ `,
102048
+ "utf8"
102049
+ );
102050
+ return {
102051
+ kind: "siluzan-tso-google-analysis-snapshot",
102052
+ schemaVersion: SCHEMA_VERSION2,
102053
+ absoluteSnapshotDir: absDir,
102054
+ manifestFile,
102055
+ writtenFiles: [fileName, outlineFileName],
102056
+ section: params.section,
102057
+ outlineFile: outlineFileName,
102058
+ agentHint: OUTLINE_AGENT_HINT
102059
+ };
102060
+ }
102061
+ var LEGACY_MANIFEST_FILE, CLI_PACKAGE, SCHEMA_VERSION2, DEFAULT_FIELD_GUIDE, GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS, RATE_BEARING_SECTIONS, GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS, GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS, GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION;
102062
+ var init_google_analysis = __esm({
102063
+ "src/utils/snapshot/google-analysis.ts"() {
102064
+ "use strict";
102065
+ init_cli_json();
102066
+ init_wrap_snapshot_envelope();
102067
+ init_dir();
102068
+ LEGACY_MANIFEST_FILE = "manifest.json";
102069
+ CLI_PACKAGE = "siluzan-tso-cli";
102070
+ SCHEMA_VERSION2 = 3;
102071
+ DEFAULT_FIELD_GUIDE = {
102072
+ markdownRefs: ["references/accounts/currency.md", "references/analytics/account-analytics.md"],
102073
+ tsTypesModule: "tso-cli/src/types/google-analysis-api.ts"
102074
+ };
102075
+ GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS = [
102076
+ "// \u91D1\u989D\uFF1A`campaigns[].budgetAmountYuan` / `campaignTargetCpaYuan` / `maximizeConversionsTargetCpaYuan` \u5747\u4E3A**\u5143**\uFF08CLI \u51FA\u53E3\u7EDF\u4E00\uFF09\uFF1B\u540C\u884C `spend` / `averageCpc` / `costPerConversion` \u4E5F\u662F\u5143\u3002",
102077
+ "// \u65E7\u5B57\u6BB5 `budgetAmount`\uFF08\u5206\uFF09/ `*Micros`\uFF08\u5FAE\u5143\uFF09\u5DF2\u4E0D\u518D\u843D\u76D8\uFF0C**\u7981\u6B62**\u518D\u505A \xF7100 / \xF71_000_000 \u6362\u7B97\u3002",
102078
+ "// \u72B6\u6001\uFF1A\u6BCF\u884C\u542B `campaignStatusDisplay`\uFF08\u6295\u653E\u4E2D/\u5DF2\u6682\u505C/\u5DF2\u79FB\u9664\uFF09\uFF1B\u62A5\u544A\u72B6\u6001\u5217**\u76F4\u63A5\u8BFB\u6B64\u5B57\u6BB5**\uFF0C\u7981\u6B62\u5BF9 `campaignStatus` \u5199 `MAP[s]||'\u5DF2\u79FB\u9664'`\u3002"
102079
+ ];
102080
+ GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS = [
102081
+ "// \u91D1\u989D\uFF1A`ads[].spend` / `averageCpc` / `costPerConversion` \u843D\u76D8\u524D\u5DF2\u7EDF\u4E00\u4E3A**\u5143**\uFF08`costPerConversion` \u7531\u7F51\u5173\u5FAE\u5143 \xF71e6\uFF09\uFF1B**\u7981\u6B62**\u518D\u5BF9 `costPerConversion` \u505A \xF71_000_000\u3002"
102082
+ ];
102083
+ GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS = [
102084
+ "// \u7ADE\u4E89\u6307\u6807\uFF1A`campaigns[].competitiveMetrics` \u4E3A **0~1 \u5C0F\u6570**\uFF08\u5982 `0.0999` = 9.99%\uFF09\uFF0C\u542B Top/AbsoluteTop/Content/ClickShare \u7B49\uFF1B\u65E0\u6570\u636E\u65F6\u4E3A `null`\u3002",
102085
+ "// \u884C\u9876 `searchImpressionShare` / `searchBudgetLostImpressionShare` / `searchRankLostImpressionShare` \u843D\u76D8\u524D\u5DF2\u5F52\u4E00\u4E3A **0~1 \u5C0F\u6570**\uFF08\u4E0E `competitiveMetrics` \u540C\u540D\u9879\u4E00\u81F4\uFF09\uFF1B\u5C55\u793A\u4E3A % \u65F6 \xD7100\u3002",
102086
+ "// `keywords[]` \u884C\u9876 3 \u9879\u4EFD\u989D\u540C\u6837\u4E3A 0~1 \u5C0F\u6570\uFF08\u65E0 `competitiveMetrics` \u65F6\u7531\u767E\u5206\u6570 \xF7100\uFF09\u3002"
102087
+ ];
102088
+ RATE_BEARING_SECTIONS = /* @__PURE__ */ new Set([
102089
+ "overview",
102090
+ "keywords",
102091
+ "search-terms",
102092
+ "campaigns",
102093
+ "ads",
102094
+ "extensions",
102095
+ "devices",
102096
+ "geographic",
102097
+ "geo-matched",
102098
+ "campaign-geo",
102099
+ "campaign-geo-matched",
102100
+ "campaign-device",
102101
+ "audience",
102102
+ "asset-images",
102103
+ "videos",
102104
+ "materials",
102105
+ "daily-metrics",
102106
+ "dimension-summary",
102107
+ "ads-index"
102108
+ ]);
102109
+ GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS = [
102110
+ "// \u6982\u7387\u5B57\u6BB5\uFF1A`ctr` / `conversionRate` \u81EA manifest schemaVersion 2\uFF082026-05\uFF09\u8D77\u5DF2\u7531 CLI \u7EDF\u4E00\u5F52\u4E00\u4E3A **0~1 \u5C0F\u6570**\uFF08\u5982 `0.0964` = 9.64%\uFF09\u3002",
102111
+ "// \u5199 Excel 0~1 \u5C0F\u6570\u5217\uFF1A\u76F4\u63A5\u5199\u5165\uFF1B\u5199\u300Cx%\u300D\u6587\u6848\uFF1A`(v * 100).toFixed(2) + '%'`\uFF1B**\u7981\u6B62**\u518D \xF7100\u3002`interactionRate` \u4ECD\u662F\u5B57\u7B26\u4E32 0~1 \u5C0F\u6570\uFF0C`parseFloat` \u540E\u4F7F\u7528\u3002"
102112
+ ];
102113
+ GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS = [
102114
+ "// \u7EDF\u4E00\u8BBF\u95EE\u5C42\uFF08manifest / \u6587\u4EF6\u5185\u5747\u4E3A `schemaVersion: 3`\uFF09\uFF1A\u5224\u522B\u53EA\u6709\u4E00\u6761\u2014\u2014`record` \u975E null \u5373**\u6C47\u603B\u7EF4\u5EA6**\uFF08overview/resource-counts/gold-account/ads-index/dimension-summary/final-urls/campaign-types\uFF09\uFF0C\u8BFB `record`\uFF1B\u5426\u5219\u8BFB `items[]`\uFF08\u6240\u6709\u5217\u8868\u7EF4\u5EA6\uFF0C\u542B materials\uFF0C\u884C\u5DF2\u7EDF\u4E00\u642C\u5165\uFF0C`itemCount` = `items.length`\uFF09\u3002",
102115
+ "// \u7EF4\u5EA6\u4E13\u5C5E\u539F\u952E\u540D\uFF08keywords/campaigns/countries/data \u7B49\uFF09\u5DF2**\u79FB\u9664**\uFF0C\u4E0D\u8981\u518D\u6309\u7EF4\u5EA6\u731C\u952E\u540D\uFF1B`search-terms`/`ads` \u7684\u7F51\u5173 `code`/`message` \u5728 `meta`\uFF08\u4E0E\u6570\u636E\u65E0\u5173\uFF09\uFF1Bmaterials \u884C\u5E26 `assetType: 'image'|'video'`\u3002"
102116
+ ];
102117
+ GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION = {
102118
+ keywords: [
102119
+ "// \u4E2D\u6587\u8BD1\u540D\uFF1A`keywordMatchTypeZh`\uFF08\u7531 `keywordMatchType` \u7ECF match-type-en2zh.json \u7FFB\u8BD1\uFF0C\u8986\u76D6 BROAD/PHRASE/EXACT \u4E0E Broad/Phrase/Exact \u5927\u5C0F\u5199\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
102120
+ ],
102121
+ "search-terms": [
102122
+ "// \u4E2D\u6587\u8BD1\u540D\uFF1A`matchTypeZh`\uFF08\u7531 `matchType` \u7ECF match-type-en2zh.json \u7FFB\u8BD1\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
102123
+ ],
102124
+ "campaign-geo": [
102125
+ "// \u4E2D\u6587\u8BD1\u540D\uFF1A`countryOrRegionZh`\uFF08\u7531 `countryOrRegion` \u7ECF geo-en2zh.json \u7FFB\u8BD1\uFF0C\u76EE\u524D\u8986\u76D6 105 \u4E2A\u56FD\u5BB6/\u5730\u533A\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
102126
+ ],
102127
+ "campaign-geo-matched": [
102128
+ "// \u4E2D\u6587\u8BD1\u540D\uFF1A`countryOrRegionZh`\uFF08\u7531 `countryOrRegion` \u7ECF geo-en2zh.json \u7FFB\u8BD1\uFF0C\u76EE\u524D\u8986\u76D6 105 \u4E2A\u56FD\u5BB6/\u5730\u533A\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
102129
+ ],
102130
+ "geo-matched": [
102131
+ "// \u4E2D\u6587\u8BD1\u540D\uFF1A`countryOrRegionZh`\uFF08\u7531 `countryOrRegion` \u7ECF geo-en2zh.json \u7FFB\u8BD1\uFF0C\u76EE\u524D\u8986\u76D6 105 \u4E2A\u56FD\u5BB6/\u5730\u533A\uFF1B\u672A\u547D\u4E2D\u65F6\u8BE5\u5B57\u6BB5\u7F3A\u7701\uFF09\u3002"
102132
+ ]
102133
+ };
102134
+ }
102135
+ });
102136
+
102137
+ // src/utils/google-analysis-snapshot.ts
102138
+ var init_google_analysis_snapshot = __esm({
102139
+ "src/utils/google-analysis-snapshot.ts"() {
102140
+ "use strict";
102141
+ init_google_analysis();
102142
+ }
102143
+ });
102144
+
102135
102145
  // src/commands/google-analysis/merge-snapshot.ts
102136
102146
  import * as fs20 from "fs/promises";
102137
102147
  import * as path25 from "path";
@@ -102141,11 +102151,11 @@ function asRecord11(value) {
102141
102151
  }
102142
102152
  return null;
102143
102153
  }
102144
- function num2(value) {
102154
+ function num3(value) {
102145
102155
  const n = Number(value);
102146
102156
  return Number.isFinite(n) ? n : void 0;
102147
102157
  }
102148
- function round22(value) {
102158
+ function round23(value) {
102149
102159
  return value == null ? void 0 : Math.round(value * 100) / 100;
102150
102160
  }
102151
102161
  async function readManifest(snapshotDir, accountId) {
@@ -102188,13 +102198,13 @@ function record(envelope) {
102188
102198
  }
102189
102199
  function pickMetricRow(row, extraFields) {
102190
102200
  const base = {
102191
- spend: num2(row.spend) ?? 0,
102192
- impressions: num2(row.impressions) ?? 0,
102193
- clicks: num2(row.clicks) ?? 0,
102194
- ctr: num2(row.ctr),
102195
- averageCpc: num2(row.averageCpc),
102196
- conversions: num2(row.conversions) ?? 0,
102197
- costPerConversion: num2(row.costPerConversion)
102201
+ spend: num3(row.spend) ?? 0,
102202
+ impressions: num3(row.impressions) ?? 0,
102203
+ clicks: num3(row.clicks) ?? 0,
102204
+ ctr: num3(row.ctr),
102205
+ averageCpc: num3(row.averageCpc),
102206
+ conversions: num3(row.conversions) ?? 0,
102207
+ costPerConversion: num3(row.costPerConversion)
102198
102208
  };
102199
102209
  for (const f of extraFields) {
102200
102210
  if (row[f] !== void 0) base[f] = row[f];
@@ -102208,22 +102218,22 @@ function sumCampaignRowsToPeriodKpis(rows) {
102208
102218
  let clicks = 0;
102209
102219
  let conversions = 0;
102210
102220
  for (const row of rows) {
102211
- cost += num2(row.spend) ?? 0;
102212
- impressions += num2(row.impressions) ?? 0;
102213
- clicks += num2(row.clicks) ?? 0;
102214
- conversions += num2(row.conversions) ?? 0;
102221
+ cost += num3(row.spend) ?? 0;
102222
+ impressions += num3(row.impressions) ?? 0;
102223
+ clicks += num3(row.clicks) ?? 0;
102224
+ conversions += num3(row.conversions) ?? 0;
102215
102225
  }
102216
102226
  const ctr = impressions > 0 ? clicks / impressions : void 0;
102217
102227
  const averageCpc = clicks > 0 ? cost / clicks : void 0;
102218
102228
  const costPerConversion = conversions > 0 ? cost / conversions : void 0;
102219
102229
  return {
102220
- cost: round22(cost),
102230
+ cost: round23(cost),
102221
102231
  impressions: Math.round(impressions),
102222
102232
  clicks: Math.round(clicks),
102223
- conversions: round22(conversions),
102233
+ conversions: round23(conversions),
102224
102234
  ctr,
102225
- averageCpc: round22(averageCpc),
102226
- costPerConversion: round22(costPerConversion)
102235
+ averageCpc: round23(averageCpc),
102236
+ costPerConversion: round23(costPerConversion)
102227
102237
  };
102228
102238
  }
102229
102239
  async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
@@ -102256,13 +102266,13 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102256
102266
  const campaignKpis = sumCampaignRowsToPeriodKpis(campaignRows2);
102257
102267
  const kpis = { ...payload.kpis ?? {} };
102258
102268
  const currentKpiSource = campaignKpis ?? (currentPeriod ? {
102259
- cost: num2(currentPeriod.spend),
102260
- impressions: num2(currentPeriod.impressions),
102261
- clicks: num2(currentPeriod.clicks),
102262
- conversions: num2(currentPeriod.conversions),
102263
- ctr: num2(currentPeriod.ctr),
102264
- averageCpc: num2(currentPeriod.averageCpc),
102265
- costPerConversion: num2(currentPeriod.costPerConversion)
102269
+ cost: num3(currentPeriod.spend),
102270
+ impressions: num3(currentPeriod.impressions),
102271
+ clicks: num3(currentPeriod.clicks),
102272
+ conversions: num3(currentPeriod.conversions),
102273
+ ctr: num3(currentPeriod.ctr),
102274
+ averageCpc: num3(currentPeriod.averageCpc),
102275
+ costPerConversion: num3(currentPeriod.costPerConversion)
102266
102276
  } : null);
102267
102277
  if (currentKpiSource) {
102268
102278
  if (kpis.cost == null) kpis.cost = currentKpiSource.cost;
@@ -102275,13 +102285,13 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102275
102285
  }
102276
102286
  if (!kpis.previousPeriod && previousPeriod) {
102277
102287
  kpis.previousPeriod = {
102278
- cost: num2(previousPeriod.spend),
102279
- impressions: num2(previousPeriod.impressions),
102280
- clicks: num2(previousPeriod.clicks),
102281
- conversions: num2(previousPeriod.conversions),
102282
- ctr: num2(previousPeriod.ctr),
102283
- averageCpc: num2(previousPeriod.averageCpc),
102284
- costPerConversion: num2(previousPeriod.costPerConversion)
102288
+ cost: num3(previousPeriod.spend),
102289
+ impressions: num3(previousPeriod.impressions),
102290
+ clicks: num3(previousPeriod.clicks),
102291
+ conversions: num3(previousPeriod.conversions),
102292
+ ctr: num3(previousPeriod.ctr),
102293
+ averageCpc: num3(previousPeriod.averageCpc),
102294
+ costPerConversion: num3(previousPeriod.costPerConversion)
102285
102295
  };
102286
102296
  }
102287
102297
  const charts = { ...payload.charts ?? {} };
@@ -102293,9 +102303,9 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102293
102303
  );
102294
102304
  charts.dailyTrend = {
102295
102305
  dates: sorted.map((r) => String(r.date ?? "\u2014")),
102296
- cost: sorted.map((r) => round22(num2(r.spend)) ?? 0),
102297
- conversions: sorted.map((r) => Math.round(num2(r.conversions) ?? 0)),
102298
- cpa: sorted.map((r) => round22(num2(r.costPerConversion)) ?? 0)
102306
+ cost: sorted.map((r) => round23(num3(r.spend)) ?? 0),
102307
+ conversions: sorted.map((r) => Math.round(num3(r.conversions) ?? 0)),
102308
+ cpa: sorted.map((r) => round23(num3(r.costPerConversion)) ?? 0)
102299
102309
  };
102300
102310
  }
102301
102311
  }
@@ -102304,16 +102314,16 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102304
102314
  const summary = record(sectionMap.get("dimension-summary"));
102305
102315
  if (summary) {
102306
102316
  tables.dimensionSummary = {
102307
- cost: num2(summary.spend),
102308
- impressions: num2(summary.impressions),
102309
- clicks: num2(summary.clicks),
102310
- conversions: num2(summary.conversions),
102311
- ctr: num2(summary.ctr),
102312
- averageCpc: num2(summary.averageCpc),
102313
- costPerConversion: num2(summary.costPerConversion),
102314
- searchImpressionShare: num2(summary.searchImpressionShare),
102315
- searchBudgetLostImpressionShare: num2(summary.searchBudgetLostImpressionShare),
102316
- searchRankLostImpressionShare: num2(summary.searchRankLostImpressionShare)
102317
+ cost: num3(summary.spend),
102318
+ impressions: num3(summary.impressions),
102319
+ clicks: num3(summary.clicks),
102320
+ conversions: num3(summary.conversions),
102321
+ ctr: num3(summary.ctr),
102322
+ averageCpc: num3(summary.averageCpc),
102323
+ costPerConversion: num3(summary.costPerConversion),
102324
+ searchImpressionShare: num3(summary.searchImpressionShare),
102325
+ searchBudgetLostImpressionShare: num3(summary.searchBudgetLostImpressionShare),
102326
+ searchRankLostImpressionShare: num3(summary.searchRankLostImpressionShare)
102317
102327
  };
102318
102328
  }
102319
102329
  }
@@ -107039,12 +107049,116 @@ init_auth();
107039
107049
  init_cli_json_snapshot();
107040
107050
  init_cli_table();
107041
107051
  init_balance();
107052
+
107053
+ // src/utils/google-campaign-spend.ts
107054
+ init_fetch();
107055
+ var GOOGLE_CAMPAIGN_SPEND_CONCURRENCY = 5;
107056
+ function round22(n) {
107057
+ if (n == null || !Number.isFinite(n)) return n;
107058
+ return Math.round(n * 100) / 100;
107059
+ }
107060
+ function num(v) {
107061
+ if (typeof v === "number" && Number.isFinite(v)) return v;
107062
+ if (typeof v === "string" && v.trim() !== "") {
107063
+ const n = Number(v);
107064
+ if (Number.isFinite(n)) return n;
107065
+ }
107066
+ return void 0;
107067
+ }
107068
+ function sumCampaignRowsToSpendTotals(rows) {
107069
+ let spend = 0;
107070
+ let impressions = 0;
107071
+ let clicks = 0;
107072
+ let conversions = 0;
107073
+ for (const row of rows) {
107074
+ spend += num(row.spend) ?? 0;
107075
+ impressions += num(row.impressions) ?? 0;
107076
+ clicks += num(row.clicks) ?? 0;
107077
+ conversions += num(row.conversions) ?? 0;
107078
+ }
107079
+ return {
107080
+ spend: round22(spend) ?? 0,
107081
+ impressions: Math.round(impressions),
107082
+ clicks: Math.round(clicks),
107083
+ conversions: round22(conversions) ?? 0,
107084
+ costPerClick: clicks > 0 ? round22(spend / clicks) ?? 0 : 0
107085
+ };
107086
+ }
107087
+ function overviewFromCampaignSectionPayload(mediaAccountId, payload) {
107088
+ const bag = payload ?? {};
107089
+ const rows = Array.isArray(bag.campaigns) ? bag.campaigns.map((r) => r) : [];
107090
+ const totals = sumCampaignRowsToSpendTotals(rows);
107091
+ return {
107092
+ mediaAccountId: String(mediaAccountId),
107093
+ spend: totals.spend,
107094
+ impressions: totals.impressions,
107095
+ clicks: totals.clicks,
107096
+ conversions: totals.conversions,
107097
+ costPerClick: totals.costPerClick,
107098
+ currencyCode: typeof bag.currencyCode === "string" ? bag.currencyCode : void 0
107099
+ };
107100
+ }
107101
+ async function mapPool(items2, concurrency, fn) {
107102
+ const results = new Array(items2.length);
107103
+ let next = 0;
107104
+ const workers = Array.from(
107105
+ { length: Math.max(1, Math.min(concurrency, items2.length || 1)) },
107106
+ async () => {
107107
+ while (true) {
107108
+ const i = next++;
107109
+ if (i >= items2.length) return;
107110
+ results[i] = await fn(items2[i]);
107111
+ }
107112
+ }
107113
+ );
107114
+ await Promise.all(workers);
107115
+ return results;
107116
+ }
107117
+ async function fetchGoogleOverviewMapFromCampaigns(opts) {
107118
+ const {
107119
+ accountIds,
107120
+ config,
107121
+ startDate,
107122
+ endDate,
107123
+ verbose,
107124
+ concurrency = GOOGLE_CAMPAIGN_SPEND_CONCURRENCY
107125
+ } = opts;
107126
+ const result = /* @__PURE__ */ new Map();
107127
+ if (accountIds.length === 0) return result;
107128
+ if (accountIds.length > 1) {
107129
+ process.stderr.write(
107130
+ `\u23F3 [stats Google\u2190campaigns] ${accountIds.length} \u6237\uFF08\u5E76\u53D1 ${concurrency}\uFF0C${startDate} ~ ${endDate}\uFF09\u2026
107131
+ `
107132
+ );
107133
+ }
107134
+ const outcomes = await mapPool(accountIds, concurrency, async (id) => {
107135
+ const { payload } = await fetchOneGoogleSection({
107136
+ config,
107137
+ accountId: id,
107138
+ section: "campaigns",
107139
+ start: startDate,
107140
+ end: endDate,
107141
+ verbose
107142
+ });
107143
+ return { id, row: overviewFromCampaignSectionPayload(id, payload) };
107144
+ });
107145
+ for (const o of outcomes) {
107146
+ result.set(o.id, o.row);
107147
+ }
107148
+ if (accountIds.length > 1) {
107149
+ process.stderr.write(` \u2713 campaigns \u7D2F\u52A0\u5B8C\u6210\uFF1A${result.size} \u6237
107150
+ `);
107151
+ }
107152
+ return result;
107153
+ }
107154
+
107155
+ // src/commands/stats.ts
107042
107156
  init_media_account_lookup();
107043
107157
  init_media_customer_id();
107044
107158
  var VALID_MEDIA_TYPES3 = ["Google", "TikTok", "Yandex", "MetaAd", "BingV2", "Kwai"];
107045
107159
  var MAX_BY_DAY_SPAN = 366;
107046
107160
  var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
107047
- function defaultDateRange2() {
107161
+ function defaultDateRange3() {
107048
107162
  const end = /* @__PURE__ */ new Date();
107049
107163
  end.setDate(end.getDate() - 1);
107050
107164
  const start = new Date(end);
@@ -107082,21 +107196,20 @@ async function fetchStatsWindow(media, ids, config, startDate, endDate, verbose)
107082
107196
  const isGoogle = media === "Google";
107083
107197
  const isMetaAd = media === "MetaAd";
107084
107198
  if (isGoogle) {
107085
- const params = new URLSearchParams({ startDate, endDate, mediaCustomerIds: ids.join("|") });
107086
- const url = `${config.apiBaseUrl}/report/media-account/google/account-spend-overview?${params.toString()}`;
107087
- const raw = await apiFetch2(url, config, {}, verbose);
107088
- return parseGoogleAccountSpendOverviewRows(raw).map((row) => ({
107089
- mediaAccountId: row.mediaAccountId,
107090
- mediaCustomerName: row.mediaCustomerName,
107091
- spend: row.spend,
107092
- impressions: row.impressions,
107093
- clicks: row.clicks,
107094
- conversions: row.conversions,
107095
- costPerClick: row.costPerClick,
107096
- currencyCode: row.currencyCode,
107097
- remainingAccountBudget: row.remainingAccountBudget,
107098
- status: row.status
107099
- }));
107199
+ const map2 = await fetchGoogleOverviewMapFromCampaigns({
107200
+ accountIds: ids,
107201
+ config,
107202
+ startDate,
107203
+ endDate,
107204
+ verbose
107205
+ });
107206
+ return ids.map((id) => {
107207
+ const row = map2.get(id);
107208
+ if (!row) {
107209
+ throw new Error(`\u672A\u4ECE CampaignSectionData \u5F97\u5230\u8D26\u6237 ${id} \u7684\u6D88\u8017\u6C47\u603B`);
107210
+ }
107211
+ return row;
107212
+ });
107100
107213
  }
107101
107214
  if (isMetaAd) {
107102
107215
  const accountMediaTypes = await lookupMetaMediaAccountTypes(config, ids, verbose);
@@ -107135,7 +107248,7 @@ async function runStats(opts) {
107135
107248
  process.exit(1);
107136
107249
  }
107137
107250
  const { startDate, endDate } = {
107138
- ...defaultDateRange2(),
107251
+ ...defaultDateRange3(),
107139
107252
  ...opts.startDate ? { startDate: opts.startDate } : {},
107140
107253
  ...opts.endDate ? { endDate: opts.endDate } : {}
107141
107254
  };
@@ -107287,7 +107400,7 @@ ${opts.media} \u6295\u653E\u6570\u636E\uFF08${rangeLabel}\uFF09\uFF08\u7B2C 1 \u
107287
107400
  }
107288
107401
  function register11(program2) {
107289
107402
  program2.command("stats").description(
107290
- "\u67E5\u8BE2\u5E7F\u544A\u6D88\u8017\u3001\u70B9\u51FB\u3001\u8F6C\u5316\u7B49\u6295\u653E\u6570\u636E\uFF08\u9ED8\u8BA4\u8FD1 7 \u5929\u533A\u95F4\u5408\u8BA1\uFF0C\u4E0D\u542B\u4ECA\u5929\uFF1B\u8981\u6309\u65E5\u660E\u7EC6\u52A0 --by-day\uFF09"
107403
+ "\u67E5\u8BE2\u5E7F\u544A\u6D88\u8017\u3001\u70B9\u51FB\u3001\u8F6C\u5316\u7B49\u6295\u653E\u6570\u636E\uFF08\u9ED8\u8BA4\u8FD1 7 \u5929\u533A\u95F4\u5408\u8BA1\uFF0C\u4E0D\u542B\u4ECA\u5929\uFF1B\u8981\u6309\u65E5\u660E\u7EC6\u52A0 --by-day\uFF09\u3002Google\uFF1A\u8D26\u6237 KPI \u7531 CampaignSectionData \u7CFB\u5217\u7D2F\u52A0"
107291
107404
  ).requiredOption(
107292
107405
  "-m, --media <type>",
107293
107406
  "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
@@ -117752,7 +117865,7 @@ async function fetchVideoUploadStatus(config, googleApiUrl, mediaAccountId, reso
117752
117865
  throw new Error("\u67E5\u8BE2\u89C6\u9891\u5904\u7406\u72B6\u6001\u54CD\u5E94\u975E JSON");
117753
117866
  }
117754
117867
  }
117755
- function sleep2(ms) {
117868
+ function sleep3(ms) {
117756
117869
  return new Promise((r) => setTimeout(r, ms));
117757
117870
  }
117758
117871
  async function pollVideoUntilProcessed(config, googleApiUrl, mediaAccountId, resourceName, verbose, onProgress) {
@@ -117779,7 +117892,7 @@ async function pollVideoUntilProcessed(config, googleApiUrl, mediaAccountId, res
117779
117892
  throw new Error(`\u89C6\u9891\u5904\u7406\u7ED3\u675F\u4F46\u65E0 video_id\uFF08state=${state}\uFF09`);
117780
117893
  }
117781
117894
  if (attempt < PMAX_VIDEO_POLL_MAX_ATTEMPTS) {
117782
- await sleep2(PMAX_VIDEO_POLL_INTERVAL_MS);
117895
+ await sleep3(PMAX_VIDEO_POLL_INTERVAL_MS);
117783
117896
  }
117784
117897
  }
117785
117898
  throw new Error(
@@ -127102,16 +127215,16 @@ function sectionRecord(env) {
127102
127215
  }
127103
127216
 
127104
127217
  // src/commands/google-ads-diagnosis/build-report-data.ts
127105
- function num(value) {
127218
+ function num2(value) {
127106
127219
  const n = Number(value);
127107
127220
  return Number.isFinite(n) ? n : 0;
127108
127221
  }
127109
127222
  function rowCost(row) {
127110
- return num(row.cost ?? row.spend);
127223
+ return num2(row.cost ?? row.spend);
127111
127224
  }
127112
127225
  function cvrOf(row, clicks) {
127113
- if (clicks > 0) return num(row.conversions) / clicks;
127114
- return num(row.conversionRate ?? row.cvr);
127226
+ if (clicks > 0) return num2(row.conversions) / clicks;
127227
+ return num2(row.conversionRate ?? row.cvr);
127115
127228
  }
127116
127229
  function pct(value) {
127117
127230
  return `${(value * 100).toFixed(2)}%`;
@@ -127142,11 +127255,11 @@ function asPeriodRecord(value) {
127142
127255
  }
127143
127256
  function toKeyMetricRow(title, current, previous) {
127144
127257
  const currentCost = rowCost(current);
127145
- const currentClicks = num(current.clicks);
127258
+ const currentClicks = num2(current.clicks);
127146
127259
  const currentCvr = cvrOf(current, currentClicks);
127147
127260
  const previousCost = previous ? rowCost(previous) : null;
127148
- const previousClicks = previous ? num(previous.clicks) : null;
127149
- const previousCvr = previous ? cvrOf(previous, num(previous.clicks)) : null;
127261
+ const previousClicks = previous ? num2(previous.clicks) : null;
127262
+ const previousCvr = previous ? cvrOf(previous, num2(previous.clicks)) : null;
127150
127263
  return {
127151
127264
  title,
127152
127265
  currentCost,
@@ -127176,13 +127289,13 @@ function buildComparisonItems(currentRows, previousRows, titleFn, keyFn) {
127176
127289
  }
127177
127290
  function toTargetingItem(row, labelKey, label) {
127178
127291
  const cost = rowCost(row);
127179
- const clicks = num(row.clicks);
127180
- const impressions = num(row.impressions);
127181
- const conversions = num(row.conversions);
127182
- const ctr = num(row.ctr);
127292
+ const clicks = num2(row.clicks);
127293
+ const impressions = num2(row.impressions);
127294
+ const conversions = num2(row.conversions);
127295
+ const ctr = num2(row.ctr);
127183
127296
  const cvr = cvrOf(row, clicks);
127184
- const cpc = num(row.averageCpc ?? row.cpc);
127185
- const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
127297
+ const cpc = num2(row.averageCpc ?? row.cpc);
127298
+ const cpa = conversions > 0 ? cost / conversions : num2(row.costPerConversion ?? row.cpa);
127186
127299
  return {
127187
127300
  [labelKey]: label,
127188
127301
  cost,
@@ -127197,11 +127310,11 @@ function toTargetingItem(row, labelKey, label) {
127197
127310
  }
127198
127311
  function toDailyConversionItem(row) {
127199
127312
  const cost = rowCost(row);
127200
- const conversions = num(row.conversions);
127313
+ const conversions = num2(row.conversions);
127201
127314
  const rawDate = String(row.date ?? "");
127202
127315
  const date = rawDate.includes("T") ? rawDate.slice(0, 10) : rawDate.slice(0, 10);
127203
- const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
127204
- return { date, cost, conversions, cpa, clicks: num(row.clicks), impressions: num(row.impressions) };
127316
+ const cpa = conversions > 0 ? cost / conversions : num2(row.costPerConversion ?? row.cpa);
127317
+ return { date, cost, conversions, cpa, clicks: num2(row.clicks), impressions: num2(row.impressions) };
127205
127318
  }
127206
127319
  function loadCompanyNameFromSnap(snapDir) {
127207
127320
  const file = path22.join(snapDir, "list-accounts-google.json");
@@ -127238,8 +127351,8 @@ function inferBusinessProfile(campaignRows2, adRows, website) {
127238
127351
  };
127239
127352
  }
127240
127353
  function primaryConversionLabel(rows) {
127241
- const sorted = [...rows].sort((a, b) => num(b.allConversions) - num(a.allConversions));
127242
- const top = sorted.find((r) => num(r.allConversions) > 0 && String(r.status) === "Enabled");
127354
+ const sorted = [...rows].sort((a, b) => num2(b.allConversions) - num2(a.allConversions));
127355
+ const top = sorted.find((r) => num2(r.allConversions) > 0 && String(r.status) === "Enabled");
127243
127356
  if (top) return String(top.name ?? "\u8BE2\u76D8/\u8F6C\u5316");
127244
127357
  const enabled = rows.find((r) => String(r.status) === "Enabled");
127245
127358
  return enabled ? String(enabled.name ?? "\u8BE2\u76D8/\u8F6C\u5316") : "\u8BE2\u76D8/\u8F6C\u5316";
@@ -127258,7 +127371,7 @@ function buildBiddingStrategyItems(campaignRows2, endDate, gold) {
127258
127371
  const strategy = String(row.biddingStrategyTypeV2 ?? row.biddingStrategyType ?? "UNKNOWN");
127259
127372
  const status = String(row.campaignStatus ?? row.campaignStatusV2 ?? "");
127260
127373
  const isPaused = /paused/i.test(status);
127261
- const recommended = num(row.conversions) > 0 ? "MAXIMIZE_CONVERSIONS" : "TARGET_SPEND";
127374
+ const recommended = num2(row.conversions) > 0 ? "MAXIMIZE_CONVERSIONS" : "TARGET_SPEND";
127262
127375
  const isCorrect = policyOk && !isPaused && strategy === recommended;
127263
127376
  return {
127264
127377
  campaignName: String(row.campaignName ?? "\u672A\u547D\u540D\u7CFB\u5217"),
@@ -127271,16 +127384,16 @@ function buildBiddingStrategyItems(campaignRows2, endDate, gold) {
127271
127384
  });
127272
127385
  }
127273
127386
  function buildStructureCounts(resourceRecord, campaignRows2, keywordRows, searchRows) {
127274
- const campaignCount = Math.max(num(resourceRecord?.campaignCount), campaignRows2.length);
127275
- const keywordCount = Math.max(num(resourceRecord?.keywordCount), keywordRows.length);
127387
+ const campaignCount = Math.max(num2(resourceRecord?.campaignCount), campaignRows2.length);
127388
+ const keywordCount = Math.max(num2(resourceRecord?.keywordCount), keywordRows.length);
127276
127389
  return {
127277
127390
  campaignCount,
127278
- adGroupCount: num(resourceRecord?.adGroupCount),
127391
+ adGroupCount: num2(resourceRecord?.adGroupCount),
127279
127392
  keywordCount,
127280
- adCount: num(resourceRecord?.adCount),
127281
- extensionCount: num(resourceRecord?.extensionCount),
127282
- negativeKeywordCount: num(resourceRecord?.negativeKeywordCount),
127283
- countriesWithConversionsCount: num(resourceRecord?.countriesWithConversionsCount),
127393
+ adCount: num2(resourceRecord?.adCount),
127394
+ extensionCount: num2(resourceRecord?.extensionCount),
127395
+ negativeKeywordCount: num2(resourceRecord?.negativeKeywordCount),
127396
+ countriesWithConversionsCount: num2(resourceRecord?.countriesWithConversionsCount),
127284
127397
  searchTermCount: searchRows.length
127285
127398
  };
127286
127399
  }
@@ -127294,26 +127407,26 @@ function loadWebsiteFromAds(snapDir, accountId, start, end) {
127294
127407
  }
127295
127408
  function buildKeywordShareItems(rows) {
127296
127409
  const totalCost = rows.reduce((sum, row) => sum + rowCost(row), 0);
127297
- const totalConversions = rows.reduce((sum, row) => sum + num(row.conversions), 0);
127410
+ const totalConversions = rows.reduce((sum, row) => sum + num2(row.conversions), 0);
127298
127411
  return rows.map((row) => {
127299
127412
  const cost = rowCost(row);
127300
- const conversions = num(row.conversions);
127413
+ const conversions = num2(row.conversions);
127301
127414
  const keywordText = String(row.keywordText ?? row.keyword ?? "").trim();
127302
127415
  return {
127303
127416
  keyword: keywordText || "\u672A\u547D\u540D",
127304
- cpc: num(row.averageCpc ?? row.cpc),
127417
+ cpc: num2(row.averageCpc ?? row.cpc),
127305
127418
  matchType: String(
127306
127419
  row.keywordMatchTypeZh ?? row.keywordMatchType ?? row.matchType ?? ""
127307
127420
  ),
127308
- impressions: num(row.impressions),
127309
- clicks: num(row.clicks),
127421
+ impressions: num2(row.impressions),
127422
+ clicks: num2(row.clicks),
127310
127423
  cost,
127311
127424
  conversions,
127312
127425
  costShare: totalCost > 0 ? cost / totalCost * 100 : 0,
127313
127426
  conversionsShare: totalConversions > 0 ? conversions / totalConversions * 100 : 0,
127314
127427
  cpa: conversions > 0 ? cost / conversions : 0,
127315
- ctr: num(row.ctr),
127316
- cvr: cvrOf(row, num(row.clicks))
127428
+ ctr: num2(row.ctr),
127429
+ cvr: cvrOf(row, num2(row.clicks))
127317
127430
  };
127318
127431
  });
127319
127432
  }
@@ -127321,17 +127434,17 @@ function buildSearchTermItems(rows) {
127321
127434
  const sorted = [...rows].sort((a, b) => rowCost(b) - rowCost(a));
127322
127435
  return sorted.slice(0, 50).map((row) => {
127323
127436
  const cost = rowCost(row);
127324
- const conversions = num(row.conversions);
127325
- const cpa = conversions > 0 ? num(row.costPerConversion) || cost / conversions : void 0;
127437
+ const conversions = num2(row.conversions);
127438
+ const cpa = conversions > 0 ? num2(row.costPerConversion) || cost / conversions : void 0;
127326
127439
  return {
127327
127440
  searchTerm: String(row.searchTermText ?? row.searchTerm ?? "\u672A\u547D\u540D"),
127328
127441
  keyword: String(row.keywordText ?? row.keyword ?? ""),
127329
127442
  cost,
127330
127443
  conversions,
127331
- clicks: num(row.clicks),
127332
- impressions: num(row.impressions),
127333
- ctr: num(row.ctr),
127334
- cvr: cvrOf(row, num(row.clicks)),
127444
+ clicks: num2(row.clicks),
127445
+ impressions: num2(row.impressions),
127446
+ ctr: num2(row.ctr),
127447
+ cvr: cvrOf(row, num2(row.clicks)),
127335
127448
  cpa
127336
127449
  };
127337
127450
  });
@@ -127373,18 +127486,18 @@ function buildAdCreativeItems(rows) {
127373
127486
  }
127374
127487
  function buildBudgetCompetitiveness(overviewRecord, dimensionRecord) {
127375
127488
  const current = asPeriodRecord(overviewRecord?.currentPeriod);
127376
- const totalCost = num(overviewRecord?.totalCost ?? current?.spend);
127377
- const activeDays = num(overviewRecord?.activeDays);
127378
- const avgDaily = num(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
127379
- const remainingBudget = num(overviewRecord?.remainingAccountBudget);
127489
+ const totalCost = num2(overviewRecord?.totalCost ?? current?.spend);
127490
+ const activeDays = num2(overviewRecord?.activeDays);
127491
+ const avgDaily = num2(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
127492
+ const remainingBudget = num2(overviewRecord?.remainingAccountBudget);
127380
127493
  const utilization = remainingBudget > 0 && avgDaily > 0 ? Math.min(100, avgDaily / (avgDaily + remainingBudget / Math.max(activeDays, 1)) * 100) : avgDaily > 0 ? 85 : 0;
127381
- const sis = num(
127494
+ const sis = num2(
127382
127495
  dimensionRecord?.searchImpressionShare ?? current?.searchImpressionShare
127383
127496
  );
127384
- const budgetLost = num(
127497
+ const budgetLost = num2(
127385
127498
  dimensionRecord?.searchBudgetLostImpressionShare ?? current?.searchBudgetLostImpressionShare
127386
127499
  );
127387
- const rankLost = num(
127500
+ const rankLost = num2(
127388
127501
  dimensionRecord?.searchRankLostImpressionShare ?? current?.searchRankLostImpressionShare
127389
127502
  );
127390
127503
  return [
@@ -127470,17 +127583,17 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
127470
127583
  };
127471
127584
  data.reportDate = end;
127472
127585
  const cost = rowCost(currentPeriod ?? overviewRecord ?? {});
127473
- const impressions = num(currentPeriod?.impressions);
127474
- const clicks = num(currentPeriod?.clicks);
127475
- const conversions = num(currentPeriod?.conversions);
127476
- const ctr = num(currentPeriod?.ctr);
127586
+ const impressions = num2(currentPeriod?.impressions);
127587
+ const clicks = num2(currentPeriod?.clicks);
127588
+ const conversions = num2(currentPeriod?.conversions);
127589
+ const ctr = num2(currentPeriod?.ctr);
127477
127590
  const cvr = cvrOf(currentPeriod ?? {}, clicks);
127478
- const cpa = conversions > 0 ? cost / conversions : num(currentPeriod?.costPerConversion);
127591
+ const cpa = conversions > 0 ? cost / conversions : num2(currentPeriod?.costPerConversion);
127479
127592
  const prevCost = previousPeriod ? rowCost(previousPeriod) : 0;
127480
- const prevConversions = previousPeriod ? num(previousPeriod.conversions) : 0;
127481
- const prevCtr = previousPeriod ? num(previousPeriod.ctr) : 0;
127482
- const prevCvr = previousPeriod ? cvrOf(previousPeriod, num(previousPeriod.clicks)) : 0;
127483
- const prevCpa = prevConversions > 0 ? rowCost(previousPeriod) / prevConversions : num(previousPeriod?.costPerConversion);
127593
+ const prevConversions = previousPeriod ? num2(previousPeriod.conversions) : 0;
127594
+ const prevCtr = previousPeriod ? num2(previousPeriod.ctr) : 0;
127595
+ const prevCvr = previousPeriod ? cvrOf(previousPeriod, num2(previousPeriod.clicks)) : 0;
127596
+ const prevCpa = prevConversions > 0 ? rowCost(previousPeriod) / prevConversions : num2(previousPeriod?.costPerConversion);
127484
127597
  data.metrics = {
127485
127598
  cost,
127486
127599
  impressions,
@@ -127516,8 +127629,8 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
127516
127629
  items: conversionRows.map((row) => ({
127517
127630
  name: String(row.name ?? row.eventName ?? "\u672A\u77E5\u4E8B\u4EF6"),
127518
127631
  status: String(row.status ?? ""),
127519
- allConversions: num(row.allConversions),
127520
- allConversionsValue: num(row.allConversionsValue)
127632
+ allConversions: num2(row.allConversions),
127633
+ allConversionsValue: num2(row.allConversionsValue)
127521
127634
  }))
127522
127635
  };
127523
127636
  const campaignPrevious = sectionItems(
@@ -127634,7 +127747,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
127634
127747
  data.newFeatures = {
127635
127748
  items: nfItems.map((item) => {
127636
127749
  const strategy = String(item.strategy ?? "");
127637
- const enabled = strategy === "PerformanceMax" ? num(campaignTypes?.PerformanceMax) > 0 : strategy === "KwMatch" ? num(campaignTypes?.Search) > 0 : strategy === "DemandGen" ? num(campaignTypes?.DemandGen) > 0 : strategy === "Youtube" ? num(campaignTypes?.Video) > 0 || num(campaignTypes?.Youtube) > 0 : false;
127750
+ const enabled = strategy === "PerformanceMax" ? num2(campaignTypes?.PerformanceMax) > 0 : strategy === "KwMatch" ? num2(campaignTypes?.Search) > 0 : strategy === "DemandGen" ? num2(campaignTypes?.DemandGen) > 0 : strategy === "Youtube" ? num2(campaignTypes?.Video) > 0 || num2(campaignTypes?.Youtube) > 0 : false;
127638
127751
  return {
127639
127752
  ...item,
127640
127753
  accountStatus: enabled,
@@ -127644,7 +127757,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
127644
127757
  ...emptyModuleNarrative()
127645
127758
  };
127646
127759
  data.summary = emptySummary();
127647
- const zeroConvKw = kwCurrent.filter((r) => rowCost(r) > 50 && num(r.conversions) === 0);
127760
+ const zeroConvKw = kwCurrent.filter((r) => rowCost(r) > 50 && num2(r.conversions) === 0);
127648
127761
  const agentBrief = buildGoogleAdsDiagnosisAgentBrief({
127649
127762
  accountId,
127650
127763
  period: formatPeriod(start, end),
@@ -127668,9 +127781,9 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
127668
127781
  prevCpa
127669
127782
  },
127670
127783
  dimension: {
127671
- searchImpressionShare: num(dimensionRecord?.searchImpressionShare),
127672
- searchBudgetLostImpressionShare: num(dimensionRecord?.searchBudgetLostImpressionShare),
127673
- searchRankLostImpressionShare: num(dimensionRecord?.searchRankLostImpressionShare)
127784
+ searchImpressionShare: num2(dimensionRecord?.searchImpressionShare),
127785
+ searchBudgetLostImpressionShare: num2(dimensionRecord?.searchBudgetLostImpressionShare),
127786
+ searchRankLostImpressionShare: num2(dimensionRecord?.searchRankLostImpressionShare)
127674
127787
  },
127675
127788
  structure: structureCounts,
127676
127789
  goldAccount: goldRecord,
@@ -128584,7 +128697,7 @@ var PHASE_LABEL = {
128584
128697
  "find-winner": "\u627E\u8D62\u5BB6",
128585
128698
  scale: "\u653E\u91CF"
128586
128699
  };
128587
- function num3(value) {
128700
+ function num4(value) {
128588
128701
  const n = Number(value);
128589
128702
  return Number.isFinite(n) ? n : void 0;
128590
128703
  }
@@ -128595,35 +128708,35 @@ function money(value, currency = "USD") {
128595
128708
  }
128596
128709
  function adSetsWithSpend(tables) {
128597
128710
  const rows = Array.isArray(tables?.adSets) ? tables.adSets : [];
128598
- return rows.filter((r) => (num3(r.spend) ?? 0) > 0);
128711
+ return rows.filter((r) => (num4(r.spend) ?? 0) > 0);
128599
128712
  }
128600
128713
  function topAdSetConcentration(adSets) {
128601
128714
  if (!adSets.length) return null;
128602
- const totalSpend = adSets.reduce((s, r) => s + (num3(r.spend) ?? 0), 0);
128603
- const totalResults = adSets.reduce((s, r) => s + (num3(r.results) ?? 0), 0);
128715
+ const totalSpend = adSets.reduce((s, r) => s + (num4(r.spend) ?? 0), 0);
128716
+ const totalResults = adSets.reduce((s, r) => s + (num4(r.results) ?? 0), 0);
128604
128717
  let best = adSets[0];
128605
- let bestSpend = num3(best.spend) ?? 0;
128718
+ let bestSpend = num4(best.spend) ?? 0;
128606
128719
  for (const r of adSets) {
128607
- const sp = num3(r.spend) ?? 0;
128720
+ const sp = num4(r.spend) ?? 0;
128608
128721
  if (sp > bestSpend) {
128609
128722
  best = r;
128610
128723
  bestSpend = sp;
128611
128724
  }
128612
128725
  }
128613
- const results = num3(best.results) ?? 0;
128726
+ const results = num4(best.results) ?? 0;
128614
128727
  return {
128615
128728
  name: String(best.adGroupName ?? best.name ?? "\u2014"),
128616
128729
  spendShare: totalSpend > 0 ? bestSpend / totalSpend : 0,
128617
128730
  resultsShare: totalResults > 0 ? results / totalResults : 0,
128618
- cpl: num3(best.costPerResult)
128731
+ cpl: num4(best.costPerResult)
128619
128732
  };
128620
128733
  }
128621
128734
  function winnerSpendShare(adSets, avgCpl, totalSpend) {
128622
128735
  if (totalSpend <= 0 || avgCpl <= 0) return 0;
128623
128736
  const winnerSpend = adSets.filter((r) => {
128624
- const cpl = num3(r.costPerResult);
128737
+ const cpl = num4(r.costPerResult);
128625
128738
  return cpl != null && cpl <= avgCpl * 0.9;
128626
- }).reduce((s, r) => s + (num3(r.spend) ?? 0), 0);
128739
+ }).reduce((s, r) => s + (num4(r.spend) ?? 0), 0);
128627
128740
  return winnerSpend / totalSpend;
128628
128741
  }
128629
128742
  function countActiveDimensions(charts, tables) {
@@ -128640,7 +128753,7 @@ function asArray2(value) {
128640
128753
  }
128641
128754
  function inferLifecyclePhase(input) {
128642
128755
  const { avgCpl, currency = "USD" } = input;
128643
- const spend = num3(input.spend) ?? 0;
128756
+ const spend = num4(input.spend) ?? 0;
128644
128757
  const adSets = adSetsWithSpend(input.tables);
128645
128758
  const activeDims = countActiveDimensions(input.charts, input.tables);
128646
128759
  const winShare = winnerSpendShare(adSets, avgCpl, spend);
@@ -128699,7 +128812,7 @@ function ensureLifecycleFromSnapshot(healthDiagnosis, merged) {
128699
128812
  }
128700
128813
 
128701
128814
  // src/commands/facebook-analysis/scorecard-autofill.ts
128702
- function num4(value) {
128815
+ function num5(value) {
128703
128816
  const n = Number(value);
128704
128817
  return Number.isFinite(n) ? n : void 0;
128705
128818
  }
@@ -128739,8 +128852,8 @@ function buildScorecardFromMergedData(input) {
128739
128852
  if (pLabels.length) {
128740
128853
  const indexed = pLabels.map((label, i) => ({
128741
128854
  label: String(label),
128742
- cpl: num4(pCpl[i]),
128743
- spend: num4(pSpend[i]) ?? 0
128855
+ cpl: num5(pCpl[i]),
128856
+ spend: num5(pSpend[i]) ?? 0
128744
128857
  })).filter((x) => x.spend > 0 || x.cpl != null && x.cpl > 0);
128745
128858
  const sorted = [...indexed].sort((a, b) => (a.cpl ?? Infinity) - (b.cpl ?? Infinity));
128746
128859
  if (sorted[0]) {
@@ -128765,7 +128878,7 @@ function buildScorecardFromMergedData(input) {
128765
128878
  const cLabels = Array.isArray(country?.labels) ? country.labels : [];
128766
128879
  const cCpl = Array.isArray(country?.cpl) ? country.cpl : [];
128767
128880
  if (cLabels.length) {
128768
- const indexed = cLabels.map((label, i) => ({ label: String(label), cpl: num4(cCpl[i]) })).filter((x) => x.cpl != null && x.cpl > 0);
128881
+ const indexed = cLabels.map((label, i) => ({ label: String(label), cpl: num5(cCpl[i]) })).filter((x) => x.cpl != null && x.cpl > 0);
128769
128882
  const sorted = [...indexed].sort((a, b) => (a.cpl ?? 0) - (b.cpl ?? 0));
128770
128883
  if (sorted[0]) {
128771
128884
  const s = cplSignal(sorted[0].cpl, avgCpl);
@@ -128786,24 +128899,24 @@ function buildScorecardFromMergedData(input) {
128786
128899
  }
128787
128900
  }
128788
128901
  const adSets = Array.isArray(tables.adSets) ? tables.adSets : [];
128789
- const adWithSpend = adSets.filter((g) => (num4(g.spend) ?? 0) > 0);
128902
+ const adWithSpend = adSets.filter((g) => (num5(g.spend) ?? 0) > 0);
128790
128903
  if (adWithSpend.length) {
128791
128904
  const sorted = [...adWithSpend].sort(
128792
- (a, b) => (num4(a.costPerResult) ?? Infinity) - (num4(b.costPerResult) ?? Infinity)
128905
+ (a, b) => (num5(a.costPerResult) ?? Infinity) - (num5(b.costPerResult) ?? Infinity)
128793
128906
  );
128794
128907
  const best = sorted[0];
128795
- const sBest = cplSignal(num4(best.costPerResult), avgCpl);
128908
+ const sBest = cplSignal(num5(best.costPerResult), avgCpl);
128796
128909
  pushRow(rows, seen, {
128797
128910
  item: String(best.name ?? "\u5E7F\u544A\u7EC4"),
128798
- data: `CPL ${money2(num4(best.costPerResult), currency)}`,
128911
+ data: `CPL ${money2(num5(best.costPerResult), currency)}`,
128799
128912
  ...sBest
128800
128913
  });
128801
128914
  if (sorted.length > 1) {
128802
128915
  const worst = sorted[sorted.length - 1];
128803
- const sWorst = cplSignal(num4(worst.costPerResult), avgCpl);
128916
+ const sWorst = cplSignal(num5(worst.costPerResult), avgCpl);
128804
128917
  pushRow(rows, seen, {
128805
128918
  item: String(worst.name ?? "\u5E7F\u544A\u7EC4"),
128806
- data: `CPL ${money2(num4(worst.costPerResult), currency)}`,
128919
+ data: `CPL ${money2(num5(worst.costPerResult), currency)}`,
128807
128920
  ...sWorst
128808
128921
  });
128809
128922
  }
@@ -128811,18 +128924,18 @@ function buildScorecardFromMergedData(input) {
128811
128924
  const topAud = Array.isArray(tables.audienceTop) ? tables.audienceTop : [];
128812
128925
  const bottomAud = Array.isArray(tables.audienceBottom) ? tables.audienceBottom : [];
128813
128926
  if (topAud[0]) {
128814
- const s = cplSignal(num4(topAud[0].costPerResult), avgCpl);
128927
+ const s = cplSignal(num5(topAud[0].costPerResult), avgCpl);
128815
128928
  pushRow(rows, seen, {
128816
128929
  item: `\u53D7\u4F17\xB7${String(topAud[0].label ?? "Top")}`,
128817
- data: `CPL ${money2(num4(topAud[0].costPerResult), currency)}`,
128930
+ data: `CPL ${money2(num5(topAud[0].costPerResult), currency)}`,
128818
128931
  ...s
128819
128932
  });
128820
128933
  }
128821
128934
  if (bottomAud[0]) {
128822
- const s = cplSignal(num4(bottomAud[0].costPerResult), avgCpl);
128935
+ const s = cplSignal(num5(bottomAud[0].costPerResult), avgCpl);
128823
128936
  pushRow(rows, seen, {
128824
128937
  item: `\u53D7\u4F17\xB7${String(bottomAud[0].label ?? "Bottom")}`,
128825
- data: `CPL ${money2(num4(bottomAud[0].costPerResult), currency)}`,
128938
+ data: `CPL ${money2(num5(bottomAud[0].costPerResult), currency)}`,
128826
128939
  ...s
128827
128940
  });
128828
128941
  }
@@ -128866,7 +128979,7 @@ function asRecord14(value) {
128866
128979
  }
128867
128980
  return null;
128868
128981
  }
128869
- function num5(value) {
128982
+ function num6(value) {
128870
128983
  const n = Number(value);
128871
128984
  return Number.isFinite(n) ? n : void 0;
128872
128985
  }
@@ -128920,8 +129033,8 @@ function aggregatePlatform(networks) {
128920
129033
  const platform = String(row.publisherPlatform ?? row.network ?? "unknown");
128921
129034
  const prev = map.get(platform) ?? { spend: 0, results: 0 };
128922
129035
  map.set(platform, {
128923
- spend: prev.spend + (num5(row.spend) ?? 0),
128924
- results: prev.results + (num5(row.results) ?? 0)
129036
+ spend: prev.spend + (num6(row.spend) ?? 0),
129037
+ results: prev.results + (num6(row.results) ?? 0)
128925
129038
  });
128926
129039
  }
128927
129040
  const labels = [];
@@ -128937,10 +129050,10 @@ function aggregatePlatform(networks) {
128937
129050
  function buildAudienceRows(audiences) {
128938
129051
  return audiences.map((a) => ({
128939
129052
  label: audienceLabel(a.age, a.gender),
128940
- spend: num5(a.spend) ?? 0,
128941
- results: num5(a.results) ?? 0,
128942
- costPerResult: num5(a.costPerResult) ?? (num5(a.results) ? (num5(a.spend) ?? 0) / (num5(a.results) ?? 1) : 0),
128943
- frequency: num5(a.frequency)
129053
+ spend: num6(a.spend) ?? 0,
129054
+ results: num6(a.results) ?? 0,
129055
+ costPerResult: num6(a.costPerResult) ?? (num6(a.results) ? (num6(a.spend) ?? 0) / (num6(a.results) ?? 1) : 0),
129056
+ frequency: num6(a.frequency)
128944
129057
  })).filter((r) => r.spend > 0 || r.results > 0);
128945
129058
  }
128946
129059
  async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
@@ -128979,7 +129092,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
128979
129092
  }
128980
129093
  if (!meta.resultType && current?.resultType) meta.resultType = String(current.resultType);
128981
129094
  const charts = { ...payload.charts ?? {} };
128982
- const avgCpl = num5(kpis.costPerResult) ?? 0;
129095
+ const avgCpl = num6(kpis.costPerResult) ?? 0;
128983
129096
  if (!charts.platform) {
128984
129097
  const platform = sectionMap.get("platform");
128985
129098
  const networks = Array.isArray(platform?.networks) ? platform.networks : [];
@@ -128989,10 +129102,10 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
128989
129102
  const country = sectionMap.get("country");
128990
129103
  const countries = Array.isArray(country?.countries) ? country.countries : [];
128991
129104
  if (countries.length) {
128992
- const sorted = [...countries].sort((a, b) => (num5(b.spend) ?? 0) - (num5(a.spend) ?? 0));
129105
+ const sorted = [...countries].sort((a, b) => (num6(b.spend) ?? 0) - (num6(a.spend) ?? 0));
128993
129106
  charts.country = {
128994
129107
  labels: sorted.map((c) => String(c.countryOrRegion ?? "\u2014")),
128995
- cpl: sorted.map((c) => num5(c.costPerResult) ?? 0)
129108
+ cpl: sorted.map((c) => num6(c.costPerResult) ?? 0)
128996
129109
  };
128997
129110
  }
128998
129111
  }
@@ -129008,7 +129121,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129008
129121
  };
129009
129122
  }
129010
129123
  }
129011
- if (!charts.funnel && num5(kpis.reach) != null) {
129124
+ if (!charts.funnel && num6(kpis.reach) != null) {
129012
129125
  charts.funnel = { reach: kpis.reach, results: kpis.results ?? 0 };
129013
129126
  }
129014
129127
  const tables = { ...payload.tables ?? {} };
@@ -129017,14 +129130,14 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129017
129130
  const groups = Array.isArray(adSets?.adGroups) ? adSets.adGroups : [];
129018
129131
  if (groups.length) {
129019
129132
  tables.adSets = groups.map((g) => {
129020
- const cpl = num5(g.costPerResult);
129021
- const freq = num5(g.frequency);
129133
+ const cpl = num6(g.costPerResult);
129134
+ const freq = num6(g.frequency);
129022
129135
  const fatigue = fatigueFromFrequency(freq);
129023
129136
  const status = statusFromCpl(cpl, avgCpl);
129024
129137
  return {
129025
129138
  name: String(g.adGroupName ?? g.campaignName ?? "\u2014"),
129026
- spend: num5(g.spend) ?? 0,
129027
- results: num5(g.results) ?? 0,
129139
+ spend: num6(g.spend) ?? 0,
129140
+ results: num6(g.results) ?? 0,
129028
129141
  costPerResult: cpl ?? 0,
129029
129142
  frequency: freq,
129030
129143
  fatigueLevel: fatigue.level,
@@ -129048,7 +129161,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129048
129161
  }
129049
129162
  const currency = typeof meta.currency === "string" ? meta.currency : "USD";
129050
129163
  const mergeCtx = {
129051
- spend: num5(kpis.spend),
129164
+ spend: num6(kpis.spend),
129052
129165
  avgCpl,
129053
129166
  currency,
129054
129167
  charts,
@@ -129671,7 +129784,7 @@ function asRecord17(value) {
129671
129784
  }
129672
129785
  return null;
129673
129786
  }
129674
- function num6(value) {
129787
+ function num7(value) {
129675
129788
  const n = Number(value);
129676
129789
  return Number.isFinite(n) ? n : void 0;
129677
129790
  }
@@ -129680,16 +129793,16 @@ function asRows(value) {
129680
129793
  }
129681
129794
  function pickMetricExtras(row, extras) {
129682
129795
  const out = {
129683
- spend: num6(row.spend) ?? 0,
129684
- impressions: num6(row.impressions) ?? 0,
129685
- clicks: num6(row.clicks) ?? 0,
129686
- results: num6(row.results),
129687
- costPerResult: num6(row.costPerResult),
129688
- reach: num6(row.reach),
129689
- frequency: num6(row.frequency),
129690
- ctr: num6(row.ctr),
129691
- conversions: num6(row.conversions),
129692
- costPerConversion: num6(row.costPerConversion)
129796
+ spend: num7(row.spend) ?? 0,
129797
+ impressions: num7(row.impressions) ?? 0,
129798
+ clicks: num7(row.clicks) ?? 0,
129799
+ results: num7(row.results),
129800
+ costPerResult: num7(row.costPerResult),
129801
+ reach: num7(row.reach),
129802
+ frequency: num7(row.frequency),
129803
+ ctr: num7(row.ctr),
129804
+ conversions: num7(row.conversions),
129805
+ costPerConversion: num7(row.costPerConversion)
129693
129806
  };
129694
129807
  for (const f of extras) {
129695
129808
  if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
@@ -129758,27 +129871,27 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
129758
129871
  if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
129759
129872
  const kpis = { ...payload.kpis ?? {} };
129760
129873
  if (currentPeriod) {
129761
- if (kpis.spend == null) kpis.spend = num6(currentPeriod.spend);
129762
- if (kpis.impressions == null) kpis.impressions = num6(currentPeriod.impressions);
129763
- if (kpis.clicks == null) kpis.clicks = num6(currentPeriod.clicks);
129764
- if (kpis.results == null) kpis.results = num6(currentPeriod.results);
129765
- if (kpis.costPerResult == null) kpis.costPerResult = num6(currentPeriod.costPerResult);
129766
- if (kpis.ctr == null) kpis.ctr = num6(currentPeriod.ctr);
129767
- if (kpis.cpc == null) kpis.cpc = num6(currentPeriod.cpc ?? currentPeriod.averageCpc);
129768
- if (kpis.reach == null) kpis.reach = num6(currentPeriod.reach);
129769
- if (kpis.frequency == null) kpis.frequency = num6(currentPeriod.frequency);
129874
+ if (kpis.spend == null) kpis.spend = num7(currentPeriod.spend);
129875
+ if (kpis.impressions == null) kpis.impressions = num7(currentPeriod.impressions);
129876
+ if (kpis.clicks == null) kpis.clicks = num7(currentPeriod.clicks);
129877
+ if (kpis.results == null) kpis.results = num7(currentPeriod.results);
129878
+ if (kpis.costPerResult == null) kpis.costPerResult = num7(currentPeriod.costPerResult);
129879
+ if (kpis.ctr == null) kpis.ctr = num7(currentPeriod.ctr);
129880
+ if (kpis.cpc == null) kpis.cpc = num7(currentPeriod.cpc ?? currentPeriod.averageCpc);
129881
+ if (kpis.reach == null) kpis.reach = num7(currentPeriod.reach);
129882
+ if (kpis.frequency == null) kpis.frequency = num7(currentPeriod.frequency);
129770
129883
  }
129771
129884
  if (!kpis.previousPeriod && previousPeriod) {
129772
129885
  kpis.previousPeriod = {
129773
- spend: num6(previousPeriod.spend),
129774
- impressions: num6(previousPeriod.impressions),
129775
- clicks: num6(previousPeriod.clicks),
129776
- results: num6(previousPeriod.results),
129777
- costPerResult: num6(previousPeriod.costPerResult),
129778
- ctr: num6(previousPeriod.ctr),
129779
- cpc: num6(previousPeriod.cpc ?? previousPeriod.averageCpc),
129780
- reach: num6(previousPeriod.reach),
129781
- frequency: num6(previousPeriod.frequency)
129886
+ spend: num7(previousPeriod.spend),
129887
+ impressions: num7(previousPeriod.impressions),
129888
+ clicks: num7(previousPeriod.clicks),
129889
+ results: num7(previousPeriod.results),
129890
+ costPerResult: num7(previousPeriod.costPerResult),
129891
+ ctr: num7(previousPeriod.ctr),
129892
+ cpc: num7(previousPeriod.cpc ?? previousPeriod.averageCpc),
129893
+ reach: num7(previousPeriod.reach),
129894
+ frequency: num7(previousPeriod.frequency)
129782
129895
  };
129783
129896
  }
129784
129897
  const tables = { ...payload.tables ?? {} };
@@ -130363,7 +130476,7 @@ init_auth();
130363
130476
  init_balance();
130364
130477
 
130365
130478
  // src/commands/bing-analysis/normalize-bing-overview.ts
130366
- function num7(v) {
130479
+ function num8(v) {
130367
130480
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
130368
130481
  }
130369
130482
  function roundMoney(v) {
@@ -130384,19 +130497,19 @@ function countInclusiveCalendarDays(startDate, endDate) {
130384
130497
  }
130385
130498
  function periodSpend(period) {
130386
130499
  if (!period || typeof period !== "object") return void 0;
130387
- return num7(period.spend);
130500
+ return num8(period.spend);
130388
130501
  }
130389
130502
  function enrichBingOverviewDerivedMetrics(record2, startDate, endDate) {
130390
130503
  const out = { ...record2 };
130391
130504
  const currentSpend = periodSpend(out.currentPeriod);
130392
- let totalCost = num7(out.totalCost);
130505
+ let totalCost = num8(out.totalCost);
130393
130506
  if (totalCost == null || totalCost <= 0) {
130394
130507
  if (currentSpend != null && currentSpend > 0) {
130395
130508
  out.totalCost = currentSpend;
130396
130509
  totalCost = currentSpend;
130397
130510
  }
130398
130511
  }
130399
- let activeDays = num7(out.activeDays);
130512
+ let activeDays = num8(out.activeDays);
130400
130513
  if (activeDays == null || activeDays <= 0) {
130401
130514
  const calendarDays = countInclusiveCalendarDays(startDate, endDate);
130402
130515
  if (calendarDays > 0) {
@@ -130404,7 +130517,7 @@ function enrichBingOverviewDerivedMetrics(record2, startDate, endDate) {
130404
130517
  activeDays = calendarDays;
130405
130518
  }
130406
130519
  }
130407
- const averageDailyCost = num7(out.averageDailyCost);
130520
+ const averageDailyCost = num8(out.averageDailyCost);
130408
130521
  if ((averageDailyCost == null || averageDailyCost <= 0) && totalCost != null && totalCost > 0 && activeDays != null && activeDays > 0) {
130409
130522
  out.averageDailyCost = roundMoney(totalCost / activeDays);
130410
130523
  }
@@ -130436,16 +130549,16 @@ function calcBingPreviousPeriodRange(startDate, endDate) {
130436
130549
  function isBingPeriodBlockEmpty(period) {
130437
130550
  if (!period || typeof period !== "object") return true;
130438
130551
  const p = period;
130439
- const spend = num7(p.spend) ?? 0;
130440
- const impressions = num7(p.impressions) ?? 0;
130441
- const clicks = num7(p.clicks) ?? 0;
130552
+ const spend = num8(p.spend) ?? 0;
130553
+ const impressions = num8(p.impressions) ?? 0;
130554
+ const clicks = num8(p.clicks) ?? 0;
130442
130555
  return spend <= 0 && impressions <= 0 && clicks <= 0;
130443
130556
  }
130444
130557
  function syncOverviewBalanceFields(record2) {
130445
130558
  const out = { ...record2 };
130446
- const budget = num7(out.remainingAccountBudget);
130559
+ const budget = num8(out.remainingAccountBudget);
130447
130560
  if (budget != null && budget >= 0) {
130448
- const balanceField = num7(out.balance);
130561
+ const balanceField = num8(out.balance);
130449
130562
  if (balanceField == null || balanceField <= 0 && budget > 0) {
130450
130563
  out.balance = budget;
130451
130564
  }
@@ -130809,11 +130922,11 @@ function asRecord20(value) {
130809
130922
  }
130810
130923
  return null;
130811
130924
  }
130812
- function num8(value) {
130925
+ function num9(value) {
130813
130926
  const n = Number(value);
130814
130927
  return Number.isFinite(n) ? n : void 0;
130815
130928
  }
130816
- function round23(value) {
130929
+ function round24(value) {
130817
130930
  return value == null ? void 0 : Math.round(value * 100) / 100;
130818
130931
  }
130819
130932
  function asRows2(value) {
@@ -130855,14 +130968,14 @@ async function readManifest4(snapshotDir, accountId) {
130855
130968
  }
130856
130969
  function pickMetrics(row, extras = []) {
130857
130970
  const out = {
130858
- spend: num8(row.spend) ?? 0,
130859
- impressions: num8(row.impressions) ?? 0,
130860
- clicks: num8(row.clicks) ?? 0,
130861
- ctr: num8(row.ctr),
130862
- averageCpc: num8(row.averageCpc),
130863
- conversions: num8(row.conversions) ?? 0,
130864
- conversionRate: num8(row.conversionRate),
130865
- costPerConversion: num8(row.costPerConversion)
130971
+ spend: num9(row.spend) ?? 0,
130972
+ impressions: num9(row.impressions) ?? 0,
130973
+ clicks: num9(row.clicks) ?? 0,
130974
+ ctr: num9(row.ctr),
130975
+ averageCpc: num9(row.averageCpc),
130976
+ conversions: num9(row.conversions) ?? 0,
130977
+ conversionRate: num9(row.conversionRate),
130978
+ costPerConversion: num9(row.costPerConversion)
130866
130979
  };
130867
130980
  for (const f of extras) {
130868
130981
  if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
@@ -130876,19 +130989,19 @@ function sumBingCampaignRowsToPeriodKpis(rows) {
130876
130989
  let clicks = 0;
130877
130990
  let conversions = 0;
130878
130991
  for (const row of rows) {
130879
- spend += num8(row.spend) ?? 0;
130880
- impressions += num8(row.impressions) ?? 0;
130881
- clicks += num8(row.clicks) ?? 0;
130882
- conversions += num8(row.conversions) ?? 0;
130992
+ spend += num9(row.spend) ?? 0;
130993
+ impressions += num9(row.impressions) ?? 0;
130994
+ clicks += num9(row.clicks) ?? 0;
130995
+ conversions += num9(row.conversions) ?? 0;
130883
130996
  }
130884
130997
  return {
130885
- spend: round23(spend),
130998
+ spend: round24(spend),
130886
130999
  impressions: Math.round(impressions),
130887
131000
  clicks: Math.round(clicks),
130888
- conversions: round23(conversions),
131001
+ conversions: round24(conversions),
130889
131002
  ctr: impressions > 0 ? clicks / impressions : void 0,
130890
- averageCpc: clicks > 0 ? round23(spend / clicks) : void 0,
130891
- costPerConversion: conversions > 0 ? round23(spend / conversions) : void 0
131003
+ averageCpc: clicks > 0 ? round24(spend / clicks) : void 0,
131004
+ costPerConversion: conversions > 0 ? round24(spend / conversions) : void 0
130892
131005
  };
130893
131006
  }
130894
131007
  function campaignStatusDisplay(status) {
@@ -131026,13 +131139,13 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
131026
131139
  if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
131027
131140
  const kpis = { ...payload.kpis ?? {} };
131028
131141
  const currentKpiSource = campaignKpis ?? (currentPeriod ? {
131029
- spend: num8(currentPeriod.spend),
131030
- impressions: num8(currentPeriod.impressions),
131031
- clicks: num8(currentPeriod.clicks),
131032
- conversions: num8(currentPeriod.conversions),
131033
- ctr: num8(currentPeriod.ctr),
131034
- averageCpc: num8(currentPeriod.averageCpc),
131035
- costPerConversion: num8(currentPeriod.costPerConversion)
131142
+ spend: num9(currentPeriod.spend),
131143
+ impressions: num9(currentPeriod.impressions),
131144
+ clicks: num9(currentPeriod.clicks),
131145
+ conversions: num9(currentPeriod.conversions),
131146
+ ctr: num9(currentPeriod.ctr),
131147
+ averageCpc: num9(currentPeriod.averageCpc),
131148
+ costPerConversion: num9(currentPeriod.costPerConversion)
131036
131149
  } : null);
131037
131150
  if (currentKpiSource) {
131038
131151
  if (kpis.spend == null) kpis.spend = currentKpiSource.spend;
@@ -131044,24 +131157,24 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
131044
131157
  if (kpis.costPerConversion == null) kpis.costPerConversion = currentKpiSource.costPerConversion;
131045
131158
  }
131046
131159
  if (kpis.averageDailyCost == null && overview) {
131047
- kpis.averageDailyCost = num8(overview.averageDailyCost);
131160
+ kpis.averageDailyCost = num9(overview.averageDailyCost);
131048
131161
  }
131049
- if (kpis.balance == null && overview) kpis.balance = num8(overview.balance);
131162
+ if (kpis.balance == null && overview) kpis.balance = num9(overview.balance);
131050
131163
  if (kpis.remainingAccountBudget == null && overview) {
131051
- kpis.remainingAccountBudget = num8(overview.remainingAccountBudget);
131164
+ kpis.remainingAccountBudget = num9(overview.remainingAccountBudget);
131052
131165
  }
131053
131166
  if (kpis.optimizationScore == null && overview) {
131054
- kpis.optimizationScore = num8(overview.optimizationScore);
131167
+ kpis.optimizationScore = num9(overview.optimizationScore);
131055
131168
  }
131056
131169
  if (!kpis.previousPeriod && previousPeriod) {
131057
131170
  kpis.previousPeriod = {
131058
- spend: num8(previousPeriod.spend),
131059
- impressions: num8(previousPeriod.impressions),
131060
- clicks: num8(previousPeriod.clicks),
131061
- conversions: num8(previousPeriod.conversions),
131062
- ctr: num8(previousPeriod.ctr),
131063
- averageCpc: num8(previousPeriod.averageCpc),
131064
- costPerConversion: num8(previousPeriod.costPerConversion)
131171
+ spend: num9(previousPeriod.spend),
131172
+ impressions: num9(previousPeriod.impressions),
131173
+ clicks: num9(previousPeriod.clicks),
131174
+ conversions: num9(previousPeriod.conversions),
131175
+ ctr: num9(previousPeriod.ctr),
131176
+ averageCpc: num9(previousPeriod.averageCpc),
131177
+ costPerConversion: num9(previousPeriod.costPerConversion)
131065
131178
  };
131066
131179
  }
131067
131180
  const tables = { ...payload.tables ?? {} };
@@ -131682,11 +131795,11 @@ function asRecord23(value) {
131682
131795
  }
131683
131796
  return null;
131684
131797
  }
131685
- function num9(value) {
131798
+ function num10(value) {
131686
131799
  const n = Number(value);
131687
131800
  return Number.isFinite(n) ? n : void 0;
131688
131801
  }
131689
- function round24(value) {
131802
+ function round25(value) {
131690
131803
  return value == null ? void 0 : Math.round(value * 100) / 100;
131691
131804
  }
131692
131805
  async function loadSectionPayload2(snapshotDir, file) {
@@ -131722,19 +131835,19 @@ async function readManifest5(snapshotDir, accountId) {
131722
131835
  }
131723
131836
  function normalizeTikTokMetrics(metrics) {
131724
131837
  const m = metrics ?? {};
131725
- const spend = num9(m.spend);
131726
- const impressions = num9(m.impressions);
131727
- const clicks = num9(m.clicks);
131728
- const conversions = num9(m.conversion);
131838
+ const spend = num10(m.spend);
131839
+ const impressions = num10(m.impressions);
131840
+ const clicks = num10(m.clicks);
131841
+ const conversions = num10(m.conversion);
131729
131842
  return {
131730
131843
  spend: spend ?? 0,
131731
131844
  impressions: impressions ?? 0,
131732
131845
  clicks: clicks ?? 0,
131733
131846
  conversions: conversions == null ? 0 : Math.round(conversions),
131734
- ctr: num9(m.ctr),
131735
- averageCpc: num9(m.cpc),
131736
- costPerConversion: num9(m.cost_per_conversion),
131737
- conversionRate: num9(m.conversion_rate)
131847
+ ctr: num10(m.ctr),
131848
+ averageCpc: num10(m.cpc),
131849
+ costPerConversion: num10(m.cost_per_conversion),
131850
+ conversionRate: num10(m.conversion_rate)
131738
131851
  };
131739
131852
  }
131740
131853
  function listFromEnvelope(payload) {
@@ -131753,19 +131866,19 @@ function sumTikTokCampaignRowsToPeriodKpis(rows) {
131753
131866
  let clicks = 0;
131754
131867
  let conversions = 0;
131755
131868
  for (const row of rows) {
131756
- spend += num9(row.spend) ?? 0;
131757
- impressions += num9(row.impressions) ?? 0;
131758
- clicks += num9(row.clicks) ?? 0;
131759
- conversions += num9(row.conversions) ?? 0;
131869
+ spend += num10(row.spend) ?? 0;
131870
+ impressions += num10(row.impressions) ?? 0;
131871
+ clicks += num10(row.clicks) ?? 0;
131872
+ conversions += num10(row.conversions) ?? 0;
131760
131873
  }
131761
131874
  return {
131762
- spend: round24(spend),
131875
+ spend: round25(spend),
131763
131876
  impressions: Math.round(impressions),
131764
131877
  clicks: Math.round(clicks),
131765
- conversions: round24(conversions),
131878
+ conversions: round25(conversions),
131766
131879
  ctr: impressions > 0 ? clicks / impressions : void 0,
131767
- averageCpc: clicks > 0 ? round24(spend / clicks) : void 0,
131768
- costPerConversion: conversions > 0 ? round24(spend / conversions) : void 0
131880
+ averageCpc: clicks > 0 ? round25(spend / clicks) : void 0,
131881
+ costPerConversion: conversions > 0 ? round25(spend / conversions) : void 0
131769
131882
  };
131770
131883
  }
131771
131884
  function mapCampaignTable(rows) {
@@ -131773,13 +131886,13 @@ function mapCampaignTable(rows) {
131773
131886
  campaignId: r.campaignId,
131774
131887
  campaignName: r.campaignName,
131775
131888
  status: r.campaignStatus,
131776
- spend: num9(r.spend) ?? 0,
131777
- impressions: num9(r.impressions) ?? 0,
131778
- clicks: num9(r.clicks) ?? 0,
131779
- conversions: num9(r.conversions) ?? 0,
131780
- ctr: num9(r.ctr),
131781
- averageCpc: num9(r.averageCpc),
131782
- costPerConversion: num9(r.costPerConversion)
131889
+ spend: num10(r.spend) ?? 0,
131890
+ impressions: num10(r.impressions) ?? 0,
131891
+ clicks: num10(r.clicks) ?? 0,
131892
+ conversions: num10(r.conversions) ?? 0,
131893
+ ctr: num10(r.ctr),
131894
+ averageCpc: num10(r.averageCpc),
131895
+ costPerConversion: num10(r.costPerConversion)
131783
131896
  }));
131784
131897
  }
131785
131898
  function mapAdGroupTable(payload) {
@@ -131861,13 +131974,13 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
131861
131974
  if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
131862
131975
  const kpis = { ...payload.kpis ?? {} };
131863
131976
  const currentKpiSource = campaignKpis ?? (currentPeriod ? {
131864
- spend: num9(currentPeriod.spend),
131865
- impressions: num9(currentPeriod.impressions),
131866
- clicks: num9(currentPeriod.clicks),
131867
- conversions: num9(currentPeriod.conversions),
131868
- ctr: num9(currentPeriod.ctr),
131869
- averageCpc: num9(currentPeriod.averageCpc),
131870
- costPerConversion: num9(currentPeriod.costPerConversion)
131977
+ spend: num10(currentPeriod.spend),
131978
+ impressions: num10(currentPeriod.impressions),
131979
+ clicks: num10(currentPeriod.clicks),
131980
+ conversions: num10(currentPeriod.conversions),
131981
+ ctr: num10(currentPeriod.ctr),
131982
+ averageCpc: num10(currentPeriod.averageCpc),
131983
+ costPerConversion: num10(currentPeriod.costPerConversion)
131871
131984
  } : null);
131872
131985
  if (currentKpiSource) {
131873
131986
  if (kpis.spend == null) kpis.spend = currentKpiSource.spend;
@@ -131880,13 +131993,13 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
131880
131993
  }
131881
131994
  if (!kpis.previousPeriod && previousPeriod) {
131882
131995
  kpis.previousPeriod = {
131883
- spend: num9(previousPeriod.spend),
131884
- impressions: num9(previousPeriod.impressions),
131885
- clicks: num9(previousPeriod.clicks),
131886
- conversions: num9(previousPeriod.conversions),
131887
- ctr: num9(previousPeriod.ctr),
131888
- averageCpc: num9(previousPeriod.averageCpc),
131889
- costPerConversion: num9(previousPeriod.costPerConversion)
131996
+ spend: num10(previousPeriod.spend),
131997
+ impressions: num10(previousPeriod.impressions),
131998
+ clicks: num10(previousPeriod.clicks),
131999
+ conversions: num10(previousPeriod.conversions),
132000
+ ctr: num10(previousPeriod.ctr),
132001
+ averageCpc: num10(previousPeriod.averageCpc),
132002
+ costPerConversion: num10(previousPeriod.costPerConversion)
131890
132003
  };
131891
132004
  }
131892
132005
  const tables = { ...payload.tables ?? {} };