edx-enterprise-data 8.7.0__py3-none-any.whl → 8.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,120 @@
1
+ """Advance Analytics for Leaderboard"""
2
+ from datetime import datetime
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ from edx_rbac.decorators import permission_required
7
+ from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
8
+ from rest_framework.views import APIView
9
+
10
+ from django.http import StreamingHttpResponse
11
+
12
+ from enterprise_data.admin_analytics.constants import ResponseType
13
+ from enterprise_data.admin_analytics.utils import (
14
+ fetch_and_cache_engagements_data,
15
+ fetch_and_cache_enrollments_data,
16
+ fetch_engagements_cache_expiry_timestamp,
17
+ fetch_enrollments_cache_expiry_timestamp,
18
+ )
19
+ from enterprise_data.api.v1.paginators import AdvanceAnalyticsPagination
20
+ from enterprise_data.api.v1.serializers import AdvanceAnalyticsQueryParamSerializer
21
+ from enterprise_data.renderers import LeaderboardCSVRenderer
22
+ from enterprise_data.utils import date_filter
23
+
24
+
25
+ class AdvanceAnalyticsLeaderboardView(APIView):
26
+ """
27
+ API for getting the advance analytics leaderboard data.
28
+ """
29
+ authentication_classes = (JwtAuthentication,)
30
+ pagination_class = AdvanceAnalyticsPagination
31
+ http_method_names = ['get']
32
+
33
+ @permission_required('can_access_enterprise', fn=lambda request, enterprise_uuid: enterprise_uuid)
34
+ def get(self, request, enterprise_uuid):
35
+ """Get leaderboard data"""
36
+ serializer = AdvanceAnalyticsQueryParamSerializer(data=request.GET)
37
+ serializer.is_valid(raise_exception=True)
38
+
39
+ enrollments_cache_expiry = fetch_enrollments_cache_expiry_timestamp()
40
+ enrollments_df = fetch_and_cache_enrollments_data(enterprise_uuid, enrollments_cache_expiry)
41
+
42
+ engagements_cache_expiry = fetch_engagements_cache_expiry_timestamp()
43
+ engagements_df = fetch_and_cache_engagements_data(enterprise_uuid, engagements_cache_expiry)
44
+
45
+ start_date = serializer.data.get('start_date', enrollments_df.enterprise_enrollment_date.min())
46
+ end_date = serializer.data.get('end_date', datetime.now())
47
+ response_type = serializer.data.get('response_type', ResponseType.JSON.value)
48
+
49
+ # only include learners who have passed the course
50
+ enrollments_df = enrollments_df[enrollments_df["has_passed"] == 1]
51
+
52
+ # filter enrollments by date
53
+ enrollments_df = date_filter(start_date, end_date, enrollments_df, "passed_date")
54
+
55
+ completions = enrollments_df.groupby(["email"]).size().reset_index()
56
+ completions.columns = ["email", "course_completions"]
57
+
58
+ # filter engagements by date
59
+ engagements_df = date_filter(start_date, end_date, engagements_df, "activity_date")
60
+
61
+ engage = (
62
+ engagements_df.groupby(["email"])
63
+ .agg({"is_engaged": ["sum"], "learning_time_seconds": ["sum"]})
64
+ .reset_index()
65
+ )
66
+ engage.columns = ["email", "daily_sessions", "learning_time_seconds"]
67
+ engage["learning_time_hours"] = round(
68
+ engage["learning_time_seconds"].astype("float") / 60 / 60, 1
69
+ )
70
+ engage["average_session_length"] = round(
71
+ engage["learning_time_hours"] / engage["daily_sessions"].astype("float"), 1
72
+ )
73
+
74
+ leaderboard_df = engage.merge(completions, on="email", how="left")
75
+ leaderboard_df = leaderboard_df.sort_values(
76
+ by=["learning_time_hours", "daily_sessions", "course_completions"],
77
+ ascending=[False, False, False],
78
+ )
79
+
80
+ # move the aggregated row with email 'null' to the end of the table
81
+ idx = leaderboard_df.index[leaderboard_df['email'] == 'null']
82
+ leaderboard_df.loc[idx, 'email'] = 'learners who have not shared consent'
83
+ leaderboard_df = pd.concat([leaderboard_df.drop(idx), leaderboard_df.loc[idx]])
84
+
85
+ # convert `nan` values to `None` because `nan` is not JSON serializable
86
+ leaderboard_df = leaderboard_df.replace(np.nan, None)
87
+
88
+ if response_type == ResponseType.CSV.value:
89
+ filename = f"""Leaderboard, {start_date} - {end_date}.csv"""
90
+ leaderboard_df = leaderboard_df[
91
+ [
92
+ "email",
93
+ "learning_time_hours",
94
+ "daily_sessions",
95
+ "average_session_length",
96
+ "course_completions",
97
+ ]
98
+ ]
99
+ return StreamingHttpResponse(
100
+ LeaderboardCSVRenderer().render(self._stream_serialized_data(leaderboard_df)),
101
+ content_type="text/csv",
102
+ headers={"Content-Disposition": f'attachment; filename="{filename}"'},
103
+ )
104
+
105
+ paginator = self.pagination_class()
106
+ page = paginator.paginate_queryset(leaderboard_df, request)
107
+ serialized_data = page.data.to_dict(orient='records')
108
+ response = paginator.get_paginated_response(serialized_data)
109
+
110
+ return response
111
+
112
+ def _stream_serialized_data(self, leaderboard_df, chunk_size=50000):
113
+ """
114
+ Stream the serialized data.
115
+ """
116
+ total_rows = leaderboard_df.shape[0]
117
+ for start_index in range(0, total_rows, chunk_size):
118
+ end_index = min(start_index + chunk_size, total_rows)
119
+ chunk = leaderboard_df.iloc[start_index:end_index]
120
+ yield from chunk.to_dict(orient='records')
@@ -18,7 +18,7 @@ from enterprise_data.admin_analytics.completions_utils import (
18
18
  get_top_courses_by_completions,
19
19
  get_top_subjects_by_completions,
20
20
  )
