fix: hydrate id export metrics and remove new tier columns
This commit is contained in:
parent
38da39589f
commit
37f7e0b5e6
@ -30,7 +30,6 @@ const GENDER_LABELS = ["男性", "女性"];
|
|||||||
const AGE_LABELS = ["18-23", "24-30", "31-40", "41-50", "50+"];
|
const AGE_LABELS = ["18-23", "24-30", "31-40", "41-50", "50+"];
|
||||||
const CITY_TIER_LABELS = [
|
const CITY_TIER_LABELS = [
|
||||||
"一线城市",
|
"一线城市",
|
||||||
"新一线城市",
|
|
||||||
"二线城市",
|
"二线城市",
|
||||||
"三线城市",
|
"三线城市",
|
||||||
"四线城市",
|
"四线城市",
|
||||||
|
|||||||
@ -296,6 +296,7 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
|||||||
`识别 ${parsed.ids.length + parsed.duplicates.length + parsed.invalidTokens.length} 个,去重后 ${parsed.ids.length} 个,非法 ${parsed.invalidTokens.length} 个`
|
`识别 ${parsed.ids.length + parsed.duplicates.length + parsed.invalidTokens.length} 个,去重后 ${parsed.ids.length} 个,非法 ${parsed.invalidTokens.length} 个`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const backendMetricsByAuthorId = await loadBackendMetricsMap(parsed.ids);
|
||||||
const rows: AudienceProfileExportRow[] = [];
|
const rows: AudienceProfileExportRow[] = [];
|
||||||
for (let index = 0; index < parsed.ids.length; index += 1) {
|
for (let index = 0; index < parsed.ids.length; index += 1) {
|
||||||
const authorId = parsed.ids[index];
|
const authorId = parsed.ids[index];
|
||||||
@ -303,7 +304,12 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
|||||||
toolbar,
|
toolbar,
|
||||||
`按ID画像导出中 ${index + 1}/${parsed.ids.length}...`
|
`按ID画像导出中 ${index + 1}/${parsed.ids.length}...`
|
||||||
);
|
);
|
||||||
rows.push(await loadAudienceProfileRowById(authorId));
|
rows.push(
|
||||||
|
await loadAudienceProfileRowById(
|
||||||
|
authorId,
|
||||||
|
backendMetricsByAuthorId.get(authorId)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.onCsvReady?.(
|
options.onCsvReady?.(
|
||||||
@ -761,12 +767,20 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadAudienceProfileRowById(
|
async function loadAudienceProfileRowById(
|
||||||
authorId: string
|
authorId: string,
|
||||||
|
backendMetrics?: BackendMetrics
|
||||||
): Promise<AudienceProfileExportRow> {
|
): Promise<AudienceProfileExportRow> {
|
||||||
const baseRecord = await loadAuthorBaseInfoSafe(authorId);
|
const [baseRecord, metricsResult] = await Promise.all([
|
||||||
|
loadAuthorBaseInfoSafe(authorId),
|
||||||
|
loadAuthorMetricsSafe(authorId)
|
||||||
|
]);
|
||||||
const recordForRequests = {
|
const recordForRequests = {
|
||||||
...baseRecord,
|
...baseRecord,
|
||||||
authorName: baseRecord.authorName || authorId
|
authorName: baseRecord.authorName || authorId,
|
||||||
|
...(metricsResult.success ? { rates: metricsResult.rates } : {}),
|
||||||
|
...(backendMetrics
|
||||||
|
? { backendMetrics, backendMetricsStatus: "success" as const }
|
||||||
|
: {})
|
||||||
};
|
};
|
||||||
const [profiles, businessAbility] = await Promise.all([
|
const [profiles, businessAbility] = await Promise.all([
|
||||||
loadAudienceProfileSet(recordForRequests),
|
loadAudienceProfileSet(recordForRequests),
|
||||||
@ -814,6 +828,40 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadAuthorMetricsSafe(
|
||||||
|
authorId: string
|
||||||
|
): Promise<MarketApiResult> {
|
||||||
|
try {
|
||||||
|
return await loadAuthorMetrics(authorId);
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
|
reason: "request-failed",
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBackendMetricsMap(
|
||||||
|
authorIds: string[]
|
||||||
|
): Promise<Map<string, BackendMetrics>> {
|
||||||
|
const metricsMap = new Map<string, BackendMetrics>();
|
||||||
|
if (!searchBackendMetrics || authorIds.length === 0) {
|
||||||
|
return metricsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rows = await searchBackendMetrics(authorIds);
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const { starId, ...backendMetrics } = row;
|
||||||
|
metricsMap.set(starId, backendMetrics);
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return metricsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
return metricsMap;
|
||||||
|
}
|
||||||
|
|
||||||
function collectAudienceProfileRowFailures(
|
function collectAudienceProfileRowFailures(
|
||||||
baseRecord: MarketRecord,
|
baseRecord: MarketRecord,
|
||||||
profiles: AudienceProfileExportRow["profiles"],
|
profiles: AudienceProfileExportRow["profiles"],
|
||||||
|
|||||||
@ -102,6 +102,9 @@ describe("audience-profile-csv", () => {
|
|||||||
expect(headerLine).toContain("观众画像-31-40占比");
|
expect(headerLine).toContain("观众画像-31-40占比");
|
||||||
expect(headerLine).toContain("粉丝画像-一线城市占比");
|
expect(headerLine).toContain("粉丝画像-一线城市占比");
|
||||||
expect(headerLine).toContain("铁粉画像-都市蓝领占比");
|
expect(headerLine).toContain("铁粉画像-都市蓝领占比");
|
||||||
|
expect(headerLine).not.toContain("观众画像-新一线城市占比");
|
||||||
|
expect(headerLine).not.toContain("粉丝画像-新一线城市占比");
|
||||||
|
expect(headerLine).not.toContain("铁粉画像-新一线城市占比");
|
||||||
expect(headerLine).not.toContain("省份");
|
expect(headerLine).not.toContain("省份");
|
||||||
expect(headerLine).not.toContain("地域TOP");
|
expect(headerLine).not.toContain("地域TOP");
|
||||||
expect(headerLine).not.toContain("兴趣TOP");
|
expect(headerLine).not.toContain("兴趣TOP");
|
||||||
@ -183,10 +186,10 @@ describe("audience-profile-csv", () => {
|
|||||||
|
|
||||||
expect(readCsvValue(csv, "铁粉画像-41-50占比")).toBe("0%");
|
expect(readCsvValue(csv, "铁粉画像-41-50占比")).toBe("0%");
|
||||||
expect(readCsvValue(csv, "铁粉画像-50+占比")).toBe("0%");
|
expect(readCsvValue(csv, "铁粉画像-50+占比")).toBe("0%");
|
||||||
expect(readCsvValue(csv, "铁粉画像-新一线城市占比")).toBe("0%");
|
|
||||||
expect(readCsvValue(csv, "铁粉画像-五线城市占比")).toBe("0%");
|
expect(readCsvValue(csv, "铁粉画像-五线城市占比")).toBe("0%");
|
||||||
expect(readCsvValue(csv, "铁粉画像-都市银发占比")).toBe("0%");
|
expect(readCsvValue(csv, "铁粉画像-都市银发占比")).toBe("0%");
|
||||||
expect(readCsvValue(csv, "铁粉画像-Z世代占比")).toBe("0%");
|
expect(readCsvValue(csv, "铁粉画像-Z世代占比")).toBe("0%");
|
||||||
|
expect(csv.split("\n")[0]).not.toContain("新一线城市占比");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1728,6 +1728,26 @@ describe("market-content-entry", () => {
|
|||||||
gender: [{ label: "男性", value: "60%" }],
|
gender: [{ label: "男性", value: "60%" }],
|
||||||
status: "success" as const
|
status: "success" as const
|
||||||
}));
|
}));
|
||||||
|
const loadAuthorMetrics = vi.fn(async (authorId: string) => ({
|
||||||
|
rates: {
|
||||||
|
personalVideoAfterSearchRate:
|
||||||
|
authorId === "6866044569306267651" ? "12.3%" : "45.6%",
|
||||||
|
singleVideoAfterSearchRate:
|
||||||
|
authorId === "6866044569306267651" ? "7.8%" : "9.1%"
|
||||||
|
},
|
||||||
|
success: true as const
|
||||||
|
}));
|
||||||
|
const searchBackendMetrics = vi.fn(async (starIds: string[]) =>
|
||||||
|
starIds.map((starId) => ({
|
||||||
|
a3IncreaseCount: starId === "6866044569306267651" ? "100" : "200",
|
||||||
|
afterViewSearchCount: starId === "6866044569306267651" ? "300" : "400",
|
||||||
|
afterViewSearchRate: starId === "6866044569306267651" ? "1.1%" : "2.2%",
|
||||||
|
cpSearch: starId === "6866044569306267651" ? "10" : "20",
|
||||||
|
cpa3: starId === "6866044569306267651" ? "30" : "40",
|
||||||
|
newA3Rate: starId === "6866044569306267651" ? "3.3%" : "4.4%",
|
||||||
|
starId
|
||||||
|
}))
|
||||||
|
);
|
||||||
const onCsvReady = vi.fn();
|
const onCsvReady = vi.fn();
|
||||||
|
|
||||||
const { createMarketController } = await import("../src/content/market/index");
|
const { createMarketController } = await import("../src/content/market/index");
|
||||||
@ -1737,10 +1757,7 @@ describe("market-content-entry", () => {
|
|||||||
loadAuthorBaseInfo,
|
loadAuthorBaseInfo,
|
||||||
loadBusinessAbility,
|
loadBusinessAbility,
|
||||||
loadAudienceProfile,
|
loadAudienceProfile,
|
||||||
loadAuthorMetrics: async () => ({
|
loadAuthorMetrics,
|
||||||
success: false,
|
|
||||||
reason: "request-failed"
|
|
||||||
}),
|
|
||||||
onCsvReady,
|
onCsvReady,
|
||||||
promptAuthorIds: () => `
|
promptAuthorIds: () => `
|
||||||
6866044569306267651
|
6866044569306267651
|
||||||
@ -1748,10 +1765,13 @@ describe("market-content-entry", () => {
|
|||||||
6866044569306267651
|
6866044569306267651
|
||||||
bad-id
|
bad-id
|
||||||
`,
|
`,
|
||||||
|
searchBackendMetrics,
|
||||||
window
|
window
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await controller.ready;
|
await controller.ready;
|
||||||
|
loadAuthorMetrics.mockClear();
|
||||||
|
searchBackendMetrics.mockClear();
|
||||||
click('[data-plugin-export-audience-profile-by-id="button"]');
|
click('[data-plugin-export-audience-profile-by-id="button"]');
|
||||||
await waitForMockCall(buildAudienceProfileCsv, 40, 50);
|
await waitForMockCall(buildAudienceProfileCsv, 40, 50);
|
||||||
|
|
||||||
@ -1761,23 +1781,56 @@ describe("market-content-entry", () => {
|
|||||||
]);
|
]);
|
||||||
expect(loadAudienceProfile).toHaveBeenCalledTimes(6);
|
expect(loadAudienceProfile).toHaveBeenCalledTimes(6);
|
||||||
expect(loadBusinessAbility).toHaveBeenCalledTimes(2);
|
expect(loadBusinessAbility).toHaveBeenCalledTimes(2);
|
||||||
|
expect(loadAuthorMetrics.mock.calls.map(([authorId]) => authorId)).toEqual([
|
||||||
|
"6866044569306267651",
|
||||||
|
"7040323176106033165"
|
||||||
|
]);
|
||||||
|
expect(searchBackendMetrics).toHaveBeenCalledTimes(1);
|
||||||
|
expect(searchBackendMetrics).toHaveBeenCalledWith([
|
||||||
|
"6866044569306267651",
|
||||||
|
"7040323176106033165"
|
||||||
|
]);
|
||||||
expect(buildAudienceProfileCsv).toHaveBeenCalledWith([
|
expect(buildAudienceProfileCsv).toHaveBeenCalledWith([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
record: expect.objectContaining({
|
record: expect.objectContaining({
|
||||||
authorId: "6866044569306267651",
|
authorId: "6866044569306267651",
|
||||||
authorName: "小九儿",
|
authorName: "小九儿",
|
||||||
|
backendMetrics: expect.objectContaining({
|
||||||
|
a3IncreaseCount: "100",
|
||||||
|
afterViewSearchCount: "300",
|
||||||
|
afterViewSearchRate: "1.1%",
|
||||||
|
cpSearch: "10",
|
||||||
|
cpa3: "30",
|
||||||
|
newA3Rate: "3.3%"
|
||||||
|
}),
|
||||||
exportFields: {
|
exportFields: {
|
||||||
达人ID: "6866044569306267651",
|
达人ID: "6866044569306267651",
|
||||||
达人名称: "小九儿",
|
达人名称: "小九儿",
|
||||||
导出状态: "成功",
|
导出状态: "成功",
|
||||||
失败原因: ""
|
失败原因: ""
|
||||||
|
},
|
||||||
|
rates: {
|
||||||
|
personalVideoAfterSearchRate: "12.3%",
|
||||||
|
singleVideoAfterSearchRate: "7.8%"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
record: expect.objectContaining({
|
record: expect.objectContaining({
|
||||||
authorId: "7040323176106033165",
|
authorId: "7040323176106033165",
|
||||||
authorName: "达人 B"
|
authorName: "达人 B",
|
||||||
|
backendMetrics: expect.objectContaining({
|
||||||
|
a3IncreaseCount: "200",
|
||||||
|
afterViewSearchCount: "400",
|
||||||
|
afterViewSearchRate: "2.2%",
|
||||||
|
cpSearch: "20",
|
||||||
|
cpa3: "40",
|
||||||
|
newA3Rate: "4.4%"
|
||||||
|
}),
|
||||||
|
rates: {
|
||||||
|
personalVideoAfterSearchRate: "45.6%",
|
||||||
|
singleVideoAfterSearchRate: "9.1%"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user