Commit 549b985e by Justin Riley

add course_id arg and csv output filename option

Massive code clean-up as well.
parent 203df998
#!/usr/bin/python
#
# Largely for 3.091-exam.
# Compute number of times a given problem has been attempted by a student,
# including StudentModuleHistory. Do this by walking through the course tree.
# For every assessment problem, look up all matching StudehtModuleHistory
# items. Count number of attempts passed, and number failed. Remove staff
# data.
#
# Compute number of times a given problem has been attempted by a student, including StudentModuleHistory.
# Do this by walking through the course tree. For every assessment problem, look up all matching
# StudehtModuleHistory items. Count number of attempts passed, and number failed. Remove staff data.
# Output table with: problem url_id, problem name, number students assigned,
# number attempts failed, number attempts succeeded
#
# Output table with: problem url_id, problem name, number students assigned, number attempts failed, number attempts succeeded
#
from courseware.module_tree_reset import *
from courseware.access import get_access_group_name
from django.core.management.base import BaseCommand
import json
import csv
import json
from optparse import make_option
#-----------------------------------------------------------------------------
from request_cache.middleware import RequestCache
from xmodule.modulestore.django import modulestore
from courseware.access import get_user_role
from courseware.module_tree_reset import ProctorModuleInfo
from courseware.models import StudentModule, StudentModuleHistory
from django.conf import settings
from xmodule.modulestore.django import modulestore
from django.dispatch import Signal
from request_cache.middleware import RequestCache
from django.core.cache import get_cache
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError
if True:
CACHE = get_cache('mongo_metadata_inheritance')
for store_name in settings.MODULESTORE:
CACHE = get_cache('mongo_metadata_inheritance')
for store_name in settings.MODULESTORE:
store = modulestore(store_name)
store.metadata_inheritance_cache_subsystem = CACHE
store.request_cache = RequestCache.get_request_cache()
modulestore_update_signal = Signal(providing_args=['modulestore', 'course_id', 'location'])
modulestore_update_signal = Signal(
providing_args=['modulestore', 'course_id', 'location'])
store.modulestore_update_signal = modulestore_update_signal
#-----------------------------------------------------------------------------
def ComputeStats():
pminfo = ProctorModuleInfo()
# get list of all problems
all_problems = []
stats = []
self = pminfo
if True:
for rpmod in self.rpmods:
assignment_set_name = rpmod.ra_ps.display_name
for ploc in rpmod.ra_rand.children:
problem = self.ms.get_item(ploc)
problem.assignment_set_name = assignment_set_name
all_problems.append(problem)
staffgroup = get_access_group_name(self.course, 'staff')
cnt = 0
class Stats(object):
class Stats(object):
def __init__(self):
self.nassigned = 0
self.nattempts = 0
self.npassed = 0
for problem in all_problems:
print problem.id
stat = Stats()
smset0 = StudentModule.objects.filter(module_state_key=problem.id, student__is_staff=False)
smset = smset0.exclude(student__groups__name=staffgroup)
def passed(state):
def passed(state):
if 'correct_map' not in state:
return False
if not state['correct_map']: # correct_map = {}
if not state['correct_map']:
return False
return all([x['correctness']=='correct' for x in state.get('correct_map').values()]) # must be all correct to pass
# must all be correct to pass
return all([x['correctness'] == 'correct' for x in
state.get('correct_map').values()])
def update_stats(sm, stat, history=False):
def update_stats(sm, stat, history=False):
if sm.grade is None:
return
state = json.loads(sm.state)
if not 'attempts' in state:
if 'attempts' not in state:
return
if not state.get('done', False):
return "notdone"
......@@ -93,23 +68,43 @@ def ComputeStats():
return "passed"
return "attempted"
def compute_stats(course_id, csv_file=None):
pminfo = ProctorModuleInfo(course_id)
all_problems = []
stats = []
staffgroup = get_user_role(User.objects.get(username='staff'), pminfo.course.id)
for rpmod in pminfo.rpmods:
assignment_set_name = rpmod.ra_ps.display_name
for ploc in rpmod.ra_rand.children:
problem = pminfo.ms.get_instance(pminfo.course.id, ploc)
problem.assignment_set_name = assignment_set_name
all_problems.append(problem)
for problem in all_problems:
stat = Stats()
smset0 = StudentModule.objects.filter(module_state_key=problem.id,
student__is_staff=False)
smset = smset0.exclude(student__groups__name__icontains=staffgroup)
for sm in smset:
stat.nassigned += 1
ret = update_stats(sm, stat)
if ret in ['passed', 'attempted']:
continue
smhset = StudentModuleHistory.objects.filter(student_module=sm)
states = [ json.loads(smh.state) for smh in smhset ]
okset = [ passed(x) for x in states ]
attempts = [ x.get('attempts', 0) for x in states]
states = [json.loads(smh.state) for smh in smhset]
okset = [passed(x) for x in states]
attempts = [x.get('attempts', 0) for x in states]
stat.nattempts += max(attempts)
if any(okset):
stat.npassed += 1
#print " assigned=%d, attempts=%d, passed=%d" % (nassigned, nattempts, npassed)
stats.append(dict(problem_id=problem.id,
print problem.id
print " assigned=%d, attempts=%d, passed=%d" % (
stat.nassigned, stat.nattempts, stat.npassed)
stats.append(dict(
problem_id=problem.id,
pset=problem.assignment_set_name,
problem_name=problem.display_name,
due=str(problem.due),
......@@ -118,23 +113,16 @@ def ComputeStats():
attempts=stat.nattempts,
passed=stat.npassed,
))
cnt += 1
#if cnt>5:
# break
if True:
dddir = settings.MITX_FEATURES.get('DOWNLOAD_DATA_DIR','')
fndir = dddir / (self.course.id.replace('/','__'))
dt = datetime.datetime.now().strftime('%Y-%m-%d-%H%M')
fn = fndir / "problem-stats-%s-%s.csv" % (self.course.id.split('/')[1], dt)
return stats
print "Saving data to %s" % fn
# fn = "stats.csv"
fieldnames = ['problem_id', 'pset', 'problem_name', 'due', 'max_attempts', 'assigned', 'attempts', 'passed']
fp = open(fn,'w')
writer = csv.DictWriter(fp, fieldnames, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL)
def write_stats(stats, csv_filename):
print "Saving data to %s" % csv_filename
fieldnames = ['problem_id', 'pset', 'problem_name', 'due', 'max_attempts',
'assigned', 'attempts', 'passed']
fp = open(csv_filename, 'w')
writer = csv.DictWriter(fp, fieldnames, dialect='excel', quotechar='"',
quoting=csv.QUOTE_ALL)
writer.writeheader()
for row in stats:
try:
......@@ -142,14 +130,26 @@ def ComputeStats():
except Exception as err:
print "Oops, failed to write %s, error=%s" % (row, err)
fp.close()
#-----------------------------------------------------------------------------
return
class Command(BaseCommand):
help = """Generate CSV file with problem attempts statistics;
CSV file columns include problem id, assigned, max_attempts, attempts, passed
for every problem in the course. Arguments: None. Works only on 3.091-exam"""
args = "<course_id>"
help = """Generate CSV file with problem attempts statistics; CSV file \
columns include problem id, assigned, max_attempts, attempts, passed for \
every problem in the course. Arguments: None. Works only on 3.091-exam"""
option_list = BaseCommand.option_list + (
make_option('--csv-output-filename',
dest='csv_output_filename',
action='store',
default=None,
help='Save stats to csv file'),
)
def handle(self, *args, **options):
ComputeStats()
if len(args) != 1:
raise CommandError("missing argument: <course_id>")
stats = compute_stats(args[0])
csv_output_filename = options['csv_output_filename']
if csv_output_filename:
write_stats(stats, csv_output_filename)
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