From 37f7e0b5e6c6edd72fe630ba2914f5b14d5327c1 Mon Sep 17 00:00:00 2001 From: admin123 Date: Mon, 18 May 2026 19:50:14 +0800 Subject: [PATCH] fix: hydrate id export metrics and remove new tier columns --- src/content/market/audience-profile-csv.ts | 1 - src/content/market/index.ts | 56 +++++++++++++++++-- tests/audience-profile-csv.test.ts | 5 +- tests/market-content-entry.test.ts | 63 ++++++++++++++++++++-- 4 files changed, 114 insertions(+), 11 deletions(-) diff --git a/src/content/market/audience-profile-csv.ts b/src/content/market/audience-profile-csv.ts index 6a710c3..11ffa35 100644 --- a/src/content/market/audience-profile-csv.ts +++ b/src/content/market/audience-profile-csv.ts @@ -30,7 +30,6 @@ const GENDER_LABELS = ["男性", "女性"]; const AGE_LABELS = ["18-23", "24-30", "31-40", "41-50", "50+"]; const CITY_TIER_LABELS = [ "一线城市", - "新一线城市", "二线城市", "三线城市", "四线城市", diff --git a/src/content/market/index.ts b/src/content/market/index.ts index 6f54341..964bd35 100644 --- a/src/content/market/index.ts +++ b/src/content/market/index.ts @@ -296,6 +296,7 @@ export function createMarketController(options: CreateMarketControllerOptions) { `识别 ${parsed.ids.length + parsed.duplicates.length + parsed.invalidTokens.length} 个,去重后 ${parsed.ids.length} 个,非法 ${parsed.invalidTokens.length} 个` ); + const backendMetricsByAuthorId = await loadBackendMetricsMap(parsed.ids); const rows: AudienceProfileExportRow[] = []; for (let index = 0; index < parsed.ids.length; index += 1) { const authorId = parsed.ids[index]; @@ -303,7 +304,12 @@ export function createMarketController(options: CreateMarketControllerOptions) { toolbar, `按ID画像导出中 ${index + 1}/${parsed.ids.length}...` ); - rows.push(await loadAudienceProfileRowById(authorId)); + rows.push( + await loadAudienceProfileRowById( + authorId, + backendMetricsByAuthorId.get(authorId) + ) + ); } options.onCsvReady?.( @@ -761,12 +767,20 @@ export function createMarketController(options: CreateMarketControllerOptions) { } async function loadAudienceProfileRowById( - authorId: string + authorId: string, + backendMetrics?: BackendMetrics ): Promise { - const baseRecord = await loadAuthorBaseInfoSafe(authorId); + const [baseRecord, metricsResult] = await Promise.all([ + loadAuthorBaseInfoSafe(authorId), + loadAuthorMetricsSafe(authorId) + ]); const recordForRequests = { ...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([ loadAudienceProfileSet(recordForRequests), @@ -814,6 +828,40 @@ export function createMarketController(options: CreateMarketControllerOptions) { } } + async function loadAuthorMetricsSafe( + authorId: string + ): Promise { + try { + return await loadAuthorMetrics(authorId); + } catch { + return { + reason: "request-failed", + success: false + }; + } + } + + async function loadBackendMetricsMap( + authorIds: string[] + ): Promise> { + const metricsMap = new Map(); + 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( baseRecord: MarketRecord, profiles: AudienceProfileExportRow["profiles"], diff --git a/tests/audience-profile-csv.test.ts b/tests/audience-profile-csv.test.ts index 75cbc2c..10472f3 100644 --- a/tests/audience-profile-csv.test.ts +++ b/tests/audience-profile-csv.test.ts @@ -102,6 +102,9 @@ describe("audience-profile-csv", () => { expect(headerLine).toContain("观众画像-31-40占比"); 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("地域TOP"); expect(headerLine).not.toContain("兴趣TOP"); @@ -183,10 +186,10 @@ describe("audience-profile-csv", () => { expect(readCsvValue(csv, "铁粉画像-41-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, "铁粉画像-Z世代占比")).toBe("0%"); + expect(csv.split("\n")[0]).not.toContain("新一线城市占比"); }); }); diff --git a/tests/market-content-entry.test.ts b/tests/market-content-entry.test.ts index 13b5131..ca3db74 100644 --- a/tests/market-content-entry.test.ts +++ b/tests/market-content-entry.test.ts @@ -1728,6 +1728,26 @@ describe("market-content-entry", () => { gender: [{ label: "男性", value: "60%" }], 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 { createMarketController } = await import("../src/content/market/index"); @@ -1737,10 +1757,7 @@ describe("market-content-entry", () => { loadAuthorBaseInfo, loadBusinessAbility, loadAudienceProfile, - loadAuthorMetrics: async () => ({ - success: false, - reason: "request-failed" - }), + loadAuthorMetrics, onCsvReady, promptAuthorIds: () => ` 6866044569306267651 @@ -1748,10 +1765,13 @@ describe("market-content-entry", () => { 6866044569306267651 bad-id `, + searchBackendMetrics, window })); await controller.ready; + loadAuthorMetrics.mockClear(); + searchBackendMetrics.mockClear(); click('[data-plugin-export-audience-profile-by-id="button"]'); await waitForMockCall(buildAudienceProfileCsv, 40, 50); @@ -1761,23 +1781,56 @@ describe("market-content-entry", () => { ]); expect(loadAudienceProfile).toHaveBeenCalledTimes(6); 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.objectContaining({ record: expect.objectContaining({ authorId: "6866044569306267651", authorName: "小九儿", + backendMetrics: expect.objectContaining({ + a3IncreaseCount: "100", + afterViewSearchCount: "300", + afterViewSearchRate: "1.1%", + cpSearch: "10", + cpa3: "30", + newA3Rate: "3.3%" + }), exportFields: { 达人ID: "6866044569306267651", 达人名称: "小九儿", 导出状态: "成功", 失败原因: "" + }, + rates: { + personalVideoAfterSearchRate: "12.3%", + singleVideoAfterSearchRate: "7.8%" } }) }), expect.objectContaining({ record: expect.objectContaining({ 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%" + } }) }) ]);