21
- from enterprise_data.admin_analytics.constants import CALCULATION, GRANULARITY
21
+ from enterprise_data.admin_analytics.constants import Calculation, Granularity
22
22
  from enterprise_data.admin_analytics.data_loaders import fetch_max_enrollment_datetime
23
23
  from enterprise_data.admin_analytics.utils import ChartType, fetch_and_cache_enrollments_data
24
24
  from enterprise_data.api.v1 import serializers
@@ -71,8 +71,8 @@ class EnterrpiseAdminCompletionsStatsView(APIView):
71
71
  start_date=start_date,
72
72
  end_date=end_date,
73
73
  enrollments=enrollments.copy(),
74
- date_agg=serializer.data.get('granularity', GRANULARITY.DAILY.value),
75
- calc=serializer.data.get('calculation', CALCULATION.TOTAL.value),
74
+ date_agg=serializer.data.get('granularity', Granularity.DAILY.value),
75
+ calc=serializer.data.get('calculation', Calculation.TOTAL.value),
76
76
  )
77
77
  elif chart_type == ChartType.TOP_COURSES_BY_COMPLETIONS.value:
78
78
  csv_data = get_csv_data_for_top_courses_by_completions(
@@ -91,8 +91,8 @@ class EnterrpiseAdminCompletionsStatsView(APIView):
91
91
  start_date=start_date,
92
92
  end_date=end_date,
93
93
  dff=enrollments.copy(),
94
- date_agg=serializer.data.get('granularity', GRANULARITY.DAILY.value),
95
- calc=serializer.data.get('calculation', CALCULATION.TOTAL.value),
94
+ date_agg=serializer.data.get('granularity', Granularity.DAILY.value),
95
+ calc=serializer.data.get('calculation', Calculation.TOTAL.value),
96
96
  )
97
97
  top_courses_by_completions = get_top_courses_by_completions(
98
98
  start_date=start_date, end_date=end_date, dff=enrollments.copy()
@@ -43,3 +43,17 @@ class IndividualEnrollmentsCSVRenderer(CSVStreamingRenderer):
43
43
  'enroll_type',
44
44
  'enterprise_enrollment_date',
45
45
  ]
