예제 #1
0
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"
            },
        ]
    }
예제 #2
0
파일: views.py 프로젝트: arasen/ka-lite
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"},
        ]
    }
예제 #3
0
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"},
        ]
    }
예제 #4
0
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"
            },
        ]
    }
예제 #5
0
파일: views.py 프로젝트: NiallEgan/ka-lite
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"},
        ]
    }