From 37fa2acf40cd117ba65b3f92660ed8f4eec82409 Mon Sep 17 00:00:00 2001 From: "christian.vidal" Date: Thu, 29 Jan 2026 20:09:28 +0100 Subject: [PATCH] Optimize data processing performance with memoization --- services/dataProcessor.ts | 43 ++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/services/dataProcessor.ts b/services/dataProcessor.ts index dbc5388..e8124f8 100644 --- a/services/dataProcessor.ts +++ b/services/dataProcessor.ts @@ -65,8 +65,12 @@ const MONTH_MAP: Record = { }; // Robust Month Normalizer +const monthCache: Record = {}; + const normalizeMonth = (rawMonth: string): string => { if (!rawMonth) return ''; + if (monthCache[rawMonth]) return monthCache[rawMonth]; + let m = String(rawMonth).trim().toLowerCase(); // 0. Check for Excel Serial Date (e.g. 45544 -> Sep) @@ -82,7 +86,10 @@ const normalizeMonth = (rawMonth: string): string => { } // 1. Direct Map Lookup (Handles "jan", "enero", "sep", etc.) - if (MONTH_MAP[m]) return MONTH_MAP[m]; + if (MONTH_MAP[m]) { + monthCache[rawMonth] = MONTH_MAP[m]; + return MONTH_MAP[m]; + } // 2. Handle numeric months "01", "1", "01-2023" // If it's a full date string like "2023-04-01" or "01/04/2023" @@ -125,6 +132,7 @@ const normalizeMonth = (rawMonth: string): string => { } } + monthCache[rawMonth] = rawMonth; return rawMonth; // Return as-is if all else fails }; @@ -508,16 +516,25 @@ export const mergeSalesAndAdsData = ( trafficData?: TrafficRecord[], velocityMap?: Map ): CombinedKPIs[] => { + const stringCache: Record = {}; + const getNorm = (s: string) => { + if (!s) return ''; + if (stringCache[s]) return stringCache[s]; + const v = s.trim().toUpperCase(); + stringCache[s] = v; + return v; + }; + // Key for both sales and ads: ASIN|Customer|Year|Week const createKey = (asin: string, customer: string, year: number, week: number) => - `${asin.trim().toUpperCase()}|${customer.trim().toUpperCase()}|${year}|${week}`; + `${getNorm(asin)}|${getNorm(customer)}|${year}|${week}`; // Build traffic lookup map - Use a more efficient key const trafficMap = new Map(); if (trafficData) { for (let i = 0; i < trafficData.length; i++) { const t = trafficData[i]; - const key = `${t.asin.trim().toUpperCase()}|${t.country.trim().toUpperCase()}|${t.year}|${t.week}`; + const key = `${getNorm(t.asin)}|${getNorm(t.country)}|${t.year}|${t.week}`; trafficMap.set(key, (trafficMap.get(key) || 0) + (t.glanceViews || 0)); } } @@ -544,8 +561,8 @@ export const mergeSalesAndAdsData = ( const weekNum = sale.week || 0; if (weekNum === 0) continue; - const asinUpper = sale.asin.trim().toUpperCase(); - const key = `${asinUpper}|${sale.customer.trim().toUpperCase()}|${sale.year}|${weekNum}`; + const asinUpper = getNorm(sale.asin); + const key = `${asinUpper}|${getNorm(sale.customer)}|${sale.year}|${weekNum}`; // If no global map provided, build it on the fly if (!asinMetadataMap) { @@ -585,7 +602,7 @@ export const mergeSalesAndAdsData = ( const adsMap = new Map(); for (let i = 0; i < adsData.length; i++) { const ad = adsData[i]; - const key = `${ad.asin.trim().toUpperCase()}|${ad.country.trim().toUpperCase()}|${ad.year}|${ad.week}`; + const key = `${getNorm(ad.asin)}|${getNorm(ad.country)}|${ad.year}|${ad.week}`; const existing = adsMap.get(key); if (existing) { existing.cost += ad.cost; @@ -633,7 +650,7 @@ export const mergeSalesAndAdsData = ( month: sale.month, week: sale.week, year: sale.year, - asin: sale.asin.trim().toUpperCase(), + asin: getNorm(sale.asin), title: sale.title, line: sale.line, sku: sale.sku, @@ -663,7 +680,7 @@ export const mergeSalesAndAdsData = ( // 5. Add ads-only records - use provided metadata for line/title adsMap.forEach((ad, key) => { if (!processedKeys.has(key)) { - const asin = ad.asin.trim().toUpperCase(); + const asin = getNorm(ad.asin); const meta = asinMetadata.get(asin); const avgWeeklySales = velocityMap?.get(asin) || 0; @@ -1299,10 +1316,12 @@ export const pivotSalesData = (data: any[], dimensions: string[] = ['title', 'cu data.forEach(record => { // Group by Dynamic Dimensions // Use a fallback for 'customer' dimension as some records use 'marketplace' - const keyParts = dimensions.map(dim => { - if (dim === 'customer') return String(record.customer || record.marketplace || ''); - return String(record[dim] || ''); - }); + const keyParts = new Array(dimensions.length); + for (let i = 0; i < dimensions.length; i++) { + const dim = dimensions[i]; + if (dim === 'customer') keyParts[i] = String(record.customer || record.marketplace || ''); + else keyParts[i] = String(record[dim] || ''); + } const key = keyParts.join('||'); if (!map.has(key)) {