siluzan-tso-cli 1.1.29-beta.4 → 1.1.29-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 +415 -71
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/meta-period-report-rules.md +5 -4
- package/dist/skill/references/accounts/accounts.md +22 -9
- package/dist/skill/report-templates/meta-period-report.html +2 -2
- package/dist/skill/report-templates/meta-period-report.md +2 -2
- 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.29-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.29-beta.5),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -258,9 +258,9 @@ var require_semver = __commonJS({
|
|
|
258
258
|
} else {
|
|
259
259
|
this.prerelease = m[4].split(".").map((id) => {
|
|
260
260
|
if (/^[0-9]+$/.test(id)) {
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
263
|
-
return
|
|
261
|
+
const num4 = +id;
|
|
262
|
+
if (num4 >= 0 && num4 < MAX_SAFE_INTEGER) {
|
|
263
|
+
return num4;
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
return id;
|
|
@@ -123395,13 +123395,297 @@ import { fileURLToPath as fileURLToPath5 } from "url";
|
|
|
123395
123395
|
// src/commands/facebook-analysis/merge-snapshot.ts
|
|
123396
123396
|
import * as fs16 from "fs/promises";
|
|
123397
123397
|
import * as path23 from "path";
|
|
123398
|
+
|
|
123399
|
+
// src/commands/facebook-analysis/lifecycle-autofill.ts
|
|
123400
|
+
var LIFECYCLE_PHASES = ["test-market", "find-winner", "scale"];
|
|
123401
|
+
var PHASE_LABEL = {
|
|
123402
|
+
"test-market": "\u6D4B\u5E02\u573A",
|
|
123403
|
+
"find-winner": "\u627E\u8D62\u5BB6",
|
|
123404
|
+
scale: "\u653E\u91CF"
|
|
123405
|
+
};
|
|
123406
|
+
function num(value) {
|
|
123407
|
+
const n = Number(value);
|
|
123408
|
+
return Number.isFinite(n) ? n : void 0;
|
|
123409
|
+
}
|
|
123410
|
+
function money(value, currency = "USD") {
|
|
123411
|
+
if (value == null) return "\u2014";
|
|
123412
|
+
const sym = currency === "USD" ? "$" : "";
|
|
123413
|
+
return `${sym}${value.toFixed(2)}`;
|
|
123414
|
+
}
|
|
123415
|
+
function adSetsWithSpend(tables) {
|
|
123416
|
+
const rows = Array.isArray(tables?.adSets) ? tables.adSets : [];
|
|
123417
|
+
return rows.filter((r) => (num(r.spend) ?? 0) > 0);
|
|
123418
|
+
}
|
|
123419
|
+
function topAdSetConcentration(adSets) {
|
|
123420
|
+
if (!adSets.length) return null;
|
|
123421
|
+
const totalSpend = adSets.reduce((s, r) => s + (num(r.spend) ?? 0), 0);
|
|
123422
|
+
const totalResults = adSets.reduce((s, r) => s + (num(r.results) ?? 0), 0);
|
|
123423
|
+
let best = adSets[0];
|
|
123424
|
+
let bestSpend = num(best.spend) ?? 0;
|
|
123425
|
+
for (const r of adSets) {
|
|
123426
|
+
const sp = num(r.spend) ?? 0;
|
|
123427
|
+
if (sp > bestSpend) {
|
|
123428
|
+
best = r;
|
|
123429
|
+
bestSpend = sp;
|
|
123430
|
+
}
|
|
123431
|
+
}
|
|
123432
|
+
const results = num(best.results) ?? 0;
|
|
123433
|
+
return {
|
|
123434
|
+
name: String(best.adGroupName ?? best.name ?? "\u2014"),
|
|
123435
|
+
spendShare: totalSpend > 0 ? bestSpend / totalSpend : 0,
|
|
123436
|
+
resultsShare: totalResults > 0 ? results / totalResults : 0,
|
|
123437
|
+
cpl: num(best.costPerResult)
|
|
123438
|
+
};
|
|
123439
|
+
}
|
|
123440
|
+
function winnerSpendShare(adSets, avgCpl, totalSpend) {
|
|
123441
|
+
if (totalSpend <= 0 || avgCpl <= 0) return 0;
|
|
123442
|
+
const winnerSpend = adSets.filter((r) => {
|
|
123443
|
+
const cpl = num(r.costPerResult);
|
|
123444
|
+
return cpl != null && cpl <= avgCpl * 0.9;
|
|
123445
|
+
}).reduce((s, r) => s + (num(r.spend) ?? 0), 0);
|
|
123446
|
+
return winnerSpend / totalSpend;
|
|
123447
|
+
}
|
|
123448
|
+
function countActiveDimensions(charts, tables) {
|
|
123449
|
+
let n = 0;
|
|
123450
|
+
const platform = charts?.platform;
|
|
123451
|
+
if (asArray(platform?.labels).length > 0) n += 1;
|
|
123452
|
+
const country = charts?.country;
|
|
123453
|
+
if (asArray(country?.labels).length > 0) n += 1;
|
|
123454
|
+
n += adSetsWithSpend(tables).length;
|
|
123455
|
+
return n;
|
|
123456
|
+
}
|
|
123457
|
+
function asArray(value) {
|
|
123458
|
+
return Array.isArray(value) ? value : [];
|
|
123459
|
+
}
|
|
123460
|
+
function inferLifecyclePhase(input) {
|
|
123461
|
+
const { avgCpl, currency = "USD" } = input;
|
|
123462
|
+
const spend = num(input.spend) ?? 0;
|
|
123463
|
+
const adSets = adSetsWithSpend(input.tables);
|
|
123464
|
+
const activeDims = countActiveDimensions(input.charts, input.tables);
|
|
123465
|
+
const winShare = winnerSpendShare(adSets, avgCpl, spend);
|
|
123466
|
+
const top = topAdSetConcentration(adSets);
|
|
123467
|
+
const reasons = [];
|
|
123468
|
+
reasons.push(`\u603B\u82B1\u8D39 ${money(spend, currency)}\uFF0C\u6D3B\u8DC3\u6295\u653E\u7EF4\u5EA6\u7EA6 ${activeDims} \u4E2A`);
|
|
123469
|
+
if (winShare > 0) reasons.push(`CPL \u4F18\u4E8E\u5747\u503C\u7684\u7EF4\u5EA6\u82B1\u8D39\u5360\u6BD4\u7EA6 ${Math.round(winShare * 100)}%`);
|
|
123470
|
+
if (top) {
|
|
123471
|
+
reasons.push(
|
|
123472
|
+
`Top \u5E7F\u544A\u7EC4\u300C${top.name}\u300D\u82B1\u8D39\u5360\u6BD4 ${Math.round(top.spendShare * 100)}%\u3001CPL ${money(top.cpl, currency)}`
|
|
123473
|
+
);
|
|
123474
|
+
}
|
|
123475
|
+
let phase = "find-winner";
|
|
123476
|
+
const lowSpendProbe = spend > 0 && spend < 200;
|
|
123477
|
+
const dispersedNoWinner = activeDims >= 4 && winShare < 0.35;
|
|
123478
|
+
const concentratedWinner = top != null && (top.spendShare >= 0.55 || top.resultsShare >= 0.55) && top.cpl != null && avgCpl > 0 && top.cpl <= avgCpl * 1.05;
|
|
123479
|
+
if (lowSpendProbe || dispersedNoWinner) {
|
|
123480
|
+
phase = "test-market";
|
|
123481
|
+
if (lowSpendProbe) reasons.push("\u603B\u82B1\u8D39\u4ECD\u5904\u4E8E\u8BD5\u63A2\u533A\u95F4");
|
|
123482
|
+
if (dispersedNoWinner) reasons.push("\u591A\u56FD/\u591A\u7EC4\u5206\u6563\u4E14\u5C1A\u672A\u5F62\u6210\u7A33\u5B9A\u8D62\u5BB6");
|
|
123483
|
+
} else if (concentratedWinner) {
|
|
123484
|
+
phase = "scale";
|
|
123485
|
+
reasons.push("\u9884\u7B97\u5DF2\u660E\u663E\u5411\u9AD8\u6548\u5E7F\u544A\u7EC4\u96C6\u4E2D\uFF0C\u9002\u5408\u653E\u91CF");
|
|
123486
|
+
} else {
|
|
123487
|
+
phase = "find-winner";
|
|
123488
|
+
reasons.push("\u5DF2\u51FA\u73B0\u4F18\u52A3\u5206\u5316\uFF0C\u4F46\u9884\u7B97\u5C1A\u672A\u9AD8\u5EA6\u96C6\u4E2D");
|
|
123489
|
+
}
|
|
123490
|
+
const label = PHASE_LABEL[phase];
|
|
123491
|
+
const verdict = `${reasons.join("\uFF1B")}\u3002\u7EFC\u5408\u5224\u65AD\u8D26\u6237\u5904\u4E8E\u300C${label}\u300D\u9636\u6BB5\uFF0C\u5EFA\u8BAE\u6309\u8BE5\u9636\u6BB5\u7B56\u7565\u8C03\u6574\u9884\u7B97\u4E0E\u7ED3\u6784\u3002`;
|
|
123492
|
+
return { phase, verdict, reasons };
|
|
123493
|
+
}
|
|
123494
|
+
function textLen(value) {
|
|
123495
|
+
return typeof value === "string" ? value.trim().length : 0;
|
|
123496
|
+
}
|
|
123497
|
+
function isValidPhase(value) {
|
|
123498
|
+
return typeof value === "string" && LIFECYCLE_PHASES.includes(value);
|
|
123499
|
+
}
|
|
123500
|
+
function ensureLifecycleFromSnapshot(healthDiagnosis, merged) {
|
|
123501
|
+
const hd = { ...healthDiagnosis ?? {} };
|
|
123502
|
+
const inferred = inferLifecyclePhase(merged);
|
|
123503
|
+
hd.lifecyclePhaseInferred = inferred.phase;
|
|
123504
|
+
const agentPhase = hd.lifecyclePhase;
|
|
123505
|
+
const agentVerdict = hd.lifecycleVerdict;
|
|
123506
|
+
if (!isValidPhase(agentPhase)) {
|
|
123507
|
+
hd.lifecyclePhase = inferred.phase;
|
|
123508
|
+
hd.lifecyclePhaseAutofilled = true;
|
|
123509
|
+
if (textLen(agentVerdict) < 60) {
|
|
123510
|
+
hd.lifecycleVerdict = inferred.verdict;
|
|
123511
|
+
hd.lifecycleVerdictAutofilled = true;
|
|
123512
|
+
}
|
|
123513
|
+
} else if (textLen(agentVerdict) < 60) {
|
|
123514
|
+
hd.lifecycleVerdict = inferred.verdict;
|
|
123515
|
+
hd.lifecycleVerdictAutofilled = true;
|
|
123516
|
+
}
|
|
123517
|
+
return hd;
|
|
123518
|
+
}
|
|
123519
|
+
|
|
123520
|
+
// src/commands/facebook-analysis/scorecard-autofill.ts
|
|
123521
|
+
function num2(value) {
|
|
123522
|
+
const n = Number(value);
|
|
123523
|
+
return Number.isFinite(n) ? n : void 0;
|
|
123524
|
+
}
|
|
123525
|
+
function money2(value, currency = "USD") {
|
|
123526
|
+
if (value == null) return "\u2014";
|
|
123527
|
+
const sym = currency === "USD" ? "$" : "";
|
|
123528
|
+
return `${sym}${value.toFixed(2)}`;
|
|
123529
|
+
}
|
|
123530
|
+
function cplSignal(cpl, avg) {
|
|
123531
|
+
if (cpl == null || avg <= 0) {
|
|
123532
|
+
return { signal: "yellow", signalLabel: "\u89C2", advice: "\u6837\u672C\u4E0D\u8DB3\uFF0C\u89C2\u5BDF 7 \u5929" };
|
|
123533
|
+
}
|
|
123534
|
+
if (cpl <= avg * 0.9) {
|
|
123535
|
+
return { signal: "green", signalLabel: "\u52A0", advice: "\u52A0\u7801\u9884\u7B97\u6216\u6269\u91CF" };
|
|
123536
|
+
}
|
|
123537
|
+
if (cpl >= avg * 1.15) {
|
|
123538
|
+
return { signal: "red", signalLabel: "\u51CF", advice: "\u9650\u6D41\u3001\u51CF\u9884\u7B97\u6216\u6682\u505C" };
|
|
123539
|
+
}
|
|
123540
|
+
return { signal: "yellow", signalLabel: "\u89C2", advice: "\u7EF4\u6301\u89C2\u5BDF\uFF0C7 \u5929\u540E\u590D\u76D8" };
|
|
123541
|
+
}
|
|
123542
|
+
function pushRow(rows, seen, row) {
|
|
123543
|
+
const key = row.item.trim();
|
|
123544
|
+
if (!key || seen.has(key)) return;
|
|
123545
|
+
seen.add(key);
|
|
123546
|
+
rows.push(row);
|
|
123547
|
+
}
|
|
123548
|
+
function buildScorecardFromMergedData(input) {
|
|
123549
|
+
const { avgCpl, currency = "USD" } = input;
|
|
123550
|
+
const charts = input.charts ?? {};
|
|
123551
|
+
const tables = input.tables ?? {};
|
|
123552
|
+
const rows = [];
|
|
123553
|
+
const seen = /* @__PURE__ */ new Set();
|
|
123554
|
+
const platform = charts.platform;
|
|
123555
|
+
const pLabels = Array.isArray(platform?.labels) ? platform.labels : [];
|
|
123556
|
+
const pCpl = Array.isArray(platform?.cpl) ? platform.cpl : [];
|
|
123557
|
+
const pSpend = Array.isArray(platform?.spend) ? platform.spend : [];
|
|
123558
|
+
if (pLabels.length) {
|
|
123559
|
+
const indexed = pLabels.map((label, i) => ({
|
|
123560
|
+
label: String(label),
|
|
123561
|
+
cpl: num2(pCpl[i]),
|
|
123562
|
+
spend: num2(pSpend[i]) ?? 0
|
|
123563
|
+
})).filter((x) => x.spend > 0 || x.cpl != null && x.cpl > 0);
|
|
123564
|
+
const sorted = [...indexed].sort((a, b) => (a.cpl ?? Infinity) - (b.cpl ?? Infinity));
|
|
123565
|
+
if (sorted[0]) {
|
|
123566
|
+
const s = cplSignal(sorted[0].cpl, avgCpl);
|
|
123567
|
+
pushRow(rows, seen, {
|
|
123568
|
+
item: `\u5E73\u53F0\xB7${sorted[0].label}`,
|
|
123569
|
+
data: `CPL ${money2(sorted[0].cpl, currency)}`,
|
|
123570
|
+
...s
|
|
123571
|
+
});
|
|
123572
|
+
}
|
|
123573
|
+
if (sorted.length > 1) {
|
|
123574
|
+
const worst = sorted[sorted.length - 1];
|
|
123575
|
+
const s = cplSignal(worst.cpl, avgCpl);
|
|
123576
|
+
pushRow(rows, seen, {
|
|
123577
|
+
item: `\u5E73\u53F0\xB7${worst.label}`,
|
|
123578
|
+
data: `CPL ${money2(worst.cpl, currency)}`,
|
|
123579
|
+
...s
|
|
123580
|
+
});
|
|
123581
|
+
}
|
|
123582
|
+
}
|
|
123583
|
+
const country = charts.country;
|
|
123584
|
+
const cLabels = Array.isArray(country?.labels) ? country.labels : [];
|
|
123585
|
+
const cCpl = Array.isArray(country?.cpl) ? country.cpl : [];
|
|
123586
|
+
if (cLabels.length) {
|
|
123587
|
+
const indexed = cLabels.map((label, i) => ({ label: String(label), cpl: num2(cCpl[i]) })).filter((x) => x.cpl != null && x.cpl > 0);
|
|
123588
|
+
const sorted = [...indexed].sort((a, b) => (a.cpl ?? 0) - (b.cpl ?? 0));
|
|
123589
|
+
if (sorted[0]) {
|
|
123590
|
+
const s = cplSignal(sorted[0].cpl, avgCpl);
|
|
123591
|
+
pushRow(rows, seen, {
|
|
123592
|
+
item: String(sorted[0].label),
|
|
123593
|
+
data: `CPL ${money2(sorted[0].cpl, currency)}`,
|
|
123594
|
+
...s
|
|
123595
|
+
});
|
|
123596
|
+
}
|
|
123597
|
+
if (sorted.length > 1) {
|
|
123598
|
+
const worst = sorted[sorted.length - 1];
|
|
123599
|
+
const s = cplSignal(worst.cpl, avgCpl);
|
|
123600
|
+
pushRow(rows, seen, {
|
|
123601
|
+
item: String(worst.label),
|
|
123602
|
+
data: `CPL ${money2(worst.cpl, currency)}`,
|
|
123603
|
+
...s
|
|
123604
|
+
});
|
|
123605
|
+
}
|
|
123606
|
+
}
|
|
123607
|
+
const adSets = Array.isArray(tables.adSets) ? tables.adSets : [];
|
|
123608
|
+
const adWithSpend = adSets.filter((g) => (num2(g.spend) ?? 0) > 0);
|
|
123609
|
+
if (adWithSpend.length) {
|
|
123610
|
+
const sorted = [...adWithSpend].sort(
|
|
123611
|
+
(a, b) => (num2(a.costPerResult) ?? Infinity) - (num2(b.costPerResult) ?? Infinity)
|
|
123612
|
+
);
|
|
123613
|
+
const best = sorted[0];
|
|
123614
|
+
const sBest = cplSignal(num2(best.costPerResult), avgCpl);
|
|
123615
|
+
pushRow(rows, seen, {
|
|
123616
|
+
item: String(best.name ?? "\u5E7F\u544A\u7EC4"),
|
|
123617
|
+
data: `CPL ${money2(num2(best.costPerResult), currency)}`,
|
|
123618
|
+
...sBest
|
|
123619
|
+
});
|
|
123620
|
+
if (sorted.length > 1) {
|
|
123621
|
+
const worst = sorted[sorted.length - 1];
|
|
123622
|
+
const sWorst = cplSignal(num2(worst.costPerResult), avgCpl);
|
|
123623
|
+
pushRow(rows, seen, {
|
|
123624
|
+
item: String(worst.name ?? "\u5E7F\u544A\u7EC4"),
|
|
123625
|
+
data: `CPL ${money2(num2(worst.costPerResult), currency)}`,
|
|
123626
|
+
...sWorst
|
|
123627
|
+
});
|
|
123628
|
+
}
|
|
123629
|
+
}
|
|
123630
|
+
const topAud = Array.isArray(tables.audienceTop) ? tables.audienceTop : [];
|
|
123631
|
+
const bottomAud = Array.isArray(tables.audienceBottom) ? tables.audienceBottom : [];
|
|
123632
|
+
if (topAud[0]) {
|
|
123633
|
+
const s = cplSignal(num2(topAud[0].costPerResult), avgCpl);
|
|
123634
|
+
pushRow(rows, seen, {
|
|
123635
|
+
item: `\u53D7\u4F17\xB7${String(topAud[0].label ?? "Top")}`,
|
|
123636
|
+
data: `CPL ${money2(num2(topAud[0].costPerResult), currency)}`,
|
|
123637
|
+
...s
|
|
123638
|
+
});
|
|
123639
|
+
}
|
|
123640
|
+
if (bottomAud[0]) {
|
|
123641
|
+
const s = cplSignal(num2(bottomAud[0].costPerResult), avgCpl);
|
|
123642
|
+
pushRow(rows, seen, {
|
|
123643
|
+
item: `\u53D7\u4F17\xB7${String(bottomAud[0].label ?? "Bottom")}`,
|
|
123644
|
+
data: `CPL ${money2(num2(bottomAud[0].costPerResult), currency)}`,
|
|
123645
|
+
...s
|
|
123646
|
+
});
|
|
123647
|
+
}
|
|
123648
|
+
if (rows.length < 6 && avgCpl > 0) {
|
|
123649
|
+
pushRow(rows, seen, {
|
|
123650
|
+
item: "\u8D26\u6237\u5747\u503C",
|
|
123651
|
+
data: `CPL ${money2(avgCpl, currency)}`,
|
|
123652
|
+
signal: "yellow",
|
|
123653
|
+
signalLabel: "\u89C2",
|
|
123654
|
+
advice: "\u4EE5\u8D26\u6237 CPL \u4E3A\u57FA\u51C6\u5BF9\u6BD4\u5404\u7EF4\u5EA6"
|
|
123655
|
+
});
|
|
123656
|
+
}
|
|
123657
|
+
return rows;
|
|
123658
|
+
}
|
|
123659
|
+
var MIN_SCORECARD_ROWS = 6;
|
|
123660
|
+
function ensureScorecardFromSnapshot(healthDiagnosis, merged) {
|
|
123661
|
+
const hd = { ...healthDiagnosis ?? {} };
|
|
123662
|
+
const existing = Array.isArray(hd.scorecard) ? hd.scorecard : [];
|
|
123663
|
+
if (existing.length >= MIN_SCORECARD_ROWS) return hd;
|
|
123664
|
+
const auto = buildScorecardFromMergedData(merged);
|
|
123665
|
+
const seen = new Set(existing.map((r) => String(r.item ?? "").trim()).filter(Boolean));
|
|
123666
|
+
const combined = [...existing];
|
|
123667
|
+
for (const row of auto) {
|
|
123668
|
+
if (combined.length >= MIN_SCORECARD_ROWS) break;
|
|
123669
|
+
const key = row.item.trim();
|
|
123670
|
+
if (!key || seen.has(key)) continue;
|
|
123671
|
+
seen.add(key);
|
|
123672
|
+
combined.push(row);
|
|
123673
|
+
}
|
|
123674
|
+
hd.scorecard = combined;
|
|
123675
|
+
if (combined.length > 0) {
|
|
123676
|
+
hd.scorecardAutofilled = existing.length < MIN_SCORECARD_ROWS;
|
|
123677
|
+
}
|
|
123678
|
+
return hd;
|
|
123679
|
+
}
|
|
123680
|
+
|
|
123681
|
+
// src/commands/facebook-analysis/merge-snapshot.ts
|
|
123398
123682
|
function asRecord5(value) {
|
|
123399
123683
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
123400
123684
|
return value;
|
|
123401
123685
|
}
|
|
123402
123686
|
return null;
|
|
123403
123687
|
}
|
|
123404
|
-
function
|
|
123688
|
+
function num3(value) {
|
|
123405
123689
|
const n = Number(value);
|
|
123406
123690
|
return Number.isFinite(n) ? n : void 0;
|
|
123407
123691
|
}
|
|
@@ -123453,8 +123737,8 @@ function aggregatePlatform(networks) {
|
|
|
123453
123737
|
const platform = String(row.publisherPlatform ?? row.network ?? "unknown");
|
|
123454
123738
|
const prev = map.get(platform) ?? { spend: 0, results: 0 };
|
|
123455
123739
|
map.set(platform, {
|
|
123456
|
-
spend: prev.spend + (
|
|
123457
|
-
results: prev.results + (
|
|
123740
|
+
spend: prev.spend + (num3(row.spend) ?? 0),
|
|
123741
|
+
results: prev.results + (num3(row.results) ?? 0)
|
|
123458
123742
|
});
|
|
123459
123743
|
}
|
|
123460
123744
|
const labels = [];
|
|
@@ -123470,10 +123754,10 @@ function aggregatePlatform(networks) {
|
|
|
123470
123754
|
function buildAudienceRows(audiences) {
|
|
123471
123755
|
return audiences.map((a) => ({
|
|
123472
123756
|
label: audienceLabel(a.age, a.gender),
|
|
123473
|
-
spend:
|
|
123474
|
-
results:
|
|
123475
|
-
costPerResult:
|
|
123476
|
-
frequency:
|
|
123757
|
+
spend: num3(a.spend) ?? 0,
|
|
123758
|
+
results: num3(a.results) ?? 0,
|
|
123759
|
+
costPerResult: num3(a.costPerResult) ?? (num3(a.results) ? (num3(a.spend) ?? 0) / (num3(a.results) ?? 1) : 0),
|
|
123760
|
+
frequency: num3(a.frequency)
|
|
123477
123761
|
})).filter((r) => r.spend > 0 || r.results > 0);
|
|
123478
123762
|
}
|
|
123479
123763
|
async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
@@ -123510,7 +123794,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
123510
123794
|
}
|
|
123511
123795
|
if (!meta.resultType && current?.resultType) meta.resultType = String(current.resultType);
|
|
123512
123796
|
const charts = { ...payload.charts ?? {} };
|
|
123513
|
-
const avgCpl =
|
|
123797
|
+
const avgCpl = num3(kpis.costPerResult) ?? 0;
|
|
123514
123798
|
if (!charts.platform) {
|
|
123515
123799
|
const platform = sectionMap.get("platform");
|
|
123516
123800
|
const networks = Array.isArray(platform?.networks) ? platform.networks : [];
|
|
@@ -123520,10 +123804,10 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
123520
123804
|
const country = sectionMap.get("country");
|
|
123521
123805
|
const countries = Array.isArray(country?.countries) ? country.countries : [];
|
|
123522
123806
|
if (countries.length) {
|
|
123523
|
-
const sorted = [...countries].sort((a, b) => (
|
|
123807
|
+
const sorted = [...countries].sort((a, b) => (num3(b.spend) ?? 0) - (num3(a.spend) ?? 0));
|
|
123524
123808
|
charts.country = {
|
|
123525
123809
|
labels: sorted.map((c) => String(c.countryOrRegion ?? "\u2014")),
|
|
123526
|
-
cpl: sorted.map((c) =>
|
|
123810
|
+
cpl: sorted.map((c) => num3(c.costPerResult) ?? 0)
|
|
123527
123811
|
};
|
|
123528
123812
|
}
|
|
123529
123813
|
}
|
|
@@ -123539,7 +123823,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
123539
123823
|
};
|
|
123540
123824
|
}
|
|
123541
123825
|
}
|
|
123542
|
-
if (!charts.funnel &&
|
|
123826
|
+
if (!charts.funnel && num3(kpis.reach) != null) {
|
|
123543
123827
|
charts.funnel = { reach: kpis.reach, results: kpis.results ?? 0 };
|
|
123544
123828
|
}
|
|
123545
123829
|
const tables = { ...payload.tables ?? {} };
|
|
@@ -123548,14 +123832,14 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
123548
123832
|
const groups = Array.isArray(adSets?.adGroups) ? adSets.adGroups : [];
|
|
123549
123833
|
if (groups.length) {
|
|
123550
123834
|
tables.adSets = groups.map((g) => {
|
|
123551
|
-
const cpl =
|
|
123552
|
-
const freq =
|
|
123835
|
+
const cpl = num3(g.costPerResult);
|
|
123836
|
+
const freq = num3(g.frequency);
|
|
123553
123837
|
const fatigue = fatigueFromFrequency(freq);
|
|
123554
123838
|
const status = statusFromCpl(cpl, avgCpl);
|
|
123555
123839
|
return {
|
|
123556
123840
|
name: String(g.adGroupName ?? g.campaignName ?? "\u2014"),
|
|
123557
|
-
spend:
|
|
123558
|
-
results:
|
|
123841
|
+
spend: num3(g.spend) ?? 0,
|
|
123842
|
+
results: num3(g.results) ?? 0,
|
|
123559
123843
|
costPerResult: cpl ?? 0,
|
|
123560
123844
|
frequency: freq,
|
|
123561
123845
|
fatigueLevel: fatigue.level,
|
|
@@ -123577,25 +123861,37 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
123577
123861
|
if (!tables.audienceBottom) tables.audienceBottom = bottomN;
|
|
123578
123862
|
}
|
|
123579
123863
|
}
|
|
123864
|
+
const currency = typeof meta.currency === "string" ? meta.currency : "USD";
|
|
123865
|
+
const mergeCtx = {
|
|
123866
|
+
spend: num3(kpis.spend),
|
|
123867
|
+
avgCpl,
|
|
123868
|
+
currency,
|
|
123869
|
+
charts,
|
|
123870
|
+
tables
|
|
123871
|
+
};
|
|
123872
|
+
let healthDiagnosis = ensureScorecardFromSnapshot(payload.healthDiagnosis, mergeCtx);
|
|
123873
|
+
healthDiagnosis = ensureLifecycleFromSnapshot(healthDiagnosis, mergeCtx);
|
|
123580
123874
|
return {
|
|
123581
123875
|
...payload,
|
|
123582
123876
|
meta,
|
|
123583
123877
|
kpis,
|
|
123584
123878
|
charts,
|
|
123585
|
-
tables
|
|
123879
|
+
tables,
|
|
123880
|
+
healthDiagnosis
|
|
123586
123881
|
};
|
|
123587
123882
|
}
|
|
123588
123883
|
|
|
123589
123884
|
// src/commands/facebook-analysis/report-content.ts
|
|
123590
123885
|
var RECOMMENDATION_TITLES = ["\u7B80\u5316\u8868\u5355\u95EE\u9898", "\u533A\u57DF\u8C03\u6574", "\u9884\u7B97\u91CD\u6784", "\u7D20\u6750\u5EFA\u8BAE"];
|
|
123591
|
-
var
|
|
123592
|
-
|
|
123886
|
+
var SCORECARD_SIGNALS = ["green", "yellow", "red"];
|
|
123887
|
+
var SNAPSHOT_HEALTH_HINT = "\u53EF\u7701\u7565\u5BF9\u5E94\u5B57\u6BB5\uFF0Crender \u65F6\u52A0 --snapshot-dir \u7531 CLI \u4ECE\u5FEB\u7167\u81EA\u52A8\u8865\u5168\uFF1B\u6216\u6309 meta-period-report-rules.md \u624B\u5199";
|
|
123888
|
+
function textLen2(value) {
|
|
123593
123889
|
return typeof value === "string" ? value.trim().length : 0;
|
|
123594
123890
|
}
|
|
123595
123891
|
function isFiniteNumber(value) {
|
|
123596
123892
|
return typeof value === "number" && Number.isFinite(value);
|
|
123597
123893
|
}
|
|
123598
|
-
function
|
|
123894
|
+
function asArray2(value) {
|
|
123599
123895
|
return Array.isArray(value) ? value : [];
|
|
123600
123896
|
}
|
|
123601
123897
|
function asRecord6(value) {
|
|
@@ -123608,7 +123904,7 @@ function pushMissing(missing, id, chapter, dimension, hint) {
|
|
|
123608
123904
|
missing.push({ id, chapter, dimension, hint });
|
|
123609
123905
|
}
|
|
123610
123906
|
function countAdSetsWithSpend(tables) {
|
|
123611
|
-
const rows =
|
|
123907
|
+
const rows = asArray2(tables?.adSets);
|
|
123612
123908
|
return rows.filter((row) => {
|
|
123613
123909
|
const r = asRecord6(row);
|
|
123614
123910
|
const spend = r?.spend;
|
|
@@ -123617,7 +123913,7 @@ function countAdSetsWithSpend(tables) {
|
|
|
123617
123913
|
}
|
|
123618
123914
|
function chartLabels(chart) {
|
|
123619
123915
|
const c = asRecord6(chart);
|
|
123620
|
-
return
|
|
123916
|
+
return asArray2(c?.labels).length;
|
|
123621
123917
|
}
|
|
123622
123918
|
function validateMetaPeriodReportContent(data) {
|
|
123623
123919
|
const missing = [];
|
|
@@ -123630,15 +123926,15 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123630
123926
|
const health = asRecord6(data.healthDiagnosis);
|
|
123631
123927
|
const priorityPlan = asRecord6(data.priorityPlan);
|
|
123632
123928
|
const actionChecklist = asRecord6(data.actionChecklist);
|
|
123633
|
-
const
|
|
123634
|
-
const regionalNarratives =
|
|
123635
|
-
const recommendations =
|
|
123636
|
-
const supplementary =
|
|
123637
|
-
const executiveSummary =
|
|
123638
|
-
const fourQuestions =
|
|
123639
|
-
const scorecard =
|
|
123640
|
-
const abTests =
|
|
123641
|
-
if (!
|
|
123929
|
+
const adSetsWithSpend2 = countAdSetsWithSpend(tables ?? void 0);
|
|
123930
|
+
const regionalNarratives = asArray2(narrative.regional).length;
|
|
123931
|
+
const recommendations = asArray2(narrative.recommendations);
|
|
123932
|
+
const supplementary = asArray2(data.supplementaryRecommendations);
|
|
123933
|
+
const executiveSummary = asArray2(data.executiveSummary);
|
|
123934
|
+
const fourQuestions = asArray2(health?.fourQuestions);
|
|
123935
|
+
const scorecard = asArray2(health?.scorecard);
|
|
123936
|
+
const abTests = asArray2(data.abTests);
|
|
123937
|
+
if (!textLen2(meta.accountName) && !textLen2(meta.accountId)) {
|
|
123642
123938
|
pushMissing(
|
|
123643
123939
|
missing,
|
|
123644
123940
|
"meta-account",
|
|
@@ -123647,7 +123943,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123647
123943
|
"meta.accountName \u6216 meta.accountId \u81F3\u5C11\u4E00\u9879\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 overview"
|
|
123648
123944
|
);
|
|
123649
123945
|
}
|
|
123650
|
-
if (!
|
|
123946
|
+
if (!textLen2(meta.periodLabel) && !(textLen2(meta.startDate) && textLen2(meta.endDate))) {
|
|
123651
123947
|
pushMissing(
|
|
123652
123948
|
missing,
|
|
123653
123949
|
"meta-period",
|
|
@@ -123680,7 +123976,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123680
123976
|
if (!isFiniteNumber(funnel?.reach)) {
|
|
123681
123977
|
pushMissing(missing, "chart-funnel", "\u6570\u636E\u56FE\u8868", "\u6F0F\u6597\u56FE reach", "charts.funnel.reach \u987B\u4E3A\u6709\u6548\u6570\u503C");
|
|
123682
123978
|
}
|
|
123683
|
-
if (
|
|
123979
|
+
if (adSetsWithSpend2 < 1) {
|
|
123684
123980
|
pushMissing(
|
|
123685
123981
|
missing,
|
|
123686
123982
|
"table-adsets",
|
|
@@ -123689,10 +123985,10 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123689
123985
|
"tables.adSets \u81F3\u5C11 1 \u884C\u82B1\u8D39>0\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 ad-sets"
|
|
123690
123986
|
);
|
|
123691
123987
|
}
|
|
123692
|
-
if (
|
|
123988
|
+
if (asArray2(tables?.audienceTop).length < 1) {
|
|
123693
123989
|
pushMissing(missing, "table-audience-top", "\u6570\u636E\u8868\u683C", "\u53D7\u4F17 Top", "tables.audienceTop \u81F3\u5C11 1 \u884C");
|
|
123694
123990
|
}
|
|
123695
|
-
if (
|
|
123991
|
+
if (asArray2(tables?.audienceBottom).length < 1) {
|
|
123696
123992
|
pushMissing(
|
|
123697
123993
|
missing,
|
|
123698
123994
|
"table-audience-bottom",
|
|
@@ -123701,7 +123997,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123701
123997
|
"tables.audienceBottom \u81F3\u5C11 1 \u884C"
|
|
123702
123998
|
);
|
|
123703
123999
|
}
|
|
123704
|
-
if (
|
|
124000
|
+
if (textLen2(narrative.overall) < 120) {
|
|
123705
124001
|
pushMissing(
|
|
123706
124002
|
missing,
|
|
123707
124003
|
"narrative-overall",
|
|
@@ -123710,13 +124006,13 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123710
124006
|
"\u2265120 \u5B57\uFF0C\u542B\u82B1\u8D39/\u7EBF\u7D22/CPL/\u8986\u76D6/\u5C55\u793A/\u9891\u6B21\u7B49\u81F3\u5C11 5 \u9879\u6570\u5B57"
|
|
123711
124007
|
);
|
|
123712
124008
|
}
|
|
123713
|
-
if (
|
|
124009
|
+
if (adSetsWithSpend2 > 0 && regionalNarratives < adSetsWithSpend2) {
|
|
123714
124010
|
pushMissing(
|
|
123715
124011
|
missing,
|
|
123716
124012
|
"narrative-regional",
|
|
123717
124013
|
"\u603B\u6570\u636E\u53D9\u4E8B",
|
|
123718
124014
|
"narrative.regional",
|
|
123719
|
-
`\u6BCF\u4E2A\u82B1\u8D39>0 \u7684\u5E7F\u544A\u7EC4\u5404 1 \u6BB5\uFF08\u5F53\u524D ${regionalNarratives}/${
|
|
124015
|
+
`\u6BCF\u4E2A\u82B1\u8D39>0 \u7684\u5E7F\u544A\u7EC4\u5404 1 \u6BB5\uFF08\u5F53\u524D ${regionalNarratives}/${adSetsWithSpend2}\uFF09`
|
|
123720
124016
|
);
|
|
123721
124017
|
} else if (regionalNarratives < 1) {
|
|
123722
124018
|
pushMissing(
|
|
@@ -123728,8 +124024,8 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123728
124024
|
);
|
|
123729
124025
|
} else {
|
|
123730
124026
|
for (let i = 0; i < regionalNarratives; i++) {
|
|
123731
|
-
const row = asRecord6(
|
|
123732
|
-
if (
|
|
124027
|
+
const row = asRecord6(asArray2(narrative.regional)[i]);
|
|
124028
|
+
if (textLen2(row?.text) < 80) {
|
|
123733
124029
|
pushMissing(
|
|
123734
124030
|
missing,
|
|
123735
124031
|
`narrative-regional-${i}`,
|
|
@@ -123740,7 +124036,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123740
124036
|
}
|
|
123741
124037
|
}
|
|
123742
124038
|
}
|
|
123743
|
-
if (
|
|
124039
|
+
if (textLen2(narrative.country) < 80) {
|
|
123744
124040
|
pushMissing(
|
|
123745
124041
|
missing,
|
|
123746
124042
|
"narrative-country",
|
|
@@ -123772,7 +124068,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123772
124068
|
}
|
|
123773
124069
|
for (let i = 0; i < recommendations.length; i++) {
|
|
123774
124070
|
const rec = asRecord6(recommendations[i]);
|
|
123775
|
-
if (
|
|
124071
|
+
if (textLen2(rec?.content) < 150) {
|
|
123776
124072
|
pushMissing(
|
|
123777
124073
|
missing,
|
|
123778
124074
|
`narrative-rec-content-${i}`,
|
|
@@ -123794,7 +124090,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123794
124090
|
} else {
|
|
123795
124091
|
for (let i = 0; i < supplementary.length; i++) {
|
|
123796
124092
|
const row = asRecord6(supplementary[i]);
|
|
123797
|
-
if (!
|
|
124093
|
+
if (!textLen2(row?.dimension) || !textLen2(row?.issue)) {
|
|
123798
124094
|
pushMissing(
|
|
123799
124095
|
missing,
|
|
123800
124096
|
`supplementary-fields-${i}`,
|
|
@@ -123803,7 +124099,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123803
124099
|
"\u987B\u542B dimension\u3001issue\u3001suggestion"
|
|
123804
124100
|
);
|
|
123805
124101
|
}
|
|
123806
|
-
if (
|
|
124102
|
+
if (textLen2(row?.suggestion) < 60) {
|
|
123807
124103
|
pushMissing(
|
|
123808
124104
|
missing,
|
|
123809
124105
|
`supplementary-suggestion-${i}`,
|
|
@@ -123815,7 +124111,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123815
124111
|
}
|
|
123816
124112
|
}
|
|
123817
124113
|
for (const level of ["high", "medium", "low"]) {
|
|
123818
|
-
const items =
|
|
124114
|
+
const items = asArray2(priorityPlan?.[level]).filter((s) => textLen2(s) >= 40);
|
|
123819
124115
|
if (items.length < 2) {
|
|
123820
124116
|
pushMissing(
|
|
123821
124117
|
missing,
|
|
@@ -123826,7 +124122,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123826
124122
|
);
|
|
123827
124123
|
}
|
|
123828
124124
|
}
|
|
123829
|
-
const summaryOk = executiveSummary.filter((p) =>
|
|
124125
|
+
const summaryOk = executiveSummary.filter((p) => textLen2(p) >= 80);
|
|
123830
124126
|
if (summaryOk.length < 3) {
|
|
123831
124127
|
pushMissing(
|
|
123832
124128
|
missing,
|
|
@@ -123836,22 +124132,33 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123836
124132
|
"3\uFF5E5 \u6BB5\uFF0C\u6BCF\u6BB5 \u226580 \u5B57\uFF0C\u89E3\u91CA\u300C\u4E3A\u4EC0\u4E48\u300D"
|
|
123837
124133
|
);
|
|
123838
124134
|
}
|
|
123839
|
-
|
|
124135
|
+
const lifecyclePhase = health?.lifecyclePhase;
|
|
124136
|
+
const lifecyclePhaseInferred = health?.lifecyclePhaseInferred;
|
|
124137
|
+
const phaseValid = LIFECYCLE_PHASES.includes(lifecyclePhase);
|
|
124138
|
+
if (!phaseValid) {
|
|
123840
124139
|
pushMissing(
|
|
123841
124140
|
missing,
|
|
123842
124141
|
"health-phase",
|
|
123843
|
-
"\u5065\u5EB7\u8BCA\u65AD",
|
|
124142
|
+
"\u5065\u5EB7\u8BCA\u65AD \u2460",
|
|
123844
124143
|
"healthDiagnosis.lifecyclePhase",
|
|
123845
|
-
|
|
124144
|
+
`\u987B\u4E3A test-market / find-winner / scale\uFF1B${SNAPSHOT_HEALTH_HINT}`
|
|
124145
|
+
);
|
|
124146
|
+
} else if (typeof lifecyclePhaseInferred === "string" && LIFECYCLE_PHASES.includes(lifecyclePhaseInferred) && lifecyclePhase !== lifecyclePhaseInferred && health?.lifecyclePhaseAutofilled !== true) {
|
|
124147
|
+
pushMissing(
|
|
124148
|
+
missing,
|
|
124149
|
+
"health-phase-mismatch",
|
|
124150
|
+
"\u5065\u5EB7\u8BCA\u65AD \u2460",
|
|
124151
|
+
"healthDiagnosis.lifecyclePhase",
|
|
124152
|
+
`Agent \u586B\u300C${lifecyclePhase}\u300D\u4F46\u5FEB\u7167\u6570\u636E\u63A8\u65AD\u4E3A\u300C${lifecyclePhaseInferred}\u300D\uFF1B\u5220\u9664 lifecyclePhase \u7531 --snapshot-dir \u81EA\u52A8\u63A8\u65AD\uFF0C\u6216\u6309\u89C4\u5219\u4FEE\u6B63`
|
|
123846
124153
|
);
|
|
123847
124154
|
}
|
|
123848
|
-
if (
|
|
124155
|
+
if (textLen2(health?.lifecycleVerdict) < 60) {
|
|
123849
124156
|
pushMissing(
|
|
123850
124157
|
missing,
|
|
123851
124158
|
"health-verdict",
|
|
123852
|
-
"\u5065\u5EB7\u8BCA\u65AD",
|
|
124159
|
+
"\u5065\u5EB7\u8BCA\u65AD \u2460",
|
|
123853
124160
|
"healthDiagnosis.lifecycleVerdict",
|
|
123854
|
-
|
|
124161
|
+
`\u226560 \u5B57\uFF0C\u7ED3\u5408\u603B\u82B1\u8D39\u4E0E\u7EF4\u5EA6\u5206\u6563\u5EA6\uFF1B${SNAPSHOT_HEALTH_HINT}`
|
|
123855
124162
|
);
|
|
123856
124163
|
}
|
|
123857
124164
|
if (fourQuestions.length !== 4) {
|
|
@@ -123865,7 +124172,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123865
124172
|
} else {
|
|
123866
124173
|
for (let i = 0; i < fourQuestions.length; i++) {
|
|
123867
124174
|
const q = asRecord6(fourQuestions[i]);
|
|
123868
|
-
const evidence =
|
|
124175
|
+
const evidence = asArray2(q?.evidence).filter((e) => textLen2(e) > 0);
|
|
123869
124176
|
if (evidence.length < 2) {
|
|
123870
124177
|
pushMissing(
|
|
123871
124178
|
missing,
|
|
@@ -123875,7 +124182,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123875
124182
|
"\u6BCF\u6761 evidence \u22652 \u6761\u4E14\u542B\u6570\u5B57"
|
|
123876
124183
|
);
|
|
123877
124184
|
}
|
|
123878
|
-
if (
|
|
124185
|
+
if (textLen2(q?.action) < 40) {
|
|
123879
124186
|
pushMissing(
|
|
123880
124187
|
missing,
|
|
123881
124188
|
`health-action-${i}`,
|
|
@@ -123886,17 +124193,51 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123886
124193
|
}
|
|
123887
124194
|
}
|
|
123888
124195
|
}
|
|
124196
|
+
let scorecardValidRows = 0;
|
|
124197
|
+
for (let i = 0; i < scorecard.length; i++) {
|
|
124198
|
+
const row = asRecord6(scorecard[i]);
|
|
124199
|
+
const itemOk = textLen2(row?.item) > 0;
|
|
124200
|
+
const dataOk = textLen2(row?.data) > 0;
|
|
124201
|
+
const signalOk = SCORECARD_SIGNALS.includes(row?.signal);
|
|
124202
|
+
const labelOk = textLen2(row?.signalLabel) > 0;
|
|
124203
|
+
const adviceOk = textLen2(row?.advice) > 0;
|
|
124204
|
+
if (itemOk && dataOk && signalOk && labelOk && adviceOk) {
|
|
124205
|
+
scorecardValidRows += 1;
|
|
124206
|
+
continue;
|
|
124207
|
+
}
|
|
124208
|
+
const parts = [];
|
|
124209
|
+
if (!itemOk) parts.push("item");
|
|
124210
|
+
if (!dataOk) parts.push("data");
|
|
124211
|
+
if (!signalOk) parts.push("signal(green|yellow|red)");
|
|
124212
|
+
if (!labelOk) parts.push("signalLabel");
|
|
124213
|
+
if (!adviceOk) parts.push("advice");
|
|
124214
|
+
pushMissing(
|
|
124215
|
+
missing,
|
|
124216
|
+
`health-scorecard-row-${i}`,
|
|
124217
|
+
"\u5065\u5EB7\u8BCA\u65AD \u2462",
|
|
124218
|
+
`healthDiagnosis.scorecard[${i}]`,
|
|
124219
|
+
`\u6BCF\u884C\u987B\u542B ${parts.join("\u3001")}`
|
|
124220
|
+
);
|
|
124221
|
+
}
|
|
123889
124222
|
if (scorecard.length < 6) {
|
|
123890
124223
|
pushMissing(
|
|
123891
124224
|
missing,
|
|
123892
|
-
"health-scorecard",
|
|
123893
|
-
"\u5065\u5EB7\u8BCA\u65AD",
|
|
124225
|
+
"health-scorecard-count",
|
|
124226
|
+
"\u5065\u5EB7\u8BCA\u65AD \u2462",
|
|
124227
|
+
"healthDiagnosis.scorecard",
|
|
124228
|
+
`\u7EA2\u7EFF\u706F\u8868 \u22656 \u884C\uFF08\u5E73\u53F0/\u56FD\u5BB6/\u5E7F\u544A\u7EC4/\u53D7\u4F17\u7B49\uFF09\uFF1B\u5F53\u524D ${scorecard.length} \u884C\uFF1B${SNAPSHOT_HEALTH_HINT}`
|
|
124229
|
+
);
|
|
124230
|
+
} else if (scorecardValidRows < 6) {
|
|
124231
|
+
pushMissing(
|
|
124232
|
+
missing,
|
|
124233
|
+
"health-scorecard-valid",
|
|
124234
|
+
"\u5065\u5EB7\u8BCA\u65AD \u2462",
|
|
123894
124235
|
"healthDiagnosis.scorecard",
|
|
123895
|
-
|
|
124236
|
+
`\u6709\u6548\u884C ${scorecardValidRows}/6\uFF08\u6BCF\u884C item/data/signal/signalLabel/advice \u9F50\u5168\uFF09`
|
|
123896
124237
|
);
|
|
123897
124238
|
}
|
|
123898
124239
|
for (const key of ["platform", "country", "adSets"]) {
|
|
123899
|
-
const insight =
|
|
124240
|
+
const insight = textLen2(asRecord6(sections?.[key])?.insight);
|
|
123900
124241
|
if (insight < 200) {
|
|
123901
124242
|
pushMissing(
|
|
123902
124243
|
missing,
|
|
@@ -123908,7 +124249,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123908
124249
|
}
|
|
123909
124250
|
}
|
|
123910
124251
|
const audience = asRecord6(sections?.audience);
|
|
123911
|
-
if (
|
|
124252
|
+
if (asArray2(audience?.goldenProfile).filter((s) => textLen2(s) > 0).length < 3) {
|
|
123912
124253
|
pushMissing(
|
|
123913
124254
|
missing,
|
|
123914
124255
|
"sections-audience-golden",
|
|
@@ -123917,7 +124258,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123917
124258
|
"\u22653 \u6761\u9EC4\u91D1\u53D7\u4F17\u753B\u50CF"
|
|
123918
124259
|
);
|
|
123919
124260
|
}
|
|
123920
|
-
if (
|
|
124261
|
+
if (asArray2(audience?.antiProfile).filter((s) => textLen2(s) > 0).length < 2) {
|
|
123921
124262
|
pushMissing(
|
|
123922
124263
|
missing,
|
|
123923
124264
|
"sections-audience-anti",
|
|
@@ -123926,7 +124267,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123926
124267
|
"\u22652 \u6761\u53CD\u753B\u50CF"
|
|
123927
124268
|
);
|
|
123928
124269
|
}
|
|
123929
|
-
if (
|
|
124270
|
+
if (textLen2(audience?.insight) < 150) {
|
|
123930
124271
|
pushMissing(
|
|
123931
124272
|
missing,
|
|
123932
124273
|
"sections-audience-insight",
|
|
@@ -123935,7 +124276,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123935
124276
|
"\u2265150 \u5B57"
|
|
123936
124277
|
);
|
|
123937
124278
|
}
|
|
123938
|
-
const landingRows =
|
|
124279
|
+
const landingRows = asArray2(asRecord6(sections?.landingPage)?.rows);
|
|
123939
124280
|
if (landingRows.length < 3) {
|
|
123940
124281
|
pushMissing(
|
|
123941
124282
|
missing,
|
|
@@ -123954,9 +124295,9 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123954
124295
|
"\u22653 \u4E2A\u5B9E\u9A8C\uFF08\u53D8\u91CF\u3001\u5047\u8BBE\u3001\u6210\u529F\u6807\u51C6\uFF09"
|
|
123955
124296
|
);
|
|
123956
124297
|
}
|
|
123957
|
-
const today =
|
|
123958
|
-
const thisWeek =
|
|
123959
|
-
const thisMonth =
|
|
124298
|
+
const today = asArray2(actionChecklist?.today).filter((s) => textLen2(s) > 0);
|
|
124299
|
+
const thisWeek = asArray2(actionChecklist?.thisWeek).filter((s) => textLen2(s) > 0);
|
|
124300
|
+
const thisMonth = asArray2(actionChecklist?.thisMonth).filter((s) => textLen2(s) > 0);
|
|
123960
124301
|
if (today.length < 2 || thisWeek.length < 3 || thisMonth.length < 3) {
|
|
123961
124302
|
pushMissing(
|
|
123962
124303
|
missing,
|
|
@@ -123974,8 +124315,11 @@ function validateMetaPeriodReportContent(data) {
|
|
|
123974
124315
|
supplementaryCount: supplementary.length,
|
|
123975
124316
|
fourQuestionsCount: fourQuestions.length,
|
|
123976
124317
|
scorecardRows: scorecard.length,
|
|
124318
|
+
scorecardValidRows,
|
|
124319
|
+
lifecyclePhase: phaseValid ? String(lifecyclePhase) : void 0,
|
|
124320
|
+
lifecyclePhaseInferred: typeof lifecyclePhaseInferred === "string" ? lifecyclePhaseInferred : void 0,
|
|
123977
124321
|
regionalNarratives,
|
|
123978
|
-
adSetsWithSpend
|
|
124322
|
+
adSetsWithSpend: adSetsWithSpend2
|
|
123979
124323
|
}
|
|
123980
124324
|
};
|
|
123981
124325
|
}
|
|
@@ -123987,7 +124331,7 @@ function formatMetaPeriodReportErrors(result) {
|
|
|
123987
124331
|
...lines,
|
|
123988
124332
|
"",
|
|
123989
124333
|
"\u8BF7 Read assets/meta-period-report-rules.md\uFF0C\u8865\u5168 meta-period-report.json \u540E\u91CD\u65B0\u6267\u884C render\u3002",
|
|
123990
|
-
"\u6570\u636E\u7C7B\u5B57\u6BB5\u53EF\u5148 facebook-analysis --json-out\uFF0Crender \u65F6\u52A0 --snapshot-dir \u81EA\u52A8\u5408\u5E76 KPI/\u56FE\u8868/\u8868\u683C\u3002"
|
|
124334
|
+
"\u6570\u636E\u7C7B\u5B57\u6BB5\u53EF\u5148 facebook-analysis --json-out\uFF0Crender \u65F6\u52A0 --snapshot-dir \u81EA\u52A8\u5408\u5E76 KPI/\u56FE\u8868/\u8868\u683C\uFF0C\u5E76\u8865\u5168 \u2460 \u9636\u6BB5\u4E0E \u2462 \u7EA2\u7EFF\u706F\u8868\u3002"
|
|
123991
124335
|
].join("\n");
|
|
123992
124336
|
}
|
|
123993
124337
|
|
|
@@ -124049,7 +124393,7 @@ async function runFacebookAnalysisRender(opts) {
|
|
|
124049
124393
|
`);
|
|
124050
124394
|
const { stats: stats2 } = contentCheck;
|
|
124051
124395
|
console.error(
|
|
124052
|
-
` \u7EDF\u8BA1\uFF1A\u5EFA\u8BAE ${stats2.recommendationCount}/4\uFF5C\u8865\u5145 ${stats2.supplementaryCount}/7\uFF5C\u56DB\u95EE ${stats2.fourQuestionsCount}/4\uFF5C\u7EA2\u7EFF\u706F ${stats2.scorecardRows}
|
|
124396
|
+
` \u7EDF\u8BA1\uFF1A\u5EFA\u8BAE ${stats2.recommendationCount}/4\uFF5C\u8865\u5145 ${stats2.supplementaryCount}/7\uFF5C\u56DB\u95EE ${stats2.fourQuestionsCount}/4\uFF5C\u9636\u6BB5 ${stats2.lifecyclePhase ?? "\u2014"}${stats2.lifecyclePhaseInferred ? `\uFF08\u63A8\u65AD ${stats2.lifecyclePhaseInferred}\uFF09` : ""}\uFF5C\u7EA2\u7EFF\u706F ${stats2.scorecardValidRows}/${stats2.scorecardRows} \u6709\u6548\u884C\uFF5C\u533A\u57DF\u53D9\u4E8B ${stats2.regionalNarratives}/${stats2.adSetsWithSpend}
|
|
124053
124397
|
`
|
|
124054
124398
|
);
|
|
124055
124399
|
process.exit(1);
|
|
@@ -124084,7 +124428,7 @@ async function runFacebookAnalysisRender(opts) {
|
|
|
124084
124428
|
\u2705 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
|
|
124085
124429
|
`);
|
|
124086
124430
|
console.log(
|
|
124087
|
-
` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u5EFA\u8BAE ${stats.recommendationCount}\uFF5C\u8865\u5145 ${stats.supplementaryCount}\uFF5C\u56DB\u95EE ${stats.fourQuestionsCount}\uFF5C\u7EA2\u7EFF\u706F ${stats.
|
|
124431
|
+
` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u5EFA\u8BAE ${stats.recommendationCount}\uFF5C\u8865\u5145 ${stats.supplementaryCount}\uFF5C\u56DB\u95EE ${stats.fourQuestionsCount}\uFF5C\u9636\u6BB5 ${stats.lifecyclePhase ?? "\u2014"}\uFF5C\u7EA2\u7EFF\u706F ${stats.scorecardValidRows} \u6709\u6548\u884C
|
|
124088
124432
|
`
|
|
124089
124433
|
);
|
|
124090
124434
|
if (runtimeCopied) {
|
package/dist/skill/_meta.json
CHANGED
|
@@ -120,11 +120,12 @@
|
|
|
120
120
|
| 字段 | 最低要求 |
|
|
121
121
|
| ---- | -------- |
|
|
122
122
|
| `executiveSummary` | **3~5 段**,每段 **≥ 80 字**;解释「为什么」而不只报数;可拆自 `narrative.overall` 但须加深因果 |
|
|
123
|
-
| `healthDiagnosis.lifecyclePhase` | `test-market` / `find-winner` / `scale`
|
|
124
|
-
| `healthDiagnosis.lifecycleVerdict` | **≥ 60
|
|
123
|
+
| `healthDiagnosis.lifecyclePhase` | `test-market` / `find-winner` / `scale` 三选一。**可省略**:`render --snapshot-dir` 按花费与集中度自动推断;若手写须与数据一致(render 校验 `lifecyclePhase` vs `lifecyclePhaseInferred`) |
|
|
124
|
+
| `healthDiagnosis.lifecycleVerdict` | **≥ 60 字**,结合总花费与维度分散度;可随 phase 由 CLI 补全 |
|
|
125
|
+
| **阶段判定(CLI 推断口径)** | **测市场**:总花费 < $200,或活跃维度 ≥4 且赢家花费占比 <35%;**放量**:Top 广告组花费/结果占比 ≥55% 且 CPL ≤ 账户均值;**找赢家**:其余有优劣分化但未高度集中 |
|
|
125
126
|
| `healthDiagnosis.fourQuestions` | **恰好 4 张卡片**(钱花得值不值 / 赢在哪 / 输在哪 / 下月重点) |
|
|
126
127
|
| 每张 `fourQuestions[]` | `verdict` + `evidence` **≥2 条**(含数字)+ `action` **≥ 40 字** |
|
|
127
|
-
| `healthDiagnosis.scorecard` | **≥ 6
|
|
128
|
+
| `healthDiagnosis.scorecard` | **≥ 6 行**有效行;每行须 `item` / `data` / `signal`(green\|yellow\|red) / `signalLabel` / `advice`。**可省略**:`render --snapshot-dir` 自动补全;render 校验行数与字段完整性 |
|
|
128
129
|
| `sections.platform.insight` | **≥ 200 字** |
|
|
129
130
|
| `sections.country.insight` | **≥ 200 字** |
|
|
130
131
|
| `sections.adSets.insight` | **≥ 200 字** |
|
|
@@ -152,7 +153,7 @@
|
|
|
152
153
|
- [ ] `narrative.recommendations` 4 条,每条 content ≥150 字且含真实数字
|
|
153
154
|
- [ ] `supplementaryRecommendations` 7 维齐全
|
|
154
155
|
- [ ] `priorityPlan` high/medium/low 各 ≥2 条
|
|
155
|
-
- [ ] HTML:`executiveSummary` ≥3 段、`fourQuestions` =4、`scorecard` ≥6
|
|
156
|
+
- [ ] HTML:`executiveSummary` ≥3 段、`fourQuestions` =4、`lifecyclePhase` 与快照推断一致(或省略由 CLI 补全)、`scorecard` ≥6 行且每行五字段齐全
|
|
156
157
|
- [ ] HTML:`sections.platform/country/adSets.insight` 各 ≥200 字
|
|
157
158
|
- [ ] HTML:`actionChecklist` 三列非空;`abTests` ≥3
|
|
158
159
|
- [ ] Excel:总数据 Sheet 叙事块 4 节齐全,无 1 句话敷衍
|
|
@@ -16,19 +16,30 @@ siluzan-tso list-accounts [选项]
|
|
|
16
16
|
| `-p, --page <n>` | 页码(默认 1) |
|
|
17
17
|
| `--page-size <n>` | 每页数量(默认 20) |
|
|
18
18
|
| `--json-out` | 输出原始 JSON |
|
|
19
|
-
| `--detail` | **合并余额与近期消耗**(及展示/点击/转化/CPC、Arit 得分);**未传时 JSON
|
|
19
|
+
| `--detail` | **合并余额与近期消耗**(及展示/点击/转化/CPC、Arit 得分);**未传时 JSON 不含这些字段**,列表更快。**仅适合少量账号**:每户额外并行请求,账号多时明显变慢;全量余额/消耗汇总改用 `balance-scan` / `accounts-digest` |
|
|
20
20
|
| `--unicode` | 表格使用 Unicode 线框;**默认**为 ASCII `+- | ` 线框(兼容各类终端) |
|
|
21
21
|
| `--plain` | 已默认 ASCII,无需再传;保留兼容旧脚本 |
|
|
22
22
|
| `--refresh-dp` | 强制重拉 Datapermission(排查「本页全部 OAuth 失效」类会话异常) |
|
|
23
23
|
|
|
24
|
+
**命令定位(Agent 必读)**:`list-accounts` 主打**精准查询账号信息**(列表、计数、按名称/ID 找户、`entityId` / `mediaCustomerId` / 币种 / 状态等元数据)。**它不是余额/消耗汇总工具**——`--detail` 只是给「少量账号」顺带带出余额与近期消耗的增强项。
|
|
25
|
+
|
|
24
26
|
**`--detail` 说明(Agent 必读):**
|
|
25
27
|
|
|
26
|
-
| 模式 | 行为 |
|
|
27
|
-
| ---- | ---- |
|
|
28
|
-
| 默认(无 `--detail`) | 只拉账户列表;JSON **不含**余额、消耗、展示、点击、转化、CPC、Arit;表格对应列为「-」 |
|
|
29
|
-
| `--detail` |
|
|
28
|
+
| 模式 | 行为 | 适用 |
|
|
29
|
+
| ---- | ---- | ---- |
|
|
30
|
+
| 默认(无 `--detail`) | 只拉账户列表;JSON **不含**余额、消耗、展示、点击、转化、CPC、Arit;表格对应列为「-」 | 任意规模的账号列表/计数/元数据 |
|
|
31
|
+
| `--detail` | 每户额外并行请求余额与投放概览;JSON 含上述字段真实数值,**响应明显变慢** | **仅少量账号**(单户 / 数个 `-k` 命中) |
|
|
32
|
+
|
|
33
|
+
**何时不要用 `--detail`(改用专用命令):**
|
|
34
|
+
|
|
35
|
+
| 需求 | 推荐命令 |
|
|
36
|
+
| ---- | -------- |
|
|
37
|
+
| **全部账号**的余额、续航/充值预警 | `balance-scan`(见 `SKILL.md` Playbook P2) |
|
|
38
|
+
| **全部/多账号**的消耗、点击、转化、CTR/CPC/CPA 投放画像汇总 | `accounts-digest`(本文下方) |
|
|
39
|
+
| 单户/少量户精确余额或消耗 | `balance` / `stats`(`-a` 指定 ID) |
|
|
40
|
+
| 列表顺带看少量户余额/消耗 | `list-accounts --detail` |
|
|
30
41
|
|
|
31
|
-
|
|
42
|
+
**禁止**在 JSON 无余额/消耗字段时臆造数值;也**禁止**用 `--detail` 全量扫描所有账号去拼余额/消耗汇总(改用上表专用命令)。
|
|
32
43
|
|
|
33
44
|
### Agent 意图速查(**必读 · 避免多次试探**)
|
|
34
45
|
|
|
@@ -40,7 +51,8 @@ siluzan-tso list-accounts [选项]
|
|
|
40
51
|
| 有多少个 Google 账户 | 同上 | **`total`**(无需翻页;`itemCount < total` 时说明 page-size 不够大) |
|
|
41
52
|
| 列出全部某媒体(TikTok / MetaAd 等) | `list-accounts -m <媒体> --page-size 999 --json-out <dir>` | 同上 |
|
|
42
53
|
| 只查某一个户 | `list-accounts -m <媒体> -k <id或名称> --json-out <dir>` | 无需大 page-size |
|
|
43
|
-
|
|
|
54
|
+
| **少量账号**顺带看余额/消耗 | 在上述命令加 `--detail`(**明显变慢**;仅少量账号且用户要余额/消耗时) | 含 `ma.balance` 等 enrichment 字段 |
|
|
55
|
+
| **全部账号**的余额/消耗汇总 | **勿用 `--detail` 全量扫描** → 余额预警用 `balance-scan`、消耗画像用 `accounts-digest` | 见各命令章节 |
|
|
44
56
|
|
|
45
57
|
**执行纪律**:
|
|
46
58
|
|
|
@@ -71,8 +83,9 @@ siluzan-tso list-accounts -m Google -k "品牌A" --json-out ./snap
|
|
|
71
83
|
# 只看正常状态
|
|
72
84
|
siluzan-tso list-accounts -m TikTok -s normal --page-size 999 --json-out ./snap
|
|
73
85
|
|
|
74
|
-
#
|
|
75
|
-
|
|
86
|
+
# 列表同时带出真实余额与近期消耗(较慢,仅少量账号且用户要余额/消耗时)
|
|
87
|
+
# 注意:全部账号的余额/消耗汇总请改用 balance-scan / accounts-digest,勿用 --detail 全量扫描
|
|
88
|
+
siluzan-tso list-accounts -m Google -k "品牌A" --detail --json-out ./snap
|
|
76
89
|
|
|
77
90
|
# 极少数账户超过 999 条时才翻页(先确认读盘 total > itemCount)
|
|
78
91
|
siluzan-tso list-accounts -m Google --page 2 --page-size 999 --json-out ./snap-p2
|
|
@@ -263,10 +263,10 @@ ${planHtml ? `<h3 style="margin-top:24px">优先级改进计划</h3>${planHtml}`
|
|
|
263
263
|
function renderHealthDiagnosis(hd) {
|
|
264
264
|
if (!hd) return "";
|
|
265
265
|
|
|
266
|
-
const phase = hd.lifecyclePhase || "
|
|
266
|
+
const phase = hd.lifecyclePhase || "";
|
|
267
267
|
const lifecycle = LIFECYCLE_PHASES.map(
|
|
268
268
|
(p) => `
|
|
269
|
-
<div class="phase${p.id === phase ? " active" : ""}">
|
|
269
|
+
<div class="phase${phase && p.id === phase ? " active" : ""}">
|
|
270
270
|
<div class="step">${escapeHtml(p.step)}</div>
|
|
271
271
|
<div class="name">${escapeHtml(p.name)}</div>
|
|
272
272
|
<div class="desc">${escapeHtml(p.desc)}</div>
|
|
@@ -166,7 +166,7 @@ siluzan-tso facebook-analysis -a <mediaCustomerId> --start <s> --end <e> --json-
|
|
|
166
166
|
用户未指定 Excel 时,除 `narrative` 外 **必须** 填写(字数见 `meta-period-report-rules.md` §四):
|
|
167
167
|
|
|
168
168
|
- `executiveSummary[]`:**3~5 段**「为什么」解读(每段 ≥80 字)
|
|
169
|
-
- `healthDiagnosis`:三阶段 + **4 张四问卡片** + **≥6
|
|
169
|
+
- `healthDiagnosis`:三阶段 + **4 张四问卡片** + **≥6 行**红绿灯表(③ 该加还是该减;可省略 `scorecard`,render 时由 `--snapshot-dir` 自动补全)
|
|
170
170
|
- `sections.platform/country/adSets.insight`:各 **≥200 字**
|
|
171
171
|
- `sections.audience`:`goldenProfile` ≥3 条 + `antiProfile` ≥2 条
|
|
172
172
|
- `sections.landingPage.rows`:≥3 行
|
|
@@ -201,7 +201,7 @@ siluzan-tso facebook-analysis -a <mediaCustomerId> --start <s> --end <e> --json-
|
|
|
201
201
|
| **`priorityPlan`** | **推荐** | 高/中/低各 ≥2 条;HTML 强烈建议填写 |
|
|
202
202
|
| `tables` / `charts` | 可省略 | Sheet2~5;由快照自动汇总 |
|
|
203
203
|
| `executiveSummary` | **HTML 必填** | 3~5 段深度摘要 |
|
|
204
|
-
| `healthDiagnosis` | **HTML 必填** | 四问 +
|
|
204
|
+
| `healthDiagnosis` | **HTML 必填** | 四问 + 红绿灯表(`scorecard` 可由 CLI 从快照自动补全) |
|
|
205
205
|
| `sections.*.insight` | **HTML 必填** | 各维度 ≥200 字 |
|
|
206
206
|
| `abTests` / `actionChecklist` | **HTML 必填** | ≥3 实验 + 三列行动清单 |
|
|
207
207
|
|
|
@@ -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.29-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.29-beta.5'
|
|
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.29-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.29-beta.5"
|
|
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"
|