Ejemplo n.º 1
0
    def parse_activity_scores(self, activity_attempt):
        '''
           Processes activity scores recieved from the mapper.

           This is called in the mapper callback function.  Each time a student attempts
           a GCB question or a Quizly exercise, a tag-assessment Event is created.
           This processes such events to extract the number of attempts the student
           made and the answers.

           Events are time-stamped and recorded by user_id. They include the instance_id
           of the Component that triggered the Event.  Both GCB questions and Quizly
           exercises have an instance_id.

           However, Quizly exercises don't have question_id and need special processing.

           Use the Dashboard to see what the data looks like for Events:
           https://console.cloud.google.com/datastore/entities/query?
               project=ram8647&ns=ns_mobileCSP&kind=EventEntity
        '''

        if activity_attempt.source == 'tag-assessment':
            data = transforms.loads(activity_attempt.data)
            instance_id = data['instanceid']
            if GLOBAL_DEBUG:
                logging.debug('***********RAM************** data[instanceid] = ' + instance_id)
            timestamp = int(
                (activity_attempt.recorded_on - datetime.datetime(1970, 1, 1)).total_seconds())

            # Get information about the course's questions (doesn't include Quizly exercises yet)
            questions = self.params['questions_by_usage_id']
            valid_question_ids = self.params['valid_question_ids']
            assessment_weights = self.params['assessment_weights']
            group_to_questions = self.params['group_to_questions']

            student = Student.get_by_user_id(activity_attempt.user_id)

            #  Get this student's answers so far
            student_answers = self.activity_scores.get(student.email, {})
            if GLOBAL_DEBUG:
                logging.debug('***RAM*** student answers = ' + str(student_answers))

            answers = event_transforms.unpack_check_answers(            # No Quizly answers in here
                data, questions, valid_question_ids, assessment_weights,
                group_to_questions, timestamp)

            # Add the score to right lesson
            # NOTE: This was throwing an exception on Quizly exercises.  Shouldn't happen now
            try: 
                #  If the event is tag-assessment and has no quid, it's a Quizly exercise
                if not 'quid' in data:
                    self.parse_quizly_scores(data, instance_id, timestamp, student, student_answers)
                else:
                    self.parse_question_scores(instance_id, questions, student_answers, answers, student, timestamp)
            except Exception as e:
                logging.error('***********RAM************** bad instance_id: %s %s\n%s', str(instance_id), e, traceback.format_exc())
        if GLOBAL_DEBUG:       
            logging.debug('***RAM*** activity_scores ' + str(self.activity_scores))
        return self.activity_scores
