siluzan-tso-cli 1.1.44 → 1.1.45-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/README.md +2 -1
- package/dist/index.js +1001 -118
- package/dist/skill/SKILL.md +1 -0
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/README.md +1 -0
- package/dist/skill/references/accounts/accounts-balance-stats.md +3 -3
- package/dist/skill/references/accounts/accounts-list.md +1 -1
- package/dist/skill/references/accounts/accounts-permissions.md +2 -2
- package/dist/skill/references/accounts/finance.md +1 -1
- package/dist/skill/references/accounts/open-account-by-media.md +1 -1
- package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
- package/dist/skill/references/analytics/account-analytics.md +44 -1
- package/dist/skill/references/analytics/rag.md +1 -1
- package/dist/skill/references/analytics/reporting.md +5 -5
- package/dist/skill/references/core/playbooks.md +1 -1
- package/dist/skill/references/core/setup.md +5 -5
- package/dist/skill/references/misc/tso-home.md +2 -2
- package/dist/skill/references/operations/hosted-automation-monitoring-json.md +1 -1
- package/dist/skill/references/report-templates/README.md +2 -0
- package/dist/skill/references/report-templates/REPORT-WORKFLOW.md +6 -4
- package/dist/skill/references/report-templates/yandex-period-report-excel.md +123 -0
- package/dist/skill/references/report-templates/yandex-period-report.md +121 -0
- package/dist/skill/report-templates/README.md +2 -0
- package/dist/skill/report-templates/REPORT-WORKFLOW.md +6 -4
- package/dist/skill/report-templates/yandex-period-report-excel.md +123 -0
- package/dist/skill/report-templates/yandex-period-report.html +968 -0
- package/dist/skill/report-templates/yandex-period-report.md +121 -0
- package/dist/skill/scripts/install.ps1 +3 -3
- package/dist/skill/scripts/install.sh +3 -3
- package/package.json +3 -1
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
|
|
262
|
-
if (
|
|
263
|
-
return
|
|
261
|
+
const num11 = +id;
|
|
262
|
+
if (num11 >= 0 && num11 < MAX_SAFE_INTEGER) {
|
|
263
|
+
return num11;
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
return id;
|
|
@@ -4026,7 +4026,7 @@ var DEFAULT_API_BASE;
|
|
|
4026
4026
|
var init_defaults = __esm({
|
|
4027
4027
|
"src/config/defaults.ts"() {
|
|
4028
4028
|
"use strict";
|
|
4029
|
-
DEFAULT_API_BASE = "https://tso-api.siluzan.com";
|
|
4029
|
+
DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
|
|
4030
4030
|
}
|
|
4031
4031
|
});
|
|
4032
4032
|
|
|
@@ -4046,6 +4046,9 @@ function deriveTiktokApiUrl(tsoApiBaseUrl) {
|
|
|
4046
4046
|
function deriveFacebookApiUrl(tsoApiBaseUrl) {
|
|
4047
4047
|
return tsoApiBaseUrl.includes("-ci") ? "https://facebookapi-ci.mysiluzan.com" : "https://facebookapi.mysiluzan.com";
|
|
4048
4048
|
}
|
|
4049
|
+
function deriveYandexApiUrl(tsoApiBaseUrl) {
|
|
4050
|
+
return tsoApiBaseUrl.includes("-ci") ? "https://yandexapi-ci.mysiluzan.com" : "https://yandexapi.mysiluzan.com";
|
|
4051
|
+
}
|
|
4049
4052
|
function deriveChatGptApiUrl(tsoApiBaseUrl) {
|
|
4050
4053
|
return tsoApiBaseUrl.includes("-ci") ? "https://chatgpt-ci.mysiluzan.com" : "https://chatgpt.mysiluzan.com";
|
|
4051
4054
|
}
|
|
@@ -4431,6 +4434,7 @@ function loadConfig(tokenArg) {
|
|
|
4431
4434
|
mainApiUrl: deriveMainApiUrl(apiBaseUrl),
|
|
4432
4435
|
tiktokApiUrl: deriveTiktokApiUrl(apiBaseUrl),
|
|
4433
4436
|
facebookApiUrl: deriveFacebookApiUrl(apiBaseUrl),
|
|
4437
|
+
yandexApiUrl: process.env.SILUZAN_YANDEX_API ?? deriveYandexApiUrl(apiBaseUrl),
|
|
4434
4438
|
chatGptApiUrl: deriveChatGptApiUrl(apiBaseUrl),
|
|
4435
4439
|
csoBaseUrl,
|
|
4436
4440
|
dataPermission: process.env.SILUZAN_DATA_PERMISSION ?? shared.dataPermission
|
|
@@ -5561,8 +5565,8 @@ var init_daily_metrics_range = __esm({
|
|
|
5561
5565
|
async function withAsyncLock(key, fn) {
|
|
5562
5566
|
const prev = chains.get(key) ?? Promise.resolve();
|
|
5563
5567
|
let release;
|
|
5564
|
-
const gate = new Promise((
|
|
5565
|
-
release =
|
|
5568
|
+
const gate = new Promise((resolve23) => {
|
|
5569
|
+
release = resolve23;
|
|
5566
5570
|
});
|
|
5567
5571
|
chains.set(
|
|
5568
5572
|
key,
|
|
@@ -6151,14 +6155,14 @@ function computeBackoffMs(attempt, policy) {
|
|
|
6151
6155
|
return capped + jitter;
|
|
6152
6156
|
}
|
|
6153
6157
|
function sleep3(ms, signal) {
|
|
6154
|
-
return new Promise((
|
|
6158
|
+
return new Promise((resolve23, reject) => {
|
|
6155
6159
|
if (signal?.aborted) {
|
|
6156
6160
|
reject(new Error("retry sleep aborted"));
|
|
6157
6161
|
return;
|
|
6158
6162
|
}
|
|
6159
6163
|
const timer = setTimeout(() => {
|
|
6160
6164
|
signal?.removeEventListener("abort", onAbort);
|
|
6161
|
-
|
|
6165
|
+
resolve23();
|
|
6162
6166
|
}, ms);
|
|
6163
6167
|
const onAbort = () => {
|
|
6164
6168
|
clearTimeout(timer);
|
|
@@ -104361,10 +104365,10 @@ var init_google_analysis3 = __esm({
|
|
|
104361
104365
|
// src/index.ts
|
|
104362
104366
|
init_dist();
|
|
104363
104367
|
init_dist();
|
|
104364
|
-
import * as
|
|
104365
|
-
import * as
|
|
104366
|
-
import { fileURLToPath as
|
|
104367
|
-
import { Command as
|
|
104368
|
+
import * as fs35 from "fs";
|
|
104369
|
+
import * as path46 from "path";
|
|
104370
|
+
import { fileURLToPath as fileURLToPath12 } from "url";
|
|
104371
|
+
import { Command as Command6 } from "commander";
|
|
104368
104372
|
|
|
104369
104373
|
// src/commands/login/urls.ts
|
|
104370
104374
|
init_defaults();
|
|
@@ -104849,8 +104853,8 @@ function deriveWebUrl(apiBaseUrl) {
|
|
|
104849
104853
|
}
|
|
104850
104854
|
var TSO_UMI_ROUTE_PREFIX = "/v3umijs/tso";
|
|
104851
104855
|
function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
|
|
104852
|
-
const
|
|
104853
|
-
const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${
|
|
104856
|
+
const path47 = tsoSubRoute.replace(/^\/+/, "");
|
|
104857
|
+
const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${path47}`;
|
|
104854
104858
|
const q = new URLSearchParams({ tso: umiPath });
|
|
104855
104859
|
if (extraQuery) {
|
|
104856
104860
|
for (const [key, value] of Object.entries(extraQuery)) {
|
|
@@ -104858,7 +104862,7 @@ function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
|
|
|
104858
104862
|
if (v) q.set(key, v);
|
|
104859
104863
|
}
|
|
104860
104864
|
}
|
|
104861
|
-
return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${
|
|
104865
|
+
return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${path47}?${q.toString()}`;
|
|
104862
104866
|
}
|
|
104863
104867
|
function cmdConfigShow() {
|
|
104864
104868
|
const shared = readSharedConfig();
|
|
@@ -106984,6 +106988,63 @@ function assessBalanceScanDataHealth(input) {
|
|
|
106984
106988
|
return null;
|
|
106985
106989
|
}
|
|
106986
106990
|
|
|
106991
|
+
// src/commands/balance-scan/evaluate.ts
|
|
106992
|
+
function evaluateBalanceScanAccounts(params) {
|
|
106993
|
+
const {
|
|
106994
|
+
items: items2,
|
|
106995
|
+
balanceMap,
|
|
106996
|
+
overviewMap,
|
|
106997
|
+
spendDays,
|
|
106998
|
+
thresholdDays,
|
|
106999
|
+
minBalance,
|
|
107000
|
+
minDailySpend,
|
|
107001
|
+
targetDays,
|
|
107002
|
+
includeInvalidOAuth
|
|
107003
|
+
} = params;
|
|
107004
|
+
const evaluated = [];
|
|
107005
|
+
for (const item of items2) {
|
|
107006
|
+
const ma = item.ma;
|
|
107007
|
+
const id = ma?.mediaCustomerId ? String(ma.mediaCustomerId) : "";
|
|
107008
|
+
if (!id) continue;
|
|
107009
|
+
if (!includeInvalidOAuth && ma.invalidOAuthToken) continue;
|
|
107010
|
+
const bal = balanceMap.get(id);
|
|
107011
|
+
const ov = overviewMap.get(id);
|
|
107012
|
+
const balance = bal?.remainingAccountBudget ?? null;
|
|
107013
|
+
const windowSpend = Number(ov?.spend ?? 0);
|
|
107014
|
+
const dailySpend = windowSpend > 0 ? windowSpend / spendDays : 0;
|
|
107015
|
+
let remainingDays = null;
|
|
107016
|
+
let recommendedTopup = null;
|
|
107017
|
+
if (dailySpend >= minDailySpend && balance !== null) {
|
|
107018
|
+
remainingDays = +(balance / dailySpend).toFixed(1);
|
|
107019
|
+
const needed = dailySpend * targetDays - balance;
|
|
107020
|
+
recommendedTopup = needed > 0 ? +needed.toFixed(2) : 0;
|
|
107021
|
+
}
|
|
107022
|
+
const lowDays = remainingDays !== null && remainingDays <= thresholdDays;
|
|
107023
|
+
const lowBalance = minBalance !== null && balance !== null && balance <= minBalance;
|
|
107024
|
+
const hitReason = lowDays && lowBalance ? "both" : lowDays ? "low-days" : lowBalance ? "low-balance" : "none";
|
|
107025
|
+
evaluated.push({
|
|
107026
|
+
mediaCustomerId: id,
|
|
107027
|
+
name: ma.mediaCustomerName ?? bal?.name ?? ov?.mediaCustomerName ?? null,
|
|
107028
|
+
advertiserName: item.mag?.advertiserName ?? null,
|
|
107029
|
+
currencyCode: bal?.currencyCode ?? ma.currencyCode ?? ov?.currencyCode ?? null,
|
|
107030
|
+
balance,
|
|
107031
|
+
dailySpend: dailySpend > 0 ? +dailySpend.toFixed(2) : null,
|
|
107032
|
+
remainingDays,
|
|
107033
|
+
recommendedTopup,
|
|
107034
|
+
invalidOAuthToken: !!ma.invalidOAuthToken,
|
|
107035
|
+
status: bal?.status ?? bal?.accountStatus ?? ov?.status ?? null,
|
|
107036
|
+
hitReason
|
|
107037
|
+
});
|
|
107038
|
+
}
|
|
107039
|
+
evaluated.sort((a, b) => {
|
|
107040
|
+
const ad = a.remainingDays ?? Number.POSITIVE_INFINITY;
|
|
107041
|
+
const bd = b.remainingDays ?? Number.POSITIVE_INFINITY;
|
|
107042
|
+
return ad - bd;
|
|
107043
|
+
});
|
|
107044
|
+
const hitCount = evaluated.filter((x) => x.hitReason !== "none").length;
|
|
107045
|
+
return { items: evaluated, hitCount };
|
|
107046
|
+
}
|
|
107047
|
+
|
|
106987
107048
|
// src/commands/balance-scan/run.ts
|
|
106988
107049
|
async function runBalanceScan(opts) {
|
|
106989
107050
|
let config = loadConfig(opts.token);
|
|
@@ -107089,48 +107150,19 @@ async function runBalanceScan(opts) {
|
|
|
107089
107150
|
balanceMap = fetched.balanceMap;
|
|
107090
107151
|
overviewMap = fetched.overviewMap;
|
|
107091
107152
|
batchChunkSize = fetched.chunkSize;
|
|
107092
|
-
process.stderr.write(`\u23F3 [balance-scan] \u4F59\u989D\u4E0E\u6D88\u8017\u5DF2\u9F50\uFF0C\u6B63\u5728\
|
|
107153
|
+
process.stderr.write(`\u23F3 [balance-scan] \u4F59\u989D\u4E0E\u6D88\u8017\u5DF2\u9F50\uFF0C\u6B63\u5728\u6C47\u603B\u5168\u90E8\u5DF2\u68C0\u67E5\u8D26\u6237\u2026
|
|
107093
107154
|
`);
|
|
107094
107155
|
}
|
|
107095
|
-
const evaluated =
|
|
107096
|
-
|
|
107097
|
-
|
|
107098
|
-
|
|
107099
|
-
|
|
107100
|
-
|
|
107101
|
-
|
|
107102
|
-
|
|
107103
|
-
|
|
107104
|
-
|
|
107105
|
-
let remainingDays = null;
|
|
107106
|
-
let recommendedTopup = null;
|
|
107107
|
-
if (dailySpend >= minDailySpend && balance !== null) {
|
|
107108
|
-
remainingDays = +(balance / dailySpend).toFixed(1);
|
|
107109
|
-
const needed = dailySpend * targetDays - balance;
|
|
107110
|
-
recommendedTopup = needed > 0 ? +needed.toFixed(2) : 0;
|
|
107111
|
-
}
|
|
107112
|
-
const lowDays = remainingDays !== null && remainingDays <= thresholdDays;
|
|
107113
|
-
const lowBalance = minBalance !== null && balance !== null && balance <= minBalance;
|
|
107114
|
-
if (!lowDays && !lowBalance && !isSubset) continue;
|
|
107115
|
-
const hitReason = lowDays && lowBalance ? "both" : lowDays ? "low-days" : lowBalance ? "low-balance" : "none";
|
|
107116
|
-
evaluated.push({
|
|
107117
|
-
mediaCustomerId: id,
|
|
107118
|
-
name: ma.mediaCustomerName ?? bal?.name ?? ov?.mediaCustomerName ?? null,
|
|
107119
|
-
advertiserName: item.mag?.advertiserName ?? null,
|
|
107120
|
-
currencyCode: bal?.currencyCode ?? ma.currencyCode ?? ov?.currencyCode ?? null,
|
|
107121
|
-
balance,
|
|
107122
|
-
dailySpend: dailySpend > 0 ? +dailySpend.toFixed(2) : null,
|
|
107123
|
-
remainingDays,
|
|
107124
|
-
recommendedTopup,
|
|
107125
|
-
invalidOAuthToken: !!ma.invalidOAuthToken,
|
|
107126
|
-
status: bal?.status ?? bal?.accountStatus ?? ov?.status ?? null,
|
|
107127
|
-
hitReason
|
|
107128
|
-
});
|
|
107129
|
-
}
|
|
107130
|
-
evaluated.sort((a, b) => {
|
|
107131
|
-
const ad = a.remainingDays ?? Number.POSITIVE_INFINITY;
|
|
107132
|
-
const bd = b.remainingDays ?? Number.POSITIVE_INFINITY;
|
|
107133
|
-
return ad - bd;
|
|
107156
|
+
const { items: evaluated, hitCount } = evaluateBalanceScanAccounts({
|
|
107157
|
+
items: allItems,
|
|
107158
|
+
balanceMap,
|
|
107159
|
+
overviewMap,
|
|
107160
|
+
spendDays,
|
|
107161
|
+
thresholdDays,
|
|
107162
|
+
minBalance,
|
|
107163
|
+
minDailySpend,
|
|
107164
|
+
targetDays,
|
|
107165
|
+
includeInvalidOAuth: isSubset
|
|
107134
107166
|
});
|
|
107135
107167
|
const balancesForHealth = validIds.map((id) => balanceMap.get(id)?.remainingAccountBudget);
|
|
107136
107168
|
const dataIssue = assessBalanceScanDataHealth({
|
|
@@ -107144,7 +107176,7 @@ async function runBalanceScan(opts) {
|
|
|
107144
107176
|
scannedAccounts: allItems.length,
|
|
107145
107177
|
validAccounts: validIds.length,
|
|
107146
107178
|
skippedInvalidOAuth: allItems.length - validIds.length,
|
|
107147
|
-
hitCount
|
|
107179
|
+
hitCount,
|
|
107148
107180
|
nullBalanceCount: balancesForHealth.filter((b) => b == null).length,
|
|
107149
107181
|
dataIssue: dataIssue?.code ?? null,
|
|
107150
107182
|
dataIssueMessage: dataIssue?.message ?? null,
|
|
@@ -107184,22 +107216,23 @@ async function runBalanceScan(opts) {
|
|
|
107184
107216
|
if (wroteJson) {
|
|
107185
107217
|
return;
|
|
107186
107218
|
}
|
|
107219
|
+
const thresholdHint = `\u9608\u503C\uFF1A\u7EED\u822A \u2264 ${thresholdDays} \u5929` + (minBalance !== null ? ` \u6216 \u4F59\u989D \u2264 ${minBalance}` : "");
|
|
107187
107220
|
if (isSubset) {
|
|
107188
107221
|
console.log(
|
|
107189
107222
|
`
|
|
107190
107223
|
${media} \u8D26\u6237\u4F59\u989D\u753B\u50CF\uFF08\u5B50\u96C6\u6A21\u5F0F\uFF0C-a \u6307\u5B9A ${filterIds.length} \u4E2A\u8D26\u6237\uFF0C\u5168\u90E8\u5C55\u793A\uFF09
|
|
107191
|
-
\
|
|
107224
|
+
\u5DF2\u68C0\u67E5 ${evaluated.length} \u6237\uFF0C\u5176\u4E2D\u9884\u8B66\u547D\u4E2D ${hitCount} \u4E2A\uFF08${thresholdHint}\uFF09
|
|
107192
107225
|
`
|
|
107193
107226
|
);
|
|
107194
107227
|
} else {
|
|
107195
107228
|
console.log(
|
|
107196
107229
|
`
|
|
107197
|
-
${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63CF ${meta.scannedAccounts} \u4E2A\u8D26\u6237\uFF08\u6709\u6548 ${meta.validAccounts}\uFF09\uFF0C\
|
|
107230
|
+
${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63CF ${meta.scannedAccounts} \u4E2A\u8D26\u6237\uFF08\u6709\u6548 ${meta.validAccounts}\uFF09\uFF0C\u8FD4\u56DE\u5DF2\u68C0\u67E5 ${evaluated.length} \u6237\uFF0C\u5176\u4E2D\u9884\u8B66\u547D\u4E2D ${hitCount} \u4E2A\uFF08${thresholdHint}\uFF09
|
|
107198
107231
|
`
|
|
107199
107232
|
);
|
|
107200
107233
|
}
|
|
107201
107234
|
if (evaluated.length === 0) {
|
|
107202
|
-
console.log(" \u2705 \
|
|
107235
|
+
console.log(" \u2705 \u6CA1\u6709\u53EF\u5C55\u793A\u7684\u5DF2\u68C0\u67E5\u8D26\u6237\uFF08\u6570\u636E\u5DF2\u6B63\u5E38\u8FD4\u56DE\uFF09\u3002\n");
|
|
107203
107236
|
return;
|
|
107204
107237
|
}
|
|
107205
107238
|
const cols = [
|
|
@@ -107213,7 +107246,7 @@ ${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63C
|
|
|
107213
107246
|
{ key: "reason", header: "\u547D\u4E2D\u539F\u56E0" }
|
|
107214
107247
|
];
|
|
107215
107248
|
const reasonText = {
|
|
107216
|
-
none: "\u2014\uFF08\
|
|
107249
|
+
none: "\u2014\uFF08\u672A\u89E6\u9608\u503C\uFF09",
|
|
107217
107250
|
"low-days": `\u7EED\u822A \u2264 ${thresholdDays} \u5929`,
|
|
107218
107251
|
"low-balance": `\u4F59\u989D \u2264 ${minBalance ?? 0}`,
|
|
107219
107252
|
both: `\u7EED\u822A + \u4F59\u989D\u53CC\u4F4E`
|
|
@@ -107231,7 +107264,7 @@ ${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63C
|
|
|
107231
107264
|
printCliTable(rows, cols);
|
|
107232
107265
|
console.log(
|
|
107233
107266
|
`
|
|
107234
|
-
\u63D0\u793A\uFF1A\u4E0A\u8868\u5DF2\u6309\u5269\u4F59\u5929\u6570\u5347\u5E8F\
|
|
107267
|
+
\u63D0\u793A\uFF1A\u4E0A\u8868\u4E3A\u5168\u90E8\u5DF2\u68C0\u67E5\u8D26\u6237\uFF0C\u6309\u5269\u4F59\u5929\u6570\u5347\u5E8F\uFF1B\u9884\u8B66\u770B\u300C\u547D\u4E2D\u539F\u56E0\u300D\u5217\uFF08JSON \u7528 hitReason !== "none"\uFF09\u3002
|
|
107235
107268
|
- \u5982\u9700\u8C03\u6574\u9608\u503C\uFF1A--threshold-days 14 / --min-balance 500
|
|
107236
107269
|
- \u5982\u9700\u8C03\u6574\u65E5\u5747\u56DE\u770B\u5929\u6570\uFF1A--spend-days 3
|
|
107237
107270
|
- \u5982\u9700\u8C03\u6574\u5EFA\u8BAE\u5145\u503C\u76EE\u6807\u7EED\u822A\uFF1A--target-days 60
|
|
@@ -107243,13 +107276,13 @@ ${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63C
|
|
|
107243
107276
|
// src/commands/balance-scan/_register.ts
|
|
107244
107277
|
function register9(program2) {
|
|
107245
107278
|
program2.command("balance-scan").description(
|
|
107246
|
-
"\u4E00\u952E\u626B\u63CF\u67D0\u5A92\u4F53\u4E0B\
|
|
107279
|
+
"\u4E00\u952E\u626B\u63CF\u67D0\u5A92\u4F53\u4E0B\u6709\u6548\u8D26\u6237\u7684\u4F59\u989D\u4E0E\u65E5\u5747\u6D88\u8017\uFF0C\u8FD4\u56DE\u5168\u90E8\u5DF2\u68C0\u67E5\u8D26\u6237\uFF1B\u7528 hitReason \u6807\u8BB0\u7EED\u822A/\u4F59\u989D\u4E0D\u8DB3\u7684\u9884\u8B66\u884C\u3002\u66FF\u4EE3\u5FAA\u73AF\u8C03\u7528 balance \u7684\u4F20\u7EDF\u505A\u6CD5\u3002"
|
|
107247
107280
|
).requiredOption(
|
|
107248
107281
|
"-m, --media <type>",
|
|
107249
107282
|
"\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
|
|
107250
107283
|
).option(
|
|
107251
107284
|
"-a, --accounts <ids>",
|
|
107252
|
-
"\u6307\u5B9A mediaCustomerId\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF1B\u8DF3\u8FC7\u6E05\u5355\u7FFB\u9875\uFF0C\u62C9\
|
|
107285
|
+
"\u6307\u5B9A mediaCustomerId\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF1B\u8DF3\u8FC7\u6E05\u5355\u7FFB\u9875\uFF0C\u5BF9\u8FD9\u4E9B ID \u62C9\u4F59\u989D+\u6D88\u8017\u540E\u5168\u90E8\u8F93\u51FA"
|
|
107253
107286
|
).option("--threshold-days <n>", "\u5269\u4F59\u7EED\u822A\u5929\u6570\u9608\u503C\uFF08\u9ED8\u8BA4 7\uFF09", (v) => parseFloat(v)).option(
|
|
107254
107287
|
"--spend-days <n>",
|
|
107255
107288
|
"\u65E5\u5747\u6D88\u8017\u56DE\u770B\u81EA\u7136\u65E5\u6570\uFF08\u622A\u81F3\u6628\u5929\u4E0D\u542B\u4ECA\u5929\uFF1B\u9ED8\u8BA4 7\uFF1B\u65E5\u5747=\u7A97\u53E3\u5408\u8BA1/\u5929\u6570\uFF09",
|
|
@@ -112413,7 +112446,7 @@ function cloneKeywordBlockShell(block) {
|
|
|
112413
112446
|
delete shell["matchTypeV2"];
|
|
112414
112447
|
return shell;
|
|
112415
112448
|
}
|
|
112416
|
-
function splitKeywordsForBatchJobBlockIfMixed(block,
|
|
112449
|
+
function splitKeywordsForBatchJobBlockIfMixed(block, path47, warnings) {
|
|
112417
112450
|
const declaredUi = matchTypeV2ToUi(readBlockMatchTypeRaw(block));
|
|
112418
112451
|
const entries = collectKeywordEntriesFromBlock(block);
|
|
112419
112452
|
if (entries.length === 0) return [block];
|
|
@@ -112432,7 +112465,7 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path44, warnings) {
|
|
|
112432
112465
|
(ui) => matchTypeUiToV2(ui)
|
|
112433
112466
|
);
|
|
112434
112467
|
warnings.push(
|
|
112435
|
-
`${
|
|
112468
|
+
`${path47} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
|
|
112436
112469
|
);
|
|
112437
112470
|
const shell = cloneKeywordBlockShell(block);
|
|
112438
112471
|
const splitBlocks = [];
|
|
@@ -112452,16 +112485,16 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path44, warnings) {
|
|
|
112452
112485
|
}
|
|
112453
112486
|
return splitBlocks;
|
|
112454
112487
|
}
|
|
112455
|
-
function validateKeywordCore(core,
|
|
112488
|
+
function validateKeywordCore(core, path47, errors, lengthViolations) {
|
|
112456
112489
|
const trimmed = core.trim();
|
|
112457
112490
|
if (!trimmed) {
|
|
112458
|
-
errors.push(`${
|
|
112491
|
+
errors.push(`${path47} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
|
|
112459
112492
|
return false;
|
|
112460
112493
|
}
|
|
112461
112494
|
if (trimmed.length > GOOGLE_KEYWORD_MAX_CORE_LENGTH) {
|
|
112462
112495
|
if (lengthViolations) {
|
|
112463
112496
|
pushLengthViolation(lengthViolations, {
|
|
112464
|
-
path:
|
|
112497
|
+
path: path47,
|
|
112465
112498
|
field: "KeywordText",
|
|
112466
112499
|
kind: "keyword_core",
|
|
112467
112500
|
limit: GOOGLE_KEYWORD_MAX_CORE_LENGTH,
|
|
@@ -112471,13 +112504,13 @@ function validateKeywordCore(core, path44, errors, lengthViolations) {
|
|
|
112471
112504
|
});
|
|
112472
112505
|
}
|
|
112473
112506
|
errors.push(
|
|
112474
|
-
`${
|
|
112507
|
+
`${path47} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
|
|
112475
112508
|
);
|
|
112476
112509
|
return false;
|
|
112477
112510
|
}
|
|
112478
112511
|
if (!VALID_CORE_REGEX.test(trimmed)) {
|
|
112479
112512
|
errors.push(
|
|
112480
|
-
`${
|
|
112513
|
+
`${path47} \u542B Google \u4E0D\u5141\u8BB8\u7684\u5B57\u7B26\uFF08\u8BCD\u5E72\u4EC5\u5141\u8BB8\u5B57\u6BCD/\u6570\u5B57/\u7A7A\u683C/\u8FDE\u5B57\u7B26/\u53E5\u70B9\uFF09\uFF1A"${trimmed.slice(0, 40)}${trimmed.length > 40 ? "\u2026" : ""}"`
|
|
112481
112514
|
);
|
|
112482
112515
|
return false;
|
|
112483
112516
|
}
|
|
@@ -112534,24 +112567,24 @@ function canonicalizeKeywordBatchBlock(block, resolvedMatchV2, normalizedTexts,
|
|
|
112534
112567
|
}
|
|
112535
112568
|
}
|
|
112536
112569
|
}
|
|
112537
|
-
function pushKeywordAutoFixWarning(warnings,
|
|
112570
|
+
function pushKeywordAutoFixWarning(warnings, path47, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
|
|
112538
112571
|
if (inferredMatchType) {
|
|
112539
112572
|
warnings.push(
|
|
112540
|
-
`${
|
|
112573
|
+
`${path47}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
|
|
112541
112574
|
);
|
|
112542
112575
|
return;
|
|
112543
112576
|
}
|
|
112544
112577
|
if (declaredUi && beforeUi !== declaredUi) {
|
|
112545
112578
|
warnings.push(
|
|
112546
|
-
`${
|
|
112579
|
+
`${path47}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
|
|
112547
112580
|
);
|
|
112548
112581
|
return;
|
|
112549
112582
|
}
|
|
112550
112583
|
if (formatted !== trimmedRaw) {
|
|
112551
|
-
warnings.push(`${
|
|
112584
|
+
warnings.push(`${path47}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
|
|
112552
112585
|
}
|
|
112553
112586
|
}
|
|
112554
|
-
function normalizeKeywordTextList(texts, matchTypeRaw,
|
|
112587
|
+
function normalizeKeywordTextList(texts, matchTypeRaw, path47, fieldLabel, errors, warnings, lengthViolations) {
|
|
112555
112588
|
const declaredUi = matchTypeV2ToUi(matchTypeRaw);
|
|
112556
112589
|
const normalized = [];
|
|
112557
112590
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -112559,7 +112592,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
|
|
|
112559
112592
|
for (let k = 0; k < texts.length; k++) {
|
|
112560
112593
|
const raw = texts[k];
|
|
112561
112594
|
if (typeof raw !== "string" || !raw.trim()) {
|
|
112562
|
-
errors.push(`${
|
|
112595
|
+
errors.push(`${path47}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
|
|
112563
112596
|
continue;
|
|
112564
112597
|
}
|
|
112565
112598
|
const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
|
|
@@ -112570,7 +112603,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
|
|
|
112570
112603
|
);
|
|
112571
112604
|
pushKeywordAutoFixWarning(
|
|
112572
112605
|
warnings,
|
|
112573
|
-
|
|
112606
|
+
path47,
|
|
112574
112607
|
fieldLabel,
|
|
112575
112608
|
trimmedRaw,
|
|
112576
112609
|
formatted,
|
|
@@ -112580,11 +112613,11 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
|
|
|
112580
112613
|
inferredMatchType
|
|
112581
112614
|
);
|
|
112582
112615
|
const core = unwrapKeywordDisplayTextForEdit(formatted);
|
|
112583
|
-
if (!validateKeywordCore(core, `${
|
|
112616
|
+
if (!validateKeywordCore(core, `${path47}.${fieldLabel}[${k}]`, errors, lengthViolations))
|
|
112584
112617
|
continue;
|
|
112585
112618
|
const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
|
|
112586
112619
|
if (seen.has(dedupeKey)) {
|
|
112587
|
-
warnings.push(`${
|
|
112620
|
+
warnings.push(`${path47}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
|
|
112588
112621
|
continue;
|
|
112589
112622
|
}
|
|
112590
112623
|
seen.add(dedupeKey);
|
|
@@ -112593,12 +112626,12 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
|
|
|
112593
112626
|
}
|
|
112594
112627
|
return { normalized, resolvedUi };
|
|
112595
112628
|
}
|
|
112596
|
-
function normalizeKeywordsForBatchJobBlock(block,
|
|
112629
|
+
function normalizeKeywordsForBatchJobBlock(block, path47, errors, warnings, lengthViolations) {
|
|
112597
112630
|
const matchTypeRaw = readBlockMatchTypeRaw(block);
|
|
112598
112631
|
const declaredUi = matchTypeV2ToUi(matchTypeRaw);
|
|
112599
112632
|
if (matchTypeRaw !== void 0 && declaredUi === null) {
|
|
112600
112633
|
errors.push(
|
|
112601
|
-
`${
|
|
112634
|
+
`${path47}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
|
|
112602
112635
|
);
|
|
112603
112636
|
return;
|
|
112604
112637
|
}
|
|
@@ -112613,7 +112646,7 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
|
|
|
112613
112646
|
const result = normalizeKeywordTextList(
|
|
112614
112647
|
texts,
|
|
112615
112648
|
matchTypeRaw,
|
|
112616
|
-
|
|
112649
|
+
path47,
|
|
112617
112650
|
"KeywordText",
|
|
112618
112651
|
errors,
|
|
112619
112652
|
warnings,
|
|
@@ -112622,7 +112655,7 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
|
|
|
112622
112655
|
normalizedTexts = result.normalized;
|
|
112623
112656
|
if (result.resolvedUi) resolvedUi = result.resolvedUi;
|
|
112624
112657
|
if (normalizedTexts.length === 0 && texts.length > 0) {
|
|
112625
|
-
errors.push(`${
|
|
112658
|
+
errors.push(`${path47}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
|
|
112626
112659
|
}
|
|
112627
112660
|
}
|
|
112628
112661
|
if (hasItems && items2) {
|
|
@@ -112630,12 +112663,12 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
|
|
|
112630
112663
|
for (let k = 0; k < items2.length; k++) {
|
|
112631
112664
|
const item = items2[k];
|
|
112632
112665
|
if (!item || typeof item !== "object") {
|
|
112633
|
-
errors.push(`${
|
|
112666
|
+
errors.push(`${path47}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
112634
112667
|
continue;
|
|
112635
112668
|
}
|
|
112636
112669
|
const raw = readItemText(item);
|
|
112637
112670
|
if (raw === null || !raw.trim()) {
|
|
112638
|
-
errors.push(`${
|
|
112671
|
+
errors.push(`${path47}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
|
|
112639
112672
|
continue;
|
|
112640
112673
|
}
|
|
112641
112674
|
const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
|
|
@@ -112646,7 +112679,7 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
|
|
|
112646
112679
|
);
|
|
112647
112680
|
pushKeywordAutoFixWarning(
|
|
112648
112681
|
warnings,
|
|
112649
|
-
|
|
112682
|
+
path47,
|
|
112650
112683
|
`Items[${k}].Text`,
|
|
112651
112684
|
trimmedRaw,
|
|
112652
112685
|
formatted,
|
|
@@ -112656,11 +112689,11 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
|
|
|
112656
112689
|
inferredMatchType
|
|
112657
112690
|
);
|
|
112658
112691
|
const core = unwrapKeywordDisplayTextForEdit(formatted);
|
|
112659
|
-
if (!validateKeywordCore(core, `${
|
|
112692
|
+
if (!validateKeywordCore(core, `${path47}.Items[${k}].Text`, errors, lengthViolations))
|
|
112660
112693
|
continue;
|
|
112661
112694
|
const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
|
|
112662
112695
|
if (seen.has(dedupeKey)) {
|
|
112663
|
-
warnings.push(`${
|
|
112696
|
+
warnings.push(`${path47}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
|
|
112664
112697
|
continue;
|
|
112665
112698
|
}
|
|
112666
112699
|
seen.add(dedupeKey);
|
|
@@ -112805,16 +112838,16 @@ function warnIfNegativeKeywordsInPositiveKeywords(campaign, warnings) {
|
|
|
112805
112838
|
const raw = texts[ki];
|
|
112806
112839
|
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
112807
112840
|
const core = normalizeKeywordCoreForNegativeCheck(raw);
|
|
112808
|
-
const
|
|
112841
|
+
const path47 = `campaign.AdGroupsForBatchJob[${gi}].KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
|
|
112809
112842
|
if (plannedNegCores.has(core)) {
|
|
112810
112843
|
warnings.push(
|
|
112811
|
-
`${
|
|
112844
|
+
`${path47} \u8BCD\u9762\u300C${raw}\u300D\u5DF2\u51FA\u73B0\u5728 campaign.NegativeKeywordsForBatchJob\uFF0C\u4E0D\u5E94\u91CD\u590D\u5199\u5165\u6B63\u5411 KeywordsForBatchJob\uFF1B\u5426\u8BCD\u53EA\u653E NegativeKeywordsForBatchJob`
|
|
112812
112845
|
);
|
|
112813
112846
|
continue;
|
|
112814
112847
|
}
|
|
112815
112848
|
if (looksLikeStandaloneNegativeKeyword(core)) {
|
|
112816
112849
|
warnings.push(
|
|
112817
|
-
`${
|
|
112850
|
+
`${path47} \u8BCD\u9762\u300C${raw}\u300D\u7591\u4F3C\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u5E38\u89C1\u5426\u8BCD\u8BCD\u6839\uFF09\uFF0C\u5E94\u5199\u5165 campaign.NegativeKeywordsForBatchJob\uFF08MatchTypeV2: BROAD\uFF09\uFF0C\u52FF\u653E\u5165 KeywordsForBatchJob`
|
|
112818
112851
|
);
|
|
112819
112852
|
}
|
|
112820
112853
|
}
|
|
@@ -116445,13 +116478,13 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116445
116478
|
for (let ki = 0; ki < texts.length; ki++) {
|
|
116446
116479
|
const t = texts[ki];
|
|
116447
116480
|
if (typeof t !== "string" || !t.trim()) continue;
|
|
116448
|
-
const
|
|
116481
|
+
const path47 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
|
|
116449
116482
|
const key = keywordKey(t, matchTypeV2);
|
|
116450
116483
|
const plannedContent = `\u8BCD\u9762: ${t} | \u5339\u914D: ${matchTypeV2}`;
|
|
116451
116484
|
if (groupAbsent) {
|
|
116452
116485
|
pushItem(items2, {
|
|
116453
116486
|
layer: "keyword",
|
|
116454
|
-
path:
|
|
116487
|
+
path: path47,
|
|
116455
116488
|
adGroupName: groupName,
|
|
116456
116489
|
plannedContent,
|
|
116457
116490
|
status: "skipped",
|
|
@@ -116463,7 +116496,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116463
116496
|
if (!liveKwKeys.has(key)) {
|
|
116464
116497
|
pushItem(items2, {
|
|
116465
116498
|
layer: "keyword",
|
|
116466
|
-
path:
|
|
116499
|
+
path: path47,
|
|
116467
116500
|
adGroupName: groupName,
|
|
116468
116501
|
plannedContent,
|
|
116469
116502
|
status: "missing",
|
|
@@ -116473,7 +116506,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116473
116506
|
} else {
|
|
116474
116507
|
pushItem(items2, {
|
|
116475
116508
|
layer: "keyword",
|
|
116476
|
-
path:
|
|
116509
|
+
path: path47,
|
|
116477
116510
|
adGroupName: groupName,
|
|
116478
116511
|
plannedContent,
|
|
116479
116512
|
status: "ok",
|
|
@@ -116498,14 +116531,14 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116498
116531
|
for (let ai = 0; ai < ads.length; ai++) {
|
|
116499
116532
|
const ad = asRecord3(ads[ai]);
|
|
116500
116533
|
if (!ad) continue;
|
|
116501
|
-
const
|
|
116534
|
+
const path47 = `${groupPath}.AdsForBatchJob[${ai}]`;
|
|
116502
116535
|
const primary = pickString2(ad["headlinePart1"], ad["AdTitle"]);
|
|
116503
116536
|
const finalUrl = pickString2(ad["Finalurl"], ad["DestinationUrl"], ad["finalUrl"]);
|
|
116504
116537
|
const rsaAssets = listPlannedRsaAssets(ad, groupPath, ai, groupName);
|
|
116505
116538
|
if (groupAbsent) {
|
|
116506
116539
|
pushItem(items2, {
|
|
116507
116540
|
layer: "ad",
|
|
116508
|
-
path:
|
|
116541
|
+
path: path47,
|
|
116509
116542
|
adGroupName: groupName,
|
|
116510
116543
|
plannedContent: primary ? `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}` : `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF09`,
|
|
116511
116544
|
status: "skipped",
|
|
@@ -116529,7 +116562,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116529
116562
|
if (!primary) {
|
|
116530
116563
|
pushItem(items2, {
|
|
116531
116564
|
layer: "ad",
|
|
116532
|
-
path:
|
|
116565
|
+
path: path47,
|
|
116533
116566
|
adGroupName: groupName,
|
|
116534
116567
|
plannedContent: `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF0C\u65E0\u6CD5\u6BD4\u5BF9\u6574\u6761 RSA\uFF09`,
|
|
116535
116568
|
status: "missing",
|
|
@@ -116555,7 +116588,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116555
116588
|
const rsaFound = liveAdsInGroup.some((la) => liveAdHasHeadline(la, primary));
|
|
116556
116589
|
pushItem(items2, {
|
|
116557
116590
|
layer: "ad",
|
|
116558
|
-
path:
|
|
116591
|
+
path: path47,
|
|
116559
116592
|
adGroupName: groupName,
|
|
116560
116593
|
plannedContent: `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}`,
|
|
116561
116594
|
status: rsaFound ? "ok" : "missing",
|
|
@@ -116621,11 +116654,11 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116621
116654
|
for (let ei = 0; ei < extBlocks.length; ei++) {
|
|
116622
116655
|
const ext = asRecord3(extBlocks[ei]);
|
|
116623
116656
|
const plannedContent = formatExtensionPlanned(ext);
|
|
116624
|
-
const
|
|
116657
|
+
const path47 = `campaign.ExtensionsForBatchJob[${ei}]`;
|
|
116625
116658
|
if (campaignMissing) {
|
|
116626
116659
|
pushItem(items2, {
|
|
116627
116660
|
layer: "extension",
|
|
116628
|
-
path:
|
|
116661
|
+
path: path47,
|
|
116629
116662
|
plannedContent,
|
|
116630
116663
|
status: "skipped",
|
|
116631
116664
|
liveNote: "\u7CFB\u5217\u672A\u627E\u5230\uFF0C\u8DF3\u8FC7\u9644\u52A0\u4FE1\u606F\u6BD4\u5BF9",
|
|
@@ -116637,7 +116670,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116637
116670
|
const remediateCommand = !found && ext && accountId && campaignId ? buildExtensionRemediateCommandLine({ accountId, campaignId, ext }) ?? void 0 : void 0;
|
|
116638
116671
|
pushItem(items2, {
|
|
116639
116672
|
layer: "extension",
|
|
116640
|
-
path:
|
|
116673
|
+
path: path47,
|
|
116641
116674
|
plannedContent,
|
|
116642
116675
|
status: found ? "ok" : "missing",
|
|
116643
116676
|
liveNote: `\u7CFB\u5217\u7EA7\u9644\u52A0\u4FE1\u606F\u8D26\u6237\u5185 ${liveExt}/${plannedExt} \u6761`,
|
|
@@ -116664,11 +116697,11 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116664
116697
|
if (!id) continue;
|
|
116665
116698
|
const displayName = locationNames[li] || pickString2(loc["locationName"], loc["name"], loc["countryOrRegion"]) || "";
|
|
116666
116699
|
const plannedContent = `locationId: ${id}${displayName ? ` | \u540D\u79F0: ${displayName}` : ""}`;
|
|
116667
|
-
const
|
|
116700
|
+
const path47 = `campaign.targetedLocations[${li}]`;
|
|
116668
116701
|
if (campaignMissing) {
|
|
116669
116702
|
pushItem(items2, {
|
|
116670
116703
|
layer: "location",
|
|
116671
|
-
path:
|
|
116704
|
+
path: path47,
|
|
116672
116705
|
plannedContent,
|
|
116673
116706
|
status: "skipped",
|
|
116674
116707
|
liveNote: "\u7CFB\u5217\u672A\u627E\u5230\uFF0C\u8DF3\u8FC7\u5730\u57DF\u6BD4\u5BF9",
|
|
@@ -116680,7 +116713,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
116680
116713
|
const remediateCommand = !found && accountId && campaignId ? buildGeoRemediateCommandLine({ accountId, campaignId, locationId: id }) : void 0;
|
|
116681
116714
|
pushItem(items2, {
|
|
116682
116715
|
layer: "location",
|
|
116683
|
-
path:
|
|
116716
|
+
path: path47,
|
|
116684
116717
|
plannedContent,
|
|
116685
116718
|
status: found ? "ok" : "missing",
|
|
116686
116719
|
liveNote: `\u7CFB\u5217\u5DF2\u5B9A\u5411 ${liveLocIds.size}/${plannedLocCount} \u4E2A\u5730\u57DF`,
|
|
@@ -118192,8 +118225,8 @@ function pmaxChannelTypesUrl(googleApiUrl) {
|
|
|
118192
118225
|
}
|
|
118193
118226
|
function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
|
|
118194
118227
|
const base = googleApiUrl.replace(/\/$/, "");
|
|
118195
|
-
const
|
|
118196
|
-
return campaignId ? `${
|
|
118228
|
+
const path47 = `${base}/accounts/${accountId}/campaign/pmax`;
|
|
118229
|
+
return campaignId ? `${path47}/${campaignId}` : path47;
|
|
118197
118230
|
}
|
|
118198
118231
|
function pmaxBrandAssetsUrl(googleApiUrl, accountId, campaignId) {
|
|
118199
118232
|
return `${pmaxCampaignUrl(googleApiUrl, accountId, campaignId)}/brand-assets`;
|
|
@@ -118203,8 +118236,8 @@ function pmaxBrandGuidelinesEnableUrl(googleApiUrl, accountId, campaignId) {
|
|
|
118203
118236
|
}
|
|
118204
118237
|
function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
|
|
118205
118238
|
const base = googleApiUrl.replace(/\/$/, "");
|
|
118206
|
-
const
|
|
118207
|
-
return suffix ? `${
|
|
118239
|
+
const path47 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
|
|
118240
|
+
return suffix ? `${path47}/${suffix.replace(/^\//, "")}` : path47;
|
|
118208
118241
|
}
|
|
118209
118242
|
function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
|
|
118210
118243
|
const base = googleApiUrl.replace(/\/$/, "");
|
|
@@ -120945,8 +120978,8 @@ async function runAdPmaxImageConvert(opts) {
|
|
|
120945
120978
|
}
|
|
120946
120979
|
|
|
120947
120980
|
// src/commands/ad/pmax-brand-mgmt.ts
|
|
120948
|
-
function loadBrandConfig(
|
|
120949
|
-
const cfg = loadPmaxJsonFile(
|
|
120981
|
+
function loadBrandConfig(path47, accountId, campaignId) {
|
|
120982
|
+
const cfg = loadPmaxJsonFile(path47);
|
|
120950
120983
|
return {
|
|
120951
120984
|
...cfg,
|
|
120952
120985
|
account: String(cfg["account"] ?? accountId).trim(),
|
|
@@ -129664,9 +129697,9 @@ function asRecord7(value) {
|
|
|
129664
129697
|
}
|
|
129665
129698
|
return null;
|
|
129666
129699
|
}
|
|
129667
|
-
function getNestedValue(root,
|
|
129700
|
+
function getNestedValue(root, path47) {
|
|
129668
129701
|
let cur = root;
|
|
129669
|
-
for (const key of
|
|
129702
|
+
for (const key of path47) {
|
|
129670
129703
|
const rec = asRecord7(cur);
|
|
129671
129704
|
if (!rec) return void 0;
|
|
129672
129705
|
cur = rec[key];
|
|
@@ -133961,6 +133994,855 @@ function registerTikTokAnalysisCommands(program2) {
|
|
|
133961
133994
|
);
|
|
133962
133995
|
}
|
|
133963
133996
|
|
|
133997
|
+
// src/commands/yandex-analysis/register-cli.ts
|
|
133998
|
+
import { Command as Command5 } from "commander";
|
|
133999
|
+
|
|
134000
|
+
// src/commands/yandex-analysis/sections.ts
|
|
134001
|
+
var YANDEX_SECTIONS = [
|
|
134002
|
+
{
|
|
134003
|
+
name: "overview",
|
|
134004
|
+
description: "\u8D26\u6237\u603B\u89C8\uFF1A\u4F59\u989D + \u533A\u95F4\u6C47\u603B KPI",
|
|
134005
|
+
path: "overview"
|
|
134006
|
+
},
|
|
134007
|
+
{
|
|
134008
|
+
name: "daily",
|
|
134009
|
+
description: "\u6309\u65E5\u7EE9\u6548\uFF08\u53EF\u4F9B\u8C03\u7528\u65B9\u81EA\u884C\u805A\u5468\uFF09",
|
|
134010
|
+
path: "daily"
|
|
134011
|
+
},
|
|
134012
|
+
{
|
|
134013
|
+
name: "search",
|
|
134014
|
+
description: "\u7F51\u7EDC\u7EF4\u5EA6\uFF08\u4EC5 SEARCH\uFF09",
|
|
134015
|
+
path: "search"
|
|
134016
|
+
},
|
|
134017
|
+
{
|
|
134018
|
+
name: "campaigns",
|
|
134019
|
+
description: "Campaign \u7EE9\u6548 + \u9884\u7B97/\u6218\u7565/targetCpa",
|
|
134020
|
+
path: "campaigns",
|
|
134021
|
+
limitOption: true
|
|
134022
|
+
},
|
|
134023
|
+
{
|
|
134024
|
+
name: "keywords",
|
|
134025
|
+
description: "\u5173\u952E\u8BCD\uFF08Criteria\uFF09\u7EE9\u6548",
|
|
134026
|
+
path: "keywords",
|
|
134027
|
+
limitOption: true
|
|
134028
|
+
},
|
|
134029
|
+
{
|
|
134030
|
+
name: "search-terms",
|
|
134031
|
+
description: "\u641C\u7D22\u8BCD\u7EE9\u6548\uFF08Direct \u9650\u5236\uFF1A\u4EC5\u8FD1 180 \u5929\uFF09",
|
|
134032
|
+
path: "search-terms",
|
|
134033
|
+
limitOption: true,
|
|
134034
|
+
searchTermsMaxDays: 180
|
|
134035
|
+
},
|
|
134036
|
+
{
|
|
134037
|
+
name: "geo",
|
|
134038
|
+
description: "\u5730\u57DF\u7EE9\u6548\uFF08\u4EC5 SEARCH\uFF09",
|
|
134039
|
+
path: "geo",
|
|
134040
|
+
limitOption: true
|
|
134041
|
+
},
|
|
134042
|
+
{
|
|
134043
|
+
name: "devices",
|
|
134044
|
+
description: "\u8BBE\u5907\u7EE9\u6548\uFF08\u4EC5 SEARCH\uFF09",
|
|
134045
|
+
path: "devices"
|
|
134046
|
+
}
|
|
134047
|
+
];
|
|
134048
|
+
var YANDEX_SECTION_NAMES = YANDEX_SECTIONS.map((s) => s.name);
|
|
134049
|
+
var YANDEX_SECTION_ALIASES = {
|
|
134050
|
+
geographic: "geo",
|
|
134051
|
+
device: "devices",
|
|
134052
|
+
"search-queries": "search-terms"
|
|
134053
|
+
};
|
|
134054
|
+
|
|
134055
|
+
// src/commands/yandex-analysis/run-batch.ts
|
|
134056
|
+
init_auth();
|
|
134057
|
+
import * as path43 from "path";
|
|
134058
|
+
import { performance as performance8 } from "perf_hooks";
|
|
134059
|
+
init_version();
|
|
134060
|
+
|
|
134061
|
+
// src/commands/yandex-analysis/fetch.ts
|
|
134062
|
+
init_auth();
|
|
134063
|
+
var YANDEX_KEYWORD_LIMIT_DEFAULT = 100;
|
|
134064
|
+
var YANDEX_KEYWORD_LIMIT_MAX = 1e4;
|
|
134065
|
+
var YANDEX_SEARCH_TERMS_MAX_DAYS = 180;
|
|
134066
|
+
function stripLocalDate2(d) {
|
|
134067
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
134068
|
+
}
|
|
134069
|
+
function parseLocalYmd2(ymd) {
|
|
134070
|
+
const parts = ymd.trim().split("-").map((x) => parseInt(x, 10));
|
|
134071
|
+
if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) {
|
|
134072
|
+
return /* @__PURE__ */ new Date(NaN);
|
|
134073
|
+
}
|
|
134074
|
+
const [y, m, day] = parts;
|
|
134075
|
+
return new Date(y, m - 1, day);
|
|
134076
|
+
}
|
|
134077
|
+
function formatLocalYmd2(d) {
|
|
134078
|
+
const y = d.getFullYear();
|
|
134079
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
134080
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
134081
|
+
return `${y}-${m}-${day}`;
|
|
134082
|
+
}
|
|
134083
|
+
function daysInclusive(start, end) {
|
|
134084
|
+
const ms = stripLocalDate2(end).getTime() - stripLocalDate2(start).getTime();
|
|
134085
|
+
return Math.floor(ms / 864e5) + 1;
|
|
134086
|
+
}
|
|
134087
|
+
function resolveYandexDateRange(start, end) {
|
|
134088
|
+
if (start && end) {
|
|
134089
|
+
const s = parseLocalYmd2(start);
|
|
134090
|
+
const e = parseLocalYmd2(end);
|
|
134091
|
+
if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime())) {
|
|
134092
|
+
console.error("\n\u274C --start / --end \u987B\u4E3A\u6709\u6548\u65E5\u671F YYYY-MM-DD\u3002\n");
|
|
134093
|
+
process.exit(1);
|
|
134094
|
+
}
|
|
134095
|
+
if (s > e) {
|
|
134096
|
+
console.error("\n\u274C \u5F00\u59CB\u65E5\u671F\u4E0D\u80FD\u665A\u4E8E\u7ED3\u675F\u65E5\u671F\u3002\n");
|
|
134097
|
+
process.exit(1);
|
|
134098
|
+
}
|
|
134099
|
+
const today = stripLocalDate2(/* @__PURE__ */ new Date());
|
|
134100
|
+
if (stripLocalDate2(s) > today || stripLocalDate2(e) > today) {
|
|
134101
|
+
console.error("\n\u274C Yandex \u62A5\u8868\u8D77\u6B62\u65E5\u671F\u4E0D\u80FD\u665A\u4E8E\u4ECA\u5929\u3002\n");
|
|
134102
|
+
process.exit(1);
|
|
134103
|
+
}
|
|
134104
|
+
return { startDate: start.trim(), endDate: end.trim() };
|
|
134105
|
+
}
|
|
134106
|
+
if (!start && !end) {
|
|
134107
|
+
const endD = stripLocalDate2(/* @__PURE__ */ new Date());
|
|
134108
|
+
const startD = new Date(endD);
|
|
134109
|
+
startD.setDate(startD.getDate() - 6);
|
|
134110
|
+
return { startDate: formatLocalYmd2(startD), endDate: formatLocalYmd2(endD) };
|
|
134111
|
+
}
|
|
134112
|
+
console.error(
|
|
134113
|
+
"\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u533A\u95F4\uFF08\u8FD1 7 \u5929\uFF0C\u542B\u4ECA\u5929\uFF09\u3002\n"
|
|
134114
|
+
);
|
|
134115
|
+
process.exit(1);
|
|
134116
|
+
}
|
|
134117
|
+
function assertYandexSearchTermsRange(startDate, endDate) {
|
|
134118
|
+
const s = parseLocalYmd2(startDate);
|
|
134119
|
+
const e = parseLocalYmd2(endDate);
|
|
134120
|
+
const today = stripLocalDate2(/* @__PURE__ */ new Date());
|
|
134121
|
+
const earliest = new Date(today);
|
|
134122
|
+
earliest.setDate(earliest.getDate() - (YANDEX_SEARCH_TERMS_MAX_DAYS - 1));
|
|
134123
|
+
if (stripLocalDate2(s) < earliest || stripLocalDate2(e) > today) {
|
|
134124
|
+
console.error(
|
|
134125
|
+
`
|
|
134126
|
+
\u274C search-terms \u4EC5\u652F\u6301\u8FD1 ${YANDEX_SEARCH_TERMS_MAX_DAYS} \u5929\uFF08Direct \u9650\u5236\uFF09\u3002\u8BF7\u5C06 --start/--end \u9650\u5236\u5728 ${formatLocalYmd2(earliest)} ~ ${formatLocalYmd2(today)}\u3002
|
|
134127
|
+
`
|
|
134128
|
+
);
|
|
134129
|
+
process.exit(1);
|
|
134130
|
+
}
|
|
134131
|
+
if (daysInclusive(s, e) > YANDEX_SEARCH_TERMS_MAX_DAYS) {
|
|
134132
|
+
console.error(
|
|
134133
|
+
`
|
|
134134
|
+
\u274C search-terms \u67E5\u8BE2\u8DE8\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${YANDEX_SEARCH_TERMS_MAX_DAYS} \u5929\u3002
|
|
134135
|
+
`
|
|
134136
|
+
);
|
|
134137
|
+
process.exit(1);
|
|
134138
|
+
}
|
|
134139
|
+
}
|
|
134140
|
+
function assertYandexAccountId(id) {
|
|
134141
|
+
const t = id.trim();
|
|
134142
|
+
if (!t) {
|
|
134143
|
+
console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\uFF08Yandex Client-Login / mediaCustomerId\uFF0C\u5982 porg-xxx\uFF09\u3002\n");
|
|
134144
|
+
process.exit(1);
|
|
134145
|
+
}
|
|
134146
|
+
return t;
|
|
134147
|
+
}
|
|
134148
|
+
function requireYandexApi(config) {
|
|
134149
|
+
const base = (config.yandexApiUrl ?? "").replace(/\/$/, "");
|
|
134150
|
+
if (!base) {
|
|
134151
|
+
console.error(
|
|
134152
|
+
"\n\u274C \u672A\u627E\u5230 Yandex API \u5730\u5740\uFF08yandexApiUrl\uFF09\u3002\n \u4ECE TSO API \u5730\u5740\u81EA\u52A8\u63A8\u5BFC\uFF0C\u4E5F\u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF SILUZAN_YANDEX_API \u8986\u76D6\u3002\n"
|
|
134153
|
+
);
|
|
134154
|
+
process.exit(1);
|
|
134155
|
+
}
|
|
134156
|
+
return base;
|
|
134157
|
+
}
|
|
134158
|
+
function resolveYandexLimit(raw) {
|
|
134159
|
+
const n = typeof raw === "number" ? raw : parseInt(String(raw ?? ""), 10);
|
|
134160
|
+
if (!Number.isFinite(n) || n <= 0) return YANDEX_KEYWORD_LIMIT_DEFAULT;
|
|
134161
|
+
return Math.min(Math.floor(n), YANDEX_KEYWORD_LIMIT_MAX);
|
|
134162
|
+
}
|
|
134163
|
+
function analysisUrl(yandexApiUrl, pathSeg, query) {
|
|
134164
|
+
const base = yandexApiUrl.replace(/\/$/, "");
|
|
134165
|
+
const q = query ? query.startsWith("?") ? query : `?${query}` : "";
|
|
134166
|
+
return `${base}/yandex-analysis/${pathSeg}${q}`;
|
|
134167
|
+
}
|
|
134168
|
+
async function fetchYandexSectionPayload(def, opts, config, accountId) {
|
|
134169
|
+
const { startDate, endDate } = resolveYandexDateRange(opts.start, opts.end);
|
|
134170
|
+
if (def.searchTermsMaxDays) {
|
|
134171
|
+
assertYandexSearchTermsRange(startDate, endDate);
|
|
134172
|
+
}
|
|
134173
|
+
const yandexApiUrl = requireYandexApi(config);
|
|
134174
|
+
const params = new URLSearchParams({
|
|
134175
|
+
account: accountId,
|
|
134176
|
+
start: startDate,
|
|
134177
|
+
end: endDate
|
|
134178
|
+
});
|
|
134179
|
+
if (def.limitOption) {
|
|
134180
|
+
params.set("limit", String(resolveYandexLimit(opts.limit)));
|
|
134181
|
+
}
|
|
134182
|
+
const url = analysisUrl(yandexApiUrl, def.path, params.toString());
|
|
134183
|
+
return apiFetch2(url, config, {}, !!opts.verbose);
|
|
134184
|
+
}
|
|
134185
|
+
function endpointHintForYandexSection(def) {
|
|
134186
|
+
return `GET \u2026/yandex-analysis/${def.path}?account=&start=&end=`;
|
|
134187
|
+
}
|
|
134188
|
+
|
|
134189
|
+
// src/commands/yandex-analysis/resolve-sections.ts
|
|
134190
|
+
function normalizeSectionToken4(raw) {
|
|
134191
|
+
const t = raw.trim();
|
|
134192
|
+
if (!t) return null;
|
|
134193
|
+
if (YANDEX_SECTION_NAMES.includes(t)) {
|
|
134194
|
+
return t;
|
|
134195
|
+
}
|
|
134196
|
+
return YANDEX_SECTION_ALIASES[t] ?? null;
|
|
134197
|
+
}
|
|
134198
|
+
function resolveSectionList5(sections, exclude) {
|
|
134199
|
+
const splitClean = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
134200
|
+
const include = splitClean(sections);
|
|
134201
|
+
const dropRaw = splitClean(exclude);
|
|
134202
|
+
const unknown = [...include, ...dropRaw].filter((n) => normalizeSectionToken4(n) === null);
|
|
134203
|
+
if (unknown.length > 0) {
|
|
134204
|
+
console.error(
|
|
134205
|
+
`
|
|
134206
|
+
\u274C \u672A\u77E5\u7684 section \u540D\u79F0\uFF1A${unknown.join(", ")}
|
|
134207
|
+
\u53EF\u9009\uFF1A${YANDEX_SECTION_NAMES.join(", ")}
|
|
134208
|
+
\u522B\u540D\uFF1A${Object.keys(YANDEX_SECTION_ALIASES).join(", ")}
|
|
134209
|
+
`
|
|
134210
|
+
);
|
|
134211
|
+
process.exit(1);
|
|
134212
|
+
}
|
|
134213
|
+
const includeCanonical = new Set(include.map((n) => normalizeSectionToken4(n)).filter(Boolean));
|
|
134214
|
+
const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken4(n)).filter(Boolean));
|
|
134215
|
+
const base = include.length > 0 ? YANDEX_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...YANDEX_SECTIONS];
|
|
134216
|
+
return base.filter((s) => !dropCanonical.has(s.name));
|
|
134217
|
+
}
|
|
134218
|
+
|
|
134219
|
+
// src/commands/yandex-analysis/run-batch.ts
|
|
134220
|
+
async function runWithConcurrency6(tasks, concurrency) {
|
|
134221
|
+
const results = new Array(tasks.length);
|
|
134222
|
+
let cursor = 0;
|
|
134223
|
+
const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
|
|
134224
|
+
while (true) {
|
|
134225
|
+
const i = cursor++;
|
|
134226
|
+
if (i >= tasks.length) return;
|
|
134227
|
+
results[i] = await tasks[i]();
|
|
134228
|
+
}
|
|
134229
|
+
});
|
|
134230
|
+
await Promise.all(workers);
|
|
134231
|
+
return results;
|
|
134232
|
+
}
|
|
134233
|
+
async function runAllYandexSections(opts) {
|
|
134234
|
+
const id = assertYandexAccountId(opts.account);
|
|
134235
|
+
const config = loadConfig(opts.token);
|
|
134236
|
+
if (!opts.jsonOut?.trim()) {
|
|
134237
|
+
console.error("\n\u274C \u5FC5\u987B\u4F20 --json-out <\u76EE\u5F55>\uFF08\u5404\u7EF4\u5EA6\u843D\u76D8\u540E\u7531 report-manifest \u7D22\u5F15\uFF09\u3002\n");
|
|
134238
|
+
process.exit(1);
|
|
134239
|
+
}
|
|
134240
|
+
const targets = resolveSectionList5(opts.sections, opts.exclude);
|
|
134241
|
+
if (targets.length === 0) {
|
|
134242
|
+
console.error("\n\u274C \u6CA1\u6709\u8981\u6267\u884C\u7684 section\uFF08\u8BF7\u68C0\u67E5 --sections / --exclude\uFF09\u3002\n");
|
|
134243
|
+
process.exit(1);
|
|
134244
|
+
}
|
|
134245
|
+
const { startDate, endDate } = resolveYandexDateRange(opts.start, opts.end);
|
|
134246
|
+
const concurrency = Math.max(
|
|
134247
|
+
1,
|
|
134248
|
+
Math.min(typeof opts.concurrency === "number" ? opts.concurrency : 5, 16)
|
|
134249
|
+
);
|
|
134250
|
+
const cliVersion = getCurrentVersion2();
|
|
134251
|
+
const overallT0 = performance8.now();
|
|
134252
|
+
const tasks = targets.map((def) => async () => {
|
|
134253
|
+
const t0 = performance8.now();
|
|
134254
|
+
const hint = endpointHintForYandexSection(def);
|
|
134255
|
+
try {
|
|
134256
|
+
const payload = await fetchYandexSectionPayload(
|
|
134257
|
+
def,
|
|
134258
|
+
{ ...opts, start: startDate, end: endDate },
|
|
134259
|
+
config,
|
|
134260
|
+
id
|
|
134261
|
+
);
|
|
134262
|
+
const summary2 = await writeReportAnalysisSnapshot({
|
|
134263
|
+
snapshotDir: opts.jsonOut,
|
|
134264
|
+
section: def.name,
|
|
134265
|
+
accountId: id,
|
|
134266
|
+
dateRange: { start: startDate, end: endDate },
|
|
134267
|
+
payload,
|
|
134268
|
+
cliVersion
|
|
134269
|
+
});
|
|
134270
|
+
return {
|
|
134271
|
+
section: def.name,
|
|
134272
|
+
ok: true,
|
|
134273
|
+
elapsedMs: performance8.now() - t0,
|
|
134274
|
+
file: summary2.writtenFiles[0],
|
|
134275
|
+
endpointHint: hint
|
|
134276
|
+
};
|
|
134277
|
+
} catch (err) {
|
|
134278
|
+
return {
|
|
134279
|
+
section: def.name,
|
|
134280
|
+
ok: false,
|
|
134281
|
+
elapsedMs: performance8.now() - t0,
|
|
134282
|
+
error: err instanceof Error ? err.message : String(err),
|
|
134283
|
+
endpointHint: hint
|
|
134284
|
+
};
|
|
134285
|
+
}
|
|
134286
|
+
});
|
|
134287
|
+
const results = await runWithConcurrency6(tasks, concurrency);
|
|
134288
|
+
const succeeded = results.filter((r) => r.ok).length;
|
|
134289
|
+
const failed = results.length - succeeded;
|
|
134290
|
+
const absoluteSnapshotDir = path43.resolve(opts.jsonOut);
|
|
134291
|
+
const summary = {
|
|
134292
|
+
kind: "siluzan-tso-yandex-analysis-snapshot-batch",
|
|
134293
|
+
absoluteSnapshotDir,
|
|
134294
|
+
manifestFile: reportManifestFile(id),
|
|
134295
|
+
accountId: id,
|
|
134296
|
+
totalSections: results.length,
|
|
134297
|
+
succeeded,
|
|
134298
|
+
failed,
|
|
134299
|
+
concurrency,
|
|
134300
|
+
elapsedMs: performance8.now() - overallT0,
|
|
134301
|
+
results
|
|
134302
|
+
};
|
|
134303
|
+
console.log(JSON.stringify(summary));
|
|
134304
|
+
if (failed > 0) process.exit(2);
|
|
134305
|
+
}
|
|
134306
|
+
|
|
134307
|
+
// src/commands/yandex-analysis/render-report.ts
|
|
134308
|
+
import fs34 from "fs";
|
|
134309
|
+
import fsPromises7 from "fs/promises";
|
|
134310
|
+
import path45 from "path";
|
|
134311
|
+
import { fileURLToPath as fileURLToPath11 } from "url";
|
|
134312
|
+
|
|
134313
|
+
// src/commands/yandex-analysis/merge-snapshot.ts
|
|
134314
|
+
import * as fs33 from "fs/promises";
|
|
134315
|
+
import * as path44 from "path";
|
|
134316
|
+
function asRecord26(value) {
|
|
134317
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
134318
|
+
return value;
|
|
134319
|
+
}
|
|
134320
|
+
return null;
|
|
134321
|
+
}
|
|
134322
|
+
function num10(value) {
|
|
134323
|
+
const n = Number(value);
|
|
134324
|
+
return Number.isFinite(n) ? n : void 0;
|
|
134325
|
+
}
|
|
134326
|
+
function round25(value) {
|
|
134327
|
+
return value == null ? void 0 : Math.round(value * 100) / 100;
|
|
134328
|
+
}
|
|
134329
|
+
function asItems(payload) {
|
|
134330
|
+
const obj = asRecord26(payload);
|
|
134331
|
+
if (!obj) return [];
|
|
134332
|
+
const items2 = obj.items;
|
|
134333
|
+
if (!Array.isArray(items2)) return [];
|
|
134334
|
+
return items2.filter((r) => asRecord26(r) !== null);
|
|
134335
|
+
}
|
|
134336
|
+
function pickMetrics2(row, extras = []) {
|
|
134337
|
+
const out = {
|
|
134338
|
+
spend: num10(row.spend) ?? 0,
|
|
134339
|
+
impressions: num10(row.impressions) ?? 0,
|
|
134340
|
+
clicks: num10(row.clicks) ?? 0,
|
|
134341
|
+
ctr: num10(row.ctr),
|
|
134342
|
+
averageCpc: num10(row.averageCpc),
|
|
134343
|
+
conversions: num10(row.conversions) ?? 0,
|
|
134344
|
+
conversionRate: num10(row.conversionRate),
|
|
134345
|
+
costPerConversion: num10(row.costPerConversion),
|
|
134346
|
+
roi: num10(row.roi)
|
|
134347
|
+
};
|
|
134348
|
+
for (const f of extras) {
|
|
134349
|
+
if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
|
|
134350
|
+
}
|
|
134351
|
+
return out;
|
|
134352
|
+
}
|
|
134353
|
+
async function loadSectionPayload3(snapshotDir, file) {
|
|
134354
|
+
try {
|
|
134355
|
+
const raw = await fs33.readFile(path44.join(snapshotDir, file), "utf8");
|
|
134356
|
+
return JSON.parse(raw);
|
|
134357
|
+
} catch {
|
|
134358
|
+
return null;
|
|
134359
|
+
}
|
|
134360
|
+
}
|
|
134361
|
+
async function readManifest6(snapshotDir, accountId) {
|
|
134362
|
+
const candidates = [];
|
|
134363
|
+
if (accountId) candidates.push(reportManifestFile(accountId));
|
|
134364
|
+
try {
|
|
134365
|
+
const entries = await fs33.readdir(snapshotDir);
|
|
134366
|
+
for (const name2 of entries.filter(
|
|
134367
|
+
(n) => n.startsWith("report-manifest") && n.endsWith(".json")
|
|
134368
|
+
)) {
|
|
134369
|
+
if (!candidates.includes(name2)) candidates.push(name2);
|
|
134370
|
+
}
|
|
134371
|
+
} catch {
|
|
134372
|
+
}
|
|
134373
|
+
for (const name2 of candidates) {
|
|
134374
|
+
try {
|
|
134375
|
+
const raw = await fs33.readFile(path44.join(snapshotDir, name2), "utf8");
|
|
134376
|
+
const parsed = JSON.parse(raw);
|
|
134377
|
+
if (Array.isArray(parsed.artifacts) && parsed.artifacts.length > 0) return parsed;
|
|
134378
|
+
} catch {
|
|
134379
|
+
}
|
|
134380
|
+
}
|
|
134381
|
+
return null;
|
|
134382
|
+
}
|
|
134383
|
+
function parseYmd2(value) {
|
|
134384
|
+
const s = String(value ?? "").trim().slice(0, 10);
|
|
134385
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
134386
|
+
if (!m) return null;
|
|
134387
|
+
const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
134388
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
134389
|
+
}
|
|
134390
|
+
function formatYmd2(d) {
|
|
134391
|
+
const y = d.getFullYear();
|
|
134392
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
134393
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
134394
|
+
return `${y}-${m}-${day}`;
|
|
134395
|
+
}
|
|
134396
|
+
function isoWeekKey(d) {
|
|
134397
|
+
const day = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
134398
|
+
const dow = (day.getDay() + 6) % 7;
|
|
134399
|
+
const weekStart = new Date(day);
|
|
134400
|
+
weekStart.setDate(weekStart.getDate() - dow);
|
|
134401
|
+
const thursday = new Date(weekStart);
|
|
134402
|
+
thursday.setDate(thursday.getDate() + 3);
|
|
134403
|
+
const yearStart = new Date(thursday.getFullYear(), 0, 1);
|
|
134404
|
+
const week = Math.floor((thursday.getTime() - yearStart.getTime()) / 864e5 / 7) + 1;
|
|
134405
|
+
const label = `W${String(week).padStart(2, "0")}`;
|
|
134406
|
+
return { key: `${thursday.getFullYear()}-${label}`, label, weekStart };
|
|
134407
|
+
}
|
|
134408
|
+
function aggregateDailyToWeekly(dailyRows) {
|
|
134409
|
+
const map = /* @__PURE__ */ new Map();
|
|
134410
|
+
for (const row of dailyRows) {
|
|
134411
|
+
const d = parseYmd2(row.date);
|
|
134412
|
+
if (!d) continue;
|
|
134413
|
+
const { key, label, weekStart } = isoWeekKey(d);
|
|
134414
|
+
let acc = map.get(key);
|
|
134415
|
+
if (!acc) {
|
|
134416
|
+
acc = {
|
|
134417
|
+
label,
|
|
134418
|
+
weekStart,
|
|
134419
|
+
minDate: d,
|
|
134420
|
+
maxDate: d,
|
|
134421
|
+
spend: 0,
|
|
134422
|
+
impressions: 0,
|
|
134423
|
+
clicks: 0,
|
|
134424
|
+
conversions: 0
|
|
134425
|
+
};
|
|
134426
|
+
map.set(key, acc);
|
|
134427
|
+
}
|
|
134428
|
+
if (d < acc.minDate) acc.minDate = d;
|
|
134429
|
+
if (d > acc.maxDate) acc.maxDate = d;
|
|
134430
|
+
acc.spend += num10(row.spend) ?? 0;
|
|
134431
|
+
acc.impressions += num10(row.impressions) ?? 0;
|
|
134432
|
+
acc.clicks += num10(row.clicks) ?? 0;
|
|
134433
|
+
acc.conversions += num10(row.conversions) ?? 0;
|
|
134434
|
+
}
|
|
134435
|
+
return [...map.values()].sort((a, b) => a.weekStart.getTime() - b.weekStart.getTime()).map((acc) => {
|
|
134436
|
+
const days = Math.floor((acc.maxDate.getTime() - acc.minDate.getTime()) / 864e5) + 1;
|
|
134437
|
+
return {
|
|
134438
|
+
weekLabel: acc.label,
|
|
134439
|
+
startDate: formatYmd2(acc.minDate),
|
|
134440
|
+
endDate: formatYmd2(acc.maxDate),
|
|
134441
|
+
days,
|
|
134442
|
+
spend: round25(acc.spend),
|
|
134443
|
+
impressions: Math.round(acc.impressions),
|
|
134444
|
+
clicks: Math.round(acc.clicks),
|
|
134445
|
+
conversions: round25(acc.conversions),
|
|
134446
|
+
ctr: acc.impressions > 0 ? acc.clicks / acc.impressions : void 0,
|
|
134447
|
+
averageCpc: acc.clicks > 0 ? round25(acc.spend / acc.clicks) : void 0,
|
|
134448
|
+
costPerConversion: acc.conversions > 0 ? round25(acc.spend / acc.conversions) : void 0
|
|
134449
|
+
};
|
|
134450
|
+
});
|
|
134451
|
+
}
|
|
134452
|
+
function mapDailyRows(payload) {
|
|
134453
|
+
return asItems(payload).map((r) => {
|
|
134454
|
+
const dateRaw = r.date;
|
|
134455
|
+
const date = typeof dateRaw === "string" ? dateRaw.slice(0, 10) : dateRaw != null ? String(dateRaw).slice(0, 10) : "";
|
|
134456
|
+
return pickMetrics2({ ...r, date }, ["date"]);
|
|
134457
|
+
});
|
|
134458
|
+
}
|
|
134459
|
+
function mapSearchRows(payload) {
|
|
134460
|
+
return asItems(payload).map((r) => pickMetrics2(r, ["network"]));
|
|
134461
|
+
}
|
|
134462
|
+
function mapCampaignRows2(payload) {
|
|
134463
|
+
return asItems(payload).map(
|
|
134464
|
+
(r) => pickMetrics2(r, [
|
|
134465
|
+
"campaignId",
|
|
134466
|
+
"campaignName",
|
|
134467
|
+
"status",
|
|
134468
|
+
"state",
|
|
134469
|
+
"campaignType",
|
|
134470
|
+
"budget",
|
|
134471
|
+
"strategy",
|
|
134472
|
+
"targetCpa",
|
|
134473
|
+
"network"
|
|
134474
|
+
])
|
|
134475
|
+
);
|
|
134476
|
+
}
|
|
134477
|
+
function mapKeywordRows2(payload) {
|
|
134478
|
+
return asItems(payload).map(
|
|
134479
|
+
(r) => pickMetrics2(r, [
|
|
134480
|
+
"campaignId",
|
|
134481
|
+
"campaignName",
|
|
134482
|
+
"adGroupId",
|
|
134483
|
+
"keyword",
|
|
134484
|
+
"criteriaType",
|
|
134485
|
+
"network"
|
|
134486
|
+
])
|
|
134487
|
+
);
|
|
134488
|
+
}
|
|
134489
|
+
function mapSearchTermRows2(payload) {
|
|
134490
|
+
return asItems(payload).map(
|
|
134491
|
+
(r) => pickMetrics2(r, [
|
|
134492
|
+
"searchQuery",
|
|
134493
|
+
"matchedKeyword",
|
|
134494
|
+
"campaignId",
|
|
134495
|
+
"campaignName",
|
|
134496
|
+
"adGroupId"
|
|
134497
|
+
])
|
|
134498
|
+
);
|
|
134499
|
+
}
|
|
134500
|
+
function mapGeoRows(payload) {
|
|
134501
|
+
return asItems(payload).map(
|
|
134502
|
+
(r) => pickMetrics2(r, ["locationName", "targetingLocationName", "network"])
|
|
134503
|
+
);
|
|
134504
|
+
}
|
|
134505
|
+
function mapDeviceRows2(payload) {
|
|
134506
|
+
return asItems(payload).map((r) => pickMetrics2(r, ["device", "network"]));
|
|
134507
|
+
}
|
|
134508
|
+
async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
134509
|
+
const absDir = path44.resolve(snapshotDir);
|
|
134510
|
+
const metaAccountId = typeof asRecord26(payload.meta)?.accountId === "string" ? asRecord26(payload.meta)?.accountId : void 0;
|
|
134511
|
+
const manifest = await readManifest6(snapshotDir, metaAccountId);
|
|
134512
|
+
if (!manifest) {
|
|
134513
|
+
throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 report-manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C yandex-analysis --json-out`);
|
|
134514
|
+
}
|
|
134515
|
+
const sectionMap = /* @__PURE__ */ new Map();
|
|
134516
|
+
for (const art of manifest.artifacts ?? []) {
|
|
134517
|
+
const data = await loadSectionPayload3(absDir, art.file);
|
|
134518
|
+
if (data != null) sectionMap.set(art.section, data);
|
|
134519
|
+
}
|
|
134520
|
+
const overview = asRecord26(sectionMap.get("overview"));
|
|
134521
|
+
const totals = asRecord26(overview?.totals);
|
|
134522
|
+
const accountId = manifest.accountId ?? metaAccountId;
|
|
134523
|
+
const meta = { ...payload.meta ?? {} };
|
|
134524
|
+
if (!meta.accountId && accountId) meta.accountId = accountId;
|
|
134525
|
+
if (!meta.currency && typeof overview?.currency === "string") meta.currency = overview.currency;
|
|
134526
|
+
if (!meta.startDate && manifest.dateRange) meta.startDate = manifest.dateRange.start;
|
|
134527
|
+
if (!meta.endDate && manifest.dateRange) meta.endDate = manifest.dateRange.end;
|
|
134528
|
+
if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
134529
|
+
if (!meta.platform) meta.platform = "Yandex Direct";
|
|
134530
|
+
const kpis = { ...payload.kpis ?? {} };
|
|
134531
|
+
if (totals) {
|
|
134532
|
+
if (num10(totals.spend) != null) kpis.spend = round25(num10(totals.spend));
|
|
134533
|
+
if (num10(totals.impressions) != null) kpis.impressions = Math.round(num10(totals.impressions));
|
|
134534
|
+
if (num10(totals.clicks) != null) kpis.clicks = Math.round(num10(totals.clicks));
|
|
134535
|
+
if (num10(totals.conversions) != null) kpis.conversions = round25(num10(totals.conversions));
|
|
134536
|
+
if (num10(totals.ctr) != null) kpis.ctr = num10(totals.ctr);
|
|
134537
|
+
if (num10(totals.averageCpc) != null) kpis.averageCpc = round25(num10(totals.averageCpc));
|
|
134538
|
+
if (num10(totals.costPerConversion) != null) {
|
|
134539
|
+
kpis.costPerConversion = round25(num10(totals.costPerConversion));
|
|
134540
|
+
}
|
|
134541
|
+
if (num10(totals.conversionRate) != null) kpis.conversionRate = num10(totals.conversionRate);
|
|
134542
|
+
if (num10(totals.roi) != null) kpis.roi = num10(totals.roi);
|
|
134543
|
+
}
|
|
134544
|
+
if (overview) {
|
|
134545
|
+
if (num10(overview.balance) != null) kpis.balance = round25(num10(overview.balance));
|
|
134546
|
+
if (num10(overview.avgDailySpend) != null) kpis.avgDailySpend = round25(num10(overview.avgDailySpend));
|
|
134547
|
+
if (num10(overview.activeDays) != null) kpis.activeDays = num10(overview.activeDays);
|
|
134548
|
+
if (overview.balanceAsOf != null) kpis.balanceAsOf = overview.balanceAsOf;
|
|
134549
|
+
}
|
|
134550
|
+
if (kpis.balance != null && kpis.avgDailySpend != null && Number(kpis.avgDailySpend) > 0) {
|
|
134551
|
+
kpis.runwayDays = Math.round(Number(kpis.balance) / Number(kpis.avgDailySpend));
|
|
134552
|
+
}
|
|
134553
|
+
const tables = { ...payload.tables ?? {} };
|
|
134554
|
+
const dailyRows = mapDailyRows(sectionMap.get("daily"));
|
|
134555
|
+
if (dailyRows.length > 0) {
|
|
134556
|
+
tables.daily = dailyRows;
|
|
134557
|
+
tables.weekly = aggregateDailyToWeekly(dailyRows);
|
|
134558
|
+
}
|
|
134559
|
+
{
|
|
134560
|
+
const rows = mapSearchRows(sectionMap.get("search"));
|
|
134561
|
+
if (rows.length > 0) tables.search = rows;
|
|
134562
|
+
}
|
|
134563
|
+
{
|
|
134564
|
+
const rows = mapCampaignRows2(sectionMap.get("campaigns"));
|
|
134565
|
+
if (rows.length > 0) tables.campaigns = rows;
|
|
134566
|
+
}
|
|
134567
|
+
{
|
|
134568
|
+
const rows = mapKeywordRows2(sectionMap.get("keywords"));
|
|
134569
|
+
if (rows.length > 0) tables.keywords = rows;
|
|
134570
|
+
}
|
|
134571
|
+
{
|
|
134572
|
+
const rows = mapSearchTermRows2(sectionMap.get("search-terms"));
|
|
134573
|
+
if (rows.length > 0) tables.searchTerms = rows;
|
|
134574
|
+
}
|
|
134575
|
+
{
|
|
134576
|
+
const rows = mapGeoRows(sectionMap.get("geo"));
|
|
134577
|
+
if (rows.length > 0) tables.geo = rows;
|
|
134578
|
+
}
|
|
134579
|
+
{
|
|
134580
|
+
const rows = mapDeviceRows2(sectionMap.get("devices"));
|
|
134581
|
+
if (rows.length > 0) tables.devices = rows;
|
|
134582
|
+
}
|
|
134583
|
+
return { ...payload, meta, kpis, tables };
|
|
134584
|
+
}
|
|
134585
|
+
|
|
134586
|
+
// src/commands/yandex-analysis/report-content.ts
|
|
134587
|
+
var SECTION_KEYS4 = [
|
|
134588
|
+
"overview",
|
|
134589
|
+
"search",
|
|
134590
|
+
"campaigns",
|
|
134591
|
+
"keywords",
|
|
134592
|
+
"searchTerms",
|
|
134593
|
+
"geo",
|
|
134594
|
+
"devices",
|
|
134595
|
+
"daily"
|
|
134596
|
+
];
|
|
134597
|
+
var SECTION_LABELS5 = {
|
|
134598
|
+
overview: "\u2460\u8D26\u6237\u603B\u89C8",
|
|
134599
|
+
search: "\u2461Search \u7F51\u7EDC",
|
|
134600
|
+
campaigns: "\u2462Campaign",
|
|
134601
|
+
keywords: "\u2463\u5173\u952E\u8BCD",
|
|
134602
|
+
searchTerms: "\u2464\u641C\u7D22\u8BCD",
|
|
134603
|
+
geo: "\u2465\u5730\u57DF",
|
|
134604
|
+
devices: "\u2466\u8BBE\u5907",
|
|
134605
|
+
daily: "\u2467\u65E5\u671F/\u5468\u62A5"
|
|
134606
|
+
};
|
|
134607
|
+
function textLen7(value) {
|
|
134608
|
+
return typeof value === "string" ? value.trim().length : 0;
|
|
134609
|
+
}
|
|
134610
|
+
function asArray7(value) {
|
|
134611
|
+
return Array.isArray(value) ? value : [];
|
|
134612
|
+
}
|
|
134613
|
+
function asRecord27(value) {
|
|
134614
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
134615
|
+
return value;
|
|
134616
|
+
}
|
|
134617
|
+
return null;
|
|
134618
|
+
}
|
|
134619
|
+
function pushMissing6(missing, id, chapter, dimension, hint) {
|
|
134620
|
+
missing.push({ id, chapter, dimension, hint });
|
|
134621
|
+
}
|
|
134622
|
+
function validateYandexPeriodReportContent(data) {
|
|
134623
|
+
const missing = [];
|
|
134624
|
+
const meta = asRecord27(data.meta) ?? {};
|
|
134625
|
+
const narrative = asRecord27(data.narrative) ?? {};
|
|
134626
|
+
const sections = asRecord27(narrative.sections) ?? {};
|
|
134627
|
+
const kpis = asRecord27(data.kpis) ?? {};
|
|
134628
|
+
if (!textLen7(meta.accountId)) {
|
|
134629
|
+
pushMissing6(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
|
|
134630
|
+
}
|
|
134631
|
+
const executiveSummary = asArray7(narrative.executiveSummary).filter((p) => textLen7(p) > 0);
|
|
134632
|
+
if (executiveSummary.length < 1) {
|
|
134633
|
+
pushMissing6(
|
|
134634
|
+
missing,
|
|
134635
|
+
"narrative-executive-summary",
|
|
134636
|
+
"\u6267\u884C\u6458\u8981",
|
|
134637
|
+
"narrative.executiveSummary",
|
|
134638
|
+
"\u22651 \u6BB5\u6267\u884C\u6458\u8981\uFF0C\u6982\u8FF0\u672C\u671F\u6D88\u8017/\u70B9\u51FB/\u8F6C\u5316/CTR/CPC \u8D70\u52BF"
|
|
134639
|
+
);
|
|
134640
|
+
}
|
|
134641
|
+
const sectionAnalysisCounts = {};
|
|
134642
|
+
const sectionSuggestionCounts = {};
|
|
134643
|
+
for (const key of SECTION_KEYS4) {
|
|
134644
|
+
const section = asRecord27(sections[key]);
|
|
134645
|
+
const analysis = asArray7(section?.analysis).filter((s) => textLen7(s) > 0);
|
|
134646
|
+
const suggestions = asArray7(section?.suggestions).filter((s) => textLen7(s) > 0);
|
|
134647
|
+
sectionAnalysisCounts[key] = analysis.length;
|
|
134648
|
+
sectionSuggestionCounts[key] = suggestions.length;
|
|
134649
|
+
if (analysis.length < 1) {
|
|
134650
|
+
pushMissing6(
|
|
134651
|
+
missing,
|
|
134652
|
+
`section-${key}-analysis`,
|
|
134653
|
+
SECTION_LABELS5[key],
|
|
134654
|
+
`narrative.sections.${key}.analysis`,
|
|
134655
|
+
`\u22651 \u6761\u603B\u7ED3\uFF08\u5F53\u524D ${analysis.length} \u6761\uFF09\uFF0C\u987B\u5199\u6E05\u73B0\u8C61+\u4F9D\u636E\u6570\u5B57`
|
|
134656
|
+
);
|
|
134657
|
+
}
|
|
134658
|
+
if (suggestions.length < 1) {
|
|
134659
|
+
pushMissing6(
|
|
134660
|
+
missing,
|
|
134661
|
+
`section-${key}-suggestions`,
|
|
134662
|
+
SECTION_LABELS5[key],
|
|
134663
|
+
`narrative.sections.${key}.suggestions`,
|
|
134664
|
+
`\u22651 \u6761\u53EF\u6267\u884C\u5EFA\u8BAE\uFF08\u5F53\u524D ${suggestions.length} \u6761\uFF09`
|
|
134665
|
+
);
|
|
134666
|
+
}
|
|
134667
|
+
}
|
|
134668
|
+
const recommendations = asArray7(narrative.recommendations).filter((r) => {
|
|
134669
|
+
if (typeof r === "string") return textLen7(r) > 0;
|
|
134670
|
+
const rec = asRecord27(r);
|
|
134671
|
+
return textLen7(rec?.content) > 0 || textLen7(rec?.text) > 0;
|
|
134672
|
+
});
|
|
134673
|
+
if (recommendations.length < 3) {
|
|
134674
|
+
pushMissing6(
|
|
134675
|
+
missing,
|
|
134676
|
+
"narrative-recommendations",
|
|
134677
|
+
"\u540E\u7EED\u4F18\u5316\u52A8\u4F5C",
|
|
134678
|
+
"narrative.recommendations",
|
|
134679
|
+
`\u22653 \u6761\u8DE8\u7EF4\u5EA6\u4F18\u5316\u5EFA\u8BAE\uFF08\u5F53\u524D ${recommendations.length} \u6761\uFF1B\u53EF\u7528 {priority,text,anchor}\uFF09`
|
|
134680
|
+
);
|
|
134681
|
+
}
|
|
134682
|
+
const hasKpi = Number.isFinite(Number(kpis.spend)) || Number.isFinite(Number(kpis.clicks)) || Number.isFinite(Number(kpis.impressions));
|
|
134683
|
+
if (!hasKpi) {
|
|
134684
|
+
pushMissing6(
|
|
134685
|
+
missing,
|
|
134686
|
+
"kpis-core",
|
|
134687
|
+
"KPI",
|
|
134688
|
+
"kpis.spend|clicks|impressions",
|
|
134689
|
+
"\u81F3\u5C11\u4E00\u9879\u6709\u6548\u6570\u5B57\uFF08\u8BF7\u4F20 --snapshot-dir \u7531 CLI \u4ECE overview \u5408\u5E76\uFF09"
|
|
134690
|
+
);
|
|
134691
|
+
}
|
|
134692
|
+
return {
|
|
134693
|
+
ok: missing.length === 0,
|
|
134694
|
+
missing,
|
|
134695
|
+
stats: {
|
|
134696
|
+
executiveSummaryCount: executiveSummary.length,
|
|
134697
|
+
recommendationCount: recommendations.length,
|
|
134698
|
+
sectionAnalysisCounts,
|
|
134699
|
+
sectionSuggestionCounts
|
|
134700
|
+
}
|
|
134701
|
+
};
|
|
134702
|
+
}
|
|
134703
|
+
function formatYandexPeriodReportErrors(result) {
|
|
134704
|
+
if (result.ok) return "";
|
|
134705
|
+
const lines = result.missing.map((m) => ` - [${m.chapter}] ${m.dimension}\uFF1A${m.hint}`);
|
|
134706
|
+
return [
|
|
134707
|
+
`\u62A5\u544A JSON \u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 yandex-period-report.md\uFF09\uFF1A`,
|
|
134708
|
+
...lines,
|
|
134709
|
+
"",
|
|
134710
|
+
"\u8BF7\u8865\u5168 narrative.sections.<dimension>.{analysis,suggestions} \u4E0E narrative.recommendations \u540E\u91CD\u65B0\u6267\u884C render\u3002",
|
|
134711
|
+
"\u6570\u503C\u7C7B\u5B57\u6BB5\uFF08kpis/tables\uFF09\u987B\u7531 --snapshot-dir \u4ECE\u843D\u76D8 JSON \u5408\u5E76\uFF1BAgent \u53EA\u64B0\u5199\u53D9\u4E8B\uFF0C\u7981\u6B62\u624B\u5199 HTML\u3002"
|
|
134712
|
+
].join("\n");
|
|
134713
|
+
}
|
|
134714
|
+
|
|
134715
|
+
// src/commands/yandex-analysis/render-report.ts
|
|
134716
|
+
var TEMPLATE_BASENAME5 = "yandex-period-report.html";
|
|
134717
|
+
var INJECT_MARKER6 = "window.__YANDEX_PERIOD_REPORT__ = window.__YANDEX_PERIOD_REPORT__ || null;";
|
|
134718
|
+
function resolveSkillTemplatePath8(basename11) {
|
|
134719
|
+
const dir = path45.dirname(fileURLToPath11(import.meta.url));
|
|
134720
|
+
const candidates = [
|
|
134721
|
+
path45.join(dir, "skill", "report-templates", basename11),
|
|
134722
|
+
path45.join(dir, "skill", "siluzan-ads", "report-templates", basename11),
|
|
134723
|
+
path45.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename11)
|
|
134724
|
+
];
|
|
134725
|
+
for (const p of candidates) {
|
|
134726
|
+
if (fs34.existsSync(p)) return p;
|
|
134727
|
+
}
|
|
134728
|
+
return candidates[0];
|
|
134729
|
+
}
|
|
134730
|
+
function yandexPeriodReportTemplatePath() {
|
|
134731
|
+
return resolveSkillTemplatePath8(TEMPLATE_BASENAME5);
|
|
134732
|
+
}
|
|
134733
|
+
async function readJsonFile7(filePath) {
|
|
134734
|
+
const text = await fsPromises7.readFile(filePath, "utf8");
|
|
134735
|
+
try {
|
|
134736
|
+
return JSON.parse(text);
|
|
134737
|
+
} catch {
|
|
134738
|
+
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
134739
|
+
}
|
|
134740
|
+
}
|
|
134741
|
+
function asRecord28(value) {
|
|
134742
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
134743
|
+
return value;
|
|
134744
|
+
}
|
|
134745
|
+
throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
|
|
134746
|
+
}
|
|
134747
|
+
function injectReportData7(html, payload) {
|
|
134748
|
+
const json = JSON.stringify(payload).replace(/</g, "\\u003c");
|
|
134749
|
+
if (!html.includes(INJECT_MARKER6)) {
|
|
134750
|
+
throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 yandex-period-report.html");
|
|
134751
|
+
}
|
|
134752
|
+
return html.replace(INJECT_MARKER6, `window.__YANDEX_PERIOD_REPORT__ = ${json};`);
|
|
134753
|
+
}
|
|
134754
|
+
async function runYandexAnalysisRender(opts) {
|
|
134755
|
+
const dataPath = path45.resolve(opts.dataFile);
|
|
134756
|
+
let data = asRecord28(await readJsonFile7(dataPath));
|
|
134757
|
+
if (opts.snapshotDir?.trim()) {
|
|
134758
|
+
data = await mergeYandexAnalysisSnapshotIntoReport(data, path45.resolve(opts.snapshotDir));
|
|
134759
|
+
}
|
|
134760
|
+
const contentCheck = validateYandexPeriodReportContent(data);
|
|
134761
|
+
if (!contentCheck.ok) {
|
|
134762
|
+
console.error(`
|
|
134763
|
+
\u274C ${formatYandexPeriodReportErrors(contentCheck)}
|
|
134764
|
+
`);
|
|
134765
|
+
const { stats: stats2 } = contentCheck;
|
|
134766
|
+
const sectionSummary = Object.entries(stats2.sectionAnalysisCounts).map(([key, count]) => `${key} \u603B\u7ED3${count}/\u5EFA\u8BAE${stats2.sectionSuggestionCounts[key] ?? 0}`).join("\uFF5C");
|
|
134767
|
+
console.error(
|
|
134768
|
+
` \u7EDF\u8BA1\uFF1A\u6267\u884C\u6458\u8981 ${stats2.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats2.recommendationCount} \u6761\uFF5C${sectionSummary}
|
|
134769
|
+
`
|
|
134770
|
+
);
|
|
134771
|
+
process.exit(1);
|
|
134772
|
+
}
|
|
134773
|
+
const templatePath = yandexPeriodReportTemplatePath();
|
|
134774
|
+
let html;
|
|
134775
|
+
try {
|
|
134776
|
+
html = await fsPromises7.readFile(templatePath, "utf8");
|
|
134777
|
+
} catch {
|
|
134778
|
+
console.error(`
|
|
134779
|
+
\u274C \u672A\u627E\u5230\u62A5\u544A\u6A21\u677F\uFF1A${templatePath}
|
|
134780
|
+
\u8BF7\u5148\u6267\u884C npm run build
|
|
134781
|
+
`);
|
|
134782
|
+
process.exit(1);
|
|
134783
|
+
}
|
|
134784
|
+
const outPath = path45.resolve(
|
|
134785
|
+
opts.out ?? path45.join(path45.dirname(dataPath), "yandex-period-report.html")
|
|
134786
|
+
);
|
|
134787
|
+
const outDir = path45.dirname(outPath);
|
|
134788
|
+
await fsPromises7.mkdir(outDir, { recursive: true });
|
|
134789
|
+
await fsPromises7.writeFile(outPath, injectReportData7(html, data), "utf8");
|
|
134790
|
+
const { stats } = contentCheck;
|
|
134791
|
+
console.log(`
|
|
134792
|
+
\u2705 Yandex \u5E7F\u544A\u8D26\u6237\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
|
|
134793
|
+
`);
|
|
134794
|
+
console.log(
|
|
134795
|
+
` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u6267\u884C\u6458\u8981 ${stats.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats.recommendationCount} \u6761\uFF5C8 \u4E2A\u5206\u6790\u7AE0\u8282\u5747\u5DF2\u9F50\u5168
|
|
134796
|
+
`
|
|
134797
|
+
);
|
|
134798
|
+
console.log("\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u5B8C\u6574\u7AE0\u8282\uFF08\u6837\u5F0F\u5BF9\u9F50\u5F00\u53D1\u6837\u4F8B\uFF09\u3002\n");
|
|
134799
|
+
}
|
|
134800
|
+
|
|
134801
|
+
// src/commands/yandex-analysis/register-cli.ts
|
|
134802
|
+
function registerBatchCommand4(sectionHelp, aliasHelp) {
|
|
134803
|
+
return new Command5("run").description(
|
|
134804
|
+
"\u6279\u91CF\u62C9\u53D6 YandexAPI /yandex-analysis \u5404\u7EF4\u5EA6\uFF08\u7701\u7565\u5B50\u547D\u4EE4\u540D\u65F6\u9ED8\u8BA4\u6267\u884C\u672C\u547D\u4EE4\uFF09"
|
|
134805
|
+
).requiredOption(
|
|
134806
|
+
"-a, --account <id>",
|
|
134807
|
+
"Yandex Client-Login / mediaCustomerId\uFF08\u5982 porg-xxx\uFF0C\u4E0E list-accounts -m Yandex \u4E00\u81F4\uFF09"
|
|
134808
|
+
).requiredOption(
|
|
134809
|
+
"--json-out <dir>",
|
|
134810
|
+
"\u7ED3\u679C\u843D\u76D8\u76EE\u5F55\uFF1B\u6BCF\u7EF4\u5EA6 `<section>-<accountId>.json` + `report-manifest-<accountId>.json`"
|
|
134811
|
+
).option("-t, --token <token>", "Auth Token").option(
|
|
134812
|
+
"--start <date>",
|
|
134813
|
+
"\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u4E0D\u80FD\u665A\u4E8E\u4ECA\u5929\uFF1B\u9ED8\u8BA4\u8FD1 7 \u5929\u542B\u4ECA\u5929\uFF09"
|
|
134814
|
+
).option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option(
|
|
134815
|
+
"--sections <list>",
|
|
134816
|
+
`\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,campaigns,keywords\uFF1B\u7701\u7565=\u5168\u90E8 8 \u4E2A\u3002\u53EF\u9009\uFF1A${sectionHelp}\uFF1B\u522B\u540D\uFF1A${aliasHelp}`
|
|
134817
|
+
).option("--exclude <list>", "\u6392\u9664\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u4E0E --sections \u53EF\u53E0\u52A0").option(
|
|
134818
|
+
"--limit <n>",
|
|
134819
|
+
"\u4EC5 campaigns / keywords / search-terms / geo\uFF1A\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF0C\u6700\u5927 10000\uFF09",
|
|
134820
|
+
(v) => parseInt(v, 10)
|
|
134821
|
+
).option("--concurrency <n>", "\u5E76\u53D1\u6570\uFF0C\u9ED8\u8BA4 5\uFF0C\u4E0A\u9650 16", (v) => parseInt(v, 10)).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
134822
|
+
await runAllYandexSections(opts);
|
|
134823
|
+
});
|
|
134824
|
+
}
|
|
134825
|
+
function registerYandexAnalysisCommands(program2) {
|
|
134826
|
+
const sectionHelp = YANDEX_SECTION_NAMES.join(", ");
|
|
134827
|
+
const aliasHelp = Object.keys(YANDEX_SECTION_ALIASES).join(", ");
|
|
134828
|
+
const root = program2.command("yandex-analysis").description(
|
|
134829
|
+
"Yandex \u8D26\u6237\u5206\u6790\uFF1A\u6279\u91CF\u62C9\u53D6 /yandex-analysis \u53EA\u8BFB\u6570\u636E\uFF08\u9ED8\u8BA4 run\uFF09\uFF0C\u6216\u7531 render \u751F\u6210 yandex-period-report.html \u7EC8\u7A3F\uFF08\u91D1\u989D\u5DF2\u4E3A\u8D26\u6237\u5E01\u79CD\u5C0F\u6570\uFF1Bsearch-terms \u4EC5\u8FD1 180 \u5929\uFF09"
|
|
134830
|
+
);
|
|
134831
|
+
root.addCommand(registerBatchCommand4(sectionHelp, aliasHelp), { isDefault: true });
|
|
134832
|
+
root.command("render").description(
|
|
134833
|
+
"\u6839\u636E Agent \u64B0\u5199\u7684\u62A5\u544A JSON \u751F\u6210 Yandex \u5E7F\u544A\u8D26\u6237\u5206\u6790 HTML \u7EC8\u7A3F\uFF08\u6821\u9A8C 8 \u4E2A\u5206\u6790\u7AE0\u8282\u5FC5\u542B\u5B57\u6BB5\uFF0C\u7F3A\u9879\u62D2\u7EDD\u6E32\u67D3\uFF09"
|
|
134834
|
+
).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 yandex-period-report.json").option(
|
|
134835
|
+
"--snapshot-dir <dir>",
|
|
134836
|
+
"\u53EF\u9009\uFF1Ayandex-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u8865\u5168 meta / kpis / tables"
|
|
134837
|
+
).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
|
|
134838
|
+
await runYandexAnalysisRender({
|
|
134839
|
+
dataFile: opts.data,
|
|
134840
|
+
snapshotDir: opts.snapshotDir,
|
|
134841
|
+
out: opts.out
|
|
134842
|
+
});
|
|
134843
|
+
});
|
|
134844
|
+
}
|
|
134845
|
+
|
|
133964
134846
|
// src/index.ts
|
|
133965
134847
|
init_version();
|
|
133966
134848
|
init_cli_json_snapshot();
|
|
@@ -134035,9 +134917,9 @@ function interceptAgentCompatArgv(argv) {
|
|
|
134035
134917
|
installProcessHandlers();
|
|
134036
134918
|
function getVersion() {
|
|
134037
134919
|
try {
|
|
134038
|
-
const __dirname3 =
|
|
134039
|
-
const pkgPath =
|
|
134040
|
-
const pkg = JSON.parse(
|
|
134920
|
+
const __dirname3 = path46.dirname(fileURLToPath12(import.meta.url));
|
|
134921
|
+
const pkgPath = path46.join(__dirname3, "..", "package.json");
|
|
134922
|
+
const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf8"));
|
|
134041
134923
|
return pkg.version ?? "0.0.0";
|
|
134042
134924
|
} catch {
|
|
134043
134925
|
return "0.0.0";
|
|
@@ -134056,9 +134938,9 @@ configureCliJsonSnapshot({
|
|
|
134056
134938
|
]
|
|
134057
134939
|
});
|
|
134058
134940
|
configureSiluzanClientIdentity({ client: "tso-cli", resolveVersion: getVersion });
|
|
134059
|
-
var program = new
|
|
134941
|
+
var program = new Command6();
|
|
134060
134942
|
program.name("siluzan-tso").description(
|
|
134061
|
-
"Siluzan \u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1A\u8D26\u6237\u67E5\u8BE2\u3001\u4F59\u989D\u3001\u6295\u653E\u6570\u636E\u3001\u5F00\u6237\u7533\u8BF7\uFF08Google/TikTok/Yandex/Bing/Kwai\uFF09\u3001\n\u8D26\u53F7\u5206\u4EAB/\u89E3\u7ED1\u3001\u4F18\u5316\u62A5\u544A\u3001Google \u8D26\u6237\u5206\u6790\
|
|
134943
|
+
"Siluzan \u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1A\u8D26\u6237\u67E5\u8BE2\u3001\u4F59\u989D\u3001\u6295\u653E\u6570\u636E\u3001\u5F00\u6237\u7533\u8BF7\uFF08Google/TikTok/Yandex/Bing/Kwai\uFF09\u3001\n\u8D26\u53F7\u5206\u4EAB/\u89E3\u7ED1\u3001\u4F18\u5316\u62A5\u544A\u3001Google/Meta/TikTok/Bing/Yandex \u8D26\u6237\u5206\u6790\u3001Google \u5E7F\u544A\u8BCA\u65AD\u62A5\u544A\uFF08google-ads-diagnosis render\uFF09\u3001\u7F51\u7AD9\u8BCA\u65AD\uFF08website-diagnosis\uFF09\u3001\u6218\u7565\u5E02\u573A\u5206\u6790\uFF08market-analysis\uFF0CAgent \u751F\u6210\u62A5\u544A\uFF09\u3001\u5145\u503C\u8F6C\u8D26\u3001\u5F00\u7968\u3001\u667A\u80FD\u9884\u8B66\u3001Google \u5E7F\u544A\u7BA1\u7406\uFF08\u542B\u5F02\u6B65\u6279\u91CF\uFF09\u3002"
|
|
134062
134944
|
).version(getVersion());
|
|
134063
134945
|
program.option("--commit <text>", "\u5199\u64CD\u4F5C\u8BF4\u660E\uFF1B\u8BB0\u5165\u672C\u673A\u5199\u5BA1\u8BA1 JSONL");
|
|
134064
134946
|
program.hook("preAction", async () => {
|
|
@@ -134093,6 +134975,7 @@ var REGISTRARS = [
|
|
|
134093
134975
|
registerFacebookAnalysisCommands,
|
|
134094
134976
|
registerBingAnalysisCommands,
|
|
134095
134977
|
registerTikTokAnalysisCommands,
|
|
134978
|
+
registerYandexAnalysisCommands,
|
|
134096
134979
|
register15,
|
|
134097
134980
|
register16,
|
|
134098
134981
|
register17,
|