fix(bsr): fix regex bug and expand column aliases in processBSRExcel

- Fix critical regex bug: /\\d+/ was matching literal '\d' instead of
  digits, causing all rows to be rejected (week always = 0)
- Add VendorCSV format support: 'Top Level Category (Rank)', 'Date', etc.
- Derive ISO week number from Date column when no Week column exists
- Process all sheets (not just first) to handle multi-tab workbooks
- Log column names on load to aid future debugging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
christian.vidal
2026-03-02 18:44:12 +01:00
co-authored by Claude Sonnet 4.6
parent 17a3df9fb3
commit 702bb021f8
+58 -31
View File
@@ -667,43 +667,70 @@ export const processBSRExcel = async (fileOrBuffer: File | ArrayBuffer): Promise
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
const allData: BSRRecord[] = [];
// Typically BSR is in the first sheet
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
const jsonData: any[] = XLSX.utils.sheet_to_json(worksheet, { defval: "" });
// Process all sheets — markets may be split across tabs
for (const sheetName of workbook.SheetNames) {
const worksheet = workbook.Sheets[sheetName];
const jsonData: any[] = XLSX.utils.sheet_to_json(worksheet, { defval: "" });
if (jsonData.length === 0) continue;
console.log(`Processing BSR sheet "${sheetName}": ${jsonData.length} rows`);
console.log(`[BSR] Processing sheet "${sheetName}": ${jsonData.length} rows. Columns:`, Object.keys(jsonData[0]));
for (let i = 0; i < jsonData.length; i++) {
const row = jsonData[i];
for (const row of jsonData) {
const marketRaw = getColumnValue(row, ['market', 'country', 'marketplace', 'Market', 'Country', 'Marketplace']);
const asinRaw = getColumnValue(row, ['asin', 'ASIN']);
if (!marketRaw || !asinRaw) continue;
const weekRaw = getColumnValue(row, ['week', 'woche', 'semana']);
const marketRaw = getColumnValue(row, ['market', 'country', 'marketplace']);
const asinRaw = getColumnValue(row, ['asin']);
const topLevelBSRRaw = getColumnValue(row, ['Mean Weekly Top Level BSR']);
const topLevelNameRaw = getColumnValue(row, ['Top Level Category Name']);
const detailLevelBSRRaw = getColumnValue(row, ['Mean Weekly Detail Level BSR']);
const detailLevelNameRaw = getColumnValue(row, ['Detail Level Category Name']);
const avgRatingRaw = getColumnValue(row, ['Mean Weekly Average Rating']);
// Week: try direct week column first, else derive from Date column
let week = 0;
const weekRaw = getColumnValue(row, ['week', 'woche', 'semana', 'week number', 'weeknumber', 'Week', 'Week Number']);
if (weekRaw) {
week = parseInt(String(weekRaw).match(/\d+/)?.[0] || '0', 10);
}
if (!week) {
const dateRaw = getColumnValue(row, ['date', 'fecha', 'datum', 'Date']);
if (dateRaw) {
const d = new Date(dateRaw);
if (!isNaN(d.getTime())) {
// ISO week number
const tmp = new Date(d);
tmp.setHours(0, 0, 0, 0);
tmp.setDate(tmp.getDate() + 3 - ((tmp.getDay() + 6) % 7));
const w1 = new Date(tmp.getFullYear(), 0, 4);
week = 1 + Math.round(((tmp.getTime() - w1.getTime()) / 86400000 - 3 + ((w1.getDay() + 6) % 7)) / 7);
}
}
}
if (!week) continue;
if (!weekRaw || !marketRaw || !asinRaw) continue;
const week = parseInt(String(weekRaw).match(/\\d+/)?.[0] || '0', 10);
if (isNaN(week) || week === 0) continue;
allData.push({
week,
market: String(marketRaw).trim(),
asin: String(asinRaw).trim(),
topLevelBSR: parseIntSafe(String(topLevelBSRRaw)),
topLevelName: topLevelNameRaw ? String(topLevelNameRaw).trim() : null,
detailLevelBSR: parseIntSafe(String(detailLevelBSRRaw)),
detailLevelName: detailLevelNameRaw ? String(detailLevelNameRaw).trim() : null,
avgRating: parseFloat(String(avgRatingRaw)) || null,
});
allData.push({
week,
market: String(marketRaw).trim(),
asin: String(asinRaw).trim(),
topLevelBSR: parseIntSafe(getColumnValue(row, [
'Mean Weekly Top Level BSR', 'Top Level Category (Rank)', 'Top Level BSR',
'TopLevelBSR', 'bsr top', 'BSR Top Level', 'top level bsr',
])),
topLevelName: getColumnValue(row, [
'Top Level Category Name', 'Top Level Category (Name)',
'TopLevelName', 'top category name', 'Top Category',
]) || null,
detailLevelBSR: parseIntSafe(getColumnValue(row, [
'Mean Weekly Detail Level BSR', 'Detail Level Category (Rank)', 'Detail Level BSR',
'DetailLevelBSR', 'bsr detail', 'BSR Detail Level', 'detail level bsr',
])),
detailLevelName: getColumnValue(row, [
'Detail Level Category Name', 'Detail Level Category (Name)',
'DetailLevelName', 'detail category name', 'Detail Category',
]) || null,
avgRating: parseFloat(getColumnValue(row, [
'Mean Weekly Average Rating', 'Average Rating', 'AvgRating',
'Rating', 'avg rating',
])) || null,
});
}
}
console.log(`Total BSR records loaded: ${allData.length}`);
console.log(`[BSR] Total records loaded: ${allData.length}`);
return allData;
} catch (error) {
console.error("Error processing BSR Excel:", error);