46
+
47
+
48
+ class LeaderboardCSVRenderer(CSVStreamingRenderer):
49
+ """
50
+ Custom streaming csv renderer for advance analytics leaderboard data.
51
+ """
52
+
53
+ header = [
54
+ 'email',
55
+ 'learning_time_hours',
56
+ 'daily_sessions',
57
+ 'average_session_length',
58
+ 'course_completions',
59
+ ]
@@ -0,0 +1,501 @@
1
+ """Mock data for enrollments"""
2
+
3
+ import pandas as pd
4
+
5
+ from enterprise_data.admin_analytics.constants import EnrollmentChart
6
+ from enterprise_data.admin_analytics.utils import ChartType
7
+
8
+ ENROLLMENTS = [
9
+ {
10
+ "enterprise_customer_name": "Hill Ltd",
11
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
12
+ "lms_enrollment_id": 1013,
13
+ "user_id": 8907,
14
+ "email": "rebeccanelson@example.com",
15
+ "course_key": "hEmW+tvk03",
16
+ "courserun_key": "course-v1:hEmW+tvk03+1T9889",
17
+ "course_id": "1681",
18
+ "course_subject": "business-management",
19
+ "course_title": "Re-engineered tangible approach",
20
+ "enterprise_enrollment_date": "2021-07-04",
21
+ "lms_enrollment_mode": "verified",
22
+ "enroll_type": "certificate",
23
+ "program_title": "Non-Program",
24
+ "date_certificate_awarded": "2021-08-25",
25
+ "grade_percent": 0.99,
26
+ "cert_awarded": 1,
27
+ "date_certificate_created_raw": "2021-08-25",
28
+ "passed_date_raw": "2021-08-25",
29
+ "passed_date": "2021-08-25",
30
+ "has_passed": 1,
31
+ },
32
+ {
33
+ "enterprise_customer_name": "Hill Ltd",
34
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
35
+ "lms_enrollment_id": 9172,
36
+ "user_id": 8369,
37
+ "email": "taylorjames@example.com",
38
+ "course_key": "hEmW+tvk03",
39
+ "courserun_key": "course-v1:hEmW+tvk03+1T9889",
40
+ "course_id": "1681",
41
+ "course_subject": "business-management",
42
+ "course_title": "Re-engineered tangible approach",
43
+ "enterprise_enrollment_date": "2021-07-03",
44
+ "lms_enrollment_mode": "verified",
45
+ "enroll_type": "certificate",
46
+ "program_title": "Non-Program",
47
+ "date_certificate_awarded": "2021-09-01",
48
+ "grade_percent": 0.93,
49
+ "cert_awarded": 1,
50
+ "date_certificate_created_raw": "2021-09-01",
51
+ "passed_date_raw": "2021-09-01",
52
+ "passed_date": "2021-09-01",
53
+ "has_passed": 1,
54
+ },
55
+ {
56
+ "enterprise_customer_name": "Hill Ltd",
57
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
58
+ "lms_enrollment_id": 9552,
59
+ "user_id": 8719,
60
+ "email": "ssmith@example.com",
61
+ "course_key": "qZJC+KFX86",
62
+ "courserun_key": "course-v1:qZJC+KFX86+1T8918",
63
+ "course_id": "1725",
64
+ "course_subject": "medicine",
65
+ "course_title": "Secured static capability",
66
+ "enterprise_enrollment_date": "2021-05-11",
67
+ "lms_enrollment_mode": "verified",
68
+ "enroll_type": "certificate",
69
+ "program_title": "Non-Program",
70
+ "date_certificate_awarded": None,
71
+ "grade_percent": 0.0,
72
+ "cert_awarded": 0,
73
+ "date_certificate_created_raw": None,
74
+ "passed_date_raw": None,
75
+ "passed_date": None,
76
+ "has_passed": 0,
77
+ },
78
+ {
79
+ "enterprise_customer_name": "Hill Ltd",
80
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
81
+ "lms_enrollment_id": 3436,
82
+ "user_id": 3125,
83
+ "email": "kathleenmartin@example.com",
84
+ "course_key": "QWXx+Jqz64",
85
+ "courserun_key": "course-v1:QWXx+Jqz64+1T9449",
86
+ "course_id": "4878",
87
+ "course_subject": "social-sciences",
88
+ "course_title": "Horizontal solution-oriented hub",
89
+ "enterprise_enrollment_date": "2020-04-03",
90
+ "lms_enrollment_mode": "verified",
91
+ "enroll_type": "certificate",
92
+ "program_title": "Non-Program",
93
+ "date_certificate_awarded": None,
94
+ "grade_percent": 0.0,
95
+ "cert_awarded": 0,
96
+ "date_certificate_created_raw": None,
97
+ "passed_date_raw": None,
98
+ "passed_date": None,
99
+ "has_passed": 0,
100
+ },
101
+ {
102
+ "enterprise_customer_name": "Hill Ltd",
103
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
104
+ "lms_enrollment_id": 5934,
105
+ "user_id": 4853,
106
+ "email": "amber79@example.com",
107
+ "course_key": "NOGk+UVD31",
108
+ "courserun_key": "course-v1:NOGk+UVD31+1T4956",
109
+ "course_id": "4141",
110
+ "course_subject": "communication",
111
+ "course_title": "Streamlined zero-defect attitude",
112
+ "enterprise_enrollment_date": "2020-04-08",
113
+ "lms_enrollment_mode": "verified",
114
+ "enroll_type": "certificate",
115
+ "program_title": "Non-Program",
116
+ "date_certificate_awarded": None,
117
+ "grade_percent": 0.0,
118
+ "cert_awarded": 0,
119
+ "date_certificate_created_raw": None,
120
+ "passed_date_raw": None,
121
+ "passed_date": None,
122
+ "has_passed": 0,
123
+ },
124
+ ]
125
+
126
+ ENGAGEMENTS = [
127
+ {
128
+ "user_id": 2340,
129
+ "email": "padillamichelle@example.org",
130
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
131
+ "course_key": "luGg+KNt30",
132
+ "enroll_type": "certificate",
133
+ "activity_date": "2021-08-05",
134
+ "course_title": "Synergized reciprocal encoding",
135
+ "course_subject": "business-management",
136
+ "is_engaged": 1,
137
+ "is_engaged_video": 1,
138
+ "is_engaged_forum": 0,
139
+ "is_engaged_problem": 1,
140
+ "is_active": 1,
141
+ "learning_time_seconds": 3724,
142
+ },
143
+ {
144
+ "user_id": 1869,
145
+ "email": "yallison@example.org",
146
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
147
+ "course_key": "luGg+KNt30",
148
+ "enroll_type": "certificate",
149
+ "activity_date": "2021-07-27",
150
+ "course_title": "Synergized reciprocal encoding",
151
+ "course_subject": "business-management",
152
+ "is_engaged": 1,
153
+ "is_engaged_video": 1,
154
+ "is_engaged_forum": 0,
155
+ "is_engaged_problem": 1,
156
+ "is_active": 1,
157
+ "learning_time_seconds": 4335,
158
+ },
159
+ {
160
+ "user_id": 6962,
161
+ "email": "weaverpatricia@example.net",
162
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
163
+ "course_key": "luGg+KNt30",
164
+ "enroll_type": "certificate",
165
+ "activity_date": "2021-08-05",
166
+ "course_title": "Synergized reciprocal encoding",
167
+ "course_subject": "business-management",
168
+ "is_engaged": 1,
169
+ "is_engaged_video": 1,
170
+ "is_engaged_forum": 0,
171
+ "is_engaged_problem": 1,
172
+ "is_active": 1,
173
+ "learning_time_seconds": 9441,
174
+ },
175
+ {
176
+ "user_id": 6798,
177
+ "email": "seth57@example.org",
178
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
179
+ "course_key": "luGg+KNt30",
180
+ "enroll_type": "certificate",
181
+ "activity_date": "2021-08-21",
182
+ "course_title": "Synergized reciprocal encoding",
183
+ "course_subject": "business-management",
184
+ "is_engaged": 1,
185
+ "is_engaged_video": 1,
186
+ "is_engaged_forum": 1,
187
+ "is_engaged_problem": 1,
188
+ "is_active": 1,
189
+ "learning_time_seconds": 9898,
190
+ },
191
+ {
192
+ "user_id": 8530,
193
+ "email": "mackwilliam@example.com",
194
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
195
+ "course_key": "luGg+KNt30",
196
+ "enroll_type": "certificate",
197
+ "activity_date": "2021-10-24",
198
+ "course_title": "Synergized reciprocal encoding",
199
+ "course_subject": "business-management",
200
+ "is_engaged": 0,
201
+ "is_engaged_video": 0,
202
+ "is_engaged_forum": 0,
203
+ "is_engaged_problem": 0,
204
+ "is_active": 1,
205
+ "learning_time_seconds": 0,
206
+ },
207
+ {
208
+ "user_id": 7584,
209
+ "email": "graceperez@example.com",
210
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
211
+ "course_key": "luGg+KNt30",
212
+ "enroll_type": "certificate",
213
+ "activity_date": "2022-05-17",
214
+ "course_title": "Synergized reciprocal encoding",
215
+ "course_subject": "business-management",
216
+ "is_engaged": 0,
217
+ "is_engaged_video": 0,
218
+ "is_engaged_forum": 0,
219
+ "is_engaged_problem": 0,
220
+ "is_active": 1,
221
+ "learning_time_seconds": 21,
222
+ },
223
+ {
224
+ "user_id": 68,
225
+ "email": "yferguson@example.net",
226
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
227
+ "course_key": "luGg+KNt30",
228
+ "enroll_type": "certificate",
229
+ "activity_date": "2021-09-02",
230
+ "course_title": "Synergized reciprocal encoding",
231
+ "course_subject": "business-management",
232
+ "is_engaged": 1,
233
+ "is_engaged_video": 1,
234
+ "is_engaged_forum": 0,
235
+ "is_engaged_problem": 1,
236
+ "is_active": 1,
237
+ "learning_time_seconds": 4747,
238
+ },
239
+ {
240
+ "user_id": 4278,
241
+ "email": "caseyjohnny@example.com",
242
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
243
+ "course_key": "Kcpr+XoR30",
244
+ "enroll_type": "certificate",
245
+ "activity_date": "2022-05-20",
246
+ "course_title": "Assimilated even-keeled focus group",
247
+ "course_subject": "engineering",
248
+ "is_engaged": 0,
249
+ "is_engaged_video": 0,
250
+ "is_engaged_forum": 0,
251
+ "is_engaged_problem": 0,
252
+ "is_active": 1,
253
+ "learning_time_seconds": 0,
254
+ },
255
+ {
256
+ "user_id": 8726,
257
+ "email": "webertodd@example.com",
258
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
259
+ "course_key": "luGg+KNt30",
260
+ "enroll_type": "certificate",
261
+ "activity_date": "2021-09-21",
262
+ "course_title": "Synergized reciprocal encoding",
263
+ "course_subject": "business-management",
264
+ "is_engaged": 1,
265
+ "is_engaged_video": 1,
266
+ "is_engaged_forum": 1,
267
+ "is_engaged_problem": 1,
268
+ "is_active": 1,
269
+ "learning_time_seconds": 5285,
270
+ },
271
+ {
272
+ "user_id": 282,
273
+ "email": "crystal86@example.net",
274
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
275
+ "course_key": "luGg+KNt30",
276
+ "enroll_type": "certificate",
277
+ "activity_date": "2021-07-11",
278
+ "course_title": "Synergized reciprocal encoding",
279
+ "course_subject": "business-management",
280
+ "is_engaged": 0,
281
+ "is_engaged_video": 0,
282
+ "is_engaged_forum": 0,
283
+ "is_engaged_problem": 0,
284
+ "is_active": 1,
285
+ "learning_time_seconds": 0,
286
+ },
287
+ {
288
+ "user_id": 2731,
289
+ "email": "paul77@example.org",
290
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
291
+ "course_key": "luGg+KNt30",
292
+ "enroll_type": "certificate",
293
+ "activity_date": "2021-07-26",
294
+ "course_title": "Synergized reciprocal encoding",
295
+ "course_subject": "business-management",
296
+ "is_engaged": 1,
297
+ "is_engaged_video": 1,
298
+ "is_engaged_forum": 1,
299
+ "is_engaged_problem": 1,
300
+ "is_active": 1,
301
+ "learning_time_seconds": 15753,
302
+ },
303
+ {
304
+ "user_id": 4038,
305
+ "email": "samanthaclarke@example.org",
306
+ "enterprise_customer_uuid": "33ce656295e04ecfa2a77d407eb96f69",
307
+ "course_key": "luGg+KNt30",
308
+ "enroll_type": "certificate",
309
+ "activity_date": "2021-07-19",
310
+ "course_title": "Synergized reciprocal encoding",
311
+ "course_subject": "business-management",
312
+ "is_engaged": 0,
313
+ "is_engaged_video": 0,
314
+ "is_engaged_forum": 0,
315
+ "is_engaged_problem": 0,
316
+ "is_active": 1,
317
+ "learning_time_seconds": 29,
318
+ }
319
+ ]
320
+
321
+ ENROLLMENT_STATS_CSVS = {
322
+ EnrollmentChart.ENROLLMENTS_OVER_TIME.value: (
323
+ b'enterprise_enrollment_date,certificate\n'
324
+ b'2020-04-03,1\n'
325
+ b'2020-04-08,1\n'
326
+ b'2021-05-11,1\n'
327
+ b'2021-07-03,1\n'
328
+ b'2021-07-04,1\n'
329
+ ),
330
+ EnrollmentChart.TOP_COURSES_BY_ENROLLMENTS.value: (
331
+ b'course_key,course_title,certificate\n'
332
+ b'NOGk+UVD31,Streamlined zero-defect attitude,1\n'
333
+ b'QWXx+Jqz64,Horizontal solution-oriented hub,1\n'
334
+ b'hEmW+tvk03,Re-engineered tangible approach,2\n'
335
+ b'qZJC+KFX86,Secured static capability,1\n'
336
+ ),
337
+ EnrollmentChart.TOP_SUBJECTS_BY_ENROLLMENTS.value: (
338
+ b'course_subject,certificate\nbusiness-management,2\ncommunication,1\nmedicine,1\nsocial-sciences,1\n'
339
+ )
340
+ }
341
+
342
+
343
+ def enrollments_dataframe():
344
+ """Return a DataFrame of enrollments."""
345
+ enrollments = pd.DataFrame(ENROLLMENTS)
346
+
347
+ enrollments['enterprise_enrollment_date'] = enrollments['enterprise_enrollment_date'].astype('datetime64[ns]')
348
+ enrollments['date_certificate_awarded'] = enrollments['date_certificate_awarded'].astype('datetime64[ns]')
349
+ enrollments['date_certificate_created_raw'] = enrollments['date_certificate_created_raw'].astype('datetime64[ns]')
350
+ enrollments['passed_date_raw'] = enrollments['passed_date_raw'].astype('datetime64[ns]')
351
+ enrollments['passed_date'] = enrollments['passed_date'].astype('datetime64[ns]')
352
+
353
+ return enrollments
354
+
355
+
356
+ def engagements_dataframe():
357
+ """Return a DataFrame of engagements."""
358
+ engagements = pd.DataFrame(ENGAGEMENTS)
359
+ engagements['activity_date'] = engagements['activity_date'].astype('datetime64[ns]')
360
+ return engagements
361
+
362
+
363
+ def enrollments_csv_content():
364
+ """Return the CSV content of enrollments."""
365
+ return (
366
+ b'email,course_title,course_subject,enroll_type,enterprise_enrollment_date\r\n'
367
+ b'rebeccanelson@example.com,Re-engineered tangible approach,business-management,certificate,2021-07-04\r\n'
368
+ b'taylorjames@example.com,Re-engineered tangible approach,business-management,certificate,2021-07-03\r\n'
369
+ b'ssmith@example.com,Secured static capability,medicine,certificate,2021-05-11\r\n'
370
+ b'amber79@example.com,Streamlined zero-defect attitude,communication,certificate,2020-04-08\r\n'
371
+ b'kathleenmartin@example.com,Horizontal solution-oriented hub,social-sciences,certificate,2020-04-03\r\n'
372
+ )
373
+
374
+
375
+ def leaderboard_csv_content():
376
+ """Return the CSV content of leaderboard."""
377
+ return (
378
+ b'email,learning_time_hours,daily_sessions,average_session_length,course_completions\r\n'
379
+ b'paul77@example.org,4.4,1,4.4,\r\nseth57@example.org,2.7,1,2.7,\r\n'
380
+ b'weaverpatricia@example.net,2.6,1,2.6,\r\nwebertodd@example.com,1.5,1,1.5,\r\n'
381
+ b'yferguson@example.net,1.3,1,1.3,\r\nyallison@example.org,1.2,1,1.2,\r\n'
382
+ b'padillamichelle@example.org,1.0,1,1.0,\r\ncaseyjohnny@example.com,0.0,0,,\r\n'
383
+ b'crystal86@example.net,0.0,0,,\r\ngraceperez@example.com,0.0,0,,\r\n'
384
+ b'mackwilliam@example.com,0.0,0,,\r\nsamanthaclarke@example.org,0.0,0,,\r\n'
385
+ )
386
+
387
+
388
+ LEADERBOARD_RESPONSE = [
389
+ {
390
+ "email": "paul77@example.org",
391
+ "daily_sessions": 1,
392
+ "learning_time_seconds": 15753,
393
+ "learning_time_hours": 4.4,
394
+ "average_session_length": 4.4,
395
+ "course_completions": None,
396
+ },
397
+ {
398
+ "email": "seth57@example.org",
399
+ "daily_sessions": 1,
400
+ "learning_time_seconds": 9898,
401
+ "learning_time_hours": 2.7,
402
+ "average_session_length": 2.7,
403
+ "course_completions": None,
404
+ },
405
+ {
406
+ "email": "weaverpatricia@example.net",
407
+ "daily_sessions": 1,
408
+ "learning_time_seconds": 9441,
409
+ "learning_time_hours": 2.6,
410
+ "average_session_length": 2.6,
411
+ "course_completions": None,
412
+ },
413
+ {
414
+ "email": "webertodd@example.com",
415
+ "daily_sessions": 1,
416
+ "learning_time_seconds": 5285,
417
+ "learning_time_hours": 1.5,
418
+ "average_session_length": 1.5,
419
+ "course_completions": None,
420
+ },
421
+ {
422
+ "email": "yferguson@example.net",
423
+ "daily_sessions": 1,
424
+ "learning_time_seconds": 4747,
425
+ "learning_time_hours": 1.3,
426
+ "average_session_length": 1.3,
427
+ "course_completions": None,
428
+ },
429
+ {
430
+ "email": "yallison@example.org",
431
+ "daily_sessions": 1,
432
+ "learning_time_seconds": 4335,
433
+ "learning_time_hours": 1.2,
434
+ "average_session_length": 1.2,
435
+ "course_completions": None,
436
+ },
437
+ {
438
+ "email": "padillamichelle@example.org",
439
+ "daily_sessions": 1,
440
+ "learning_time_seconds": 3724,
441
+ "learning_time_hours": 1.0,
442
+ "average_session_length": 1.0,
443
+ "course_completions": None,
444
+ },
445
+ {
446
+ "email": "caseyjohnny@example.com",
447
+ "daily_sessions": 0,
448
+ "learning_time_seconds": 0,
449
+ "learning_time_hours": 0.0,
450
+ "average_session_length": None,
451
+ "course_completions": None,
452
+ },
453
+ {
454
+ "email": "crystal86@example.net",
455
+ "daily_sessions": 0,
456
+ "learning_time_seconds": 0,
457
+ "learning_time_hours": 0.0,
458
+ "average_session_length": None,
459
+ "course_completions": None,
460
+ },
461
+ {
462
+ "email": "graceperez@example.com",
463
+ "daily_sessions": 0,
464
+ "learning_time_seconds": 21,
465
+ "learning_time_hours": 0.0,
466
+ "average_session_length": None,
467
+ "course_completions": None,
468
+ },
469
+ {
470
+ "email": "mackwilliam@example.com",
471
+ "daily_sessions": 0,
472
+ "learning_time_seconds": 0,
473
+ "learning_time_hours": 0.0,
474
+ "average_session_length": None,
475
+ "course_completions": None,
476
+ },
477
+ {
478
+ "email": "samanthaclarke@example.org",
479
+ "daily_sessions": 0,
480
+ "learning_time_seconds": 29,
481
+ "learning_time_hours": 0.0,
482
+ "average_session_length": None,
483
+ "course_completions": None,
484
+ },
485
+ ]
486
+
487
+ COMPLETIONS_STATS_CSVS = {
488
+ ChartType.COMPLETIONS_OVER_TIME.value: (
489
+ b'passed_date,certificate\n'
490
+ b'2021-08-25,1\n'
491
+ b'2021-09-01,2\n'
492
+ ),
493
+ ChartType.TOP_COURSES_BY_COMPLETIONS.value: (
494
+ b'course_key,course_title,certificate\n'
495
+ b'hEmW+tvk03,Re-engineered tangible approach,2\n'
496
+ ),
497
+ ChartType.TOP_SUBJECTS_BY_COMPLETIONS.value: (
498
+ b'course_subject,certificate\n'
499
+ b'business-management,2\n'
500
+ )
501
+ }