feat: enable custom baseline periods for Experiments to solve seasonality

This commit is contained in:
Christian Vidal Wolf
2026-02-23 18:34:04 +01:00
parent 69f4456942
commit 4935112a55
14 changed files with 428 additions and 1144 deletions
+49 -10
View File
@@ -555,10 +555,9 @@ const ExperimentDetailView: React.FC<{
{/* Confidence bar */}
<div className="mt-4 h-1.5 bg-slate-700/50 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-500 ${
experiment.verdict === 'winner' ? 'bg-emerald-400' :
className={`h-full rounded-full transition-all duration-500 ${experiment.verdict === 'winner' ? 'bg-emerald-400' :
experiment.verdict === 'loser' ? 'bg-red-400' : 'bg-slate-400'
}`}
}`}
style={{ width: `${Math.round((experiment.verdict_probability || 0) * 100)}%` }}
/>
</div>
@@ -577,6 +576,17 @@ const ExperimentDetailView: React.FC<{
{/* Timeline */}
<Card title="Timeline">
<div className="space-y-3">
{experiment.baseline_start_date && experiment.baseline_end_date && (
<div className="flex flex-col mb-3 pb-3 border-b border-slate-700/50">
<span className="text-xs font-semibold text-indigo-400 mb-1 uppercase tracking-wider">Baseline Period</span>
<span className="text-white text-sm">
{new Date(experiment.baseline_start_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}
{' — '}
{new Date(experiment.baseline_end_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}
</span>
<span className="text-[10px] text-slate-500 mt-0.5">Custom Seasonal Comparison</span>
</div>
)}
<div className="flex justify-between text-sm">
<span className="text-slate-400">Start</span>
<span className="text-white font-medium">{new Date(experiment.start_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</span>
@@ -721,9 +731,8 @@ const ExperimentDetailView: React.FC<{
return (
<div
key={metric}
className={`p-4 rounded-xl border transition-colors ${
isPrimary ? 'border-indigo-500/40 bg-indigo-500/5' : 'border-slate-800 bg-slate-900/50'
}`}
className={`p-4 rounded-xl border transition-colors ${isPrimary ? 'border-indigo-500/40 bg-indigo-500/5' : 'border-slate-800 bg-slate-900/50'
}`}
>
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-medium text-slate-400 uppercase tracking-wide">
@@ -742,10 +751,9 @@ const ExperimentDetailView: React.FC<{
<div className="mt-2 flex items-center gap-2">
<div className="flex-1 h-1 bg-slate-700 rounded-full overflow-hidden">
<div
className={`h-full rounded-full ${
result.posterior_prob_positive >= 0.9 ? 'bg-emerald-400' :
className={`h-full rounded-full ${result.posterior_prob_positive >= 0.9 ? 'bg-emerald-400' :
result.posterior_prob_positive <= 0.1 ? 'bg-red-400' : 'bg-slate-400'
}`}
}`}
style={{ width: `${Math.round(result.posterior_prob_positive * 100)}%` }}
/>
</div>
@@ -850,6 +858,9 @@ const ExperimentCreateView: React.FC<{
const [primaryMetric, setPrimaryMetric] = useState<ExperimentMetric>('units');
const [startDate, setStartDate] = useState(new Date().toISOString().split('T')[0]);
const [endDate, setEndDate] = useState('');
const [enableCustomBaseline, setEnableCustomBaseline] = useState(false);
const [baselineStartDate, setBaselineStartDate] = useState('');
const [baselineEndDate, setBaselineEndDate] = useState('');
const [treatmentAsins, setTreatmentAsins] = useState('');
const [controlAsins, setControlAsins] = useState('');
const [changes, setChanges] = useState<ExperimentChangeAnnotation[]>([{ field: '', before_value: '', after_value: '' }]);
@@ -895,6 +906,8 @@ const ExperimentCreateView: React.FC<{
marketplace,
start_date: startDate,
end_date: endDate || undefined,
baseline_start_date: enableCustomBaseline && baselineStartDate ? baselineStartDate : undefined,
baseline_end_date: enableCustomBaseline && baselineEndDate ? baselineEndDate : undefined,
hypothesis: hypothesis.trim() || undefined,
primary_metric: primaryMetric,
changes: validChanges,
@@ -1073,7 +1086,7 @@ const ExperimentCreateView: React.FC<{
{/* Timeline */}
<Card title="Timeline">
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-2 gap-4 mb-6">
<FormField label="Start Date" required>
<input type="date" value={startDate} onChange={e => setStartDate(e.target.value)} className="input-field" />
</FormField>
@@ -1081,6 +1094,32 @@ const ExperimentCreateView: React.FC<{
<input type="date" value={endDate} onChange={e => setEndDate(e.target.value)} className="input-field" />
</FormField>
</div>
<div className="border-t border-slate-700/50 pt-4">
<label className="flex items-center gap-2 cursor-pointer mb-4">
<input
type="checkbox"
checked={enableCustomBaseline}
onChange={e => setEnableCustomBaseline(e.target.checked)}
className="w-4 h-4 rounded-md border-slate-600 bg-slate-800 text-indigo-500 focus:ring-indigo-500/50"
/>
<span className="text-sm font-medium text-slate-300">Set Custom Baseline Period (Solves Seasonality)</span>
</label>
{enableCustomBaseline && (
<div className="grid grid-cols-2 gap-4 bg-indigo-500/5 p-4 rounded-xl border border-indigo-500/20">
<div className="col-span-2 text-xs text-slate-400 mb-1">
Instead of dynamically looking right before the start date, specify an exact previous period (like year-over-year).
</div>
<FormField label="Baseline Start Date">
<input type="date" value={baselineStartDate} onChange={e => setBaselineStartDate(e.target.value)} className="input-field" />
</FormField>
<FormField label="Baseline End Date">
<input type="date" value={baselineEndDate} onChange={e => setBaselineEndDate(e.target.value)} className="input-field" />
</FormField>
</div>
)}
</div>
</Card>
{/* Error + Submit */}