fix: logic to show BSR improvements (rank drops) in green

This commit is contained in:
Christian Vidal Wolf
2026-03-10 14:04:20 +01:00
parent c15642d62d
commit 74d92b83b5
14 changed files with 924 additions and 1 deletions
+118
View File
@@ -0,0 +1,118 @@
import XLSX from 'xlsx';
import { readFileSync, existsSync } from 'fs';
import Papa from 'papaparse';
const ADS_FILE = 'Ads Weekly.xlsx';
const DATA_FILE = 'dist/Amazon-Sell-Out-2023-2025.csv'; // Need to check if this exists locally or if I need to fetch it.
async function main() {
console.log('--- AD SPEND BY PRODUCT LINE (UK, 2026) ---');
// 1. Build Metadata Map (ASIN -> Line) from Sales Records
const metaMap = new Map<string, string>();
// Check local CSV if it exists, otherwise we might need to fetch it
const dropboxUrl = "https://www.dropbox.com/scl/fi/b9zxn4z5i7sxwfakk5g5y/Amazon-Sell-Out-2023-2025.csv?rlkey=uoto6v0mm99py8nszy8ldtez8&st=pzn1zkrg&dl=1";
console.log('Fetching Sales data to build ASIN-Line mapping...');
const csvResponse = await fetch(dropboxUrl);
const csvText = await csvResponse.text();
console.log('Parsing CSV...');
const results = Papa.parse(csvText, { header: true, skipEmptyLines: true });
const rows = results.data as any[];
if (rows.length > 0) {
console.log('CSV Columns:', Object.keys(rows[0]));
}
rows.forEach(r => {
const asin = String(rowValue(r, ['CUSTOMER REFERENCE', 'ASIN'])).trim().toUpperCase();
const line = rowValue(r, ['PRODUCT Line', 'Line']) || 'Unassigned';
if (asin && !metaMap.has(asin)) {
metaMap.set(asin, line);
}
});
console.log(`Built mapping for ${metaMap.size} unique ASINs.`);
// Helper to get case-insensitive row value
function rowValue(row: any, aliases: string[]): string {
for (const a of aliases) {
if (row[a]) return row[a];
const lowerA = a.toLowerCase();
const found = Object.keys(row).find(k => k.toLowerCase() === lowerA);
if (found) return row[found];
}
return '';
}
// 2. Parse Ads Data
if (!existsSync(ADS_FILE)) {
console.error(`Error: ${ADS_FILE} not found.`);
return;
}
const buf = readFileSync(ADS_FILE);
const wb = XLSX.read(buf, { type: 'buffer' });
const spendByLine = new Map<string, number>();
let totalUkSpend = 0;
let foundMatches: any[] = [];
for (const sheetName of wb.SheetNames) {
const ws = wb.Sheets[sheetName];
const rows: any[] = XLSX.utils.sheet_to_json(ws, { defval: '' });
rows.forEach((r: any) => {
const countryRaw = String(r.Country || '').toUpperCase();
const asin = String(r.ASIN || '').trim().toUpperCase();
const cost = parseFloat(r.Cost) || 0;
const line = metaMap.get(asin) || 'Unassigned';
// Still track official UK 2026 total for the summary
if (sheetName === '2026' && (countryRaw === 'UK' || countryRaw === 'GB' || countryRaw === 'AMAZON UK' || countryRaw === 'AMAZON GB')) {
spendByLine.set(line, (spendByLine.get(line) || 0) + cost);
totalUkSpend += cost;
}
// DEBUG: Find ALL "INKEE" records anywhere that might explain the 126
if (line.toUpperCase() === 'INKEE' && cost > 0) {
foundMatches.push({
Sheet: sheetName,
Country: countryRaw,
ASIN: asin,
Cost: cost,
Week: r.Week
});
}
});
}
console.log('\n--- ALL INKEE AD RECORDS FOUND (ANY COUNTRY/YEAR) ---');
console.table(foundMatches.slice(0, 50)); // Showing first 50
if (foundMatches.length > 50) console.log(`... and ${foundMatches.length - 50} more records.`);
const totalsByGroup = foundMatches.reduce((acc, curr) => {
const key = `${curr.Sheet} | ${curr.Country}`;
acc[key] = (acc[key] || 0) + curr.Cost;
return acc;
}, {});
console.log('\nSummary of INKEE Spend by Sheet and Country:');
console.table(totalsByGroup);
// 3. Output results
console.log('\nOfficial Results for Amazon UK (2026 YTD) based on sheet "2026":');
const sortedLines = Array.from(spendByLine.entries())
.sort((a, b) => b[1] - a[1]);
console.log('--------------------------------------------------');
console.log(`${'Product Line'.padEnd(30)} | ${'Ad Spend (£)'.padStart(15)}`);
console.log('--------------------------------------------------');
sortedLines.forEach(([line, spend]) => {
console.log(`${line.padEnd(30)} | ${spend.toFixed(2).padStart(15)}`);
});
console.log('--------------------------------------------------');
console.log(`${'TOTAL'.padEnd(30)} | ${totalUkSpend.toFixed(2).padStart(15)}`);
}
main().catch(console.error);
+1 -1
View File
@@ -600,7 +600,7 @@ const ExperimentDetailView: React.FC<{
<td className={`py-4 text-sm font-bold flex items-center gap-1 ${isGood ? 'text-emerald-400' : 'text-[#f43f5e]'}`}> <td className={`py-4 text-sm font-bold flex items-center gap-1 ${isGood ? 'text-emerald-400' : 'text-[#f43f5e]'}`}>
<div className="flex flex-col"> <div className="flex flex-col">
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<svg className={`w-3.5 h-3.5 ${isGood ? (res.lift_percent >= 0 ? '' : 'rotate-180') : 'rotate-180'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}><path strokeLinecap="round" strokeLinejoin="round" d="M5 15l7-7 7 7" /></svg> <svg className={`w-3.5 h-3.5 ${isPositiveGood ? (res.lift_percent >= 0 ? '' : 'rotate-180') : (res.lift_percent <= 0 ? '' : 'rotate-180')}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}><path strokeLinecap="round" strokeLinejoin="round" d="M5 15l7-7 7 7" /></svg>
{res.lift_percent >= 0 ? '+' : ''}{res.lift_percent.toFixed(1)}% {res.lift_percent >= 0 ? '+' : ''}{res.lift_percent.toFixed(1)}%
</span> </span>
<span className="text-[10px] text-[#64748b] font-medium mt-0.5 ml-1"> <span className="text-[10px] text-[#64748b] font-medium mt-0.5 ml-1">
+100
View File
@@ -0,0 +1,100 @@
import { createClient } from '@supabase/supabase-js';
import { readFileSync } from 'fs';
import { processAdsExcel, mergeSalesAndAdsData } from './services/dataProcessor';
import { computeDiD } from './services/experimentAnalysis';
import { CombinedKPIs } from './types';
// Load .env.local if it exists
import { config } from 'dotenv';
config({ path: '.env.local' });
async function run() {
const supabase = createClient(
process.env.SUPABASE_URL || '',
process.env.SUPABASE_SERVICE_KEY || ''
);
// Get the INKEE DE experiment
const { data: experiments, error } = await supabase
.from('experiments')
.select('*')
.ilike('name', '%INKEE%')
.eq('marketplace', 'DE');
if (error) {
console.error('Supabase error:', error);
return;
}
if (!experiments || experiments.length === 0) {
console.log('No INKEE DE experiments found. Listing all experiments...');
const { data: all } = await supabase.from('experiments').select('name, marketplace, start_date, end_date, asins').limit(20);
console.log(JSON.stringify(all, null, 2));
return;
}
for (const exp of experiments) {
console.log(`\n=== Experiment: "${exp.name}" ===`);
console.log(` Marketplace: ${exp.marketplace}`);
console.log(` Start: ${exp.start_date} → End: ${exp.end_date}`);
console.log(` Baseline: ${exp.baseline_start_date}${exp.baseline_end_date}`);
console.log(` ASINs (${exp.asins?.length || 0}):`, exp.asins);
console.log(` Control ASINs (${exp.control_asins?.length || 0}):`, exp.control_asins);
}
// Now load the Ads data and check all ASINs
console.log('\n=== Loading Ads data ===');
const buf = readFileSync('/tmp/Ads-Weekly.xlsx').buffer;
const adsData = await processAdsExcel(buf);
const merged = mergeSalesAndAdsData([], adsData) as CombinedKPIs[];
const inkeeExp = experiments[0];
const allAsins = inkeeExp.asins?.map((a: string) => a.trim().toUpperCase()) || [];
console.log(`\n=== Checking ${allAsins.length} ASINs in Ads data (weeks 7-8 of 2026) ===`);
let totalCostW7W8 = 0;
let totalAdRevW7W8 = 0;
let missingFromAds: string[] = [];
for (const asin of allAsins) {
const adRecords = merged.filter(r =>
r.asin === asin &&
r.year === 2026 &&
[7, 8].includes(r.week) &&
(r.marketplace || r.customer || '').toLowerCase().includes('de')
);
if (adRecords.length === 0) {
missingFromAds.push(asin);
} else {
const cost = adRecords.reduce((s, r) => s + (r.cost || 0), 0);
const adRev = adRecords.reduce((s, r) => s + (r.salesAds || 0), 0);
const clicks = adRecords.reduce((s, r) => s + (r.clicks || 0), 0);
if (cost > 0 || adRev > 0 || clicks > 0) {
console.log(` ${asin}: W7+W8 cost=${cost.toFixed(2)} adRev=${adRev.toFixed(2)} clicks=${clicks}`);
totalCostW7W8 += cost;
totalAdRevW7W8 += adRev;
} else {
console.log(` ${asin}: W7+W8 cost=0 adRev=0 clicks=0 (zero activity)`);
}
}
}
console.log(`\nMissing from Ads file entirely (${missingFromAds.length}):`, missingFromAds);
console.log(`\nTotal W7+W8 2026 cost: €${totalCostW7W8.toFixed(2)}`);
console.log(`Total W7+W8 2026 adRev: €${totalAdRevW7W8.toFixed(2)}`);
if (totalCostW7W8 > 0) {
console.log(`Implied ACOS: ${(totalCostW7W8 / (totalAdRevW7W8 || 1) * 100).toFixed(1)}%`);
}
// Run actual DiD
const did = computeDiD(inkeeExp, merged);
console.log('\n=== DiD Result ===');
console.log('ACOS:', did.metrics['acos']);
console.log('ROAS:', did.metrics['roas']);
console.log('Revenue:', did.metrics['revenue']);
console.log('Units:', did.metrics['units']);
}
run().catch(console.error);
+243
View File
@@ -0,0 +1,243 @@
import { createClient } from '@supabase/supabase-js';
interface Experiment {
id: string;
name: string;
start_date: string;
end_date?: string;
marketplace: string;
asins: string[];
baseline_start_date?: string;
baseline_end_date?: string;
}
// Configura Supabase con credenciales hardcodeadas para debug
const supabase = createClient(
'https://qjioywarwdbxmdihyrti.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFqaW95d2Fyd2RieG1kaWh5cnRpIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc3MTUxMDU0NCwiZXhwIjoyMDg3MDg2NTQ0fQ.oXxdNBDvMSyfMRaf57oCb8cUS2Bqm73CeyM19v3GJPI'
);
async function debugBodynessExperiment() {
console.log('=== DEBUG BODYNESS ES EXPERIMENT ===\n');
// 1. Obtener el experimento
const { data: expData, error: expError } = await supabase
.from('experiments')
.select('*')
.ilike('name', '%bodyness%')
.single();
if (expError || !expData) {
console.log('❌ No se encontró el experimento');
console.log('Error:', expError);
return;
}
const experiment: Experiment = expData;
console.log('📋 Experimento:', experiment.name);
console.log('📅 Start:', experiment.start_date);
console.log('📅 End:', experiment.end_date);
console.log('🌍 Marketplace:', experiment.marketplace);
console.log('📦 ASINs:', experiment.asins);
console.log('');
// 2. Obtener datos de ventas (sin filtro de año primero) - verificar total
const { count, error: countError } = await supabase
.from('vendor_daily_data')
.select('*', { count: 'exact', head: true });
console.log(`📊 Total registros en vendor_daily_data: ${count || 'unknown'}`);
console.log('');
// Buscar específicamente los ASINs del experimento
console.log('🔍 Buscando ASINs del experimento en la DB...');
const { data: asinData, error: asinError } = await supabase
.from('vendor_daily_data')
.select('*')
.in('asin', experiment.asins);
if (asinError) {
console.log('Error buscando ASINs:', asinError.message);
} else {
console.log(`✅ Registros encontrados para los ASINs del experimento: ${asinData?.length || 0}`);
if (asinData && asinData.length > 0) {
const asinsEncontrados = [...new Set(asinData.map(r => r.asin))];
console.log('ASINs encontrados:', asinsEncontrados);
const marketsEncontrados = [...new Set(asinData.map(r => r.market))];
console.log('Markets encontrados:', marketsEncontrados);
}
}
console.log('');
// Obtener datos filtrados por los ASINs del experimento (usamos asinData que ya tiene los datos)
const salesData = asinData || [];
console.log(`📊 Total registros para ASINs del experimento: ${salesData.length}`);
console.log('');
// Ver muestra de datos
console.log('📋 Muestra de datos (primeros 3 registros - todos los campos):');
salesData.slice(0, 3).forEach((r: any, i) => {
console.log(` ${i+1}.`, JSON.stringify(r, null, 2));
});
console.log('');
// Ver rango de fechas
const dates = salesData.map(r => r.date).filter(Boolean).sort();
if (dates.length > 0) {
console.log(`📅 Rango de fechas: ${dates[0]}${dates[dates.length - 1]}`);
}
console.log('');
// 4. Ver marketplace en los datos
const marketplaces = [...new Set(salesData.map(r => r.market || 'N/A'))];
console.log('🌍 Marketplaces en los datos:', marketplaces);
console.log('');
// 5. Filtrar por marketplace del experimento (ES)
const mktFilter = (experiment.marketplace || '').toLowerCase();
console.log(`Filtrando por marketplace: "${experiment.marketplace}"`);
const filteredByMkt = salesData.filter(r => {
const mkt = (r.market || '').toLowerCase();
return !mktFilter || mktFilter === 'all' || mkt.includes(mktFilter) || mktFilter.includes(mkt);
});
console.log(`✅ Registros después de filtrar por marketplace: ${filteredByMkt.length}`);
console.log('');
// 6. Agrupar por semana ISO (usando el campo date)
const weeksMap = new Map<string, { units: number; records: number; dates: string[] }>();
filteredByMkt.forEach((r: any) => {
const dateStr = r.date;
if (!dateStr) return;
const date = new Date(dateStr);
const iso = getISOWeek(date);
const key = `${iso.year}-W${String(iso.week).padStart(2, '0')}`;
const existing = weeksMap.get(key) || { units: 0, records: 0, dates: [] as string[] };
existing.units += (r.units || r.unitsTotal || 0);
existing.records++;
if (!existing.dates.includes(dateStr)) existing.dates.push(dateStr);
weeksMap.set(key, existing);
});
console.log('📅 Unidades por semana (TODOS los datos):');
const sortedWeeks = Array.from(weeksMap.entries()).sort();
sortedWeeks.forEach(([week, data]) => {
console.log(` ${week}: ${data.units} unidades (${data.records} registros) - fechas: ${data.dates.sort().join(', ')}`);
});
console.log('');
// 7. Ver semanas dentro del período del experimento (Jan 22 - Feb 4, 2026)
const startDate = new Date(experiment.start_date!);
const endDate = new Date(experiment.end_date!);
console.log('📅 Período del experimento:', startDate.toISOString().split('T')[0], '→', endDate.toISOString().split('T')[0]);
// Calcular semanas ISO que toca el experimento
const startISO = getISOWeek(startDate);
const endISO = getISOWeek(endDate);
console.log(`📅 Semanas ISO: ${startISO.year}-W${startISO.week}${endISO.year}-W${endISO.week}`);
const afterWeeks: string[] = [];
let currentYear = startISO.year;
let currentWeek = startISO.week;
while (true) {
const weekKey = `${currentYear}-W${String(currentWeek).padStart(2, '0')}`;
afterWeeks.push(weekKey);
if (currentYear === endISO.year && currentWeek === endISO.week) {
break;
}
currentWeek++;
const weeksInYear = getWeeksInYear(currentYear);
if (currentWeek > weeksInYear) {
currentWeek = 1;
currentYear++;
}
}
console.log(`📅 Semanas incluidas en AFTER: ${afterWeeks.join(', ')}`);
console.log('');
// 8. Calcular unidades en esas semanas
let afterUnits = 0;
let afterWeekCount = 0;
console.log('📊 Unidades en semanas AFTER:');
sortedWeeks.forEach(([week, data]) => {
if (afterWeeks.includes(week)) {
afterUnits += data.units;
afterWeekCount++;
console.log(`${week}: ${data.units} unidades`);
}
});
console.log('');
console.log(`📊 Total unidades en AFTER: ${afterUnits}`);
console.log(`📊 Número de semanas con datos: ${afterWeekCount}`);
console.log(`📊 Media calculada: ${afterUnits} / ${afterWeekCount} = ${(afterUnits / afterWeekCount).toFixed(2)}`);
console.log('');
// 9. Ver baseline (3 semanas antes)
const baselineEndTs = getISOWeekMonday(startISO.year, startISO.week);
const baselineStartTs = baselineEndTs - (afterWeeks.length * 7 * 86400000);
console.log('📅 Baseline período:', new Date(baselineStartTs).toISOString().split('T')[0], '→', new Date(baselineEndTs).toISOString().split('T')[0]);
// Calcular semanas del baseline
const baselineWeeks: string[] = [];
let baselineDate = new Date(baselineStartTs);
while (baselineDate.getTime() < baselineEndTs) {
const iso = getISOWeek(baselineDate);
const weekKey = `${iso.year}-W${String(iso.week).padStart(2, '0')}`;
if (!baselineWeeks.includes(weekKey)) {
baselineWeeks.push(weekKey);
}
baselineDate.setDate(baselineDate.getDate() + 7);
}
console.log(`📅 Semanas del BASELINE: ${baselineWeeks.join(', ')}`);
let beforeUnits = 0;
let beforeWeekCount = 0;
console.log('📊 Unidades en semanas BASELINE:');
sortedWeeks.forEach(([week, data]) => {
if (baselineWeeks.includes(week)) {
beforeUnits += data.units;
beforeWeekCount++;
console.log(`${week}: ${data.units} unidades`);
}
});
console.log('');
console.log(`📊 Total unidades en BASELINE: ${beforeUnits}`);
console.log(`📊 Número de semanas con datos: ${beforeWeekCount}`);
console.log(`📊 Media calculada: ${beforeUnits} / ${beforeWeekCount} = ${(beforeUnits / beforeWeekCount).toFixed(2)}`);
}
// Funciones helper ISO week
function getISOWeek(date: Date): { year: number; week: number } {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
const weekNum = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
return { year: d.getUTCFullYear(), week: weekNum };
}
function getWeeksInYear(year: number): number {
const dec28 = new Date(Date.UTC(year, 11, 28));
const iso = getISOWeek(dec28);
return iso.week;
}
function getISOWeekMonday(year: number, week: number): number {
const jan4 = new Date(Date.UTC(year, 0, 4));
const dayOfWeek = jan4.getUTCDay() || 7;
const week1Monday = new Date(Date.UTC(year, 0, 4 - (dayOfWeek - 1)));
return week1Monday.getTime() + (week - 1) * 7 * 86400000;
}
debugBodynessExperiment().catch(console.error);
+50
View File
@@ -0,0 +1,50 @@
import XLSX from 'xlsx';
import { readFileSync } from 'fs';
import { processAdsExcel } from './services/dataProcessor';
async function run() {
const buf = readFileSync('/tmp/Ads-Weekly.xlsx').buffer;
const adsData = await processAdsExcel(buf);
// Show ALL INKEE DE ads with their cost/salesAds
const inkeeDE = adsData.filter(a =>
a.asin.toUpperCase().startsWith('B0CQ') &&
a.country === 'Amazon DE'
);
console.log('=== ALL INKEE DE ADS RECORDS ===');
console.log('Count:', inkeeDE.length);
// Group by ASIN
const byAsin = new Map<string, any[]>();
for (const a of inkeeDE) {
const list = byAsin.get(a.asin) || [];
list.push(a);
byAsin.set(a.asin, list);
}
for (const [asin, records] of byAsin.entries()) {
const totalCost = records.reduce((s, r) => s + r.cost, 0);
const totalSales = records.reduce((s, r) => s + r.attributedSales30d, 0);
const totalClicks = records.reduce((s, r) => s + r.clicks, 0);
console.log(`\nASIN: ${asin}`);
console.log(` Records: ${records.length} | TotalCost: ${totalCost.toFixed(2)} | TotalSales: ${totalSales.toFixed(2)} | TotalClicks: ${totalClicks}`);
for (const r of records) {
console.log(` Year:${r.year} Week:${r.week} cost:${r.cost} salesAds:${r.attributedSales30d} clicks:${r.clicks}`);
}
}
// Show acos calculation possibility
const withCost = inkeeDE.filter(a => a.cost > 0);
console.log('\nINKEE DE records with cost > 0:', withCost.length);
// What week numbers are we talking about?
// INKEE DE experiment likely runs in Jan-Feb 2026 or similar
// Week 1 of 2026 = Jan 5-11, Week 6 = Feb 9-15, Week 8 = Feb 23 - Mar 1
console.log('\nWeek 1 2026 start (Sun):', new Date(2026, 0, 4).toISOString().split('T')[0]);
console.log('Week 8 2026 start (Sun):', new Date(2026, 0, 4 + 7 * 7).toISOString().split('T')[0]);
}
run().catch(console.error);
+42
View File
@@ -0,0 +1,42 @@
import XLSX from 'xlsx';
import { readFileSync } from 'fs';
import { processAdsExcel, mergeSalesAndAdsData } from './services/dataProcessor';
async function run() {
const buf = readFileSync('/tmp/Ads-Weekly.xlsx').buffer;
const adsData = await processAdsExcel(buf);
// Simulate with empty sales - ads-only records
const merged = mergeSalesAndAdsData([], adsData);
console.log('Total merged records (ads-only):', merged.length);
// Find INKEE DE ads-only records
const inkeeDE = merged.filter(m =>
m.asin.startsWith('B0CQ') &&
(m.marketplace || m.customer || '').includes('DE')
);
console.log('\nINKEE DE in merged:', inkeeDE.length);
for (const r of inkeeDE) {
console.log(` ASIN:${r.asin} year:${r.year} week:${r.week} marketplace:"${r.marketplace}" customer:"${r.customer}" cost:${r.cost} salesAds:${r.salesAds} clicks:${r.clicks}`);
}
// Now test the aggregation (just for "DE" marketplace filter)
const asinSet = new Set(['B0CQ247T2V', 'B0CQTLZRCV']);
for (const marketplace of ['DE', 'Amazon DE', 'de', 'amazon de', '']) {
const matching = inkeeDE.filter(r => {
const mkt = (r.marketplace || r.customer || '').toLowerCase();
if (!marketplace || marketplace === 'All') return true;
return mkt.includes(marketplace.toLowerCase());
});
console.log(`\nFilter mkt="${marketplace}": ${matching.length} matching INKEE DE records`);
if (matching.length > 0) {
const totalCost = matching.reduce((s, r) => s + (r.cost || 0), 0);
console.log(` Total cost: ${totalCost.toFixed(2)}`);
}
}
}
run().catch(console.error);
+42
View File
@@ -0,0 +1,42 @@
import XLSX from 'xlsx';
import { readFileSync } from 'fs';
import { processAdsExcel, mergeSalesAndAdsData } from './services/dataProcessor';
async function run() {
// Load ads directly
const buf = readFileSync('/tmp/Ads-Weekly.xlsx').buffer;
const adsData = await processAdsExcel(buf);
console.log('Total ads records:', adsData.length);
// Find INKEE ads
const inkeeAds = adsData.filter(a => a.asin.includes('B0CQ'));
console.log('INKEE ads rows:', inkeeAds.length);
if (inkeeAds.length > 0) {
console.log('INKEE ad years:', [...new Set(inkeeAds.map(a => a.year))]);
console.log('INKEE ad weeks:', [...new Set(inkeeAds.map(a => a.week))].sort((a, b) => a - b));
console.log('INKEE cost sample:', inkeeAds.slice(0, 3).map(a => ({ year: a.year, week: a.week, asin: a.asin, cost: a.cost, country: a.country })));
// What does "DE" country map to?
const deAds = inkeeAds.filter(a => a.country.includes('DE'));
console.log('\nINKEE DE ads count:', deAds.length);
if (deAds.length > 0) {
console.log('First DE ad:', deAds[0]);
}
}
// Now simulate merge with a mock sales record
const mockSales = [
{
id: 'test', year: 2025, week: 1, asin: 'B0CQTLZRCV', customer: 'Amazon DE',
sellOut: 500, units: 5, month: 'Jan', sku: 'TEST', title: 'INKEE', line: 'Inkee', articleName: 'INKEE'
}
] as any[];
const merged = mergeSalesAndAdsData(mockSales, adsData);
const inkeeRow = merged.find(m => m.asin === 'B0CQTLZRCV' && m.year === 2025);
console.log('\nMerged INKEE row:', inkeeRow ? { cost: inkeeRow.cost, salesAds: inkeeRow.salesAds, clicks: inkeeRow.clicks } : 'NOT FOUND');
}
run().catch(console.error);
+70
View File
@@ -0,0 +1,70 @@
import XLSX from 'xlsx';
import { readFileSync } from 'fs';
import { processAdsExcel, mergeSalesAndAdsData } from './services/dataProcessor';
import { computeDiD } from './services/experimentAnalysis';
import { getExperimentAsins } from './services/experiments';
import { Experiment, CombinedKPIs } from './types';
// Simulate the getWeekStartSunday function from experimentAnalysis.ts
function getWeekStartSunday(year: number, week: number): number {
const jan1 = new Date(year, 0, 1);
const day = jan1.getDay();
const startYear = new Date(year, 0, 1 - day);
return startYear.getTime() + (week - 1) * 7 * 86400000;
}
async function run() {
const buf = readFileSync('/tmp/Ads-Weekly.xlsx').buffer;
const adsData = await processAdsExcel(buf);
const merged = mergeSalesAndAdsData([], adsData);
// Show the timestamps for INKEE DE weeks
const inkeeDE = merged.filter(m =>
m.asin.startsWith('B0CQ') &&
(m.marketplace || m.customer || '').includes('DE')
);
console.log('=== INKEE DE TIMESTAMP ANALYSIS ===');
for (const r of inkeeDE.slice(0, 5)) {
const weekStartTs = getWeekStartSunday(r.year, r.week);
const weekStartDate = new Date(weekStartTs).toISOString().split('T')[0];
console.log(`ASIN:${r.asin} ${r.year}-W${r.week} weekStart:${weekStartDate} cost:${r.cost.toFixed(2)}`);
}
console.log('\n=== WEEK DATE RANGES ===');
for (let week = 1; week <= 8; week++) {
const ts2025 = getWeekStartSunday(2025, week);
const ts2026 = getWeekStartSunday(2026, week);
const d2025 = new Date(ts2025).toISOString().split('T')[0];
const d2026 = new Date(ts2026).toISOString().split('T')[0];
console.log(`Week ${week}: 2025 → ${d2025} | 2026 → ${d2026}`);
}
// Now simulate an experiment that spans the existing data range
// The experiment "INKEE DE" - let's test with dates in Jan 2026 (W1-W4)
const testExperiment: Experiment = {
id: 'test',
name: 'INKEE DE Test',
type: 'advertising',
status: 'active',
asins: ['B0CQ247T2V', 'B0CQTLZRCV'],
control_asins: [],
marketplace: 'DE',
start_date: '2026-01-05', // Start of week 2, 2026
end_date: '2026-02-22', // End of week 8, 2026
baseline_start_date: '2025-01-06', // Week 2, 2025
baseline_end_date: '2025-02-23', // Week 8, 2025
primary_metric: 'acos',
notes: '',
owner: ''
};
console.log('\n=== DiD SIMULATION ===');
const did = computeDiD(testExperiment, merged as CombinedKPIs[]);
console.log('ACOS:', did.metrics['acos']);
console.log('ROAS:', did.metrics['roas']);
console.log('Units:', did.metrics['units']);
}
run().catch(console.error);
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
import XLSX from 'xlsx';
import { readFileSync } from 'fs';
const buf = readFileSync('/tmp/Ads-Weekly.xlsx');
const wb = XLSX.read(buf, { type: 'buffer' });
console.log('=== SHEETS:', wb.SheetNames, '\n');
for (const sheetName of wb.SheetNames) {
const ws = wb.Sheets[sheetName];
const rows: any[] = XLSX.utils.sheet_to_json(ws, { defval: '' });
const weeks = [...new Set(rows.map((r: any) => r.Week))].sort((a: any, b: any) => a - b);
console.log(`Sheet ${sheetName}: ${rows.length} rows | weeks ${weeks[0]} to ${weeks[weeks.length - 1]}`);
const inkeeRows = rows.filter((r: any) =>
String(r.ASIN || '').toUpperCase().startsWith('B0CQ')
);
if (inkeeRows.length > 0) {
console.log(` INKEE rows: ${inkeeRows.length}`);
console.log(' First INKEE row:', JSON.stringify(inkeeRows[0]));
const inkeeWeeks = [...new Set(inkeeRows.map((r: any) => r.Week))].sort((a: any, b: any) => a - b);
console.log(' INKEE weeks:', inkeeWeeks.join(', '));
} else {
console.log(` No INKEE rows (B0CQ...) in ${sheetName}`);
const deRows = rows.filter((r: any) => String(r.Country || '') === 'DE');
const uniqueAsins = [...new Set(deRows.map((r: any) => r.ASIN))].slice(0, 8);
console.log(' Some DE ASINs:', uniqueAsins.join(', '));
}
}
+37
View File
@@ -0,0 +1,37 @@
import XLSX from 'xlsx';
import { readFileSync } from 'fs';
const buf = readFileSync('/Users/christianvidalwolf/github/CrazeAnalytix/docs/plans/Ads_Performance_Export_2026-02-26.xlsx');
const wb = XLSX.read(buf, { type: 'buffer' });
console.log('Sheets:', wb.SheetNames);
for (const sheetName of wb.SheetNames) {
const ws = wb.Sheets[sheetName];
const rows: any[] = XLSX.utils.sheet_to_json(ws, { defval: '' });
if (rows.length === 0) { console.log(`Sheet "${sheetName}" is empty`); continue; }
console.log(`\n=== Sheet: "${sheetName}" (${rows.length} rows) ===`);
const headers = Object.keys(rows[0]);
console.log('Columns:', headers.join(' | '));
console.log('First 3 rows:');
rows.slice(0, 3).forEach(r => console.log(' ', JSON.stringify(r).slice(0, 300)));
// Look for key columns
const weekCol = headers.find(h => /week|woche|semana/i.test(h));
const asinCol = headers.find(h => /asin/i.test(h));
const countryCol = headers.find(h => /country|marketplace|portfolio|land|país/i.test(h));
const costCol = headers.find(h => /^cost$|^spend$|^ausgaben$|^gasto$/i.test(h) || (/cost|spend/i.test(h) && !/acos|of sales/i.test(h)));
const salesCol = headers.find(h => /sales|umsatz|ventas/i.test(h) && !/acos|cost of/i.test(h));
console.log(`\nKey columns: week="${weekCol}" | asin="${asinCol}" | country="${countryCol}" | cost="${costCol}" | sales="${salesCol}"`);
if (weekCol && costCol) {
const weeks = [...new Set(rows.map(r => r[weekCol!]))];
console.log('Unique weeks:', weeks.sort());
const totalCost = rows.reduce((s, r) => s + (parseFloat(String(r[costCol!]).replace(',', '.')) || 0), 0);
const totalSales = salesCol ? rows.reduce((s, r) => s + (parseFloat(String(r[salesCol!]).replace(',', '.')) || 0), 0) : 0;
console.log(`Total cost: ${totalCost.toFixed(2)}, Total sales: ${totalSales.toFixed(2)}`);
}
}
+57
View File
@@ -0,0 +1,57 @@
import fs from 'fs';
import { processCSV } from './services/dataProcessor';
import { processAdsExcel, mergeSalesAndAdsData } from './services/dataProcessor';
import { computeDiD } from './services/experimentAnalysis';
async function runTest() {
console.log("Loading sales data...");
const csvStr = fs.readFileSync('public/Craze analytix 2026.csv', 'utf-8');
const sales = await processCSV(csvStr);
console.log("Loading ads data...");
const excelBuffer = fs.readFileSync('public/ads.xlsx').buffer;
const ads = await processAdsExcel(excelBuffer);
console.log("Sales rows:", sales.length);
console.log("Ads rows:", ads.length);
// Check INKEE DE explicitly
const inkeeSales = sales.filter(s => s.asin.includes('B0CQ'))
console.log("INKEE Sales rows:", inkeeSales.length);
const inkeeAds = ads.filter(a => a.asin.includes('B0CQ'))
console.log("INKEE Ad rows:", inkeeAds.length);
console.log("Merging...");
const merged = mergeSalesAndAdsData(sales, ads);
const mergedInkee = merged.filter(m => m.asin.includes('B0CQ') && m.year === 2024);
const totalCost = mergedInkee.reduce((sum, r) => sum + (r.cost || 0), 0);
const totalAdSales = mergedInkee.reduce((sum, r) => sum + (r.salesAds || 0), 0);
console.log("Merged INKEE total cost 2024:", totalCost);
console.log("Merged INKEE total ad sales 2024:", totalAdSales);
const experiment = {
id: "INKEE_DE",
name: "INKEE DE Test",
status: "active",
type: "advertising",
asins: ["B0CQXNBGBQ"], // Assuming this is INKEE DE
control_asins: [],
marketplace: "Amazon DE", // Matching the UI
start_date: "2024-03-01",
end_date: "2024-04-15",
baseline_start_date: "2024-01-01",
baseline_end_date: "2024-02-28",
primary_metric: "acos"
} as any;
console.log("Running DiD...");
const did = computeDiD(experiment, merged);
console.log("DiD Output:", JSON.stringify(did.metrics, null, 2));
}
runTest().catch(console.error);
+94
View File
@@ -0,0 +1,94 @@
// Verificar fechas ISO para el experimento BODYNESS
function getISOWeek(date: Date): { year: number; week: number } {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
const weekNum = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
return { year: d.getUTCFullYear(), week: weekNum };
}
function getISOWeekMonday(year: number, week: number): number {
const jan4 = new Date(Date.UTC(year, 0, 4));
const dayOfWeek = jan4.getUTCDay() || 7;
const week1Monday = new Date(Date.UTC(year, 0, 4 - (dayOfWeek - 1)));
return week1Monday.getTime() + (week - 1) * 7 * 86400000;
}
function getWeeksInYear(year: number): number {
const dec28 = new Date(Date.UTC(year, 11, 28));
const iso = getISOWeek(dec28);
return iso.week;
}
function calculateISOWeekRange(startDate: Date, endDate: Date): {
numWeeks: number;
expandedStartTs: number;
expandedEndTs: number;
weekKeys: string[];
} {
const startISO = getISOWeek(startDate);
const endISO = getISOWeek(endDate);
const weekKeys: string[] = [];
let currentYear = startISO.year;
let currentWeek = startISO.week;
while (true) {
const weekKey = `${currentYear}-W${String(currentWeek).padStart(2, '0')}`;
weekKeys.push(weekKey);
if (currentYear === endISO.year && currentWeek === endISO.week) {
break;
}
currentWeek++;
const weeksInYear = getWeeksInYear(currentYear);
if (currentWeek > weeksInYear) {
currentWeek = 1;
currentYear++;
}
}
const expandedStartTs = getISOWeekMonday(startISO.year, startISO.week);
const expandedEndTs = getISOWeekMonday(endISO.year, endISO.week) + 7 * 86400000;
return {
numWeeks: weekKeys.length,
expandedStartTs,
expandedEndTs,
weekKeys,
};
}
// Experimento: 22 enero - 4 febrero 2026
const startDate = new Date('2026-01-22');
const endDate = new Date('2026-02-04');
console.log('=== Experimento BODYNESS ES ===');
console.log('Start:', startDate.toISOString().split('T')[0], '(day', startDate.getDay(), ')');
console.log('End:', endDate.toISOString().split('T')[0], '(day', endDate.getDay(), ')');
console.log('');
const startISO = getISOWeek(startDate);
const endISO = getISOWeek(endDate);
console.log('Start ISO:', `W${startISO.week}-${startISO.year}`);
console.log('End ISO:', `W${endISO.week}-${endISO.year}`);
console.log('');
const isoRange = calculateISOWeekRange(startDate, endDate);
console.log('ISO Week Range:');
console.log(' numWeeks:', isoRange.numWeeks);
console.log(' weekKeys:', isoRange.weekKeys);
console.log(' expandedStart:', new Date(isoRange.expandedStartTs).toISOString().split('T')[0]);
console.log(' expandedEnd:', new Date(isoRange.expandedEndTs).toISOString().split('T')[0]);
console.log('');
// Verificar cada semana
isoRange.weekKeys.forEach(week => {
const [year, weekNum] = week.split('-W');
const monday = getISOWeekMonday(Number(year), Number(weekNum));
const sunday = monday + 6 * 86400000 + 86399999;
console.log(` ${week}: ${new Date(monday).toISOString().split('T')[0]} (Mon) → ${new Date(sunday).toISOString().split('T')[0]} (Sun)`);
});
+40
View File
@@ -0,0 +1,40 @@
import { computeDiD } from './services/experimentAnalysis';
import { CombinedKPIs, Experiment } from './types';
const salesData: CombinedKPIs[] = [
{
id: '1', marketplace: 'Amazon DE', customer: 'Amazon DE', month: 'Jan-24', week: 1, year: 2024,
asin: 'B0C123', title: 'Test', line: 'Test', sku: 'T-1',
salesTotal: 1000, unitsTotal: 100, salesAds: 500, unitsAds: 50, cost: 100,
clicks: 100, impressions: 1000, conversions: 5, salesOrganic: 500, unitsOrganic: 50,
paidSalesShare: 50, organicSalesShare: 50, acos: 20, tacos: 10, roas: 5, ctr: 10, cpc: 1, cvrUnits: 5, glanceViews: 1000
},
{
id: '2', marketplace: 'Amazon DE', customer: 'Amazon DE', month: 'Feb-24', week: 5, year: 2024,
asin: 'B0C123', title: 'Test', line: 'Test', sku: 'T-1',
salesTotal: 2000, unitsTotal: 200, salesAds: 2000, unitsAds: 200, cost: 200,
clicks: 200, impressions: 2000, conversions: 10, salesOrganic: 0, unitsOrganic: 0,
paidSalesShare: 100, organicSalesShare: 0, acos: 10, tacos: 10, roas: 10, ctr: 10, cpc: 1, cvrUnits: 5, glanceViews: 2000
}
];
const experiment: Experiment = {
id: 'exp1',
name: 'Test Exp',
type: 'advertising',
status: 'active',
asins: ['B0C123'],
control_asins: [],
marketplace: 'DE',
start_date: '2024-01-20',
end_date: '2024-02-28',
baseline_start_date: '2023-12-01',
baseline_end_date: '2024-01-19',
created_at: '',
updated_at: '',
primary_metric: 'acos',
changes: []
};
const result = computeDiD(experiment, salesData);
console.log(JSON.stringify(result.metrics, null, 2));