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(); 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] ?? "" })); }