mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:45:23 +02:00
fix(experiments): include overlapping weeks in date range logic and use exact ISO weeks
This commit is contained in:
@@ -42,6 +42,13 @@ interface ComputedWeeklyMetrics extends WeeklyMetrics {
|
|||||||
roas: number;
|
roas: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getISOWeekStart(year: number, week: number): number {
|
||||||
|
const jan4 = new Date(year, 0, 4);
|
||||||
|
const day = jan4.getDay() || 7;
|
||||||
|
const startYear = new Date(year, 0, 4 - day + 1);
|
||||||
|
return startYear.getTime() + (week - 1) * 7 * 86400000;
|
||||||
|
}
|
||||||
|
|
||||||
function aggregateWeeklyMetrics(
|
function aggregateWeeklyMetrics(
|
||||||
asinSet: Set<string>,
|
asinSet: Set<string>,
|
||||||
salesData: CombinedKPIs[],
|
salesData: CombinedKPIs[],
|
||||||
@@ -61,10 +68,9 @@ function aggregateWeeklyMetrics(
|
|||||||
const key = `${year}-W${String(weekNum).padStart(2, '0')}`;
|
const key = `${year}-W${String(weekNum).padStart(2, '0')}`;
|
||||||
|
|
||||||
if (!weeklyMap.has(key)) {
|
if (!weeklyMap.has(key)) {
|
||||||
const d = new Date(year, 0, 1 + (weekNum - 1) * 7);
|
|
||||||
weeklyMap.set(key, {
|
weeklyMap.set(key, {
|
||||||
week: key,
|
week: key,
|
||||||
timestamp: d.getTime(),
|
timestamp: getISOWeekStart(year, weekNum),
|
||||||
units: 0,
|
units: 0,
|
||||||
revenue: 0,
|
revenue: 0,
|
||||||
sessions: 0,
|
sessions: 0,
|
||||||
@@ -125,10 +131,20 @@ function splitPeriods(
|
|||||||
const after: ComputedWeeklyMetrics[] = [];
|
const after: ComputedWeeklyMetrics[] = [];
|
||||||
|
|
||||||
for (const w of data) {
|
for (const w of data) {
|
||||||
if (w.timestamp >= beforeStartTs && w.timestamp < beforeEndTs) {
|
const weekStartTs = w.timestamp;
|
||||||
before.push(w);
|
const weekEndTs = w.timestamp + 6 * 86400000 + 86399999; // End of the 7th day
|
||||||
} else if (w.timestamp >= startTs && w.timestamp <= endTs) {
|
|
||||||
|
// A week overlaps the treatment period if it starts before the period ends,
|
||||||
|
// AND ends after the period starts.
|
||||||
|
const overlapsAfter = weekEndTs >= startTs && weekStartTs <= endTs;
|
||||||
|
const overlapsBefore = weekEndTs >= beforeStartTs && weekStartTs < beforeEndTs;
|
||||||
|
|
||||||
|
// We prioritize assigning to 'after' so that we capture all units happening
|
||||||
|
// closely around the experiment dates for accurate reporting.
|
||||||
|
if (overlapsAfter) {
|
||||||
after.push(w);
|
after.push(w);
|
||||||
|
} else if (overlapsBefore) {
|
||||||
|
before.push(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,12 +338,13 @@ export function buildCounterfactualSeries(
|
|||||||
if (!hasControlGroup) {
|
if (!hasControlGroup) {
|
||||||
// Without control group, counterfactual = flat line at pre-treatment average
|
// Without control group, counterfactual = flat line at pre-treatment average
|
||||||
const startTs = startDate.getTime();
|
const startTs = startDate.getTime();
|
||||||
|
const endTs = endDate.getTime();
|
||||||
const beforeStartTs = beforeStart.getTime();
|
const beforeStartTs = beforeStart.getTime();
|
||||||
const beforeEndTs = beforeEnd.getTime();
|
const beforeEndTs = beforeEnd.getTime();
|
||||||
|
|
||||||
// To calculate counterfactual, we need the "before" average.
|
// To calculate counterfactual, we need the "before" average.
|
||||||
// Treatment before period:
|
// Treatment before period:
|
||||||
const beforeData = treatmentWeekly.filter(w => w.timestamp >= beforeStartTs && w.timestamp < beforeEndTs);
|
const { before: beforeData } = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs); // Use splitPeriods to match exactly
|
||||||
const preAvg = avgMetric(beforeData, metric);
|
const preAvg = avgMetric(beforeData, metric);
|
||||||
|
|
||||||
return treatmentWeekly.map(w => ({
|
return treatmentWeekly.map(w => ({
|
||||||
@@ -342,11 +359,12 @@ export function buildCounterfactualSeries(
|
|||||||
const controlWeekly = aggregateWeeklyMetrics(controlAsinSet, salesData, experiment.marketplace);
|
const controlWeekly = aggregateWeeklyMetrics(controlAsinSet, salesData, experiment.marketplace);
|
||||||
|
|
||||||
const startTs = startDate.getTime();
|
const startTs = startDate.getTime();
|
||||||
|
const endTs = endDate.getTime();
|
||||||
const beforeStartTs = beforeStart.getTime();
|
const beforeStartTs = beforeStart.getTime();
|
||||||
const beforeEndTs = beforeEnd.getTime();
|
const beforeEndTs = beforeEnd.getTime();
|
||||||
|
|
||||||
const tBeforeData = treatmentWeekly.filter(w => w.timestamp >= beforeStartTs && w.timestamp < beforeEndTs);
|
const { before: tBeforeData } = splitPeriods(treatmentWeekly, startTs, endTs, beforeStartTs, beforeEndTs);
|
||||||
const cBeforeData = controlWeekly.filter(w => w.timestamp >= beforeStartTs && w.timestamp < beforeEndTs);
|
const { before: cBeforeData } = splitPeriods(controlWeekly, startTs, endTs, beforeStartTs, beforeEndTs);
|
||||||
|
|
||||||
const tPreAvg = avgMetric(tBeforeData, metric);
|
const tPreAvg = avgMetric(tBeforeData, metric);
|
||||||
const cPreAvg = avgMetric(cBeforeData, metric);
|
const cPreAvg = avgMetric(cBeforeData, metric);
|
||||||
|
|||||||
Reference in New Issue
Block a user