fix: format business ability estimates

This commit is contained in:
admin123 2026-05-18 18:40:34 +08:00
parent 249e6a5971
commit 39c4191a95
3 changed files with 53 additions and 13 deletions

View File

@ -158,19 +158,19 @@ export function mapBusinessAbilityEstimateResponse(
return { return {
oneToTwenty: { oneToTwenty: {
expectedCpe: formatDecimal(readNumber(data?.cpe_1_20), 1), expectedCpe: formatDecimal(readNumber(data?.cpe_1_20), 1),
expectedCpm: formatDecimal(readNumber(data?.cpm_1_20), 0), expectedCpm: formatFixedDecimal(readNumber(data?.cpm_1_20), 1),
expectedPlay, expectedPlay,
hotRate hotRate
}, },
overSixty: { overSixty: {
expectedCpe: formatDecimal(readNumber(data?.cpe_60), 1), expectedCpe: formatDecimal(readNumber(data?.cpe_60), 1),
expectedCpm: formatDecimal(readNumber(data?.cpm_60), 0), expectedCpm: formatFixedDecimal(readNumber(data?.cpm_60), 1),
expectedPlay, expectedPlay,
hotRate hotRate
}, },
twentyToSixty: { twentyToSixty: {
expectedCpe: formatDecimal(readNumber(data?.cpe_20_60), 1), expectedCpe: formatDecimal(readNumber(data?.cpe_20_60), 1),
expectedCpm: formatDecimal(readNumber(data?.cpm_20_60), 0), expectedCpm: formatFixedDecimal(readNumber(data?.cpm_20_60), 1),
expectedPlay, expectedPlay,
hotRate hotRate
} }
@ -203,7 +203,7 @@ function formatBasisPointRate(value: number | null): string {
function formatDecimalRate(value: number | null): string { function formatDecimalRate(value: number | null): string {
if (value === null) { if (value === null) {
return ""; return "缺失";
} }
return `${formatDecimal(value * 100, 0)}%`; return `${formatDecimal(value * 100, 0)}%`;
@ -230,6 +230,14 @@ function formatDecimal(value: number | null, digits: number): string {
return fixed.replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1"); return fixed.replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
} }
function formatFixedDecimal(value: number | null, digits: number): string {
if (value === null || !Number.isFinite(value)) {
return "";
}
return value.toFixed(digits);
}
function readNestedNumber( function readNestedNumber(
data: Record<string, unknown> | null, data: Record<string, unknown> | null,
objectKey: string, objectKey: string,

View File

@ -39,15 +39,15 @@ describe("audience-profile-csv", () => {
estimates: { estimates: {
oneToTwenty: { oneToTwenty: {
expectedCpe: "2.1", expectedCpe: "2.1",
expectedCpm: "120", expectedCpm: "120.0",
expectedPlay: "250w", expectedPlay: "250w",
hotRate: "100%" hotRate: "100%"
}, },
twentyToSixty: { twentyToSixty: {
expectedCpe: "3.7", expectedCpe: "3.7",
expectedCpm: "212", expectedCpm: "212.0",
expectedPlay: "250w", expectedPlay: "250w",
hotRate: "100%" hotRate: "缺失"
} }
}, },
status: "success", status: "success",
@ -109,8 +109,8 @@ describe("audience-profile-csv", () => {
expect(rowLine).toContain("60%"); expect(rowLine).toContain("60%");
expect(readCsvValue(csv, "商业能力-个人视频-播放量中位数")).toBe("3738.4w"); expect(readCsvValue(csv, "商业能力-个人视频-播放量中位数")).toBe("3738.4w");
expect(readCsvValue(csv, "商业能力-星图视频-平均转发")).toBe("68.4w"); expect(readCsvValue(csv, "商业能力-星图视频-平均转发")).toBe("68.4w");
expect(readCsvValue(csv, "商业能力-1-20s视频-预期CPM")).toBe("120"); expect(readCsvValue(csv, "商业能力-1-20s视频-预期CPM")).toBe("120.0");
expect(readCsvValue(csv, "商业能力-20-60s视频-爆文率")).toBe("100%"); expect(readCsvValue(csv, "商业能力-20-60s视频-爆文率")).toBe("缺失");
}); });
test("leaves distribution cells empty when profile loading fails", () => { test("leaves distribution cells empty when profile loading fails", () => {

View File

@ -47,25 +47,57 @@ describe("business-ability-client", () => {
expect(mapBusinessAbilityEstimateResponse(buildEstimatePayload())).toEqual({ expect(mapBusinessAbilityEstimateResponse(buildEstimatePayload())).toEqual({
oneToTwenty: { oneToTwenty: {
expectedCpe: "2.1", expectedCpe: "2.1",
expectedCpm: "120", expectedCpm: "120.0",
expectedPlay: "250w", expectedPlay: "250w",
hotRate: "100%" hotRate: "100%"
}, },
overSixty: { overSixty: {
expectedCpe: "4.2", expectedCpe: "4.2",
expectedCpm: "240", expectedCpm: "240.0",
expectedPlay: "250w", expectedPlay: "250w",
hotRate: "100%" hotRate: "100%"
}, },
twentyToSixty: { twentyToSixty: {
expectedCpe: "3.7", expectedCpe: "3.7",
expectedCpm: "212", expectedCpm: "212.0",
expectedPlay: "250w", expectedPlay: "250w",
hotRate: "100%" hotRate: "100%"
} }
}); });
}); });
test("keeps decimal CPM values and marks missing hot rate", () => {
expect(mapBusinessAbilityEstimateResponse({
base_resp: { status_code: 0, status_message: "" },
cpe_1_20: "1.6347",
cpe_20_60: "2.002",
cpe_60: "2.104",
cpm_1_20: "21.7955",
cpm_20_60: "27.5628",
cpm_60: "29.877",
vv: "1010234"
})).toEqual({
oneToTwenty: {
expectedCpe: "1.6",
expectedCpm: "21.8",
expectedPlay: "101w",
hotRate: "缺失"
},
overSixty: {
expectedCpe: "2.1",
expectedCpm: "29.9",
expectedPlay: "101w",
hotRate: "缺失"
},
twentyToSixty: {
expectedCpe: "2",
expectedCpm: "27.6",
expectedPlay: "101w",
hotRate: "缺失"
}
});
});
test("loads personal video, Xingtu video, and duration estimate metrics", async () => { test("loads personal video, Xingtu video, and duration estimate metrics", async () => {
const requestedUrls: string[] = []; const requestedUrls: string[] = [];
const fetchImpl = vi.fn(async (input: string) => { const fetchImpl = vi.fn(async (input: string) => {
@ -92,7 +124,7 @@ describe("business-ability-client", () => {
}) })
).resolves.toMatchObject({ ).resolves.toMatchObject({
estimates: expect.objectContaining({ estimates: expect.objectContaining({
twentyToSixty: expect.objectContaining({ expectedCpm: "212" }) twentyToSixty: expect.objectContaining({ expectedCpm: "212.0" })
}), }),
status: "success", status: "success",
videos: { videos: {