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