Commit 696b0cf4 by Stephen Sanchez

Refactor the methods for creating median scores

parent 468cf9db
......@@ -6,11 +6,10 @@ the workflow for a given submission.
"""
import copy
import logging
import math
from django.db import DatabaseError
from openassessment.peer.models import Assessment, AssessmentPart
from openassessment.peer.models import Assessment
from openassessment.peer.serializers import (
AssessmentSerializer, rubric_from_dict, get_assessment_review)
from submissions import api as submission_api
......@@ -246,9 +245,8 @@ def get_assessment_median_scores(submission_id, must_be_graded_by):
# found in an assessment.
try:
submission = Submission.objects.get(uuid=submission_id)
assessments = Assessment.objects.filter(
submission=submission
).order_by("scored_at")[:must_be_graded_by]
scores = Assessment.get_assessment_scores_by_criterion(submission, must_be_graded_by)
return Assessment.get_median_scores(scores)
except DatabaseError:
error_message = (
u"Error getting assessment median scores {}".format(submission_id)
......@@ -256,34 +254,6 @@ def get_assessment_median_scores(submission_id, must_be_graded_by):
logger.exception(error_message)
raise PeerAssessmentInternalError(error_message)
# Iterate over every part of every assessment. Each part is associated with
# a criterion name, which becomes a key in the score dictionary, with a list
# of scores. These collected lists of scores are used to find a median value
# per criterion.
scores = {}
median_scores = {}
for assessment in assessments:
for part in assessment.parts.all():
criterion_name = part.option.criterion.name
if criterion_name not in scores:
scores[criterion_name] = []
scores[criterion_name].append(part.option.points)
# Once we have lists of values for each criterion, sort each value and set
# to the median value for each.
for criterion, criterion_scores in scores.iteritems():
total_criterion_scores = len(scores[criterion])
criterion_scores = sorted(criterion_scores)
median = int(math.ceil(total_criterion_scores / float(2)))
if total_criterion_scores == 0:
criterion_score = 0
elif total_criterion_scores % 2:
criterion_score = criterion_scores[median-1]
else:
criterion_score = int(math.ceil(sum(criterion_scores[median-1:median+1])/float(2)))
median_scores[criterion] = criterion_score
return median_scores
def has_finished_required_evaluating(student_id, required_assessments):
"""Check if a student still needs to evaluate more submissions
......
......@@ -4,12 +4,14 @@ These models have to capture not only the state of assessments made for certain
submissions, but also the state of the rubrics at the time those assessments
were made.
"""
from collections import defaultdict
from copy import deepcopy
from hashlib import sha1
import json
from django.db import models
from django.utils.timezone import now
import math
from submissions.models import Submission
......@@ -206,6 +208,78 @@ class Assessment(models.Model):
def __unicode__(self):
return u"Assessment {}".format(self.id)
@classmethod
def get_median_scores(cls, scores):
"""Determine the median score in a dictionary of lists of scores
For a dictionary of lists, where each list contains a set of scores,
determine the median value in each list.
Args:
scores (dict): A dictionary of lists of int values. These int values
are reduced to a single value that represents the median.
Returns:
(dict): A dictionary with criterion name keys and median score
values.
Examples:
>>> scores = {
>>> "foo": [1, 2, 3, 4, 5],
>>> "bar": [6, 7, 8, 9, 10]
>>> }
>>> Attribute.get_median_scores(scores)
{"foo": 3, "bar": 8}
"""
median_scores = {}
for criterion, criterion_scores in scores.iteritems():
total_criterion_scores = len(scores[criterion])
criterion_scores = sorted(criterion_scores)
median = int(math.ceil(total_criterion_scores / float(2)))
if total_criterion_scores == 0:
criterion_score = 0
elif total_criterion_scores % 2:
criterion_score = criterion_scores[median-1]
else:
criterion_score = int(math.ceil(sum(criterion_scores[median-1:median+1])/float(2)))
median_scores[criterion] = criterion_score
return median_scores
@classmethod
def get_assessment_scores_by_criterion(cls, submission, must_be_graded_by):
"""Create a dictionary of lists for scores associated with criterion
Create a key value in a dict with a list of values, for every criterion
found in an assessment.
Iterate over every part of every assessment. Each part is associated with
a criterion name, which becomes a key in the score dictionary, with a list
of scores.
Args:
submission (Submission): Obtain assessments associated with this
submission
must_be_graded_by (int): The number of assessments to include in
this score analysis.
Examples:
>>> Attribute.get_assessment_scores_by_criterion(submission, 3)
{
"foo": [1, 2, 3],
"bar": [6, 7, 8]
}
"""
assessments = cls.objects.filter(
submission=submission).order_by("scored_at")[:must_be_graded_by]
scores = defaultdict(list)
for assessment in assessments:
for part in assessment.parts.all():
criterion_name = part.option.criterion.name
scores[criterion_name].append(part.option.points)
return scores
class AssessmentPart(models.Model):
"""Part of an Assessment corresponding to a particular Criterion.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment