siluzan-tso-cli 1.1.45-beta.3 → 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 +80 -10
- package/dist/skill/_meta.json +2 -2
- 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 +353 -77
- 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
|
@@ -134342,15 +134342,19 @@ function asItems(payload) {
|
|
|
134342
134342
|
return items2.filter((r) => asRecord26(r) !== null);
|
|
134343
134343
|
}
|
|
134344
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;
|
|
134345
134349
|
const out = {
|
|
134346
|
-
spend
|
|
134347
|
-
impressions
|
|
134348
|
-
clicks
|
|
134349
|
-
|
|
134350
|
-
|
|
134351
|
-
|
|
134352
|
-
conversionRate: num10(row.conversionRate),
|
|
134353
|
-
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),
|
|
134354
134358
|
roi: num10(row.roi)
|
|
134355
134359
|
};
|
|
134356
134360
|
for (const f of extras) {
|
|
@@ -134627,6 +134631,39 @@ function asRecord27(value) {
|
|
|
134627
134631
|
function pushMissing6(missing, id, chapter, dimension, hint) {
|
|
134628
134632
|
missing.push({ id, chapter, dimension, hint });
|
|
134629
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
|
+
];
|
|
134630
134667
|
function validateYandexPeriodReportContent(data) {
|
|
134631
134668
|
const missing = [];
|
|
134632
134669
|
const meta = asRecord27(data.meta) ?? {};
|
|
@@ -134697,6 +134734,38 @@ function validateYandexPeriodReportContent(data) {
|
|
|
134697
134734
|
"\u81F3\u5C11\u4E00\u9879\u6709\u6548\u6570\u5B57\uFF08\u8BF7\u4F20 --snapshot-dir \u7531 CLI \u4ECE overview \u5408\u5E76\uFF09"
|
|
134698
134735
|
);
|
|
134699
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
|
+
}
|
|
134700
134769
|
return {
|
|
134701
134770
|
ok: missing.length === 0,
|
|
134702
134771
|
missing,
|
|
@@ -134704,7 +134773,8 @@ function validateYandexPeriodReportContent(data) {
|
|
|
134704
134773
|
executiveSummaryCount: executiveSummary.length,
|
|
134705
134774
|
recommendationCount: recommendations.length,
|
|
134706
134775
|
sectionAnalysisCounts,
|
|
134707
|
-
sectionSuggestionCounts
|
|
134776
|
+
sectionSuggestionCounts,
|
|
134777
|
+
rowInsightCoverage
|
|
134708
134778
|
}
|
|
134709
134779
|
};
|
|
134710
134780
|
}
|
|
@@ -134715,7 +134785,7 @@ function formatYandexPeriodReportErrors(result) {
|
|
|
134715
134785
|
`\u62A5\u544A JSON \u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 yandex-period-report.md\uFF09\uFF1A`,
|
|
134716
134786
|
...lines,
|
|
134717
134787
|
"",
|
|
134718
|
-
"\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",
|
|
134719
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"
|
|
134720
134790
|
].join("\n");
|
|
134721
134791
|
}
|
package/dist/skill/_meta.json
CHANGED
|
@@ -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
|
|
|
@@ -272,6 +272,39 @@
|
|
|
272
272
|
return n === undefined ? "—" : n.toFixed(2);
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/** 接口未回 CTR/CPC/转化率时,用展示/点击/消耗/转化补算(与 merge-snapshot 口径一致)。 */
|
|
276
|
+
function derivedCtr(r) {
|
|
277
|
+
const existing = toNum(r && r.ctr);
|
|
278
|
+
if (existing !== undefined) return existing;
|
|
279
|
+
const impressions = toNum(r && r.impressions);
|
|
280
|
+
const clicks = toNum(r && r.clicks);
|
|
281
|
+
return impressions > 0 && clicks !== undefined ? clicks / impressions : undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function derivedCpc(r) {
|
|
285
|
+
const existing = toNum(r && r.averageCpc);
|
|
286
|
+
if (existing !== undefined) return existing;
|
|
287
|
+
const spend = toNum(r && r.spend);
|
|
288
|
+
const clicks = toNum(r && r.clicks);
|
|
289
|
+
return clicks > 0 && spend !== undefined ? spend / clicks : undefined;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function derivedCr(r) {
|
|
293
|
+
const existing = toNum(r && r.conversionRate);
|
|
294
|
+
if (existing !== undefined) return existing;
|
|
295
|
+
const clicks = toNum(r && r.clicks);
|
|
296
|
+
const conversions = toNum(r && r.conversions);
|
|
297
|
+
return clicks > 0 && conversions !== undefined ? conversions / clicks : undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function derivedCpa(r) {
|
|
301
|
+
const existing = toNum(r && r.costPerConversion);
|
|
302
|
+
if (existing !== undefined) return existing;
|
|
303
|
+
const spend = toNum(r && r.spend);
|
|
304
|
+
const conversions = toNum(r && r.conversions);
|
|
305
|
+
return conversions > 0 && spend !== undefined ? spend / conversions : undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
275
308
|
function asArr(v) {
|
|
276
309
|
return Array.isArray(v) ? v : [];
|
|
277
310
|
}
|
|
@@ -343,39 +376,61 @@
|
|
|
343
376
|
.slice(0, n);
|
|
344
377
|
}
|
|
345
378
|
|
|
346
|
-
function
|
|
347
|
-
return (
|
|
348
|
-
'<td class="num">' +
|
|
349
|
-
money(r.spend, currency) +
|
|
350
|
-
'</td><td class="num">' +
|
|
351
|
-
int(r.impressions) +
|
|
352
|
-
'</td><td class="num">' +
|
|
353
|
-
int(r.clicks) +
|
|
354
|
-
'</td><td class="num">' +
|
|
355
|
-
pct(r.ctr) +
|
|
356
|
-
'</td><td class="num">' +
|
|
357
|
-
money(r.averageCpc, currency) +
|
|
358
|
-
'</td><td class="num">' +
|
|
359
|
-
num2(r.conversions) +
|
|
360
|
-
'</td><td class="num">' +
|
|
361
|
-
pct(r.conversionRate) +
|
|
362
|
-
'</td><td class="num">' +
|
|
363
|
-
money(r.costPerConversion, currency) +
|
|
364
|
-
"</td>"
|
|
365
|
-
);
|
|
379
|
+
function normKey(v) {
|
|
380
|
+
return String(v ?? "").trim().toLowerCase();
|
|
366
381
|
}
|
|
367
382
|
|
|
368
|
-
/**
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
383
|
+
/**
|
|
384
|
+
* 从 Agent 可选提供的 `narrative.rowInsights.<dim>[]` 里按主字段(如关键词原文/搜索词原文)
|
|
385
|
+
* 找匹配行的洞察(翻译/分类/问题/建议)。找不到返回 null——渲染层一律显示「—」,不编造内容。
|
|
386
|
+
* 每条洞察支持 `{match:{...}, ...}` 或扁平写法 `{keyword, campaignName, ...}`。
|
|
387
|
+
*/
|
|
388
|
+
function findInsight(list, primaryField, primaryValue, row) {
|
|
389
|
+
const target = normKey(primaryValue);
|
|
390
|
+
if (!target) return null;
|
|
391
|
+
const candidates = asArr(list).filter(function (it) {
|
|
392
|
+
const m = (it && it.match) || it || {};
|
|
393
|
+
return normKey(m[primaryField]) === target;
|
|
394
|
+
});
|
|
395
|
+
if (candidates.length <= 1) return candidates[0] || null;
|
|
396
|
+
const cn = normKey(row && row.campaignName);
|
|
397
|
+
const narrowed = candidates.find(function (it) {
|
|
398
|
+
const m = (it && it.match) || it || {};
|
|
399
|
+
return normKey(m.campaignName) === cn;
|
|
400
|
+
});
|
|
401
|
+
return narrowed || candidates[0];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function rowInsightsOf(data, key) {
|
|
405
|
+
return asArr(((data.narrative || {}).rowInsights || {})[key]);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* 「样本」徽标——纯阈值判断(基于点击量),不依赖大模型/不编造:
|
|
410
|
+
* 点击 <30 视为极小样本,效率类结论不可用;<100 转化类结论慎出;否则样本充足。
|
|
411
|
+
*/
|
|
412
|
+
function sampleBadge(clicks) {
|
|
413
|
+
const c = toNum(clicks) || 0;
|
|
414
|
+
if (c < 30) {
|
|
415
|
+
return '<span class="badge b-danger">点击 ' + int(c) + ",样本极小,效率类结论不可用</span>";
|
|
416
|
+
}
|
|
417
|
+
if (c < 100) {
|
|
418
|
+
return '<span class="badge b-warn">点击 ' + int(c) + ",转化类结论慎出</span>";
|
|
419
|
+
}
|
|
420
|
+
return '<span class="badge b-ok">点击 ' + int(c) + ",样本充足</span>";
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Campaign 状态徽标——只用真实 status/state 字段做颜色归类。 */
|
|
424
|
+
function campaignStatusBadge(r) {
|
|
425
|
+
const raw = r.status || r.state || "";
|
|
426
|
+
if (!raw) return '<span class="badge b-gray">—</span>';
|
|
427
|
+
const low = String(raw).toLowerCase();
|
|
428
|
+
let cls = "b-gray";
|
|
429
|
+
if (/(active|running|accept|enable)/.test(low)) cls = "b-ok";
|
|
430
|
+
else if (/(reject|declin|error)/.test(low)) cls = "b-danger";
|
|
431
|
+
else if (/(moderat|review|pending)/.test(low)) cls = "b-warn";
|
|
432
|
+
return '<span class="badge ' + cls + '">' + escapeHtml(String(raw)) + "</span>";
|
|
433
|
+
}
|
|
379
434
|
|
|
380
435
|
function renderHero(data) {
|
|
381
436
|
const meta = data.meta || {};
|
|
@@ -687,6 +742,10 @@
|
|
|
687
742
|
);
|
|
688
743
|
}
|
|
689
744
|
|
|
745
|
+
/**
|
|
746
|
+
* 通用维度表格:`opts.headers` / `opts.rowCells(r, currency, data)` 由调用方给出
|
|
747
|
+
* 完整列定义与单元格 HTML,逐维对齐开发样例的列顺序(而不是共用一套固定指标列)。
|
|
748
|
+
*/
|
|
690
749
|
function dimSection(opts) {
|
|
691
750
|
const data = opts.data;
|
|
692
751
|
const currency = currencyOf(data);
|
|
@@ -698,22 +757,10 @@
|
|
|
698
757
|
if (rows.length) {
|
|
699
758
|
const rowHtml = rows
|
|
700
759
|
.map(function (r) {
|
|
701
|
-
|
|
702
|
-
return (
|
|
703
|
-
"<tr><td>" +
|
|
704
|
-
escapeHtml(name) +
|
|
705
|
-
"</td>" +
|
|
706
|
-
(opts.extraCells ? opts.extraCells(r, currency) : "") +
|
|
707
|
-
metricCells(r, currency) +
|
|
708
|
-
"</tr>"
|
|
709
|
-
);
|
|
760
|
+
return "<tr>" + opts.rowCells(r, currency, data) + "</tr>";
|
|
710
761
|
})
|
|
711
762
|
.join("");
|
|
712
|
-
|
|
713
|
-
opts.extraHeaders || [],
|
|
714
|
-
METRIC_HEADERS
|
|
715
|
-
);
|
|
716
|
-
body = tableHtml(headers, rowHtml);
|
|
763
|
+
body = tableHtml(opts.headers, rowHtml);
|
|
717
764
|
} else {
|
|
718
765
|
body = '<div class="empty-note">' + EMPTY_NOTE + "</div>";
|
|
719
766
|
}
|
|
@@ -732,6 +779,11 @@
|
|
|
732
779
|
sectionHead(opts.title, desc) +
|
|
733
780
|
'<div class="panel">' +
|
|
734
781
|
body +
|
|
782
|
+
(opts.footnote
|
|
783
|
+
? '<div class="empty-note" style="text-align:left;padding:0 16px 14px;">' +
|
|
784
|
+
escapeHtml(opts.footnote) +
|
|
785
|
+
"</div>"
|
|
786
|
+
: "") +
|
|
735
787
|
analysisBlock(sec) +
|
|
736
788
|
"</div>" +
|
|
737
789
|
"</div></section>"
|
|
@@ -852,9 +904,42 @@
|
|
|
852
904
|
tableKey: "search",
|
|
853
905
|
title: "广告类型 · Search",
|
|
854
906
|
desc: "搜索网络(Search)投放汇总",
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
907
|
+
headers: [
|
|
908
|
+
{ label: "Network" },
|
|
909
|
+
{ label: "Spend", num: true },
|
|
910
|
+
{ label: "Impressions", num: true },
|
|
911
|
+
{ label: "Clicks", num: true },
|
|
912
|
+
{ label: "CPC", num: true },
|
|
913
|
+
{ label: "Conversions", num: true },
|
|
914
|
+
{ label: "CPA", num: true },
|
|
915
|
+
{ label: "ROI", num: true },
|
|
916
|
+
{ label: "CTR", num: true },
|
|
917
|
+
{ label: "样本" },
|
|
918
|
+
],
|
|
919
|
+
rowCells: function (r, currency) {
|
|
920
|
+
return (
|
|
921
|
+
"<td>" +
|
|
922
|
+
escapeHtml(r.network || "Search") +
|
|
923
|
+
'</td><td class="num">' +
|
|
924
|
+
money(r.spend, currency) +
|
|
925
|
+
'</td><td class="num">' +
|
|
926
|
+
int(r.impressions) +
|
|
927
|
+
'</td><td class="num">' +
|
|
928
|
+
int(r.clicks) +
|
|
929
|
+
'</td><td class="num">' +
|
|
930
|
+
money(r.averageCpc, currency) +
|
|
931
|
+
'</td><td class="num">' +
|
|
932
|
+
num2(r.conversions) +
|
|
933
|
+
'</td><td class="num">' +
|
|
934
|
+
money(r.costPerConversion, currency) +
|
|
935
|
+
'</td><td class="num">' +
|
|
936
|
+
pct(r.roi) +
|
|
937
|
+
'</td><td class="num">' +
|
|
938
|
+
pct(r.ctr) +
|
|
939
|
+
"</td><td>" +
|
|
940
|
+
sampleBadge(r.clicks) +
|
|
941
|
+
"</td>"
|
|
942
|
+
);
|
|
858
943
|
},
|
|
859
944
|
})
|
|
860
945
|
);
|
|
@@ -866,17 +951,55 @@
|
|
|
866
951
|
tableKey: "campaigns",
|
|
867
952
|
title: "Campaign",
|
|
868
953
|
desc: "按消耗排序的系列明细",
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
954
|
+
headers: [
|
|
955
|
+
{ label: "Campaign" },
|
|
956
|
+
{ label: "预算", num: true },
|
|
957
|
+
{ label: "战略" },
|
|
958
|
+
{ label: "目标CPA(API)", num: true },
|
|
959
|
+
{ label: "Spend", num: true },
|
|
960
|
+
{ label: "Impr", num: true },
|
|
961
|
+
{ label: "Clicks", num: true },
|
|
962
|
+
{ label: "CPC", num: true },
|
|
963
|
+
{ label: "Conv", num: true },
|
|
964
|
+
{ label: "CPA", num: true },
|
|
965
|
+
{ label: "状态" },
|
|
966
|
+
{ label: "问题" },
|
|
967
|
+
{ label: "建议" },
|
|
968
|
+
],
|
|
969
|
+
rowCells: function (r, currency, data) {
|
|
970
|
+
const insight = findInsight(
|
|
971
|
+
rowInsightsOf(data, "campaigns"),
|
|
972
|
+
"campaignName",
|
|
973
|
+
r.campaignName,
|
|
974
|
+
r
|
|
975
|
+
);
|
|
875
976
|
return (
|
|
876
977
|
"<td>" +
|
|
877
|
-
escapeHtml(r.
|
|
978
|
+
escapeHtml(r.campaignName || r.campaignId || "—") +
|
|
979
|
+
'</td><td class="num">' +
|
|
980
|
+
(r.budget != null ? money(r.budget, currency) : "—") +
|
|
981
|
+
"</td><td>" +
|
|
982
|
+
escapeHtml(r.strategy || "—") +
|
|
983
|
+
'</td><td class="num">' +
|
|
984
|
+
(r.targetCpa != null ? money(r.targetCpa, currency) : "—") +
|
|
985
|
+
'</td><td class="num">' +
|
|
986
|
+
money(r.spend, currency) +
|
|
987
|
+
'</td><td class="num">' +
|
|
988
|
+
int(r.impressions) +
|
|
989
|
+
'</td><td class="num">' +
|
|
990
|
+
int(r.clicks) +
|
|
991
|
+
'</td><td class="num">' +
|
|
992
|
+
money(r.averageCpc, currency) +
|
|
993
|
+
'</td><td class="num">' +
|
|
994
|
+
num2(r.conversions) +
|
|
995
|
+
'</td><td class="num">' +
|
|
996
|
+
money(r.costPerConversion, currency) +
|
|
997
|
+
"</td><td>" +
|
|
998
|
+
campaignStatusBadge(r) +
|
|
878
999
|
"</td><td>" +
|
|
879
|
-
escapeHtml(
|
|
1000
|
+
escapeHtml((insight && insight.issue) || "—") +
|
|
1001
|
+
"</td><td>" +
|
|
1002
|
+
escapeHtml((insight && insight.suggestion) || "—") +
|
|
880
1003
|
"</td>"
|
|
881
1004
|
);
|
|
882
1005
|
},
|
|
@@ -891,17 +1014,55 @@
|
|
|
891
1014
|
tableKey: "keywords",
|
|
892
1015
|
title: "关键词",
|
|
893
1016
|
desc: "按消耗排序的关键词明细",
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1017
|
+
footnote:
|
|
1018
|
+
"「中文翻译」「问题/建议」为 Agent 撰写报告时提供(narrative.rowInsights.keywords),未提供的行显示「—」,不做机械占位。",
|
|
1019
|
+
headers: [
|
|
1020
|
+
{ label: "Campaign" },
|
|
1021
|
+
{ label: "Group" },
|
|
1022
|
+
{ label: "Keyword" },
|
|
1023
|
+
{ label: "中文翻译" },
|
|
1024
|
+
{ label: "Impr", num: true },
|
|
1025
|
+
{ label: "Clicks", num: true },
|
|
1026
|
+
{ label: "Spend", num: true },
|
|
1027
|
+
{ label: "Conv", num: true },
|
|
1028
|
+
{ label: "CR%", num: true },
|
|
1029
|
+
{ label: "CPA", num: true },
|
|
1030
|
+
{ label: "CTR", num: true },
|
|
1031
|
+
{ label: "CPC", num: true },
|
|
1032
|
+
{ label: "问题" },
|
|
1033
|
+
{ label: "建议" },
|
|
1034
|
+
],
|
|
1035
|
+
rowCells: function (r, currency, data) {
|
|
1036
|
+
const insight = findInsight(rowInsightsOf(data, "keywords"), "keyword", r.keyword, r);
|
|
900
1037
|
return (
|
|
901
1038
|
"<td>" +
|
|
902
1039
|
escapeHtml(r.campaignName || "—") +
|
|
903
1040
|
"</td><td>" +
|
|
904
|
-
escapeHtml(r.
|
|
1041
|
+
escapeHtml(r.adGroupId != null ? String(r.adGroupId) : "—") +
|
|
1042
|
+
"</td><td>" +
|
|
1043
|
+
escapeHtml(r.keyword || "—") +
|
|
1044
|
+
"</td><td>" +
|
|
1045
|
+
escapeHtml((insight && insight.translation) || "—") +
|
|
1046
|
+
'</td><td class="num">' +
|
|
1047
|
+
int(r.impressions) +
|
|
1048
|
+
'</td><td class="num">' +
|
|
1049
|
+
int(r.clicks) +
|
|
1050
|
+
'</td><td class="num">' +
|
|
1051
|
+
money(r.spend, currency) +
|
|
1052
|
+
'</td><td class="num">' +
|
|
1053
|
+
num2(r.conversions) +
|
|
1054
|
+
'</td><td class="num">' +
|
|
1055
|
+
pct(derivedCr(r)) +
|
|
1056
|
+
'</td><td class="num">' +
|
|
1057
|
+
money(derivedCpa(r), currency) +
|
|
1058
|
+
'</td><td class="num">' +
|
|
1059
|
+
pct(derivedCtr(r)) +
|
|
1060
|
+
'</td><td class="num">' +
|
|
1061
|
+
money(derivedCpc(r), currency) +
|
|
1062
|
+
"</td><td>" +
|
|
1063
|
+
escapeHtml((insight && insight.issue) || "—") +
|
|
1064
|
+
"</td><td>" +
|
|
1065
|
+
escapeHtml((insight && insight.suggestion) || "—") +
|
|
905
1066
|
"</td>"
|
|
906
1067
|
);
|
|
907
1068
|
},
|
|
@@ -916,17 +1077,54 @@
|
|
|
916
1077
|
tableKey: "searchTerms",
|
|
917
1078
|
title: "搜索词",
|
|
918
1079
|
desc: "按消耗排序的搜索词明细(仅近 180 天数据)",
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1080
|
+
footnote:
|
|
1081
|
+
"「问题/建议」为 Agent 撰写报告时提供(narrative.rowInsights.searchTerms),未提供的行显示「—」,不做机械占位。",
|
|
1082
|
+
headers: [
|
|
1083
|
+
{ label: "Search Query" },
|
|
1084
|
+
{ label: "匹配关键词" },
|
|
1085
|
+
{ label: "Impr", num: true },
|
|
1086
|
+
{ label: "Clicks", num: true },
|
|
1087
|
+
{ label: "CTR", num: true },
|
|
1088
|
+
{ label: "CPC", num: true },
|
|
1089
|
+
{ label: "Spend", num: true },
|
|
1090
|
+
{ label: "Conv", num: true },
|
|
1091
|
+
{ label: "cr%", num: true },
|
|
1092
|
+
{ label: "CPA", num: true },
|
|
1093
|
+
{ label: "问题" },
|
|
1094
|
+
{ label: "建议" },
|
|
1095
|
+
],
|
|
1096
|
+
rowCells: function (r, currency, data) {
|
|
1097
|
+
const insight = findInsight(
|
|
1098
|
+
rowInsightsOf(data, "searchTerms"),
|
|
1099
|
+
"searchQuery",
|
|
1100
|
+
r.searchQuery,
|
|
1101
|
+
r
|
|
1102
|
+
);
|
|
925
1103
|
return (
|
|
926
1104
|
"<td>" +
|
|
1105
|
+
escapeHtml(r.searchQuery || "—") +
|
|
1106
|
+
"</td><td>" +
|
|
927
1107
|
escapeHtml(r.matchedKeyword || "—") +
|
|
1108
|
+
'</td><td class="num">' +
|
|
1109
|
+
int(r.impressions) +
|
|
1110
|
+
'</td><td class="num">' +
|
|
1111
|
+
int(r.clicks) +
|
|
1112
|
+
'</td><td class="num">' +
|
|
1113
|
+
pct(derivedCtr(r)) +
|
|
1114
|
+
'</td><td class="num">' +
|
|
1115
|
+
money(derivedCpc(r), currency) +
|
|
1116
|
+
'</td><td class="num">' +
|
|
1117
|
+
money(r.spend, currency) +
|
|
1118
|
+
'</td><td class="num">' +
|
|
1119
|
+
num2(r.conversions) +
|
|
1120
|
+
'</td><td class="num">' +
|
|
1121
|
+
pct(derivedCr(r)) +
|
|
1122
|
+
'</td><td class="num">' +
|
|
1123
|
+
money(derivedCpa(r), currency) +
|
|
928
1124
|
"</td><td>" +
|
|
929
|
-
escapeHtml(
|
|
1125
|
+
escapeHtml((insight && insight.issue) || "—") +
|
|
1126
|
+
"</td><td>" +
|
|
1127
|
+
escapeHtml((insight && insight.suggestion) || "—") +
|
|
930
1128
|
"</td>"
|
|
931
1129
|
);
|
|
932
1130
|
},
|
|
@@ -941,14 +1139,55 @@
|
|
|
941
1139
|
tableKey: "geo",
|
|
942
1140
|
title: "地域",
|
|
943
1141
|
desc: "按消耗排序的地域明细",
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1142
|
+
footnote: "「国家」取自账户投放地区(meta.region);「City」为 Yandex 返回的地域名称。",
|
|
1143
|
+
headers: [
|
|
1144
|
+
{ label: "国家" },
|
|
1145
|
+
{ label: "City" },
|
|
1146
|
+
{ label: "Spend", num: true },
|
|
1147
|
+
{ label: "Clicks", num: true },
|
|
1148
|
+
{ label: "Impr", num: true },
|
|
1149
|
+
{ label: "CTR", num: true },
|
|
1150
|
+
{ label: "CPC", num: true },
|
|
1151
|
+
{ label: "Conv", num: true },
|
|
1152
|
+
{ label: "cr%", num: true },
|
|
1153
|
+
{ label: "CPA", num: true },
|
|
1154
|
+
{ label: "问题" },
|
|
1155
|
+
{ label: "建议" },
|
|
1156
|
+
],
|
|
1157
|
+
rowCells: function (r, currency, data) {
|
|
1158
|
+
const cityName =
|
|
947
1159
|
r.locationName ||
|
|
948
1160
|
r.targetingLocationName ||
|
|
949
1161
|
r.location ||
|
|
950
1162
|
r.name ||
|
|
951
|
-
(r.locationId != null ? "地域 " + r.locationId : "—")
|
|
1163
|
+
(r.locationId != null ? "地域 " + r.locationId : "—");
|
|
1164
|
+
const insight = findInsight(rowInsightsOf(data, "geo"), "locationName", cityName, r);
|
|
1165
|
+
return (
|
|
1166
|
+
"<td>" +
|
|
1167
|
+
escapeHtml((data.meta && data.meta.region) || "—") +
|
|
1168
|
+
"</td><td>" +
|
|
1169
|
+
escapeHtml(cityName) +
|
|
1170
|
+
'</td><td class="num">' +
|
|
1171
|
+
money(r.spend, currency) +
|
|
1172
|
+
'</td><td class="num">' +
|
|
1173
|
+
int(r.clicks) +
|
|
1174
|
+
'</td><td class="num">' +
|
|
1175
|
+
int(r.impressions) +
|
|
1176
|
+
'</td><td class="num">' +
|
|
1177
|
+
pct(derivedCtr(r)) +
|
|
1178
|
+
'</td><td class="num">' +
|
|
1179
|
+
money(derivedCpc(r), currency) +
|
|
1180
|
+
'</td><td class="num">' +
|
|
1181
|
+
num2(r.conversions) +
|
|
1182
|
+
'</td><td class="num">' +
|
|
1183
|
+
pct(derivedCr(r)) +
|
|
1184
|
+
'</td><td class="num">' +
|
|
1185
|
+
money(derivedCpa(r), currency) +
|
|
1186
|
+
"</td><td>" +
|
|
1187
|
+
escapeHtml((insight && insight.issue) || "—") +
|
|
1188
|
+
"</td><td>" +
|
|
1189
|
+
escapeHtml((insight && insight.suggestion) || "—") +
|
|
1190
|
+
"</td>"
|
|
952
1191
|
);
|
|
953
1192
|
},
|
|
954
1193
|
limit: 30,
|
|
@@ -962,9 +1201,46 @@
|
|
|
962
1201
|
tableKey: "devices",
|
|
963
1202
|
title: "设备",
|
|
964
1203
|
desc: "按消耗排序的设备明细",
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1204
|
+
headers: [
|
|
1205
|
+
{ label: "Device" },
|
|
1206
|
+
{ label: "Spend", num: true },
|
|
1207
|
+
{ label: "impr", num: true },
|
|
1208
|
+
{ label: "Clicks", num: true },
|
|
1209
|
+
{ label: "ctr", num: true },
|
|
1210
|
+
{ label: "CPC", num: true },
|
|
1211
|
+
{ label: "Conv", num: true },
|
|
1212
|
+
{ label: "cr%", num: true },
|
|
1213
|
+
{ label: "CPA", num: true },
|
|
1214
|
+
{ label: "问题" },
|
|
1215
|
+
{ label: "建议" },
|
|
1216
|
+
],
|
|
1217
|
+
rowCells: function (r, currency, data) {
|
|
1218
|
+
const insight = findInsight(rowInsightsOf(data, "devices"), "device", r.device, r);
|
|
1219
|
+
return (
|
|
1220
|
+
"<td>" +
|
|
1221
|
+
escapeHtml(r.device || "—") +
|
|
1222
|
+
'</td><td class="num">' +
|
|
1223
|
+
money(r.spend, currency) +
|
|
1224
|
+
'</td><td class="num">' +
|
|
1225
|
+
int(r.impressions) +
|
|
1226
|
+
'</td><td class="num">' +
|
|
1227
|
+
int(r.clicks) +
|
|
1228
|
+
'</td><td class="num">' +
|
|
1229
|
+
pct(derivedCtr(r)) +
|
|
1230
|
+
'</td><td class="num">' +
|
|
1231
|
+
money(derivedCpc(r), currency) +
|
|
1232
|
+
'</td><td class="num">' +
|
|
1233
|
+
num2(r.conversions) +
|
|
1234
|
+
'</td><td class="num">' +
|
|
1235
|
+
pct(derivedCr(r)) +
|
|
1236
|
+
'</td><td class="num">' +
|
|
1237
|
+
money(derivedCpa(r), currency) +
|
|
1238
|
+
"</td><td>" +
|
|
1239
|
+
escapeHtml((insight && insight.issue) || "—") +
|
|
1240
|
+
"</td><td>" +
|
|
1241
|
+
escapeHtml((insight && insight.suggestion) || "—") +
|
|
1242
|
+
"</td>"
|
|
1243
|
+
);
|
|
968
1244
|
},
|
|
969
1245
|
limit: 20,
|
|
970
1246
|
})
|
|
@@ -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
|
|
|
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
$PKG_NAME = 'siluzan-tso-cli'
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
$PKG_VERSION = '1.1.45-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.45-beta.4'
|
|
13
13
|
$CLI_BIN = 'siluzan-tso'
|
|
14
14
|
$SKILL_LABEL = 'Siluzan TSO'
|
|
15
15
|
$INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
|
|
@@ -9,7 +9,7 @@ set -euo pipefail
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
readonly PKG_NAME="siluzan-tso-cli"
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
readonly PKG_VERSION="1.1.45-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.45-beta.4"
|
|
13
13
|
readonly CLI_BIN="siluzan-tso"
|
|
14
14
|
readonly SKILL_LABEL="Siluzan TSO"
|
|
15
15
|
readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
|