125 lines
3.4 KiB
TypeScript

import { normalizeRateDisplay } from "../../shared/rate-normalizer";
import { escapeCsvCell } from "../../shared/csv";
import type { MarketRecord } from "./types";
export type CsvColumn = {
header: string;
readValue: (record: MarketRecord) => string;
};
const FALLBACK_BASE_COLUMNS: CsvColumn[] = [
{
header: "达人ID",
readValue: (record: MarketRecord) => record.authorId
},
{
header: "达人名称",
readValue: (record: MarketRecord) => record.authorName
},
{
header: "地区",
readValue: (record: MarketRecord) => record.location ?? ""
},
{
header: "21-60s报价",
readValue: (record: MarketRecord) => record.price21To60s ?? ""
}
];
const RATE_COLUMNS: CsvColumn[] = [
{
header: "商单视频看后搜率",
readValue: (record: MarketRecord) =>
record.rates?.singleVideoAfterSearchRate
? normalizeRateDisplay(record.rates.singleVideoAfterSearchRate)
: ""
},
{
header: "个人视频看后搜率",
readValue: (record: MarketRecord) =>
record.rates?.personalVideoAfterSearchRate
? normalizeRateDisplay(record.rates.personalVideoAfterSearchRate)
: ""
}
];
const BACKEND_METRIC_COLUMNS: CsvColumn[] = [
{
header: "秒思api-看后搜率",
readValue: (record: MarketRecord) =>
record.backendMetrics?.afterViewSearchRate ?? ""
},
{
header: "秒思api-看后搜数",
readValue: (record: MarketRecord) =>
record.backendMetrics?.afterViewSearchCount ?? ""
},
{
header: "秒思api-新增A3数",
readValue: (record: MarketRecord) =>
record.backendMetrics?.a3IncreaseCount ?? ""
},
{
header: "秒思api-新增A3率",
readValue: (record: MarketRecord) =>
record.backendMetrics?.newA3Rate ?? ""
},
{
header: "秒思api-CPA3",
readValue: (record: MarketRecord) => record.backendMetrics?.cpa3 ?? ""
},
{
header: "秒思api-cp_search",
readValue: (record: MarketRecord) => record.backendMetrics?.cpSearch ?? ""
}
];
export function listRateCsvHeaders(): string[] {
return RATE_COLUMNS.map((column) => column.header);
}
export function listBackendMetricCsvHeaders(): string[] {
return BACKEND_METRIC_COLUMNS.map((column) => column.header);
}
export function buildMarketCsv(records: MarketRecord[]): string {
const csvColumns = buildMarketCsvColumns(records);
const headerLine = csvColumns.map((column) => column.header).join(",");
const rowLines = records.map((record) =>
csvColumns.map((column) => escapeCsvCell(column.readValue(record))).join(",")
);
return [headerLine, ...rowLines].join("\n");
}
export function buildMarketCsvColumns(records: MarketRecord[]): CsvColumn[] {
const baseColumns = buildBaseColumns(records);
return [...baseColumns, ...RATE_COLUMNS, ...BACKEND_METRIC_COLUMNS];
}
export function buildBaseColumns(records: MarketRecord[]): CsvColumn[] {
const orderedHeaders: string[] = [];
const seenHeaders = new Set<string>();
const excludedHeaders = new Set(["代表视频"]);
records.forEach((record) => {
Object.keys(record.exportFields ?? {}).forEach((header) => {
if (seenHeaders.has(header) || excludedHeaders.has(header)) {
return;
}
seenHeaders.add(header);
orderedHeaders.push(header);
});
});
if (orderedHeaders.length === 0) {
return FALLBACK_BASE_COLUMNS;
}
return orderedHeaders.map((header) => ({
header,
readValue: (record: MarketRecord) => record.exportFields?.[header] ?? ""
}));
}