diff --git a/components/ExperimentDetail.tsx b/components/ExperimentDetail.tsx index bcb8fa2..c7f3d94 100644 --- a/components/ExperimentDetail.tsx +++ b/components/ExperimentDetail.tsx @@ -64,10 +64,57 @@ const ExperimentDetail: React.FC = ({ experimentId, onClo } }; + const targetAsins = useMemo(() => { + if (!experiment) return []; + return Array.from(getExperimentAsins(experiment.asins, salesData)); + }, [experiment, salesData]); + + const productBreakdown = useMemo(() => { + if (!experiment || targetAsins.length <= 1 || !salesData.length) return null; + + const parseDate = (d: string) => { + const [y, m, da] = d.split('T')[0].split('-'); + return new Date(Number(y), Number(m) - 1, Number(da)); + }; + + const startDate = parseDate(experiment.start_date); + const endDate = experiment.end_date ? parseDate(experiment.end_date) : new Date(); + + const asinMap = new Map(); + + targetAsins.forEach(a => asinMap.set(a, { units: 0, revenue: 0, gv: 0, spend: 0 })); + + salesData.forEach(r => { + const asin = (r.asin || '').toUpperCase(); + if (!asinMap.has(asin)) return; + + const recordDate = new Date(r.year, 0, 1 + ((r.week || 1) - 1) * 7); + if (recordDate >= startDate && recordDate <= endDate) { + const d = asinMap.get(asin)!; + d.units += r.unitsTotal || 0; + d.revenue += r.salesTotal || 0; + d.gv += r.glanceViews || 0; + d.spend += r.cost || 0; + } + }); + + return Array.from(asinMap.entries()) + .map(([asin, metrics]) => ({ + asin, + ...metrics, + cvr: metrics.gv > 0 ? (metrics.units / metrics.gv) * 100 : 0, + acos: metrics.revenue > 0 ? (metrics.spend / metrics.revenue) * 100 : 0 + })) + .filter(m => m.units > 0 || m.gv > 0 || m.spend > 0) + .sort((a, b) => b[experiment.primary_metric === 'bsr' ? 'units' : experiment.primary_metric as 'units' | 'revenue' | 'gv' | 'cvr' | 'acos' || 'units'] - a[experiment.primary_metric === 'bsr' ? 'units' : experiment.primary_metric as 'units' | 'revenue' | 'gv' | 'cvr' | 'acos' || 'units']); + }, [experiment, salesData, targetAsins]); + + const targetAsinsOriginal = experiment?.asins || []; + const chartData = useMemo(() => { if (!experiment || !salesData.length) return []; - const asinSet = getExperimentAsins(experiment.asins, salesData); + const asinSet = new Set(targetAsins); const weeklyMap = new Map(); salesData.forEach(r => { @@ -488,19 +535,66 @@ const ExperimentDetail: React.FC = ({ experimentId, onClo {/* ASINs */} - -
- {(experiment.asins || []).map((asin, idx) => ( - - {asin} - - ))} + +
+
+ {targetAsinsOriginal.map((asin, idx) => ( + + {asin} + + ))} + {targetAsins.map((asin, idx) => { + // Don't show again if it's the exact same as an explicit ASIN, + // but show if it was resolved from a LINE: prefix + if (targetAsinsOriginal.includes(asin)) return null; + return ( + + {asin} + + ); + })} +
+ {/* Product Breakdown (Only shown if multiple ASINs, i.e. product line) */} + {productBreakdown && productBreakdown.length > 0 && ( + +
+ + + + + + + + + + + + + {productBreakdown.map(p => ( + + + + + + + + + ))} + +
ASINUnitsRevenueGlance ViewsCVRACOS
{p.asin}{p.units.toLocaleString()}€{p.revenue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}{p.gv.toLocaleString()}{p.cvr.toFixed(1)}%{p.acos.toFixed(1)}%
+
+
+ )} + {/* Learnings */} {editing ? ( diff --git a/services/experiments.ts b/services/experiments.ts index 5c5e041..58f4b26 100644 --- a/services/experiments.ts +++ b/services/experiments.ts @@ -232,8 +232,13 @@ export const calculateExperimentPerformance = async ( experiment_acos: number; actual_lift_percent: number; }> => { - const startDate = new Date(experiment.start_date); - const endDate = experiment.end_date ? new Date(experiment.end_date) : new Date(); + const parseLocalDate = (dateStr: string) => { + const [y, m, d] = dateStr.split('T')[0].split('-'); + return new Date(Number(y), Number(m) - 1, Number(d)); + }; + + const startDate = parseLocalDate(experiment.start_date); + const endDate = experiment.end_date ? parseLocalDate(experiment.end_date) : new Date(); // Calculate baseline period (same duration before experiment) const durationMs = endDate.getTime() - startDate.getTime(); diff --git a/test-calc.js b/test-calc.js new file mode 100644 index 0000000..16b1ca0 --- /dev/null +++ b/test-calc.js @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; + +// read dummy data or mock +const mockSalesData = [ + { + asin: 'B08F2J8S1Y', + line: 'LEGENDS', + year: 2026, + week: 1, + unitsTotal: 10, + salesTotal: 100, + }, + { + asin: 'B08F2J8S1Y', + line: 'LEGENDS', + year: 2026, + week: 2, + unitsTotal: 15, + salesTotal: 150, + } +]; + +const experimentAsins = ['LINE:LEGENDS']; + +const lines = new Set(); +const explicitAsins = new Set(); +experimentAsins.forEach(a => { + const val = (a || '').trim().toUpperCase(); + if (val.startsWith('LINE:')) { + lines.add(val.substring(5).trim()); + } else if (val) { + explicitAsins.add(val); + } +}); + +const asinSet = new Set(explicitAsins); +if (lines.size > 0 && mockSalesData) { + mockSalesData.forEach(r => { + const line = (r.line || '').trim().toUpperCase(); + if (line && lines.has(line)) { + if (r.asin) asinSet.add(r.asin.toUpperCase()); + } + }); +} + +console.log('Resolved ASINs:', Array.from(asinSet)); + +// Date test +const startDateStr = '2026-01-01'; +const endDateStr = '2026-01-14'; + +const startDate = new Date(startDateStr); +const endDate = new Date(endDateStr); + +console.log('Start Date UTC:', startDate.toISOString(), 'Local:', startDate.toString()); +console.log('End Date UTC:', endDate.toISOString(), 'Local:', endDate.toString()); + +const recordDate1 = new Date(2026, 0, 1); // week 1 +const recordDate2 = new Date(2026, 0, 8); // week 2 +const recordDate3 = new Date(2026, 0, 15); // week 3 + +console.log('Week 1 Record Date Local:', recordDate1.toString()); +console.log('Week 1 included in experiment?', recordDate1 >= startDate && recordDate1 <= endDate); +console.log('Week 2 Record Date Local:', recordDate2.toString()); +console.log('Week 2 included in experiment?', recordDate2 >= startDate && recordDate2 <= endDate); +console.log('Week 3 Record Date Local:', recordDate3.toString()); +console.log('Week 3 included in experiment?', recordDate3 >= startDate && recordDate3 <= endDate); +