mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:15:23 +02:00
perf: fix critical O(n²) bottleneck in mergeSalesAndAdsData
- The ads-only metadata lookup was doing a nested forEach (O(ads × sales)) - Now pre-builds asinMetadata map during sales aggregation for O(1) lookups - This reduces complexity from O(n²) to O(n) for large datasets - Should dramatically improve loading time for Weekly Sales tab
This commit is contained in:
+15
-14
@@ -446,6 +446,9 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor
|
||||
const createKey = (asin: string, customer: string, year: number, week: number) =>
|
||||
`${asin.trim().toUpperCase()}|${customer.trim().toUpperCase()}|${year}|${week}`;
|
||||
|
||||
// Pre-build metadata lookup map for O(1) access (instead of O(n) nested loop)
|
||||
const asinMetadata = new Map<string, { sku: string; title: string; line: string }>();
|
||||
|
||||
// 1. Aggregate Sales by ASIN|Customer|Year|Week (combine all SKUs)
|
||||
const salesMap = new Map<string, {
|
||||
sellOut: number;
|
||||
@@ -462,15 +465,21 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor
|
||||
|
||||
salesData.forEach(sale => {
|
||||
const weekNum = sale.week || 0;
|
||||
if (weekNum === 0) return; // Skip records without week data for weekly analysis
|
||||
if (weekNum === 0) return;
|
||||
|
||||
const key = createKey(sale.asin, sale.customer, sale.year, weekNum);
|
||||
|
||||
// Build metadata map: ASIN|Customer -> best metadata
|
||||
const metaKey = `${sale.asin.trim().toUpperCase()}|${sale.customer.trim().toUpperCase()}`;
|
||||
const existingMeta = asinMetadata.get(metaKey);
|
||||
if (!existingMeta || (sale.title && sale.title.length > (existingMeta.title?.length || 0))) {
|
||||
asinMetadata.set(metaKey, { sku: sale.sku, title: sale.title, line: sale.line });
|
||||
}
|
||||
|
||||
if (salesMap.has(key)) {
|
||||
const existing = salesMap.get(key)!;
|
||||
existing.sellOut += sale.sellOut;
|
||||
existing.units += sale.units;
|
||||
// Keep the best metadata (longest title, first non-empty SKU)
|
||||
if (sale.title && sale.title.length > (existing.title?.length || 0)) {
|
||||
existing.title = sale.title;
|
||||
}
|
||||
@@ -513,7 +522,7 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor
|
||||
const mergedData: CombinedKPIs[] = [];
|
||||
const processedKeys = new Set<string>();
|
||||
|
||||
// 3. Create ONE record per ASIN/Customer/Year/Week from sales, attach ads if available
|
||||
// 3. Create ONE record per ASIN/Customer/Year/Week from sales
|
||||
salesMap.forEach((sale, key) => {
|
||||
processedKeys.add(key);
|
||||
const ad = adsMap.get(key);
|
||||
@@ -529,7 +538,6 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor
|
||||
const salesOrganic = Math.max(0, salesTotal - adSales);
|
||||
const unitsOrganic = Math.max(0, unitsTotal - adUnits);
|
||||
|
||||
// KPIs
|
||||
const acos = adSales > 0 ? (adCost / adSales) * 100 : 0;
|
||||
const tacos = salesTotal > 0 ? (adCost / salesTotal) * 100 : 0;
|
||||
const roas = adCost > 0 ? adSales / adCost : 0;
|
||||
@@ -568,18 +576,11 @@ export const mergeSalesAndAdsData = (salesData: SalesRecord[], adsData: AdsRecor
|
||||
});
|
||||
});
|
||||
|
||||
// 4. Add ads-only records (ASINs with ads but no sales in the filtered data)
|
||||
// 4. Add ads-only records - use O(1) metadata lookup instead of O(n) loop
|
||||
adsMap.forEach((ad, key) => {
|
||||
if (!processedKeys.has(key)) {
|
||||
// Look up metadata from sales data for this ASIN (any week)
|
||||
let meta: { sku: string; title: string; line: string } | undefined;
|
||||
salesMap.forEach((sale, saleKey) => {
|
||||
if (saleKey.startsWith(ad.asin.trim().toUpperCase() + '|' + ad.country.trim().toUpperCase())) {
|
||||
if (!meta || sale.title?.length > meta.title?.length) {
|
||||
meta = { sku: sale.sku, title: sale.title, line: sale.line };
|
||||
}
|
||||
}
|
||||
});
|
||||
const metaKey = `${ad.asin.trim().toUpperCase()}|${ad.country.trim().toUpperCase()}`;
|
||||
const meta = asinMetadata.get(metaKey);
|
||||
|
||||
mergedData.push({
|
||||
id: `ads-only-${key}`,
|
||||
|
||||
Reference in New Issue
Block a user