mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 16:05:23 +02:00
feat: integrate Glance Views (GV) from Traffic Weekly.xlsx
- types.ts: Added TrafficRecord interface and glanceViews field to CombinedKPIs - dataProcessor.ts: Added processTrafficExcel parser for Traffic Weekly.xlsx - dataProcessor.ts: Updated mergeSalesAndAdsData to accept and merge traffic data - App.tsx: Added trafficData state and handleTrafficUpload handler - FileUpload.tsx: Added Traffic file upload section for GV data - AdsPerformance.tsx: Added GV and CVR(GV) summary cards - AdsPerformance.tsx: Added GV column to product table
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { SalesRecord, AdsRecord, CombinedKPIs, FilterState, AggregatedData, LineGrowthMetric, ItemGrowthMetric, SeasonalityPoint, YearlySplitData, PivotRow, YearlyData, TimeSeriesData, ComparisonTimeSeriesPoint } from '../types';
|
||||
import { SalesRecord, AdsRecord, TrafficRecord, CombinedKPIs, FilterState, AggregatedData, LineGrowthMetric, ItemGrowthMetric, SeasonalityPoint, YearlySplitData, PivotRow, YearlyData, TimeSeriesData, ComparisonTimeSeriesPoint } from '../types';
|
||||
import * as XLSX from 'xlsx';
|
||||
import Papa from 'papaparse';
|
||||
|
||||
@@ -439,17 +439,87 @@ export const processAdsExcel = async (fileOrBuffer: File | ArrayBuffer): Promise
|
||||
}
|
||||
};
|
||||
|
||||
// --- TRAFFIC DATA PARSING ---
|
||||
|
||||
export const processTrafficExcel = async (fileOrBuffer: File | ArrayBuffer): Promise<TrafficRecord[]> => {
|
||||
try {
|
||||
const arrayBuffer = fileOrBuffer instanceof File
|
||||
? await fileOrBuffer.arrayBuffer()
|
||||
: fileOrBuffer;
|
||||
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: "" });
|
||||
|
||||
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)
|
||||
|
||||
// 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;
|
||||
|
||||
const yearRaw = row[0];
|
||||
const weekRaw = row[1];
|
||||
const asin = row[2];
|
||||
const countryRaw = row[5];
|
||||
const gvRaw = row[6];
|
||||
|
||||
// Skip if missing essential data
|
||||
if (!asin || yearRaw === undefined || weekRaw === undefined || !countryRaw) continue;
|
||||
|
||||
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;
|
||||
|
||||
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}`);
|
||||
return allData;
|
||||
} catch (error) {
|
||||
console.error("Error processing Traffic Excel:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// --- DATA MERGING ---
|
||||
|
||||
export const mergeSalesAndAdsData = (
|
||||
salesData: SalesRecord[],
|
||||
adsData: AdsRecord[],
|
||||
asinMetadataMap?: Map<string, { sku: string; title: string; line: string }>
|
||||
asinMetadataMap?: Map<string, { sku: string; title: string; line: string }>,
|
||||
trafficData?: TrafficRecord[]
|
||||
): CombinedKPIs[] => {
|
||||
// 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}`;
|
||||
|
||||
// Build traffic lookup map
|
||||
const trafficMap = new Map<string, number>();
|
||||
if (trafficData) {
|
||||
trafficData.forEach(t => {
|
||||
const key = createKey(t.asin, t.country, t.year, t.week);
|
||||
trafficMap.set(key, (trafficMap.get(key) || 0) + t.glanceViews);
|
||||
});
|
||||
}
|
||||
|
||||
// 1. Initialize metadata lookup map with provided global map if available, otherwise build from current sales
|
||||
const asinMetadata = asinMetadataMap || new Map<string, { sku: string; title: string; line: string }>();
|
||||
|
||||
@@ -579,7 +649,8 @@ export const mergeSalesAndAdsData = (
|
||||
roas,
|
||||
ctr,
|
||||
cpc,
|
||||
cvrUnits
|
||||
cvrUnits,
|
||||
glanceViews: trafficMap.get(key) || 0
|
||||
});
|
||||
});
|
||||
|
||||
@@ -617,7 +688,8 @@ export const mergeSalesAndAdsData = (
|
||||
roas: ad.cost > 0 ? ad.attributedSales30d / ad.cost : 0,
|
||||
ctr: ad.impressions > 0 ? (ad.clicks / ad.impressions) * 100 : 0,
|
||||
cpc: ad.clicks > 0 ? ad.cost / ad.clicks : 0,
|
||||
cvrUnits: ad.clicks > 0 ? (ad.attributedUnits30d / ad.clicks) * 100 : 0
|
||||
cvrUnits: ad.clicks > 0 ? (ad.attributedUnits30d / ad.clicks) * 100 : 0,
|
||||
glanceViews: trafficMap.get(key) || 0
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user