siluzan-tso-cli 1.1.33 → 1.1.34-beta.3

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 num5 = +id;
262
- if (num5 >= 0 && num5 < MAX_SAFE_INTEGER) {
263
- return num5;
261
+ const num6 = +id;
262
+ if (num6 >= 0 && num6 < MAX_SAFE_INTEGER) {
263
+ return num6;
264
264
  }
265
265
  }
266
266
  return id;
@@ -3829,7 +3829,7 @@ var DEFAULT_API_BASE;
3829
3829
  var init_defaults = __esm({
3830
3830
  "src/config/defaults.ts"() {
3831
3831
  "use strict";
3832
- DEFAULT_API_BASE = "https://tso-api.siluzan.com";
3832
+ DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
3833
3833
  }
3834
3834
  });
3835
3835
 
@@ -107794,6 +107794,57 @@ async function fetchBingJson(config, url, verbose) {
107794
107794
  // src/commands/report-bing-analysis/run.ts
107795
107795
  init_auth();
107796
107796
  init_balance();
107797
+
107798
+ // src/commands/report-bing-analysis/normalize-bing-overview.ts
107799
+ function num(v) {
107800
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
107801
+ }
107802
+ function roundMoney(v) {
107803
+ return Math.round(v * 100) / 100;
107804
+ }
107805
+ function countInclusiveCalendarDays(startDate, endDate) {
107806
+ const parts = (ymd) => ymd.trim().split("-").map((x) => parseInt(x, 10));
107807
+ const sp = parts(startDate);
107808
+ const ep = parts(endDate);
107809
+ if (sp.length !== 3 || ep.length !== 3 || sp.some((n) => !Number.isFinite(n)) || ep.some((n) => !Number.isFinite(n))) {
107810
+ return 0;
107811
+ }
107812
+ const s = new Date(sp[0], sp[1] - 1, sp[2]);
107813
+ const e = new Date(ep[0], ep[1] - 1, ep[2]);
107814
+ if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime()) || s > e) return 0;
107815
+ const msPerDay = 24 * 60 * 60 * 1e3;
107816
+ return Math.floor((e.getTime() - s.getTime()) / msPerDay) + 1;
107817
+ }
107818
+ function periodSpend(period) {
107819
+ if (!period || typeof period !== "object") return void 0;
107820
+ return num(period.spend);
107821
+ }
107822
+ function enrichBingOverviewDerivedMetrics(record, startDate, endDate) {
107823
+ const out = { ...record };
107824
+ const currentSpend = periodSpend(out.currentPeriod);
107825
+ let totalCost = num(out.totalCost);
107826
+ if (totalCost == null || totalCost <= 0) {
107827
+ if (currentSpend != null && currentSpend > 0) {
107828
+ out.totalCost = currentSpend;
107829
+ totalCost = currentSpend;
107830
+ }
107831
+ }
107832
+ let activeDays = num(out.activeDays);
107833
+ if (activeDays == null || activeDays <= 0) {
107834
+ const calendarDays = countInclusiveCalendarDays(startDate, endDate);
107835
+ if (calendarDays > 0) {
107836
+ out.activeDays = calendarDays;
107837
+ activeDays = calendarDays;
107838
+ }
107839
+ }
107840
+ const averageDailyCost = num(out.averageDailyCost);
107841
+ if ((averageDailyCost == null || averageDailyCost <= 0) && totalCost != null && totalCost > 0 && activeDays != null && activeDays > 0) {
107842
+ out.averageDailyCost = roundMoney(totalCost / activeDays);
107843
+ }
107844
+ return out;
107845
+ }
107846
+
107847
+ // src/commands/report-bing-analysis/run.ts
107797
107848
  async function runReportBingOverview(opts) {
107798
107849
  const id = assertBingAccountId(opts.account);
107799
107850
  const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
@@ -107802,13 +107853,17 @@ async function runReportBingOverview(opts) {
107802
107853
  const url = reportingUrl2(config, id, "OverviewSectionData", params.toString());
107803
107854
  try {
107804
107855
  const raw = await fetchBingJson(config, url, !!opts.verbose);
107805
- const data = raw && typeof raw === "object" && !Array.isArray(raw) ? await enrichOverviewRecordWithRealtimeBalance(
107806
- "BingV2",
107807
- id,
107808
- config,
107809
- raw,
107810
- opts.verbose
107811
- ) : raw;
107856
+ let data = raw;
107857
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
107858
+ const withBalance = await enrichOverviewRecordWithRealtimeBalance(
107859
+ "BingV2",
107860
+ id,
107861
+ config,
107862
+ raw,
107863
+ opts.verbose
107864
+ );
107865
+ data = enrichBingOverviewDerivedMetrics(withBalance, startDate, endDate);
107866
+ }
107812
107867
  if (opts.jsonOut) {
107813
107868
  await emitBingSnapshot(opts, "bing-overview", id, startDate, endDate, data);
107814
107869
  return;
@@ -120851,6 +120906,309 @@ async function runAccountMe(opts) {
120851
120906
  }
120852
120907
  }
120853
120908
 
120909
+ // src/commands/account-manage/google-access-permission.ts
120910
+ init_dist();
120911
+ init_auth();
120912
+ init_cli_json_snapshot();
120913
+
120914
+ // src/commands/account-manage/google-mcc.ts
120915
+ init_auth();
120916
+ init_cli_json_snapshot();
120917
+ function parseMccManagerIds(input) {
120918
+ const normalized = input.replace(/[,,;、]/g, ";");
120919
+ return normalized.split(";").map((part) => {
120920
+ const digits = part.replace(/\D/g, "");
120921
+ return digits ? +digits : NaN;
120922
+ }).filter((n) => Number.isFinite(n) && n > 0);
120923
+ }
120924
+ var MCC_AGENT_TYPES = ["ChengGongYi", "SiGeUS", "SiGeCN", "Dee"];
120925
+ function googleGatewayBase(config) {
120926
+ const raw = (config.googleApiUrl ?? "").replace(/\/$/, "");
120927
+ if (!raw) {
120928
+ console.error("\n\u274C Google \u7F51\u5173\u5730\u5740\u672A\u80FD\u63A8\u5BFC\uFF0C\u8BF7\u68C0\u67E5 tsoApiBaseUrl \u914D\u7F6E\u3002\n");
120929
+ process.exit(1);
120930
+ }
120931
+ const err = validateBaseUrl(raw);
120932
+ if (err) {
120933
+ console.error(`
120934
+ \u274C googleApiUrl \u4E0D\u5408\u6CD5\uFF1A${err}
120935
+ `);
120936
+ process.exit(1);
120937
+ }
120938
+ return raw;
120939
+ }
120940
+ function mccResponseHasSuccess(res, managerId) {
120941
+ if (!Array.isArray(res)) return false;
120942
+ return res.some((item) => String(item) === String(managerId));
120943
+ }
120944
+ async function runAccountMccBind(opts) {
120945
+ const config = loadConfig(opts.token);
120946
+ const base = googleGatewayBase(config);
120947
+ const managerIds = parseMccManagerIds(opts.mcc);
120948
+ if (managerIds.length === 0) {
120949
+ console.error("\n\u274C --mcc \u4E2D\u672A\u89E3\u6790\u51FA\u6709\u6548\u7684\u6570\u5B57\u578B MCC \u5BA2\u6237 ID\n");
120950
+ process.exit(1);
120951
+ }
120952
+ if (opts.customers.length === 0) {
120953
+ console.error("\n\u274C \u8BF7\u901A\u8FC7 --customers \u6307\u5B9A\u81F3\u5C11\u4E00\u4E2A Google \u5B50\u8D26\u6237 mediaCustomerId\n");
120954
+ process.exit(1);
120955
+ }
120956
+ const body = { agentTypes: [...MCC_AGENT_TYPES], managerIds };
120957
+ const results = [];
120958
+ for (const customerId of opts.customers) {
120959
+ const url = `${base}/command/media-account/${customerId}/CustomerLinks`;
120960
+ try {
120961
+ const response = await apiFetch2(
120962
+ url,
120963
+ config,
120964
+ { method: "POST", body: JSON.stringify(body) },
120965
+ opts.verbose
120966
+ );
120967
+ results.push({ customerId, response });
120968
+ } catch (err) {
120969
+ console.error(
120970
+ `
120971
+ \u274C \u8D26\u6237 ${customerId} MCC \u7ED1\u5B9A\u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
120972
+ `
120973
+ );
120974
+ process.exit(1);
120975
+ }
120976
+ }
120977
+ if (await emitCliJsonOrSnapshot(opts, {
120978
+ section: "account-mcc-bind",
120979
+ commandLabel: "account mcc-bind",
120980
+ payload: results,
120981
+ idSuffix: opts.mcc
120982
+ })) {
120983
+ return;
120984
+ }
120985
+ let hasFailure = false;
120986
+ for (const { customerId, response } of results) {
120987
+ const failed = managerIds.filter((mid) => !mccResponseHasSuccess(response, mid));
120988
+ if (failed.length > 0) {
120989
+ hasFailure = true;
120990
+ console.error(` \u274C \u8D26\u6237 ${customerId}\uFF1A\u4EE5\u4E0B MCC \u672A\u5728\u6210\u529F\u5217\u8868\u4E2D \u2192 ${failed.join("\uFF0C")}`);
120991
+ } else {
120992
+ console.log(
120993
+ ` \u2705 \u8D26\u6237 ${customerId}\uFF1A\u5DF2\u5411 ${managerIds.join("\u3001")} \u53D1\u8D77\u7ED1\u5B9A\uFF08\u63A5\u53E3\u8FD4\u56DE\u6210\u529F\uFF09`
120994
+ );
120995
+ }
120996
+ }
120997
+ console.log(
120998
+ hasFailure ? "\n\u26A0\uFE0F \u90E8\u5206 MCC \u7ED1\u5B9A\u53EF\u80FD\u672A\u751F\u6548\uFF0C\u8BF7\u7ED3\u5408\u7F51\u9875\u6216 --json-out \u6838\u5BF9\u3002\n" : "\n\u2705 MCC \u7ED1\u5B9A\u6D41\u7A0B\u5DF2\u5B8C\u6210\u3002\n"
120999
+ );
121000
+ }
121001
+ async function runAccountMccUnbind(opts) {
121002
+ const config = loadConfig(opts.token);
121003
+ const base = googleGatewayBase(config);
121004
+ const managerIds = parseMccManagerIds(opts.mcc);
121005
+ if (managerIds.length === 0) {
121006
+ console.error("\n\u274C --mcc \u4E2D\u672A\u89E3\u6790\u51FA\u6709\u6548\u7684\u6570\u5B57\u578B MCC \u5BA2\u6237 ID\n");
121007
+ process.exit(1);
121008
+ }
121009
+ if (opts.customers.length === 0) {
121010
+ console.error("\n\u274C \u8BF7\u901A\u8FC7 --customers \u6307\u5B9A\u81F3\u5C11\u4E00\u4E2A Google \u5B50\u8D26\u6237 mediaCustomerId\n");
121011
+ process.exit(1);
121012
+ }
121013
+ const results = [];
121014
+ for (const customerId of opts.customers) {
121015
+ const url = `${base}/command/media-account/${customerId}/CustomerUnlinks`;
121016
+ try {
121017
+ const response = await apiFetch2(
121018
+ url,
121019
+ config,
121020
+ { method: "POST", body: JSON.stringify(managerIds) },
121021
+ opts.verbose
121022
+ );
121023
+ results.push({ customerId, response });
121024
+ } catch (err) {
121025
+ console.error(
121026
+ `
121027
+ \u274C \u8D26\u6237 ${customerId} MCC \u89E3\u7ED1\u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
121028
+ `
121029
+ );
121030
+ process.exit(1);
121031
+ }
121032
+ }
121033
+ if (await emitCliJsonOrSnapshot(opts, {
121034
+ section: "account-mcc-unbind",
121035
+ commandLabel: "account mcc-unbind",
121036
+ payload: results,
121037
+ idSuffix: opts.mcc
121038
+ })) {
121039
+ return;
121040
+ }
121041
+ let hasFailure = false;
121042
+ for (const { customerId, response } of results) {
121043
+ const failed = managerIds.filter((mid) => !mccResponseHasSuccess(response, mid));
121044
+ if (failed.length > 0) {
121045
+ hasFailure = true;
121046
+ console.error(` \u274C \u8D26\u6237 ${customerId}\uFF1A\u4EE5\u4E0B MCC \u672A\u5728\u6210\u529F\u89E3\u7ED1\u5217\u8868\u4E2D \u2192 ${failed.join("\uFF0C")}`);
121047
+ } else {
121048
+ console.log(
121049
+ ` \u2705 \u8D26\u6237 ${customerId}\uFF1A\u5DF2\u5411 ${managerIds.join("\u3001")} \u53D1\u8D77\u89E3\u7ED1\uFF08\u63A5\u53E3\u8FD4\u56DE\u6210\u529F\uFF09`
121050
+ );
121051
+ }
121052
+ }
121053
+ console.log(
121054
+ hasFailure ? "\n\u26A0\uFE0F \u90E8\u5206 MCC \u89E3\u7ED1\u53EF\u80FD\u672A\u751F\u6548\uFF0C\u8BF7\u7ED3\u5408\u7F51\u9875\u6216 --json-out \u6838\u5BF9\u3002\n" : "\n\u2705 MCC \u89E3\u7ED1\u6D41\u7A0B\u5DF2\u5B8C\u6210\u3002\n"
121055
+ );
121056
+ }
121057
+
121058
+ // src/commands/account-manage/google-access-permission.ts
121059
+ function assertGoogleMediaCustomerId(raw) {
121060
+ const id = raw.trim();
121061
+ if (!id) {
121062
+ console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\u3002\n");
121063
+ process.exit(1);
121064
+ }
121065
+ if (!/^\d+$/.test(id)) {
121066
+ console.error("\n\u274C --account \u987B\u4E3A Google mediaCustomerId\uFF08\u7EAF\u6570\u5B57\uFF0C\u4E0E list-accounts -m Google \u4E00\u81F4\uFF09\u3002\n");
121067
+ process.exit(1);
121068
+ }
121069
+ return id;
121070
+ }
121071
+ function parseCustomerAccessPermissionBody(text) {
121072
+ const trimmed = text.trim();
121073
+ if (!trimmed) return null;
121074
+ try {
121075
+ const parsed = JSON.parse(trimmed);
121076
+ if (typeof parsed === "boolean") return parsed;
121077
+ if (typeof parsed === "string") return parsed;
121078
+ } catch {
121079
+ }
121080
+ return trimmed;
121081
+ }
121082
+ function interpretCustomerAccessPermission(mediaCustomerId, httpStatus, rawBody) {
121083
+ const base = {
121084
+ mediaCustomerId,
121085
+ httpStatus,
121086
+ rawBody
121087
+ };
121088
+ if (httpStatus === 200) {
121089
+ if (rawBody === true) {
121090
+ return {
121091
+ ...base,
121092
+ status: "accessible",
121093
+ accessible: true,
121094
+ message: "\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u53EF\u8BBF\u95EE\u8BE5 Google \u5E7F\u544A\u8D26\u6237\u3002"
121095
+ };
121096
+ }
121097
+ if (rawBody === false) {
121098
+ return {
121099
+ ...base,
121100
+ status: "reauth_required",
121101
+ accessible: false,
121102
+ message: "\u8D26\u6237\u5DF2\u7ED1\u5B9A\u4F46 Google OAuth \u4E0D\u53EF\u7528\uFF0C\u9700\u91CD\u65B0\u6388\u6743\u3002",
121103
+ hint: "list-accounts --json-out \u53D6 ma.entityId \u540E\u6267\u884C\uFF1Asiluzan-tso account reauth -m Google --id <entityId>"
121104
+ };
121105
+ }
121106
+ return {
121107
+ ...base,
121108
+ status: "unknown",
121109
+ accessible: false,
121110
+ message: `HTTP 200 \u4F46\u54CD\u5E94\u4F53\u975E\u5E38\u89C4\uFF08${String(rawBody)}\uFF09\uFF0C\u8BF7\u7528 --verbose \u67E5\u770B\u539F\u59CB\u54CD\u5E94\u3002`
121111
+ };
121112
+ }
121113
+ if (httpStatus === 403) {
121114
+ const text = typeof rawBody === "string" ? rawBody : rawBody == null ? "" : String(rawBody);
121115
+ if (text.includes("token\u4E0D\u80FD\u4E3A\u7A7A") || text.toLowerCase().includes("token") && text.includes("\u7A7A")) {
121116
+ return {
121117
+ ...base,
121118
+ status: "google_not_bound",
121119
+ accessible: false,
121120
+ message: "\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u672A\u7ED1\u5B9A Google \u5A92\u4F53\u6388\u6743\u3002",
121121
+ hint: "\u6267\u884C siluzan-tso account auth -m Google \u5B8C\u6210 OAuth \u7ED1\u5B9A\u3002"
121122
+ };
121123
+ }
121124
+ return {
121125
+ ...base,
121126
+ status: "no_permission",
121127
+ accessible: false,
121128
+ message: "\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u5BF9\u8BE5 Google \u5E7F\u544A\u8D26\u6237\u65E0\u8BBF\u95EE\u6743\u9650\uFF1B\u901A\u5E38\u8868\u793A\u8BE5\u8D26\u6237\u4E0D\u5728\u672C\u8D26\u53F7\u4E0B\uFF08\u6216\u672A\u5206\u4EAB\u7ED9\u4F60\uFF09\u3002",
121129
+ hint: "\u8BF7\u786E\u8BA4 mediaCustomerId \u662F\u5426\u6B63\u786E\uFF0C\u6216\u5207\u6362\u5230\u62E5\u6709\u8BE5\u8D26\u6237\u7684\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u767B\u5F55\uFF08send-login-code + login\uFF09\u3002"
121130
+ };
121131
+ }
121132
+ if (httpStatus === 401) {
121133
+ return {
121134
+ ...base,
121135
+ status: "siluzan_token_invalid",
121136
+ accessible: false,
121137
+ message: "\u4E1D\u8DEF\u8D5E\u767B\u5F55\u51ED\u636E\u65E0\u6548\u6216\u5DF2\u8FC7\u671F\u3002",
121138
+ hint: "\u6267\u884C send-login-code + login --phone --code \u91CD\u65B0\u767B\u5F55\uFF0C\u6216 siluzan-tso config show \u68C0\u67E5 Token\u3002"
121139
+ };
121140
+ }
121141
+ return {
121142
+ ...base,
121143
+ status: "unknown",
121144
+ accessible: false,
121145
+ message: `\u672A\u9884\u671F\u7684 HTTP ${httpStatus}\uFF1B\u8BF7\u7528 --verbose \u67E5\u770B\u539F\u59CB\u54CD\u5E94\u3002`
121146
+ };
121147
+ }
121148
+ function buildCustomerAccessPermissionUrl(googleApiUrl, mediaCustomerId) {
121149
+ const base = googleApiUrl.replace(/\/$/, "");
121150
+ return `${base}/query/media-account/${mediaCustomerId}/customer-access-permission`;
121151
+ }
121152
+ async function fetchGoogleCustomerAccessPermission(config, mediaCustomerId, verbose) {
121153
+ await ensureDataPermission(config);
121154
+ const url = buildCustomerAccessPermissionUrl(googleGatewayBase(config), mediaCustomerId);
121155
+ const res = await rawRequest(url, {
121156
+ method: "GET",
121157
+ headers: {
121158
+ "Content-Type": "application/json",
121159
+ "Accept-Language": "zh-CN",
121160
+ Accept: "application/json",
121161
+ ...buildSiluzanAuthHeaders(config),
121162
+ Datapermission: config.dataPermission ?? ""
121163
+ }
121164
+ });
121165
+ if (verbose && res.text) {
121166
+ console.error(` [verbose] HTTP ${res.status} body: ${res.text.slice(0, 300)}`);
121167
+ }
121168
+ const rawBody = parseCustomerAccessPermissionBody(res.text);
121169
+ return interpretCustomerAccessPermission(mediaCustomerId, res.status, rawBody);
121170
+ }
121171
+ async function runAccountCheckAccess(opts) {
121172
+ const mediaCustomerId = assertGoogleMediaCustomerId(opts.account);
121173
+ const config = loadConfig(opts.token);
121174
+ const payload = await fetchGoogleCustomerAccessPermission(
121175
+ config,
121176
+ mediaCustomerId,
121177
+ opts.verbose
121178
+ );
121179
+ if (await emitCliJsonOrSnapshot(opts, {
121180
+ section: "account-check-access",
121181
+ commandLabel: "account check-access",
121182
+ payload
121183
+ })) {
121184
+ if (!payload.accessible) {
121185
+ process.exit(1);
121186
+ }
121187
+ return;
121188
+ }
121189
+ const statusLabel = {
121190
+ accessible: "\u53EF\u8BBF\u95EE",
121191
+ reauth_required: "\u9700\u91CD\u65B0\u6388\u6743",
121192
+ no_permission: "\u65E0\u6743\u9650",
121193
+ google_not_bound: "\u672A\u7ED1\u5B9A Google",
121194
+ siluzan_token_invalid: "\u4E1D\u8DEF\u8D5E\u51ED\u636E\u5931\u6548",
121195
+ unknown: "\u672A\u77E5"
121196
+ };
121197
+ console.log(`
121198
+ Google \u8D26\u6237\u8BBF\u95EE\u6743\u9650\u6821\u9A8C ${mediaCustomerId}`);
121199
+ console.log(` \u72B6\u6001 : ${statusLabel[payload.status]} (${payload.status})`);
121200
+ console.log(` HTTP : ${payload.httpStatus}`);
121201
+ console.log(` \u53EF\u8BBF\u95EE : ${payload.accessible ? "\u662F" : "\u5426"}`);
121202
+ console.log(` \u8BF4\u660E : ${payload.message}`);
121203
+ if (payload.hint) {
121204
+ console.log(` \u5EFA\u8BAE : ${payload.hint}`);
121205
+ }
121206
+ console.log();
121207
+ if (!payload.accessible) {
121208
+ process.exit(1);
121209
+ }
121210
+ }
121211
+
120854
121212
  // src/commands/account-manage/share.ts
120855
121213
  init_auth();
120856
121214
  init_cli_json_snapshot();
@@ -121075,150 +121433,6 @@ async function runAccountReauth(opts) {
121075
121433
  });
121076
121434
  }
