siluzan-tso-cli 1.1.45-beta.3 → 1.1.45-beta.5
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 +1 -1
- package/dist/index.js +274 -153
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/analytics/account-analytics.md +1 -1
- package/dist/skill/references/report-templates/yandex-period-report-excel.md +4 -3
- package/dist/skill/references/report-templates/yandex-period-report.md +40 -7
- package/dist/skill/report-templates/yandex-period-report-excel.md +4 -3
- package/dist/skill/report-templates/yandex-period-report.html +330 -83
- package/dist/skill/report-templates/yandex-period-report.md +40 -7
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
|
|
|
51
51
|
siluzan-tso init --force # 强制覆盖已存在文件
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
> **注意**:当前为测试版(1.1.45-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.45-beta.5),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -134013,7 +134013,7 @@ var YANDEX_SECTIONS = [
|
|
|
134013
134013
|
},
|
|
134014
134014
|
{
|
|
134015
134015
|
name: "daily",
|
|
134016
|
-
description: "\u6309\u65E5\u7EE9\u6548\
|
|
134016
|
+
description: "\u6309\u65E5\u7EE9\u6548\uFF1B\u843D\u76D8\u542B weekly[]\uFF08ISO \u5468\uFF09\uFF0C\u7981\u6B62\u81EA\u884C\u805A\u5468",
|
|
134017
134017
|
path: "daily"
|
|
134018
134018
|
},
|
|
134019
134019
|
{
|
|
@@ -134061,7 +134061,7 @@ var YANDEX_SECTION_ALIASES = {
|
|
|
134061
134061
|
|
|
134062
134062
|
// src/commands/yandex-analysis/run-batch.ts
|
|
134063
134063
|
init_auth();
|
|
134064
|
-
import * as
|
|
134064
|
+
import * as path44 from "path";
|
|
134065
134065
|
import { performance as performance8 } from "perf_hooks";
|
|
134066
134066
|
init_version();
|
|
134067
134067
|
|
|
@@ -134193,133 +134193,9 @@ function endpointHintForYandexSection(def) {
|
|
|
134193
134193
|
return `GET \u2026/yandex-analysis/${def.path}?account=&start=&end=`;
|
|
134194
134194
|
}
|
|
134195
134195
|
|
|
134196
|
-
// src/commands/yandex-analysis/resolve-sections.ts
|
|
134197
|
-
function normalizeSectionToken4(raw) {
|
|
134198
|
-
const t = raw.trim();
|
|
134199
|
-
if (!t) return null;
|
|
134200
|
-
if (YANDEX_SECTION_NAMES.includes(t)) {
|
|
134201
|
-
return t;
|
|
134202
|
-
}
|
|
134203
|
-
return YANDEX_SECTION_ALIASES[t] ?? null;
|
|
134204
|
-
}
|
|
134205
|
-
function resolveSectionList5(sections, exclude) {
|
|
134206
|
-
const splitClean = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
134207
|
-
const include = splitClean(sections);
|
|
134208
|
-
const dropRaw = splitClean(exclude);
|
|
134209
|
-
const unknown = [...include, ...dropRaw].filter((n) => normalizeSectionToken4(n) === null);
|
|
134210
|
-
if (unknown.length > 0) {
|
|
134211
|
-
console.error(
|
|
134212
|
-
`
|
|
134213
|
-
\u274C \u672A\u77E5\u7684 section \u540D\u79F0\uFF1A${unknown.join(", ")}
|
|
134214
|
-
\u53EF\u9009\uFF1A${YANDEX_SECTION_NAMES.join(", ")}
|
|
134215
|
-
\u522B\u540D\uFF1A${Object.keys(YANDEX_SECTION_ALIASES).join(", ")}
|
|
134216
|
-
`
|
|
134217
|
-
);
|
|
134218
|
-
process.exit(1);
|
|
134219
|
-
}
|
|
134220
|
-
const includeCanonical = new Set(include.map((n) => normalizeSectionToken4(n)).filter(Boolean));
|
|
134221
|
-
const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken4(n)).filter(Boolean));
|
|
134222
|
-
const base = include.length > 0 ? YANDEX_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...YANDEX_SECTIONS];
|
|
134223
|
-
return base.filter((s) => !dropCanonical.has(s.name));
|
|
134224
|
-
}
|
|
134225
|
-
|
|
134226
|
-
// src/commands/yandex-analysis/run-batch.ts
|
|
134227
|
-
async function runWithConcurrency6(tasks, concurrency) {
|
|
134228
|
-
const results = new Array(tasks.length);
|
|
134229
|
-
let cursor = 0;
|
|
134230
|
-
const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
|
|
134231
|
-
while (true) {
|
|
134232
|
-
const i = cursor++;
|
|
134233
|
-
if (i >= tasks.length) return;
|
|
134234
|
-
results[i] = await tasks[i]();
|
|
134235
|
-
}
|
|
134236
|
-
});
|
|
134237
|
-
await Promise.all(workers);
|
|
134238
|
-
return results;
|
|
134239
|
-
}
|
|
134240
|
-
async function runAllYandexSections(opts) {
|
|
134241
|
-
const id = assertYandexAccountId(opts.account);
|
|
134242
|
-
const config = loadConfig(opts.token);
|
|
134243
|
-
if (!opts.jsonOut?.trim()) {
|
|
134244
|
-
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");
|
|
134245
|
-
process.exit(1);
|
|
134246
|
-
}
|
|
134247
|
-
const targets = resolveSectionList5(opts.sections, opts.exclude);
|
|
134248
|
-
if (targets.length === 0) {
|
|
134249
|
-
console.error("\n\u274C \u6CA1\u6709\u8981\u6267\u884C\u7684 section\uFF08\u8BF7\u68C0\u67E5 --sections / --exclude\uFF09\u3002\n");
|
|
134250
|
-
process.exit(1);
|
|
134251
|
-
}
|
|
134252
|
-
const { startDate, endDate } = resolveYandexDateRange(opts.start, opts.end);
|
|
134253
|
-
const concurrency = Math.max(
|
|
134254
|
-
1,
|
|
134255
|
-
Math.min(typeof opts.concurrency === "number" ? opts.concurrency : 5, 16)
|
|
134256
|
-
);
|
|
134257
|
-
const cliVersion = getCurrentVersion2();
|
|
134258
|
-
const overallT0 = performance8.now();
|
|
134259
|
-
const tasks = targets.map((def) => async () => {
|
|
134260
|
-
const t0 = performance8.now();
|
|
134261
|
-
const hint = endpointHintForYandexSection(def);
|
|
134262
|
-
try {
|
|
134263
|
-
const payload = await fetchYandexSectionPayload(
|
|
134264
|
-
def,
|
|
134265
|
-
{ ...opts, start: startDate, end: endDate },
|
|
134266
|
-
config,
|
|
134267
|
-
id
|
|
134268
|
-
);
|
|
134269
|
-
const summary2 = await writeReportAnalysisSnapshot({
|
|
134270
|
-
snapshotDir: opts.jsonOut,
|
|
134271
|
-
section: def.name,
|
|
134272
|
-
accountId: id,
|
|
134273
|
-
dateRange: { start: startDate, end: endDate },
|
|
134274
|
-
payload,
|
|
134275
|
-
cliVersion
|
|
134276
|
-
});
|
|
134277
|
-
return {
|
|
134278
|
-
section: def.name,
|
|
134279
|
-
ok: true,
|
|
134280
|
-
elapsedMs: performance8.now() - t0,
|
|
134281
|
-
file: summary2.writtenFiles[0],
|
|
134282
|
-
endpointHint: hint
|
|
134283
|
-
};
|
|
134284
|
-
} catch (err) {
|
|
134285
|
-
return {
|
|
134286
|
-
section: def.name,
|
|
134287
|
-
ok: false,
|
|
134288
|
-
elapsedMs: performance8.now() - t0,
|
|
134289
|
-
error: err instanceof Error ? err.message : String(err),
|
|
134290
|
-
endpointHint: hint
|
|
134291
|
-
};
|
|
134292
|
-
}
|
|
134293
|
-
});
|
|
134294
|
-
const results = await runWithConcurrency6(tasks, concurrency);
|
|
134295
|
-
const succeeded = results.filter((r) => r.ok).length;
|
|
134296
|
-
const failed = results.length - succeeded;
|
|
134297
|
-
const absoluteSnapshotDir = path43.resolve(opts.jsonOut);
|
|
134298
|
-
const summary = {
|
|
134299
|
-
kind: "siluzan-tso-yandex-analysis-snapshot-batch",
|
|
134300
|
-
absoluteSnapshotDir,
|
|
134301
|
-
manifestFile: reportManifestFile(id),
|
|
134302
|
-
accountId: id,
|
|
134303
|
-
totalSections: results.length,
|
|
134304
|
-
succeeded,
|
|
134305
|
-
failed,
|
|
134306
|
-
concurrency,
|
|
134307
|
-
elapsedMs: performance8.now() - overallT0,
|
|
134308
|
-
results
|
|
134309
|
-
};
|
|
134310
|
-
console.log(JSON.stringify(summary));
|
|
134311
|
-
if (failed > 0) process.exit(2);
|
|
134312
|
-
}
|
|
134313
|
-
|
|
134314
|
-
// src/commands/yandex-analysis/render-report.ts
|
|
134315
|
-
import fs34 from "fs";
|
|
134316
|
-
import fsPromises7 from "fs/promises";
|
|
134317
|
-
import path45 from "path";
|
|
134318
|
-
import { fileURLToPath as fileURLToPath11 } from "url";
|
|
134319
|
-
|
|
134320
134196
|
// src/commands/yandex-analysis/merge-snapshot.ts
|
|
134321
134197
|
import * as fs33 from "fs/promises";
|
|
134322
|
-
import * as
|
|
134198
|
+
import * as path43 from "path";
|
|
134323
134199
|
function asRecord26(value) {
|
|
134324
134200
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
134325
134201
|
return value;
|
|
@@ -134342,15 +134218,19 @@ function asItems(payload) {
|
|
|
134342
134218
|
return items2.filter((r) => asRecord26(r) !== null);
|
|
134343
134219
|
}
|
|
134344
134220
|
function pickMetrics2(row, extras = []) {
|
|
134221
|
+
const spend = num10(row.spend) ?? 0;
|
|
134222
|
+
const impressions = num10(row.impressions) ?? 0;
|
|
134223
|
+
const clicks = num10(row.clicks) ?? 0;
|
|
134224
|
+
const conversions = num10(row.conversions) ?? 0;
|
|
134345
134225
|
const out = {
|
|
134346
|
-
spend
|
|
134347
|
-
impressions
|
|
134348
|
-
clicks
|
|
134349
|
-
|
|
134350
|
-
|
|
134351
|
-
|
|
134352
|
-
conversionRate: num10(row.conversionRate),
|
|
134353
|
-
costPerConversion: num10(row.costPerConversion),
|
|
134226
|
+
spend,
|
|
134227
|
+
impressions,
|
|
134228
|
+
clicks,
|
|
134229
|
+
conversions,
|
|
134230
|
+
ctr: num10(row.ctr) ?? (impressions > 0 ? clicks / impressions : void 0),
|
|
134231
|
+
averageCpc: num10(row.averageCpc) ?? (clicks > 0 ? round25(spend / clicks) : void 0),
|
|
134232
|
+
conversionRate: num10(row.conversionRate) ?? (clicks > 0 ? conversions / clicks : void 0),
|
|
134233
|
+
costPerConversion: num10(row.costPerConversion) ?? (conversions > 0 ? round25(spend / conversions) : void 0),
|
|
134354
134234
|
roi: num10(row.roi)
|
|
134355
134235
|
};
|
|
134356
134236
|
for (const f of extras) {
|
|
@@ -134360,7 +134240,7 @@ function pickMetrics2(row, extras = []) {
|
|
|
134360
134240
|
}
|
|
134361
134241
|
async function loadSectionPayload3(snapshotDir, file) {
|
|
134362
134242
|
try {
|
|
134363
|
-
const raw = await fs33.readFile(
|
|
134243
|
+
const raw = await fs33.readFile(path43.join(snapshotDir, file), "utf8");
|
|
134364
134244
|
return JSON.parse(raw);
|
|
134365
134245
|
} catch {
|
|
134366
134246
|
return null;
|
|
@@ -134380,7 +134260,7 @@ async function readManifest6(snapshotDir, accountId) {
|
|
|
134380
134260
|
}
|
|
134381
134261
|
for (const name2 of candidates) {
|
|
134382
134262
|
try {
|
|
134383
|
-
const raw = await fs33.readFile(
|
|
134263
|
+
const raw = await fs33.readFile(path43.join(snapshotDir, name2), "utf8");
|
|
134384
134264
|
const parsed = JSON.parse(raw);
|
|
134385
134265
|
if (Array.isArray(parsed.artifacts) && parsed.artifacts.length > 0) return parsed;
|
|
134386
134266
|
} catch {
|
|
@@ -134388,8 +134268,12 @@ async function readManifest6(snapshotDir, accountId) {
|
|
|
134388
134268
|
}
|
|
134389
134269
|
return null;
|
|
134390
134270
|
}
|
|
134271
|
+
function normalizeYandexDailyDate(value) {
|
|
134272
|
+
const m = /(\d{4}-\d{2}-\d{2})/.exec(String(value ?? "").trim());
|
|
134273
|
+
return m?.[1] ?? "";
|
|
134274
|
+
}
|
|
134391
134275
|
function parseYmd2(value) {
|
|
134392
|
-
const s =
|
|
134276
|
+
const s = normalizeYandexDailyDate(value);
|
|
134393
134277
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
134394
134278
|
if (!m) return null;
|
|
134395
134279
|
const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
@@ -134406,26 +134290,28 @@ function isoWeekKey(d) {
|
|
|
134406
134290
|
const dow = (day.getDay() + 6) % 7;
|
|
134407
134291
|
const weekStart = new Date(day);
|
|
134408
134292
|
weekStart.setDate(weekStart.getDate() - dow);
|
|
134293
|
+
const weekEnd = new Date(weekStart);
|
|
134294
|
+
weekEnd.setDate(weekEnd.getDate() + 6);
|
|
134409
134295
|
const thursday = new Date(weekStart);
|
|
134410
134296
|
thursday.setDate(thursday.getDate() + 3);
|
|
134411
134297
|
const yearStart = new Date(thursday.getFullYear(), 0, 1);
|
|
134412
134298
|
const week = Math.floor((thursday.getTime() - yearStart.getTime()) / 864e5 / 7) + 1;
|
|
134413
134299
|
const label = `W${String(week).padStart(2, "0")}`;
|
|
134414
|
-
return { key: `${thursday.getFullYear()}-${label}`, label, weekStart };
|
|
134300
|
+
return { key: `${thursday.getFullYear()}-${label}`, label, weekStart, weekEnd };
|
|
134415
134301
|
}
|
|
134416
134302
|
function aggregateDailyToWeekly(dailyRows) {
|
|
134417
134303
|
const map = /* @__PURE__ */ new Map();
|
|
134418
134304
|
for (const row of dailyRows) {
|
|
134419
134305
|
const d = parseYmd2(row.date);
|
|
134420
134306
|
if (!d) continue;
|
|
134421
|
-
const { key, label, weekStart } = isoWeekKey(d);
|
|
134307
|
+
const { key, label, weekStart, weekEnd } = isoWeekKey(d);
|
|
134422
134308
|
let acc = map.get(key);
|
|
134423
134309
|
if (!acc) {
|
|
134424
134310
|
acc = {
|
|
134425
134311
|
label,
|
|
134426
134312
|
weekStart,
|
|
134427
|
-
|
|
134428
|
-
|
|
134313
|
+
weekEnd,
|
|
134314
|
+
days: 0,
|
|
134429
134315
|
spend: 0,
|
|
134430
134316
|
impressions: 0,
|
|
134431
134317
|
clicks: 0,
|
|
@@ -134433,20 +134319,21 @@ function aggregateDailyToWeekly(dailyRows) {
|
|
|
134433
134319
|
};
|
|
134434
134320
|
map.set(key, acc);
|
|
134435
134321
|
}
|
|
134436
|
-
|
|
134437
|
-
if (d > acc.maxDate) acc.maxDate = d;
|
|
134322
|
+
acc.days += 1;
|
|
134438
134323
|
acc.spend += num10(row.spend) ?? 0;
|
|
134439
134324
|
acc.impressions += num10(row.impressions) ?? 0;
|
|
134440
134325
|
acc.clicks += num10(row.clicks) ?? 0;
|
|
134441
134326
|
acc.conversions += num10(row.conversions) ?? 0;
|
|
134442
134327
|
}
|
|
134443
134328
|
return [...map.values()].sort((a, b) => a.weekStart.getTime() - b.weekStart.getTime()).map((acc) => {
|
|
134444
|
-
const
|
|
134329
|
+
const startDate = formatYmd2(acc.weekStart);
|
|
134330
|
+
const endDate = formatYmd2(acc.weekEnd);
|
|
134445
134331
|
return {
|
|
134446
134332
|
weekLabel: acc.label,
|
|
134447
|
-
|
|
134448
|
-
|
|
134449
|
-
|
|
134333
|
+
isoWeek: acc.label,
|
|
134334
|
+
startDate,
|
|
134335
|
+
endDate,
|
|
134336
|
+
days: acc.days,
|
|
134450
134337
|
spend: round25(acc.spend),
|
|
134451
134338
|
impressions: Math.round(acc.impressions),
|
|
134452
134339
|
clicks: Math.round(acc.clicks),
|
|
@@ -134457,10 +134344,25 @@ function aggregateDailyToWeekly(dailyRows) {
|
|
|
134457
134344
|
};
|
|
134458
134345
|
});
|
|
134459
134346
|
}
|
|
134347
|
+
function prepareYandexDailyPayload(payload) {
|
|
134348
|
+
const obj = asRecord26(payload) ?? {};
|
|
134349
|
+
const items2 = asItems(obj).map((r) => {
|
|
134350
|
+
const date = normalizeYandexDailyDate(r.date);
|
|
134351
|
+
return date ? { ...r, date } : r;
|
|
134352
|
+
});
|
|
134353
|
+
return { ...obj, items: items2, weekly: aggregateDailyToWeekly(items2) };
|
|
134354
|
+
}
|
|
134355
|
+
var YANDEX_DAILY_OUTLINE_HINTS = [
|
|
134356
|
+
"// `items[].date` \u5DF2\u89C4\u8303\u4E3A YYYY-MM-DD\u3002\u63A5\u53E3\u53EF\u80FD\u8FD4\u56DE ISO \u65F6\u95F4\u6233\uFF08\u5982 2026-07-01T00:00:00+00:00\uFF09\uFF0CCLI \u5DF2\u622A\u65AD\uFF1B\u7981\u6B62\u518D\u62FC\u63A5 T00:00:00Z\uFF0C\u5426\u5219 new Date() \u4F1A Invalid Date\uFF0C\u5468\u6807\u7B7E\u53D8\u6210 NaN\u3002",
|
|
134357
|
+
"// \u5468\u8D8B\u52BF\u8BFB\u672C\u6587\u4EF6 `weekly[]`\uFF08ISO \u5468\u4E00~\u5468\u65E5\uFF1B`days` \u4E3A\u6709\u6570\u636E\u7684\u5929\u6570\uFF09\u3002\u7981\u6B62\u81EA\u884C\u6309 date \u805A\u5468\u3002"
|
|
134358
|
+
];
|
|
134359
|
+
function buildYandexOutlineHints(section) {
|
|
134360
|
+
if (section === "daily") return YANDEX_DAILY_OUTLINE_HINTS;
|
|
134361
|
+
return void 0;
|
|
134362
|
+
}
|
|
134460
134363
|
function mapDailyRows(payload) {
|
|
134461
134364
|
return asItems(payload).map((r) => {
|
|
134462
|
-
const
|
|
134463
|
-
const date = typeof dateRaw === "string" ? dateRaw.slice(0, 10) : dateRaw != null ? String(dateRaw).slice(0, 10) : "";
|
|
134365
|
+
const date = normalizeYandexDailyDate(r.date);
|
|
134464
134366
|
return pickMetrics2({ ...r, date }, ["date"]);
|
|
134465
134367
|
});
|
|
134466
134368
|
}
|
|
@@ -134514,7 +134416,7 @@ function mapDeviceRows2(payload) {
|
|
|
134514
134416
|
return asItems(payload).map((r) => pickMetrics2(r, ["device", "network"]));
|
|
134515
134417
|
}
|
|
134516
134418
|
async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
134517
|
-
const absDir =
|
|
134419
|
+
const absDir = path43.resolve(snapshotDir);
|
|
134518
134420
|
const metaAccountId = typeof asRecord26(payload.meta)?.accountId === "string" ? asRecord26(payload.meta)?.accountId : void 0;
|
|
134519
134421
|
const manifest = await readManifest6(snapshotDir, metaAccountId);
|
|
134520
134422
|
if (!manifest) {
|
|
@@ -134591,6 +134493,132 @@ async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
134591
134493
|
return { ...payload, meta, kpis, tables };
|
|
134592
134494
|
}
|
|
134593
134495
|
|
|
134496
|
+
// src/commands/yandex-analysis/resolve-sections.ts
|
|
134497
|
+
function normalizeSectionToken4(raw) {
|
|
134498
|
+
const t = raw.trim();
|
|
134499
|
+
if (!t) return null;
|
|
134500
|
+
if (YANDEX_SECTION_NAMES.includes(t)) {
|
|
134501
|
+
return t;
|
|
134502
|
+
}
|
|
134503
|
+
return YANDEX_SECTION_ALIASES[t] ?? null;
|
|
134504
|
+
}
|
|
134505
|
+
function resolveSectionList5(sections, exclude) {
|
|
134506
|
+
const splitClean = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
134507
|
+
const include = splitClean(sections);
|
|
134508
|
+
const dropRaw = splitClean(exclude);
|
|
134509
|
+
const unknown = [...include, ...dropRaw].filter((n) => normalizeSectionToken4(n) === null);
|
|
134510
|
+
if (unknown.length > 0) {
|
|
134511
|
+
console.error(
|
|
134512
|
+
`
|
|
134513
|
+
\u274C \u672A\u77E5\u7684 section \u540D\u79F0\uFF1A${unknown.join(", ")}
|
|
134514
|
+
\u53EF\u9009\uFF1A${YANDEX_SECTION_NAMES.join(", ")}
|
|
134515
|
+
\u522B\u540D\uFF1A${Object.keys(YANDEX_SECTION_ALIASES).join(", ")}
|
|
134516
|
+
`
|
|
134517
|
+
);
|
|
134518
|
+
process.exit(1);
|
|
134519
|
+
}
|
|
134520
|
+
const includeCanonical = new Set(include.map((n) => normalizeSectionToken4(n)).filter(Boolean));
|
|
134521
|
+
const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken4(n)).filter(Boolean));
|
|
134522
|
+
const base = include.length > 0 ? YANDEX_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...YANDEX_SECTIONS];
|
|
134523
|
+
return base.filter((s) => !dropCanonical.has(s.name));
|
|
134524
|
+
}
|
|
134525
|
+
|
|
134526
|
+
// src/commands/yandex-analysis/run-batch.ts
|
|
134527
|
+
async function runWithConcurrency6(tasks, concurrency) {
|
|
134528
|
+
const results = new Array(tasks.length);
|
|
134529
|
+
let cursor = 0;
|
|
134530
|
+
const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
|
|
134531
|
+
while (true) {
|
|
134532
|
+
const i = cursor++;
|
|
134533
|
+
if (i >= tasks.length) return;
|
|
134534
|
+
results[i] = await tasks[i]();
|
|
134535
|
+
}
|
|
134536
|
+
});
|
|
134537
|
+
await Promise.all(workers);
|
|
134538
|
+
return results;
|
|
134539
|
+
}
|
|
134540
|
+
async function runAllYandexSections(opts) {
|
|
134541
|
+
const id = assertYandexAccountId(opts.account);
|
|
134542
|
+
const config = loadConfig(opts.token);
|
|
134543
|
+
if (!opts.jsonOut?.trim()) {
|
|
134544
|
+
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");
|
|
134545
|
+
process.exit(1);
|
|
134546
|
+
}
|
|
134547
|
+
const targets = resolveSectionList5(opts.sections, opts.exclude);
|
|
134548
|
+
if (targets.length === 0) {
|
|
134549
|
+
console.error("\n\u274C \u6CA1\u6709\u8981\u6267\u884C\u7684 section\uFF08\u8BF7\u68C0\u67E5 --sections / --exclude\uFF09\u3002\n");
|
|
134550
|
+
process.exit(1);
|
|
134551
|
+
}
|
|
134552
|
+
const { startDate, endDate } = resolveYandexDateRange(opts.start, opts.end);
|
|
134553
|
+
const concurrency = Math.max(
|
|
134554
|
+
1,
|
|
134555
|
+
Math.min(typeof opts.concurrency === "number" ? opts.concurrency : 5, 16)
|
|
134556
|
+
);
|
|
134557
|
+
const cliVersion = getCurrentVersion2();
|
|
134558
|
+
const overallT0 = performance8.now();
|
|
134559
|
+
const tasks = targets.map((def) => async () => {
|
|
134560
|
+
const t0 = performance8.now();
|
|
134561
|
+
const hint = endpointHintForYandexSection(def);
|
|
134562
|
+
try {
|
|
134563
|
+
const raw = await fetchYandexSectionPayload(
|
|
134564
|
+
def,
|
|
134565
|
+
{ ...opts, start: startDate, end: endDate },
|
|
134566
|
+
config,
|
|
134567
|
+
id
|
|
134568
|
+
);
|
|
134569
|
+
const payload = def.name === "daily" ? prepareYandexDailyPayload(raw) : raw;
|
|
134570
|
+
const summary2 = await writeReportAnalysisSnapshot({
|
|
134571
|
+
snapshotDir: opts.jsonOut,
|
|
134572
|
+
section: def.name,
|
|
134573
|
+
accountId: id,
|
|
134574
|
+
dateRange: { start: startDate, end: endDate },
|
|
134575
|
+
payload,
|
|
134576
|
+
cliVersion,
|
|
134577
|
+
outlineExtraHints: buildYandexOutlineHints(def.name)
|
|
134578
|
+
});
|
|
134579
|
+
return {
|
|
134580
|
+
section: def.name,
|
|
134581
|
+
ok: true,
|
|
134582
|
+
elapsedMs: performance8.now() - t0,
|
|
134583
|
+
file: summary2.writtenFiles[0],
|
|
134584
|
+
endpointHint: hint
|
|
134585
|
+
};
|
|
134586
|
+
} catch (err) {
|
|
134587
|
+
return {
|
|
134588
|
+
section: def.name,
|
|
134589
|
+
ok: false,
|
|
134590
|
+
elapsedMs: performance8.now() - t0,
|
|
134591
|
+
error: err instanceof Error ? err.message : String(err),
|
|
134592
|
+
endpointHint: hint
|
|
134593
|
+
};
|
|
134594
|
+
}
|
|
134595
|
+
});
|
|
134596
|
+
const results = await runWithConcurrency6(tasks, concurrency);
|
|
134597
|
+
const succeeded = results.filter((r) => r.ok).length;
|
|
134598
|
+
const failed = results.length - succeeded;
|
|
134599
|
+
const absoluteSnapshotDir = path44.resolve(opts.jsonOut);
|
|
134600
|
+
const summary = {
|
|
134601
|
+
kind: "siluzan-tso-yandex-analysis-snapshot-batch",
|
|
134602
|
+
absoluteSnapshotDir,
|
|
134603
|
+
manifestFile: reportManifestFile(id),
|
|
134604
|
+
accountId: id,
|
|
134605
|
+
totalSections: results.length,
|
|
134606
|
+
succeeded,
|
|
134607
|
+
failed,
|
|
134608
|
+
concurrency,
|
|
134609
|
+
elapsedMs: performance8.now() - overallT0,
|
|
134610
|
+
results
|
|
134611
|
+
};
|
|
134612
|
+
console.log(JSON.stringify(summary));
|
|
134613
|
+
if (failed > 0) process.exit(2);
|
|
134614
|
+
}
|
|
134615
|
+
|
|
134616
|
+
// src/commands/yandex-analysis/render-report.ts
|
|
134617
|
+
import fs34 from "fs";
|
|
134618
|
+
import fsPromises7 from "fs/promises";
|
|
134619
|
+
import path45 from "path";
|
|
134620
|
+
import { fileURLToPath as fileURLToPath11 } from "url";
|
|
134621
|
+
|
|
134594
134622
|
// src/commands/yandex-analysis/report-content.ts
|
|
134595
134623
|
var SECTION_KEYS4 = [
|
|
134596
134624
|
"overview",
|
|
@@ -134627,6 +134655,39 @@ function asRecord27(value) {
|
|
|
134627
134655
|
function pushMissing6(missing, id, chapter, dimension, hint) {
|
|
134628
134656
|
missing.push({ id, chapter, dimension, hint });
|
|
134629
134657
|
}
|
|
134658
|
+
function normKey(value) {
|
|
134659
|
+
return String(value ?? "").trim().toLowerCase();
|
|
134660
|
+
}
|
|
134661
|
+
function insightMatchRecord(item) {
|
|
134662
|
+
const rec = asRecord27(item);
|
|
134663
|
+
if (!rec) return {};
|
|
134664
|
+
return asRecord27(rec.match) ?? rec;
|
|
134665
|
+
}
|
|
134666
|
+
function findRowInsight(list, primaryField, primaryValue, row) {
|
|
134667
|
+
const target = normKey(primaryValue);
|
|
134668
|
+
if (!target) return null;
|
|
134669
|
+
const candidates = list.filter((it) => normKey(insightMatchRecord(it)[primaryField]) === target);
|
|
134670
|
+
if (candidates.length <= 1) return asRecord27(candidates[0]);
|
|
134671
|
+
const campaign = normKey(row.campaignName);
|
|
134672
|
+
const narrowed = candidates.find(
|
|
134673
|
+
(it) => normKey(insightMatchRecord(it).campaignName) === campaign
|
|
134674
|
+
);
|
|
134675
|
+
return asRecord27(narrowed ?? candidates[0]);
|
|
134676
|
+
}
|
|
134677
|
+
function topRowsBySpend(rows, limit) {
|
|
134678
|
+
return rows.map((r) => asRecord27(r)).filter((r) => r != null).sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0)).slice(0, limit);
|
|
134679
|
+
}
|
|
134680
|
+
var ROW_INSIGHT_SPECS = [
|
|
134681
|
+
{
|
|
134682
|
+
tableKey: "keywords",
|
|
134683
|
+
insightKey: "keywords",
|
|
134684
|
+
matchField: "keyword",
|
|
134685
|
+
requiredField: "translation",
|
|
134686
|
+
limit: 40,
|
|
134687
|
+
chapter: "\u2463\u5173\u952E\u8BCD",
|
|
134688
|
+
label: "\u4E2D\u6587\u7FFB\u8BD1"
|
|
134689
|
+
}
|
|
134690
|
+
];
|
|
134630
134691
|
function validateYandexPeriodReportContent(data) {
|
|
134631
134692
|
const missing = [];
|
|
134632
134693
|
const meta = asRecord27(data.meta) ?? {};
|
|
@@ -134697,6 +134758,51 @@ function validateYandexPeriodReportContent(data) {
|
|
|
134697
134758
|
"\u81F3\u5C11\u4E00\u9879\u6709\u6548\u6570\u5B57\uFF08\u8BF7\u4F20 --snapshot-dir \u7531 CLI \u4ECE overview \u5408\u5E76\uFF09"
|
|
134698
134759
|
);
|
|
134699
134760
|
}
|
|
134761
|
+
const tables = asRecord27(data.tables) ?? {};
|
|
134762
|
+
const weeklyRows = asArray7(tables.weekly).map((r) => asRecord27(r)).filter((r) => r != null);
|
|
134763
|
+
for (const [i, row] of weeklyRows.entries()) {
|
|
134764
|
+
const label = `${row.weekLabel ?? ""} ${row.startDate ?? ""} ${row.endDate ?? ""}`;
|
|
134765
|
+
if (/nan/i.test(label) || !normalizeYandexDailyDate(row.startDate) || !normalizeYandexDailyDate(row.endDate)) {
|
|
134766
|
+
pushMissing6(
|
|
134767
|
+
missing,
|
|
134768
|
+
`tables-weekly-${i}`,
|
|
134769
|
+
"\u2467\u65E5\u671F/\u5468\u62A5",
|
|
134770
|
+
`tables.weekly[${i}]`,
|
|
134771
|
+
"\u5468\u6807\u7B7E/\u8D77\u6B62\u65E5\u671F\u65E0\u6548\uFF08\u542B NaN \u6216\u975E YYYY-MM-DD\uFF09\u3002\u8BF7\u4F20 --snapshot-dir\uFF0C\u7531 CLI \u4ECE daily \u805A\u5408 weekly\uFF0C\u7981\u6B62\u81EA\u884C\u6309 date \u62FC T00:00:00Z \u805A\u5468\u3002"
|
|
134772
|
+
);
|
|
134773
|
+
}
|
|
134774
|
+
}
|
|
134775
|
+
const rowInsights = asRecord27(narrative.rowInsights) ?? {};
|
|
134776
|
+
const rowInsightCoverage = {};
|
|
134777
|
+
for (const spec of ROW_INSIGHT_SPECS) {
|
|
134778
|
+
const shown = topRowsBySpend(asArray7(tables[spec.tableKey]), spec.limit).filter(
|
|
134779
|
+
(row) => textLen7(row[spec.matchField])
|
|
134780
|
+
);
|
|
134781
|
+
const insights = asArray7(rowInsights[spec.insightKey]);
|
|
134782
|
+
let covered = 0;
|
|
134783
|
+
const uncovered = [];
|
|
134784
|
+
for (const row of shown) {
|
|
134785
|
+
const insight = findRowInsight(insights, spec.matchField, row[spec.matchField], row);
|
|
134786
|
+
if (insight && textLen7(insight[spec.requiredField]) > 0) {
|
|
134787
|
+
covered += 1;
|
|
134788
|
+
} else {
|
|
134789
|
+
uncovered.push(String(row[spec.matchField]));
|
|
134790
|
+
}
|
|
134791
|
+
}
|
|
134792
|
+
rowInsightCoverage[spec.insightKey] = { shown: shown.length, covered };
|
|
134793
|
+
if (shown.length === 0) continue;
|
|
134794
|
+
if (covered < shown.length) {
|
|
134795
|
+
const sample = uncovered.slice(0, 3).join("\u3001");
|
|
134796
|
+
const more = uncovered.length > 3 ? ` \u7B49 ${uncovered.length} \u884C` : "";
|
|
134797
|
+
pushMissing6(
|
|
134798
|
+
missing,
|
|
134799
|
+
`row-insights-${spec.insightKey}`,
|
|
134800
|
+
spec.chapter,
|
|
134801
|
+
`narrative.rowInsights.${spec.insightKey}.${spec.requiredField}`,
|
|
134802
|
+
`HTML \u5C06\u5C55\u793A ${shown.length} \u884C\uFF0C\u6BCF\u884C\u987B\u6709${spec.label}\uFF08\u5F53\u524D ${covered}/${shown.length}\uFF09\u3002\u672A\u8986\u76D6\u793A\u4F8B\uFF1A${sample}${more}`
|
|
134803
|
+
);
|
|
134804
|
+
}
|
|
134805
|
+
}
|
|
134700
134806
|
return {
|
|
134701
134807
|
ok: missing.length === 0,
|
|
134702
134808
|
missing,
|
|
@@ -134704,7 +134810,8 @@ function validateYandexPeriodReportContent(data) {
|
|
|
134704
134810
|
executiveSummaryCount: executiveSummary.length,
|
|
134705
134811
|
recommendationCount: recommendations.length,
|
|
134706
134812
|
sectionAnalysisCounts,
|
|
134707
|
-
sectionSuggestionCounts
|
|
134813
|
+
sectionSuggestionCounts,
|
|
134814
|
+
rowInsightCoverage
|
|
134708
134815
|
}
|
|
134709
134816
|
};
|
|
134710
134817
|
}
|
|
@@ -134715,7 +134822,7 @@ function formatYandexPeriodReportErrors(result) {
|
|
|
134715
134822
|
`\u62A5\u544A JSON \u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 yandex-period-report.md\uFF09\uFF1A`,
|
|
134716
134823
|
...lines,
|
|
134717
134824
|
"",
|
|
134718
|
-
"\u8BF7\u8865\u5168 narrative.sections.<dimension>.{analysis,suggestions}
|
|
134825
|
+
"\u8BF7\u8865\u5168 narrative.sections.<dimension>.{analysis,suggestions}\u3001narrative.recommendations\uFF0C\u4EE5\u53CA\u8868\u683C\u884C\u5BF9\u5E94\u7684 narrative.rowInsights\uFF08\u5173\u952E\u8BCD translation\uFF09\u540E\u91CD\u65B0\u6267\u884C render\u3002",
|
|
134719
134826
|
"\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"
|
|
134720
134827
|
].join("\n");
|
|
134721
134828
|
}
|
|
@@ -134752,6 +134859,19 @@ function asRecord28(value) {
|
|
|
134752
134859
|
}
|
|
134753
134860
|
throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
|
|
134754
134861
|
}
|
|
134862
|
+
function refreshWeeklyFromDaily(data) {
|
|
134863
|
+
const tables = data.tables;
|
|
134864
|
+
if (!tables || typeof tables !== "object" || Array.isArray(tables)) return data;
|
|
134865
|
+
const daily = Array.isArray(tables.daily) ? tables.daily : [];
|
|
134866
|
+
if (daily.length === 0) return data;
|
|
134867
|
+
return {
|
|
134868
|
+
...data,
|
|
134869
|
+
tables: {
|
|
134870
|
+
...tables,
|
|
134871
|
+
weekly: aggregateDailyToWeekly(daily)
|
|
134872
|
+
}
|
|
134873
|
+
};
|
|
134874
|
+
}
|
|
134755
134875
|
function injectReportData7(html, payload) {
|
|
134756
134876
|
const json = JSON.stringify(payload).replace(/</g, "\\u003c");
|
|
134757
134877
|
if (!html.includes(INJECT_MARKER6)) {
|
|
@@ -134765,6 +134885,7 @@ async function runYandexAnalysisRender(opts) {
|
|
|
134765
134885
|
if (opts.snapshotDir?.trim()) {
|
|
134766
134886
|
data = await mergeYandexAnalysisSnapshotIntoReport(data, path45.resolve(opts.snapshotDir));
|
|
134767
134887
|
}
|
|
134888
|
+
data = refreshWeeklyFromDaily(data);
|
|
134768
134889
|
const contentCheck = validateYandexPeriodReportContent(data);
|
|
134769
134890
|
if (!contentCheck.ok) {
|
|
134770
134891
|
console.error(`
|
package/dist/skill/_meta.json
CHANGED
|
@@ -405,7 +405,7 @@ siluzan-tso yandex-analysis render \
|
|
|
405
405
|
--out ./yandex-period-report.html
|
|
406
406
|
```
|
|
407
407
|
|
|
408
|
-
`search` / `campaigns` / `keywords` / `geo` / `devices` 在 Direct 侧过滤 `AdNetworkType=SEARCH`。无 Metrica / 写操作。报告模板:`report-templates/yandex-period-report.md`(8
|
|
408
|
+
`search` / `campaigns` / `keywords` / `geo` / `devices` 在 Direct 侧过滤 `AdNetworkType=SEARCH`。无 Metrica / 写操作。报告模板:`report-templates/yandex-period-report.md`(8 个分析章节必写总结+建议;周趋势读落盘 `daily.weekly[]`,**禁止**自行按 `date` 聚周)。
|
|
409
409
|
|
|
410
410
|
---
|
|
411
411
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
| --------------- | ------------------------- | ---------------------------- |
|
|
36
36
|
| 账户总览 | `overview` | 对象:`totals` / `balance` … |
|
|
37
37
|
| 日报表 | `daily` | `items[]` |
|
|
38
|
-
| 周报表 |
|
|
38
|
+
| 周报表 | `daily` 的 `weekly[]` | **读落盘 weekly,禁止自行聚周** |
|
|
39
39
|
| Search 网络 | `search` | `items[]` |
|
|
40
40
|
| 广告系列 | `campaigns` | `items[]` |
|
|
41
41
|
| 关键词 | `keywords` | `items[]` |
|
|
@@ -78,7 +78,7 @@ siluzan-tso yandex-analysis -a <porg-xxx> --start <S> --end <E> --json-out ./sna
|
|
|
78
78
|
| 地域 | `locationName` / `targetingLocationName` |
|
|
79
79
|
| 设备 | `device` |
|
|
80
80
|
| 日报 | `date` |
|
|
81
|
-
| 周报 | `weekLabel`、`startDate`、`endDate`、`days
|
|
81
|
+
| 周报 | `daily.weekly[]` 的 `weekLabel`、`startDate`、`endDate`、`days`(CLI 已算) |
|
|
82
82
|
|
|
83
83
|
**账户总览 Sheet**:KPI 区(余额、本期消耗、日均、点击、CTR、CPC、转化)+ **全部叙事**(执行摘要、各维总结/建议、≥3 条跨维优化建议)写在本 Sheet;列表 Sheet 仅数据表。
|
|
84
84
|
|
|
@@ -89,7 +89,8 @@ siluzan-tso yandex-analysis -a <porg-xxx> --start <S> --end <E> --json-out ./sna
|
|
|
89
89
|
| ❌ 禁止 | ✅ 正确 |
|
|
90
90
|
| ------------------------------------ | ---------------------------------------------------- |
|
|
91
91
|
| 把 `ctr` 当已是百分数再 ÷100 | 落盘为 0~1;Excel 用百分比格式或 `*100` 后加 `%` |
|
|
92
|
-
|
|
|
92
|
+
| 编造学习期、搜索词分类等无接口硬事实 | 无字段且算不出则不写该列;关键词翻译走 rowInsights |
|
|
93
|
+
| 自行按 `daily.date` 聚周(易出 `NaN-NaN-NaN`) | 读 `daily.weekly[]`;`date` 已是 YYYY-MM-DD,禁止再拼 `T00:00:00Z` |
|
|
93
94
|
| `campaignId` 写成数字 | `String(id)` + 文本格式 |
|
|
94
95
|
| search-terms 拉超过 180 天 | 缩短区间或排除该维 |
|
|
95
96
|
|