diff --git a/ceuk-marking/urls.py b/ceuk-marking/urls.py
index ae15ec78..782ec39d 100644
--- a/ceuk-marking/urls.py
+++ b/ceuk-marking/urls.py
@@ -183,11 +183,21 @@
stats.QuestionDataCSVView.as_view(),
name="question_data_csv",
),
+ path(
+ "stats/scores/question_scores/",
+ stats.QuestionScoresCSV.as_view(),
+ name="question_scores_csv",
+ ),
path(
"stats/scores/weighted_totals/",
stats.WeightedScoresDataCSVView.as_view(),
name="weighted_totals_csv",
),
+ path(
+ "stats/scores/raw_and_weighted_totals/",
+ stats.SectionScoresDataCSVView.as_view(),
+ name="raw_and_weighted_totals_csv",
+ ),
path(
"stats/scores/bad_responses/",
stats.BadResponsesView.as_view(),
diff --git a/crowdsourcer/scoring.py b/crowdsourcer/scoring.py
index 0b24b264..86e7739d 100644
--- a/crowdsourcer/scoring.py
+++ b/crowdsourcer/scoring.py
@@ -266,6 +266,9 @@ def calculate_council_totals(
section_totals[council][section] = {
"raw": score,
"raw_percent": percentage_score,
+ "raw_weighted": weighted_scores[council][section],
+ "unweighted_percentage": weighted_scores[council][section]
+ / weighted_maxes[section][council_group],
"weighted": weighted_score,
}
diff --git a/crowdsourcer/templates/crowdsourcer/base.html b/crowdsourcer/templates/crowdsourcer/base.html
index 50113ed7..807d9be1 100644
--- a/crowdsourcer/templates/crowdsourcer/base.html
+++ b/crowdsourcer/templates/crowdsourcer/base.html
@@ -89,9 +89,15 @@
Responses with no answer
+
+ Raw and weighted scores and totals (CSV)
+
Weighted section totals (CSV)
+
+ Question scores and answers (CSV)
+
diff --git a/crowdsourcer/views/stats.py b/crowdsourcer/views/stats.py
index 471c1752..df8595c7 100644
--- a/crowdsourcer/views/stats.py
+++ b/crowdsourcer/views/stats.py
@@ -9,11 +9,12 @@
from django.utils.text import slugify
from django.views.generic import ListView, TemplateView
-from crowdsourcer.models import PublicAuthority, Question, Response
+from crowdsourcer.models import Option, PublicAuthority, Question, Response
from crowdsourcer.scoring import (
calculate_council_totals,
get_section_maxes,
get_section_scores,
+ weighting_to_points,
)
logger = logging.getLogger(__name__)
@@ -336,7 +337,39 @@ def get_response_data(self, response):
return data
-class WeightedScoresDataCSVView(UserPassesTestMixin, TemplateView):
+class BaseScoresView(UserPassesTestMixin, TemplateView):
+ def test_func(self):
+ return self.request.user.is_superuser
+
+ def get_scores(self):
+ council_gss_map, groups = PublicAuthority.maps()
+ maxes, group_maxes, q_maxes, weighted_maxes = get_section_maxes()
+ raw_scores, weighted = get_section_scores(q_maxes)
+
+ council_totals, section_totals = calculate_council_totals(
+ raw_scores, weighted, weighted_maxes, maxes, group_maxes, groups
+ )
+
+ self.groups = groups
+ self.maxes = maxes
+ self.weighted_maxes = weighted_maxes
+
+ return council_totals, section_totals
+
+ def render_to_response(self, context, **response_kwargs):
+ response = HttpResponse(
+ content_type="text/csv",
+ headers={"Content-Disposition": f'attachment; filename="{self.file_name}"'},
+ )
+ writer = csv.writer(response)
+ for row in context["rows"]:
+ writer.writerow(row)
+ return response
+
+
+class WeightedScoresDataCSVView(BaseScoresView):
+ file_name = "all_sections_scores.csv"
+
def test_func(self):
return self.request.user.is_superuser
@@ -357,13 +390,7 @@ def get_context_data(self, **kwargs):
]
context = super().get_context_data(**kwargs)
- council_gss_map, groups = PublicAuthority.maps()
- maxes, group_maxes, q_maxes, weighted_maxes = get_section_maxes()
- raw_scores, weighted = get_section_scores(q_maxes)
-
- council_totals, section_totals = calculate_council_totals(
- raw_scores, weighted, weighted_maxes, maxes, group_maxes, groups
- )
+ council_totals, section_totals = self.get_scores()
rows = []
rows.append(
@@ -388,19 +415,144 @@ def get_context_data(self, **kwargs):
rows.append(row)
- context["weighted_scores"] = rows
+ context["rows"] = rows
return context
- def render_to_response(self, context, **response_kwargs):
- file_name = "all_sections_scores.csv"
+class SectionScoresDataCSVView(BaseScoresView):
+ file_name = "raw_and_weighted_sections_scores.csv"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+
+ council_totals, section_totals = self.get_scores()
+
+ rows = []
+ rows.append(
+ [
+ "council",
+ "section",
+ "raw score",
+ "raw max",
+ "raw weighted",
+ "weighted max",
+ "weighted score",
+ "section weighted score",
+ ]
+ )
+
+ for council, council_score in section_totals.items():
+ for section, scores in council_score.items():
+ row = [
+ council,
+ section,
+ scores["raw"],
+ self.maxes[section][self.groups[council]],
+ scores["raw_weighted"],
+ self.weighted_maxes[section][self.groups[council]],
+ scores["unweighted_percentage"],
+ scores["weighted"],
+ ]
+
+ rows.append(row)
+
+ context["rows"] = rows
+
+ return context
+
+
+class QuestionScoresCSV(UserPassesTestMixin, ListView):
+ context_object_name = "options"
+
+ def test_func(self):
+
+ return self.request.user.is_superuser
+
+ def get_queryset(self):
+ return Option.objects.select_related("question", "question__section").order_by(
+ "question__section__title",
+ "question__number",
+ "question__number_part",
+ "score",
+ )
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+
+ maxes, group_maxes, q_maxes, weighted_maxes = get_section_maxes()
+
+ sections = defaultdict(dict)
+ for option in context["options"]:
+ section = option.question.section.title
+ number = option.question.number_and_part
+
+ option_text = f"{option.description} ({option.score})"
+
+ try:
+ q = sections[section][number]
+ except KeyError:
+ max_score = q_maxes[section].get(number, 0)
+ q = {
+ "q": option.question.description,
+ "how_marked": option.question.how_marked,
+ "type": option.question.question_type,
+ "raw_max": max_score,
+ "weighted_max": weighting_to_points(option.question.weighting),
+ "options": [],
+ }
+ q["options"].append(option_text)
+
+ sections[section][number] = q
+
+ rows = []
+ rows.append(
+ [
+ "section",
+ "No",
+ "question",
+ "how marked",
+ "type",
+ "raw max",
+ "weighted max",
+ "options",
+ ]
+ )
+
+ for section, questions in sections.items():
+ keys = sorted(
+ list(questions.keys()),
+ key=lambda s: [
+ int(t) if t.isdigit() else t.lower() for t in re.split(r"(\d+)", s)
+ ],
+ )
+ for k in keys:
+ q = questions[k]
+ row = [
+ section,
+ k,
+ q["q"],
+ q["how_marked"],
+ q["type"],
+ q["raw_max"],
+ q["weighted_max"],
+ q["options"],
+ ]
+ rows.append(row)
+
+ context["rows"] = rows
+
+ return context
+
+ def render_to_response(self, context, **response_kwargs):
response = HttpResponse(
content_type="text/csv",
- headers={"Content-Disposition": f'attachment; filename="{file_name}"'},
+ headers={
+ "Content-Disposition": 'attachment; filename="questions_with_max_scores.csv"'
+ },
)
writer = csv.writer(response)
- for row in context["weighted_scores"]:
+ for row in context["rows"]:
writer.writerow(row)
return response