121077
121435
 
121078
- // src/commands/account-manage/google-mcc.ts
121079
- init_auth();
121080
- init_cli_json_snapshot();
121081
- function parseMccManagerIds(input) {
121082
- const normalized = input.replace(/[,,;、]/g, ";");
121083
- return normalized.split(";").map((part) => {
121084
- const digits = part.replace(/\D/g, "");
121085
- return digits ? +digits : NaN;
121086
- }).filter((n) => Number.isFinite(n) && n > 0);
121087
- }
121088
- var MCC_AGENT_TYPES = ["ChengGongYi", "SiGeUS", "SiGeCN", "Dee"];
121089
- function googleGatewayBase(config) {
121090
- const raw = (config.googleApiUrl ?? "").replace(/\/$/, "");
121091
- if (!raw) {
121092
- console.error("\n\u274C Google \u7F51\u5173\u5730\u5740\u672A\u80FD\u63A8\u5BFC\uFF0C\u8BF7\u68C0\u67E5 tsoApiBaseUrl \u914D\u7F6E\u3002\n");
121093
- process.exit(1);
121094
- }
121095
- const err = validateBaseUrl(raw);
121096
- if (err) {
121097
- console.error(`
121098
- \u274C googleApiUrl \u4E0D\u5408\u6CD5\uFF1A${err}
121099
- `);
121100
- process.exit(1);
121101
- }
121102
- return raw;
121103
- }
121104
- function mccResponseHasSuccess(res, managerId) {
121105
- if (!Array.isArray(res)) return false;
121106
- return res.some((item) => String(item) === String(managerId));
121107
- }
121108
- async function runAccountMccBind(opts) {
121109
- const config = loadConfig(opts.token);
121110
- const base = googleGatewayBase(config);
121111
- const managerIds = parseMccManagerIds(opts.mcc);
121112
- if (managerIds.length === 0) {
121113
- console.error("\n\u274C --mcc \u4E2D\u672A\u89E3\u6790\u51FA\u6709\u6548\u7684\u6570\u5B57\u578B MCC \u5BA2\u6237 ID\n");
121114
- process.exit(1);
121115
- }
121116
- if (opts.customers.length === 0) {
121117
- console.error("\n\u274C \u8BF7\u901A\u8FC7 --customers \u6307\u5B9A\u81F3\u5C11\u4E00\u4E2A Google \u5B50\u8D26\u6237 mediaCustomerId\n");
121118
- process.exit(1);
121119
- }
121120
- const body = { agentTypes: [...MCC_AGENT_TYPES], managerIds };
121121
- const results = [];
121122
- for (const customerId of opts.customers) {
121123
- const url = `${base}/command/media-account/${customerId}/CustomerLinks`;
121124
- try {
121125
- const response = await apiFetch2(
121126
- url,
121127
- config,
121128
- { method: "POST", body: JSON.stringify(body) },
121129
- opts.verbose
121130
- );
121131
- results.push({ customerId, response });
121132
- } catch (err) {
121133
- console.error(
121134
- `
121135
- \u274C \u8D26\u6237 ${customerId} MCC \u7ED1\u5B9A\u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
121136
- `
121137
- );
121138
- process.exit(1);
121139
- }
121140
- }
121141
- if (await emitCliJsonOrSnapshot(opts, {
121142
- section: "account-mcc-bind",
121143
- commandLabel: "account mcc-bind",
121144
- payload: results,
121145
- idSuffix: opts.mcc
121146
- })) {
121147
- return;
121148
- }
121149
- let hasFailure = false;
121150
- for (const { customerId, response } of results) {
121151
- const failed = managerIds.filter((mid) => !mccResponseHasSuccess(response, mid));
121152
- if (failed.length > 0) {
121153
- hasFailure = true;
121154
- console.error(` \u274C \u8D26\u6237 ${customerId}\uFF1A\u4EE5\u4E0B MCC \u672A\u5728\u6210\u529F\u5217\u8868\u4E2D \u2192 ${failed.join("\uFF0C")}`);
121155
- } else {
121156
- console.log(
121157
- ` \u2705 \u8D26\u6237 ${customerId}\uFF1A\u5DF2\u5411 ${managerIds.join("\u3001")} \u53D1\u8D77\u7ED1\u5B9A\uFF08\u63A5\u53E3\u8FD4\u56DE\u6210\u529F\uFF09`
121158
- );
121159
- }
121160
- }
121161
- console.log(
121162
- hasFailure ? "\n\u26A0\uFE0F \u90E8\u5206 MCC \u7ED1\u5B9A\u53EF\u80FD\u672A\u751F\u6548\uFF0C\u8BF7\u7ED3\u5408\u7F51\u9875\u6216 --json-out \u6838\u5BF9\u3002\n" : "\n\u2705 MCC \u7ED1\u5B9A\u6D41\u7A0B\u5DF2\u5B8C\u6210\u3002\n"
121163
- );
121164
- }
121165
- async function runAccountMccUnbind(opts) {
121166
- const config = loadConfig(opts.token);
121167
- const base = googleGatewayBase(config);
121168
- const managerIds = parseMccManagerIds(opts.mcc);
121169
- if (managerIds.length === 0) {
121170
- console.error("\n\u274C --mcc \u4E2D\u672A\u89E3\u6790\u51FA\u6709\u6548\u7684\u6570\u5B57\u578B MCC \u5BA2\u6237 ID\n");
121171
- process.exit(1);
121172
- }
121173
- if (opts.customers.length === 0) {
121174
- console.error("\n\u274C \u8BF7\u901A\u8FC7 --customers \u6307\u5B9A\u81F3\u5C11\u4E00\u4E2A Google \u5B50\u8D26\u6237 mediaCustomerId\n");
121175
- process.exit(1);
121176
- }
121177
- const results = [];
121178
- for (const customerId of opts.customers) {
121179
- const url = `${base}/command/media-account/${customerId}/CustomerUnlinks`;
121180
- try {
121181
- const response = await apiFetch2(
121182
- url,
121183
- config,
121184
- { method: "POST", body: JSON.stringify(managerIds) },
121185
- opts.verbose
121186
- );
121187
- results.push({ customerId, response });
121188
- } catch (err) {
121189
- console.error(
121190
- `
121191
- \u274C \u8D26\u6237 ${customerId} MCC \u89E3\u7ED1\u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
121192
- `
121193
- );
121194
- process.exit(1);
121195
- }
121196
- }
121197
- if (await emitCliJsonOrSnapshot(opts, {
121198
- section: "account-mcc-unbind",
121199
- commandLabel: "account mcc-unbind",
121200
- payload: results,
121201
- idSuffix: opts.mcc
121202
- })) {
121203
- return;
121204
- }
121205
- let hasFailure = false;
121206
- for (const { customerId, response } of results) {
121207
- const failed = managerIds.filter((mid) => !mccResponseHasSuccess(response, mid));
121208
- if (failed.length > 0) {
121209
- hasFailure = true;
121210
- console.error(` \u274C \u8D26\u6237 ${customerId}\uFF1A\u4EE5\u4E0B MCC \u672A\u5728\u6210\u529F\u89E3\u7ED1\u5217\u8868\u4E2D \u2192 ${failed.join("\uFF0C")}`);
121211
- } else {
121212
- console.log(
121213
- ` \u2705 \u8D26\u6237 ${customerId}\uFF1A\u5DF2\u5411 ${managerIds.join("\u3001")} \u53D1\u8D77\u89E3\u7ED1\uFF08\u63A5\u53E3\u8FD4\u56DE\u6210\u529F\uFF09`
121214
- );
121215
- }
121216
- }
121217
- console.log(
121218
- hasFailure ? "\n\u26A0\uFE0F \u90E8\u5206 MCC \u89E3\u7ED1\u53EF\u80FD\u672A\u751F\u6548\uFF0C\u8BF7\u7ED3\u5408\u7F51\u9875\u6216 --json-out \u6838\u5BF9\u3002\n" : "\n\u2705 MCC \u89E3\u7ED1\u6D41\u7A0B\u5DF2\u5B8C\u6210\u3002\n"
121219
- );
121220
- }
121221
-
121222
121436
  // src/commands/account-manage/tiktok-close.ts
