def student_view_context(request, xaxis="pct_mastery", yaxis="ex:attempts"): """ Context done separately, to be importable for similar pages. """ user = get_user_from_request(request=request) topic_slugs = [t["id"] for t in get_knowledgemap_topics()] topics = [NODE_CACHE["Topic"][slug] for slug in topic_slugs] user_id = user.id exercise_logs = list(ExerciseLog.objects \ .filter(user=user) \ .values("exercise_id", "complete", "points", "attempts", "streak_progress", "struggling", "completion_timestamp")) video_logs = list(VideoLog.objects \ .filter(user=user) \ .values("youtube_id", "complete", "total_seconds_watched", "points", "completion_timestamp")) exercise_sparklines = dict() stats = dict() topic_exercises = dict() topic_videos = dict() exercises_by_topic = dict() videos_by_topic = dict() # Categorize every exercise log into a "midlevel" exercise for elog in exercise_logs: parents = NODE_CACHE["Exercise"][elog["exercise_id"]]["parents"] topic = set(parents).intersection(set(topic_slugs)) if not topic: logging.error( "Could not find a topic for exercise %s (parents=%s)" % (elog["exercise_id"], parents)) continue topic = topic.pop() if not topic in topic_exercises: topic_exercises[topic] = get_topic_exercises( path=NODE_CACHE["Topic"][topic]["path"]) exercises_by_topic[topic] = exercises_by_topic.get(topic, []) + [elog] # Categorize every video log into a "midlevel" exercise. for vlog in video_logs: parents = NODE_CACHE["Video"][ID2SLUG_MAP[ vlog["youtube_id"]]]["parents"] topic = set(parents).intersection(set(topic_slugs)) if not topic: logging.error("Could not find a topic for video %s (parents=%s)" % (vlog["youtube_id"], parents)) continue topic = topic.pop() if not topic in topic_videos: topic_videos[topic] = get_topic_videos( path=NODE_CACHE["Topic"][topic]["path"]) videos_by_topic[topic] = videos_by_topic.get(topic, []) + [vlog] # Now compute stats for topic in topic_slugs: #set(topic_exercises.keys()).union(set(topic_videos.keys())): n_exercises = len(topic_exercises.get(topic, [])) n_videos = len(topic_videos.get(topic, [])) exercises = exercises_by_topic.get(topic, []) videos = videos_by_topic.get(topic, []) n_exercises_touched = len(exercises) n_videos_touched = len(videos) exercise_sparklines[topic] = [ el["completion_timestamp"] for el in filter(lambda n: n["complete"], exercises) ] # total streak currently a pct, but expressed in max 100; convert to # proportion (like other percentages here) stats[topic] = { "ex:pct_mastery": 0 if not n_exercises_touched else sum([el["complete"] for el in exercises]) / float(n_exercises), "ex:pct_started": 0 if not n_exercises_touched else n_exercises_touched / float(n_exercises), "ex:average_points": 0 if not n_exercises_touched else sum([el["points"] for el in exercises]) / float(n_exercises_touched), "ex:average_attempts": 0 if not n_exercises_touched else sum([el["attempts"] for el in exercises]) / float(n_exercises_touched), "ex:average_streak": 0 if not n_exercises_touched else sum([el["streak_progress"] for el in exercises]) / float(n_exercises_touched) / 100., "ex:total_struggling": 0 if not n_exercises_touched else sum( [el["struggling"] for el in exercises]), "ex:last_completed": None if not n_exercises_touched else max_none( [el["completion_timestamp"] or None for el in exercises]), "vid:pct_started": 0 if not n_videos_touched else n_videos_touched / float(n_videos), "vid:pct_completed": 0 if not n_videos_touched else sum([vl["complete"] for vl in videos]) / float(n_videos), "vid:total_minutes": 0 if not n_videos_touched else sum([vl["total_seconds_watched"] for vl in videos]) / 60., "vid:average_points": 0. if not n_videos_touched else float( sum([vl["points"] for vl in videos]) / float(n_videos_touched)), "vid:last_completed": None if not n_videos_touched else max_none( [vl["completion_timestamp"] or None for vl in videos]), } context = plotting_metadata_context(request) return { "form": context["form"], "groups": context["groups"], "facilities": context["facilities"], "student": user, "topics": topics, "exercises": topic_exercises, "exercise_logs": exercises_by_topic, "video_logs": videos_by_topic, "exercise_sparklines": exercise_sparklines, "no_data": not exercise_logs and not video_logs, "stats": stats, "stat_defs": [ # this order determines the order of display { "key": "ex:pct_mastery", "title": _("% Mastery"), "type": "pct" }, { "key": "ex:pct_started", "title": _("% Started"), "type": "pct" }, { "key": "ex:average_points", "title": _("Average Points"), "type": "float" }, { "key": "ex:average_attempts", "title": _("Average Attempts"), "type": "float" }, { "key": "ex:average_streak", "title": _("Average Streak"), "type": "pct" }, { "key": "ex:total_struggling", "title": _("Struggling"), "type": "int" }, { "key": "ex:last_completed", "title": _("Last Completed"), "type": "date" }, { "key": "vid:pct_completed", "title": _("% Completed"), "type": "pct" }, { "key": "vid:pct_started", "title": _("% Started"), "type": "pct" }, { "key": "vid:total_minutes", "title": _("Average Minutes Watched"), "type": "float" }, { "key": "vid:average_points", "title": _("Average Points"), "type": "float" }, { "key": "vid:last_completed", "title": _("Last Completed"), "type": "date" }, ] }
def student_view_context(request, xaxis="pct_mastery", yaxis="ex:attempts"): """ Context done separately, to be importable for similar pages. """ user = get_user_from_request(request=request) topic_slugs = [t["id"] for t in get_knowledgemap_topics()] topics = [NODE_CACHE["Topic"][slug] for slug in topic_slugs] user_id = user.id exercise_logs = list(ExerciseLog.objects \ .filter(user=user) \ .values("exercise_id", "complete", "points", "attempts", "streak_progress", "struggling", "completion_timestamp")) video_logs = list(VideoLog.objects \ .filter(user=user) \ .values("youtube_id", "complete", "total_seconds_watched", "points", "completion_timestamp")) exercise_sparklines = dict() stats = dict() topic_exercises = dict() topic_videos = dict() exercises_by_topic = dict() videos_by_topic = dict() # Categorize every exercise log into a "midlevel" exercise for elog in exercise_logs: parents = NODE_CACHE["Exercise"][elog["exercise_id"]]["parents"] topic = set(parents).intersection(set(topic_slugs)) if not topic: logging.error("Could not find a topic for exercise %s (parents=%s)" % (elog["exercise_id"], parents)) continue topic = topic.pop() if not topic in topic_exercises: topic_exercises[topic] = get_topic_exercises(path=NODE_CACHE["Topic"][topic]["path"]) exercises_by_topic[topic] = exercises_by_topic.get(topic, []) + [elog] # Categorize every video log into a "midlevel" exercise. for vlog in video_logs: parents = NODE_CACHE["Video"][ID2SLUG_MAP[vlog["youtube_id"]]]["parents"] topic = set(parents).intersection(set(topic_slugs)) if not topic: logging.error("Could not find a topic for video %s (parents=%s)" % (vlog["youtube_id"], parents)) continue topic = topic.pop() if not topic in topic_videos: topic_videos[topic] = get_topic_videos(path=NODE_CACHE["Topic"][topic]["path"]) videos_by_topic[topic] = videos_by_topic.get(topic, []) + [vlog] # Now compute stats for topic in topic_slugs:#set(topic_exercises.keys()).union(set(topic_videos.keys())): n_exercises = len(topic_exercises.get(topic, [])) n_videos = len(topic_videos.get(topic, [])) exercises = exercises_by_topic.get(topic, []) videos = videos_by_topic.get(topic, []) n_exercises_touched = len(exercises) n_videos_touched = len(videos) exercise_sparklines[topic] = [el["completion_timestamp"] for el in filter(lambda n: n["complete"], exercises)] # total streak currently a pct, but expressed in max 100; convert to # proportion (like other percentages here) stats[topic] = { "ex:pct_mastery": 0 if not n_exercises_touched else sum([el["complete"] for el in exercises]) / float(n_exercises), "ex:pct_started": 0 if not n_exercises_touched else n_exercises_touched / float(n_exercises), "ex:average_points": 0 if not n_exercises_touched else sum([el["points"] for el in exercises]) / float(n_exercises_touched), "ex:average_attempts": 0 if not n_exercises_touched else sum([el["attempts"] for el in exercises]) / float(n_exercises_touched), "ex:average_streak": 0 if not n_exercises_touched else sum([el["streak_progress"] for el in exercises]) / float(n_exercises_touched) / 100., "ex:total_struggling": 0 if not n_exercises_touched else sum([el["struggling"] for el in exercises]), "ex:last_completed": None if not n_exercises_touched else max_none([el["completion_timestamp"] or None for el in exercises]), "vid:pct_started": 0 if not n_videos_touched else n_videos_touched / float(n_videos), "vid:pct_completed": 0 if not n_videos_touched else sum([vl["complete"] for vl in videos]) / float(n_videos), "vid:total_minutes": 0 if not n_videos_touched else sum([vl["total_seconds_watched"] for vl in videos]) / 60., "vid:average_points": 0. if not n_videos_touched else float(sum([vl["points"] for vl in videos]) / float(n_videos_touched)), "vid:last_completed": None if not n_videos_touched else max_none([vl["completion_timestamp"] or None for vl in videos]), } context = plotting_metadata_context(request) return { "form": context["form"], "groups": context["groups"], "facilities": context["facilities"], "student": user, "topics": topics, "exercises": topic_exercises, "exercise_logs": exercises_by_topic, "video_logs": videos_by_topic, "exercise_sparklines": exercise_sparklines, "no_data": not exercise_logs and not video_logs, "stats": stats, "stat_defs": [ # this order determines the order of display {"key": "ex:pct_mastery", "title": _("% Mastery"), "type": "pct"}, {"key": "ex:pct_started", "title": _("% Started"), "type": "pct"}, {"key": "ex:average_points", "title": _("Average Points"), "type": "float"}, {"key": "ex:average_attempts", "title": _("Average Attempts"), "type": "float"}, {"key": "ex:average_streak", "title": _("Average Streak"), "type": "pct"}, {"key": "ex:total_struggling", "title": _("Struggling"), "type": "int"}, {"key": "ex:last_completed", "title": _("Last Completed"), "type": "date"}, {"key": "vid:pct_completed", "title": _("% Completed"), "type": "pct"}, {"key": "vid:pct_started", "title": _("% Started"), "type": "pct"}, {"key": "vid:total_minutes", "title": _("Average Minutes Watched"),"type": "float"}, {"key": "vid:average_points", "title": _("Average Points"), "type": "float"}, {"key": "vid:last_completed", "title": _("Last Completed"), "type": "date"}, ] }
def student_view_context(request, xaxis="pct_mastery", yaxis="ex:attempts"): """ Context done separately, to be importable for similar pages. """ user = get_user_from_request(request=request) if not user: raise Http404("User not found.") node_cache = get_node_cache() topic_ids = get_knowledgemap_topics() topic_ids += [ch["id"] for node in get_topic_tree()["children"] for ch in node["children"] if node["id"] != "math"] topics = [node_cache["Topic"][id][0] for id in topic_ids] user_id = user.id exercise_logs = list(ExerciseLog.objects \ .filter(user=user) \ .values("exercise_id", "complete", "points", "attempts", "streak_progress", "struggling", "completion_timestamp")) video_logs = list(VideoLog.objects \ .filter(user=user) \ .values("video_id", "complete", "total_seconds_watched", "points", "completion_timestamp")) exercise_sparklines = dict() stats = dict() topic_exercises = dict() topic_videos = dict() exercises_by_topic = dict() videos_by_topic = dict() # Categorize every exercise log into a "midlevel" exercise for elog in exercise_logs: if not elog["exercise_id"] in node_cache["Exercise"]: # Sometimes KA updates their topic tree and eliminates exercises; # we also want to support 3rd party switching of trees arbitrarily. logging.debug("Skip unknown exercise log for %s/%s" % (user_id, elog["exercise_id"])) continue parent_ids = [topic for ex in node_cache["Exercise"][elog["exercise_id"]] for topic in ex["ancestor_ids"]] topic = set(parent_ids).intersection(set(topic_ids)) if not topic: logging.error("Could not find a topic for exercise %s (parents=%s)" % (elog["exercise_id"], parent_ids)) continue topic = topic.pop() if not topic in topic_exercises: topic_exercises[topic] = get_topic_exercises(path=node_cache["Topic"][topic][0]["path"]) exercises_by_topic[topic] = exercises_by_topic.get(topic, []) + [elog] # Categorize every video log into a "midlevel" exercise. for vlog in video_logs: if not vlog["video_id"] in node_cache["Video"]: # Sometimes KA updates their topic tree and eliminates videos; # we also want to support 3rd party switching of trees arbitrarily. logging.debug("Skip unknown video log for %s/%s" % (user_id, vlog["video_id"])) continue parent_ids = [topic for vid in node_cache["Video"][vlog["video_id"]] for topic in vid["ancestor_ids"]] topic = set(parent_ids).intersection(set(topic_ids)) if not topic: logging.error("Could not find a topic for video %s (parents=%s)" % (vlog["video_id"], parent_ids)) continue topic = topic.pop() if not topic in topic_videos: topic_videos[topic] = get_topic_videos(path=node_cache["Topic"][topic][0]["path"]) videos_by_topic[topic] = videos_by_topic.get(topic, []) + [vlog] # Now compute stats for id in topic_ids:#set(topic_exercises.keys()).union(set(topic_videos.keys())): n_exercises = len(topic_exercises.get(id, [])) n_videos = len(topic_videos.get(id, [])) exercises = exercises_by_topic.get(id, []) videos = videos_by_topic.get(id, []) n_exercises_touched = len(exercises) n_videos_touched = len(videos) exercise_sparklines[id] = [el["completion_timestamp"] for el in filter(lambda n: n["complete"], exercises)] # total streak currently a pct, but expressed in max 100; convert to # proportion (like other percentages here) stats[id] = { "ex:pct_mastery": 0 if not n_exercises_touched else sum([el["complete"] for el in exercises]) / float(n_exercises), "ex:pct_started": 0 if not n_exercises_touched else n_exercises_touched / float(n_exercises), "ex:average_points": 0 if not n_exercises_touched else sum([el["points"] for el in exercises]) / float(n_exercises_touched), "ex:average_attempts": 0 if not n_exercises_touched else sum([el["attempts"] for el in exercises]) / float(n_exercises_touched), "ex:average_streak": 0 if not n_exercises_touched else sum([el["streak_progress"] for el in exercises]) / float(n_exercises_touched) / 100., "ex:total_struggling": 0 if not n_exercises_touched else sum([el["struggling"] for el in exercises]), "ex:last_completed": None if not n_exercises_touched else max_none([el["completion_timestamp"] or None for el in exercises]), "vid:pct_started": 0 if not n_videos_touched else n_videos_touched / float(n_videos), "vid:pct_completed": 0 if not n_videos_touched else sum([vl["complete"] for vl in videos]) / float(n_videos), "vid:total_minutes": 0 if not n_videos_touched else sum([vl["total_seconds_watched"] for vl in videos]) / 60., "vid:average_points": 0. if not n_videos_touched else float(sum([vl["points"] for vl in videos]) / float(n_videos_touched)), "vid:last_completed": None if not n_videos_touched else max_none([vl["completion_timestamp"] or None for vl in videos]), } context = plotting_metadata_context(request) return { "form": context["form"], "groups": context["groups"], "facilities": context["facilities"], "student": user, "topics": topics, "exercises": topic_exercises, "exercise_logs": exercises_by_topic, "video_logs": videos_by_topic, "exercise_sparklines": exercise_sparklines, "no_data": not exercise_logs and not video_logs, "stats": stats, "stat_defs": [ # this order determines the order of display {"key": "ex:pct_mastery", "title": _("% Mastery"), "type": "pct"}, {"key": "ex:pct_started", "title": _("% Started"), "type": "pct"}, {"key": "ex:average_points", "title": _("Average Points"), "type": "float"}, {"key": "ex:average_attempts", "title": _("Average Attempts"), "type": "float"}, {"key": "ex:average_streak", "title": _("Average Streak"), "type": "pct"}, {"key": "ex:total_struggling", "title": _("Struggling"), "type": "int"}, {"key": "ex:last_completed", "title": _("Last Completed"), "type": "date"}, {"key": "vid:pct_completed", "title": _("% Completed"), "type": "pct"}, {"key": "vid:pct_started", "title": _("% Started"), "type": "pct"}, {"key": "vid:total_minutes", "title": _("Average Minutes Watched"),"type": "float"}, {"key": "vid:average_points", "title": _("Average Points"), "type": "float"}, {"key": "vid:last_completed", "title": _("Last Completed"), "type": "date"}, ] }
def student_view(request, xaxis="pct_mastery", yaxis="ex:attempts"): """ Student view: data generated on the back-end. Student view lists a by-topic-summary of their activity logs. """ user = get_user_from_request(request=request) topics = get_all_midlevel_topics() topic_ids = [t['id'] for t in topics] topics = filter( partial(lambda n, ids: n['id'] in ids, ids=topic_ids), topicdata.NODE_CACHE['Topic'].values()) # real data, like paths any_data = False # whether the user has any data at all. exercise_logs = dict() video_logs = dict() exercise_sparklines = dict() stats = dict() topic_exercises = dict() for topic in topics: topic_exercises[topic['id']] = get_topic_exercises(path=topic['path']) n_exercises = len(topic_exercises[topic['id']]) exercise_logs[topic['id']] = ExerciseLog.objects.filter( user=user, exercise_id__in=[t['name'] for t in topic_exercises[topic['id']] ]).order_by("completion_timestamp") n_exercises_touched = len(exercise_logs[topic['id']]) topic_videos = get_topic_videos(topic_id=topic['id']) n_videos = len(topic_videos) video_logs[topic['id']] = VideoLog.objects.filter( user=user, youtube_id__in=[tv['youtube_id'] for tv in topic_videos ]).order_by("completion_timestamp") n_videos_touched = len(video_logs[topic['id']]) exercise_sparklines[topic['id']] = [ el.completion_timestamp for el in filter(lambda n: n.complete, exercise_logs[topic['id']]) ] # total streak currently a pct, but expressed in max 100; convert to # proportion (like other percentages here) stats[topic['id']] = { "ex:pct_mastery": 0 if not n_exercises_touched else sum([el.complete for el in exercise_logs[topic['id']]]) / float(n_exercises), "ex:pct_started": 0 if not n_exercises_touched else n_exercises_touched / float(n_exercises), "ex:average_points": 0 if not n_exercises_touched else sum([el.points for el in exercise_logs[topic['id']]]) / float(n_exercises_touched), "ex:average_attempts": 0 if not n_exercises_touched else sum([el.attempts for el in exercise_logs[topic['id']]]) / float(n_exercises_touched), "ex:average_streak": 0 if not n_exercises_touched else sum([el.streak_progress for el in exercise_logs[topic['id']]]) / float(n_exercises_touched) / 100., "ex:total_struggling": 0 if not n_exercises_touched else sum( [el.struggling for el in exercise_logs[topic['id']]]), "ex:last_completed": None if not n_exercises_touched else max_none([ el.completion_timestamp or None for el in exercise_logs[topic['id']] ]), "vid:pct_started": 0 if not n_videos_touched else n_videos_touched / float(n_videos), "vid:pct_completed": 0 if not n_videos_touched else sum([vl.complete for vl in video_logs[topic['id']]]) / float(n_videos), "vid:total_minutes": 0 if not n_videos_touched else sum([vl.total_seconds_watched for vl in video_logs[topic['id']]]) / 60., "vid:average_points": 0. if not n_videos_touched else float( sum([vl.points for vl in video_logs[topic['id']]]) / float(n_videos_touched)), "vid:last_completed": None if not n_videos_touched else max_none([ vl.completion_timestamp or None for vl in video_logs[topic['id']] ]), } any_data = any_data or n_exercises_touched > 0 or n_videos_touched > 0 context = plotting_metadata_context(request) return { "form": context["form"], "groups": context["groups"], "facilities": context["facilities"], "student": user, "topics": topics, "topic_ids": topic_ids, "exercises": topic_exercises, "exercise_logs": exercise_logs, "video_logs": video_logs, "exercise_sparklines": exercise_sparklines, "no_data": not any_data, "stats": stats, "stat_defs": [ # this order determines the order of display { "key": "ex:pct_mastery", "title": _("% Mastery"), "type": "pct" }, { "key": "ex:pct_started", "title": _("% Started"), "type": "pct" }, { "key": "ex:average_points", "title": _("Average Points"), "type": "float" }, { "key": "ex:average_attempts", "title": _("Average Attempts"), "type": "float" }, { "key": "ex:average_streak", "title": _("Average Streak"), "type": "pct" }, { "key": "ex:total_struggling", "title": _("Struggling"), "type": "int" }, { "key": "ex:last_completed", "title": _("Last Completed"), "type": "date" }, { "key": "vid:pct_completed", "title": _("% Completed"), "type": "pct" }, { "key": "vid:pct_started", "title": _("% Started"), "type": "pct" }, { "key": "vid:total_minutes", "title": _("Average Minutes Watched"), "type": "float" }, { "key": "vid:average_points", "title": _("Average Points"), "type": "float" }, { "key": "vid:last_completed", "title": _("Last Completed"), "type": "date" }, ] }
def student_view(request, xaxis="pct_mastery", yaxis="ex:attempts"): """ Student view: data generated on the back-end. Student view lists a by-topic-summary of their activity logs. """ user = get_user_from_request(request=request) topics = get_all_midlevel_topics() topic_ids = [t['id'] for t in topics] topics = filter(partial(lambda n, ids: n['id'] in ids, ids=topic_ids), topicdata.NODE_CACHE['Topic'].values()) # real data, like paths any_data = False # whether the user has any data at all. exercise_logs = dict() video_logs = dict() exercise_sparklines = dict() stats = dict() topic_exercises = dict() for topic in topics: topic_exercises[topic['id']] = get_topic_exercises(path=topic['path']) n_exercises = len(topic_exercises[topic['id']]) exercise_logs[topic['id']] = ExerciseLog.objects.filter(user=user, exercise_id__in=[t['name'] for t in topic_exercises[topic['id']]]).order_by("completion_timestamp") n_exercises_touched = len(exercise_logs[topic['id']]) topic_videos = get_topic_videos(topic_id=topic['id']) n_videos = len(topic_videos) video_logs[topic['id']] = VideoLog.objects.filter(user=user, youtube_id__in=[tv['youtube_id'] for tv in topic_videos]).order_by("completion_timestamp") n_videos_touched = len(video_logs[topic['id']]) exercise_sparklines[topic['id']] = [el.completion_timestamp for el in filter(lambda n: n.complete, exercise_logs[topic['id']])] # total streak currently a pct, but expressed in max 100; convert to # proportion (like other percentages here) stats[topic['id']] = { "ex:pct_mastery": 0 if not n_exercises_touched else sum([el.complete for el in exercise_logs[topic['id']]]) / float(n_exercises), "ex:pct_started": 0 if not n_exercises_touched else n_exercises_touched / float(n_exercises), "ex:average_points": 0 if not n_exercises_touched else sum([el.points for el in exercise_logs[topic['id']]]) / float(n_exercises_touched), "ex:average_attempts": 0 if not n_exercises_touched else sum([el.attempts for el in exercise_logs[topic['id']]]) / float(n_exercises_touched), "ex:average_streak": 0 if not n_exercises_touched else sum([el.streak_progress for el in exercise_logs[topic['id']]]) / float(n_exercises_touched) / 100., "ex:total_struggling": 0 if not n_exercises_touched else sum([el.struggling for el in exercise_logs[topic['id']]]), "ex:last_completed": None if not n_exercises_touched else max_none([el.completion_timestamp or None for el in exercise_logs[topic['id']]]), "vid:pct_started": 0 if not n_videos_touched else n_videos_touched / float(n_videos), "vid:pct_completed": 0 if not n_videos_touched else sum([vl.complete for vl in video_logs[topic['id']]]) / float(n_videos), "vid:total_minutes": 0 if not n_videos_touched else sum([vl.total_seconds_watched for vl in video_logs[topic['id']]]) / 60., "vid:average_points": 0. if not n_videos_touched else float(sum([vl.points for vl in video_logs[topic['id']]]) / float(n_videos_touched)), "vid:last_completed": None if not n_videos_touched else max_none([vl.completion_timestamp or None for vl in video_logs[topic['id']]]), } any_data = any_data or n_exercises_touched > 0 or n_videos_touched > 0 context = plotting_metadata_context(request) return { "form": context["form"], "groups": context["groups"], "facilities": context["facilities"], "student": user, "topics": topics, "topic_ids": topic_ids, "exercises": topic_exercises, "exercise_logs": exercise_logs, "video_logs": video_logs, "exercise_sparklines": exercise_sparklines, "no_data": not any_data, "stats": stats, "stat_defs": [ # this order determines the order of display {"key": "ex:pct_mastery", "title": _("% Mastery"), "type": "pct"}, {"key": "ex:pct_started", "title": _("% Started"), "type": "pct"}, {"key": "ex:average_points", "title": _("Average Points"), "type": "float"}, {"key": "ex:average_attempts", "title": _("Average Attempts"), "type": "float"}, {"key": "ex:average_streak", "title": _("Average Streak"), "type": "pct"}, {"key": "ex:total_struggling", "title": _("Struggling"), "type": "int"}, {"key": "ex:last_completed", "title": _("Last Completed"), "type": "date"}, {"key": "vid:pct_completed", "title": _("% Completed"), "type": "pct"}, {"key": "vid:pct_started", "title": _("% Started"), "type": "pct"}, {"key": "vid:total_minutes", "title": _("Average Minutes Watched"),"type": "float"}, {"key": "vid:average_points", "title": _("Average Points"), "type": "float"}, {"key": "vid:last_completed", "title": _("Last Completed"), "type": "date"}, ] }