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 %}
+
+ The following questions need marking
+ {% for exam_index in unmarked %}
+
{{ exam_index|add:1 }}
+ {% endfor %}
+
+
+ {% endif %}
+
+
+
+
Stats
+ Candidates: {{cids|length}}
+ Max score: {{max_score}}
+ Mean: {{mean}}, Median {{median}}, Mode {{mode}}
+
+
+
+
+
+ | Candidate ID |
+ Score |
+ {% comment %} Normalised Score | {% endcomment %}
+
+ {% comment %} {% for user, value in user_answers_marks.items %} {% endcomment %}
+ {% for cid in cids %}
+
+ | {{cid}} |
+ {{user_scores|get_item:cid}} |
+ {% comment %} {{user_scores_normalised|get_item:user}} | {% endcomment %}
+
+ {% endfor %}
+
+
+
+
Answers as a table
+
+
+
+ | Candidate |
+ {% for cid in cids %}
+ {{cid}} |
+ {% comment %} {{cid}} | {% endcomment %}
+ {% endfor %}
+
+
+
+ {% for question in questions %}
+
+ | Question {{forloop.counter}}
+ |
+ {% comment %} {% for cid in cids %}
+ {{ans_by_question|get_item:question|get_item:cid}} |
+ {% endfor %} {% endcomment %}
+ {% for cid in cids %}
+ {% with by_question|get_item:question|get_item:cid as ans_score %}
+ {{ans_score.0}} |
+ {% endwith %}
+ {% endfor %}
+
+ {% endfor %}
+
+ | Score: |
+ {% for cid in cids %}
+ {{user_scores|get_item:cid}} |
+ {% endfor %}
+
+
+
+
+{% 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,
+ },
+ )