This commit is contained in:
Ross
2022-04-09 00:28:09 +01:00
parent 88092d6846
commit 57f88af28f
3 changed files with 228 additions and 9 deletions
+3
View File
@@ -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
@@ -0,0 +1,80 @@
{% extends 'atlas/base.html' %}
{% block content %}
<div id="stats-plot">{{plot|safe}}</div>
<div class="atlas">
<h2>{{ exam.name }}</h2>
{% if unmarked %}
<div class="alert alert-warning" role="alert">
The following questions need marking
{% for exam_index in unmarked %}
<a href="{% url 'atlas:mark' exam.pk exam_index %}">{{ exam_index|add:1 }}</a>
{% endfor %}
</div>
{% endif %}
</div>
<div id="stats-block">
<h3>Stats</h3>
Candidates: {{cids|length}}<br />
Max score: {{max_score}}<br />
Mean: {{mean}}, Median {{median}}, Mode {{mode}}
</div>
<div>
<table class="table table-dark table-striped table-hover table-sm cid-score-table">
<tr>
<th>Candidate ID</th>
<th>Score</th>
{% comment %} <th>Normalised Score</th> {% endcomment %}
</tr>
{% comment %} {% for user, value in user_answers_marks.items %} {% endcomment %}
{% for cid in cids %}
<tr>
<td><a href="{% url 'cid_scores_admin' cid %}">{{cid}}</a></td>
<td>{{user_scores|get_item:cid}}</td>
{% comment %} <td>{{user_scores_normalised|get_item:user}}</td> {% endcomment %}
</tr>
{% endfor %}
</table>
</div>
<div>
<h3>Answers as a table</h3>
<table class="table table-dark table-striped table-hover table-sm col-sm">
<thead class="thead-dark">
<tr>
<th>Candidate</th>
{% for cid in cids %}
<th><a href="{% url 'atlas:exam_scores_cid_user' exam.pk cid 'None' %}">{{cid}}</a></th>
{% comment %} <th><a href="">{{cid}}</a></th> {% endcomment %}
{% endfor %}
</tr>
</thead>
{% for question in questions %}
<tr>
<td><a href="{% url 'atlas:mark' exam_pk=exam.pk sk=forloop.counter0 %}">Question {{forloop.counter}}</a>
</td>
{% comment %} {% for cid in cids %}
<td class="atlas-ans user-answer-score-{{score_by_question|get_item:question|get_item:cid}}" title="answer score: {{score_by_question|get_item:question|get_item:cid}}">{{ans_by_question|get_item:question|get_item:cid}}</td>
{% endfor %} {% endcomment %}
{% for cid in cids %}
{% with by_question|get_item:question|get_item:cid as ans_score %}
<td class="atlas-ans user-answer-score-{{ans_score.1}}" title="answer score: {{ans_score.1}}">{{ans_score.0}}</td>
{% endwith %}
{% endfor %}
</tr>
{% endfor %}
<tr>
<td>Score:</td>
{% for cid in cids %}
<td>{{user_scores|get_item:cid}}</td>
{% endfor %}
</tr>
</table>
</div>
{% endblock %}
+145 -9
View File
@@ -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,
},
)