From 6379d4a37d606dbbb4ccc931260ebc3743909a03 Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Mon, 26 Jan 2026 16:50:46 +0100 Subject: [PATCH] fix: resolve triplicated ASIN in Forecast View and improve metadata mapping --- App.tsx | 17 ++++++++++++++--- services/dataProcessor.ts | 11 +++++++---- types.ts | 3 +++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/App.tsx b/App.tsx index 4355432..7ceec39 100644 --- a/App.tsx +++ b/App.tsx @@ -90,7 +90,7 @@ const App: React.FC = () => { console.log('[App] Successfully loaded', data.length, 'rows'); // Refresh forecast too - handleForecastFetch(data, globalAsinMetadata); + handleForecastFetch(data); } catch (error) { console.error("Failed to fetch/parse CSV", error); alert("Error loading data. Please refresh the page."); @@ -143,7 +143,7 @@ const App: React.FC = () => { } }, []); - const handleForecastFetch = useCallback(async (sales: SalesRecord[], meta: Map) => { + const handleForecastFetch = useCallback(async (sales: SalesRecord[]) => { try { console.log('[App] Fetching forecast from /forecast.xlsx...'); const response = await fetch('/forecast.xlsx'); @@ -151,6 +151,17 @@ const App: React.FC = () => { const buffer = await response.arrayBuffer(); const fcRecords = await processForecastExcel(buffer); + + // Build precise metadata map from sales records + const meta = new Map(); + sales.forEach(r => { + const asin = r.asin.trim().toUpperCase(); + const existing = meta.get(asin); + if (!existing || (r.title && r.title.length > (existing.title?.length || 0))) { + meta.set(asin, { sku: r.sku, title: r.title, line: r.line }); + } + }); + const viewData = calculateForecastViewData(sales, fcRecords, meta); setForecastData(viewData); console.log('[App] Forecast loaded:', viewData.length, 'records'); @@ -234,7 +245,7 @@ const App: React.FC = () => { handleTrafficFetch(); // 1d. Fetch Forecast data - handleForecastFetch(cachedData || [], globalAsinMetadata); + handleForecastFetch(cachedData || []); }; initApp(); }, [handleDataFetch]); diff --git a/services/dataProcessor.ts b/services/dataProcessor.ts index f5a6923..b692798 100644 --- a/services/dataProcessor.ts +++ b/services/dataProcessor.ts @@ -1509,7 +1509,10 @@ export const processForecastExcel = async (fileOrBuffer: File | ArrayBuffer): Pr return jsonData.map(row => ({ asin: String(row['ASIN'] || row['asin'] || '').trim().toUpperCase(), - annualForecast: parseUnits(String(row['Forecast 2026'] || row['forecast 2026'] || '0')) + annualForecast: parseUnits(String(row['Forecast 2026'] || row['forecast 2026'] || '0')), + sku: row['SKU'] || row['sku'] || undefined, + title: row['Title'] || row['title'] || row['Article Name'] || undefined, + line: row['Product Line'] || row['line'] || row['ProductLine'] || undefined })).filter(r => r.asin && r.annualForecast > 0); } catch (error) { console.error("Error processing Forecast Excel:", error); @@ -1582,9 +1585,9 @@ export const calculateForecastViewData = ( return { asin: identifier, - sku: meta?.sku || identifier, // Fallback to ASIN if SKU not found - title: meta?.title || identifier, - line: meta?.line || "", + sku: meta?.sku || fc.sku || identifier, + title: meta?.title || fc.title || identifier, + line: meta?.line || fc.line || "", annualForecast: fc.annualForecast, monthlyData }; diff --git a/types.ts b/types.ts index 76b6666..2b2573e 100644 --- a/types.ts +++ b/types.ts @@ -198,6 +198,9 @@ export interface CombinedKPIs { export interface ForecastRecord { asin: string; annualForecast: number; + sku?: string; + title?: string; + line?: string; } export interface MonthlyForecastPoint {