siluzan-tso-cli 1.1.50-beta.2 → 1.1.50
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 -2
- package/dist/index.js +657 -570
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/website-diagnosis-rules.md +1 -1
- package/dist/skill/references/accounts/accounts-list.md +1 -1
- package/dist/skill/references/accounts/accounts-permissions.md +2 -2
- package/dist/skill/references/accounts/finance.md +1 -1
- package/dist/skill/references/accounts/open-account-by-media.md +1 -1
- package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
- package/dist/skill/references/analytics/rag.md +1 -1
- package/dist/skill/references/analytics/reporting.md +5 -5
- package/dist/skill/references/analytics/website-diagnosis-guide.md +3 -3
- package/dist/skill/references/core/intent-routing.md +1 -1
- package/dist/skill/references/core/playbooks.md +1 -1
- package/dist/skill/references/core/setup.md +5 -5
- package/dist/skill/references/misc/tso-home.md +2 -2
- package/dist/skill/references/report-templates/google-ads-diagnosis.md +8 -9
- package/dist/skill/references/report-templates/okki-weekly-google-client.md +5 -4
- package/dist/skill/references/report-templates/website-diagnosis-report.md +1 -1
- package/dist/skill/report-templates/google-ads-diagnosis.md +8 -9
- package/dist/skill/report-templates/okki-weekly-google-client.md +5 -4
- package/dist/skill/report-templates/website-diagnosis-report.md +1 -1
- package/dist/skill/scripts/install.ps1 +3 -3
- package/dist/skill/scripts/install.sh +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -258,9 +258,9 @@ var require_semver = __commonJS({
|
|
|
258
258
|
} else {
|
|
259
259
|
this.prerelease = m[4].split(".").map((id) => {
|
|
260
260
|
if (/^[0-9]+$/.test(id)) {
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
263
|
-
return
|
|
261
|
+
const num12 = +id;
|
|
262
|
+
if (num12 >= 0 && num12 < MAX_SAFE_INTEGER) {
|
|
263
|
+
return num12;
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
return id;
|
|
@@ -4018,7 +4018,7 @@ var DEFAULT_API_BASE;
|
|
|
4018
4018
|
var init_defaults = __esm({
|
|
4019
4019
|
"src/config/defaults.ts"() {
|
|
4020
4020
|
"use strict";
|
|
4021
|
-
DEFAULT_API_BASE = "https://tso-api
|
|
4021
|
+
DEFAULT_API_BASE = "https://tso-api.siluzan.com";
|
|
4022
4022
|
}
|
|
4023
4023
|
});
|
|
4024
4024
|
|
|
@@ -102833,20 +102833,138 @@ var init_fetch = __esm({
|
|
|
102833
102833
|
}
|
|
102834
102834
|
});
|
|
102835
102835
|
|
|
102836
|
+
// src/commands/google-analysis/okki-display.ts
|
|
102837
|
+
function num2(value) {
|
|
102838
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
102839
|
+
return Number.isFinite(n) ? n : 0;
|
|
102840
|
+
}
|
|
102841
|
+
function round22(value) {
|
|
102842
|
+
return Math.round(value * 100) / 100;
|
|
102843
|
+
}
|
|
102844
|
+
function share(part, total) {
|
|
102845
|
+
return total > 0 ? part / total : 0;
|
|
102846
|
+
}
|
|
102847
|
+
function unwrapOkkiKeywordDisplay(raw) {
|
|
102848
|
+
const t = raw.trim();
|
|
102849
|
+
if (t.length < 2) return t;
|
|
102850
|
+
const first = t[0];
|
|
102851
|
+
const last = t[t.length - 1];
|
|
102852
|
+
if (first === "[" && last === "]") return t.slice(1, -1).trim();
|
|
102853
|
+
if (first === '"' && last === '"') return t.slice(1, -1).trim();
|
|
102854
|
+
if ((first === "\u201C" || first === "\u201D") && (last === "\u201C" || last === "\u201D")) {
|
|
102855
|
+
return t.slice(1, -1).trim();
|
|
102856
|
+
}
|
|
102857
|
+
return t;
|
|
102858
|
+
}
|
|
102859
|
+
function classifyOkkiMatchKind(matchType) {
|
|
102860
|
+
const raw = typeof matchType === "string" ? matchType.trim() : "";
|
|
102861
|
+
if (!raw) return "unknown";
|
|
102862
|
+
const lower = raw.toLowerCase();
|
|
102863
|
+
if (raw.includes("\u5E7F\u6CDB") || lower.includes("broad")) return "broad";
|
|
102864
|
+
if (raw.includes("\u8BCD\u7EC4") || lower.includes("phrase")) return "phrase";
|
|
102865
|
+
if (raw.includes("\u7CBE\u786E") || raw.includes("\u7CBE\u51C6") || raw.includes("\u5B8C\u5168") || lower.includes("exact")) {
|
|
102866
|
+
return "exact";
|
|
102867
|
+
}
|
|
102868
|
+
return "unknown";
|
|
102869
|
+
}
|
|
102870
|
+
function formatOkkiKeywordDisplay(keyword, matchType) {
|
|
102871
|
+
const raw = typeof keyword === "string" ? keyword.trim() : keyword == null ? "" : String(keyword).trim();
|
|
102872
|
+
if (!raw) return raw;
|
|
102873
|
+
const kind = classifyOkkiMatchKind(matchType);
|
|
102874
|
+
if (kind === "unknown") return raw;
|
|
102875
|
+
const core = unwrapOkkiKeywordDisplay(raw);
|
|
102876
|
+
if (kind === "exact") return `[${core}]`;
|
|
102877
|
+
if (kind === "phrase") return `"${core}"`;
|
|
102878
|
+
return core;
|
|
102879
|
+
}
|
|
102880
|
+
function classifyDeviceKind(value) {
|
|
102881
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
102882
|
+
const lower = raw.toLowerCase();
|
|
102883
|
+
if (raw.includes("\u79FB\u52A8") || lower.includes("mobile")) return "mobile";
|
|
102884
|
+
if (raw.includes("\u5E73\u677F") || lower.includes("tablet")) return "tablet";
|
|
102885
|
+
if (raw.includes("\u8BA1\u7B97\u673A") || raw.includes("\u7535\u8111") || lower.includes("desktop") || lower.includes("computer")) {
|
|
102886
|
+
return "desktop";
|
|
102887
|
+
}
|
|
102888
|
+
return "other";
|
|
102889
|
+
}
|
|
102890
|
+
function okkiGeoInteractionCount(row) {
|
|
102891
|
+
const interactions = num2(row.interactions);
|
|
102892
|
+
if (interactions > 0) return interactions;
|
|
102893
|
+
const clicks = num2(row.clicks);
|
|
102894
|
+
return clicks > 0 ? clicks : 0;
|
|
102895
|
+
}
|
|
102896
|
+
function summarizeOkkiDeviceSpend(rows) {
|
|
102897
|
+
const acc = { desktop: 0, mobile: 0, tablet: 0, other: 0 };
|
|
102898
|
+
for (const row of rows) {
|
|
102899
|
+
acc[classifyDeviceKind(row.deviceType)] += num2(row.spend);
|
|
102900
|
+
}
|
|
102901
|
+
const desktop = round22(acc.desktop);
|
|
102902
|
+
const mobile = round22(acc.mobile);
|
|
102903
|
+
const tablet = round22(acc.tablet);
|
|
102904
|
+
const other = round22(acc.other);
|
|
102905
|
+
const total = round22(desktop + mobile + tablet + other);
|
|
102906
|
+
return {
|
|
102907
|
+
desktop,
|
|
102908
|
+
mobile,
|
|
102909
|
+
tablet,
|
|
102910
|
+
other,
|
|
102911
|
+
total,
|
|
102912
|
+
desktopShare: share(desktop, total),
|
|
102913
|
+
mobileShare: share(mobile, total),
|
|
102914
|
+
desktopLeads: desktop > mobile,
|
|
102915
|
+
mobileLeads: mobile > desktop
|
|
102916
|
+
};
|
|
102917
|
+
}
|
|
102918
|
+
function narrativeClaimsMobileSpendLeads(text2) {
|
|
102919
|
+
const t = text2.replace(/\s+/g, "");
|
|
102920
|
+
if (!t.includes("\u79FB\u52A8")) return false;
|
|
102921
|
+
return /移动(?:端|设备)?(?:为|是)主要消耗/.test(t) || /移动(?:端|设备)?为主要(?:消耗)?设备/.test(t) || /移动(?:端|设备)?(?:消耗|花费|费用|流量)?占比高/.test(t);
|
|
102922
|
+
}
|
|
102923
|
+
function asRecord11(value) {
|
|
102924
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
102925
|
+
return null;
|
|
102926
|
+
}
|
|
102927
|
+
function stringList(value) {
|
|
102928
|
+
return (Array.isArray(value) ? value : []).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
102929
|
+
}
|
|
102930
|
+
function collectOkkiNarrativeText(data) {
|
|
102931
|
+
const narrative = asRecord11(data.narrative) ?? {};
|
|
102932
|
+
const parts = [];
|
|
102933
|
+
const sheetAnalysis = asRecord11(narrative.sheetAnalysis) ?? {};
|
|
102934
|
+
for (const block of Object.values(sheetAnalysis)) {
|
|
102935
|
+
const rec = asRecord11(block);
|
|
102936
|
+
parts.push(...stringList(rec?.summary), ...stringList(rec?.suggestions));
|
|
102937
|
+
}
|
|
102938
|
+
const review = asRecord11(narrative.review) ?? {};
|
|
102939
|
+
for (const block of Object.values(review)) {
|
|
102940
|
+
const rec = asRecord11(block);
|
|
102941
|
+
for (const key of ["overview", "summary", "suggestion"]) {
|
|
102942
|
+
const value = rec?.[key];
|
|
102943
|
+
if (typeof value === "string" && value.trim()) parts.push(value.trim());
|
|
102944
|
+
}
|
|
102945
|
+
}
|
|
102946
|
+
return parts.join("\n");
|
|
102947
|
+
}
|
|
102948
|
+
var init_okki_display = __esm({
|
|
102949
|
+
"src/commands/google-analysis/okki-display.ts"() {
|
|
102950
|
+
"use strict";
|
|
102951
|
+
}
|
|
102952
|
+
});
|
|
102953
|
+
|
|
102836
102954
|
// src/commands/google-analysis/merge-snapshot.ts
|
|
102837
102955
|
import * as fs22 from "fs/promises";
|
|
102838
102956
|
import * as path27 from "path";
|
|
102839
|
-
function
|
|
102957
|
+
function asRecord12(value) {
|
|
102840
102958
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
102841
102959
|
return value;
|
|
102842
102960
|
}
|
|
102843
102961
|
return null;
|
|
102844
102962
|
}
|
|
102845
|
-
function
|
|
102963
|
+
function num3(value) {
|
|
102846
102964
|
const n = Number(value);
|
|
102847
102965
|
return Number.isFinite(n) ? n : void 0;
|
|
102848
102966
|
}
|
|
102849
|
-
function
|
|
102967
|
+
function round23(value) {
|
|
102850
102968
|
return value == null ? void 0 : Math.round(value * 100) / 100;
|
|
102851
102969
|
}
|
|
102852
102970
|
async function readManifest(snapshotDir, accountId) {
|
|
@@ -102921,31 +103039,31 @@ async function recoverMissingReportSections(sectionMap, absDir, accountId, prefe
|
|
|
102921
103039
|
function periodMetricsFromRow(row) {
|
|
102922
103040
|
if (!row) return null;
|
|
102923
103041
|
return {
|
|
102924
|
-
cost:
|
|
102925
|
-
impressions:
|
|
102926
|
-
clicks:
|
|
102927
|
-
conversions:
|
|
102928
|
-
ctr:
|
|
102929
|
-
averageCpc:
|
|
102930
|
-
costPerConversion:
|
|
103042
|
+
cost: num3(row.spend),
|
|
103043
|
+
impressions: num3(row.impressions),
|
|
103044
|
+
clicks: num3(row.clicks),
|
|
103045
|
+
conversions: num3(row.conversions),
|
|
103046
|
+
ctr: num3(row.ctr),
|
|
103047
|
+
averageCpc: num3(row.averageCpc),
|
|
103048
|
+
costPerConversion: num3(row.costPerConversion)
|
|
102931
103049
|
};
|
|
102932
103050
|
}
|
|
102933
103051
|
function items(envelope) {
|
|
102934
103052
|
if (!envelope || !Array.isArray(envelope.items)) return [];
|
|
102935
|
-
return envelope.items.filter((row) =>
|
|
103053
|
+
return envelope.items.filter((row) => asRecord12(row) !== null);
|
|
102936
103054
|
}
|
|
102937
103055
|
function record(envelope) {
|
|
102938
103056
|
return envelope?.record ?? null;
|
|
102939
103057
|
}
|
|
102940
103058
|
function pickMetricRow(row, extraFields) {
|
|
102941
103059
|
const base = {
|
|
102942
|
-
spend:
|
|
102943
|
-
impressions:
|
|
102944
|
-
clicks:
|
|
102945
|
-
ctr:
|
|
102946
|
-
averageCpc:
|
|
102947
|
-
conversions:
|
|
102948
|
-
costPerConversion:
|
|
103060
|
+
spend: num3(row.spend) ?? 0,
|
|
103061
|
+
impressions: num3(row.impressions) ?? 0,
|
|
103062
|
+
clicks: num3(row.clicks) ?? 0,
|
|
103063
|
+
ctr: num3(row.ctr),
|
|
103064
|
+
averageCpc: num3(row.averageCpc),
|
|
103065
|
+
conversions: num3(row.conversions) ?? 0,
|
|
103066
|
+
costPerConversion: num3(row.costPerConversion)
|
|
102949
103067
|
};
|
|
102950
103068
|
for (const f of extraFields) {
|
|
102951
103069
|
if (row[f] !== void 0) base[f] = row[f];
|
|
@@ -102959,34 +103077,34 @@ function sumCampaignRowsToPeriodKpis(rows) {
|
|
|
102959
103077
|
let clicks = 0;
|
|
102960
103078
|
let conversions = 0;
|
|
102961
103079
|
for (const row of rows) {
|
|
102962
|
-
cost +=
|
|
102963
|
-
impressions +=
|
|
102964
|
-
clicks +=
|
|
102965
|
-
conversions +=
|
|
103080
|
+
cost += num3(row.spend) ?? 0;
|
|
103081
|
+
impressions += num3(row.impressions) ?? 0;
|
|
103082
|
+
clicks += num3(row.clicks) ?? 0;
|
|
103083
|
+
conversions += num3(row.conversions) ?? 0;
|
|
102966
103084
|
}
|
|
102967
103085
|
const ctr = impressions > 0 ? clicks / impressions : void 0;
|
|
102968
103086
|
const averageCpc = clicks > 0 ? cost / clicks : void 0;
|
|
102969
103087
|
const costPerConversion = conversions > 0 ? cost / conversions : void 0;
|
|
102970
103088
|
return {
|
|
102971
|
-
cost:
|
|
103089
|
+
cost: round23(cost),
|
|
102972
103090
|
impressions: Math.round(impressions),
|
|
102973
103091
|
clicks: Math.round(clicks),
|
|
102974
|
-
conversions:
|
|
103092
|
+
conversions: round23(conversions),
|
|
102975
103093
|
ctr,
|
|
102976
|
-
averageCpc:
|
|
102977
|
-
costPerConversion:
|
|
103094
|
+
averageCpc: round23(averageCpc),
|
|
103095
|
+
costPerConversion: round23(costPerConversion)
|
|
102978
103096
|
};
|
|
102979
103097
|
}
|
|
102980
103098
|
async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
102981
103099
|
const absDir = path27.resolve(snapshotDir);
|
|
102982
|
-
const metaAccountId = typeof
|
|
103100
|
+
const metaAccountId = typeof asRecord12(payload.meta)?.accountId === "string" ? asRecord12(payload.meta)?.accountId : void 0;
|
|
102983
103101
|
const manifest = await readManifest(snapshotDir, metaAccountId);
|
|
102984
103102
|
if (!manifest) {
|
|
102985
103103
|
throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C google-analysis --json-out`);
|
|
102986
103104
|
}
|
|
102987
103105
|
const accountId = manifest.googleAnalysis?.accountId ?? metaAccountId;
|
|
102988
103106
|
const dateRange = manifest.googleAnalysis?.dateRange;
|
|
102989
|
-
const preferredDateTag = dateTagFromRange(dateRange) ?? dateTagFromMeta(
|
|
103107
|
+
const preferredDateTag = dateTagFromRange(dateRange) ?? dateTagFromMeta(asRecord12(payload.meta) ?? void 0);
|
|
102990
103108
|
const sectionMap = /* @__PURE__ */ new Map();
|
|
102991
103109
|
for (const art of manifest.artifacts ?? []) {
|
|
102992
103110
|
const data = await loadSectionFile(absDir, art.file);
|
|
@@ -102994,8 +103112,8 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
102994
103112
|
}
|
|
102995
103113
|
await recoverMissingReportSections(sectionMap, absDir, accountId, preferredDateTag);
|
|
102996
103114
|
const overviewRecord = record(sectionMap.get("overview"));
|
|
102997
|
-
const currentPeriod =
|
|
102998
|
-
const previousPeriod =
|
|
103115
|
+
const currentPeriod = asRecord12(overviewRecord?.currentPeriod);
|
|
103116
|
+
const previousPeriod = asRecord12(overviewRecord?.previousPeriod);
|
|
102999
103117
|
const meta = { ...payload.meta ?? {} };
|
|
103000
103118
|
if (!meta.accountId && accountId) meta.accountId = accountId;
|
|
103001
103119
|
if (!meta.currency) {
|
|
@@ -103022,20 +103140,20 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
103022
103140
|
}
|
|
103023
103141
|
}
|
|
103024
103142
|
if (overviewRecord) {
|
|
103025
|
-
const activeDays =
|
|
103143
|
+
const activeDays = num3(overviewRecord.activeDays);
|
|
103026
103144
|
if (activeDays != null) kpis.activeDays = Math.round(activeDays);
|
|
103027
|
-
const averageDailyCost =
|
|
103028
|
-
if (averageDailyCost != null) kpis.averageDailyCost =
|
|
103145
|
+
const averageDailyCost = num3(overviewRecord.averageDailyCost);
|
|
103146
|
+
if (averageDailyCost != null) kpis.averageDailyCost = round23(averageDailyCost);
|
|
103029
103147
|
}
|
|
103030
103148
|
if (previousPeriod) {
|
|
103031
103149
|
kpis.previousPeriod = {
|
|
103032
|
-
cost:
|
|
103033
|
-
impressions:
|
|
103034
|
-
clicks:
|
|
103035
|
-
conversions:
|
|
103036
|
-
ctr:
|
|
103037
|
-
averageCpc:
|
|
103038
|
-
costPerConversion:
|
|
103150
|
+
cost: num3(previousPeriod.spend),
|
|
103151
|
+
impressions: num3(previousPeriod.impressions),
|
|
103152
|
+
clicks: num3(previousPeriod.clicks),
|
|
103153
|
+
conversions: num3(previousPeriod.conversions),
|
|
103154
|
+
ctr: num3(previousPeriod.ctr),
|
|
103155
|
+
averageCpc: num3(previousPeriod.averageCpc),
|
|
103156
|
+
costPerConversion: num3(previousPeriod.costPerConversion)
|
|
103039
103157
|
};
|
|
103040
103158
|
}
|
|
103041
103159
|
const charts = { ...payload.charts ?? {} };
|
|
@@ -103047,25 +103165,25 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
103047
103165
|
);
|
|
103048
103166
|
charts.dailyTrend = {
|
|
103049
103167
|
dates: sorted.map((r) => String(r.date ?? "\u2014")),
|
|
103050
|
-
cost: sorted.map((r) =>
|
|
103051
|
-
conversions: sorted.map((r) => Math.round(
|
|
103052
|
-
cpa: sorted.map((r) =>
|
|
103168
|
+
cost: sorted.map((r) => round23(num3(r.spend)) ?? 0),
|
|
103169
|
+
conversions: sorted.map((r) => Math.round(num3(r.conversions) ?? 0)),
|
|
103170
|
+
cpa: sorted.map((r) => round23(num3(r.costPerConversion)) ?? 0)
|
|
103053
103171
|
};
|
|
103054
103172
|
}
|
|
103055
103173
|
}
|
|
103056
103174
|
const tables = { ...payload.tables ?? {} };
|
|
103057
103175
|
if (dimensionSummaryRecord) {
|
|
103058
103176
|
tables.dimensionSummary = {
|
|
103059
|
-
cost:
|
|
103060
|
-
impressions:
|
|
103061
|
-
clicks:
|
|
103062
|
-
conversions:
|
|
103063
|
-
ctr:
|
|
103064
|
-
averageCpc:
|
|
103065
|
-
costPerConversion:
|
|
103066
|
-
searchImpressionShare:
|
|
103067
|
-
searchBudgetLostImpressionShare:
|
|
103068
|
-
searchRankLostImpressionShare:
|
|
103177
|
+
cost: num3(dimensionSummaryRecord.spend),
|
|
103178
|
+
impressions: num3(dimensionSummaryRecord.impressions),
|
|
103179
|
+
clicks: num3(dimensionSummaryRecord.clicks),
|
|
103180
|
+
conversions: num3(dimensionSummaryRecord.conversions),
|
|
103181
|
+
ctr: num3(dimensionSummaryRecord.ctr),
|
|
103182
|
+
averageCpc: num3(dimensionSummaryRecord.averageCpc),
|
|
103183
|
+
costPerConversion: num3(dimensionSummaryRecord.costPerConversion),
|
|
103184
|
+
searchImpressionShare: num3(dimensionSummaryRecord.searchImpressionShare),
|
|
103185
|
+
searchBudgetLostImpressionShare: num3(dimensionSummaryRecord.searchBudgetLostImpressionShare),
|
|
103186
|
+
searchRankLostImpressionShare: num3(dimensionSummaryRecord.searchRankLostImpressionShare)
|
|
103069
103187
|
};
|
|
103070
103188
|
}
|
|
103071
103189
|
if (campaignRows3.length > 0) {
|
|
@@ -103128,13 +103246,13 @@ function textLen(value) {
|
|
|
103128
103246
|
function asArray(value) {
|
|
103129
103247
|
return Array.isArray(value) ? value : [];
|
|
103130
103248
|
}
|
|
103131
|
-
function
|
|
103249
|
+
function asRecord13(value) {
|
|
103132
103250
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
103133
103251
|
return value;
|
|
103134
103252
|
}
|
|
103135
103253
|
return null;
|
|
103136
103254
|
}
|
|
103137
|
-
function
|
|
103255
|
+
function stringList2(value) {
|
|
103138
103256
|
return asArray(value).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
103139
103257
|
}
|
|
103140
103258
|
function pushMissing(missing, id, chapter, dimension, hint) {
|
|
@@ -103142,7 +103260,7 @@ function pushMissing(missing, id, chapter, dimension, hint) {
|
|
|
103142
103260
|
}
|
|
103143
103261
|
function validateOkkiWeeklyReportContent(data) {
|
|
103144
103262
|
const missing = [];
|
|
103145
|
-
const meta =
|
|
103263
|
+
const meta = asRecord13(data.meta) ?? {};
|
|
103146
103264
|
if (!textLen(meta.accountId)) {
|
|
103147
103265
|
pushMissing(
|
|
103148
103266
|
missing,
|
|
@@ -103152,7 +103270,7 @@ function validateOkkiWeeklyReportContent(data) {
|
|
|
103152
103270
|
"meta.accountId \u5FC5\u586B\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 overview/manifest"
|
|
103153
103271
|
);
|
|
103154
103272
|
}
|
|
103155
|
-
const snapshotSections =
|
|
103273
|
+
const snapshotSections = asRecord13(data.snapshot?.sections) ?? {};
|
|
103156
103274
|
const snapshotPresent = {};
|
|
103157
103275
|
for (const section of OKKI_SNAPSHOT_SECTIONS) {
|
|
103158
103276
|
const present = snapshotSections[section] === true;
|
|
@@ -103167,14 +103285,14 @@ function validateOkkiWeeklyReportContent(data) {
|
|
|
103167
103285
|
);
|
|
103168
103286
|
}
|
|
103169
103287
|
}
|
|
103170
|
-
const narrative =
|
|
103171
|
-
const sheetAnalysis =
|
|
103172
|
-
const review =
|
|
103288
|
+
const narrative = asRecord13(data.narrative) ?? {};
|
|
103289
|
+
const sheetAnalysis = asRecord13(narrative.sheetAnalysis) ?? {};
|
|
103290
|
+
const review = asRecord13(narrative.review) ?? {};
|
|
103173
103291
|
const sheetAnalysisCounts = {};
|
|
103174
103292
|
for (const key of OKKI_SHEET_KEYS) {
|
|
103175
|
-
const block =
|
|
103176
|
-
const summary =
|
|
103177
|
-
const suggestions =
|
|
103293
|
+
const block = asRecord13(sheetAnalysis[key]);
|
|
103294
|
+
const summary = stringList2(block?.summary);
|
|
103295
|
+
const suggestions = stringList2(block?.suggestions);
|
|
103178
103296
|
sheetAnalysisCounts[key] = { summary: summary.length, suggestions: suggestions.length };
|
|
103179
103297
|
const label = SHEET_LABELS[key];
|
|
103180
103298
|
if (summary.length < 1) {
|
|
@@ -103198,7 +103316,7 @@ function validateOkkiWeeklyReportContent(data) {
|
|
|
103198
103316
|
}
|
|
103199
103317
|
const reviewFilled = {};
|
|
103200
103318
|
for (const key of OKKI_REVIEW_KEYS) {
|
|
103201
|
-
const block =
|
|
103319
|
+
const block = asRecord13(review[key]);
|
|
103202
103320
|
const overview = textLen(block?.overview);
|
|
103203
103321
|
const summary = textLen(block?.summary);
|
|
103204
103322
|
const suggestion = textLen(block?.suggestion);
|
|
@@ -103232,6 +103350,17 @@ function validateOkkiWeeklyReportContent(data) {
|
|
|
103232
103350
|
);
|
|
103233
103351
|
}
|
|
103234
103352
|
}
|
|
103353
|
+
const deviceRows2 = Array.isArray(data.tables?.devices) ? data.tables.devices : [];
|
|
103354
|
+
const deviceSpend = summarizeOkkiDeviceSpend(deviceRows2.filter((row) => asRecord13(row) !== null));
|
|
103355
|
+
if (deviceSpend.total > 0 && deviceSpend.desktop >= deviceSpend.mobile && narrativeClaimsMobileSpendLeads(collectOkkiNarrativeText(data))) {
|
|
103356
|
+
pushMissing(
|
|
103357
|
+
missing,
|
|
103358
|
+
"narrative-devices-spend-lead",
|
|
103359
|
+
"\u8868\u672B\u5206\u6790 \xB7 \u8BBE\u5907",
|
|
103360
|
+
"narrative \u8BBE\u5907\u6D88\u8017\u4E3B\u6B21",
|
|
103361
|
+
`\u6587\u6848\u5199\u79FB\u52A8\u7AEF\u6D88\u8017/\u5360\u6BD4\u66F4\u9AD8\uFF0C\u4F46\u8868\u5185\u684C\u9762 \xA5${deviceSpend.desktop.toFixed(2)}\uFF08${(deviceSpend.desktopShare * 100).toFixed(1)}%\uFF09\u2265 \u79FB\u52A8 \xA5${deviceSpend.mobile.toFixed(2)}\uFF08${(deviceSpend.mobileShare * 100).toFixed(1)}%\uFF09\u3002\u8BF7\u6309\u8868\u5185\u8D39\u7528\u5408\u8BA1\u6539\u5199\uFF0C\u7981\u6B62\u628A\u884C\u6570\u6216\u5C55\u793A\u5F53\u6210\u6D88\u8017\u5360\u6BD4\u3002`
|
|
103362
|
+
);
|
|
103363
|
+
}
|
|
103235
103364
|
return {
|
|
103236
103365
|
ok: missing.length === 0,
|
|
103237
103366
|
missing,
|
|
@@ -103253,6 +103382,7 @@ var OKKI_SHEET_KEYS, OKKI_REVIEW_KEYS, OKKI_SNAPSHOT_SECTIONS, SHEET_LABELS, REV
|
|
|
103253
103382
|
var init_okki_report_content = __esm({
|
|
103254
103383
|
"src/commands/google-analysis/okki-report-content.ts"() {
|
|
103255
103384
|
"use strict";
|
|
103385
|
+
init_okki_display();
|
|
103256
103386
|
OKKI_SHEET_KEYS = [
|
|
103257
103387
|
"campaigns",
|
|
103258
103388
|
"keywords",
|
|
@@ -103301,16 +103431,16 @@ var init_okki_report_content = __esm({
|
|
|
103301
103431
|
// src/commands/google-analysis/okki-merge-snapshot.ts
|
|
103302
103432
|
import * as fs23 from "fs/promises";
|
|
103303
103433
|
import * as path28 from "path";
|
|
103304
|
-
function
|
|
103434
|
+
function num4(value) {
|
|
103305
103435
|
const n = Number(value);
|
|
103306
103436
|
return Number.isFinite(n) ? n : void 0;
|
|
103307
103437
|
}
|
|
103308
|
-
function
|
|
103438
|
+
function round24(value) {
|
|
103309
103439
|
return value == null ? void 0 : Math.round(value * 100) / 100;
|
|
103310
103440
|
}
|
|
103311
103441
|
function items2(envelope) {
|
|
103312
103442
|
if (!envelope || !Array.isArray(envelope.items)) return [];
|
|
103313
|
-
return envelope.items.filter((row) =>
|
|
103443
|
+
return envelope.items.filter((row) => asRecord13(row) !== null);
|
|
103314
103444
|
}
|
|
103315
103445
|
function dateTagFromRange2(dateRange) {
|
|
103316
103446
|
if (!dateRange || dateRange.mode !== "range") return void 0;
|
|
@@ -103354,14 +103484,14 @@ async function loadSectionFile2(snapshotDir, file) {
|
|
|
103354
103484
|
}
|
|
103355
103485
|
async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
|
|
103356
103486
|
const absDir = path28.resolve(snapshotDir);
|
|
103357
|
-
const metaAccountId = typeof
|
|
103487
|
+
const metaAccountId = typeof asRecord13(payload.meta)?.accountId === "string" ? asRecord13(payload.meta)?.accountId : void 0;
|
|
103358
103488
|
const manifest = await readManifest2(absDir, metaAccountId);
|
|
103359
103489
|
if (!manifest) {
|
|
103360
103490
|
throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C google-analysis --json-out`);
|
|
103361
103491
|
}
|
|
103362
103492
|
const accountId = manifest.googleAnalysis?.accountId ?? metaAccountId;
|
|
103363
103493
|
const dateRange = manifest.googleAnalysis?.dateRange;
|
|
103364
|
-
const preferredDateTag = dateTagFromRange2(dateRange) ?? dateTagFromMeta2(
|
|
103494
|
+
const preferredDateTag = dateTagFromRange2(dateRange) ?? dateTagFromMeta2(asRecord13(payload.meta) ?? void 0);
|
|
103365
103495
|
const sectionMap = /* @__PURE__ */ new Map();
|
|
103366
103496
|
for (const art of manifest.artifacts ?? []) {
|
|
103367
103497
|
const data = await loadSectionFile2(absDir, art.file);
|
|
@@ -103387,7 +103517,7 @@ async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
|
|
|
103387
103517
|
snapshotPresent[section] = sectionMap.has(section);
|
|
103388
103518
|
}
|
|
103389
103519
|
const overviewRecord = sectionMap.get("overview")?.record ?? null;
|
|
103390
|
-
const currentPeriod =
|
|
103520
|
+
const currentPeriod = asRecord13(overviewRecord?.currentPeriod);
|
|
103391
103521
|
const meta = { ...payload.meta ?? {} };
|
|
103392
103522
|
if (!meta.accountId && accountId) meta.accountId = accountId;
|
|
103393
103523
|
if (!meta.currency) {
|
|
@@ -103397,16 +103527,17 @@ async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
|
|
|
103397
103527
|
if (!meta.startDate && dateRange && dateRange.mode === "range") meta.startDate = dateRange.start;
|
|
103398
103528
|
if (!meta.endDate && dateRange && dateRange.mode === "range") meta.endDate = dateRange.end;
|
|
103399
103529
|
const campaignRows3 = items2(sectionMap.get("campaigns"));
|
|
103530
|
+
const deviceRows2 = items2(sectionMap.get("campaign-device"));
|
|
103400
103531
|
const campaignKpis = sumCampaignRowsToPeriodKpis(campaignRows3);
|
|
103401
103532
|
const kpis = { ...payload.kpis ?? {} };
|
|
103402
103533
|
const kpiSource = campaignKpis ?? (currentPeriod ? {
|
|
103403
|
-
cost:
|
|
103404
|
-
impressions:
|
|
103405
|
-
clicks:
|
|
103406
|
-
conversions:
|
|
103407
|
-
ctr:
|
|
103408
|
-
averageCpc:
|
|
103409
|
-
costPerConversion:
|
|
103534
|
+
cost: num4(currentPeriod.spend),
|
|
103535
|
+
impressions: num4(currentPeriod.impressions),
|
|
103536
|
+
clicks: num4(currentPeriod.clicks),
|
|
103537
|
+
conversions: num4(currentPeriod.conversions),
|
|
103538
|
+
ctr: num4(currentPeriod.ctr),
|
|
103539
|
+
averageCpc: num4(currentPeriod.averageCpc),
|
|
103540
|
+
costPerConversion: num4(currentPeriod.costPerConversion)
|
|
103410
103541
|
} : null);
|
|
103411
103542
|
if (kpiSource) {
|
|
103412
103543
|
if (kpiSource.cost != null) kpis.cost = kpiSource.cost;
|
|
@@ -103418,10 +103549,10 @@ async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
|
|
|
103418
103549
|
if (kpiSource.costPerConversion != null) kpis.costPerConversion = kpiSource.costPerConversion;
|
|
103419
103550
|
}
|
|
103420
103551
|
if (overviewRecord) {
|
|
103421
|
-
const activeDays =
|
|
103552
|
+
const activeDays = num4(overviewRecord.activeDays);
|
|
103422
103553
|
if (activeDays != null) kpis.activeDays = Math.round(activeDays);
|
|
103423
|
-
const averageDailyCost =
|
|
103424
|
-
if (averageDailyCost != null) kpis.averageDailyCost =
|
|
103554
|
+
const averageDailyCost = num4(overviewRecord.averageDailyCost);
|
|
103555
|
+
if (averageDailyCost != null) kpis.averageDailyCost = round24(averageDailyCost);
|
|
103425
103556
|
}
|
|
103426
103557
|
return {
|
|
103427
103558
|
...payload,
|
|
@@ -103431,9 +103562,12 @@ async function mergeOkkiSnapshotIntoReport(payload, snapshotDir) {
|
|
|
103431
103562
|
campaigns: campaignRows3,
|
|
103432
103563
|
keywords: items2(sectionMap.get("keywords")),
|
|
103433
103564
|
searchTerms: items2(sectionMap.get("search-terms")),
|
|
103434
|
-
devices:
|
|
103565
|
+
devices: deviceRows2,
|
|
103435
103566
|
geo: items2(sectionMap.get("campaign-geo-matched"))
|
|
103436
103567
|
},
|
|
103568
|
+
facts: {
|
|
103569
|
+
devices: summarizeOkkiDeviceSpend(deviceRows2)
|
|
103570
|
+
},
|
|
103437
103571
|
snapshot: { sections: snapshotPresent }
|
|
103438
103572
|
};
|
|
103439
103573
|
}
|
|
@@ -103441,37 +103575,38 @@ var init_okki_merge_snapshot = __esm({
|
|
|
103441
103575
|
"src/commands/google-analysis/okki-merge-snapshot.ts"() {
|
|
103442
103576
|
"use strict";
|
|
103443
103577
|
init_google_analysis();
|
|
103578
|
+
init_okki_display();
|
|
103444
103579
|
init_merge_snapshot();
|
|
103445
103580
|
init_okki_report_content();
|
|
103446
103581
|
}
|
|
103447
103582
|
});
|
|
103448
103583
|
|
|
103449
103584
|
// src/commands/google-analysis/okki-render-excel.ts
|
|
103450
|
-
function
|
|
103585
|
+
function num5(value) {
|
|
103451
103586
|
if (value == null || value === "") return void 0;
|
|
103452
103587
|
const n = typeof value === "number" ? value : Number(value);
|
|
103453
103588
|
return Number.isFinite(n) ? n : void 0;
|
|
103454
103589
|
}
|
|
103455
|
-
function
|
|
103590
|
+
function round25(value) {
|
|
103456
103591
|
return Math.round(value * 100) / 100;
|
|
103457
103592
|
}
|
|
103458
103593
|
function money(value) {
|
|
103459
|
-
return
|
|
103594
|
+
return round25(num5(value) ?? 0);
|
|
103460
103595
|
}
|
|
103461
103596
|
function count(value) {
|
|
103462
|
-
const n =
|
|
103463
|
-
return Number.isInteger(n) ? n :
|
|
103597
|
+
const n = num5(value) ?? 0;
|
|
103598
|
+
return Number.isInteger(n) ? n : round25(n);
|
|
103464
103599
|
}
|
|
103465
103600
|
function rate(value) {
|
|
103466
|
-
return
|
|
103601
|
+
return num5(value) ?? 0;
|
|
103467
103602
|
}
|
|
103468
103603
|
function dashOrRate(numer, denom) {
|
|
103469
103604
|
if (denom <= 0) return "\u2014";
|
|
103470
|
-
return
|
|
103605
|
+
return round25(numer / denom);
|
|
103471
103606
|
}
|
|
103472
103607
|
function dashOrMoney(numer, denom) {
|
|
103473
103608
|
if (denom <= 0) return "\u2014";
|
|
103474
|
-
return
|
|
103609
|
+
return round25(numer / denom);
|
|
103475
103610
|
}
|
|
103476
103611
|
function str(value, fallback = "\u2014") {
|
|
103477
103612
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
@@ -103505,18 +103640,18 @@ function targetingStatus(row) {
|
|
|
103505
103640
|
return TARGETING_STATUS_ZH[raw] ?? (raw || "\u2014");
|
|
103506
103641
|
}
|
|
103507
103642
|
function conversionsPair(row) {
|
|
103508
|
-
const conv =
|
|
103509
|
-
const all =
|
|
103643
|
+
const conv = num5(row.conversions) ?? 0;
|
|
103644
|
+
const all = num5(row.allConversions);
|
|
103510
103645
|
return { all: all ?? conv, conv };
|
|
103511
103646
|
}
|
|
103512
103647
|
function interactionRateOf(row, impressions, interactions) {
|
|
103513
103648
|
if (interactions <= 0) return "\u2014";
|
|
103514
|
-
const parsed = typeof row.interactionRate === "string" ? Number.parseFloat(row.interactionRate) :
|
|
103649
|
+
const parsed = typeof row.interactionRate === "string" ? Number.parseFloat(row.interactionRate) : num5(row.interactionRate);
|
|
103515
103650
|
if (parsed != null) return parsed;
|
|
103516
103651
|
return dashOrRate(interactions, impressions);
|
|
103517
103652
|
}
|
|
103518
103653
|
function sortBySpendDesc(rows) {
|
|
103519
|
-
return [...rows].sort((a, b) => (
|
|
103654
|
+
return [...rows].sort((a, b) => (num5(b.spend) ?? 0) - (num5(a.spend) ?? 0));
|
|
103520
103655
|
}
|
|
103521
103656
|
function prefixLines(items3, firstPrefix) {
|
|
103522
103657
|
return items3.map((text2, idx) => {
|
|
@@ -103525,18 +103660,18 @@ function prefixLines(items3, firstPrefix) {
|
|
|
103525
103660
|
});
|
|
103526
103661
|
}
|
|
103527
103662
|
function analysisLines(data, key) {
|
|
103528
|
-
const block =
|
|
103529
|
-
const rec =
|
|
103663
|
+
const block = asRecord13(asRecord13(data.narrative)?.sheetAnalysis)?.[key];
|
|
103664
|
+
const rec = asRecord13(block);
|
|
103530
103665
|
return [
|
|
103531
|
-
...prefixLines(
|
|
103532
|
-
...prefixLines(
|
|
103666
|
+
...prefixLines(stringList2(rec?.summary), "\u603B\u7ED3\uFF1A"),
|
|
103667
|
+
...prefixLines(stringList2(rec?.suggestions), "\u5EFA\u8BAE\uFF1A")
|
|
103533
103668
|
];
|
|
103534
103669
|
}
|
|
103535
103670
|
function reviewLines(data) {
|
|
103536
|
-
const review =
|
|
103671
|
+
const review = asRecord13(asRecord13(data.narrative)?.review) ?? {};
|
|
103537
103672
|
const lines = [];
|
|
103538
103673
|
OKKI_REVIEW_KEYS.forEach((key, idx) => {
|
|
103539
|
-
const block =
|
|
103674
|
+
const block = asRecord13(review[key]) ?? {};
|
|
103540
103675
|
lines.push(`${idx + 1}\u3001${REVIEW_LABELS2[key]}\uFF1A${str(block.overview, "")}`);
|
|
103541
103676
|
lines.push(` \u603B\u7ED3\uFF1A${str(block.summary, "")}`);
|
|
103542
103677
|
lines.push(` \u5EFA\u8BAE\uFF1A${str(block.suggestion, "")}`);
|
|
@@ -103552,9 +103687,9 @@ function campaignRows(data) {
|
|
|
103552
103687
|
let conv = 0;
|
|
103553
103688
|
const body = rows.map((row) => {
|
|
103554
103689
|
const pair = conversionsPair(row);
|
|
103555
|
-
spend +=
|
|
103556
|
-
impressions +=
|
|
103557
|
-
clicks +=
|
|
103690
|
+
spend += num5(row.spend) ?? 0;
|
|
103691
|
+
impressions += num5(row.impressions) ?? 0;
|
|
103692
|
+
clicks += num5(row.clicks) ?? 0;
|
|
103558
103693
|
allConv += pair.all;
|
|
103559
103694
|
conv += pair.conv;
|
|
103560
103695
|
return [
|
|
@@ -103567,7 +103702,7 @@ function campaignRows(data) {
|
|
|
103567
103702
|
money(row.averageCpc),
|
|
103568
103703
|
count(pair.all),
|
|
103569
103704
|
count(pair.conv),
|
|
103570
|
-
|
|
103705
|
+
num5(row.costPerConversion) == null && pair.conv <= 0 ? "\u2014" : money(row.costPerConversion),
|
|
103571
103706
|
rate(row.conversionRate)
|
|
103572
103707
|
];
|
|
103573
103708
|
});
|
|
@@ -103576,7 +103711,7 @@ function campaignRows(data) {
|
|
|
103576
103711
|
total: [
|
|
103577
103712
|
"\u603B\u8BA1",
|
|
103578
103713
|
"--",
|
|
103579
|
-
|
|
103714
|
+
round25(spend),
|
|
103580
103715
|
Math.round(impressions),
|
|
103581
103716
|
Math.round(clicks),
|
|
103582
103717
|
dashOrRate(clicks, impressions),
|
|
@@ -103590,7 +103725,7 @@ function campaignRows(data) {
|
|
|
103590
103725
|
}
|
|
103591
103726
|
function keywordRows(data) {
|
|
103592
103727
|
return sortBySpendDesc(data.tables?.keywords ?? []).map((row) => [
|
|
103593
|
-
|
|
103728
|
+
formatOkkiKeywordDisplay(row.keyword, row.keywordMatchTypeZh || row.keywordMatchType),
|
|
103594
103729
|
str(row.keywordMatchTypeZh || row.keywordMatchType),
|
|
103595
103730
|
str(row.campaignName),
|
|
103596
103731
|
str(row.adGroupName),
|
|
@@ -103600,7 +103735,7 @@ function keywordRows(data) {
|
|
|
103600
103735
|
rate(row.ctr),
|
|
103601
103736
|
money(row.averageCpc),
|
|
103602
103737
|
count(row.conversions),
|
|
103603
|
-
|
|
103738
|
+
num5(row.costPerConversion) == null && (num5(row.conversions) ?? 0) <= 0 ? "\u2014" : money(row.costPerConversion),
|
|
103604
103739
|
rate(row.conversionRate)
|
|
103605
103740
|
]);
|
|
103606
103741
|
}
|
|
@@ -103611,14 +103746,17 @@ function searchTermRows(data) {
|
|
|
103611
103746
|
targetingStatus(row),
|
|
103612
103747
|
str(row.campaignName),
|
|
103613
103748
|
str(row.adGroupName),
|
|
103614
|
-
|
|
103749
|
+
formatOkkiKeywordDisplay(
|
|
103750
|
+
row.keywordText || row.keyword,
|
|
103751
|
+
row.keywordMatchTypeZh || row.keywordMatchType || row.matchTypeZh || row.matchType
|
|
103752
|
+
),
|
|
103615
103753
|
count(row.impressions),
|
|
103616
103754
|
count(row.clicks),
|
|
103617
103755
|
rate(row.ctr),
|
|
103618
103756
|
money(row.averageCpc),
|
|
103619
103757
|
money(row.spend),
|
|
103620
103758
|
count(row.conversions),
|
|
103621
|
-
|
|
103759
|
+
num5(row.costPerConversion) == null && (num5(row.conversions) ?? 0) <= 0 ? "\u2014" : money(row.costPerConversion),
|
|
103622
103760
|
rate(row.conversionRate)
|
|
103623
103761
|
]);
|
|
103624
103762
|
}
|
|
@@ -103637,7 +103775,7 @@ function deviceRows(data) {
|
|
|
103637
103775
|
money(row.spend),
|
|
103638
103776
|
count(pair.all),
|
|
103639
103777
|
count(pair.conv),
|
|
103640
|
-
|
|
103778
|
+
num5(row.costPerConversion) == null && pair.conv <= 0 ? "\u2014" : money(row.costPerConversion),
|
|
103641
103779
|
rate(row.conversionRate)
|
|
103642
103780
|
];
|
|
103643
103781
|
});
|
|
@@ -103645,8 +103783,8 @@ function deviceRows(data) {
|
|
|
103645
103783
|
function geoRows(data) {
|
|
103646
103784
|
return sortBySpendDesc(data.tables?.geo ?? []).map((row) => {
|
|
103647
103785
|
const pair = conversionsPair(row);
|
|
103648
|
-
const impressions =
|
|
103649
|
-
const interactions =
|
|
103786
|
+
const impressions = num5(row.impressions) ?? 0;
|
|
103787
|
+
const interactions = okkiGeoInteractionCount(row);
|
|
103650
103788
|
return [
|
|
103651
103789
|
str(row.countryOrRegionZh || row.countryNameZh || row.countryOrRegion),
|
|
103652
103790
|
str(row.campaignName),
|
|
@@ -103654,18 +103792,18 @@ function geoRows(data) {
|
|
|
103654
103792
|
interactions > 0 ? count(interactions) : 0,
|
|
103655
103793
|
interactionRateOf(row, impressions, interactions),
|
|
103656
103794
|
money(row.spend),
|
|
103657
|
-
dashOrMoney(
|
|
103795
|
+
dashOrMoney(num5(row.spend) ?? 0, interactions),
|
|
103658
103796
|
count(row.clicks),
|
|
103659
103797
|
rate(row.ctr),
|
|
103660
103798
|
count(pair.all),
|
|
103661
103799
|
count(pair.conv),
|
|
103662
|
-
|
|
103800
|
+
num5(row.costPerConversion) == null && pair.conv <= 0 ? "\u2014" : money(row.costPerConversion),
|
|
103663
103801
|
rate(row.conversionRate)
|
|
103664
103802
|
];
|
|
103665
103803
|
});
|
|
103666
103804
|
}
|
|
103667
103805
|
function buildOkkiWeeklySheetDrafts(data) {
|
|
103668
|
-
const meta =
|
|
103806
|
+
const meta = asRecord13(data.meta) ?? {};
|
|
103669
103807
|
const period = formatOkkiZhDateRange(str(meta.startDate, ""), str(meta.endDate, ""));
|
|
103670
103808
|
const campaigns = campaignRows(data);
|
|
103671
103809
|
const drafts = {
|
|
@@ -103823,6 +103961,7 @@ var init_okki_render_excel = __esm({
|
|
|
103823
103961
|
"src/commands/google-analysis/okki-render-excel.ts"() {
|
|
103824
103962
|
"use strict";
|
|
103825
103963
|
init_xlsx_style_engine();
|
|
103964
|
+
init_okki_display();
|
|
103826
103965
|
init_okki_report_content();
|
|
103827
103966
|
REVIEW_LABELS2 = {
|
|
103828
103967
|
account: "\u8D26\u6237\u5C42\u7EA7",
|
|
@@ -103925,7 +104064,7 @@ function textLen2(value) {
|
|
|
103925
104064
|
function asArray2(value) {
|
|
103926
104065
|
return Array.isArray(value) ? value : [];
|
|
103927
104066
|
}
|
|
103928
|
-
function
|
|
104067
|
+
function asRecord14(value) {
|
|
103929
104068
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
103930
104069
|
return value;
|
|
103931
104070
|
}
|
|
@@ -103936,9 +104075,9 @@ function pushMissing2(missing, id, chapter, dimension, hint) {
|
|
|
103936
104075
|
}
|
|
103937
104076
|
function validateGooglePeriodReportContent(data) {
|
|
103938
104077
|
const missing = [];
|
|
103939
|
-
const meta =
|
|
103940
|
-
const narrative =
|
|
103941
|
-
const sections =
|
|
104078
|
+
const meta = asRecord14(data.meta) ?? {};
|
|
104079
|
+
const narrative = asRecord14(data.narrative) ?? {};
|
|
104080
|
+
const sections = asRecord14(narrative.sections) ?? {};
|
|
103942
104081
|
if (!textLen2(meta.accountId)) {
|
|
103943
104082
|
pushMissing2(
|
|
103944
104083
|
missing,
|
|
@@ -103958,7 +104097,7 @@ function validateGooglePeriodReportContent(data) {
|
|
|
103958
104097
|
"\u22651 \u6BB5\u6267\u884C\u6458\u8981\uFF0C\u6982\u8FF0\u672C\u671F\u6D88\u8017/\u8F6C\u5316/CTR/CPA \u8D70\u52BF"
|
|
103959
104098
|
);
|
|
103960
104099
|
}
|
|
103961
|
-
const kpis =
|
|
104100
|
+
const kpis = asRecord14(data.kpis);
|
|
103962
104101
|
const hasCoreKpi = ["cost", "impressions", "clicks"].some(
|
|
103963
104102
|
(key) => Number.isFinite(Number(kpis?.[key]))
|
|
103964
104103
|
);
|
|
@@ -103974,7 +104113,7 @@ function validateGooglePeriodReportContent(data) {
|
|
|
103974
104113
|
const sectionAnalysisCounts = {};
|
|
103975
104114
|
const sectionSuggestionCounts = {};
|
|
103976
104115
|
for (const key of SECTION_KEYS) {
|
|
103977
|
-
const section =
|
|
104116
|
+
const section = asRecord14(sections[key]);
|
|
103978
104117
|
const analysis = asArray2(section?.analysis).filter((s) => textLen2(s) > 0);
|
|
103979
104118
|
const suggestions = asArray2(section?.suggestions).filter((s) => textLen2(s) > 0);
|
|
103980
104119
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -104000,7 +104139,7 @@ function validateGooglePeriodReportContent(data) {
|
|
|
104000
104139
|
}
|
|
104001
104140
|
const recommendations = asArray2(narrative.recommendations).filter((r) => {
|
|
104002
104141
|
if (typeof r === "string") return textLen2(r) > 0;
|
|
104003
|
-
const rec =
|
|
104142
|
+
const rec = asRecord14(r);
|
|
104004
104143
|
return textLen2(rec?.content) > 0;
|
|
104005
104144
|
});
|
|
104006
104145
|
if (recommendations.length < 3) {
|
|
@@ -104087,7 +104226,7 @@ async function readJsonFile3(filePath) {
|
|
|
104087
104226
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
104088
104227
|
}
|
|
104089
104228
|
}
|
|
104090
|
-
function
|
|
104229
|
+
function asRecord15(value) {
|
|
104091
104230
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
104092
104231
|
return value;
|
|
104093
104232
|
}
|
|
@@ -104102,7 +104241,7 @@ function injectReportData2(html, payload) {
|
|
|
104102
104241
|
}
|
|
104103
104242
|
async function runGoogleAnalysisRender(opts) {
|
|
104104
104243
|
const dataPath = path30.resolve(opts.dataFile);
|
|
104105
|
-
let data =
|
|
104244
|
+
let data = asRecord15(await readJsonFile3(dataPath));
|
|
104106
104245
|
if (opts.snapshotDir?.trim()) {
|
|
104107
104246
|
data = await mergeGoogleAnalysisSnapshotIntoReport(data, path30.resolve(opts.snapshotDir));
|
|
104108
104247
|
}
|
|
@@ -130793,7 +130932,105 @@ function buildDiagnosisScaffold(signals) {
|
|
|
130793
130932
|
}
|
|
130794
130933
|
|
|
130795
130934
|
// src/commands/website-diagnosis/lighthouse-with-fallback.ts
|
|
130935
|
+
init_dist();
|
|
130796
130936
|
init_auth();
|
|
130937
|
+
var LIGHTHOUSE_MAX_ATTEMPTS = 3;
|
|
130938
|
+
var LIGHTHOUSE_RETRY_DELAYS_MS = [2e3, 4e3];
|
|
130939
|
+
function lighthouseHasScores2(lh) {
|
|
130940
|
+
if (!lh || typeof lh !== "object") return false;
|
|
130941
|
+
const d = lh.desktop?.score;
|
|
130942
|
+
const m = lh.mobile?.score;
|
|
130943
|
+
return typeof d === "number" && Number.isFinite(d) || typeof m === "number" && Number.isFinite(m);
|
|
130944
|
+
}
|
|
130945
|
+
function isLighthouseRetryableError(error) {
|
|
130946
|
+
const status = parseHttpStatusFromError(new Error(error));
|
|
130947
|
+
if (status === 401) return false;
|
|
130948
|
+
return true;
|
|
130949
|
+
}
|
|
130950
|
+
function defaultSleep(ms) {
|
|
130951
|
+
return new Promise((resolve25) => {
|
|
130952
|
+
setTimeout(resolve25, ms);
|
|
130953
|
+
});
|
|
130954
|
+
}
|
|
130955
|
+
async function retryLighthouseFetch(opts) {
|
|
130956
|
+
const maxAttempts = opts.maxAttempts ?? LIGHTHOUSE_MAX_ATTEMPTS;
|
|
130957
|
+
const delays = opts.delaysMs ?? LIGHTHOUSE_RETRY_DELAYS_MS;
|
|
130958
|
+
const sleep4 = opts.sleep ?? defaultSleep;
|
|
130959
|
+
let lastError = "TSO Lighthouse API \u5931\u8D25";
|
|
130960
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
130961
|
+
const result = await opts.fetchOnce();
|
|
130962
|
+
if (result.ok) return { ...result, attempts: attempt };
|
|
130963
|
+
lastError = result.error;
|
|
130964
|
+
const canRetry = isLighthouseRetryableError(result.error) && attempt < maxAttempts;
|
|
130965
|
+
if (!canRetry) return { ok: false, error: lastError, attempts: attempt };
|
|
130966
|
+
const delayMs = delays[attempt - 1] ?? delays[delays.length - 1] ?? 2e3;
|
|
130967
|
+
opts.onRetry?.({ attempt, delayMs, error: result.error });
|
|
130968
|
+
await sleep4(delayMs);
|
|
130969
|
+
}
|
|
130970
|
+
return { ok: false, error: lastError, attempts: maxAttempts };
|
|
130971
|
+
}
|
|
130972
|
+
async function tryTsoLighthouseApi(config, websiteUrl, verbose) {
|
|
130973
|
+
try {
|
|
130974
|
+
const lighthouse = await apiFetch2(
|
|
130975
|
+
`${config.apiBaseUrl}/api/WebsiteDiagnosisReports/performance?url=${encodeURIComponent(websiteUrl)}`,
|
|
130976
|
+
config,
|
|
130977
|
+
{},
|
|
130978
|
+
verbose
|
|
130979
|
+
);
|
|
130980
|
+
if (!lighthouseHasScores2(lighthouse)) {
|
|
130981
|
+
return { ok: false, error: "TSO Lighthouse API \u8FD4\u56DE\u7A7A\u5206\u6570" };
|
|
130982
|
+
}
|
|
130983
|
+
return { ok: true, lighthouse };
|
|
130984
|
+
} catch (err) {
|
|
130985
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
130986
|
+
}
|
|
130987
|
+
}
|
|
130988
|
+
async function resolveLighthouseWithFallback(opts) {
|
|
130989
|
+
const websiteUrl = opts.url;
|
|
130990
|
+
const api = await retryLighthouseFetch({
|
|
130991
|
+
fetchOnce: () => tryTsoLighthouseApi(opts.config, websiteUrl, opts.verbose),
|
|
130992
|
+
onRetry: ({ attempt, delayMs, error }) => {
|
|
130993
|
+
console.error(
|
|
130994
|
+
` \u26A0\uFE0F Lighthouse \u7B2C ${attempt} \u6B21\u5931\u8D25\uFF0C${(delayMs / 1e3).toFixed(0)}s \u540E\u91CD\u8BD5\uFF1A${error}
|
|
130995
|
+
`
|
|
130996
|
+
);
|
|
130997
|
+
}
|
|
130998
|
+
});
|
|
130999
|
+
if (api.ok) {
|
|
131000
|
+
return {
|
|
131001
|
+
source: "api",
|
|
131002
|
+
lighthouse: api.lighthouse,
|
|
131003
|
+
attempts: api.attempts
|
|
131004
|
+
};
|
|
131005
|
+
}
|
|
131006
|
+
return {
|
|
131007
|
+
source: "none",
|
|
131008
|
+
lighthouse: {},
|
|
131009
|
+
lighthouseWarning: `Lighthouse API \u91CD\u8BD5 ${api.attempts} \u6B21\u4ECD\u5931\u8D25\uFF1A${api.error}`,
|
|
131010
|
+
attempts: api.attempts
|
|
131011
|
+
};
|
|
131012
|
+
}
|
|
131013
|
+
function attachLandingPageMeta(lighthouse, resolved) {
|
|
131014
|
+
const desktop = lighthouse.desktop ?? {};
|
|
131015
|
+
const mobile = lighthouse.mobile ?? {};
|
|
131016
|
+
return {
|
|
131017
|
+
desktop: {
|
|
131018
|
+
score: desktop.score ?? 0,
|
|
131019
|
+
firstContentfulPaint: desktop.firstContentfulPaint ?? 0,
|
|
131020
|
+
firstMeaningfulPaint: desktop.firstMeaningfulPaint ?? 0,
|
|
131021
|
+
speedIndex: desktop.speedIndex ?? 0
|
|
131022
|
+
},
|
|
131023
|
+
mobile: {
|
|
131024
|
+
score: mobile.score ?? 0,
|
|
131025
|
+
firstContentfulPaint: mobile.firstContentfulPaint ?? 0,
|
|
131026
|
+
firstMeaningfulPaint: mobile.firstMeaningfulPaint ?? 0,
|
|
131027
|
+
speedIndex: mobile.speedIndex ?? 0
|
|
131028
|
+
},
|
|
131029
|
+
source: resolved.source,
|
|
131030
|
+
...resolved.lighthouseWarning ? { lighthouseWarning: resolved.lighthouseWarning } : {},
|
|
131031
|
+
lighthouseOk: resolved.source === "api"
|
|
131032
|
+
};
|
|
131033
|
+
}
|
|
130797
131034
|
|
|
130798
131035
|
// src/commands/website-diagnosis/shared.ts
|
|
130799
131036
|
init_dist();
|
|
@@ -130858,134 +131095,6 @@ async function fetchWebsiteHtml(config, websiteUrl, verbose) {
|
|
|
130858
131095
|
return picked.value;
|
|
130859
131096
|
}
|
|
130860
131097
|
|
|
130861
|
-
// src/commands/website-diagnosis/lighthouse-with-fallback.ts
|
|
130862
|
-
function lighthouseHasScores2(lh) {
|
|
130863
|
-
if (!lh || typeof lh !== "object") return false;
|
|
130864
|
-
const d = lh.desktop?.score;
|
|
130865
|
-
const m = lh.mobile?.score;
|
|
130866
|
-
return typeof d === "number" && Number.isFinite(d) || typeof m === "number" && Number.isFinite(m);
|
|
130867
|
-
}
|
|
130868
|
-
function toSimple(signals, htmlAsset) {
|
|
130869
|
-
const fetchMs = typeof htmlAsset?.downloadTime === "number" && Number.isFinite(htmlAsset.downloadTime) ? htmlAsset.downloadTime : null;
|
|
130870
|
-
const contentLength = typeof htmlAsset?.contentLength === "number" && Number.isFinite(htmlAsset.contentLength) ? htmlAsset.contentLength : signals.htmlChars || null;
|
|
130871
|
-
return {
|
|
130872
|
-
fetchMs,
|
|
130873
|
-
contentLength,
|
|
130874
|
-
finalUrl: htmlAsset?.finalUrl ?? signals.url,
|
|
130875
|
-
https: signals.https,
|
|
130876
|
-
title: signals.title,
|
|
130877
|
-
hasViewportMeta: signals.hasViewportMeta,
|
|
130878
|
-
hasNav: signals.hasNav,
|
|
130879
|
-
hasForm: signals.hasForm,
|
|
130880
|
-
hasMailto: signals.hasMailto,
|
|
130881
|
-
hasTel: signals.hasTel,
|
|
130882
|
-
analyticsHints: [...signals.analyticsHints],
|
|
130883
|
-
socialLinkCount: signals.socialLinks.length,
|
|
130884
|
-
ctaHintCount: signals.ctaHints.length,
|
|
130885
|
-
note: "TSO Lighthouse API \u4E0D\u53EF\u7528\uFF0C\u5DF2\u964D\u7EA7\u4E3A CLI \u7B80\u6613\u8BCA\u65AD\uFF08HTML \u7ED3\u6784\u4FE1\u53F7 + \u4E0B\u8F7D\u8017\u65F6\uFF09\u3002\u4E0D\u542B\u5B98\u65B9 Lighthouse score/FCP\uFF1B\u7981\u6B62\u628A fetchMs \u5199\u6210 FCP \u6216\u6027\u80FD\u6307\u6570\u3002"
|
|
130886
|
-
};
|
|
130887
|
-
}
|
|
130888
|
-
async function tryTsoLighthouseApi(config, websiteUrl, verbose) {
|
|
130889
|
-
try {
|
|
130890
|
-
const lighthouse = await apiFetch2(
|
|
130891
|
-
`${config.apiBaseUrl}/api/WebsiteDiagnosisReports/performance?url=${encodeURIComponent(websiteUrl)}`,
|
|
130892
|
-
config,
|
|
130893
|
-
{},
|
|
130894
|
-
verbose
|
|
130895
|
-
);
|
|
130896
|
-
if (!lighthouseHasScores2(lighthouse)) {
|
|
130897
|
-
return { ok: false, error: "TSO Lighthouse API \u8FD4\u56DE\u7A7A\u5206\u6570" };
|
|
130898
|
-
}
|
|
130899
|
-
return { ok: true, lighthouse };
|
|
130900
|
-
} catch (err) {
|
|
130901
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
130902
|
-
}
|
|
130903
|
-
}
|
|
130904
|
-
async function resolveLighthouseWithFallback(opts) {
|
|
130905
|
-
const websiteUrl = opts.url;
|
|
130906
|
-
const api = await tryTsoLighthouseApi(opts.config, websiteUrl, opts.verbose);
|
|
130907
|
-
if (api.ok) {
|
|
130908
|
-
let signals2;
|
|
130909
|
-
let htmlAsset2 = opts.htmlAsset;
|
|
130910
|
-
const htmlContent2 = opts.htmlContent ?? htmlAsset2?.content;
|
|
130911
|
-
if (htmlContent2) {
|
|
130912
|
-
signals2 = extractWebsiteDiagnosisSignals({
|
|
130913
|
-
url: websiteUrl,
|
|
130914
|
-
html: String(htmlContent2),
|
|
130915
|
-
lighthouse: api.lighthouse
|
|
130916
|
-
});
|
|
130917
|
-
}
|
|
130918
|
-
return {
|
|
130919
|
-
source: "api",
|
|
130920
|
-
lighthouse: api.lighthouse,
|
|
130921
|
-
signals: signals2,
|
|
130922
|
-
htmlAsset: htmlAsset2
|
|
130923
|
-
};
|
|
130924
|
-
}
|
|
130925
|
-
const apiError = api.error;
|
|
130926
|
-
if (opts.skipSimpleHtml) {
|
|
130927
|
-
return {
|
|
130928
|
-
source: "none",
|
|
130929
|
-
lighthouse: {},
|
|
130930
|
-
lighthouseWarning: `Lighthouse API \u5931\u8D25\u4E14\u5DF2\u8DF3\u8FC7\u7B80\u6613\u8BCA\u65AD: ${apiError}`
|
|
130931
|
-
};
|
|
130932
|
-
}
|
|
130933
|
-
let htmlAsset = opts.htmlAsset;
|
|
130934
|
-
let htmlContent = opts.htmlContent ?? (htmlAsset?.content ? String(htmlAsset.content) : "");
|
|
130935
|
-
let htmlError;
|
|
130936
|
-
if (!htmlContent) {
|
|
130937
|
-
try {
|
|
130938
|
-
htmlAsset = await fetchWebsiteHtml(opts.config, websiteUrl, opts.verbose);
|
|
130939
|
-
htmlContent = String(htmlAsset.content ?? "");
|
|
130940
|
-
} catch (err) {
|
|
130941
|
-
htmlError = err instanceof Error ? err.message : String(err);
|
|
130942
|
-
return {
|
|
130943
|
-
source: "none",
|
|
130944
|
-
lighthouse: {},
|
|
130945
|
-
lighthouseWarning: `Lighthouse API \u5931\u8D25\uFF08${apiError}\uFF09\uFF1B\u7B80\u6613\u8BCA\u65AD HTML \u4EA6\u5931\u8D25\uFF08${htmlError}\uFF09`
|
|
130946
|
-
};
|
|
130947
|
-
}
|
|
130948
|
-
}
|
|
130949
|
-
const signals = extractWebsiteDiagnosisSignals({
|
|
130950
|
-
url: websiteUrl,
|
|
130951
|
-
html: htmlContent,
|
|
130952
|
-
lighthouse: {},
|
|
130953
|
-
lighthouseWarning: `Lighthouse \u6027\u80FD\u6570\u636E\u83B7\u53D6\u5931\u8D25: ${apiError}`,
|
|
130954
|
-
htmlError
|
|
130955
|
-
});
|
|
130956
|
-
const simple = toSimple(signals, htmlAsset);
|
|
130957
|
-
return {
|
|
130958
|
-
source: "simple",
|
|
130959
|
-
lighthouse: {},
|
|
130960
|
-
lighthouseWarning: `Lighthouse API \u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u7B80\u6613\u8BCA\u65AD: ${apiError}`,
|
|
130961
|
-
simple,
|
|
130962
|
-
signals,
|
|
130963
|
-
htmlAsset
|
|
130964
|
-
};
|
|
130965
|
-
}
|
|
130966
|
-
function attachLandingPageMeta(lighthouse, resolved) {
|
|
130967
|
-
const desktop = lighthouse.desktop ?? {};
|
|
130968
|
-
const mobile = lighthouse.mobile ?? {};
|
|
130969
|
-
return {
|
|
130970
|
-
desktop: {
|
|
130971
|
-
score: desktop.score ?? 0,
|
|
130972
|
-
firstContentfulPaint: desktop.firstContentfulPaint ?? 0,
|
|
130973
|
-
firstMeaningfulPaint: desktop.firstMeaningfulPaint ?? 0,
|
|
130974
|
-
speedIndex: desktop.speedIndex ?? 0
|
|
130975
|
-
},
|
|
130976
|
-
mobile: {
|
|
130977
|
-
score: mobile.score ?? 0,
|
|
130978
|
-
firstContentfulPaint: mobile.firstContentfulPaint ?? 0,
|
|
130979
|
-
firstMeaningfulPaint: mobile.firstMeaningfulPaint ?? 0,
|
|
130980
|
-
speedIndex: mobile.speedIndex ?? 0
|
|
130981
|
-
},
|
|
130982
|
-
source: resolved.source,
|
|
130983
|
-
...resolved.lighthouseWarning ? { lighthouseWarning: resolved.lighthouseWarning } : {},
|
|
130984
|
-
...resolved.simple ? { simpleDiagnosis: resolved.simple } : {},
|
|
130985
|
-
lighthouseOk: resolved.source === "api"
|
|
130986
|
-
};
|
|
130987
|
-
}
|
|
130988
|
-
|
|
130989
131098
|
// src/commands/website-diagnosis/run.ts
|
|
130990
131099
|
async function runWebsiteDiagnosisPerformance(opts) {
|
|
130991
131100
|
const config = loadConfig(opts.token);
|
|
@@ -131004,9 +131113,8 @@ async function runWebsiteDiagnosisPerformance(opts) {
|
|
|
131004
131113
|
source: resolved.source,
|
|
131005
131114
|
lighthouse: resolved.lighthouse,
|
|
131006
131115
|
...resolved.lighthouseWarning ? { warning: resolved.lighthouseWarning } : {},
|
|
131007
|
-
...resolved.
|
|
131008
|
-
|
|
131009
|
-
agentHint: resolved.source === "api" ? "TSO Lighthouse API \u6210\u529F\u3002" : resolved.source === "simple" ? "\u5DF2\u964D\u7EA7 CLI \u7B80\u6613\u8BCA\u65AD\uFF1A\u7528 simpleDiagnosis/signals \u5199\u7ED3\u8BBA\uFF0C\u7981\u6B62\u7F16\u9020 score/FCP\u3002" : "Lighthouse \u4E0E\u7B80\u6613\u8BCA\u65AD\u5747\u5931\u8D25\uFF0C\u6027\u80FD\u76F8\u5173\u7ED3\u8BBA\u987B\u6807\u660E\u6570\u636E\u4E0D\u53EF\u7528\u3002"
|
|
131116
|
+
...resolved.attempts != null ? { attempts: resolved.attempts } : {},
|
|
131117
|
+
agentHint: resolved.source === "api" ? "TSO Lighthouse API \u6210\u529F\u3002" : "Lighthouse API \u5931\u8D25\uFF1A\u6027\u80FD\u76F8\u5173\u7ED3\u8BBA\u987B\u6807\u660E\u6570\u636E\u4E0D\u53EF\u7528\uFF0C\u7981\u6B62\u7F16\u9020 score/FCP\u3002"
|
|
131010
131118
|
};
|
|
131011
131119
|
if (await emitCliJsonOrSnapshot(
|
|
131012
131120
|
{ jsonOut: opts.jsonOut },
|
|
@@ -131085,34 +131193,17 @@ async function runWebsiteDiagnosisCollect(opts) {
|
|
|
131085
131193
|
htmlError = err instanceof Error ? err.message : String(err);
|
|
131086
131194
|
}
|
|
131087
131195
|
}
|
|
131088
|
-
const htmlForSignals = htmlFull || htmlPreview || "";
|
|
131089
131196
|
const resolved = await resolveLighthouseWithFallback({
|
|
131090
131197
|
config,
|
|
131091
131198
|
url: websiteUrl,
|
|
131092
|
-
verbose: opts.verbose
|
|
131093
|
-
htmlContent: htmlForSignals || void 0,
|
|
131094
|
-
htmlAsset,
|
|
131095
|
-
skipSimpleHtml: opts.skipHtml && !htmlForSignals
|
|
131199
|
+
verbose: opts.verbose
|
|
131096
131200
|
});
|
|
131097
|
-
|
|
131098
|
-
|
|
131099
|
-
htmlMeta = {
|
|
131100
|
-
originUrl: htmlAsset.originUrl,
|
|
131101
|
-
finalUrl: htmlAsset.finalUrl,
|
|
131102
|
-
mimeType: htmlAsset.mimeType,
|
|
131103
|
-
contentLength: htmlAsset.contentLength,
|
|
131104
|
-
downloadTime: htmlAsset.downloadTime
|
|
131105
|
-
};
|
|
131106
|
-
const content = String(htmlAsset.content ?? "");
|
|
131107
|
-
htmlPreview = content.slice(0, HTML_PREVIEW_CHARS);
|
|
131108
|
-
if (opts.includeHtml) htmlFull = content;
|
|
131109
|
-
}
|
|
131110
|
-
const htmlBody = htmlFull || htmlPreview || String(resolved.htmlAsset?.content ?? "");
|
|
131111
|
-
const signals = resolved.signals ?? extractWebsiteDiagnosisSignals({
|
|
131201
|
+
const htmlBody = String(htmlAsset?.content ?? htmlFull ?? htmlPreview ?? "");
|
|
131202
|
+
const signals = extractWebsiteDiagnosisSignals({
|
|
131112
131203
|
url: websiteUrl,
|
|
131113
131204
|
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
131114
131205
|
html: htmlBody,
|
|
131115
|
-
htmlTruncated: !htmlFull && Boolean(htmlPreview || htmlBody),
|
|
131206
|
+
htmlTruncated: Boolean(htmlAsset?.content) ? false : !htmlFull && Boolean(htmlPreview || htmlBody),
|
|
131116
131207
|
lighthouse: resolved.lighthouse,
|
|
131117
131208
|
lighthouseWarning: resolved.lighthouseWarning,
|
|
131118
131209
|
htmlError,
|
|
@@ -131124,14 +131215,14 @@ async function runWebsiteDiagnosisCollect(opts) {
|
|
|
131124
131215
|
lighthouseSource: resolved.source,
|
|
131125
131216
|
lighthouse: resolved.lighthouse,
|
|
131126
131217
|
...resolved.lighthouseWarning ? { lighthouseWarning: resolved.lighthouseWarning } : {},
|
|
131127
|
-
...resolved.
|
|
131218
|
+
...resolved.attempts != null ? { lighthouseAttempts: resolved.attempts } : {},
|
|
131128
131219
|
html: htmlMeta,
|
|
131129
131220
|
htmlPreview,
|
|
131130
131221
|
...htmlFull ? { htmlContent: htmlFull } : {},
|
|
131131
131222
|
...htmlError ? { htmlError } : {},
|
|
131132
131223
|
signals,
|
|
131133
131224
|
dataAvailability: signals.dataAvailability,
|
|
131134
|
-
agentHint: `lighthouseSource=${resolved.source}\u3002` + (resolved.source === "
|
|
131225
|
+
agentHint: `lighthouseSource=${resolved.source}\u3002` + (resolved.source === "none" ? "Lighthouse API \u5931\u8D25\uFF1Am2i1/m5i1 \u987B status=Absent\u3001score=0\uFF0C\u7981\u6B62\u7F16\u9020\u6027\u80FD\u5206\u3002" : "TSO Lighthouse \u53EF\u7528\u3002") + ` \u8868\u5355/\u5BFC\u822A\u4EE5 signals \u4E3A\u51C6\uFF08\u5DF2\u6309\u5B8C\u6574 HTML \u62BD\u53D6\uFF09\uFF1B\u7981\u6B62\u7528 htmlPreview \u5224\u65AD\u6709\u65E0\u8868\u5355\u3002 1) \u8BFB signals + dataAvailability\uFF1B2) website-diagnosis prepare\uFF1B3) \u8865\u5168 diagnosis\uFF1B4) render\u3002 ${signals.dataAvailability.agentHint}`
|
|
131135
131226
|
};
|
|
131136
131227
|
if (await emitCliJsonOrSnapshot(
|
|
131137
131228
|
{ jsonOut: opts.jsonOut },
|
|
@@ -131343,7 +131434,7 @@ function registerWebsiteDiagnosisCommands(program2) {
|
|
|
131343
131434
|
"\u7F51\u7AD9\u8BCA\u65AD\uFF1ALighthouse \u6027\u80FD\u3001HTML \u91C7\u96C6\u3001ARIT \u5386\u53F2\u5F97\u5206\uFF1B\u9ED8\u8BA4\u4EA4\u4ED8 HTML \u62A5\u544A\uFF08render \u5B50\u547D\u4EE4\uFF09"
|
|
131344
131435
|
);
|
|
131345
131436
|
root.command("performance").description(
|
|
131346
|
-
"\u62C9\u53D6 Lighthouse\uFF1A\
|
|
131437
|
+
"\u62C9\u53D6 Lighthouse\uFF1A\u8BF7\u6C42 TSO performance API\uFF08\u5931\u8D25\u81EA\u52A8\u91CD\u8BD5\uFF0C\u4E0D\u7F16\u9020 score/FCP\uFF09"
|
|
131347
131438
|
).requiredOption("--url <url>", "\u7F51\u7AD9 URL\uFF08\u53EF\u7701\u7565 https://\uFF0C\u81EA\u52A8\u8865\u5168\uFF09").option("--token <token>", "JWT\uFF08\u9ED8\u8BA4\u8BFB config / \u73AF\u5883\u53D8\u91CF\uFF09").option("--verbose", "\u6253\u5370\u8BF7\u6C42\u8BE6\u60C5").option("--json-out <dir>", "\u843D\u76D8 cli-manifest + JSON").action(async (opts) => {
|
|
131348
131439
|
await runWebsiteDiagnosisPerformance({
|
|
131349
131440
|
url: opts.url,
|
|
@@ -133149,7 +133240,7 @@ async function runGoogleAdsDiagnosisCollect(opts) {
|
|
|
133149
133240
|
let landingPageResolve;
|
|
133150
133241
|
if (!opts.skipLandingPage && websiteUrl) {
|
|
133151
133242
|
console.error(`
|
|
133152
|
-
\u{1F4E5} \u7740\u9646\u9875\
|
|
133243
|
+
\u{1F4E5} \u7740\u9646\u9875\uFF1ATSO Lighthouse\uFF08\u5931\u8D25\u81EA\u52A8\u91CD\u8BD5\uFF09\uFF1A${websiteUrl}
|
|
133153
133244
|
`);
|
|
133154
133245
|
const config = loadConfig(opts.token);
|
|
133155
133246
|
landingPageResolve = await resolveLighthouseWithFallback({
|
|
@@ -133159,12 +133250,8 @@ async function runGoogleAdsDiagnosisCollect(opts) {
|
|
|
133159
133250
|
});
|
|
133160
133251
|
if (landingPageResolve.source === "api") {
|
|
133161
133252
|
console.error(" \u2713 TSO Lighthouse API \u6210\u529F\n");
|
|
133162
|
-
} else if (landingPageResolve.source === "simple") {
|
|
133163
|
-
console.error(` \u26A0\uFE0F ${landingPageResolve.lighthouseWarning ?? "\u5DF2\u964D\u7EA7\u7B80\u6613\u8BCA\u65AD"}
|
|
133164
|
-
`);
|
|
133165
|
-
degraded = true;
|
|
133166
133253
|
} else {
|
|
133167
|
-
console.error(` \u26A0\uFE0F ${landingPageResolve.lighthouseWarning ?? "\u7740\u9646\u9875\
|
|
133254
|
+
console.error(` \u26A0\uFE0F ${landingPageResolve.lighthouseWarning ?? "\u7740\u9646\u9875\u6D4B\u901F\u4E0D\u53EF\u7528"}
|
|
133168
133255
|
`);
|
|
133169
133256
|
degraded = true;
|
|
133170
133257
|
}
|
|
@@ -133452,7 +133539,7 @@ function restoreLandingPageSpeedFromCollect(finalData, collectReportData) {
|
|
|
133452
133539
|
...finalBlock ?? {},
|
|
133453
133540
|
desktop: needsDesktop ? collectBlock.desktop : finalBlock?.desktop,
|
|
133454
133541
|
mobile: needsMobile ? collectBlock.mobile : finalBlock?.mobile,
|
|
133455
|
-
// 保留 collect
|
|
133542
|
+
// 保留 collect 的测速元数据(source / lighthouseWarning);旧 JSON 的 simpleDiagnosis 仅透传
|
|
133456
133543
|
...collectBlock.source != null && finalBlock?.source == null ? { source: collectBlock.source } : {},
|
|
133457
133544
|
...collectBlock.simpleDiagnosis != null && finalBlock?.simpleDiagnosis == null ? { simpleDiagnosis: collectBlock.simpleDiagnosis } : {},
|
|
133458
133545
|
...collectBlock.lighthouseWarning != null && finalBlock?.lighthouseWarning == null ? { lighthouseWarning: collectBlock.lighthouseWarning } : {},
|
|
@@ -133547,7 +133634,7 @@ var NARRATIVE_CONSISTENCY_RULES = [
|
|
|
133547
133634
|
id: "landing-page-missing-as-core-issue",
|
|
133548
133635
|
textPattern: /(着陆页|落地页).{0,24}(性能|测速|Lighthouse|打开速度|加载).{0,24}(缺失|未获取|无法评估|数据缺失)/,
|
|
133549
133636
|
isContradicted: () => true,
|
|
133550
|
-
hint: "\u7740\u9646\u9875\u6D4B\u901F\u672A\u83B7\u53D6\u662F\
|
|
133637
|
+
hint: "\u7740\u9646\u9875\u6D4B\u901F\u672A\u83B7\u53D6\u662F Lighthouse API \u5931\u8D25\uFF0C\u4E0D\u662F\u8D26\u6237\u6295\u653E\u6838\u5FC3\u95EE\u9898\u3002\u7981\u6B62\u5199\u5165 keyIssues/disadvantages\uFF1B\u4EC5\u5728 \xA707 landingPageAnalysis.analysis \u8BF4\u660E source=none\u3002"
|
|
133551
133638
|
}
|
|
133552
133639
|
];
|
|
133553
133640
|
function findIdenticalTexts(texts, minCount) {
|
|
@@ -133592,12 +133679,12 @@ function getLandingPageTopicErrors(data) {
|
|
|
133592
133679
|
const analysis = String(lp.analysis ?? "");
|
|
133593
133680
|
if (!analysis.trim()) return [];
|
|
133594
133681
|
const talksKeywords = /关键词|搜索词|匹配类型|广泛匹配/.test(analysis);
|
|
133595
|
-
const talksLanding = /着陆页|落地页|Lighthouse|加载|打开速度|FCP|性能指数|HTTPS
|
|
133682
|
+
const talksLanding = /着陆页|落地页|Lighthouse|加载|打开速度|FCP|性能指数|HTTPS|表单|测速|未测到|下载耗时/.test(
|
|
133596
133683
|
analysis
|
|
133597
133684
|
);
|
|
133598
133685
|
if (talksKeywords && !talksLanding) {
|
|
133599
133686
|
return [
|
|
133600
|
-
"landingPageAnalysis.analysis \u5199\u6210\u4E86\u5173\u952E\u8BCD/\u641C\u7D22\u8BCD\u6D1E\u5BDF\uFF0C\u4E0E \xA707 \u7740\u9646\u9875\u6A21\u5757\u4E0D\u7B26\u3002\u8BF7\u53EA\u4F9D\u636E landingPageAnalysis.source / desktop / mobile
|
|
133687
|
+
"landingPageAnalysis.analysis \u5199\u6210\u4E86\u5173\u952E\u8BCD/\u641C\u7D22\u8BCD\u6D1E\u5BDF\uFF0C\u4E0E \xA707 \u7740\u9646\u9875\u6A21\u5757\u4E0D\u7B26\u3002\u8BF7\u53EA\u4F9D\u636E landingPageAnalysis.source / desktop / mobile \u5199\u6D4B\u901F\u7ED3\u8BBA\uFF08source=none \u5219\u5199\u672A\u6D4B\u5230\uFF09\uFF1B\u5173\u952E\u8BCD\u5206\u6790\u653E\u5230 \xA708\u3002"
|
|
133601
133688
|
];
|
|
133602
133689
|
}
|
|
133603
133690
|
return [];
|
|
@@ -133765,7 +133852,7 @@ function registerGoogleAdsDiagnosisCommands(program2) {
|
|
|
133765
133852
|
).requiredOption("-a, --account <id>", "Google mediaCustomerId").requiredOption("--start <date>", "\u8BCA\u65AD\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").requiredOption("--end <date>", "\u8BCA\u65AD\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--company-name <name>", "\u8D26\u6237\u540D\u79F0\uFF08\u9ED8\u8BA4\u4ECE list-accounts \u5FEB\u7167\u8BFB\u53D6\uFF09").option("--website <url>", "\u5B98\u7F51 URL\uFF08\u9ED8\u8BA4\u4ECE ads \u5FEB\u7167 finalurl \u8BFB\u53D6\uFF09").option(
|
|
133766
133853
|
"--json-out <dir>",
|
|
133767
133854
|
"\u843D\u76D8\u76EE\u5F55\uFF08google-analysis \u5FEB\u7167 + google-ads-diagnosis-collect.json\uFF09"
|
|
133768
|
-
).option("--skip-landing-page", "\u4E0D\u62C9\u53D6\u7740\u9646\u9875\uFF08\u8DF3\u8FC7 TSO Lighthouse
|
|
133855
|
+
).option("--skip-landing-page", "\u4E0D\u62C9\u53D6\u7740\u9646\u9875\uFF08\u8DF3\u8FC7 TSO Lighthouse\uFF09").option("--no-fetch-previous-period", "\u4E0D\u62C9\u4E0A\u4E00\u5468\u671F campaigns/geographic/keywords \u5BF9\u6BD4\u6570\u636E").option("--token <token>", "JWT").option("--verbose", "\u6253\u5370\u8BF7\u6C42\u8BE6\u60C5").action(
|
|
133769
133856
|
async (opts) => {
|
|
133770
133857
|
if (!opts.jsonOut?.trim()) {
|
|
133771
133858
|
console.error("\n\u274C collect \u987B\u6307\u5B9A --json-out <\u76EE\u5F55>\u3002\n");
|
|
@@ -133896,13 +133983,13 @@ import * as fs28 from "fs/promises";
|
|
|
133896
133983
|
import * as path35 from "path";
|
|
133897
133984
|
|
|
133898
133985
|
// src/commands/facebook-analysis/report-metrics.ts
|
|
133899
|
-
function
|
|
133986
|
+
function asRecord16(value) {
|
|
133900
133987
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
133901
133988
|
return value;
|
|
133902
133989
|
}
|
|
133903
133990
|
return null;
|
|
133904
133991
|
}
|
|
133905
|
-
function
|
|
133992
|
+
function num6(value) {
|
|
133906
133993
|
const n = Number(value);
|
|
133907
133994
|
return Number.isFinite(n) ? n : void 0;
|
|
133908
133995
|
}
|
|
@@ -133927,14 +134014,14 @@ function normalizeInsightDate(value) {
|
|
|
133927
134014
|
}
|
|
133928
134015
|
function pickMetricFields(row) {
|
|
133929
134016
|
return {
|
|
133930
|
-
reach:
|
|
133931
|
-
impressions:
|
|
133932
|
-
frequency:
|
|
134017
|
+
reach: num6(row.reach),
|
|
134018
|
+
impressions: num6(row.impressions),
|
|
134019
|
+
frequency: num6(row.frequency),
|
|
133933
134020
|
attributionSetting: str2(row.attributionSetting) || void 0,
|
|
133934
134021
|
resultType: str2(row.resultType) || void 0,
|
|
133935
|
-
results:
|
|
133936
|
-
spend:
|
|
133937
|
-
costPerResult:
|
|
134022
|
+
results: num6(row.results),
|
|
134023
|
+
spend: num6(row.spend),
|
|
134024
|
+
costPerResult: num6(row.costPerResult)
|
|
133938
134025
|
};
|
|
133939
134026
|
}
|
|
133940
134027
|
function audienceLabel(age, gender) {
|
|
@@ -134033,13 +134120,13 @@ function localizeFacebookReportLabels(payload) {
|
|
|
134033
134120
|
for (const rows of Object.values(tables)) {
|
|
134034
134121
|
if (!Array.isArray(rows)) continue;
|
|
134035
134122
|
for (const row of rows) {
|
|
134036
|
-
localizeMetricLabels(
|
|
134123
|
+
localizeMetricLabels(asRecord16(row));
|
|
134037
134124
|
}
|
|
134038
134125
|
}
|
|
134039
134126
|
return payload;
|
|
134040
134127
|
}
|
|
134041
134128
|
function hasResults(row) {
|
|
134042
|
-
const n =
|
|
134129
|
+
const n = num6(row.results);
|
|
134043
134130
|
return n != null && n > 0;
|
|
134044
134131
|
}
|
|
134045
134132
|
|
|
@@ -134066,7 +134153,7 @@ async function readManifest3(snapshotDir) {
|
|
|
134066
134153
|
async function loadSectionFile3(snapshotDir, file) {
|
|
134067
134154
|
try {
|
|
134068
134155
|
const raw = await fs28.readFile(path35.join(snapshotDir, file), "utf8");
|
|
134069
|
-
return
|
|
134156
|
+
return asRecord16(JSON.parse(raw));
|
|
134070
134157
|
} catch {
|
|
134071
134158
|
return null;
|
|
134072
134159
|
}
|
|
@@ -134122,7 +134209,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
134122
134209
|
if (data) sectionMap.set(art.section, data);
|
|
134123
134210
|
}
|
|
134124
134211
|
const overview = sectionMap.get("overview");
|
|
134125
|
-
const current =
|
|
134212
|
+
const current = asRecord16(overview?.currentPeriod);
|
|
134126
134213
|
const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
|
|
134127
134214
|
const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
|
|
134128
134215
|
const kpis = { ...payload.kpis ?? {} };
|
|
@@ -134259,7 +134346,7 @@ function writeSimpleXlsx(filePath, sheets, opts = {}) {
|
|
|
134259
134346
|
}
|
|
134260
134347
|
|
|
134261
134348
|
// src/commands/facebook-analysis/render-excel.ts
|
|
134262
|
-
function
|
|
134349
|
+
function asRecord17(value) {
|
|
134263
134350
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
134264
134351
|
return value;
|
|
134265
134352
|
}
|
|
@@ -134285,30 +134372,30 @@ function metricHeaders(currency, firstCol) {
|
|
|
134285
134372
|
];
|
|
134286
134373
|
}
|
|
134287
134374
|
function moneyText(value) {
|
|
134288
|
-
const n =
|
|
134375
|
+
const n = num6(value);
|
|
134289
134376
|
if (n == null) return "\u2014";
|
|
134290
134377
|
return `$${n.toFixed(2)}`;
|
|
134291
134378
|
}
|
|
134292
134379
|
function metricCells(row) {
|
|
134293
|
-
const results =
|
|
134380
|
+
const results = num6(row.results);
|
|
134294
134381
|
const showResults = hasResults({ results });
|
|
134295
134382
|
return [
|
|
134296
|
-
|
|
134297
|
-
|
|
134298
|
-
|
|
134383
|
+
num6(row.reach) ?? "",
|
|
134384
|
+
num6(row.impressions) ?? "",
|
|
134385
|
+
num6(row.frequency) ?? "",
|
|
134299
134386
|
displayAttribution(row.attributionSetting),
|
|
134300
134387
|
showResults ? displayResultType(row.resultType) : "",
|
|
134301
134388
|
showResults ? results ?? "" : "",
|
|
134302
|
-
|
|
134303
|
-
showResults ?
|
|
134389
|
+
num6(row.spend) ?? "",
|
|
134390
|
+
showResults ? num6(row.costPerResult) ?? "" : ""
|
|
134304
134391
|
];
|
|
134305
134392
|
}
|
|
134306
134393
|
function bullets(value) {
|
|
134307
134394
|
return asArray3(value).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
134308
134395
|
}
|
|
134309
134396
|
function insightLines(section) {
|
|
134310
|
-
const rec =
|
|
134311
|
-
const insight =
|
|
134397
|
+
const rec = asRecord17(section);
|
|
134398
|
+
const insight = asRecord17(rec?.insight) ?? rec;
|
|
134312
134399
|
return [...bullets(insight?.analysis), ...bullets(insight?.advice)];
|
|
134313
134400
|
}
|
|
134314
134401
|
function numberedSection(index, title, lines) {
|
|
@@ -134317,19 +134404,19 @@ function numberedSection(index, title, lines) {
|
|
|
134317
134404
|
${body}`;
|
|
134318
134405
|
}
|
|
134319
134406
|
function buildSummaryNarrative(data) {
|
|
134320
|
-
const kpis =
|
|
134321
|
-
const meta =
|
|
134322
|
-
const sections =
|
|
134407
|
+
const kpis = asRecord17(data.kpis) ?? {};
|
|
134408
|
+
const meta = asRecord17(data.meta) ?? {};
|
|
134409
|
+
const sections = asRecord17(data.sections) ?? {};
|
|
134323
134410
|
const period = str2(meta.periodLabel) || `${str2(meta.startDate)} ~ ${str2(meta.endDate)}`.trim();
|
|
134324
134411
|
const account = str2(meta.accountName) || "Facebook \u5E7F\u544A\u8D26\u6237";
|
|
134325
134412
|
const campaign = str2(meta.campaignName);
|
|
134326
134413
|
const resultLabel = displayResultType(kpis.resultType ?? meta.resultType) || "\u6210\u6548";
|
|
134327
134414
|
const resultNoun = /潜在客户|lead/i.test(resultLabel) ? "\u4E2A\u6F5C\u5728\u5BA2\u6237\u8868\u5355" : `\u4E2A${resultLabel}`;
|
|
134328
|
-
const firstQuestion =
|
|
134415
|
+
const firstQuestion = asRecord17(asArray3(data.fourQuestions)[0]);
|
|
134329
134416
|
const extra = str2(firstQuestion?.verdict);
|
|
134330
134417
|
const spendLine = [
|
|
134331
134418
|
campaign ? `\uFF08\u5E7F\u544A\u7CFB\u5217\uFF1A${campaign}\uFF09` : "",
|
|
134332
|
-
`\u5171\u6D88\u8017 ${moneyText(kpis.spend)}\uFF0C\u83B7\u53D6 ${
|
|
134419
|
+
`\u5171\u6D88\u8017 ${moneyText(kpis.spend)}\uFF0C\u83B7\u53D6 ${num6(kpis.results) ?? "\u2014"} ${resultNoun}\uFF0C\u5E73\u5747\u5355\u6B21\u83B7\u5BA2\u6210\u672C ${moneyText(kpis.costPerResult)}\u3002`,
|
|
134333
134420
|
extra
|
|
134334
134421
|
].filter(Boolean).join("");
|
|
134335
134422
|
const partOne = [`\u4E00\u3001${account}${period ? ` ${period}` : ""} Facebook \u5E7F\u544A\u8D26\u6237`, spendLine].join(
|
|
@@ -134345,7 +134432,7 @@ function buildSummaryNarrative(data) {
|
|
|
134345
134432
|
numberedSection(3, "\u5E7F\u544A\u9891\u6B21\u4E0E\u65E5\u8D8B\u52BF", insightLines(sections.daily))
|
|
134346
134433
|
].join("\n");
|
|
134347
134434
|
const recBlocks = asArray3(data.recommendations).map((raw, i) => {
|
|
134348
|
-
const rec =
|
|
134435
|
+
const rec = asRecord17(raw);
|
|
134349
134436
|
if (!rec) return "";
|
|
134350
134437
|
const title = [str2(rec.title), str2(rec.tag)].filter(Boolean).join(" \xB7 ");
|
|
134351
134438
|
return numberedSection(i + 1, title || "\u4F18\u5316\u5EFA\u8BAE", bullets(rec.items));
|
|
@@ -134356,9 +134443,9 @@ function buildSummaryNarrative(data) {
|
|
|
134356
134443
|
return [partOne, partTwo, partThree];
|
|
134357
134444
|
}
|
|
134358
134445
|
function buildMetaPeriodExcelSheets(data) {
|
|
134359
|
-
const meta =
|
|
134360
|
-
const kpis =
|
|
134361
|
-
const tables =
|
|
134446
|
+
const meta = asRecord17(data.meta) ?? {};
|
|
134447
|
+
const kpis = asRecord17(data.kpis) ?? {};
|
|
134448
|
+
const tables = asRecord17(data.tables) ?? {};
|
|
134362
134449
|
const currency = str2(meta.currency) || "USD";
|
|
134363
134450
|
const resultType = displayResultType(kpis.resultType ?? meta.resultType);
|
|
134364
134451
|
const attribution = displayAttribution(kpis.attributionSetting ?? meta.attributionSetting);
|
|
@@ -134377,19 +134464,19 @@ function buildMetaPeriodExcelSheets(data) {
|
|
|
134377
134464
|
];
|
|
134378
134465
|
const dailyRows = [metricHeaders(currency, "\u5355\u65E5")];
|
|
134379
134466
|
for (const raw of asArray3(tables.daily)) {
|
|
134380
|
-
const row =
|
|
134467
|
+
const row = asRecord17(raw);
|
|
134381
134468
|
if (!row) continue;
|
|
134382
134469
|
dailyRows.push([normalizeInsightDate(row.date), ...metricCells(row)]);
|
|
134383
134470
|
}
|
|
134384
134471
|
const countryRows = [metricHeaders(currency, "\u56FD\u5BB6/\u5730\u533A")];
|
|
134385
134472
|
for (const raw of asArray3(tables.countries)) {
|
|
134386
|
-
const row =
|
|
134473
|
+
const row = asRecord17(raw);
|
|
134387
134474
|
if (!row) continue;
|
|
134388
134475
|
countryRows.push([str2(row.countryOrRegion), ...metricCells(row)]);
|
|
134389
134476
|
}
|
|
134390
134477
|
const campaignRows3 = [metricHeaders(currency, "\u5E7F\u544A\u7CFB\u5217\u540D\u79F0")];
|
|
134391
134478
|
for (const raw of asArray3(tables.campaigns)) {
|
|
134392
|
-
const row =
|
|
134479
|
+
const row = asRecord17(raw);
|
|
134393
134480
|
if (!row) continue;
|
|
134394
134481
|
campaignRows3.push([str2(row.campaignName), ...metricCells(row)]);
|
|
134395
134482
|
}
|
|
@@ -134408,7 +134495,7 @@ function buildMetaPeriodExcelSheets(data) {
|
|
|
134408
134495
|
]
|
|
134409
134496
|
];
|
|
134410
134497
|
for (const raw of asArray3(tables.audiences)) {
|
|
134411
|
-
const row =
|
|
134498
|
+
const row = asRecord17(raw);
|
|
134412
134499
|
if (!row) continue;
|
|
134413
134500
|
audienceRows.push([str2(row.age), str2(row.gender), ...metricCells(row)]);
|
|
134414
134501
|
}
|
|
@@ -134440,7 +134527,7 @@ function isFiniteNumber(value) {
|
|
|
134440
134527
|
function asArray4(value) {
|
|
134441
134528
|
return Array.isArray(value) ? value : [];
|
|
134442
134529
|
}
|
|
134443
|
-
function
|
|
134530
|
+
function asRecord18(value) {
|
|
134444
134531
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
134445
134532
|
return value;
|
|
134446
134533
|
}
|
|
@@ -134449,15 +134536,15 @@ function asRecord17(value) {
|
|
|
134449
134536
|
function pushMissing3(missing, id, chapter, dimension, hint) {
|
|
134450
134537
|
missing.push({ id, chapter, dimension, hint });
|
|
134451
134538
|
}
|
|
134452
|
-
function
|
|
134539
|
+
function stringList3(value) {
|
|
134453
134540
|
return asArray4(value).map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
134454
134541
|
}
|
|
134455
134542
|
function insightLists(section) {
|
|
134456
|
-
const rec =
|
|
134457
|
-
const insight =
|
|
134543
|
+
const rec = asRecord18(section);
|
|
134544
|
+
const insight = asRecord18(rec?.insight) ?? rec;
|
|
134458
134545
|
return {
|
|
134459
|
-
analysis:
|
|
134460
|
-
advice:
|
|
134546
|
+
analysis: stringList3(insight?.analysis),
|
|
134547
|
+
advice: stringList3(insight?.advice)
|
|
134461
134548
|
};
|
|
134462
134549
|
}
|
|
134463
134550
|
function requireInsight(missing, key, chapter, section, required) {
|
|
@@ -134484,11 +134571,11 @@ function requireInsight(missing, key, chapter, section, required) {
|
|
|
134484
134571
|
}
|
|
134485
134572
|
function validateMetaPeriodReportContent(data) {
|
|
134486
134573
|
const missing = [];
|
|
134487
|
-
const meta =
|
|
134488
|
-
const kpis =
|
|
134489
|
-
const tables =
|
|
134490
|
-
const charts =
|
|
134491
|
-
const sections =
|
|
134574
|
+
const meta = asRecord18(data.meta) ?? {};
|
|
134575
|
+
const kpis = asRecord18(data.kpis) ?? {};
|
|
134576
|
+
const tables = asRecord18(data.tables) ?? {};
|
|
134577
|
+
const charts = asRecord18(data.charts) ?? {};
|
|
134578
|
+
const sections = asRecord18(data.sections) ?? {};
|
|
134492
134579
|
const fourQuestions = asArray4(data.fourQuestions);
|
|
134493
134580
|
const recommendations = asArray4(data.recommendations);
|
|
134494
134581
|
if (!textLen3(meta.accountName) && !textLen3(meta.accountId)) {
|
|
@@ -134567,7 +134654,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
134567
134654
|
"\u987B\u6709 tables.audiences \u6570\u7EC4\uFF1B\u62C9\u6570\u52A0 --sections audience\uFF0Crender \u65F6\u4F20 --snapshot-dir\u3002\u672C\u671F\u65E0\u6D88\u8017\u53EF\u4E3A []"
|
|
134568
134655
|
);
|
|
134569
134656
|
}
|
|
134570
|
-
const dailyChart =
|
|
134657
|
+
const dailyChart = asRecord18(charts.daily);
|
|
134571
134658
|
if (!Array.isArray(dailyChart?.labels)) {
|
|
134572
134659
|
pushMissing3(
|
|
134573
134660
|
missing,
|
|
@@ -134577,7 +134664,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
134577
134664
|
"\u987B\u6709 charts.daily.labels \u6570\u7EC4\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 daily\u3002\u672C\u671F\u65E0\u6D88\u8017\u53EF\u4E3A []"
|
|
134578
134665
|
);
|
|
134579
134666
|
}
|
|
134580
|
-
const countryChart =
|
|
134667
|
+
const countryChart = asRecord18(charts.country);
|
|
134581
134668
|
if (!Array.isArray(countryChart?.labels)) {
|
|
134582
134669
|
pushMissing3(
|
|
134583
134670
|
missing,
|
|
@@ -134587,7 +134674,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
134587
134674
|
"\u987B\u6709 charts.country.labels \u6570\u7EC4\uFF1B\u53EF\u7531 --snapshot-dir \u5408\u5E76 country\u3002\u672C\u671F\u65E0\u6D88\u8017\u53EF\u4E3A []"
|
|
134588
134675
|
);
|
|
134589
134676
|
}
|
|
134590
|
-
const audienceChart =
|
|
134677
|
+
const audienceChart = asRecord18(charts.audience);
|
|
134591
134678
|
if (!Array.isArray(audienceChart?.labels)) {
|
|
134592
134679
|
pushMissing3(
|
|
134593
134680
|
missing,
|
|
@@ -134606,7 +134693,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
134606
134693
|
"\u5FC5\u987B\u6070\u597D 4 \u95EE\uFF1A\u94B1\u82B1\u5F97\u503C\u4E0D\u503C / \u8C01\u771F\u7684\u60F3\u4E70 / \u5E7F\u544A\u8FD8\u65B0\u9C9C\u5417 / \u7528\u6237\u4E3A\u4EC0\u4E48\u4E0D\u7559\u8D44"
|
|
134607
134694
|
);
|
|
134608
134695
|
} else {
|
|
134609
|
-
const titles = fourQuestions.map((q) =>
|
|
134696
|
+
const titles = fourQuestions.map((q) => asRecord18(q)?.title);
|
|
134610
134697
|
for (const title of FOUR_QUESTION_TITLES) {
|
|
134611
134698
|
if (!titles.includes(title)) {
|
|
134612
134699
|
pushMissing3(
|
|
@@ -134619,7 +134706,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
134619
134706
|
}
|
|
134620
134707
|
}
|
|
134621
134708
|
for (let i = 0; i < fourQuestions.length; i++) {
|
|
134622
|
-
const q =
|
|
134709
|
+
const q = asRecord18(fourQuestions[i]);
|
|
134623
134710
|
if (textLen3(q?.verdict) < 8) {
|
|
134624
134711
|
pushMissing3(
|
|
134625
134712
|
missing,
|
|
@@ -134629,7 +134716,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
134629
134716
|
"\u4E00\u53E5\u8BDD\u7ED3\u8BBA\uFF0C\u5F15\u7528\u5F53\u6B21\u56FD\u5BB6/CPL/\u4EBA\u7FA4"
|
|
134630
134717
|
);
|
|
134631
134718
|
}
|
|
134632
|
-
if (
|
|
134719
|
+
if (stringList3(q?.bullets).length < 1) {
|
|
134633
134720
|
pushMissing3(
|
|
134634
134721
|
missing,
|
|
134635
134722
|
`four-questions-bullets-${i}`,
|
|
@@ -134659,7 +134746,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
134659
134746
|
);
|
|
134660
134747
|
} else {
|
|
134661
134748
|
for (let i = 0; i < recommendations.length; i++) {
|
|
134662
|
-
const rec =
|
|
134749
|
+
const rec = asRecord18(recommendations[i]);
|
|
134663
134750
|
if (textLen3(rec?.title) < 2) {
|
|
134664
134751
|
pushMissing3(
|
|
134665
134752
|
missing,
|
|
@@ -134669,7 +134756,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
134669
134756
|
"\u987B\u6709\u6807\u9898"
|
|
134670
134757
|
);
|
|
134671
134758
|
}
|
|
134672
|
-
if (
|
|
134759
|
+
if (stringList3(rec?.items).length < 1) {
|
|
134673
134760
|
pushMissing3(
|
|
134674
134761
|
missing,
|
|
134675
134762
|
`recommendations-items-${i}`,
|
|
@@ -134752,7 +134839,7 @@ async function readJsonFile4(filePath) {
|
|
|
134752
134839
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
134753
134840
|
}
|
|
134754
134841
|
}
|
|
134755
|
-
function
|
|
134842
|
+
function asRecord19(value) {
|
|
134756
134843
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
134757
134844
|
return value;
|
|
134758
134845
|
}
|
|
@@ -134781,7 +134868,7 @@ function defaultOutPath(dataPath, format, out) {
|
|
|
134781
134868
|
}
|
|
134782
134869
|
async function runFacebookAnalysisRender(opts) {
|
|
134783
134870
|
const dataPath = path36.resolve(opts.dataFile);
|
|
134784
|
-
let data =
|
|
134871
|
+
let data = asRecord19(await readJsonFile4(dataPath));
|
|
134785
134872
|
if (opts.snapshotDir?.trim()) {
|
|
134786
134873
|
data = await mergeFacebookSnapshotIntoReport(data, path36.resolve(opts.snapshotDir));
|
|
134787
134874
|
}
|
|
@@ -134845,31 +134932,31 @@ import { fileURLToPath as fileURLToPath8 } from "url";
|
|
|
134845
134932
|
// src/commands/facebook-analysis/diagnosis-merge-snapshot.ts
|
|
134846
134933
|
import * as fs30 from "fs/promises";
|
|
134847
134934
|
import * as path37 from "path";
|
|
134848
|
-
function
|
|
134935
|
+
function asRecord20(value) {
|
|
134849
134936
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
134850
134937
|
return value;
|
|
134851
134938
|
}
|
|
134852
134939
|
return null;
|
|
134853
134940
|
}
|
|
134854
|
-
function
|
|
134941
|
+
function num7(value) {
|
|
134855
134942
|
const n = Number(value);
|
|
134856
134943
|
return Number.isFinite(n) ? n : void 0;
|
|
134857
134944
|
}
|
|
134858
134945
|
function asRows(value) {
|
|
134859
|
-
return Array.isArray(value) ? value.filter((r) =>
|
|
134946
|
+
return Array.isArray(value) ? value.filter((r) => asRecord20(r) !== null) : [];
|
|
134860
134947
|
}
|
|
134861
134948
|
function pickMetricExtras(row, extras) {
|
|
134862
134949
|
const out = {
|
|
134863
|
-
spend:
|
|
134864
|
-
impressions:
|
|
134865
|
-
clicks:
|
|
134866
|
-
results:
|
|
134867
|
-
costPerResult:
|
|
134868
|
-
reach:
|
|
134869
|
-
frequency:
|
|
134870
|
-
ctr:
|
|
134871
|
-
conversions:
|
|
134872
|
-
costPerConversion:
|
|
134950
|
+
spend: num7(row.spend) ?? 0,
|
|
134951
|
+
impressions: num7(row.impressions) ?? 0,
|
|
134952
|
+
clicks: num7(row.clicks) ?? 0,
|
|
134953
|
+
results: num7(row.results),
|
|
134954
|
+
costPerResult: num7(row.costPerResult),
|
|
134955
|
+
reach: num7(row.reach),
|
|
134956
|
+
frequency: num7(row.frequency),
|
|
134957
|
+
ctr: num7(row.ctr),
|
|
134958
|
+
conversions: num7(row.conversions),
|
|
134959
|
+
costPerConversion: num7(row.costPerConversion)
|
|
134873
134960
|
};
|
|
134874
134961
|
for (const f of extras) {
|
|
134875
134962
|
if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
|
|
@@ -134905,14 +134992,14 @@ async function readManifest4(snapshotDir, accountId) {
|
|
|
134905
134992
|
async function loadSectionFile4(snapshotDir, file) {
|
|
134906
134993
|
try {
|
|
134907
134994
|
const raw = await fs30.readFile(path37.join(snapshotDir, file), "utf8");
|
|
134908
|
-
return
|
|
134995
|
+
return asRecord20(JSON.parse(raw));
|
|
134909
134996
|
} catch {
|
|
134910
134997
|
return null;
|
|
134911
134998
|
}
|
|
134912
134999
|
}
|
|
134913
135000
|
async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
|
|
134914
135001
|
const absDir = path37.resolve(snapshotDir);
|
|
134915
|
-
const metaAccountId = typeof
|
|
135002
|
+
const metaAccountId = typeof asRecord20(payload.meta)?.accountId === "string" ? asRecord20(payload.meta)?.accountId : void 0;
|
|
134916
135003
|
const manifest = await readManifest4(snapshotDir, metaAccountId);
|
|
134917
135004
|
if (!manifest) {
|
|
134918
135005
|
throw new Error(
|
|
@@ -134925,8 +135012,8 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
134925
135012
|
if (data) sectionMap.set(art.section, data);
|
|
134926
135013
|
}
|
|
134927
135014
|
const overview = sectionMap.get("overview");
|
|
134928
|
-
const currentPeriod =
|
|
134929
|
-
const previousPeriod =
|
|
135015
|
+
const currentPeriod = asRecord20(overview?.currentPeriod);
|
|
135016
|
+
const previousPeriod = asRecord20(overview?.previousPeriod);
|
|
134930
135017
|
const accountId = manifest.accountId ?? metaAccountId;
|
|
134931
135018
|
const meta = { ...payload.meta ?? {} };
|
|
134932
135019
|
if (!meta.accountId && accountId) meta.accountId = accountId;
|
|
@@ -134938,15 +135025,15 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
134938
135025
|
if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
134939
135026
|
const kpis = { ...payload.kpis ?? {} };
|
|
134940
135027
|
if (currentPeriod) {
|
|
134941
|
-
const spend =
|
|
134942
|
-
const impressions =
|
|
134943
|
-
const clicks =
|
|
134944
|
-
const results =
|
|
134945
|
-
const costPerResult =
|
|
134946
|
-
const ctr =
|
|
134947
|
-
const cpc =
|
|
134948
|
-
const reach =
|
|
134949
|
-
const frequency =
|
|
135028
|
+
const spend = num7(currentPeriod.spend);
|
|
135029
|
+
const impressions = num7(currentPeriod.impressions);
|
|
135030
|
+
const clicks = num7(currentPeriod.clicks);
|
|
135031
|
+
const results = num7(currentPeriod.results);
|
|
135032
|
+
const costPerResult = num7(currentPeriod.costPerResult);
|
|
135033
|
+
const ctr = num7(currentPeriod.ctr);
|
|
135034
|
+
const cpc = num7(currentPeriod.cpc ?? currentPeriod.averageCpc);
|
|
135035
|
+
const reach = num7(currentPeriod.reach);
|
|
135036
|
+
const frequency = num7(currentPeriod.frequency);
|
|
134950
135037
|
if (spend != null) kpis.spend = spend;
|
|
134951
135038
|
if (impressions != null) kpis.impressions = impressions;
|
|
134952
135039
|
if (clicks != null) kpis.clicks = clicks;
|
|
@@ -134959,15 +135046,15 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
134959
135046
|
}
|
|
134960
135047
|
if (previousPeriod) {
|
|
134961
135048
|
kpis.previousPeriod = {
|
|
134962
|
-
spend:
|
|
134963
|
-
impressions:
|
|
134964
|
-
clicks:
|
|
134965
|
-
results:
|
|
134966
|
-
costPerResult:
|
|
134967
|
-
ctr:
|
|
134968
|
-
cpc:
|
|
134969
|
-
reach:
|
|
134970
|
-
frequency:
|
|
135049
|
+
spend: num7(previousPeriod.spend),
|
|
135050
|
+
impressions: num7(previousPeriod.impressions),
|
|
135051
|
+
clicks: num7(previousPeriod.clicks),
|
|
135052
|
+
results: num7(previousPeriod.results),
|
|
135053
|
+
costPerResult: num7(previousPeriod.costPerResult),
|
|
135054
|
+
ctr: num7(previousPeriod.ctr),
|
|
135055
|
+
cpc: num7(previousPeriod.cpc ?? previousPeriod.averageCpc),
|
|
135056
|
+
reach: num7(previousPeriod.reach),
|
|
135057
|
+
frequency: num7(previousPeriod.frequency)
|
|
134971
135058
|
};
|
|
134972
135059
|
}
|
|
134973
135060
|
const tables = { ...payload.tables ?? {} };
|
|
@@ -135060,7 +135147,7 @@ function textLen4(value) {
|
|
|
135060
135147
|
function asArray5(value) {
|
|
135061
135148
|
return Array.isArray(value) ? value : [];
|
|
135062
135149
|
}
|
|
135063
|
-
function
|
|
135150
|
+
function asRecord21(value) {
|
|
135064
135151
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
135065
135152
|
return value;
|
|
135066
135153
|
}
|
|
@@ -135071,9 +135158,9 @@ function pushMissing4(missing, id, chapter, dimension, hint) {
|
|
|
135071
135158
|
}
|
|
135072
135159
|
function validateMetaDiagnosisReportContent(data) {
|
|
135073
135160
|
const missing = [];
|
|
135074
|
-
const meta =
|
|
135075
|
-
const narrative =
|
|
135076
|
-
const sections =
|
|
135161
|
+
const meta = asRecord21(data.meta) ?? {};
|
|
135162
|
+
const narrative = asRecord21(data.narrative) ?? {};
|
|
135163
|
+
const sections = asRecord21(narrative.sections) ?? {};
|
|
135077
135164
|
if (!textLen4(meta.accountId)) {
|
|
135078
135165
|
pushMissing4(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
|
|
135079
135166
|
}
|
|
@@ -135091,7 +135178,7 @@ function validateMetaDiagnosisReportContent(data) {
|
|
|
135091
135178
|
const sectionSuggestionCounts = {};
|
|
135092
135179
|
const sectionNotAvailable = {};
|
|
135093
135180
|
for (const key of NORMAL_SECTION_KEYS) {
|
|
135094
|
-
const section =
|
|
135181
|
+
const section = asRecord21(sections[key]);
|
|
135095
135182
|
const analysis = asArray5(section?.analysis).filter((s) => textLen4(s) > 0);
|
|
135096
135183
|
const suggestions = asArray5(section?.suggestions).filter((s) => textLen4(s) > 0);
|
|
135097
135184
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -135117,7 +135204,7 @@ function validateMetaDiagnosisReportContent(data) {
|
|
|
135117
135204
|
}
|
|
135118
135205
|
}
|
|
135119
135206
|
for (const key of NA_SECTION_KEYS) {
|
|
135120
|
-
const section =
|
|
135207
|
+
const section = asRecord21(sections[key]);
|
|
135121
135208
|
const analysis = asArray5(section?.analysis).filter((s) => textLen4(s) > 0);
|
|
135122
135209
|
const suggestions = asArray5(section?.suggestions).filter((s) => textLen4(s) > 0);
|
|
135123
135210
|
const notAvailable = section?.notAvailable === true;
|
|
@@ -135139,7 +135226,7 @@ function validateMetaDiagnosisReportContent(data) {
|
|
|
135139
135226
|
}
|
|
135140
135227
|
const recommendations = asArray5(narrative.recommendations).filter((r) => {
|
|
135141
135228
|
if (typeof r === "string") return textLen4(r) > 0;
|
|
135142
|
-
const rec =
|
|
135229
|
+
const rec = asRecord21(r);
|
|
135143
135230
|
return textLen4(rec?.content) > 0;
|
|
135144
135231
|
});
|
|
135145
135232
|
if (recommendations.length < 3) {
|
|
@@ -135212,7 +135299,7 @@ async function readJsonFile5(filePath) {
|
|
|
135212
135299
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
135213
135300
|
}
|
|
135214
135301
|
}
|
|
135215
|
-
function
|
|
135302
|
+
function asRecord22(value) {
|
|
135216
135303
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
135217
135304
|
return value;
|
|
135218
135305
|
}
|
|
@@ -135227,7 +135314,7 @@ function injectReportData4(html, payload) {
|
|
|
135227
135314
|
}
|
|
135228
135315
|
async function runFacebookAnalysisDiagnosisRender(opts) {
|
|
135229
135316
|
const dataPath = path38.resolve(opts.dataFile);
|
|
135230
|
-
let data =
|
|
135317
|
+
let data = asRecord22(await readJsonFile5(dataPath));
|
|
135231
135318
|
if (opts.snapshotDir?.trim()) {
|
|
135232
135319
|
data = await mergeMetaDiagnosisSnapshotIntoReport(data, path38.resolve(opts.snapshotDir));
|
|
135233
135320
|
}
|
|
@@ -135426,7 +135513,7 @@ function resolveFacebookPageSize(raw, fallback = FB_ENTITY_PAGE_SIZE_DEFAULT) {
|
|
|
135426
135513
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
135427
135514
|
return Math.min(Math.max(Math.floor(n), 1), FB_ENTITY_PAGE_SIZE_MAX);
|
|
135428
135515
|
}
|
|
135429
|
-
function
|
|
135516
|
+
function asRecord23(value) {
|
|
135430
135517
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
135431
135518
|
}
|
|
135432
135519
|
function pickProp(obj, ...keys) {
|
|
@@ -135436,12 +135523,12 @@ function pickProp(obj, ...keys) {
|
|
|
135436
135523
|
return void 0;
|
|
135437
135524
|
}
|
|
135438
135525
|
function unwrapFacebookPaged(raw) {
|
|
135439
|
-
const rec =
|
|
135526
|
+
const rec = asRecord23(raw);
|
|
135440
135527
|
if (!rec) return { items: [] };
|
|
135441
135528
|
const data = pickProp(rec, "data", "Data");
|
|
135442
135529
|
const items3 = Array.isArray(data) ? data : [];
|
|
135443
|
-
const paging =
|
|
135444
|
-
const cursors = paging ?
|
|
135530
|
+
const paging = asRecord23(pickProp(rec, "paging", "Paging"));
|
|
135531
|
+
const cursors = paging ? asRecord23(pickProp(paging, "cursors", "Cursors")) : void 0;
|
|
135445
135532
|
const afterRaw = cursors ? pickProp(cursors, "after", "After") : void 0;
|
|
135446
135533
|
const after = typeof afterRaw === "string" && afterRaw.trim() ? afterRaw.trim() : void 0;
|
|
135447
135534
|
return { items: items3, after };
|
|
@@ -135589,7 +135676,7 @@ async function fetchFacebookInsights(config, digits, level, opts) {
|
|
|
135589
135676
|
opts.verbose
|
|
135590
135677
|
);
|
|
135591
135678
|
if (Array.isArray(raw)) return raw;
|
|
135592
|
-
const rec =
|
|
135679
|
+
const rec = asRecord23(raw);
|
|
135593
135680
|
const data = rec ? pickProp(rec, "data", "Data") : void 0;
|
|
135594
135681
|
return Array.isArray(data) ? data : [];
|
|
135595
135682
|
}
|
|
@@ -135598,7 +135685,7 @@ function sumActionArray(raw) {
|
|
|
135598
135685
|
let sum = 0;
|
|
135599
135686
|
let hit = false;
|
|
135600
135687
|
for (const item of raw) {
|
|
135601
|
-
const rec =
|
|
135688
|
+
const rec = asRecord23(item);
|
|
135602
135689
|
if (!rec) continue;
|
|
135603
135690
|
const n = Number(rec.value);
|
|
135604
135691
|
if (!Number.isFinite(n)) continue;
|
|
@@ -136134,7 +136221,7 @@ init_auth();
|
|
|
136134
136221
|
init_balance();
|
|
136135
136222
|
|
|
136136
136223
|
// src/commands/bing-analysis/normalize-bing-overview.ts
|
|
136137
|
-
function
|
|
136224
|
+
function num8(v) {
|
|
136138
136225
|
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
136139
136226
|
}
|
|
136140
136227
|
function roundMoney(v) {
|
|
@@ -136155,19 +136242,19 @@ function countInclusiveCalendarDays(startDate, endDate) {
|
|
|
136155
136242
|
}
|
|
136156
136243
|
function periodSpend(period) {
|
|
136157
136244
|
if (!period || typeof period !== "object") return void 0;
|
|
136158
|
-
return
|
|
136245
|
+
return num8(period.spend);
|
|
136159
136246
|
}
|
|
136160
136247
|
function enrichBingOverviewDerivedMetrics(record2, startDate, endDate) {
|
|
136161
136248
|
const out = { ...record2 };
|
|
136162
136249
|
const currentSpend = periodSpend(out.currentPeriod);
|
|
136163
|
-
let totalCost =
|
|
136250
|
+
let totalCost = num8(out.totalCost);
|
|
136164
136251
|
if (totalCost == null || totalCost <= 0) {
|
|
136165
136252
|
if (currentSpend != null && currentSpend > 0) {
|
|
136166
136253
|
out.totalCost = currentSpend;
|
|
136167
136254
|
totalCost = currentSpend;
|
|
136168
136255
|
}
|
|
136169
136256
|
}
|
|
136170
|
-
let activeDays =
|
|
136257
|
+
let activeDays = num8(out.activeDays);
|
|
136171
136258
|
if (activeDays == null || activeDays <= 0) {
|
|
136172
136259
|
const calendarDays = countInclusiveCalendarDays(startDate, endDate);
|
|
136173
136260
|
if (calendarDays > 0) {
|
|
@@ -136175,7 +136262,7 @@ function enrichBingOverviewDerivedMetrics(record2, startDate, endDate) {
|
|
|
136175
136262
|
activeDays = calendarDays;
|
|
136176
136263
|
}
|
|
136177
136264
|
}
|
|
136178
|
-
const averageDailyCost =
|
|
136265
|
+
const averageDailyCost = num8(out.averageDailyCost);
|
|
136179
136266
|
if ((averageDailyCost == null || averageDailyCost <= 0) && totalCost != null && totalCost > 0 && activeDays != null && activeDays > 0) {
|
|
136180
136267
|
out.averageDailyCost = roundMoney(totalCost / activeDays);
|
|
136181
136268
|
}
|
|
@@ -136207,16 +136294,16 @@ function calcBingPreviousPeriodRange(startDate, endDate) {
|
|
|
136207
136294
|
function isBingPeriodBlockEmpty(period) {
|
|
136208
136295
|
if (!period || typeof period !== "object") return true;
|
|
136209
136296
|
const p = period;
|
|
136210
|
-
const spend =
|
|
136211
|
-
const impressions =
|
|
136212
|
-
const clicks =
|
|
136297
|
+
const spend = num8(p.spend) ?? 0;
|
|
136298
|
+
const impressions = num8(p.impressions) ?? 0;
|
|
136299
|
+
const clicks = num8(p.clicks) ?? 0;
|
|
136213
136300
|
return spend <= 0 && impressions <= 0 && clicks <= 0;
|
|
136214
136301
|
}
|
|
136215
136302
|
function syncOverviewBalanceFields(record2) {
|
|
136216
136303
|
const out = { ...record2 };
|
|
136217
|
-
const budget =
|
|
136304
|
+
const budget = num8(out.remainingAccountBudget);
|
|
136218
136305
|
if (budget != null && budget >= 0) {
|
|
136219
|
-
const balanceField =
|
|
136306
|
+
const balanceField = num8(out.balance);
|
|
136220
136307
|
if (balanceField == null || balanceField <= 0 && budget > 0) {
|
|
136221
136308
|
out.balance = budget;
|
|
136222
136309
|
}
|
|
@@ -136877,22 +136964,22 @@ import { fileURLToPath as fileURLToPath9 } from "url";
|
|
|
136877
136964
|
// src/commands/bing-analysis/merge-snapshot.ts
|
|
136878
136965
|
import * as fs32 from "fs/promises";
|
|
136879
136966
|
import * as path41 from "path";
|
|
136880
|
-
function
|
|
136967
|
+
function asRecord24(value) {
|
|
136881
136968
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
136882
136969
|
return value;
|
|
136883
136970
|
}
|
|
136884
136971
|
return null;
|
|
136885
136972
|
}
|
|
136886
|
-
function
|
|
136973
|
+
function num9(value) {
|
|
136887
136974
|
const n = Number(value);
|
|
136888
136975
|
return Number.isFinite(n) ? n : void 0;
|
|
136889
136976
|
}
|
|
136890
|
-
function
|
|
136977
|
+
function round26(value) {
|
|
136891
136978
|
return value == null ? void 0 : Math.round(value * 100) / 100;
|
|
136892
136979
|
}
|
|
136893
136980
|
function asRows2(value) {
|
|
136894
136981
|
if (Array.isArray(value)) {
|
|
136895
|
-
return value.filter((r) =>
|
|
136982
|
+
return value.filter((r) => asRecord24(r) !== null);
|
|
136896
136983
|
}
|
|
136897
136984
|
return [];
|
|
136898
136985
|
}
|
|
@@ -136929,14 +137016,14 @@ async function readManifest5(snapshotDir, accountId) {
|
|
|
136929
137016
|
}
|
|
136930
137017
|
function pickMetrics(row, extras = []) {
|
|
136931
137018
|
const out = {
|
|
136932
|
-
spend:
|
|
136933
|
-
impressions:
|
|
136934
|
-
clicks:
|
|
136935
|
-
ctr:
|
|
136936
|
-
averageCpc:
|
|
136937
|
-
conversions:
|
|
136938
|
-
conversionRate:
|
|
136939
|
-
costPerConversion:
|
|
137019
|
+
spend: num9(row.spend) ?? 0,
|
|
137020
|
+
impressions: num9(row.impressions) ?? 0,
|
|
137021
|
+
clicks: num9(row.clicks) ?? 0,
|
|
137022
|
+
ctr: num9(row.ctr),
|
|
137023
|
+
averageCpc: num9(row.averageCpc),
|
|
137024
|
+
conversions: num9(row.conversions) ?? 0,
|
|
137025
|
+
conversionRate: num9(row.conversionRate),
|
|
137026
|
+
costPerConversion: num9(row.costPerConversion)
|
|
136940
137027
|
};
|
|
136941
137028
|
for (const f of extras) {
|
|
136942
137029
|
if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
|
|
@@ -136950,19 +137037,19 @@ function sumBingCampaignRowsToPeriodKpis(rows) {
|
|
|
136950
137037
|
let clicks = 0;
|
|
136951
137038
|
let conversions = 0;
|
|
136952
137039
|
for (const row of rows) {
|
|
136953
|
-
spend +=
|
|
136954
|
-
impressions +=
|
|
136955
|
-
clicks +=
|
|
136956
|
-
conversions +=
|
|
137040
|
+
spend += num9(row.spend) ?? 0;
|
|
137041
|
+
impressions += num9(row.impressions) ?? 0;
|
|
137042
|
+
clicks += num9(row.clicks) ?? 0;
|
|
137043
|
+
conversions += num9(row.conversions) ?? 0;
|
|
136957
137044
|
}
|
|
136958
137045
|
return {
|
|
136959
|
-
spend:
|
|
137046
|
+
spend: round26(spend),
|
|
136960
137047
|
impressions: Math.round(impressions),
|
|
136961
137048
|
clicks: Math.round(clicks),
|
|
136962
|
-
conversions:
|
|
137049
|
+
conversions: round26(conversions),
|
|
136963
137050
|
ctr: impressions > 0 ? clicks / impressions : void 0,
|
|
136964
|
-
averageCpc: clicks > 0 ?
|
|
136965
|
-
costPerConversion: conversions > 0 ?
|
|
137051
|
+
averageCpc: clicks > 0 ? round26(spend / clicks) : void 0,
|
|
137052
|
+
costPerConversion: conversions > 0 ? round26(spend / conversions) : void 0
|
|
136966
137053
|
};
|
|
136967
137054
|
}
|
|
136968
137055
|
function campaignStatusDisplay(status) {
|
|
@@ -136974,17 +137061,17 @@ function campaignStatusDisplay(status) {
|
|
|
136974
137061
|
return status == null || status === "" ? "\u2014" : String(status);
|
|
136975
137062
|
}
|
|
136976
137063
|
function mapDeviceRows(payload) {
|
|
136977
|
-
const obj =
|
|
137064
|
+
const obj = asRecord24(payload);
|
|
136978
137065
|
return asRows2(obj?.devices).map((r) => pickMetrics(r, ["deviceType"]));
|
|
136979
137066
|
}
|
|
136980
137067
|
function mapGeographicRows(payload) {
|
|
136981
|
-
const obj =
|
|
137068
|
+
const obj = asRecord24(payload);
|
|
136982
137069
|
return asRows2(obj?.countries).map(
|
|
136983
137070
|
(r) => pickMetrics(r, ["countryOrRegion", "countryCriteriaId", "regionName", "cityName"])
|
|
136984
137071
|
);
|
|
136985
137072
|
}
|
|
136986
137073
|
function mapAudienceLabelRows(payload, labelKey) {
|
|
136987
|
-
const obj =
|
|
137074
|
+
const obj = asRecord24(payload);
|
|
136988
137075
|
return asRows2(obj?.audience).map((r) => {
|
|
136989
137076
|
const m = pickMetrics(r, ["audience", "bidModifier"]);
|
|
136990
137077
|
m[labelKey] = r.audience;
|
|
@@ -136992,7 +137079,7 @@ function mapAudienceLabelRows(payload, labelKey) {
|
|
|
136992
137079
|
});
|
|
136993
137080
|
}
|
|
136994
137081
|
function mapAudienceFromMerged(payload) {
|
|
136995
|
-
const data =
|
|
137082
|
+
const data = asRecord24(asRecord24(payload)?.data);
|
|
136996
137083
|
return {
|
|
136997
137084
|
audienceAge: mapAudienceLabelRows(data?.ageAudience, "ageRange"),
|
|
136998
137085
|
audienceGender: mapAudienceLabelRows(data?.genderAudience, "gender")
|
|
@@ -137087,7 +137174,7 @@ function mapSearchTermRows(payload) {
|
|
|
137087
137174
|
}
|
|
137088
137175
|
async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
137089
137176
|
const absDir = path41.resolve(snapshotDir);
|
|
137090
|
-
const metaAccountId = typeof
|
|
137177
|
+
const metaAccountId = typeof asRecord24(payload.meta)?.accountId === "string" ? asRecord24(payload.meta)?.accountId : void 0;
|
|
137091
137178
|
const manifest = await readManifest5(snapshotDir, metaAccountId);
|
|
137092
137179
|
if (!manifest) {
|
|
137093
137180
|
throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 report-manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C bing-analysis --json-out`);
|
|
@@ -137097,9 +137184,9 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
137097
137184
|
const data = await loadSectionPayload(absDir, art.file);
|
|
137098
137185
|
if (data != null) sectionMap.set(art.section, data);
|
|
137099
137186
|
}
|
|
137100
|
-
const overview =
|
|
137101
|
-
const currentPeriod =
|
|
137102
|
-
const previousPeriod =
|
|
137187
|
+
const overview = asRecord24(sectionMap.get("overview"));
|
|
137188
|
+
const currentPeriod = asRecord24(overview?.currentPeriod);
|
|
137189
|
+
const previousPeriod = asRecord24(overview?.previousPeriod);
|
|
137103
137190
|
const accountId = manifest.accountId ?? metaAccountId;
|
|
137104
137191
|
const campaignRows3 = mapCampaignRows2(sectionMap.get("campaigns"));
|
|
137105
137192
|
const campaignKpis = sumBingCampaignRowsToPeriodKpis(campaignRows3);
|
|
@@ -137118,13 +137205,13 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
137118
137205
|
if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
137119
137206
|
const kpis = { ...payload.kpis ?? {} };
|
|
137120
137207
|
const currentKpiSource = campaignKpis ?? (currentPeriod ? {
|
|
137121
|
-
spend:
|
|
137122
|
-
impressions:
|
|
137123
|
-
clicks:
|
|
137124
|
-
conversions:
|
|
137125
|
-
ctr:
|
|
137126
|
-
averageCpc:
|
|
137127
|
-
costPerConversion:
|
|
137208
|
+
spend: num9(currentPeriod.spend),
|
|
137209
|
+
impressions: num9(currentPeriod.impressions),
|
|
137210
|
+
clicks: num9(currentPeriod.clicks),
|
|
137211
|
+
conversions: num9(currentPeriod.conversions),
|
|
137212
|
+
ctr: num9(currentPeriod.ctr),
|
|
137213
|
+
averageCpc: num9(currentPeriod.averageCpc),
|
|
137214
|
+
costPerConversion: num9(currentPeriod.costPerConversion)
|
|
137128
137215
|
} : null);
|
|
137129
137216
|
if (currentKpiSource) {
|
|
137130
137217
|
if (currentKpiSource.spend != null) kpis.spend = currentKpiSource.spend;
|
|
@@ -137138,24 +137225,24 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
137138
137225
|
}
|
|
137139
137226
|
}
|
|
137140
137227
|
if (overview) {
|
|
137141
|
-
const avgDaily =
|
|
137228
|
+
const avgDaily = num9(overview.averageDailyCost);
|
|
137142
137229
|
if (avgDaily != null) kpis.averageDailyCost = avgDaily;
|
|
137143
|
-
const bal =
|
|
137230
|
+
const bal = num9(overview.balance);
|
|
137144
137231
|
if (bal != null) kpis.balance = bal;
|
|
137145
|
-
const rem =
|
|
137232
|
+
const rem = num9(overview.remainingAccountBudget);
|
|
137146
137233
|
if (rem != null) kpis.remainingAccountBudget = rem;
|
|
137147
|
-
const opt =
|
|
137234
|
+
const opt = num9(overview.optimizationScore);
|
|
137148
137235
|
if (opt != null) kpis.optimizationScore = opt;
|
|
137149
137236
|
}
|
|
137150
137237
|
if (previousPeriod) {
|
|
137151
137238
|
kpis.previousPeriod = {
|
|
137152
|
-
spend:
|
|
137153
|
-
impressions:
|
|
137154
|
-
clicks:
|
|
137155
|
-
conversions:
|
|
137156
|
-
ctr:
|
|
137157
|
-
averageCpc:
|
|
137158
|
-
costPerConversion:
|
|
137239
|
+
spend: num9(previousPeriod.spend),
|
|
137240
|
+
impressions: num9(previousPeriod.impressions),
|
|
137241
|
+
clicks: num9(previousPeriod.clicks),
|
|
137242
|
+
conversions: num9(previousPeriod.conversions),
|
|
137243
|
+
ctr: num9(previousPeriod.ctr),
|
|
137244
|
+
averageCpc: num9(previousPeriod.averageCpc),
|
|
137245
|
+
costPerConversion: num9(previousPeriod.costPerConversion)
|
|
137159
137246
|
};
|
|
137160
137247
|
}
|
|
137161
137248
|
const tables = { ...payload.tables ?? {} };
|
|
@@ -137226,7 +137313,7 @@ function textLen5(value) {
|
|
|
137226
137313
|
function asArray6(value) {
|
|
137227
137314
|
return Array.isArray(value) ? value : [];
|
|
137228
137315
|
}
|
|
137229
|
-
function
|
|
137316
|
+
function asRecord25(value) {
|
|
137230
137317
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
137231
137318
|
return value;
|
|
137232
137319
|
}
|
|
@@ -137237,9 +137324,9 @@ function pushMissing5(missing, id, chapter, dimension, hint) {
|
|
|
137237
137324
|
}
|
|
137238
137325
|
function validateBingPeriodReportContent(data) {
|
|
137239
137326
|
const missing = [];
|
|
137240
|
-
const meta =
|
|
137241
|
-
const narrative =
|
|
137242
|
-
const sections =
|
|
137327
|
+
const meta = asRecord25(data.meta) ?? {};
|
|
137328
|
+
const narrative = asRecord25(data.narrative) ?? {};
|
|
137329
|
+
const sections = asRecord25(narrative.sections) ?? {};
|
|
137243
137330
|
if (!textLen5(meta.accountId)) {
|
|
137244
137331
|
pushMissing5(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
|
|
137245
137332
|
}
|
|
@@ -137256,7 +137343,7 @@ function validateBingPeriodReportContent(data) {
|
|
|
137256
137343
|
const sectionAnalysisCounts = {};
|
|
137257
137344
|
const sectionSuggestionCounts = {};
|
|
137258
137345
|
for (const key of SECTION_KEYS2) {
|
|
137259
|
-
const section =
|
|
137346
|
+
const section = asRecord25(sections[key]);
|
|
137260
137347
|
const analysis = asArray6(section?.analysis).filter((s) => textLen5(s) > 0);
|
|
137261
137348
|
const suggestions = asArray6(section?.suggestions).filter((s) => textLen5(s) > 0);
|
|
137262
137349
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -137282,7 +137369,7 @@ function validateBingPeriodReportContent(data) {
|
|
|
137282
137369
|
}
|
|
137283
137370
|
const recommendations = asArray6(narrative.recommendations).filter((r) => {
|
|
137284
137371
|
if (typeof r === "string") return textLen5(r) > 0;
|
|
137285
|
-
const rec =
|
|
137372
|
+
const rec = asRecord25(r);
|
|
137286
137373
|
return textLen5(rec?.content) > 0;
|
|
137287
137374
|
});
|
|
137288
137375
|
if (recommendations.length < 3) {
|
|
@@ -137343,7 +137430,7 @@ async function readJsonFile6(filePath) {
|
|
|
137343
137430
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
137344
137431
|
}
|
|
137345
137432
|
}
|
|
137346
|
-
function
|
|
137433
|
+
function asRecord26(value) {
|
|
137347
137434
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
137348
137435
|
return value;
|
|
137349
137436
|
}
|
|
@@ -137358,7 +137445,7 @@ function injectReportData5(html, payload) {
|
|
|
137358
137445
|
}
|
|
137359
137446
|
async function runBingAnalysisRender(opts) {
|
|
137360
137447
|
const dataPath = path42.resolve(opts.dataFile);
|
|
137361
|
-
let data =
|
|
137448
|
+
let data = asRecord26(await readJsonFile6(dataPath));
|
|
137362
137449
|
if (opts.snapshotDir?.trim()) {
|
|
137363
137450
|
data = await mergeBingAnalysisSnapshotIntoReport(data, path42.resolve(opts.snapshotDir));
|
|
137364
137451
|
}
|
|
@@ -137831,17 +137918,17 @@ import { fileURLToPath as fileURLToPath10 } from "url";
|
|
|
137831
137918
|
// src/commands/tiktok-analysis/merge-snapshot.ts
|
|
137832
137919
|
import * as fs34 from "fs/promises";
|
|
137833
137920
|
import * as path44 from "path";
|
|
137834
|
-
function
|
|
137921
|
+
function asRecord27(value) {
|
|
137835
137922
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
137836
137923
|
return value;
|
|
137837
137924
|
}
|
|
137838
137925
|
return null;
|
|
137839
137926
|
}
|
|
137840
|
-
function
|
|
137927
|
+
function num10(value) {
|
|
137841
137928
|
const n = Number(value);
|
|
137842
137929
|
return Number.isFinite(n) ? n : void 0;
|
|
137843
137930
|
}
|
|
137844
|
-
function
|
|
137931
|
+
function round27(value) {
|
|
137845
137932
|
return value == null ? void 0 : Math.round(value * 100) / 100;
|
|
137846
137933
|
}
|
|
137847
137934
|
async function loadSectionPayload2(snapshotDir, file) {
|
|
@@ -137876,16 +137963,16 @@ async function readManifest6(snapshotDir, accountId) {
|
|
|
137876
137963
|
return null;
|
|
137877
137964
|
}
|
|
137878
137965
|
function normalizeTikTokRate(value) {
|
|
137879
|
-
const n =
|
|
137966
|
+
const n = num10(value);
|
|
137880
137967
|
if (n == null) return void 0;
|
|
137881
137968
|
return n > 1 ? n / 100 : n;
|
|
137882
137969
|
}
|
|
137883
137970
|
function normalizeTikTokMetrics(metrics) {
|
|
137884
137971
|
const m = metrics ?? {};
|
|
137885
|
-
const spend =
|
|
137886
|
-
const impressions =
|
|
137887
|
-
const clicks =
|
|
137888
|
-
const conversions =
|
|
137972
|
+
const spend = num10(m.spend);
|
|
137973
|
+
const impressions = num10(m.impressions);
|
|
137974
|
+
const clicks = num10(m.clicks);
|
|
137975
|
+
const conversions = num10(m.conversion);
|
|
137889
137976
|
const ctrFromCounts = impressions != null && impressions > 0 && clicks != null ? clicks / impressions : void 0;
|
|
137890
137977
|
const cvrFromCounts = clicks != null && clicks > 0 && conversions != null ? conversions / clicks : void 0;
|
|
137891
137978
|
return {
|
|
@@ -137894,8 +137981,8 @@ function normalizeTikTokMetrics(metrics) {
|
|
|
137894
137981
|
clicks: clicks ?? 0,
|
|
137895
137982
|
conversions: conversions == null ? 0 : Math.round(conversions),
|
|
137896
137983
|
ctr: ctrFromCounts ?? normalizeTikTokRate(m.ctr),
|
|
137897
|
-
averageCpc:
|
|
137898
|
-
costPerConversion:
|
|
137984
|
+
averageCpc: num10(m.cpc),
|
|
137985
|
+
costPerConversion: num10(m.cost_per_conversion),
|
|
137899
137986
|
conversionRate: cvrFromCounts ?? normalizeTikTokRate(m.conversion_rate)
|
|
137900
137987
|
};
|
|
137901
137988
|
}
|
|
@@ -137915,19 +138002,19 @@ function sumTikTokCampaignRowsToPeriodKpis(rows) {
|
|
|
137915
138002
|
let clicks = 0;
|
|
137916
138003
|
let conversions = 0;
|
|
137917
138004
|
for (const row of rows) {
|
|
137918
|
-
spend +=
|
|
137919
|
-
impressions +=
|
|
137920
|
-
clicks +=
|
|
137921
|
-
conversions +=
|
|
138005
|
+
spend += num10(row.spend) ?? 0;
|
|
138006
|
+
impressions += num10(row.impressions) ?? 0;
|
|
138007
|
+
clicks += num10(row.clicks) ?? 0;
|
|
138008
|
+
conversions += num10(row.conversions) ?? 0;
|
|
137922
138009
|
}
|
|
137923
138010
|
return {
|
|
137924
|
-
spend:
|
|
138011
|
+
spend: round27(spend),
|
|
137925
138012
|
impressions: Math.round(impressions),
|
|
137926
138013
|
clicks: Math.round(clicks),
|
|
137927
|
-
conversions:
|
|
138014
|
+
conversions: round27(conversions),
|
|
137928
138015
|
ctr: impressions > 0 ? clicks / impressions : void 0,
|
|
137929
|
-
averageCpc: clicks > 0 ?
|
|
137930
|
-
costPerConversion: conversions > 0 ?
|
|
138016
|
+
averageCpc: clicks > 0 ? round27(spend / clicks) : void 0,
|
|
138017
|
+
costPerConversion: conversions > 0 ? round27(spend / conversions) : void 0
|
|
137931
138018
|
};
|
|
137932
138019
|
}
|
|
137933
138020
|
function mapCampaignTable(rows) {
|
|
@@ -137935,13 +138022,13 @@ function mapCampaignTable(rows) {
|
|
|
137935
138022
|
campaignId: r.campaignId,
|
|
137936
138023
|
campaignName: r.campaignName,
|
|
137937
138024
|
status: r.campaignStatus,
|
|
137938
|
-
spend:
|
|
137939
|
-
impressions:
|
|
137940
|
-
clicks:
|
|
137941
|
-
conversions:
|
|
137942
|
-
ctr:
|
|
137943
|
-
averageCpc:
|
|
137944
|
-
costPerConversion:
|
|
138025
|
+
spend: num10(r.spend) ?? 0,
|
|
138026
|
+
impressions: num10(r.impressions) ?? 0,
|
|
138027
|
+
clicks: num10(r.clicks) ?? 0,
|
|
138028
|
+
conversions: num10(r.conversions) ?? 0,
|
|
138029
|
+
ctr: num10(r.ctr),
|
|
138030
|
+
averageCpc: num10(r.averageCpc),
|
|
138031
|
+
costPerConversion: num10(r.costPerConversion)
|
|
137945
138032
|
}));
|
|
137946
138033
|
}
|
|
137947
138034
|
function pickTikTokName(item, metricKey, dimensionKey) {
|
|
@@ -138009,7 +138096,7 @@ function mapAudienceMerged(payload) {
|
|
|
138009
138096
|
}
|
|
138010
138097
|
async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
138011
138098
|
const absDir = path44.resolve(snapshotDir);
|
|
138012
|
-
const metaAccountId = typeof
|
|
138099
|
+
const metaAccountId = typeof asRecord27(payload.meta)?.accountId === "string" ? asRecord27(payload.meta)?.accountId : void 0;
|
|
138013
138100
|
const manifest = await readManifest6(snapshotDir, metaAccountId);
|
|
138014
138101
|
if (!manifest) {
|
|
138015
138102
|
throw new Error(
|
|
@@ -138038,13 +138125,13 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
138038
138125
|
if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
138039
138126
|
const kpis = { ...payload.kpis ?? {} };
|
|
138040
138127
|
const currentKpiSource = campaignKpis ?? (currentPeriod ? {
|
|
138041
|
-
spend:
|
|
138042
|
-
impressions:
|
|
138043
|
-
clicks:
|
|
138044
|
-
conversions:
|
|
138045
|
-
ctr:
|
|
138046
|
-
averageCpc:
|
|
138047
|
-
costPerConversion:
|
|
138128
|
+
spend: num10(currentPeriod.spend),
|
|
138129
|
+
impressions: num10(currentPeriod.impressions),
|
|
138130
|
+
clicks: num10(currentPeriod.clicks),
|
|
138131
|
+
conversions: num10(currentPeriod.conversions),
|
|
138132
|
+
ctr: num10(currentPeriod.ctr),
|
|
138133
|
+
averageCpc: num10(currentPeriod.averageCpc),
|
|
138134
|
+
costPerConversion: num10(currentPeriod.costPerConversion)
|
|
138048
138135
|
} : null);
|
|
138049
138136
|
if (currentKpiSource) {
|
|
138050
138137
|
if (currentKpiSource.spend != null) kpis.spend = currentKpiSource.spend;
|
|
@@ -138059,13 +138146,13 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
138059
138146
|
}
|
|
138060
138147
|
if (previousPeriod) {
|
|
138061
138148
|
kpis.previousPeriod = {
|
|
138062
|
-
spend:
|
|
138063
|
-
impressions:
|
|
138064
|
-
clicks:
|
|
138065
|
-
conversions:
|
|
138066
|
-
ctr:
|
|
138067
|
-
averageCpc:
|
|
138068
|
-
costPerConversion:
|
|
138149
|
+
spend: num10(previousPeriod.spend),
|
|
138150
|
+
impressions: num10(previousPeriod.impressions),
|
|
138151
|
+
clicks: num10(previousPeriod.clicks),
|
|
138152
|
+
conversions: num10(previousPeriod.conversions),
|
|
138153
|
+
ctr: num10(previousPeriod.ctr),
|
|
138154
|
+
averageCpc: num10(previousPeriod.averageCpc),
|
|
138155
|
+
costPerConversion: num10(previousPeriod.costPerConversion)
|
|
138069
138156
|
};
|
|
138070
138157
|
}
|
|
138071
138158
|
const tables = { ...payload.tables ?? {} };
|
|
@@ -138123,7 +138210,7 @@ function textLen6(value) {
|
|
|
138123
138210
|
function asArray7(value) {
|
|
138124
138211
|
return Array.isArray(value) ? value : [];
|
|
138125
138212
|
}
|
|
138126
|
-
function
|
|
138213
|
+
function asRecord28(value) {
|
|
138127
138214
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
138128
138215
|
return value;
|
|
138129
138216
|
}
|
|
@@ -138134,9 +138221,9 @@ function pushMissing6(missing, id, chapter, dimension, hint) {
|
|
|
138134
138221
|
}
|
|
138135
138222
|
function validateTikTokPeriodReportContent(data) {
|
|
138136
138223
|
const missing = [];
|
|
138137
|
-
const meta =
|
|
138138
|
-
const narrative =
|
|
138139
|
-
const sections =
|
|
138224
|
+
const meta = asRecord28(data.meta) ?? {};
|
|
138225
|
+
const narrative = asRecord28(data.narrative) ?? {};
|
|
138226
|
+
const sections = asRecord28(narrative.sections) ?? {};
|
|
138140
138227
|
if (!textLen6(meta.accountId)) {
|
|
138141
138228
|
pushMissing6(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
|
|
138142
138229
|
}
|
|
@@ -138153,7 +138240,7 @@ function validateTikTokPeriodReportContent(data) {
|
|
|
138153
138240
|
const sectionAnalysisCounts = {};
|
|
138154
138241
|
const sectionSuggestionCounts = {};
|
|
138155
138242
|
for (const key of SECTION_KEYS3) {
|
|
138156
|
-
const section =
|
|
138243
|
+
const section = asRecord28(sections[key]);
|
|
138157
138244
|
const analysis = asArray7(section?.analysis).filter((s) => textLen6(s) > 0);
|
|
138158
138245
|
const suggestions = asArray7(section?.suggestions).filter((s) => textLen6(s) > 0);
|
|
138159
138246
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -138179,7 +138266,7 @@ function validateTikTokPeriodReportContent(data) {
|
|
|
138179
138266
|
}
|
|
138180
138267
|
const recommendations = asArray7(narrative.recommendations).filter((r) => {
|
|
138181
138268
|
if (typeof r === "string") return textLen6(r) > 0;
|
|
138182
|
-
const rec =
|
|
138269
|
+
const rec = asRecord28(r);
|
|
138183
138270
|
return textLen6(rec?.content) > 0;
|
|
138184
138271
|
});
|
|
138185
138272
|
if (recommendations.length < 3) {
|
|
@@ -138240,7 +138327,7 @@ async function readJsonFile7(filePath) {
|
|
|
138240
138327
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
138241
138328
|
}
|
|
138242
138329
|
}
|
|
138243
|
-
function
|
|
138330
|
+
function asRecord29(value) {
|
|
138244
138331
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
138245
138332
|
return value;
|
|
138246
138333
|
}
|
|
@@ -138255,7 +138342,7 @@ function injectReportData6(html, payload) {
|
|
|
138255
138342
|
}
|
|
138256
138343
|
async function runTikTokAnalysisRender(opts) {
|
|
138257
138344
|
const dataPath = path45.resolve(opts.dataFile);
|
|
138258
|
-
let data =
|
|
138345
|
+
let data = asRecord29(await readJsonFile7(dataPath));
|
|
138259
138346
|
if (opts.snapshotDir?.trim()) {
|
|
138260
138347
|
data = await mergeTikTokAnalysisSnapshotIntoReport(data, path45.resolve(opts.snapshotDir));
|
|
138261
138348
|
}
|
|
@@ -139354,46 +139441,46 @@ function endpointHintForYandexSection(def) {
|
|
|
139354
139441
|
// src/commands/yandex-analysis/merge-snapshot.ts
|
|
139355
139442
|
import * as fs36 from "fs/promises";
|
|
139356
139443
|
import * as path46 from "path";
|
|
139357
|
-
function
|
|
139444
|
+
function asRecord30(value) {
|
|
139358
139445
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
139359
139446
|
return value;
|
|
139360
139447
|
}
|
|
139361
139448
|
return null;
|
|
139362
139449
|
}
|
|
139363
|
-
function
|
|
139450
|
+
function num11(value) {
|
|
139364
139451
|
if (value === null || value === void 0 || value === "") return void 0;
|
|
139365
139452
|
const n = Number(value);
|
|
139366
139453
|
return Number.isFinite(n) ? n : void 0;
|
|
139367
139454
|
}
|
|
139368
|
-
function
|
|
139455
|
+
function round28(value) {
|
|
139369
139456
|
return value == null ? void 0 : Math.round(value * 100) / 100;
|
|
139370
139457
|
}
|
|
139371
139458
|
function normalizeYandexRoi(value) {
|
|
139372
139459
|
if (typeof value !== "number" && typeof value !== "string") return void 0;
|
|
139373
|
-
const r =
|
|
139460
|
+
const r = num11(value);
|
|
139374
139461
|
return r === -1 ? void 0 : r;
|
|
139375
139462
|
}
|
|
139376
139463
|
function asItems(payload) {
|
|
139377
|
-
const obj =
|
|
139464
|
+
const obj = asRecord30(payload);
|
|
139378
139465
|
if (!obj) return [];
|
|
139379
139466
|
const items3 = obj.items;
|
|
139380
139467
|
if (!Array.isArray(items3)) return [];
|
|
139381
|
-
return items3.filter((r) =>
|
|
139468
|
+
return items3.filter((r) => asRecord30(r) !== null);
|
|
139382
139469
|
}
|
|
139383
139470
|
function pickMetrics2(row, extras = []) {
|
|
139384
|
-
const spend =
|
|
139385
|
-
const impressions =
|
|
139386
|
-
const clicks =
|
|
139387
|
-
const conversions =
|
|
139471
|
+
const spend = num11(row.spend) ?? 0;
|
|
139472
|
+
const impressions = num11(row.impressions) ?? 0;
|
|
139473
|
+
const clicks = num11(row.clicks) ?? 0;
|
|
139474
|
+
const conversions = num11(row.conversions) ?? 0;
|
|
139388
139475
|
const out = {
|
|
139389
139476
|
spend,
|
|
139390
139477
|
impressions,
|
|
139391
139478
|
clicks,
|
|
139392
139479
|
conversions,
|
|
139393
|
-
ctr:
|
|
139394
|
-
averageCpc:
|
|
139395
|
-
conversionRate:
|
|
139396
|
-
costPerConversion:
|
|
139480
|
+
ctr: num11(row.ctr) ?? (impressions > 0 ? clicks / impressions : void 0),
|
|
139481
|
+
averageCpc: num11(row.averageCpc) ?? (clicks > 0 ? round28(spend / clicks) : void 0),
|
|
139482
|
+
conversionRate: num11(row.conversionRate) ?? (clicks > 0 ? conversions / clicks : void 0),
|
|
139483
|
+
costPerConversion: num11(row.costPerConversion) ?? (conversions > 0 ? round28(spend / conversions) : void 0),
|
|
139397
139484
|
roi: normalizeYandexRoi(row.roi)
|
|
139398
139485
|
};
|
|
139399
139486
|
for (const f of extras) {
|
|
@@ -139489,10 +139576,10 @@ function aggregateDailyToWeekly(dailyRows) {
|
|
|
139489
139576
|
map.set(key, acc);
|
|
139490
139577
|
}
|
|
139491
139578
|
acc.days += 1;
|
|
139492
|
-
acc.spend +=
|
|
139493
|
-
acc.impressions +=
|
|
139494
|
-
acc.clicks +=
|
|
139495
|
-
acc.conversions +=
|
|
139579
|
+
acc.spend += num11(row.spend) ?? 0;
|
|
139580
|
+
acc.impressions += num11(row.impressions) ?? 0;
|
|
139581
|
+
acc.clicks += num11(row.clicks) ?? 0;
|
|
139582
|
+
acc.conversions += num11(row.conversions) ?? 0;
|
|
139496
139583
|
}
|
|
139497
139584
|
return [...map.values()].sort((a, b) => a.weekStart.getTime() - b.weekStart.getTime()).map((acc) => {
|
|
139498
139585
|
const startDate = formatYmd2(acc.weekStart);
|
|
@@ -139503,18 +139590,18 @@ function aggregateDailyToWeekly(dailyRows) {
|
|
|
139503
139590
|
startDate,
|
|
139504
139591
|
endDate,
|
|
139505
139592
|
days: acc.days,
|
|
139506
|
-
spend:
|
|
139593
|
+
spend: round28(acc.spend),
|
|
139507
139594
|
impressions: Math.round(acc.impressions),
|
|
139508
139595
|
clicks: Math.round(acc.clicks),
|
|
139509
|
-
conversions:
|
|
139596
|
+
conversions: round28(acc.conversions),
|
|
139510
139597
|
ctr: acc.impressions > 0 ? acc.clicks / acc.impressions : void 0,
|
|
139511
|
-
averageCpc: acc.clicks > 0 ?
|
|
139512
|
-
costPerConversion: acc.conversions > 0 ?
|
|
139598
|
+
averageCpc: acc.clicks > 0 ? round28(acc.spend / acc.clicks) : void 0,
|
|
139599
|
+
costPerConversion: acc.conversions > 0 ? round28(acc.spend / acc.conversions) : void 0
|
|
139513
139600
|
};
|
|
139514
139601
|
});
|
|
139515
139602
|
}
|
|
139516
139603
|
function prepareYandexDailyPayload(payload) {
|
|
139517
|
-
const obj =
|
|
139604
|
+
const obj = asRecord30(payload) ?? {};
|
|
139518
139605
|
const items3 = asItems(obj).map((r) => {
|
|
139519
139606
|
const date = normalizeYandexDailyDate(r.date);
|
|
139520
139607
|
return date ? { ...r, date } : r;
|
|
@@ -139583,7 +139670,7 @@ function mapDeviceRows2(payload) {
|
|
|
139583
139670
|
}
|
|
139584
139671
|
async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
139585
139672
|
const absDir = path46.resolve(snapshotDir);
|
|
139586
|
-
const metaAccountId = typeof
|
|
139673
|
+
const metaAccountId = typeof asRecord30(payload.meta)?.accountId === "string" ? asRecord30(payload.meta)?.accountId : void 0;
|
|
139587
139674
|
const manifest = await readManifest7(snapshotDir, metaAccountId);
|
|
139588
139675
|
if (!manifest) {
|
|
139589
139676
|
throw new Error(
|
|
@@ -139595,8 +139682,8 @@ async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
139595
139682
|
const data = await loadSectionPayload3(absDir, art.file);
|
|
139596
139683
|
if (data != null) sectionMap.set(art.section, data);
|
|
139597
139684
|
}
|
|
139598
|
-
const overview =
|
|
139599
|
-
const totals =
|
|
139685
|
+
const overview = asRecord30(sectionMap.get("overview"));
|
|
139686
|
+
const totals = asRecord30(overview?.totals);
|
|
139600
139687
|
const accountId = manifest.accountId ?? metaAccountId;
|
|
139601
139688
|
const meta = { ...payload.meta ?? {} };
|
|
139602
139689
|
if (!meta.accountId && accountId) meta.accountId = accountId;
|
|
@@ -139607,24 +139694,24 @@ async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
|
|
|
139607
139694
|
if (!meta.platform) meta.platform = "Yandex Direct";
|
|
139608
139695
|
const kpis = { ...payload.kpis ?? {} };
|
|
139609
139696
|
if (totals) {
|
|
139610
|
-
if (
|
|
139611
|
-
if (
|
|
139612
|
-
if (
|
|
139613
|
-
if (
|
|
139614
|
-
if (
|
|
139615
|
-
if (
|
|
139616
|
-
if (
|
|
139617
|
-
kpis.costPerConversion =
|
|
139618
|
-
}
|
|
139619
|
-
if (
|
|
139697
|
+
if (num11(totals.spend) != null) kpis.spend = round28(num11(totals.spend));
|
|
139698
|
+
if (num11(totals.impressions) != null) kpis.impressions = Math.round(num11(totals.impressions));
|
|
139699
|
+
if (num11(totals.clicks) != null) kpis.clicks = Math.round(num11(totals.clicks));
|
|
139700
|
+
if (num11(totals.conversions) != null) kpis.conversions = round28(num11(totals.conversions));
|
|
139701
|
+
if (num11(totals.ctr) != null) kpis.ctr = num11(totals.ctr);
|
|
139702
|
+
if (num11(totals.averageCpc) != null) kpis.averageCpc = round28(num11(totals.averageCpc));
|
|
139703
|
+
if (num11(totals.costPerConversion) != null) {
|
|
139704
|
+
kpis.costPerConversion = round28(num11(totals.costPerConversion));
|
|
139705
|
+
}
|
|
139706
|
+
if (num11(totals.conversionRate) != null) kpis.conversionRate = num11(totals.conversionRate);
|
|
139620
139707
|
const overviewRoi = normalizeYandexRoi(totals.roi);
|
|
139621
139708
|
if (overviewRoi != null) kpis.roi = overviewRoi;
|
|
139622
139709
|
}
|
|
139623
139710
|
if (overview) {
|
|
139624
|
-
if (
|
|
139625
|
-
if (
|
|
139626
|
-
kpis.avgDailySpend =
|
|
139627
|
-
if (
|
|
139711
|
+
if (num11(overview.balance) != null) kpis.balance = round28(num11(overview.balance));
|
|
139712
|
+
if (num11(overview.avgDailySpend) != null)
|
|
139713
|
+
kpis.avgDailySpend = round28(num11(overview.avgDailySpend));
|
|
139714
|
+
if (num11(overview.activeDays) != null) kpis.activeDays = num11(overview.activeDays);
|
|
139628
139715
|
if (overview.balanceAsOf != null) kpis.balanceAsOf = overview.balanceAsOf;
|
|
139629
139716
|
}
|
|
139630
139717
|
if (kpis.balance != null && kpis.avgDailySpend != null && Number(kpis.avgDailySpend) > 0) {
|
|
@@ -139818,7 +139905,7 @@ function textLen7(value) {
|
|
|
139818
139905
|
function asArray8(value) {
|
|
139819
139906
|
return Array.isArray(value) ? value : [];
|
|
139820
139907
|
}
|
|
139821
|
-
function
|
|
139908
|
+
function asRecord31(value) {
|
|
139822
139909
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
139823
139910
|
return value;
|
|
139824
139911
|
}
|
|
@@ -139831,23 +139918,23 @@ function normKey(value) {
|
|
|
139831
139918
|
return String(value ?? "").trim().toLowerCase();
|
|
139832
139919
|
}
|
|
139833
139920
|
function insightMatchRecord(item) {
|
|
139834
|
-
const rec =
|
|
139921
|
+
const rec = asRecord31(item);
|
|
139835
139922
|
if (!rec) return {};
|
|
139836
|
-
return
|
|
139923
|
+
return asRecord31(rec.match) ?? rec;
|
|
139837
139924
|
}
|
|
139838
139925
|
function findRowInsight(list, primaryField, primaryValue, row) {
|
|
139839
139926
|
const target = normKey(primaryValue);
|
|
139840
139927
|
if (!target) return null;
|
|
139841
139928
|
const candidates = list.filter((it) => normKey(insightMatchRecord(it)[primaryField]) === target);
|
|
139842
|
-
if (candidates.length <= 1) return
|
|
139929
|
+
if (candidates.length <= 1) return asRecord31(candidates[0]);
|
|
139843
139930
|
const campaign = normKey(row.campaignName);
|
|
139844
139931
|
const narrowed = candidates.find(
|
|
139845
139932
|
(it) => normKey(insightMatchRecord(it).campaignName) === campaign
|
|
139846
139933
|
);
|
|
139847
|
-
return
|
|
139934
|
+
return asRecord31(narrowed ?? candidates[0]);
|
|
139848
139935
|
}
|
|
139849
139936
|
function topRowsBySpend(rows, limit) {
|
|
139850
|
-
return rows.map((r) =>
|
|
139937
|
+
return rows.map((r) => asRecord31(r)).filter((r) => r != null).sort((a, b) => (Number(b.spend) || 0) - (Number(a.spend) || 0)).slice(0, limit);
|
|
139851
139938
|
}
|
|
139852
139939
|
var ROW_INSIGHT_SPECS = [
|
|
139853
139940
|
{
|
|
@@ -139862,17 +139949,17 @@ var ROW_INSIGHT_SPECS = [
|
|
|
139862
139949
|
];
|
|
139863
139950
|
function validateYandexPeriodReportContent(data) {
|
|
139864
139951
|
const missing = [];
|
|
139865
|
-
const meta =
|
|
139866
|
-
const narrative =
|
|
139867
|
-
const sections =
|
|
139868
|
-
const kpis =
|
|
139952
|
+
const meta = asRecord31(data.meta) ?? {};
|
|
139953
|
+
const narrative = asRecord31(data.narrative) ?? {};
|
|
139954
|
+
const sections = asRecord31(narrative.sections) ?? {};
|
|
139955
|
+
const kpis = asRecord31(data.kpis) ?? {};
|
|
139869
139956
|
if (!textLen7(meta.accountId)) {
|
|
139870
139957
|
pushMissing7(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
|
|
139871
139958
|
}
|
|
139872
139959
|
const sectionAnalysisCounts = {};
|
|
139873
139960
|
const sectionSuggestionCounts = {};
|
|
139874
139961
|
for (const key of SECTION_KEYS4) {
|
|
139875
|
-
const section =
|
|
139962
|
+
const section = asRecord31(sections[key]);
|
|
139876
139963
|
const analysis = asArray8(section?.analysis).filter((s) => textLen7(s) > 0);
|
|
139877
139964
|
const suggestions = asArray8(section?.suggestions).filter((s) => textLen7(s) > 0);
|
|
139878
139965
|
sectionAnalysisCounts[key] = analysis.length;
|
|
@@ -139898,7 +139985,7 @@ function validateYandexPeriodReportContent(data) {
|
|
|
139898
139985
|
}
|
|
139899
139986
|
const recommendations = asArray8(narrative.recommendations).filter((r) => {
|
|
139900
139987
|
if (typeof r === "string") return textLen7(r) > 0;
|
|
139901
|
-
const rec =
|
|
139988
|
+
const rec = asRecord31(r);
|
|
139902
139989
|
return textLen7(rec?.content) > 0 || textLen7(rec?.text) > 0;
|
|
139903
139990
|
});
|
|
139904
139991
|
if (recommendations.length < 3) {
|
|
@@ -139920,8 +140007,8 @@ function validateYandexPeriodReportContent(data) {
|
|
|
139920
140007
|
"\u81F3\u5C11\u4E00\u9879\u6709\u6548\u6570\u5B57\uFF08\u8BF7\u4F20 --snapshot-dir \u7531 CLI \u4ECE overview \u5408\u5E76\uFF09"
|
|
139921
140008
|
);
|
|
139922
140009
|
}
|
|
139923
|
-
const tables =
|
|
139924
|
-
const weeklyRows = asArray8(tables.weekly).map((r) =>
|
|
140010
|
+
const tables = asRecord31(data.tables) ?? {};
|
|
140011
|
+
const weeklyRows = asArray8(tables.weekly).map((r) => asRecord31(r)).filter((r) => r != null);
|
|
139925
140012
|
for (const [i, row] of weeklyRows.entries()) {
|
|
139926
140013
|
const label = `${row.weekLabel ?? ""} ${row.startDate ?? ""} ${row.endDate ?? ""}`;
|
|
139927
140014
|
if (/nan/i.test(label) || !normalizeYandexDailyDate(row.startDate) || !normalizeYandexDailyDate(row.endDate)) {
|
|
@@ -139934,7 +140021,7 @@ function validateYandexPeriodReportContent(data) {
|
|
|
139934
140021
|
);
|
|
139935
140022
|
}
|
|
139936
140023
|
}
|
|
139937
|
-
const rowInsights =
|
|
140024
|
+
const rowInsights = asRecord31(narrative.rowInsights) ?? {};
|
|
139938
140025
|
const rowInsightCoverage = {};
|
|
139939
140026
|
for (const spec of ROW_INSIGHT_SPECS) {
|
|
139940
140027
|
const shown = topRowsBySpend(asArray8(tables[spec.tableKey]), spec.limit).filter(
|
|
@@ -140014,7 +140101,7 @@ async function readJsonFile8(filePath) {
|
|
|
140014
140101
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
|
|
140015
140102
|
}
|
|
140016
140103
|
}
|
|
140017
|
-
function
|
|
140104
|
+
function asRecord32(value) {
|
|
140018
140105
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
140019
140106
|
return value;
|
|
140020
140107
|
}
|
|
@@ -140042,7 +140129,7 @@ function injectReportData7(html, payload) {
|
|
|
140042
140129
|
}
|
|
140043
140130
|
async function runYandexAnalysisRender(opts) {
|
|
140044
140131
|
const dataPath = path48.resolve(opts.dataFile);
|
|
140045
|
-
let data =
|
|
140132
|
+
let data = asRecord32(await readJsonFile8(dataPath));
|
|
140046
140133
|
if (opts.snapshotDir?.trim()) {
|
|
140047
140134
|
data = await mergeYandexAnalysisSnapshotIntoReport(data, path48.resolve(opts.snapshotDir));
|
|
140048
140135
|
}
|
|
@@ -140238,7 +140325,7 @@ function unwrapYandexItems(raw) {
|
|
|
140238
140325
|
}
|
|
140239
140326
|
return [];
|
|
140240
140327
|
}
|
|
140241
|
-
function
|
|
140328
|
+
function asRecord33(raw) {
|
|
140242
140329
|
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
140243
140330
|
}
|
|
140244
140331
|
|
|
@@ -140317,7 +140404,7 @@ async function runYandexAccountStatus(opts) {
|
|
|
140317
140404
|
} catch (err) {
|
|
140318
140405
|
fail3("\u67E5\u8BE2 Yandex \u8D26\u6237\u603B\u89C8\u5931\u8D25", err);
|
|
140319
140406
|
}
|
|
140320
|
-
const row =
|
|
140407
|
+
const row = asRecord33(result.raw);
|
|
140321
140408
|
await emitOrTable(opts, {
|
|
140322
140409
|
section: "yandex-account-status",
|
|
140323
140410
|
commandLabel: "yandex-analysis account-status",
|