diff --git a/services/dataProcessor.ts b/services/dataProcessor.ts index 6c6f141..19cf284 100644 --- a/services/dataProcessor.ts +++ b/services/dataProcessor.ts @@ -506,46 +506,66 @@ export const processTrafficExcel = async (fileOrBuffer: File | ArrayBuffer): Pro const workbook = XLSX.read(arrayBuffer, { type: 'array' }); const allData: TrafficRecord[] = []; - // Process first sheet only (Traffic Weekly.xlsx typically has one sheet) - const sheetName = workbook.SheetNames[0]; - const worksheet = workbook.Sheets[sheetName]; - const jsonData: any[][] = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "" }); + // Process ALL sheets (e.g., "2025", "2026") - same pattern as processAdsExcel + for (const sheetName of workbook.SheetNames) { + const worksheet = workbook.Sheets[sheetName]; + const jsonData: any[][] = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "" }); - console.log(`Processing Traffic sheet "${sheetName}": ${jsonData.length} rows`); + console.log(`Processing Traffic sheet "${sheetName}": ${jsonData.length} rows`); - // Column mapping for Traffic Weekly.xlsx: - // A (0): Year - // B (1): Week - // C (2): ASIN - // F (5): Country - // G (6): Glance Views (GV) + // Detect if sheet name is a valid year (multi-sheet format) + const sheetYear = parseInt(sheetName); + const isYearSheet = !isNaN(sheetYear) && sheetYear >= 2020 && sheetYear <= 2100; - // Skip header row (index 0), process data rows - for (let i = 1; i < jsonData.length; i++) { - const row = jsonData[i]; - if (!row || row.length < 7) continue; + // Auto-detect column layout from header row + const headerRow = jsonData[0]; + if (!headerRow) continue; - const yearRaw = row[0]; - const weekRaw = row[1]; - const asin = row[2]; - const countryRaw = row[5]; - const gvRaw = row[6]; + const headers = headerRow.map((h: any) => String(h).trim().toLowerCase()); + let yearIdx = headers.findIndex((h: string) => h === 'year' || h === 'año'); + let weekIdx = headers.findIndex((h: string) => h === 'week' || h === 'semana'); + let asinIdx = headers.findIndex((h: string) => h === 'asin'); + let countryIdx = headers.findIndex((h: string) => h === 'country' || h === 'país' || h === 'pais' || h === 'marketplace'); + let gvIdx = headers.findIndex((h: string) => h.includes('glance') || h === 'gv' || h.includes('page view')); - // Skip if missing essential data - if (!asin || yearRaw === undefined || weekRaw === undefined || !countryRaw) continue; + // Fallback to positional mapping if headers not found + if (asinIdx === -1 || countryIdx === -1 || gvIdx === -1) { + if (isYearSheet) { + // Year-based sheets: no year column + weekIdx = 0; asinIdx = 1; countryIdx = 4; gvIdx = 5; yearIdx = -1; + } else { + // Single sheet with year column + yearIdx = 0; weekIdx = 1; asinIdx = 2; countryIdx = 5; gvIdx = 6; + } + } - const year = parseInt(String(yearRaw)); - const weekNum = parseInt(String(weekRaw)); - if (isNaN(year) || year < 2020 || year > 2100) continue; - if (isNaN(weekNum) || weekNum < 1 || weekNum > 53) continue; + const minCols = Math.max(asinIdx, countryIdx, gvIdx) + 1; - allData.push({ - country: mapCountryToMarketplace(String(countryRaw)), - year, - week: weekNum, - asin: String(asin).trim().toUpperCase(), - glanceViews: parseUnits(String(gvRaw)), - }); + for (let i = 1; i < jsonData.length; i++) { + const row = jsonData[i]; + if (!row || row.length < minCols) continue; + + const yearRaw = isYearSheet ? sheetYear : (yearIdx >= 0 ? row[yearIdx] : undefined); + const weekRaw = weekIdx >= 0 ? row[weekIdx] : undefined; + const asin = row[asinIdx]; + const countryRaw = row[countryIdx]; + const gvRaw = row[gvIdx]; + + if (!asin || yearRaw === undefined || weekRaw === undefined || !countryRaw) continue; + + const year = typeof yearRaw === 'number' ? yearRaw : parseInt(String(yearRaw)); + const weekNum = parseInt(String(weekRaw)); + if (isNaN(year) || year < 2020 || year > 2100) continue; + if (isNaN(weekNum) || weekNum < 1 || weekNum > 53) continue; + + allData.push({ + country: mapCountryToMarketplace(String(countryRaw)), + year, + week: weekNum, + asin: String(asin).trim().toUpperCase(), + glanceViews: parseUnits(String(gvRaw)), + }); + } } console.log(`Total Traffic records loaded: ${allData.length}`);