diff --git a/atlas/models.py b/atlas/models.py index 8b9c8f6a..3b632d59 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -762,3 +762,6 @@ class CidReportAnswer(models.Model): def clean(self): if self.answer: self.answer = self.answer.strip() + + def get_answer_score(self): + return self.score \ No newline at end of file diff --git a/atlas/templates/atlas/collection_scores.html b/atlas/templates/atlas/collection_scores.html new file mode 100644 index 00000000..b41dc8fa --- /dev/null +++ b/atlas/templates/atlas/collection_scores.html @@ -0,0 +1,80 @@ +{% extends 'atlas/base.html' %} + +{% block content %} +
{{plot|safe}}
+
+

{{ exam.name }}

+ + {% if unmarked %} + + + {% endif %} + +
+
+

Stats

+ Candidates: {{cids|length}}
+ Max score: {{max_score}}
+ Mean: {{mean}}, Median {{median}}, Mode {{mode}} + +
+
+ + + + + {% comment %} {% endcomment %} + + {% comment %} {% for user, value in user_answers_marks.items %} {% endcomment %} + {% for cid in cids %} + + + + {% comment %} {% endcomment %} + + {% endfor %} +
Candidate IDScoreNormalised Score
{{cid}}{{user_scores|get_item:cid}}{{user_scores_normalised|get_item:user}}
+
+
+

Answers as a table

+ + + + + {% for cid in cids %} + + {% comment %} {% endcomment %} + {% endfor %} + + + + {% for question in questions %} + + + {% comment %} {% for cid in cids %} + + {% endfor %} {% endcomment %} + {% for cid in cids %} + {% with by_question|get_item:question|get_item:cid as ans_score %} + + {% endwith %} + {% endfor %} + + {% endfor %} + + + {% for cid in cids %} + + {% endfor %} + +
Candidate{{cid}}{{cid}}
Question {{forloop.counter}} + {{ans_by_question|get_item:question|get_item:cid}}{{ans_score.0}}
Score:{{user_scores|get_item:cid}}
+ +
+{% endblock %} + diff --git a/atlas/views.py b/atlas/views.py index 0d98cf39..59a55411 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -1169,7 +1169,7 @@ def collection_mark_question(request, pk, case_number): return redirect( "atlas:collection_mark_question", case_number=int(request.POST.get("goto")), - **kwargs + **kwargs, ) else: for answer in answers: @@ -1195,13 +1195,6 @@ def collection_mark_question(request, pk, case_number): ) -@user_is_collection_author_or_atlas_editor -def collection_scores_cid(request, pk): - collection = get_object_or_404(CaseCollection, pk=pk) - - return render(request, "atlas/collection_take.html", {"collection": collection}) - - @user_is_collection_author_or_atlas_editor def collection_candidates(request, pk): collection = get_object_or_404(CaseCollection, pk=pk) @@ -1324,7 +1317,7 @@ def collection_case_view_take(request, pk, case_number, cid, passcode): return redirect( "atlas:collection_case_view_take", case_number=int(request.POST.get("goto")), - **kwargs + **kwargs, ) else: form = CidReportAnswerForm(instance=answer) @@ -1404,3 +1397,146 @@ def collection_case_view(request, pk, case_number): "answer": answer, }, ) + + +@user_is_collection_author_or_atlas_editor +def collection_scores_cid(request, pk): + collection = get_object_or_404(CaseCollection, pk=pk) + + if not collection.exam_mode: + raise Http404("Collection not in exam mode") + + user_answers_and_marks = defaultdict(list) + user_answers_marks = defaultdict(list) + user_answers = defaultdict(list) + user_names = {} + # cid_passcodes = {} + + by_question = defaultdict(dict) + unmarked = set() + + cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related() + + case_details = CaseDetail.objects.filter( + case__in=cases, collection=collection + ).prefetch_related("case") + + cid_user_answers = CidReportAnswer.objects.filter(question__in=case_details) + + # questions = exam.exam_questions.all().prefetch_related("answers") + + # cid_user_answers = CidReportAnswer.objects.select_related("question").filter( + # question__in=questions, exam__id=pk + # ) + + cids = set() + + # Loop through all candidates + for cid_user_answer in cid_user_answers: + # Convoluted (probably...) + cid = cid_user_answer.cid + # cid_passcodes[cid] = cid_user_answer.passcode + cids.add(cid) + s = cid_user_answer + user_names[cid] = cid + + q = cid_user_answer.question + + # if not s: + # # skip if no answer + # user_answers_marks[cid].append(0) + # user_answers[cid].append("") + # by_question[q].append(("", 0)) + # continue + + ans = s.answer + + answer_score = s.get_answer_score() + if answer_score == "unmarked": + index = cases.index(q) + unmarked.add(index) + user_answers[cid].append(ans) + user_answers_marks[cid].append(answer_score) + user_answers_and_marks[cid].append((ans, answer_score)) + + by_question[q][cid] = (ans, answer_score) + + user_scores = {} + user_scores_normalised = {} + for user in user_answers_marks: + + user_scores[user] = sum( + [i for i in user_answers_marks[user] if i != "unmarked"] + ) + + user_scores_normalised[user] = user_scores[user] + + # ignore scores of 0 for stats + user_scores_list = [i for i in user_scores.values() if i > 0] + + max_score = len(cases) + + if len(user_scores_list) < 1: + mean = 0 + median = 0 + mode = 0 + fig_html = "" + else: + mean = statistics.mean(user_scores_list) + median = statistics.median(user_scores_list) + try: + mode = statistics.mode(user_scores_list) + except statistics.StatisticsError: + mode = "No unique mode" + + df = user_scores_list + fig = px.histogram( + df, + x=0, + title="{}: distribution of scores".format(collection), + labels={"0": "Score"}, + height=400, + width=600, + ) + fig_html = fig.to_html() + + collection.stats_mean = mean + collection.stats_median = median + collection.stats_mode = mode + + collection.stats_candidates = len(user_scores_list) + collection.stats_max_possible = max_score + + collection.stats_min = min(user_scores_list) + collection.stats_max = max(user_scores_list) + + collection.stats_graph = fig_html + + collection.user_scores = user_scores + + collection.save() + + return render( + request, + f"atlas/collection_scores.html", + { + "cids": sorted(cids), + # "cid_passcodes": cid_passcodes, + "exam": collection, + "unmarked": unmarked, + "questions": cases, + "by_question": by_question, + "user_answers": dict(user_answers), + "user_answers_marks": dict(user_answers_marks), + "user_scores": user_scores, + "user_scores_normalised": user_scores_normalised, + "user_scores_list": user_scores_list, + "user_names": user_names, + "user_answers_and_marks": user_answers_and_marks, + "max_score": max_score, + "mean": mean, + "median": median, + "mode": mode, + "plot": fig_html, + }, + )