fix(experiments): use exact fractional proportions for overlapping weeks in DiD calculation

This commit is contained in:
Christian Vidal Wolf
2026-02-25 12:59:03 +01:00
parent 1edfd97ed1
commit 8ed073ff6e
+35 -3
View File
@@ -40,6 +40,7 @@ interface ComputedWeeklyMetrics extends WeeklyMetrics {
cvr: number;
ctr: number;
roas: number;
fraction?: number;
}
function getISOWeekStart(year: number, week: number): number {
@@ -142,9 +143,39 @@ function splitPeriods(
// 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);
const overlapStart = Math.max(weekStartTs, startTs);
const overlapEnd = Math.min(weekEndTs, endTs);
const overlapFraction = Math.max(0, Math.min(1, (overlapEnd - overlapStart) / (7 * 86400000)));
if (overlapFraction > 0) {
after.push({
...w,
units: w.units * overlapFraction,
revenue: w.revenue * overlapFraction,
sessions: w.sessions * overlapFraction,
cost: w.cost * overlapFraction,
clicks: w.clicks * overlapFraction,
impressions: w.impressions * overlapFraction,
fraction: overlapFraction
});
}
} else if (overlapsBefore) {
before.push(w);
const overlapStart = Math.max(weekStartTs, beforeStartTs);
const overlapEnd = Math.min(weekEndTs, beforeEndTs);
const overlapFraction = Math.max(0, Math.min(1, (overlapEnd - overlapStart) / (7 * 86400000)));
if (overlapFraction > 0) {
before.push({
...w,
units: w.units * overlapFraction,
revenue: w.revenue * overlapFraction,
sessions: w.sessions * overlapFraction,
cost: w.cost * overlapFraction,
clicks: w.clicks * overlapFraction,
impressions: w.impressions * overlapFraction,
fraction: overlapFraction
});
}
}
}
@@ -154,7 +185,8 @@ function splitPeriods(
function avgMetric(data: ComputedWeeklyMetrics[], metric: string): number {
if (data.length === 0) return 0;
const sum = data.reduce((s, w) => s + getMetricValue(w, metric), 0);
return sum / data.length;
const totalFraction = data.reduce((s, w) => s + (w.fraction || 1), 0);
return totalFraction > 0 ? sum / totalFraction : 0;
}
const METRICS = ['units', 'sessions', 'cvr', 'ctr', 'roas', 'revenue', 'acos'];