Skip to content

Commit 980f1d9

Browse files
committed
fix(nimbus): fix retention displaying incorrect values
1 parent 976a5b5 commit 980f1d9

3 files changed

Lines changed: 130 additions & 17 deletions

File tree

experimenter/experimenter/jetstream/client.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,29 @@ def get_experiment_data(experiment: NimbusExperiment):
287287
if data and window == AnalysisWindow.OVERALL:
288288
# Append some values onto the incoming Jetstream data
289289
data.append_population_percentages()
290-
data.append_retention_data(
290+
week_2_retention = data.get_retention_by_window(
291+
2,
291292
raw_data.get(AnalysisWindow.WEEKLY, {})
292293
.get(AnalysisBasis.ENROLLMENTS, {})
293-
.get(segment)
294+
.get(segment),
295+
Metric.RETENTION,
294296
)
297+
if not week_2_retention:
298+
runtime_errors.append(
299+
AnalysisError(
300+
experiment=experiment.slug,
301+
filename="experimenter/jetstream/client.py",
302+
func_name="get_experiment_data",
303+
log_level="WARNING",
304+
message=(
305+
"Week 2 retention is unavailable because this "
306+
"experiment did not run long enough."
307+
),
308+
metric=Metric.RETENTION,
309+
timestamp=timezone.now(),
310+
)
311+
)
312+
data.extend(week_2_retention)
295313
# Append 3-day retention from daily data
296314
data.append_retention_3_days(
297315
raw_data.get(AnalysisWindow.DAILY, {})
@@ -307,6 +325,12 @@ def get_experiment_data(experiment: NimbusExperiment):
307325
if segment == Segment.ALL:
308326
experiment_data["other_metrics"] = other_metrics
309327
elif data and window == AnalysisWindow.WEEKLY:
328+
data.replace_retention_weeks(
329+
raw_data.get(AnalysisWindow.WEEKLY, {})
330+
.get(AnalysisBasis.ENROLLMENTS, {})
331+
.get(segment)
332+
)
333+
310334
# Append 3-day retention from daily data
311335
data.append_retention_3_days(
312336
raw_data.get(AnalysisWindow.DAILY, {})
@@ -370,6 +394,12 @@ def get_experiment_data(experiment: NimbusExperiment):
370394
if segment == Segment.ALL:
371395
experiment_data["other_metrics"].update(other_metrics)
372396
elif data and window == AnalysisWindow.WEEKLY:
397+
data.replace_retention_weeks(
398+
raw_data.get(AnalysisWindow.WEEKLY, {})
399+
.get(AnalysisBasis.EXPOSURES, {})
400+
.get(segment)
401+
)
402+
373403
# Append 3-day retention from daily data
374404
data.append_retention_3_days(
375405
raw_data.get(AnalysisWindow.DAILY, {})
@@ -421,14 +451,17 @@ def get_experiment_data(experiment: NimbusExperiment):
421451
errors_experiment_overall.append(err)
422452

423453
for e in runtime_errors:
424-
analysis_error = AnalysisError(
425-
experiment=experiment.slug,
426-
filename="experimenter/jetstream/client.py",
427-
func_name="load_data_from_gcs",
428-
log_level="WARNING",
429-
message=e,
430-
timestamp=timezone.now(),
431-
)
454+
if isinstance(e, AnalysisError):
455+
analysis_error = e
456+
else:
457+
analysis_error = AnalysisError(
458+
experiment=experiment.slug,
459+
filename="experimenter/jetstream/client.py",
460+
func_name="load_data_from_gcs",
461+
log_level="WARNING",
462+
message=e,
463+
timestamp=timezone.now(),
464+
)
432465
errors_experiment_overall.append(analysis_error.model_dump())
433466

434467
errors_by_metric["experiment"] = errors_experiment_overall

experimenter/experimenter/jetstream/models.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class Group(StrEnum):
7777
Group.SEARCH: SEARCH_METRICS,
7878
Group.USAGE: USAGE_METRICS,
7979
}
80+
RETENTION_2_WEEKS_WINDOW_INDEX = 2
8081
RETENTION_3_DAYS_WINDOW_INDEX = 4
8182

8283

@@ -155,14 +156,24 @@ def get_retention_by_window(self, window_index, data, metric):
155156
]
156157

157158
def append_retention_data(self, weekly_data):
158-
# Try to get the two-week retention data. If it doesn't
159-
# exist (experiment was too short), settle for 1 week.
160-
retention_data = self.get_retention_by_window(2, weekly_data, Metric.RETENTION)
161-
if len(retention_data) == 0:
162-
retention_data = self.get_retention_by_window(
163-
1, weekly_data, Metric.RETENTION
164-
)
159+
# Only use two-week retention data.
160+
retention_data = self.get_retention_by_window(
161+
RETENTION_2_WEEKS_WINDOW_INDEX, weekly_data, Metric.RETENTION
162+
)
163+
164+
self.extend(retention_data)
165+
166+
def replace_retention_weeks(self, weekly_data):
167+
# Remove all weekly retention data except for week 2.
168+
retention_data = self.get_retention_by_window(
169+
RETENTION_2_WEEKS_WINDOW_INDEX, weekly_data, Metric.RETENTION
170+
)
165171

172+
self.root = [
173+
jetstream_data_point
174+
for jetstream_data_point in self.root
175+
if jetstream_data_point.metric != Metric.RETENTION
176+
]
166177
self.extend(retention_data)
167178

168179
def append_retention_3_days(self, daily_data):

experimenter/experimenter/jetstream/tests/test_jetstream_data.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,75 @@ def test_append_retention_3_days_extracts_data(self):
6161

6262
self.assertIn(retention, data)
6363

64+
def test_append_retention_data_ignores_week_1_only_data(self):
65+
week_1_retention = JetstreamDataPoint(
66+
metric=Metric.RETENTION,
67+
statistic=Statistic.BINOMIAL,
68+
branch="control",
69+
point=0.65,
70+
segment=Segment.ALL,
71+
window_index="1",
72+
)
73+
74+
data = JetstreamData([])
75+
data.append_retention_data([week_1_retention])
76+
77+
self.assertNotIn(week_1_retention, data)
78+
79+
def test_append_retention_data_uses_week_2_data(self):
80+
week_2_retention = JetstreamDataPoint(
81+
metric=Metric.RETENTION,
82+
statistic=Statistic.BINOMIAL,
83+
branch="control",
84+
point=0.65,
85+
segment=Segment.ALL,
86+
window_index="2",
87+
)
88+
89+
data = JetstreamData([])
90+
data.append_retention_data([week_2_retention])
91+
92+
self.assertIn(week_2_retention, data)
93+
94+
def test_replace_retention_replaces_existing_entries(self):
95+
existing_retention_1 = JetstreamDataPoint(
96+
metric=Metric.RETENTION,
97+
statistic=Statistic.BINOMIAL,
98+
branch="control",
99+
point=0.5,
100+
segment=Segment.ALL,
101+
window_index="1",
102+
)
103+
kept_retention = JetstreamDataPoint(
104+
metric=Metric.RETENTION_3_DAYS,
105+
statistic=Statistic.BINOMIAL,
106+
branch="control",
107+
point=0.25,
108+
segment=Segment.ALL,
109+
window_index="2",
110+
)
111+
existing_retention_2 = JetstreamDataPoint(
112+
metric=Metric.RETENTION_3_DAYS,
113+
statistic=Statistic.BINOMIAL,
114+
branch="control",
115+
point=0.25,
116+
segment=Segment.ALL,
117+
window_index="3",
118+
)
119+
120+
data = JetstreamData(
121+
[
122+
existing_retention_1,
123+
kept_retention,
124+
existing_retention_2,
125+
]
126+
)
127+
data.replace_retention_3_days(data)
128+
129+
self.assertNotIn(existing_retention_1, data)
130+
self.assertNotIn(existing_retention_2, data)
131+
self.assertIn(kept_retention, data)
132+
64133
def test_replace_retention_3_days_replaces_existing_entries(self):
65134
existing_retention_1 = JetstreamDataPoint(
66135
metric=Metric.RETENTION_3_DAYS,

0 commit comments

Comments
 (0)