export interface TimeSeriesFinancialDto { symbol: string; metric_name: string; period_date: string; value: number; source?: string; } export interface FinancialTableRow { metric: string; [year: string]: string | number | undefined; } export interface FinancialTableData { headers: string[]; // Sorted years rows: FinancialTableRow[]; } /** * Transforms a flat list of TimeSeriesFinancialDto into a pivoted table structure. * * Input: * [ * { metric_name: "Revenue", period_date: "2023-12-31", value: 100, source: "tushare" }, * { metric_name: "Revenue", period_date: "2022-12-31", value: 90, source: "tushare" }, * ] * * Output: * { * headers: ["2023", "2022"], * rows: [ * { metric: "Revenue", "2023": 100, "2022": 90 } * ] * } */ export function transformFinancialData(data: TimeSeriesFinancialDto[]): FinancialTableData { if (!data || data.length === 0) { return { headers: [], rows: [] }; } // 1. Collect all unique years (from period_date) const yearsSet = new Set(); // 2. Group by metric name const metricMap = new Map>(); data.forEach(item => { if (!item.period_date) return; // Extract year from "YYYY-MM-DD" const year = item.period_date.substring(0, 4); yearsSet.add(year); if (!metricMap.has(item.metric_name)) { metricMap.set(item.metric_name, { metric: item.metric_name }); } const row = metricMap.get(item.metric_name)!; // Handle potential conflicts: // If multiple sources provide data, we currently just overwrite or keep the last one. // A better approach might be to append source info or average, but for a table view, single value is cleaner. // Maybe prioritize sources? Tushare > YFinance > AlphaVantage? // For now, we just use the value. // We could format it with source: "100 (Tushare)" but that breaks numeric sorting/formatting. row[year] = item.value; }); // Sort years descending (newest first) const headers = Array.from(yearsSet).sort((a, b) => Number(b) - Number(a)); // Create rows const rows = Array.from(metricMap.values()).map(row => { // Ensure all header keys exist (fill with undefined/null if needed) return row as FinancialTableRow; }); return { headers, rows }; }