Ejemplo n.º 2
0
    def get_activity_scores(cls, student_user_ids, course, force_refresh=True):
        """Retrieve activity data for student using EventEntity.

           For each student, launch a Query of EventEntities to retrieve student
           scores.  The Query is launched as a map-reduce background process that
           will return up to 500 results, reporting back every second.  It reports
           back by calling the map_fn callback, which in turn calls parse_activity
           scores.

           As soon as the Query is launched (in the background) the foreground
           process calls build_missing_scores() to construct a student_answer.dict
           that will be updated as score data for that student is received.

           Events properties include a userid (a number) and a source (e.g.,
           tag-assessement), a  recorded-on date (timestamp) and data (a dictionary).
           Here's a typeical data dict:

           {"loc": {"city": "mililani", "language": "en-US,en;q=0.8", "locale": "en_US",
           "country": "US", "region": "hi", "long": -158.01528099999999, "lat": 21.451331,
           "page_locale": "en_US"}, "instanceid": "yOkVTqWogdaF", "quid": "5733935958982656",
           "score": 1, "location": "https://mobilecsp-201608.appspot.com/mobilecsp/unit?unit=1&lesson=45",
           "answer": [0, 1, 2, 4], "type": "McQuestion", "user_agent":
            "Mozilla/5.0 ..."}

           Note that it includes the unit_id and lesson_id as part of the Url
        """

        # Instantiate parser object
        cached_date = datetime.datetime.now()
        activityParser = ActivityScoreParser()

        if force_refresh:
            activityParser.params = activityParser.build_additional_mapper_params(
                course.app_context)

            #  Launch a background Query for each student's activity data.  This is expensive.
            for user_id in student_user_ids:
                #                if GLOBAL_DEBUG:
                logging.debug('***RAM*** launching a query for student ' +
                              str(user_id))
                mapper = models_utils.QueryMapper(
                    EventEntity.all().filter('user_id in', [user_id])       \
                                     .filter('recorded_on  >= ', cls.CUTOFF_DATE), \
                                        batch_size=1000, report_every=1000)

                # Callback function -- e.g., 45-50 callbacks per query
                def map_fn(activity_attempt):
                    #                    if GLOBAL_DEBUG:
                    #                     logging.debug('***RAM*** map_fn ' + str(activity_attempt))
                    activityParser.parse_activity_scores(activity_attempt)

                mapper.run(map_fn)

            #  In the foreground create the student_answer_dict, which is stored at:
            #   activity_scores[student][unit][lesson][sequence]  where sequence is
            #   the question's sequential position within the lesson.
            #  So each question in the lesson will have a question_answer_dict.
            activityParser.build_missing_scores()

            #Lets cache results for each student
            for user_id in student_user_ids:
                cached_student_data = {}
                cached_student_data['date'] = cached_date

                student = Student.get_by_user_id(user_id)

                cached_student_data[
                    'scores'] = activityParser.activity_scores.get(
                        student.email, {})
                cached_student_data[
                    'attempts'] = activityParser.num_attempts_dict.get(
                        student.email, {})
                MemcacheManager.set(
                    cls._memcache_key_for_student(student.email),
                    cached_student_data)
        else:
            uncached_students = []
            for student_id in student_user_ids:
                if student_id != '':
                    student = Student.get_by_user_id(student_id)
                    temp_email = student.email
                    temp_mem = cls._memcache_key_for_student(temp_email)
                    scores_for_student = MemcacheManager.get(temp_mem)
                    if scores_for_student:
                        cached_date = scores_for_student['date']
                        activityParser.activity_scores[
                            student_id] = scores_for_student['scores']
                        activityParser.num_attempts_dict[
                            student_id] = scores_for_student['scores']
                    else:
                        uncached_students.append(student_id)
            if len(uncached_students) > 0:
                if cached_date == None or datetime.datetime.now(
                ) < cached_date:
                    cached_date = datetime.datetime.now()

                activityParser.params = activityParser.build_additional_mapper_params(
                    course.app_context)

                for user_id in uncached_students:
                    mapper = models_utils.QueryMapper(
                        EventEntity.all().filter('user_id in', [user_id])       \
                                     .filter('recorded_on  >= ', cls.CUTOFF_DATE), \
                                        batch_size=1000, report_every=1000)

                    def map_fn(activity_attempt):
                        activityParser.parse_activity_scores(activity_attempt)

                    mapper.run(map_fn)

                activityParser.build_missing_scores()

                #Lets cache results for each student
                for user_id in uncached_students:
                    cached_student_data = {}
                    cached_student_data['date'] = cached_date

                    student = Student.get_by_user_id(user_id)

                    cached_student_data[
                        'scores'] = activityParser.activity_scores.get(
                            student.email, {})
                    MemcacheManager.set(
                        cls._memcache_key_for_student(student.email),
                        cached_student_data)

        score_data = {}
        score_data['date'] = cached_date
        score_data['scores'] = activityParser.activity_scores
        score_data['attempts'] = activityParser.num_attempts_dict
        if GLOBAL_DEBUG:
            logging.debug('***RAM*** get_activity_scores returning scores: ' +
                          str(score_data['scores']))

        return score_data
Ejemplo n.º 3
0
    def parse_activity_scores(self, activity_attempt):
        '''
           Processes activity scores recieved from the mapper.

           This is called in the mapper callback function.  Each time a student attempts
           a GCB question or a Quizly exercise, a tag-assessment Event is created.
           This processes such events to extract the number of attempts the student
           made and the answers.

           Events are time-stamped and recorded by user_id. They include the instance_id
           of the Component that triggered the Event.  Both GCB questions and Quizly
           exercises have an instance_id.

           However, Quizly exercises don't have question_id and need special processing.

           Use the Dashboard to see what the data looks like for Events:
           https://console.cloud.google.com/datastore/entities/query?
               project=ram8647&ns=ns_mobileCSP&kind=EventEntity
        '''

        if activity_attempt.source == 'tag-assessment':
            data = transforms.loads(activity_attempt.data)
            instance_id = data['instanceid']
            if GLOBAL_DEBUG:
                logging.debug(
                    '***********RAM************** data[instanceid] = ' +
                    instance_id)
            timestamp = int((activity_attempt.recorded_on -
                             datetime.datetime(1970, 1, 1)).total_seconds())

            # Get information about the course's questions (doesn't include Quizly exercises yet)
            questions = self.params['questions_by_usage_id']
            valid_question_ids = self.params['valid_question_ids']
            assessment_weights = self.params['assessment_weights']
            group_to_questions = self.params['group_to_questions']

            student = Student.get_by_user_id(activity_attempt.user_id)

            #  Get this student's answers so far
            student_answers = self.activity_scores.get(student.email, {})
            if GLOBAL_DEBUG:
                logging.debug('***RAM*** student answers = ' +
                              str(student_answers))

            answers = event_transforms.unpack_check_answers(  # No Quizly answers in here
                data, questions, valid_question_ids, assessment_weights,
                group_to_questions, timestamp)

            # Add the score to right lesson
            # NOTE: This was throwing an exception on Quizly exercises.  Shouldn't happen now
            try:
                #  If the event is tag-assessment and has no quid, it's a Quizly exercise
                if not 'quid' in data:
                    self.parse_quizly_scores(data, instance_id, timestamp,
                                             student, student_answers)
                else:
                    self.parse_question_scores(instance_id, questions,
                                               student_answers, answers,
                                               student, timestamp)
            except Exception as e:
                logging.error(
                    '***********RAM************** bad instance_id: %s %s\n%s',
                    str(instance_id), e, traceback.format_exc())
        if GLOBAL_DEBUG:
            logging.debug('***RAM*** activity_scores ' +
                          str(self.activity_scores))
        return self.activity_scores
