Skip to content

Commit

Permalink
Merge c05732b into 1ba61f2
Browse files Browse the repository at this point in the history
  • Loading branch information
ajpal committed Jun 21, 2016
2 parents 1ba61f2 + c05732b commit cf8f13b
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 86 deletions.
8 changes: 0 additions & 8 deletions analytics_data_api/constants/engagement_entity_types.py

This file was deleted.

31 changes: 21 additions & 10 deletions analytics_data_api/constants/engagement_events.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
from analytics_data_api.constants import engagement_entity_types

ATTEMPTED = 'attempted'
ATTEMPTS_PER_COMPLETED = 'attempts_per_completed'
COMPLETED = 'completed'
CONTRIBUTED = 'contributed'
VIEWED = 'viewed'

# map entity types to events
EVENTS = {
engagement_entity_types.DISCUSSION: [CONTRIBUTED],
engagement_entity_types.PROBLEM: [ATTEMPTED, ATTEMPTS_PER_COMPLETED, COMPLETED],
engagement_entity_types.PROBLEMS: [ATTEMPTED, COMPLETED],
engagement_entity_types.VIDEO: [VIEWED],
engagement_entity_types.VIDEOS: [VIEWED],
}
DISCUSSION = 'discussion'
PROBLEM = 'problem'
VIDEO = 'video'
PROBLEMS = 'problems'
VIDEOS = 'videos'

INDIVIDUAL_EVENTS = [
'problem_attempts_per_completed',
'problem_attempted',
'problem_completed',
'discussion_contributed',
'video_viewed'
]

EVENTS = [
'problem_attempts_per_completed',
'problems_attempted',
'problems_completed',
'discussion_contributions',
'videos_viewed'
]
8 changes: 6 additions & 2 deletions analytics_data_api/constants/engagement_types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from analytics_data_api.constants.engagement_entity_types import DISCUSSION, PROBLEM, VIDEO
from analytics_data_api.constants.engagement_events import ATTEMPTED, COMPLETED, CONTRIBUTED, VIEWED
from analytics_data_api.constants.engagement_events import (ATTEMPTED, ATTEMPTS_PER_COMPLETED, COMPLETED,
CONTRIBUTED, DISCUSSION, PROBLEM, VIDEO, VIEWED)


