siluzan-tso-cli 1.1.45-beta.2 → 1.1.45-beta.4
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 +141 -63
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/references/accounts/accounts-balance-stats.md +2 -2
- package/dist/skill/references/analytics/account-analytics.md +1 -1
- package/dist/skill/references/operations/hosted-automation-monitoring-json.md +1 -1
- package/dist/skill/references/report-templates/yandex-period-report-excel.md +1 -1
- package/dist/skill/references/report-templates/yandex-period-report.md +38 -1
- package/dist/skill/report-templates/yandex-period-report-excel.md +1 -1
- package/dist/skill/report-templates/yandex-period-report.html +475 -159
- package/dist/skill/report-templates/yandex-period-report.md +38 -1
- 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.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -4503,17 +4503,57 @@ var init_currency_display = __esm({
|
|
|
4503
4503
|
}
|
|
4504
4504
|
});
|
|
4505
4505
|
|
|
4506
|
+
// src/commands/google-analysis/daily-metrics-range.ts
|
|
4507
|
+
function pad2(n) {
|
|
4508
|
+
return String(n).padStart(2, "0");
|
|
4509
|
+
}
|
|
4510
|
+
function formatChinaOffsetDateTime(d) {
|
|
4511
|
+
const sh = new Date(d.getTime() + CHINA_OFFSET_MS);
|
|
4512
|
+
return `${sh.getUTCFullYear()}-${pad2(sh.getUTCMonth() + 1)}-${pad2(sh.getUTCDate())}T${pad2(sh.getUTCHours())}:${pad2(sh.getUTCMinutes())}:${pad2(sh.getUTCSeconds())}+08:00`;
|
|
4513
|
+
}
|
|
4514
|
+
function defaultChinaDateRange(now = /* @__PURE__ */ new Date()) {
|
|
4515
|
+
const chinaNow = new Date(now.getTime() + CHINA_OFFSET_MS);
|
|
4516
|
+
chinaNow.setUTCDate(chinaNow.getUTCDate() - 1);
|
|
4517
|
+
const endY = chinaNow.getUTCFullYear();
|
|
4518
|
+
const endM = chinaNow.getUTCMonth();
|
|
4519
|
+
const endD = chinaNow.getUTCDate();
|
|
4520
|
+
const startChina = new Date(Date.UTC(endY, endM, endD - 6));
|
|
4521
|
+
const endChina = new Date(Date.UTC(endY, endM, endD));
|
|
4522
|
+
const fmt = (utcCal) => `${utcCal.getUTCFullYear()}-${pad2(utcCal.getUTCMonth() + 1)}-${pad2(utcCal.getUTCDate())}`;
|
|
4523
|
+
return { startDate: fmt(startChina), endDate: fmt(endChina) };
|
|
4524
|
+
}
|
|
4525
|
+
function resolveDailyMetricsQueryTimes(startDate, endDate, now = /* @__PURE__ */ new Date()) {
|
|
4526
|
+
const startInstant = `${startDate}T00:00:00+08:00`;
|
|
4527
|
+
const endOfDay = /* @__PURE__ */ new Date(`${endDate}T23:59:59+08:00`);
|
|
4528
|
+
const endInstant = endOfDay.getTime() > now.getTime() ? formatChinaOffsetDateTime(now) : formatChinaOffsetDateTime(endOfDay);
|
|
4529
|
+
return { startInstant, endInstant };
|
|
4530
|
+
}
|
|
4531
|
+
function resolveChinaQueryTimes(startDate, endDate, _now = /* @__PURE__ */ new Date()) {
|
|
4532
|
+
return { startInstant: startDate, endInstant: endDate };
|
|
4533
|
+
}
|
|
4534
|
+
var CHINA_OFFSET_MS;
|
|
4535
|
+
var init_daily_metrics_range = __esm({
|
|
4536
|
+
"src/commands/google-analysis/daily-metrics-range.ts"() {
|
|
4537
|
+
"use strict";
|
|
4538
|
+
CHINA_OFFSET_MS = 8 * 36e5;
|
|
4539
|
+
}
|
|
4540
|
+
});
|
|
4541
|
+
|
|
4506
4542
|
// src/utils/balance.ts
|
|
4507
|
-
function
|
|
4508
|
-
return
|
|
4543
|
+
function pad22(n) {
|
|
4544
|
+
return String(n).padStart(2, "0");
|
|
4509
4545
|
}
|
|
4510
|
-
function dateRangeLastNDays(n) {
|
|
4546
|
+
function dateRangeLastNDays(n, now = /* @__PURE__ */ new Date()) {
|
|
4511
4547
|
const days = Math.max(1, Math.floor(Number(n) || 1));
|
|
4512
|
-
const
|
|
4513
|
-
|
|
4514
|
-
const
|
|
4515
|
-
|
|
4516
|
-
|
|
4548
|
+
const chinaNow = new Date(now.getTime() + CHINA_OFFSET_MS2);
|
|
4549
|
+
chinaNow.setUTCDate(chinaNow.getUTCDate() - 1);
|
|
4550
|
+
const endY = chinaNow.getUTCFullYear();
|
|
4551
|
+
const endM = chinaNow.getUTCMonth();
|
|
4552
|
+
const endD = chinaNow.getUTCDate();
|
|
4553
|
+
const startChina = new Date(Date.UTC(endY, endM, endD - (days - 1)));
|
|
4554
|
+
const endChina = new Date(Date.UTC(endY, endM, endD));
|
|
4555
|
+
const fmtChina = (utcCal) => `${utcCal.getUTCFullYear()}-${pad22(utcCal.getUTCMonth() + 1)}-${pad22(utcCal.getUTCDate())}`;
|
|
4556
|
+
return { startDate: fmtChina(startChina), endDate: fmtChina(endChina) };
|
|
4517
4557
|
}
|
|
4518
4558
|
function defaultDateRange() {
|
|
4519
4559
|
return dateRangeLastNDays(7);
|
|
@@ -4709,9 +4749,10 @@ async function fetchOverviewMap(route, accountIds, config, startDate, endDate, v
|
|
|
4709
4749
|
const end = endDate ?? range.endDate;
|
|
4710
4750
|
try {
|
|
4711
4751
|
if (route === "Google") {
|
|
4752
|
+
const { startInstant, endInstant } = resolveDailyMetricsQueryTimes(start, end);
|
|
4712
4753
|
const params = new URLSearchParams({
|
|
4713
|
-
startDate:
|
|
4714
|
-
endDate:
|
|
4754
|
+
startDate: startInstant,
|
|
4755
|
+
endDate: endInstant,
|
|
4715
4756
|
mediaCustomerIds: accountIds.join("|")
|
|
4716
4757
|
});
|
|
4717
4758
|
const url = `${config.apiBaseUrl}/report/media-account/google/account-spend-overview?${params}`;
|
|
@@ -4797,10 +4838,11 @@ async function fetchOverviewMap(route, accountIds, config, startDate, endDate, v
|
|
|
4797
4838
|
}
|
|
4798
4839
|
return result;
|
|
4799
4840
|
}
|
|
4800
|
-
var BALANCE_SUPPORTED_MEDIA;
|
|
4841
|
+
var BALANCE_SUPPORTED_MEDIA, CHINA_OFFSET_MS2;
|
|
4801
4842
|
var init_balance = __esm({
|
|
4802
4843
|
"src/utils/balance.ts"() {
|
|
4803
4844
|
"use strict";
|
|
4845
|
+
init_daily_metrics_range();
|
|
4804
4846
|
init_auth();
|
|
4805
4847
|
BALANCE_SUPPORTED_MEDIA = [
|
|
4806
4848
|
"Google",
|
|
@@ -4810,6 +4852,7 @@ var init_balance = __esm({
|
|
|
4810
4852
|
"Kwai",
|
|
4811
4853
|
"MetaAd"
|
|
4812
4854
|
];
|
|
4855
|
+
CHINA_OFFSET_MS2 = 8 * 36e5;
|
|
4813
4856
|
}
|
|
4814
4857
|
});
|
|
4815
4858
|
|
|
@@ -5525,42 +5568,6 @@ var init_accounts_digest2 = __esm({
|
|
|
5525
5568
|
}
|
|
5526
5569
|
});
|
|
5527
5570
|
|
|
5528
|
-
// src/commands/google-analysis/daily-metrics-range.ts
|
|
5529
|
-
function pad2(n) {
|
|
5530
|
-
return String(n).padStart(2, "0");
|
|
5531
|
-
}
|
|
5532
|
-
function formatChinaOffsetDateTime(d) {
|
|
5533
|
-
const sh = new Date(d.getTime() + CHINA_OFFSET_MS);
|
|
5534
|
-
return `${sh.getUTCFullYear()}-${pad2(sh.getUTCMonth() + 1)}-${pad2(sh.getUTCDate())}T${pad2(sh.getUTCHours())}:${pad2(sh.getUTCMinutes())}:${pad2(sh.getUTCSeconds())}+08:00`;
|
|
5535
|
-
}
|
|
5536
|
-
function defaultChinaDateRange(now = /* @__PURE__ */ new Date()) {
|
|
5537
|
-
const chinaNow = new Date(now.getTime() + CHINA_OFFSET_MS);
|
|
5538
|
-
chinaNow.setUTCDate(chinaNow.getUTCDate() - 1);
|
|
5539
|
-
const endY = chinaNow.getUTCFullYear();
|
|
5540
|
-
const endM = chinaNow.getUTCMonth();
|
|
5541
|
-
const endD = chinaNow.getUTCDate();
|
|
5542
|
-
const startChina = new Date(Date.UTC(endY, endM, endD - 6));
|
|
5543
|
-
const endChina = new Date(Date.UTC(endY, endM, endD));
|
|
5544
|
-
const fmt2 = (utcCal) => `${utcCal.getUTCFullYear()}-${pad2(utcCal.getUTCMonth() + 1)}-${pad2(utcCal.getUTCDate())}`;
|
|
5545
|
-
return { startDate: fmt2(startChina), endDate: fmt2(endChina) };
|
|
5546
|
-
}
|
|
5547
|
-
function resolveDailyMetricsQueryTimes(startDate, endDate, now = /* @__PURE__ */ new Date()) {
|
|
5548
|
-
const startInstant = `${startDate}T00:00:00+08:00`;
|
|
5549
|
-
const endOfDay = /* @__PURE__ */ new Date(`${endDate}T23:59:59+08:00`);
|
|
5550
|
-
const endInstant = endOfDay.getTime() > now.getTime() ? formatChinaOffsetDateTime(now) : formatChinaOffsetDateTime(endOfDay);
|
|
5551
|
-
return { startInstant, endInstant };
|
|
5552
|
-
}
|
|
5553
|
-
function resolveChinaQueryTimes(startDate, endDate, _now = /* @__PURE__ */ new Date()) {
|
|
5554
|
-
return { startInstant: startDate, endInstant: endDate };
|
|
5555
|
-
}
|
|
5556
|
-
var CHINA_OFFSET_MS;
|
|
5557
|
-
var init_daily_metrics_range = __esm({
|
|
5558
|
-
"src/commands/google-analysis/daily-metrics-range.ts"() {
|
|
5559
|
-
"use strict";
|
|
5560
|
-
CHINA_OFFSET_MS = 8 * 36e5;
|
|
5561
|
-
}
|
|
5562
|
-
});
|
|
5563
|
-
|
|
5564
5571
|
// src/utils/snapshot/async-lock.ts
|
|
5565
5572
|
async function withAsyncLock(key, fn) {
|
|
5566
5573
|
const prev = chains.get(key) ?? Promise.resolve();
|
|
@@ -108630,8 +108637,8 @@ function resolveFacebookDateRange(start, end) {
|
|
|
108630
108637
|
endD.setDate(endD.getDate() - 1);
|
|
108631
108638
|
const startD = new Date(endD);
|
|
108632
108639
|
startD.setDate(startD.getDate() - 6);
|
|
108633
|
-
const
|
|
108634
|
-
return { startDate:
|
|
108640
|
+
const fmt = (d) => d.toISOString().slice(0, 10);
|
|
108641
|
+
return { startDate: fmt(startD), endDate: fmt(endD) };
|
|
108635
108642
|
}
|
|
108636
108643
|
console.error("\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u8FD1 7 \u5929\uFF08\u622A\u81F3\u6628\u5929\uFF09\u3002\n");
|
|
108637
108644
|
process.exit(1);
|
|
@@ -108729,8 +108736,8 @@ function resolveMetaOverviewDateRange(start, end) {
|
|
|
108729
108736
|
endD.setDate(endD.getDate() - 1);
|
|
108730
108737
|
const startD = new Date(endD);
|
|
108731
108738
|
startD.setDate(startD.getDate() - 6);
|
|
108732
|
-
const
|
|
108733
|
-
return { startDate:
|
|
108739
|
+
const fmt = (d) => d.toISOString().slice(0, 10);
|
|
108740
|
+
return { startDate: fmt(startD), endDate: fmt(endD) };
|
|
108734
108741
|
}
|
|
108735
108742
|
console.error("\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u8FD1 7 \u5929\uFF08\u622A\u81F3\u6628\u5929\uFF09\u3002\n");
|
|
108736
108743
|
process.exit(1);
|
|
@@ -133252,8 +133259,8 @@ function resolveDateRange2(start, end) {
|
|
|
133252
133259
|
endD.setDate(endD.getDate() - 1);
|
|
133253
133260
|
const startD = new Date(endD);
|
|
133254
133261
|
startD.setDate(startD.getDate() - 6);
|
|
133255
|
-
const
|
|
133256
|
-
return { startDate:
|
|
133262
|
+
const fmt = (d) => d.toISOString().slice(0, 10);
|
|
133263
|
+
return { startDate: fmt(startD), endDate: fmt(endD) };
|
|
133257
133264
|
}
|
|
133258
133265
|
console.error("\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u8FD1 7 \u5929\uFF08\u622A\u81F3\u6628\u5929\uFF09\u3002\n");
|
|
133259
133266
|
process.exit(1);
|
|
@@ -134320,6 +134327,7 @@ function asRecord26(value) {
|
|
|
134320
134327
|
return null;
|
|
134321
134328
|
}
|
|
134322
134329
|
function num10(value) {
|
|
134330
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
134323
134331
|
const n = Number(value);
|
|
134324
134332
|
return Number.isFinite(n) ? n : void 0;
|
|
134325
134333
|
}
|
|
@@ -134334,15 +134342,19 @@ function asItems(payload) {
|
|
|
134334
134342
|
return items2.filter((r) => asRecord26(r) !== null);
|
|
134335
134343
|
}
|
|
134336
134344
|
function pickMetrics2(row, extras = []) {
|
|
134345
|
+
const spend = num10(row.spend) ?? 0;
|
|
134346
|
+
const impressions = num10(row.impressions) ?? 0;
|
|
134347
|
+
const clicks = num10(row.clicks) ?? 0;
|
|
134348
|
+
const conversions = num10(row.conversions) ?? 0;
|
|
134337
134349
|
const out = {
|
|
134338
|
-
spend
|
|
134339
|
-
impressions
|
|
134340
|
-
clicks
|
|
134341
|
-
|
|
134342
|
-
|
|
134343
|
-
|
|
134344
|
-
conversionRate: num10(row.conversionRate),
|
|
134345
|
-
costPerConversion: num10(row.costPerConversion),
|
|
134350
|
+
spend,
|
|
134351
|
+
impressions,
|
|
134352
|
+
clicks,
|
|
134353
|
+
conversions,
|
|
134354
|
+
ctr: num10(row.ctr) ?? (impressions > 0 ? clicks / impressions : void 0),
|
|
134355
|
+
averageCpc: num10(row.averageCpc) ?? (clicks > 0 ? round25(spend / clicks) : void 0),
|
|
134356
|
+
conversionRate: num10(row.conversionRate) ?? (clicks > 0 ? conversions / clicks : void 0),
|
|
134357
|
+
costPerConversion: num10(row.costPerConversion) ?? (conversions > 0 ? round25(spend / conversions) : void 0),
|
|
134346
134358
|
roi: num10(row.roi)
|
|
134347
134359
|
};
|
|
134348
134360
|
for (const f of extras) {
|
|
@@ -134619,6 +134631,39 @@ function asRecord27(value) {
|
|
|
134619
134631
|
function pushMissing6(missing, id, chapter, dimension, hint) {
|
|
134620
134632
|
missing.push({ id, chapter, dimension, hint });
|
|
134621
134633
|
}
|
|
134634
|
+
function normKey(value) {
|
|
134635
|
+
return String(value ?? "").trim().toLowerCase();
|
|
134636
|
+
}
|
|
134637
|
+
function insightMatchRecord(item) {
|
|
134638
|
+
const rec = asRecord27(item);
|
|
134639
|
+
if (!rec) return {};
|
|
134640
|
+
return asRecord27(rec.match) ?? rec;
|
|
134641
|
+
}
|
|
134642
|
+
function findRowInsight(list, primaryField, primaryValue, row) {
|
|
134643
|
+
const target = normKey(primaryValue);
|
|
134644
|
+
if (!target) return null;
|
|
134645
|
+
const candidates = list.filter((it) => normKey(insightMatchRecord(it)[primaryField]) === target);
|
|
134646
|
+
if (candidates.length <= 1) return asRecord27(candidates[0]);
|
|
134647
|
+
const campaign = normKey(row.campaignName);
|
|
134648
|
+
const narrowed = candidates.find(
|
|
134649
|
+
(it) => normKey(insightMatchRecord(it).campaignName) === campaign
|
|
134650
|
+
);
|
|
134651
|
+
return asRecord27(narrowed ?? candidates[0]);
|
|
134652
|
+
}
|
|
134653
|
+
function topRowsBySpend(rows, limit) {
|
|
134654
|
+
return rows.map((r) => asRecord27(r)).filter((r) => r != null).sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0)).slice(0, limit);
|
|
134655
|
+
}
|
|
134656
|
+
var ROW_INSIGHT_SPECS = [
|
|
134657
|
+
{
|
|
134658
|
+
tableKey: "keywords",
|
|
134659
|
+
insightKey: "keywords",
|
|
134660
|
+
matchField: "keyword",
|
|
134661
|
+
requiredField: "translation",
|
|
134662
|
+
limit: 40,
|
|
134663
|
+
chapter: "\u2463\u5173\u952E\u8BCD",
|
|
134664
|
+
label: "\u4E2D\u6587\u7FFB\u8BD1"
|
|
134665
|
+
}
|
|
134666
|
+
];
|
|
134622
134667
|
function validateYandexPeriodReportContent(data) {
|
|
134623
134668
|
const missing = [];
|
|
134624
134669
|
const meta = asRecord27(data.meta) ?? {};
|
|
@@ -134689,6 +134734,38 @@ function validateYandexPeriodReportContent(data) {
|
|
|
134689
134734
|
"\u81F3\u5C11\u4E00\u9879\u6709\u6548\u6570\u5B57\uFF08\u8BF7\u4F20 --snapshot-dir \u7531 CLI \u4ECE overview \u5408\u5E76\uFF09"
|
|
134690
134735
|
);
|
|
134691
134736
|
}
|
|
134737
|
+
const tables = asRecord27(data.tables) ?? {};
|
|
134738
|
+
const rowInsights = asRecord27(narrative.rowInsights) ?? {};
|
|
134739
|
+
const rowInsightCoverage = {};
|
|
134740
|
+
for (const spec of ROW_INSIGHT_SPECS) {
|
|
134741
|
+
const shown = topRowsBySpend(asArray7(tables[spec.tableKey]), spec.limit).filter(
|
|
134742
|
+
(row) => textLen7(row[spec.matchField])
|
|
134743
|
+
);
|
|
134744
|
+
const insights = asArray7(rowInsights[spec.insightKey]);
|
|
134745
|
+
let covered = 0;
|
|
134746
|
+
const uncovered = [];
|
|
134747
|
+
for (const row of shown) {
|
|
134748
|
+
const insight = findRowInsight(insights, spec.matchField, row[spec.matchField], row);
|
|
134749
|
+
if (insight && textLen7(insight[spec.requiredField]) > 0) {
|
|
134750
|
+
covered += 1;
|
|
134751
|
+
} else {
|
|
134752
|
+
uncovered.push(String(row[spec.matchField]));
|
|
134753
|
+
}
|
|
134754
|
+
}
|
|
134755
|
+
rowInsightCoverage[spec.insightKey] = { shown: shown.length, covered };
|
|
134756
|
+
if (shown.length === 0) continue;
|
|
134757
|
+
if (covered < shown.length) {
|
|
134758
|
+
const sample = uncovered.slice(0, 3).join("\u3001");
|
|
134759
|
+
const more = uncovered.length > 3 ? ` \u7B49 ${uncovered.length} \u884C` : "";
|
|
134760
|
+
pushMissing6(
|
|
134761
|
+
missing,
|
|
134762
|
+
`row-insights-${spec.insightKey}`,
|
|
134763
|
+
spec.chapter,
|
|
134764
|
+
`narrative.rowInsights.${spec.insightKey}.${spec.requiredField}`,
|
|
134765
|
+
`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}`
|
|
134766
|
+
);
|
|
134767
|
+
}
|
|
134768
|
+
}
|
|
134692
134769
|
return {
|
|
134693
134770
|
ok: missing.length === 0,
|
|
134694
134771
|
missing,
|
|
@@ -134696,7 +134773,8 @@ function validateYandexPeriodReportContent(data) {
|
|
|
134696
134773
|
executiveSummaryCount: executiveSummary.length,
|
|
134697
134774
|
recommendationCount: recommendations.length,
|
|
134698
134775
|
sectionAnalysisCounts,
|
|
134699
|
-
sectionSuggestionCounts
|
|
134776
|
+
sectionSuggestionCounts,
|
|
134777
|
+
rowInsightCoverage
|
|
134700
134778
|
}
|
|
134701
134779
|
};
|
|
134702
134780
|
}
|
|
@@ -134707,7 +134785,7 @@ function formatYandexPeriodReportErrors(result) {
|
|
|
134707
134785
|
`\u62A5\u544A JSON \u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 yandex-period-report.md\uFF09\uFF1A`,
|
|
134708
134786
|
...lines,
|
|
134709
134787
|
"",
|
|
134710
|
-
"\u8BF7\u8865\u5168 narrative.sections.<dimension>.{analysis,suggestions}
|
|
134788
|
+
"\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",
|
|
134711
134789
|
"\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
134790
|
].join("\n");
|
|
134713
134791
|
}
|
package/dist/skill/_meta.json
CHANGED
|
@@ -24,7 +24,7 @@ siluzan-tso balance-scan -m <媒体类型> [选项]
|
|
|
24
24
|
| `-m, --media <type>` | 必填:`Google \| TikTok \| Yandex \| MetaAd \| BingV2 \| Kwai` | — |
|
|
25
25
|
| `-a, --accounts <ids>` | 指定 `mediaCustomerId`(逗号分隔);**跳过清单翻页**,对这些 ID 拉数后全部输出 | — |
|
|
26
26
|
| `--threshold-days <n>` | 剩余续航天数阈值 | `7` |
|
|
27
|
-
| `--spend-days <n>` |
|
|
27
|
+
| `--spend-days <n>` | 日均消耗回看自然日数(截至昨天不含今天,**北京时间**;日均 = 窗口合计 / N) | `7` |
|
|
28
28
|
| `--min-balance <n>` | 绝对余额阈值(与 threshold 取并集) | — |
|
|
29
29
|
| `--min-daily-spend <n>` | 日均消耗低于此值视为僵尸账户,不做续航估算 | `0.01` |
|
|
30
30
|
| `--target-days <n>` | 建议充值目标续航天数 | `30` |
|
|
@@ -47,7 +47,7 @@ siluzan-tso balance-scan -m BingV2 -a id1,id2,id3 --json-out ./snap-p2-subset
|
|
|
47
47
|
siluzan-tso balance-scan -m MetaAd --json-out ./snap-p2-meta
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
**读盘要点**:先看 `meta.dataIssue`(非空则失败)。`data.items` = **全部已检查账户**(OAuth 失效户不在 items,见 `meta.skippedInvalidOAuth`);`meta.hitCount` = 触阈值条数。预警筛 `hitReason !== "none"`;`hitReason="none"` 表示已检查但未触阈值。按 `remainingDays` 升序交付。用 **`dailySpend`** 做日均(口径见 `meta.spendDays` / `meta.spendWindow`),勿把 `stats` 区间合计 `spend`
|
|
50
|
+
**读盘要点**:先看 `meta.dataIssue`(非空则失败)。`data.items` = **全部已检查账户**(OAuth 失效户不在 items,见 `meta.skippedInvalidOAuth`);`meta.hitCount` = 触阈值条数。预警筛 `hitReason !== "none"`;`hitReason="none"` 表示已检查但未触阈值。按 `remainingDays` 升序交付。用 **`dailySpend`** 做日均(口径见 `meta.spendDays` / `meta.spendWindow`),勿把 `stats` 区间合计 `spend` 当「每天消耗」。Google 消耗走 `account-spend-overview`,`startDate`/`endDate` 带东八区墙钟(`…T00:00:00+08:00` / `…T23:59:59+08:00`),与 `stats` 同口径。与 `balance` / `accounts-digest` 分工见 `references/core/agent-conventions.md` §八。
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
| `google-analysis --sections overview`(Google 网关 `OverviewSectionData`) | **实时**;请求起止为 **YYYY-MM-DD**(不附加时区) | ✅ 可查当天 | 当天/今日消耗、当天高消耗账号排行 |
|
|
28
28
|
| `google-analysis --sections campaign-types`(`types-summary`) | **实时** | ✅ 可查当天 | 当天系列类型分布 |
|
|
29
29
|
| `google-analysis` 其他 range 维度(`campaigns` / `keywords` / `devices` / `geographic` / `daily-metrics` / …) | 实时(受 Google Ads API 同步延迟影响);**一律 YYYY-MM-DD**(不附加时区;与 `stats` 的 UTC+8 不同) | 可查当天,但当天可能尚未结算 | 周期分析、报告 |
|
|
30
|
-
| `stats -m Google` / `balance-scan -m Google` / `accounts-digest -m Google` / `list-accounts -m Google` 合并消耗(TSO `account-spend-overview`,2026-05 起) | **后端自动分流**:窗口完全在历史 → `database`;含今天 → `googleCombined
|
|
30
|
+
| `stats -m Google` / `balance-scan -m Google` / `accounts-digest -m Google` / `list-accounts -m Google` 合并消耗(TSO `account-spend-overview`,2026-05 起) | **后端自动分流**:窗口完全在历史 → `database`;含今天 → `googleCombined`。**凡走本接口的请求起止一律东八区墙钟**(`…T00:00:00+08:00` ~ `…T23:59:59+08:00`) | ✅ 可查当天(含今天时切实时聚合) | 历史回溯、巡检、周报 KPI、余额续航估算 |
|
|
31
31
|
| `stats` / `balance-scan` / `accounts-digest` / `list-accounts` 的 **TikTok / Yandex / BingV2 / Kwai / MetaAd** 合并消耗(TSO `accountsoverview`;MetaAd OAuth 户走 `FacebookAds` 段) | **每日同步昨天** | ❌ 查今天会全为 0 | 历史回溯、巡检、余额续航估算(口径为"截至昨天") |
|
|
32
32
|
| `balance`(`GetMediaAccountInfo`) | 实时 | — | 仅当前余额,不反映消耗 |
|
|
33
33
|
|
|
@@ -22,7 +22,7 @@ siluzan-tso balance-scan -m Google [--threshold-days <n>] [--min-balance <n>] [-
|
|
|
22
22
|
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
23
23
|
| 账户 | `data.items[].mediaCustomerId`、`data.items[].name`、`data.items[].advertiserName` |
|
|
24
24
|
| 余额(主币种金额,与平台余额接口一致) | **`data.items[].balance`**(由 `remainingAccountBudget` 计算而来) |
|
|
25
|
-
| 近 7 日估算日均消耗 | **`data.items[].dailySpend`** = 近 7 日总消耗 / 7(窗口为 **[T-7, T-1]
|
|
25
|
+
| 近 7 日估算日均消耗 | **`data.items[].dailySpend`** = 近 7 日总消耗 / 7(窗口为 **[T-7, T-1]**,北京时间截至昨天的 7 个自然日,**不含当天**;Google 请求带 `+08:00`) |
|
|
26
26
|
| 按余额÷日均估算的续航天数 | **`data.items[].remainingDays`** = `balance / dailySpend`(消耗过低 < `minDailySpend` 时为 `null`) |
|
|
27
27
|
| 建议充值额(按 `meta.thresholds.targetDaysForTopup` 目标) | **`data.items[].recommendedTopup`** |
|
|
28
28
|
| 命中原因(阈值逻辑) | **`data.items[].hitReason`**:`low-days` \| `low-balance` \| `both` \| `none`(已检查但未触阈值)。`data.items` 含全部已检查账户;预警筛 `hitReason !== "none"`。`meta.hitCount` 为触阈值条数 |
|
|
@@ -89,7 +89,7 @@ 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
93
|
| `campaignId` 写成数字 | `String(id)` + 文本格式 |
|
|
94
94
|
| search-terms 拉超过 180 天 | 缩短区间或排除该维 |
|
|
95
95
|
|
|
@@ -60,6 +60,43 @@ siluzan-tso yandex-analysis render \
|
|
|
60
60
|
- `executiveSummary[]`:≥1 段执行摘要
|
|
61
61
|
- `sections.{overview,search,campaigns,keywords,searchTerms,geo,devices,daily}`:每个维度各 `{analysis[](≥1,含数字依据), suggestions[](≥1)}`
|
|
62
62
|
- `recommendations[]`:≥3 条跨维度优化建议(可用 `{priority,text,anchor}`,anchor 对齐样例锚点如 `#keyword` / `#query`)
|
|
63
|
+
- `rowInsights`:有关键词表格行时,**HTML 将展示的 Top N 行必须有中文翻译**;`render` 缺项会拒绝出 HTML。问题/建议可空。见下方「逐行洞察」
|
|
64
|
+
|
|
65
|
+
#### 逐行洞察 `narrative.rowInsights`
|
|
66
|
+
|
|
67
|
+
开发样例的部分表格列(关键词"中文翻译"、各维度"问题/建议")不是原始接口字段,而是 **Agent 基于已拉取数据做的翻译/判断**:
|
|
68
|
+
|
|
69
|
+
- **必填(`render` 校验)**:`tables.keywords` 有行时,须为 HTML 将展示的每一行(按消耗降序,最多 40 行)提供匹配的 `rowInsights`,且有非空 `translation`。缺一项即拒绝生成 HTML。
|
|
70
|
+
- **可空**:各维 `issue` / `suggestion`(空表示该行无问题,模板显示「—」)。
|
|
71
|
+
- 搜索词「分类」Yandex 不提供,也无法由现有指标算出,报告不展示该列。
|
|
72
|
+
|
|
73
|
+
示例:
|
|
74
|
+
|
|
75
|
+
```jsonc
|
|
76
|
+
"narrative": {
|
|
77
|
+
"rowInsights": {
|
|
78
|
+
// 按 keyword 原文匹配(如同词跨系列,可加 campaignName 消歧)
|
|
79
|
+
"keywords": [
|
|
80
|
+
{ "match": { "keyword": "металлопрокатное оборудование" }, "translation": "金属轧制设备", "issue": "", "suggestion": "可加大预算" }
|
|
81
|
+
],
|
|
82
|
+
// 按 searchQuery 原文匹配
|
|
83
|
+
"searchTerms": [
|
|
84
|
+
{ "match": { "searchQuery": "прокатный стан своими руками" }, "issue": "DIY 意图消耗", "suggestion": "添加否定短语 своими руками" }
|
|
85
|
+
],
|
|
86
|
+
// 按 campaignName 匹配
|
|
87
|
+
"campaigns": [
|
|
88
|
+
{ "match": { "campaignName": "Search - 自动定向" }, "issue": "自动定向 CTR 弱", "suggestion": "评估降价或暂停" }
|
|
89
|
+
],
|
|
90
|
+
// 按 locationName(城市名)匹配
|
|
91
|
+
"geo": [{ "match": { "locationName": "Moscow" }, "issue": "", "suggestion": "" }],
|
|
92
|
+
// 按 device 匹配
|
|
93
|
+
"devices": [{ "match": { "device": "tablet" }, "issue": "样本不足", "suggestion": "暂不调整设备系数" }]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- `translation` 是 Agent **对已知原文的翻译**,允许生成;不得编造原始接口没有、也无法由现有指标算出的硬事实。Campaign「学习期」、搜索词「分类」无数据源,报告不展示这两列。
|
|
99
|
+
- 每条洞察按原文匹配一行;HTML 将展示的关键词行若缺中文翻译,`render` 会拒绝出 HTML。
|
|
63
100
|
|
|
64
101
|
### 快照合并范围(`--snapshot-dir`)
|
|
65
102
|
|
|
@@ -93,7 +130,7 @@ siluzan-tso yandex-analysis -a <porg-xxx> --start <S> --end <E> --json-out ./sna
|
|
|
93
130
|
|
|
94
131
|
- 写脚本前先读各 `yandex-*-<id>.outline.txt`(或 stdout 摘要里的 outline),再脚本读 `.json`。
|
|
95
132
|
- `search` / `campaigns` / `keywords` / `geo` / `devices` 在 Direct 侧过滤 `AdNetworkType=SEARCH`。
|
|
96
|
-
-
|
|
133
|
+
- Campaign「学习期」、搜索词「分类」无接口字段、也无法由现有指标算出,报告不展示这两列。关键词中文翻译可由 Agent 通过 `narrative.rowInsights` 提供(见上方「逐行洞察」)。
|
|
97
134
|
|
|
98
135
|
---
|
|
99
136
|
|
|
@@ -89,7 +89,7 @@ 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
93
|
| `campaignId` 写成数字 | `String(id)` + 文本格式 |
|
|
94
94
|
| search-terms 拉超过 180 天 | 缩短区间或排除该维 |
|
|
95
95
|
|