Ejemplo n.º 4
0
    def get_activity_scores(cls, student_user_ids, course, force_refresh = True):
        """Retrieve activity data for student using EventEntity.

           For each student, launch a Query of EventEntities to retrieve student
           scores.  The Query is launched as a map-reduce background process that
           will return up to 500 results, reporting back every second.  It reports
           back by calling the map_fn callback, which in turn calls parse_activity
           scores.

           As soon as the Query is launched (in the background) the foreground
           process calls build_missing_scores() to construct a student_answer.dict
           that will be updated as score data for that student is received.

           Events properties include a userid (a number) and a source (e.g.,
           tag-assessement), a  recorded-on date (timestamp) and data (a dictionary).
           Here's a typeical data dict:

           {"loc": {"city": "mililani", "language": "en-US,en;q=0.8", "locale": "en_US",
           "country": "US", "region": "hi", "long": -158.01528099999999, "lat": 21.451331,
           "page_locale": "en_US"}, "instanceid": "yOkVTqWogdaF", "quid": "5733935958982656",
           "score": 1, "location": "https://mobilecsp-201608.appspot.com/mobilecsp/unit?unit=1&lesson=45",
           "answer": [0, 1, 2, 4], "type": "McQuestion", "user_agent":
            "Mozilla/5.0 ..."}

           Note that it includes the unit_id and lesson_id as part of the Url
        """

        # Instantiate parser object
        cached_date = datetime.datetime.now()
        activityParser = ActivityScoreParser()

        if force_refresh:
            activityParser.params = activityParser.build_additional_mapper_params(course.app_context)

            #  Launch a background Query for each student's activity data.  This is expensive.
            for user_id in student_user_ids:
#                if GLOBAL_DEBUG:
#                     logging.debug('***RAM*** launching a query for student ' + str(user_id))
                mapper = models_utils.QueryMapper(
                    EventEntity.all().filter('user_id in', [user_id])       \
                                     .filter('recorded_on  >= ', cls.CUTOFF_DATE), \
                                        batch_size=1000, report_every=1000)

                # Callback function -- e.g., 45-50 callbacks per query
                def map_fn(activity_attempt):
#                    if GLOBAL_DEBUG:
#                     logging.debug('***RAM*** map_fn ' + str(activity_attempt))
                    activityParser.parse_activity_scores(activity_attempt)

                mapper.run(map_fn)

            #  In the foreground create the student_answer_dict, which is stored at:
            #   activity_scores[student][unit][lesson][sequence]  where sequence is
            #   the question's sequential position within the lesson.
            #  So each question in the lesson will have a question_answer_dict.
            activityParser.build_missing_scores()

            #Lets cache results for each student
            for user_id in student_user_ids:
                cached_student_data = {}
                cached_student_data['date'] = cached_date

                student = Student.get_by_user_id(user_id)
                
                cached_student_data['scores'] = activityParser.activity_scores.get(student.email, {})
                cached_student_data['attempts'] = activityParser.num_attempts_dict.get(student.email, {})
                MemcacheManager.set(cls._memcache_key_for_student(student.email),cached_student_data)
        else:
            uncached_students = []
            for student_id in student_user_ids:
                if student_id != '':
                    student = Student.get_by_user_id(student_id)
                    temp_email = student.email
                    temp_mem = cls._memcache_key_for_student(temp_email)
                    scores_for_student = MemcacheManager.get(temp_mem)
                    if scores_for_student:
                        cached_date = scores_for_student['date']
                        activityParser.activity_scores[student_id] = scores_for_student['scores']
                        activityParser.num_attempts_dict[student_id] = scores_for_student['scores']
                    else:
                        uncached_students.append(student_id)
            if len(uncached_students) > 0:
                if cached_date == None or datetime.datetime.now() < cached_date:
                    cached_date = datetime.datetime.now()

                activityParser.params = activityParser.build_additional_mapper_params(course.app_context)

                for user_id in uncached_students:
                    mapper = models_utils.QueryMapper(
                        EventEntity.all().filter('user_id in', [user_id])       \
                                     .filter('recorded_on  >= ', cls.CUTOFF_DATE), \
                                        batch_size=1000, report_every=1000)

                    def map_fn(activity_attempt):
                        activityParser.parse_activity_scores(activity_attempt)

                    mapper.run(map_fn)

                activityParser.build_missing_scores()

                #Lets cache results for each student
                for user_id in uncached_students:
                    cached_student_data = {}
                    cached_student_data['date'] = cached_date

                    student = Student.get_by_user_id(user_id)

                    cached_student_data['scores'] = activityParser.activity_scores.get(student.email, {})
                    MemcacheManager.set(cls._memcache_key_for_student(student.email),cached_student_data)

        score_data = {}
        score_data['date'] = cached_date
        score_data['scores'] = activityParser.activity_scores
        score_data['attempts'] = activityParser.num_attempts_dict
        if GLOBAL_DEBUG:
            logging.debug('***RAM*** get_activity_scores returning scores: ' + str(score_data['scores']))

        return score_data