class EngagementType(object):
Expand All @@ -12,6 +12,7 @@ class EngagementType(object):
# Defines the current canonical set of engagement types used in the Learner
# Analytics API.
ALL_TYPES = (
'problem_attempts_per_completed',
'problems_attempted',
'problems_completed',
'videos_viewed',
Expand All @@ -30,6 +31,9 @@ def __init__(self, entity_type, event_type):
if event_type == ATTEMPTED:
self.name = 'problems_attempted'
self.is_counted_by_entity = True
if event_type == ATTEMPTS_PER_COMPLETED:
self.name = 'problem_attempts_per_completed'
self.is_counted_by_entity = True
if event_type == COMPLETED:
self.name = 'problems_completed'
self.is_counted_by_entity = True
Expand Down
45 changes: 21 additions & 24 deletions analytics_data_api/management/commands/generate_fake_course_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from analytics_data_api.v0 import models
from analytics_data_api.constants import engagement_entity_types, engagement_events
from analytics_data_api.constants import engagement_events

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -194,36 +194,33 @@ def generate_learner_engagement_data(self, course_id, username, start_date, end_
current = start_date
while current < end_date:
current = current + datetime.timedelta(days=1)
for entity_type in engagement_entity_types.INDIVIDUAL_TYPES:
for event in engagement_events.EVENTS[entity_type]:
num_events = random.randint(0, max_value)
if num_events:
for _ in xrange(num_events):
count = random.randint(0, max_value / 20)
entity_id = 'an-id-{}-{}'.format(entity_type, event)
models.ModuleEngagement.objects.create(
course_id=course_id, username=username, date=current,
entity_type=entity_type, entity_id=entity_id, event=event, count=count)
for metric in engagement_events.INDIVIDUAL_EVENTS:
num_events = random.randint(0, max_value)
if num_events:
for _ in xrange(num_events):
count = random.randint(0, max_value / 20)
entity_type = metric.split('_', 1)[0]
event = metric.split('_', 1)[1]
entity_id = 'an-id-{}-{}'.format(entity_type, event)
models.ModuleEngagement.objects.create(
course_id=course_id, username=username, date=current,
entity_type=entity_type, entity_id=entity_id, event=event, count=count)
logger.info("Done!")

def generate_learner_engagement_range_data(self, course_id, start_date, end_date, max_value=100):
logger.info("Deleting engagement range data...")
models.ModuleEngagementMetricRanges.objects.all().delete()

logger.info("Generating engagement range data...")
for entity_type in engagement_entity_types.AGGREGATE_TYPES:
for event in engagement_events.EVENTS[entity_type]:
metric = '{0}_{1}'.format(entity_type, event)

low_ceil = random.random() * max_value * 0.5
models.ModuleEngagementMetricRanges.objects.create(
course_id=course_id, start_date=start_date, end_date=end_date, metric=metric,
range_type='low', low_value=0, high_value=low_ceil)

high_floor = random.random() * max_value * 0.5 + low_ceil
models.ModuleEngagementMetricRanges.objects.create(
course_id=course_id, start_date=start_date, end_date=end_date, metric=metric,
range_type='high', low_value=high_floor, high_value=max_value)
for event in engagement_events.EVENTS:
low_ceil = random.random() * max_value * 0.5
models.ModuleEngagementMetricRanges.objects.create(
course_id=course_id, start_date=start_date, end_date=end_date, metric=event,
range_type='low', low_value=0, high_value=low_ceil)
high_floor = random.random() * max_value * 0.5 + low_ceil
models.ModuleEngagementMetricRanges.objects.create(
course_id=course_id, start_date=start_date, end_date=end_date, metric=event,
range_type='high', low_value=high_floor, high_value=max_value)

def generate_tags_distribution_data(self, course_id):
logger.info("Deleting existed tags distribution data...")
Expand Down
30 changes: 11 additions & 19 deletions analytics_data_api/v0/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from rest_framework import pagination, serializers

from analytics_data_api.constants import (
engagement_entity_types,
engagement_events,
enrollment_modes,
genders,
Expand Down Expand Up @@ -458,23 +457,16 @@ def get_engagement_ranges(self, obj):
'date_range': DateRangeSerializer(query_set[0] if len(query_set) else None).data
}

# go through each entity and event type combination and fill in the ranges
for entity_type in engagement_entity_types.AGGREGATE_TYPES:
for event in engagement_events.EVENTS[entity_type]:
metric = '{0}_{1}'.format(entity_type, event)
# It's assumed that there may be any combination of low, normal,
# and high ranges in the database for the given course. Some
# edge cases result from a lack of available data; in such
# cases, only some ranges may be returned.
low_range_queryset = query_set.filter(metric=metric, range_type='low')
normal_range_queryset = query_set.filter(metric=metric, range_type='normal')
high_range_queryset = query_set.filter(metric=metric, range_type='high')
engagement_ranges.update({
metric: EnagementRangeMetricSerializer({
'low_range': low_range_queryset[0] if len(low_range_queryset) else None,
'normal_range': normal_range_queryset[0] if len(normal_range_queryset) else None,
'high_range': high_range_queryset[0] if len(high_range_queryset) else None,
}).data
})
for metric in engagement_events.EVENTS:
low_range_queryset = query_set.filter(metric=metric, range_type='low')
normal_range_queryset = query_set.filter(metric=metric, range_type='normal')
high_range_queryset = query_set.filter(metric=metric, range_type='high')
engagement_ranges.update({
metric: EnagementRangeMetricSerializer({
'low_range': low_range_queryset[0] if len(low_range_queryset) else None,
'normal_range': normal_range_queryset[0] if len(normal_range_queryset) else None,
'high_range': high_range_queryset[0] if len(high_range_queryset) else None,
}).data
})

return engagement_ranges
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from rest_framework import status

from analyticsdataserver.tests import TestCaseWithAuthentication
from analytics_data_api.constants.engagement_entity_types import DISCUSSION, PROBLEM, VIDEO
from analytics_data_api.constants.engagement_events import ATTEMPTED, COMPLETED, CONTRIBUTED, VIEWED
from analytics_data_api.constants.engagement_events import (ATTEMPTED, COMPLETED, CONTRIBUTED, DISCUSSION,
PROBLEM, VIDEO, VIEWED)
from analytics_data_api.v0 import models
from analytics_data_api.v0.tests.views import DemoCourseMixin, VerifyCourseIdMixin

Expand Down
29 changes: 8 additions & 21 deletions analytics_data_api/v0/tests/views/test_learners.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from django.core import management

from analyticsdataserver.tests import TestCaseWithAuthentication
from analytics_data_api.constants import engagement_entity_types, engagement_events
from analytics_data_api.constants import engagement_events
from analytics_data_api.v0.models import ModuleEngagementMetricRanges
from analytics_data_api.v0.tests.views import DemoCourseMixin, VerifyCourseIdMixin

Expand Down Expand Up @@ -571,19 +571,10 @@ def empty_engagement_ranges(self):
empty_range = {
range_type: None for range_type in ['below_average', 'average', 'above_average']
}
for metric in self.engagement_metrics:
for metric in engagement_events.EVENTS:
empty_engagement_ranges['engagement_ranges'][metric] = copy.deepcopy(empty_range)
return empty_engagement_ranges

@property
def engagement_metrics(self):
""" Convenience method for getting the metric types. """
metrics = []
for entity_type in engagement_entity_types.AGGREGATE_TYPES:
for event in engagement_events.EVENTS[entity_type]:
metrics.append('{0}_{1}'.format(entity_type, event))
return metrics

def test_no_engagement_ranges(self):
response = self._get(self.course_id)
self.assertEqual(response.status_code, 200)
Expand Down Expand Up @@ -627,7 +618,7 @@ def _get_full_engagement_ranges(self):
}

max_value = 1000.0
for metric_type in self.engagement_metrics:
for metric_type in engagement_events.EVENTS:
low_ceil = 100.5
G(ModuleEngagementMetricRanges, course_id=self.course_id, start_date=start_date, end_date=end_date,
metric=metric_type, range_type='low', low_value=0, high_value=low_ceil)
Expand All @@ -649,12 +640,8 @@ def test_engagement_ranges_only(self):
self.assertDictContainsSubset(expected, json.loads(response.content))

def test_engagement_ranges_fields(self):
actual_entity_types = engagement_entity_types.INDIVIDUAL_TYPES
expected_entity_types = ['discussion', 'problem', 'video']
self.assertEqual(actual_entity_types, expected_entity_types)
actual_events = []
for entity_type in actual_entity_types:
for event in engagement_events.EVENTS[entity_type]:
actual_events.append(event)
expected_events = ['contributed', 'attempted', 'attempts_per_completed', 'completed', 'viewed']
self.assertEqual(actual_events, expected_events)
expected_events = engagement_events.EVENTS
response = json.loads(self._get(self.course_id).content)
self.assertTrue('engagement_ranges' in response)
for event in expected_events:
self.assertTrue(event in response['engagement_ranges'])

0 comments on commit cf8f13b

Please sign in to comment.