121223
121437
  init_auth();
121224
121438
  init_cli_json_snapshot();
@@ -121988,6 +122202,21 @@ function register23(program2) {
121988
122202
  });
121989
122203
  }
121990
122204
  );
122205
+ accountCmd.command("check-access").description(
122206
+ "\u6821\u9A8C Google \u5E7F\u544A\u8D26\u6237\u8BBF\u95EE\u6743\u9650\uFF08GET googleapi \u2026/customer-access-permission\uFF09\uFF1B403 \u901A\u5E38\u8868\u793A\u8D26\u6237\u4E0D\u5728\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u4E0B"
122207
+ ).requiredOption(
122208
+ "-a, --account <mediaCustomerId>",
122209
+ "Google mediaCustomerId\uFF08\u7EAF\u6570\u5B57\uFF0C\u4E0E list-accounts -m Google \u4E00\u81F4\uFF09"
122210
+ ).option("-t, --token <token>", "Auth Token").option("--json-out <path>", "\u843D\u76D8 JSON\uFF1Bstdout \u4E00\u884C\u6458\u8981\uFF1B\u4E0D\u53EF\u8BBF\u95EE\u65F6 exit 1").option("--verbose", "\u6253\u5370\u539F\u59CB HTTP \u54CD\u5E94\u7247\u6BB5", false).action(
122211
+ async (opts) => {
122212
+ await runAccountCheckAccess({
122213
+ account: opts.account,
122214
+ token: opts.token,
122215
+ jsonOut: opts.jsonOut,
122216
+ verbose: opts.verbose
122217
+ });
122218
+ }
122219
+ );
121991
122220
  accountCmd.command("delink").description(
121992
122221
  "\u89E3\u9664\u6388\u6743\uFF1A\u65AD\u5F00\u5E7F\u544A\u8D26\u6237\u4E0E\u5F53\u524D\u4E1D\u8DEF\u8D5E\u8D26\u53F7\u7684\u5173\u8054\uFF08\u7F51\u9875 manageAccounts\u300C\u89E3\u9664\u6388\u6743\u300D\uFF1B--id \u6216 --ids\uFF09"
121993
122222
  ).option("--id <entityId>", "\u5355\u4E2A\u8D26\u6237 entityId\uFF08\u6765\u81EA list-accounts \u7684 ma.entityId \u5B57\u6BB5\uFF09").option("--ids <entityIds>", "\u6279\u91CF entityId\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u4E0E --id \u4E8C\u9009\u4E00\uFF0C\u53EF\u540C\u65F6\u4F20\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
@@ -125282,12 +125511,12 @@ function sectionRecord(env) {
125282
125511
  }
125283
125512
 
125284
125513
  // src/commands/google-ads-diagnosis/build-report-data.ts
125285
- function num(value) {
125514
+ function num2(value) {
125286
125515
  const n = Number(value);
125287
125516
  return Number.isFinite(n) ? n : 0;
125288
125517
  }
125289
125518
  function rowCost(row) {
125290
- return num(row.cost ?? row.spend);
125519
+ return num2(row.cost ?? row.spend);
125291
125520
  }
125292
125521
  function pct(value) {
125293
125522
  return `${(value * 100).toFixed(2)}%`;
@@ -125318,11 +125547,11 @@ function asPeriodRecord(value) {
125318
125547
  }
125319
125548
  function toKeyMetricRow(title, current, previous) {
125320
125549
  const currentCost = rowCost(current);
125321
- const currentClicks = num(current.clicks);
125322
- const currentCvr = num(current.conversionRate ?? current.cvr);
125550
+ const currentClicks = num2(current.clicks);
125551
+ const currentCvr = num2(current.conversionRate ?? current.cvr);
125323
125552
  const previousCost = previous ? rowCost(previous) : null;
125324
- const previousClicks = previous ? num(previous.clicks) : null;
125325
- const previousCvr = previous ? num(previous.conversionRate ?? previous.cvr) : null;
125553
+ const previousClicks = previous ? num2(previous.clicks) : null;
125554
+ const previousCvr = previous ? num2(previous.conversionRate ?? previous.cvr) : null;
125326
125555
  return {
125327
125556
  title,
125328
125557
  currentCost,
@@ -125352,13 +125581,13 @@ function buildComparisonItems(currentRows, previousRows, titleFn, keyFn) {
125352
125581
  }
125353
125582
  function toTargetingItem(row, labelKey, label) {
125354
125583
  const cost = rowCost(row);
125355
- const clicks = num(row.clicks);
125356
- const impressions = num(row.impressions);
125357
- const conversions = num(row.conversions);
125358
- const ctr = num(row.ctr);
125359
- const cvr = num(row.conversionRate ?? row.cvr);
125360
- const cpc = num(row.averageCpc ?? row.cpc);
125361
- const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
125584
+ const clicks = num2(row.clicks);
125585
+ const impressions = num2(row.impressions);
125586
+ const conversions = num2(row.conversions);
125587
+ const ctr = num2(row.ctr);
125588
+ const cvr = num2(row.conversionRate ?? row.cvr);
125589
+ const cpc = num2(row.averageCpc ?? row.cpc);
125590
+ const cpa = conversions > 0 ? cost / conversions : num2(row.costPerConversion ?? row.cpa);
125362
125591
  return {
125363
125592
  [labelKey]: label,
125364
125593
  cost,
@@ -125373,11 +125602,11 @@ function toTargetingItem(row, labelKey, label) {
125373
125602
  }
125374
125603
  function toDailyConversionItem(row) {
125375
125604
  const cost = rowCost(row);
125376
- const conversions = num(row.conversions);
125605
+ const conversions = num2(row.conversions);
125377
125606
  const rawDate = String(row.date ?? "");
125378
125607
  const date = rawDate.includes("T") ? rawDate.slice(0, 10) : rawDate.slice(0, 10);
125379
- const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
125380
- return { date, cost, conversions, cpa, clicks: num(row.clicks), impressions: num(row.impressions) };
125608
+ const cpa = conversions > 0 ? cost / conversions : num2(row.costPerConversion ?? row.cpa);
125609
+ return { date, cost, conversions, cpa, clicks: num2(row.clicks), impressions: num2(row.impressions) };
125381
125610
  }
125382
125611
  function loadCompanyNameFromSnap(snapDir) {
125383
125612
  const file = path21.join(snapDir, "list-accounts-google.json");
@@ -125414,8 +125643,8 @@ function inferBusinessProfile(campaignRows, adRows, website) {
125414
125643
  };
125415
125644
  }
125416
125645
  function primaryConversionLabel(rows) {
125417
- const sorted = [...rows].sort((a, b) => num(b.allConversions) - num(a.allConversions));
125418
- const top = sorted.find((r) => num(r.allConversions) > 0 && String(r.status) === "Enabled");
125646
+ const sorted = [...rows].sort((a, b) => num2(b.allConversions) - num2(a.allConversions));
125647
+ const top = sorted.find((r) => num2(r.allConversions) > 0 && String(r.status) === "Enabled");
125419
125648
  if (top) return String(top.name ?? "\u8BE2\u76D8/\u8F6C\u5316");
125420
125649
  const enabled = rows.find((r) => String(r.status) === "Enabled");
125421
125650
  return enabled ? String(enabled.name ?? "\u8BE2\u76D8/\u8F6C\u5316") : "\u8BE2\u76D8/\u8F6C\u5316";
@@ -125434,7 +125663,7 @@ function buildBiddingStrategyItems(campaignRows, endDate, gold) {
125434
125663
  const strategy = String(row.biddingStrategyTypeV2 ?? row.biddingStrategyType ?? "UNKNOWN");
125435
125664
  const status = String(row.campaignStatus ?? row.campaignStatusV2 ?? "");
125436
125665
  const isPaused = /paused/i.test(status);
125437
- const recommended = num(row.conversions) > 0 ? "MAXIMIZE_CONVERSIONS" : "TARGET_SPEND";
125666
+ const recommended = num2(row.conversions) > 0 ? "MAXIMIZE_CONVERSIONS" : "TARGET_SPEND";
125438
125667
  const isCorrect = policyOk && !isPaused && strategy === recommended;
125439
125668
  return {
125440
125669
  campaignName: String(row.campaignName ?? "\u672A\u547D\u540D\u7CFB\u5217"),
@@ -125447,16 +125676,16 @@ function buildBiddingStrategyItems(campaignRows, endDate, gold) {
125447
125676
  });
125448
125677
  }
125449
125678
  function buildStructureCounts(resourceRecord, campaignRows, keywordRows, searchRows) {
125450
- const campaignCount = Math.max(num(resourceRecord?.campaignCount), campaignRows.length);
125451
- const keywordCount = Math.max(num(resourceRecord?.keywordCount), keywordRows.length);
125679
+ const campaignCount = Math.max(num2(resourceRecord?.campaignCount), campaignRows.length);
125680
+ const keywordCount = Math.max(num2(resourceRecord?.keywordCount), keywordRows.length);
125452
125681
  return {
125453
125682
  campaignCount,
125454
- adGroupCount: num(resourceRecord?.adGroupCount),
125683
+ adGroupCount: num2(resourceRecord?.adGroupCount),
125455
125684
  keywordCount,
125456
- adCount: num(resourceRecord?.adCount),
125457
- extensionCount: num(resourceRecord?.extensionCount),
125458
- negativeKeywordCount: num(resourceRecord?.negativeKeywordCount),
125459
- countriesWithConversionsCount: num(resourceRecord?.countriesWithConversionsCount),
125685
+ adCount: num2(resourceRecord?.adCount),
125686
+ extensionCount: num2(resourceRecord?.extensionCount),
125687
+ negativeKeywordCount: num2(resourceRecord?.negativeKeywordCount),
125688
+ countriesWithConversionsCount: num2(resourceRecord?.countriesWithConversionsCount),
125460
125689
  searchTermCount: searchRows.length
125461
125690
  };
125462
125691
  }
@@ -125470,26 +125699,26 @@ function loadWebsiteFromAds(snapDir, accountId, start, end) {
125470
125699
  }
125471
125700
  function buildKeywordShareItems(rows) {
125472
125701
  const totalCost = rows.reduce((sum, row) => sum + rowCost(row), 0);
125473
- const totalConversions = rows.reduce((sum, row) => sum + num(row.conversions), 0);
125702
+ const totalConversions = rows.reduce((sum, row) => sum + num2(row.conversions), 0);
125474
125703
  return rows.map((row) => {
125475
125704
  const cost = rowCost(row);
125476
- const conversions = num(row.conversions);
125705
+ const conversions = num2(row.conversions);
125477
125706
  const keywordText = String(row.keywordText ?? row.keyword ?? "").trim();
125478
125707
  return {
125479
125708
  keyword: keywordText || "\u672A\u547D\u540D",
125480
- cpc: num(row.averageCpc ?? row.cpc),
125709
+ cpc: num2(row.averageCpc ?? row.cpc),
125481
125710
  matchType: String(
125482
125711
  row.keywordMatchTypeZh ?? row.keywordMatchType ?? row.matchType ?? ""
125483
125712
  ),
125484
- impressions: num(row.impressions),
125485
- clicks: num(row.clicks),
125713
+ impressions: num2(row.impressions),
125714
+ clicks: num2(row.clicks),
125486
125715
  cost,
125487
125716
  conversions,
125488
125717
  costShare: totalCost > 0 ? cost / totalCost * 100 : 0,
125489
125718
  conversionsShare: totalConversions > 0 ? conversions / totalConversions * 100 : 0,
125490
125719
  cpa: conversions > 0 ? cost / conversions : 0,
125491
- ctr: num(row.ctr),
125492
- cvr: num(row.conversionRate ?? row.cvr)
125720
+ ctr: num2(row.ctr),
125721
+ cvr: num2(row.conversionRate ?? row.cvr)
125493
125722
  };
125494
125723
  });
125495
125724
  }
@@ -125497,17 +125726,17 @@ function buildSearchTermItems(rows) {
125497
125726
  const sorted = [...rows].sort((a, b) => rowCost(b) - rowCost(a));
125498
125727
  return sorted.slice(0, 50).map((row) => {
125499
125728
  const cost = rowCost(row);
125500
- const conversions = num(row.conversions);
125501
- const cpa = conversions > 0 ? num(row.costPerConversion) || cost / conversions : void 0;
125729
+ const conversions = num2(row.conversions);
125730
+ const cpa = conversions > 0 ? num2(row.costPerConversion) || cost / conversions : void 0;
125502
125731
  return {
125503
125732
  searchTerm: String(row.searchTermText ?? row.searchTerm ?? "\u672A\u547D\u540D"),
125504
125733
  keyword: String(row.keywordText ?? row.keyword ?? ""),
125505
125734
  cost,
125506
125735
  conversions,
125507
- clicks: num(row.clicks),
125508
- impressions: num(row.impressions),
125509
- ctr: num(row.ctr),
125510
- cvr: num(row.conversionRate ?? row.cvr),
125736
+ clicks: num2(row.clicks),
125737
+ impressions: num2(row.impressions),
125738
+ ctr: num2(row.ctr),
125739
+ cvr: num2(row.conversionRate ?? row.cvr),
125511
125740
  cpa
125512
125741
  };
125513
125742
  });
@@ -125549,18 +125778,18 @@ function buildAdCreativeItems(rows) {
125549
125778
  }
125550
125779
  function buildBudgetCompetitiveness(overviewRecord, dimensionRecord) {
125551
125780
  const current = asPeriodRecord(overviewRecord?.currentPeriod);
125552
- const totalCost = num(overviewRecord?.totalCost ?? current?.spend);
125553
- const activeDays = num(overviewRecord?.activeDays);
125554
- const avgDaily = num(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
125555
- const remainingBudget = num(overviewRecord?.remainingAccountBudget);
125781
+ const totalCost = num2(overviewRecord?.totalCost ?? current?.spend);
125782
+ const activeDays = num2(overviewRecord?.activeDays);
125783
+ const avgDaily = num2(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
125784
+ const remainingBudget = num2(overviewRecord?.remainingAccountBudget);
125556
125785
  const utilization = remainingBudget > 0 && avgDaily > 0 ? Math.min(100, avgDaily / (avgDaily + remainingBudget / Math.max(activeDays, 1)) * 100) : avgDaily > 0 ? 85 : 0;
125557
- const sis = num(
125786
+ const sis = num2(
125558
125787
  dimensionRecord?.searchImpressionShare ?? current?.searchImpressionShare
125559
125788
  );
125560
- const budgetLost = num(
125789
+ const budgetLost = num2(
125561
125790
  dimensionRecord?.searchBudgetLostImpressionShare ?? current?.searchBudgetLostImpressionShare
125562
125791
  );
125563
- const rankLost = num(
125792
+ const rankLost = num2(
125564
125793
  dimensionRecord?.searchRankLostImpressionShare ?? current?.searchRankLostImpressionShare
125565
125794
  );
125566
125795
  return [
@@ -125646,17 +125875,17 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125646
125875
  };
125647
125876
  data.reportDate = end;
125648
125877
  const cost = rowCost(currentPeriod ?? overviewRecord ?? {});
125649
- const impressions = num(currentPeriod?.impressions);
125650
- const clicks = num(currentPeriod?.clicks);
125651
- const conversions = num(currentPeriod?.conversions);
125652
- const ctr = num(currentPeriod?.ctr);
125653
- const cvr = num(currentPeriod?.conversionRate ?? currentPeriod?.cvr);
125654
- const cpa = conversions > 0 ? cost / conversions : num(currentPeriod?.costPerConversion);
125878
+ const impressions = num2(currentPeriod?.impressions);
125879
+ const clicks = num2(currentPeriod?.clicks);
125880
+ const conversions = num2(currentPeriod?.conversions);
125881
+ const ctr = num2(currentPeriod?.ctr);
125882
+ const cvr = num2(currentPeriod?.conversionRate ?? currentPeriod?.cvr);
125883
+ const cpa = conversions > 0 ? cost / conversions : num2(currentPeriod?.costPerConversion);
125655
125884
  const prevCost = previousPeriod ? rowCost(previousPeriod) : 0;
125656
- const prevConversions = previousPeriod ? num(previousPeriod.conversions) : 0;
125657
- const prevCtr = previousPeriod ? num(previousPeriod.ctr) : 0;
125658
- const prevCvr = previousPeriod ? num(previousPeriod.conversionRate ?? previousPeriod.cvr) : 0;
125659
- const prevCpa = prevConversions > 0 ? rowCost(previousPeriod) / prevConversions : num(previousPeriod?.costPerConversion);
125885
+ const prevConversions = previousPeriod ? num2(previousPeriod.conversions) : 0;
125886
+ const prevCtr = previousPeriod ? num2(previousPeriod.ctr) : 0;
125887
+ const prevCvr = previousPeriod ? num2(previousPeriod.conversionRate ?? previousPeriod.cvr) : 0;
125888
+ const prevCpa = prevConversions > 0 ? rowCost(previousPeriod) / prevConversions : num2(previousPeriod?.costPerConversion);
125660
125889
  data.metrics = {
125661
125890
  cost,
125662
125891
  impressions,
@@ -125692,8 +125921,8 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125692
125921
  items: conversionRows.map((row) => ({
125693
125922
  name: String(row.name ?? row.eventName ?? "\u672A\u77E5\u4E8B\u4EF6"),
125694
125923
  status: String(row.status ?? ""),
125695
- allConversions: num(row.allConversions),
125696
- allConversionsValue: num(row.allConversionsValue)
125924
+ allConversions: num2(row.allConversions),
125925
+ allConversionsValue: num2(row.allConversionsValue)
125697
125926
  }))
125698
125927
  };
125699
125928
  const campaignPrevious = sectionItems(
@@ -125777,16 +126006,16 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125777
126006
  const lighthouse = opts.landingPage ?? {};
125778
126007
  data.landingPageAnalysis = {
125779
126008
  desktop: {
125780
- score: num(lighthouse.desktop?.score),
125781
- firstContentfulPaint: num(lighthouse.desktop?.firstContentfulPaint),
125782
- firstMeaningfulPaint: num(lighthouse.desktop?.firstMeaningfulPaint),
125783
- speedIndex: num(lighthouse.desktop?.speedIndex)
126009
+ score: num2(lighthouse.desktop?.score),
126010
+ firstContentfulPaint: num2(lighthouse.desktop?.firstContentfulPaint),
126011
+ firstMeaningfulPaint: num2(lighthouse.desktop?.firstMeaningfulPaint),
126012
+ speedIndex: num2(lighthouse.desktop?.speedIndex)
125784
126013
  },
125785
126014
  mobile: {
125786
- score: num(lighthouse.mobile?.score),
125787
- firstContentfulPaint: num(lighthouse.mobile?.firstContentfulPaint),
125788
- firstMeaningfulPaint: num(lighthouse.mobile?.firstMeaningfulPaint),
125789
- speedIndex: num(lighthouse.mobile?.speedIndex)
126015
+ score: num2(lighthouse.mobile?.score),
126016
+ firstContentfulPaint: num2(lighthouse.mobile?.firstContentfulPaint),
126017
+ firstMeaningfulPaint: num2(lighthouse.mobile?.firstMeaningfulPaint),
126018
+ speedIndex: num2(lighthouse.mobile?.speedIndex)
125790
126019
  }
125791
126020
  };
125792
126021
  const keywordShareItems = buildKeywordShareItems(kwCurrent);
@@ -125820,7 +126049,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125820
126049
  data.newFeatures = {
125821
126050
  items: nfItems.map((item) => {
125822
126051
  const strategy = String(item.strategy ?? "");
125823
- 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;
126052
+ 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;
125824
126053
  return {
125825
126054
  ...item,
125826
126055
  accountStatus: enabled,
@@ -125830,7 +126059,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125830
126059
  ...emptyModuleNarrative()
125831
126060
  };
125832
126061
  data.summary = emptySummary();
125833
- const zeroConvKw = kwCurrent.filter((r) => rowCost(r) > 50 && num(r.conversions) === 0);
126062
+ const zeroConvKw = kwCurrent.filter((r) => rowCost(r) > 50 && num2(r.conversions) === 0);
125834
126063
  const agentBrief = buildGoogleAdsDiagnosisAgentBrief({
125835
126064
  accountId,
125836
126065
  period: formatPeriod(start, end),
@@ -125854,9 +126083,9 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
125854
126083
  prevCpa
125855
126084
  },
125856
126085
  dimension: {
125857
- searchImpressionShare: num(dimensionRecord?.searchImpressionShare),
125858
- searchBudgetLostImpressionShare: num(dimensionRecord?.searchBudgetLostImpressionShare),
125859
- searchRankLostImpressionShare: num(dimensionRecord?.searchRankLostImpressionShare)
126086
+ searchImpressionShare: num2(dimensionRecord?.searchImpressionShare),
126087
+ searchBudgetLostImpressionShare: num2(dimensionRecord?.searchBudgetLostImpressionShare),
126088
+ searchRankLostImpressionShare: num2(dimensionRecord?.searchRankLostImpressionShare)
125860
126089
  },
125861
126090
  structure: structureCounts,
125862
126091
  goldAccount: goldRecord,
@@ -126091,7 +126320,7 @@ function validateNewFeatureRecommendations(data) {
126091
126320
  const warnings = [];
126092
126321
  for (let i = 0; i < items.length; i++) {
126093
126322
  const row = asRecord5(items[i]);
126094
- const name2 = String(row?.strategyName ?? row?.strategy ?? i);
126323
+ const name2 = String(row?.strategyName ?? row?.feature ?? row?.strategy ?? i);
126095
126324
  if (!hasNarrativeContent(row?.optimizerRecommendation)) {
126096
126325
  warnings.push(`newFeatures.items[${name2}].optimizerRecommendation \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
126097
126326
  }
@@ -126161,6 +126390,13 @@ var GOOGLE_ADS_DIAGNOSIS_COMPARISON_MODULES = [
126161
126390
  "geographic",
126162
126391
  "keywords"
126163
126392
  ];
126393
+ var GOOGLE_ADS_DIAGNOSIS_BIDDING_STRATEGY_MODULE = "biddingStrategy";
126394
+ var GOOGLE_ADS_DIAGNOSIS_NEW_FEATURES_MODULE = "newFeatures";
126395
+ var GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES = [
126396
+ ...GOOGLE_ADS_DIAGNOSIS_COMPARISON_MODULES,
126397
+ GOOGLE_ADS_DIAGNOSIS_BIDDING_STRATEGY_MODULE,
126398
+ GOOGLE_ADS_DIAGNOSIS_NEW_FEATURES_MODULE
126399
+ ];
126164
126400
  function asRecord6(value) {
126165
126401
  if (value && typeof value === "object" && !Array.isArray(value)) {
126166
126402
  return value;
@@ -126183,21 +126419,48 @@ function isComparisonTableRowValid(row) {
126183
126419
  if (!isValidRateChange(rec.cvrRateChange)) return false;
126184
126420
  return true;
126185
126421
  }
126186
- function moduleItemsNeedRestore(items) {
126422
+ function isNewFeatureRowValid(row) {
126423
+ const rec = asRecord6(row);
126424
+ if (!rec) return false;
126425
+ return Boolean(String(rec.strategyName ?? rec.feature ?? rec.strategy ?? "").trim());
126426
+ }
126427
+ function isBiddingStrategyRowValid(row) {
126428
+ const rec = asRecord6(row);
126429
+ if (!rec) return false;
126430
+ if (!String(rec.campaignName ?? "").trim()) return false;
126431
+ if (!Number.isFinite(Number(rec.duration)) || Number(rec.duration) <= 0) return false;
126432
+ if (!String(rec.biddingStrategyType ?? "").trim()) return false;
126433
+ return true;
126434
+ }
126435
+ var MODULE_ITEM_VALIDATORS = {
126436
+ campaigns: isComparisonTableRowValid,
126437
+ geographic: isComparisonTableRowValid,
126438
+ keywords: isComparisonTableRowValid,
126439
+ biddingStrategy: isBiddingStrategyRowValid,
126440
+ newFeatures: isNewFeatureRowValid
126441
+ };
126442
+ var MODULE_ITEM_ERROR_HINT = {
126443
+ campaigns: "\u7F3A\u5C11 title/currentCost/currentClicks \u6216\u73AF\u6BD4\u5B57\u6BB5\u88AB\u5199\u6210\u5B57\u7B26\u4E32\uFF08\u987B\u4FDD\u7559 collect \u4E8B\u5B9E\u884C\uFF0C\u4EC5\u7531 Agent \u586B\u5199 analysis/suggestions\uFF09",
126444
+ geographic: "\u7F3A\u5C11 title/currentCost/currentClicks \u6216\u73AF\u6BD4\u5B57\u6BB5\u88AB\u5199\u6210\u5B57\u7B26\u4E32\uFF08\u987B\u4FDD\u7559 collect \u4E8B\u5B9E\u884C\uFF0C\u4EC5\u7531 Agent \u586B\u5199 analysis/suggestions\uFF09",
126445
+ keywords: "\u7F3A\u5C11 title/currentCost/currentClicks \u6216\u73AF\u6BD4\u5B57\u6BB5\u88AB\u5199\u6210\u5B57\u7B26\u4E32\uFF08\u987B\u4FDD\u7559 collect \u4E8B\u5B9E\u884C\uFF0C\u4EC5\u7531 Agent \u586B\u5199 analysis/suggestions\uFF09",
126446
+ biddingStrategy: "\u7F3A\u5C11 campaignName/duration(\u5929\u6570)/biddingStrategyType\uFF08Agent \u52FF\u628A duration \u5199\u6210\u6587\u6848\u5B57\u7B26\u4E32\uFF0C\u52FF\u7528 currentStrategy \u66FF\u4EE3 biddingStrategyType\uFF09",
126447
+ newFeatures: "\u7F3A\u5C11 strategyName/strategy\uFF08collect \u4E8B\u5B9E\u884C\uFF1BAgent \u53EA\u586B optimizerRecommendation\uFF0C\u52FF\u6539\u7528 feature \u5B57\u6BB5\u66FF\u4EE3 strategyName\uFF09"
126448
+ };
126449
+ function moduleItemsNeedRestore(items, validator) {
126187
126450
  if (!Array.isArray(items) || items.length === 0) return false;
126188
- return items.some((row) => !isComparisonTableRowValid(row));
126451
+ return items.some((row) => !validator(row));
126189
126452
  }
126190
- function getComparisonTableErrors(data) {
126453
+ function getFactItemsErrors(data) {
126191
126454
  const errors = [];
126192
- for (const mod of GOOGLE_ADS_DIAGNOSIS_COMPARISON_MODULES) {
126455
+ for (const mod of GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES) {
126456
+ const validator = MODULE_ITEM_VALIDATORS[mod];
126457
+ const hint = MODULE_ITEM_ERROR_HINT[mod];
126193
126458
  const block = asRecord6(data[mod]);
126194
126459
  const items = block?.items;
126195
126460
  if (!Array.isArray(items) || items.length === 0) continue;
126196
126461
  for (let i = 0; i < items.length; i++) {
126197
- if (!isComparisonTableRowValid(items[i])) {
126198
- errors.push(
126199
- `${mod}.items[${i}] \u7F3A\u5C11 title/currentCost/currentClicks \u6216\u73AF\u6BD4\u5B57\u6BB5\u88AB\u5199\u6210\u5B57\u7B26\u4E32\uFF08\u987B\u4FDD\u7559 collect \u4E8B\u5B9E\u884C\uFF0C\u4EC5\u7531 Agent \u586B\u5199 analysis/suggestions\uFF09`
126200
- );
126462
+ if (!validator(items[i])) {
126463
+ errors.push(`${mod}.items[${i}] ${hint}`);
126201
126464
  }
126202
126465
  }
126203
126466
  }
@@ -126206,12 +126469,13 @@ function getComparisonTableErrors(data) {
126206
126469
  function mergeComparisonItemsFromCollect(finalData, collectReportData) {
126207
126470
  const data = { ...finalData };
126208
126471
  const restoredModules = [];
126209
- for (const mod of GOOGLE_ADS_DIAGNOSIS_COMPARISON_MODULES) {
126472
+ for (const mod of GOOGLE_ADS_DIAGNOSIS_RESTORE_ITEM_MODULES) {
126473
+ const validator = MODULE_ITEM_VALIDATORS[mod];
126210
126474
  const finalBlock = asRecord6(data[mod]);
126211
126475
  const collectBlock = asRecord6(collectReportData[mod]);
126212
126476
  const finalItems = finalBlock?.items;
126213
126477
  const collectItems = collectBlock?.items;
126214
- if (!moduleItemsNeedRestore(finalItems)) continue;
126478
+ if (!moduleItemsNeedRestore(finalItems, validator)) continue;
126215
126479
  if (!Array.isArray(collectItems) || collectItems.length === 0) continue;
126216
126480
  data[mod] = {
126217
126481
  ...finalBlock ?? {},
@@ -126265,16 +126529,16 @@ async function runGoogleAdsDiagnosisRender(opts) {
126265
126529
  if (restoredModules.length > 0) {
126266
126530
  console.log(
126267
126531
  `
126268
- \u2139\uFE0F \u5DF2\u4ECE collect \u6062\u590D \xA73.5 \u5BF9\u6BD4\u8868 items\uFF08${restoredModules.join("\u3001")}\uFF09\uFF1BAgent \u53EA\u5E94\u586B\u5199 analysis/suggestions\uFF0C\u52FF\u6539\u5199 items \u884C\u6570\u636E\u3002
126532
+ \u2139\uFE0F \u5DF2\u4ECE collect \u6062\u590D\u4E8B\u5B9E items\uFF08${restoredModules.join("\u3001")}\uFF09\uFF1BAgent \u53EA\u5E94\u586B\u5199 analysis/suggestions\uFF0C\u52FF\u6539\u5199 items \u884C\uFF08\u542B biddingStrategy.duration \u5929\u6570\uFF09\u3002
126269
126533
  \u6765\u6E90\uFF1A${collectPath}
126270
126534
  `
126271
126535
  );
126272
126536
  }
126273
126537
  }
126274
126538
  }
126275
- const comparisonErrors = getComparisonTableErrors(data);
126539
+ const comparisonErrors = getFactItemsErrors(data);
126276
126540
  if (comparisonErrors.length > 0) {
126277
- console.error("\n\u274C \u5BF9\u6BD4\u8868 items \u4E0D\u5B8C\u6574\uFF0CHTML \u4E2D\u7CFB\u5217/\u5730\u57DF/\u5173\u952E\u8BCD\u8868\u4F1A\u663E\u793A\u4E3A\u7A7A\uFF1A\n");
126541
+ console.error("\n\u274C \u4E8B\u5B9E items \u4E0D\u5B8C\u6574\uFF0CHTML \u4E2D\u90E8\u5206\u8868\u683C\u4F1A\u663E\u793A\u4E3A\u7A7A\uFF1A\n");
126278
126542
  for (const err of comparisonErrors) {
126279
126543
  console.error(` \xB7 ${err}`);
126280
126544
  }
@@ -126467,7 +126731,7 @@ var PHASE_LABEL = {
126467
126731
  "find-winner": "\u627E\u8D62\u5BB6",
126468
126732
  scale: "\u653E\u91CF"
126469
126733
  };
126470
- function num2(value) {
126734
+ function num3(value) {
126471
126735
  const n = Number(value);
126472
126736
  return Number.isFinite(n) ? n : void 0;
126473
126737
  }
@@ -126478,35 +126742,35 @@ function money(value, currency = "USD") {
126478
126742
  }
126479
126743
  function adSetsWithSpend(tables) {
126480
126744
  const rows = Array.isArray(tables?.adSets) ? tables.adSets : [];
126481
- return rows.filter((r) => (num2(r.spend) ?? 0) > 0);
126745
+ return rows.filter((r) => (num3(r.spend) ?? 0) > 0);
126482
126746
  }
126483
126747
  function topAdSetConcentration(adSets) {
126484
126748
  if (!adSets.length) return null;
126485
- const totalSpend = adSets.reduce((s, r) => s + (num2(r.spend) ?? 0), 0);
126486
- const totalResults = adSets.reduce((s, r) => s + (num2(r.results) ?? 0), 0);
126749
+ const totalSpend = adSets.reduce((s, r) => s + (num3(r.spend) ?? 0), 0);
126750
+ const totalResults = adSets.reduce((s, r) => s + (num3(r.results) ?? 0), 0);
126487
126751
  let best = adSets[0];
126488
- let bestSpend = num2(best.spend) ?? 0;
126752
+ let bestSpend = num3(best.spend) ?? 0;
126489
126753
  for (const r of adSets) {
126490
- const sp = num2(r.spend) ?? 0;
126754
+ const sp = num3(r.spend) ?? 0;
126491
126755
  if (sp > bestSpend) {
126492
126756
  best = r;
126493
126757
  bestSpend = sp;
126494
126758
  }
126495
126759
  }
126496
- const results = num2(best.results) ?? 0;
126760
+ const results = num3(best.results) ?? 0;
126497
126761
  return {
126498
126762
  name: String(best.adGroupName ?? best.name ?? "\u2014"),
126499
126763
  spendShare: totalSpend > 0 ? bestSpend / totalSpend : 0,
126500
126764
  resultsShare: totalResults > 0 ? results / totalResults : 0,
126501
- cpl: num2(best.costPerResult)
126765
+ cpl: num3(best.costPerResult)
126502
126766
  };
126503
126767
  }
126504
126768
  function winnerSpendShare(adSets, avgCpl, totalSpend) {
126505
126769
  if (totalSpend <= 0 || avgCpl <= 0) return 0;
126506
126770
  const winnerSpend = adSets.filter((r) => {
126507
- const cpl = num2(r.costPerResult);
126771
+ const cpl = num3(r.costPerResult);
126508
126772
  return cpl != null && cpl <= avgCpl * 0.9;
126509
- }).reduce((s, r) => s + (num2(r.spend) ?? 0), 0);
126773
+ }).reduce((s, r) => s + (num3(r.spend) ?? 0), 0);
126510
126774
  return winnerSpend / totalSpend;
126511
126775
  }
126512
126776
  function countActiveDimensions(charts, tables) {
@@ -126523,7 +126787,7 @@ function asArray(value) {
126523
126787
  }
126524
126788
  function inferLifecyclePhase(input) {
126525
126789
  const { avgCpl, currency = "USD" } = input;
126526
- const spend = num2(input.spend) ?? 0;
126790
+ const spend = num3(input.spend) ?? 0;
126527
126791
  const adSets = adSetsWithSpend(input.tables);
126528
126792
  const activeDims = countActiveDimensions(input.charts, input.tables);
126529
126793
  const winShare = winnerSpendShare(adSets, avgCpl, spend);
@@ -126582,7 +126846,7 @@ function ensureLifecycleFromSnapshot(healthDiagnosis, merged) {
126582
126846
  }
126583
126847
 
126584
126848
  // src/commands/facebook-analysis/scorecard-autofill.ts
126585
- function num3(value) {
126849
+ function num4(value) {
126586
126850
  const n = Number(value);
126587
126851
  return Number.isFinite(n) ? n : void 0;
126588
126852
  }
@@ -126622,8 +126886,8 @@ function buildScorecardFromMergedData(input) {
126622
126886
  if (pLabels.length) {
126623
126887
  const indexed = pLabels.map((label, i) => ({
126624
126888
  label: String(label),
126625
- cpl: num3(pCpl[i]),
126626
- spend: num3(pSpend[i]) ?? 0
126889
+ cpl: num4(pCpl[i]),
126890
+ spend: num4(pSpend[i]) ?? 0
126627
126891
  })).filter((x) => x.spend > 0 || x.cpl != null && x.cpl > 0);
126628
126892
  const sorted = [...indexed].sort((a, b) => (a.cpl ?? Infinity) - (b.cpl ?? Infinity));
126629
126893
  if (sorted[0]) {
@@ -126648,7 +126912,7 @@ function buildScorecardFromMergedData(input) {
126648
126912
  const cLabels = Array.isArray(country?.labels) ? country.labels : [];
126649
126913
  const cCpl = Array.isArray(country?.cpl) ? country.cpl : [];
126650
126914
  if (cLabels.length) {
126651
- const indexed = cLabels.map((label, i) => ({ label: String(label), cpl: num3(cCpl[i]) })).filter((x) => x.cpl != null && x.cpl > 0);
126915
+ const indexed = cLabels.map((label, i) => ({ label: String(label), cpl: num4(cCpl[i]) })).filter((x) => x.cpl != null && x.cpl > 0);
126652
126916
  const sorted = [...indexed].sort((a, b) => (a.cpl ?? 0) - (b.cpl ?? 0));
126653
126917
  if (sorted[0]) {
126654
126918
  const s = cplSignal(sorted[0].cpl, avgCpl);
@@ -126669,24 +126933,24 @@ function buildScorecardFromMergedData(input) {
126669
126933
  }
126670
126934
  }
126671
126935
  const adSets = Array.isArray(tables.adSets) ? tables.adSets : [];
126672
- const adWithSpend = adSets.filter((g) => (num3(g.spend) ?? 0) > 0);
126936
+ const adWithSpend = adSets.filter((g) => (num4(g.spend) ?? 0) > 0);
126673
126937
  if (adWithSpend.length) {
126674
126938
  const sorted = [...adWithSpend].sort(
126675
- (a, b) => (num3(a.costPerResult) ?? Infinity) - (num3(b.costPerResult) ?? Infinity)
126939
+ (a, b) => (num4(a.costPerResult) ?? Infinity) - (num4(b.costPerResult) ?? Infinity)
126676
126940
  );
126677
126941
  const best = sorted[0];
126678
- const sBest = cplSignal(num3(best.costPerResult), avgCpl);
126942
+ const sBest = cplSignal(num4(best.costPerResult), avgCpl);
126679
126943
  pushRow(rows, seen, {
126680
126944
  item: String(best.name ?? "\u5E7F\u544A\u7EC4"),
126681
- data: `CPL ${money2(num3(best.costPerResult), currency)}`,
126945
+ data: `CPL ${money2(num4(best.costPerResult), currency)}`,
126682
126946
  ...sBest
126683
126947
  });
126684
126948
  if (sorted.length > 1) {
126685
126949
  const worst = sorted[sorted.length - 1];
126686
- const sWorst = cplSignal(num3(worst.costPerResult), avgCpl);
126950
+ const sWorst = cplSignal(num4(worst.costPerResult), avgCpl);
126687
126951
  pushRow(rows, seen, {
126688
126952
  item: String(worst.name ?? "\u5E7F\u544A\u7EC4"),
126689
- data: `CPL ${money2(num3(worst.costPerResult), currency)}`,
126953
+ data: `CPL ${money2(num4(worst.costPerResult), currency)}`,
126690
126954
  ...sWorst
126691
126955
  });
126692
126956
  }
@@ -126694,18 +126958,18 @@ function buildScorecardFromMergedData(input) {
126694
126958
  const topAud = Array.isArray(tables.audienceTop) ? tables.audienceTop : [];
126695
126959
  const bottomAud = Array.isArray(tables.audienceBottom) ? tables.audienceBottom : [];
126696
126960
  if (topAud[0]) {
126697
- const s = cplSignal(num3(topAud[0].costPerResult), avgCpl);
126961
+ const s = cplSignal(num4(topAud[0].costPerResult), avgCpl);
126698
126962
  pushRow(rows, seen, {
126699
126963
  item: `\u53D7\u4F17\xB7${String(topAud[0].label ?? "Top")}`,
126700
- data: `CPL ${money2(num3(topAud[0].costPerResult), currency)}`,
126964
+ data: `CPL ${money2(num4(topAud[0].costPerResult), currency)}`,
126701
126965
  ...s
126702
126966
  });
126703
126967
  }
126704
126968
  if (bottomAud[0]) {
126705
- const s = cplSignal(num3(bottomAud[0].costPerResult), avgCpl);
126969
+ const s = cplSignal(num4(bottomAud[0].costPerResult), avgCpl);
126706
126970
  pushRow(rows, seen, {
126707
126971
  item: `\u53D7\u4F17\xB7${String(bottomAud[0].label ?? "Bottom")}`,
126708
- data: `CPL ${money2(num3(bottomAud[0].costPerResult), currency)}`,
126972
+ data: `CPL ${money2(num4(bottomAud[0].costPerResult), currency)}`,
126709
126973
  ...s
126710
126974
  });
126711
126975
  }
@@ -126749,7 +127013,7 @@ function asRecord8(value) {
126749
127013
  }
126750
127014
  return null;
126751
127015
  }
126752
- function num4(value) {
127016
+ function num5(value) {
126753
127017
  const n = Number(value);
126754
127018
  return Number.isFinite(n) ? n : void 0;
126755
127019
  }
@@ -126803,8 +127067,8 @@ function aggregatePlatform(networks) {
126803
127067
  const platform = String(row.publisherPlatform ?? row.network ?? "unknown");
126804
127068
  const prev = map.get(platform) ?? { spend: 0, results: 0 };
126805
127069
  map.set(platform, {
126806
- spend: prev.spend + (num4(row.spend) ?? 0),
126807
- results: prev.results + (num4(row.results) ?? 0)
127070
+ spend: prev.spend + (num5(row.spend) ?? 0),
127071
+ results: prev.results + (num5(row.results) ?? 0)
126808
127072
  });
126809
127073
  }
126810
127074
  const labels = [];
@@ -126820,10 +127084,10 @@ function aggregatePlatform(networks) {
126820
127084
  function buildAudienceRows(audiences) {
126821
127085
  return audiences.map((a) => ({
126822
127086
  label: audienceLabel(a.age, a.gender),
126823
- spend: num4(a.spend) ?? 0,
126824
- results: num4(a.results) ?? 0,
126825
- costPerResult: num4(a.costPerResult) ?? (num4(a.results) ? (num4(a.spend) ?? 0) / (num4(a.results) ?? 1) : 0),
126826
- frequency: num4(a.frequency)
127087
+ spend: num5(a.spend) ?? 0,
127088
+ results: num5(a.results) ?? 0,
127089
+ costPerResult: num5(a.costPerResult) ?? (num5(a.results) ? (num5(a.spend) ?? 0) / (num5(a.results) ?? 1) : 0),
127090
+ frequency: num5(a.frequency)
126827
127091
  })).filter((r) => r.spend > 0 || r.results > 0);
126828
127092
  }
126829
127093
  async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
@@ -126862,7 +127126,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126862
127126
  }
126863
127127
  if (!meta.resultType && current?.resultType) meta.resultType = String(current.resultType);
126864
127128
  const charts = { ...payload.charts ?? {} };
126865
- const avgCpl = num4(kpis.costPerResult) ?? 0;
127129
+ const avgCpl = num5(kpis.costPerResult) ?? 0;
126866
127130
  if (!charts.platform) {
126867
127131
  const platform = sectionMap.get("platform");
126868
127132
  const networks = Array.isArray(platform?.networks) ? platform.networks : [];
@@ -126872,10 +127136,10 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126872
127136
  const country = sectionMap.get("country");
126873
127137
  const countries = Array.isArray(country?.countries) ? country.countries : [];
126874
127138
  if (countries.length) {
126875
- const sorted = [...countries].sort((a, b) => (num4(b.spend) ?? 0) - (num4(a.spend) ?? 0));
127139
+ const sorted = [...countries].sort((a, b) => (num5(b.spend) ?? 0) - (num5(a.spend) ?? 0));
126876
127140
  charts.country = {
126877
127141
  labels: sorted.map((c) => String(c.countryOrRegion ?? "\u2014")),
126878
- cpl: sorted.map((c) => num4(c.costPerResult) ?? 0)
127142
+ cpl: sorted.map((c) => num5(c.costPerResult) ?? 0)
126879
127143
  };
126880
127144
  }
126881
127145
  }
@@ -126891,7 +127155,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126891
127155
  };
126892
127156
  }
126893
127157
  }
126894
- if (!charts.funnel && num4(kpis.reach) != null) {
127158
+ if (!charts.funnel && num5(kpis.reach) != null) {
126895
127159
  charts.funnel = { reach: kpis.reach, results: kpis.results ?? 0 };
126896
127160
  }
126897
127161
  const tables = { ...payload.tables ?? {} };
@@ -126900,14 +127164,14 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126900
127164
  const groups = Array.isArray(adSets?.adGroups) ? adSets.adGroups : [];
126901
127165
  if (groups.length) {
126902
127166
  tables.adSets = groups.map((g) => {
126903
- const cpl = num4(g.costPerResult);
126904
- const freq = num4(g.frequency);
127167
+ const cpl = num5(g.costPerResult);
127168
+ const freq = num5(g.frequency);
126905
127169
  const fatigue = fatigueFromFrequency(freq);
126906
127170
  const status = statusFromCpl(cpl, avgCpl);
126907
127171
  return {
126908
127172
  name: String(g.adGroupName ?? g.campaignName ?? "\u2014"),
126909
- spend: num4(g.spend) ?? 0,
126910
- results: num4(g.results) ?? 0,
127173
+ spend: num5(g.spend) ?? 0,
127174
+ results: num5(g.results) ?? 0,
126911
127175
  costPerResult: cpl ?? 0,
126912
127176
  frequency: freq,
126913
127177
  fatigueLevel: fatigue.level,
@@ -126931,7 +127195,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
126931
127195
  }
126932
127196
  const currency = typeof meta.currency === "string" ? meta.currency : "USD";
126933
127197
  const mergeCtx = {
126934
- spend: num4(kpis.spend),
127198
+ spend: num5(kpis.spend),
126935
127199
  avgCpl,
126936
127200
  currency,
126937
127201
  charts,