def get(self): ''' List problems. Sample arguments: course="CompoundProblems", set_id="Assignment1" Response: ''' course = self.get_argument('course') set_id = self.get_argument('set_id') query = ''' SELECT p.problem_id, p.source_file, p.value, COUNT(a.answer_id) AS attempt_count FROM {0}_problem AS p LEFT OUTER JOIN {0}_past_answer AS a ON a.set_id = p.set_id AND a.problem_id = p.problem_id WHERE p.set_id = %s GROUP BY COALESCE(a.problem_id, p.problem_id) ORDER BY problem_id ASC; '''.format(course) result = conn.query(query, set_id) hints_query = ''' SELECT p.problem_id, COUNT(h.id) as hint_count FROM {0}_problem as p LEFT OUTER JOIN {0}_hint as h ON h.set_id = p.set_id AND h.problem_id = p.problem_id AND h.deleted = 0 WHERE p.set_id = %s GROUP BY COALESCE(h.problem_id, p.problem_id) ORDER BY problem_id ASC; '''.format(course) hints = conn.query(hints_query, set_id) for i in range(len(result)): result[i]['hint_count'] = hints[i]['hint_count'] self.write(json.dumps(result))
def get(self): ''' List answers by part. Sample arguments: course="CompoundProblems", set_id="Assignment1", problem_id=1, user_id=iawwal Response: [{"answer_id": 123..., "answer_string":"42"}, ...] ''' only_counts = self.get_argument('counts', False) course = self.get_argument('course') if not only_counts: query_parts = ['select * from {0}_answers_by_part'.format(course)] query_parts.append( self.where_clause('set_id', 'problem_id', 'part_id', 'user_id')) query_parts.append('ORDER BY timestamp ASC;') else: query_parts = [ '''SELECT set_id, problem_id, part_id, count(id) as attempt_count FROM {course}_answers_by_part'''.format(course=course) ] query_parts.append( self.where_clause('set_id', 'problem_id', 'part_id', 'user_id')) query_parts.append('GROUP BY set_id, problem_id, part_id;') query = ' '.join(query_parts) result = conn.query(query) self.write(json.dumps(result, default=serialize_datetime))
def get(self): ''' List counts of students attempted, completed for problem part. Sample arguments: course="CompoundProblems", set_id="Assignment1" problem_id=1 Response: {"students_completed": 193.0, "students_attempted": 193} ''' course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = self.get_argument('problem_id') part_id = self.get_argument('part_id') status_query = ''' SELECT COUNT(user_id) as students_attempted, SUM(completed) as students_completed FROM (SELECT user_id, MAX(score) AS completed FROM {course}_answers_by_part WHERE set_id = '{set_id}' AND problem_id = {problem_id} AND part_id = {part_id} GROUP BY user_id) as stats; '''.format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id) result = conn.query(status_query) out = result[0] self.write(json.dumps(out))
def get(self): ''' List answers by part. Sample arguments: course="CompoundProblems", set_id="Assignment1", problem_id=1, user_id=iawwal Response: [{"answer_id": 123..., "answer_string":"42"}, ...] ''' only_counts = self.get_argument('counts', False) course = self.get_argument('course') if not only_counts: query_parts = ['select * from {0}_answers_by_part'.format( course)] query_parts.append(self.where_clause('set_id', 'problem_id', 'part_id', 'user_id')) query_parts.append('ORDER BY timestamp ASC;') else: query_parts = ['''SELECT set_id, problem_id, part_id, count(id) as attempt_count FROM {course}_answers_by_part'''.format(course=course)] query_parts.append(self.where_clause('set_id', 'problem_id', 'part_id', 'user_id')) query_parts.append('GROUP BY set_id, problem_id, part_id;') query = ' '.join(query_parts) result = conn.query(query) self.write(json.dumps(result, default=serialize_datetime))
def get_source(course, set_id, problem_id): source_file = conn.query('''select source_file from {course}_problem where problem_id={problem_id} and set_id="{set_id}"; '''.format(course=course, set_id=set_id, problem_id=problem_id))[0]['source_file'] pg_path = os.path.join(webwork_dir, 'courses', course, 'templates', source_file) with open(pg_path, 'r') as fin: pg_file = fin.read() return pg_file
def get(self): ''' Assigns a filter function to a given part and hint. ''' query = '''SELECT * FROM assigned_filters {WHERE}'''.\ format(self.where_clause('course', 'set_id', 'problem_id', 'part_id')) res = conn.query(query) self.write(json.dumps(res))
def check_hint_assignment(self, rows): ''' Given the course, user_id, set_id, problem_id, and pg_id, return a pandas DataFrame containing the rows in the mysql realtime_past_answers table that match the given args ''' # Convert the context of the student struggling on the particular part # to a pandas DataFrame df = pd.DataFrame(rows) # Go through each entry in the assigned_hint_filter table. # Find matches for query_template = ''' SELECT hint_filter.filter_name, assigned_hint_filter.hint_id, assigned_hint_filter.trigger_cond, hint.set_id, hint.problem_id FROM {{course}}_hint_filter AS hint_filter JOIN {{course}}_assigned_hint_filter as assigned_hint_filter ON assigned_hint_filter.hint_filter_id = hint_filter.id JOIN {{course}}_hint as hint ON assigned_hint_filter.hint_id = hint.id WHERE hint.set_id = '{{set_id}}' AND hint.problem_id = {{problem_id}} ''' query_rendered = Template(query_template).generate(**self.args) hint_filters_dict = dict((f.__name__, f) for f in hint_filters) assigned_hint_filters = conn.query(query_rendered) hint_ids_to_assign = set([]) for assigned_hint_filter in assigned_hint_filters: filter_name = assigned_hint_filter['filter_name'] hint_id = assigned_hint_filter['hint_id'] trigger_cond = assigned_hint_filter['trigger_cond'] hint_filter = hint_filters_dict[filter_name] self.args['hint_id'] = hint_id # Get a list of other places the hint has been assigned query_template = ''' select {{course}}_assigned_hint.* from {{course}}_hint, {{course}}_assigned_hint where {{course}}_assigned_hint.hint_id={{course}}_hint.id and {{course}}_hint.id = {{hint_id}} ''' query_rendered = Template(query_template).generate(**self.args) previous_hint_assignments = conn.query(query_rendered) if hint_filter(self.args, df, previous_hint_assignments, trigger_cond): hint_ids_to_assign.add(hint_id) return list(hint_ids_to_assign)
def add_problem_source(self, args): query_template = '''select source_file from {{course}}_problem where {{course}}_problem.set_id = "{{set_id}}" AND {{course}}_problem.problem_id = {{problem_id}} ''' query_rendered = Template(query_template).generate(**args) rows = conn.query(query_rendered) if len(rows) == 1: args['source_file'] = rows[0]['source_file'] else: args['source_file'] = None return args
def get_user_vars(course, set_id, problem_id): ''' Gets user specific PG variables from the webwork database ''' user_variables = conn.query('''SELECT * from {course}_user_variables WHERE set_id="{set_id}" AND problem_id = {problem_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id)) variables_df = pd.DataFrame(user_variables) if len(variables_df) == 0: logger.warn("No user variables saved for assignment %s, please run the save_answers script", set_id) return variables_df
def check_hint_assignment(self, rows): ''' Given the course, user_id, set_id, problem_id, and pg_id, return a pandas DataFrame containing the rows in the mysql realtime_past_answers table that match the given args ''' # Convert the context of the student struggling on the particular part # to a pandas DataFrame df = pd.DataFrame(rows) # Go through each entry in the assigned_hint_filter table. # Find matches for query_template = ''' select {{course}}_hint_filter.filter_name, {{course}}_assigned_hint_filter.hint_id from {{course}}_hint_filter, {{course}}_assigned_hint_filter where {{course}}_assigned_hint_filter.hint_filter_id = {{course}}_hint_filter.id ''' query_rendered = Template(query_template).generate(**self.args) hint_filters_dict = dict( (f.__name__, f) for f in hint_filters) assigned_hint_filters = conn.query(query_rendered) hint_ids_to_assign = set([]) for assigned_hint_filter in assigned_hint_filters: filter_name = assigned_hint_filter['filter_name'] hint_id = assigned_hint_filter['hint_id'] hint_filter = hint_filters_dict[filter_name] self.args['hint_id'] = hint_id # Get a list of other places the hint has been assigned query_template = ''' select {{course}}_assigned_hint.* from {{course}}_hint, {{course}}_assigned_hint where {{course}}_assigned_hint.hint_id={{course}}_hint.id and {{course}}_hint.id = {{hint_id}} ''' query_rendered = Template(query_template).generate(**self.args) previous_hint_assignments = conn.query(query_rendered) if hint_filter(self.args, df, previous_hint_assignments): hint_ids_to_assign.add(hint_id) return list(hint_ids_to_assign)
def get(self): ''' Sample arguments: course="CompoundProblems", hint_id=10 ''' query = ''' select hint_id, user_id from {course}_assigned_hint where hint_id={hint_id};'''.format( course=self.get_argument('course'), hint_id=self.get_argument('hint_id')) rows = conn.query(query) self.write(json.dumps(rows, default=serialize_datetime))
def get(self): ''' Sample arguments: course="CompoundProblems", problem_id = 1, part_id=1 ''' query = ''' select pg_id, user_id, hint_id from {course}_assigned_hint where set_id='{set_id}' and problem_id={problem_id};'''.format( course=self.get_argument('course'), set_id=self.get_argument('set_id'), problem_id=self.get_argument('problem_id')) rows = conn.query(query) self.write(json.dumps(rows, default=serialize_datetime))
def get(self): only_counts = self.get_argument('counts', False) course = self.get_argument('course') if not only_counts: query_parts = ['select * from {0}_answers_by_part'.format( course)] query_parts.append(self.where_clause('set_id', 'problem_id', 'part_id')) query_parts.append('ORDER BY timestamp ASC;') else: query_parts = ['''SELECT set_id, problem_id, part_id, count(id) as attempt_count FROM {course}_answers_by_part'''.format(course=course)] query_parts.append(self.where_clause('set_id', 'problem_id', 'part_id')) query_parts.append('GROUP BY set_id, problem_id, part_id;') query = ' '.join(query_parts) result = conn.query(query) self.write(json.dumps(result, default=serialize_datetime))
def get(self): ''' Export all data about a problem ''' course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = self.get_argument('problem_id') query = 'SELECT * from {0}_problem WHERE set_id = %s AND problem_id = %s'.format( course) result = conn.query(query, set_id, problem_id)[0] pg_path = result['source_file'] full_path = os.path.join(webwork_dir, 'courses', course, 'templates', pg_path) with open(full_path, 'r') as f: pg_file_contents = f.read() out = {} out['pg_file'] = pg_file_contents out['filename'] = full_path past_answers = conn.query( 'SELECT * from {0}_past_answer where set_id = %s AND problem_id = %s' .format(course), set_id, problem_id) out['past_answers'] = past_answers realtime_past_answers = conn.query( 'SELECT * from {0}_realtime_past_answer where set_id = %s AND problem_id = %s' .format(course), set_id, problem_id) out['realtime_past_answers'] = realtime_past_answers hints = conn.query( 'SELECT * from {0}_hint where set_id = %s AND problem_id = %s'. format(course), set_id, problem_id) out['hints'] = hints assigned_hints = conn.query( 'SELECT * from {0}_assigned_hint where set_id = %s AND problem_id = %s' .format(course), set_id, problem_id) out['assigned_hints'] = assigned_hints hint_feedback = conn.query( 'SELECT {0}_assigned_hint_feedback.* from {0}_assigned_hint_feedback INNER JOIN {0}_assigned_hint ON {0}_assigned_hint_feedback.assigned_hint_id = {0}_assigned_hint.id WHERE {0}_assigned_hint.set_id = %s AND {0}_assigned_hint.problem_id = %s' .format(course), set_id, problem_id) out['hint_feedback'] = hint_feedback self.write(json.dumps(out, default=serialize_datetime))
def post(self): ''' For authenticating users against a Webwork course. Arguments: course="CompoundProblems", user_id="iawwal", password="******", Returns: { "user_id": "iawwal", "permission": 5 } ''' data = tornado.escape.json_decode(self.request.body) course = data.get("course") user_id = data.get("username") password = data.get("password") query = '''SELECT * from {0}_password as passwd JOIN {0}_permission as perm ON perm.user_id = passwd.user_id WHERE passwd.user_id=%s'''.format(course) result = conn.query(query, user_id)[0] salt = result['password'] permission_level = result['permission'] if crypt(password, salt) == salt and permission_level > 0: # Password is correct and has higher than student privileges userdata = {"user_id": user_id, "permission": permission_level, } jwt_string = jwt.encode(userdata, jwt_key) response = {"message": "Successfully logged in", "token": jwt_string } else: response = {"message": "Incorrect username or password"} self.set_status(401) self.write(json.dumps(response)) self.flush() return
def get(self): ''' Export all data about a problem ''' course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = self.get_argument('problem_id') query = 'SELECT * from {0}_problem WHERE set_id = %s AND problem_id = %s'.format(course) result = conn.query(query, set_id, problem_id)[0] pg_path = result['source_file'] full_path = os.path.join(webwork_dir, 'courses', course, 'templates', pg_path) with open(full_path, 'r') as f: pg_file_contents = f.read() out = {} out['pg_file'] = pg_file_contents out['filename'] = full_path past_answers = conn.query( 'SELECT * from {0}_past_answer where set_id = %s AND problem_id = %s'.format(course), set_id, problem_id) out['past_answers'] = past_answers realtime_past_answers = conn.query( 'SELECT * from {0}_realtime_past_answer where set_id = %s AND problem_id = %s'.format(course), set_id, problem_id) out['realtime_past_answers'] = realtime_past_answers hints = conn.query( 'SELECT * from {0}_hint where set_id = %s AND problem_id = %s'.format(course), set_id, problem_id) out['hints'] = hints assigned_hints = conn.query( 'SELECT * from {0}_assigned_hint where set_id = %s AND problem_id = %s'.format(course), set_id, problem_id) out['assigned_hints'] = assigned_hints hint_feedback = conn.query( 'SELECT {0}_assigned_hint_feedback.* from {0}_assigned_hint_feedback INNER JOIN {0}_assigned_hint ON {0}_assigned_hint_feedback.assigned_hint_id = {0}_assigned_hint.id WHERE {0}_assigned_hint.set_id = %s AND {0}_assigned_hint.problem_id = %s'.format(course), set_id, problem_id) out['hint_feedback'] = hint_feedback self.write(json.dumps(out, default=serialize_datetime))
def get(self): ''' List counts of students attempted, completed for problem. Sample arguments: course="CompoundProblems", set_id="Assignment1" problem_id=1 Response: {"students": 350, "students_completed": 184, "students_attempted": 1 ''' course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = self.get_argument('problem_id') status_query = ''' SELECT COUNT(*) as students, SUM(CASE WHEN status=1 then 1 else 0 end) AS students_completed, SUM(attempted) AS students_attempted FROM {course}_problem_user WHERE set_id = '{set_id}' AND problem_id = {problem_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id) result = conn.query(status_query) out = result[0] self.write(json.dumps(out))
def post(self): ''' For authenticating users against a Webwork course. Sample arguments: course="CompoundProblems", user_id="iawwal", password="******", Returning: [ { "hint_id": 3," }, ... ] ''' data = tornado.escape.json_decode(self.request.body) course = data.get("course") user_id = data.get("username") password = data.get("password") query = 'SELECT * from {0}_password WHERE user_id=%s'.format(course) result = conn.query(query, user_id)[0] salt = result['password'] if crypt(password, salt) == salt: # Password is correct # TODO: Send along access levels too userdata = {"user_id": user_id, } jwt_string = jwt.encode(userdata, jwt_key) response = {"message": "Successfully logged in", "token": jwt_string } else: response = {"message": "Incorrect username or password"} self.set_status(401) self.write(json.dumps(response)) self.flush() return
def post(self): logger.debug('post starting') self.answer_exps = {} course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = int(self.get_argument('problem_id')) part_id = int(self.get_argument('part_id')) include_finished = (int(self.get_argument('include_finished', 1)) == 1) filter_function = self.get_argument('filter_function') pg_file = get_source(course, set_id, problem_id) # get the variables as set for this user. It seems that there is an alternative code for doing the same thing in the method vars_for_student user_variables = conn.query('''SELECT * from {course}_user_variables WHERE set_id="{set_id}" AND problem_id = {problem_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id)) self.user_variables = {row['name']: row['value'] for row in user_variables} #logger.debug('computing user vars. user_variables=%s, self_variables_df=%s'%(str(user_variables),str(self.variables_df))) if len(user_variables) == 0: logger.warn("No user variables saved for assignment %s, please run the save_answers script", set_id) # Get the answer from pg file self.part_answer = get_part_answer(pg_file, part_id) # Get attempts by part if include_finished: query = '''SELECT * from {course}_answers_by_part WHERE set_id="{set_id}" AND problem_id = {problem_id} AND part_id={part_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id) else: # This self join query idea comes from http://stackoverflow.com/a/4519302/90551 # You can do this more clearly with subqueries but it's super slow query = '''SELECT abp.* FROM {course}_answers_by_part as abp LEFT JOIN {course}_answers_by_part AS abp2 ON abp.problem_id = abp2.problem_id AND abp.set_id = abp2.set_id AND abp.part_id = abp2.part_id AND abp.user_id = abp2.user_id AND abp2.score = '1' WHERE abp.set_id='{set_id}' AND abp.problem_id={problem_id} AND abp.part_id={part_id} AND abp2.user_id IS NULL; '''.format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id) logger.debug('before sending sql query') answers = conn.query(query) logger.debug('after sending sql query') self.load_filters() func_name = filter_function[4:filter_function.index('(')] status=self.filter_bank.add_filter(func_name,filter_function) #This is the case when a filter which is saved and run immediately searchString = "Filter named: " + func_name + " already exists, the author of the function has to replace it" if status != None and status.find(searchString) != -1: print "Filter " + func_name + " which is in filter bank, is invoked " + status elif status!=None: print "ERROR LOADING FUNCTION"+status return status _stdout='' _hints=[] logger.warn("Environment keys before parsers.py main loop " , self.filter_bank.get_env_keys()) for a in answers: user_id = a['user_id'] attempt=a['answer_string'] etree = parse_and_eval(attempt) self.part_answer = replace_variables(self.user_variables, self.part_answer) # Get the correct answer and generate an etree for it. self.answer_etree = parse_and_eval(self.part_answer) #ans = self.answer_for_student(user_id) if etree: status,hint,output=self.filter_bank.exec_filter(func_name,{'attempt':attempt, 'att_tree':etree, 'answer': self.part_answer, 'ans_tree':self.answer_etree, 'variables':self.user_variables}) if status: logger.debug('exec_filter succeeded, attempt=%s,hint=%s,output=%s'%(attempt,hint,output)) _hints.append(hint) _stdout += output else: logger.debug('exec_filter failed attempt=%s,error=%s output=%s'%(attempt,hint,output)) else: logger.debug('filed to parse attempt=%s, etree=%s'%(attempt,str(etree))) out = { 'output': _stdout, 'matches': _hints } self.write(json.dumps(out)) logger.debug('finished post')
def get(self): ''' Parses all expressions for a given question Sample arguments: course='CSE103_Fall14', set_id='Week1', problem_id=1, part_id=1 include_finished=0 Response: ... ''' # Get correct answers logger.debug('Starting get') self.answer_exps = {} course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = self.get_argument('problem_id') part_id = int(self.get_argument('part_id')) include_finished = (int(self.get_argument('include_finished', 1)) == 1) source_file = conn.query(''' select source_file from {course}_problem where problem_id={problem_id} and set_id="{set_id}"; '''.format(course=course, set_id=set_id, problem_id=problem_id))[0]['source_file'] pg_path = os.path.join(webwork_dir, 'courses', course, 'templates', source_file) with open(pg_path, 'r') as fin: pg_file = fin.read() # name problem_id set_id user_id value user_variables = conn.query('''SELECT * from {course}_user_variables WHERE set_id="{set_id}" AND problem_id = {problem_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id)) self.variables_df = pd.DataFrame(user_variables) if len(self.variables_df) == 0: logger.warn("No user variables saved for assignment %s, please run the save_answers script", set_id) # raise tornado.web.HTTPError(500) answer_re = re.compile('\[__+\]{(?:Compute\(")?(.+?)(?:"\))?}') answer_boxes = answer_re.findall(pg_file) self.part_answer = answer_boxes[part_id-1] self.answer_ptree = parse_webwork(self.part_answer) # Get attempts by part if include_finished: query = '''SELECT * from {course}_answers_by_part WHERE set_id="{set_id}" AND problem_id = {problem_id} AND part_id={part_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id) else: # This self join query idea comes from http://stackoverflow.com/a/4519302/90551 # You can do this more clearly with subqueries but it's super slow query = '''SELECT abp.* FROM {course}_answers_by_part as abp LEFT JOIN {course}_answers_by_part AS abp2 ON abp.problem_id = abp2.problem_id AND abp.set_id = abp2.set_id AND abp.part_id = abp2.part_id AND abp.user_id = abp2.user_id AND abp2.score = '1' WHERE abp.set_id='{set_id}' AND abp.problem_id={problem_id} AND abp.part_id={part_id} AND abp2.user_id IS NULL; '''.format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id) logger.debug('Before sending SQL') answers = conn.query(query) logger.debug('After sending SQL') all_correct_terms = set() correct_terms_map = defaultdict(lambda: defaultdict(list)) incorrect_terms_map = defaultdict(lambda: defaultdict(list)) # Parse and evaluate all answers for a in answers: # {'user_id': u'acpatel', 'timestamp': datetime.datetime(2014, 10, 12, 2, 10, 19), 'id': 284488L, 'score': u'1', 'answer_string': u'C(55,6)', 'part_id': 2L, 'problem_id': 10L, 'set_id': u'Week2', 'answer_id': 199326L} user_id = a['user_id'] ans = self.answer_for_student(user_id) if a['score'] != '1': etree, nums = parsed(a['answer_string']) if etree and nums: correct_terms = self.correct_terms(nums, ans) all_correct_terms |= set(correct_terms) # logger.debug(set(correct_terms)) if a['user_id'] not in correct_terms_map[str(sorted(correct_terms))][a['answer_string']]: correct_terms_map[str(sorted(correct_terms))][a['answer_string']].append(a['user_id']) out = {} out['correct'] = correct_terms_map out['correct_terms'] = sorted(all_correct_terms) out['incorrect'] = incorrect_terms_map out['answer_ptree'] = str(self.answer_ptree) self.write(json.dumps(out, default=serialize_datetime)) logger.debug('Finished')
def post(self): """ Tests one student's answer against the filters defined for the problem part. For any filters which match, returns the matched hint(PGML) which should be inserted into the hint. """ course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = self.get_argument('problem_id') part_id = int(self.get_argument('part_id')) user_id = self.get_argument('user_id') answer_string = self.get_argument('answer_string') pg_file = self.get_source() # re.compile(r'\[__+\]{(?:Compute\(")?(.+?)(?:"\))?}') answer_re = re.compile('\[__+\]{(?:(?:Compute\(")(.+?)(?:"\))(?:.*)|(.+?))}') answer_boxes = answer_re.findall(pg_file) part_answer = answer_boxes[part_id-1][0] or answer_boxes[part_id-1][1] # Get student's variables, parse their answer, their correct answer user_variables = conn.query('''SELECT * from {course}_user_variables WHERE set_id="{set_id}" AND problem_id = {problem_id} AND user_id = "{user_id}"; '''.format(course=course, set_id=set_id, problem_id=problem_id, user_id=user_id)) user_variables = {row['name']: row['value'] for row in user_variables} part_answer = replace_variables(user_variables, part_answer) answer_etree = parse_and_eval(part_answer) etree = parse_and_eval(answer_string) answer_data = {'attempt': answer_string, 'att_tree': etree, 'answer': part_answer, 'ans_tree': answer_etree, 'variables': user_variables} logger.debug(answer_data) if etree == None: logger.error("att_tree is None") return if answer_etree == None: logger.error("ans_tree is None") return # #get conditional filter functions # conditional_filter_funcs = conn.query('''SELECT ff.id, ff.code, af.hint_id, af.course, af.set_id, # af.problem_id, af.part_id FROM filter_functions as ff # JOIN assigned_filters as af ON af.filter_function_id = ff.id # WHERE af.course='{course}' AND af.set_id='{set_id}' AND af.problem_id={problem_id} AND af.part_id={part_id} AND af.function_type='C';'''. # format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id)) # logger.info("Conditional Filter Functions:") # logger.info(conditional_filter_funcs) # #get universal filter functions # universal_filter_funcs = conn.query('''SELECT ff.id, ff.code, af.hint_id, af.course, af.set_id, # af.problem_id, af.part_id FROM filter_functions as ff # JOIN assigned_filters as af ON af.filter_function_id = ff.id # WHERE af.course='{course}' AND af.set_id='{set_id}' AND af.problem_id={problem_id} AND af.part_id={part_id} AND af.function_type='U';'''. # format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id)) # logger.info("Universal Filter Functions:") # logger.info(universal_filter_funcs) #logger.debug('Filters: %s', filter_funcs) # load from folder con_filter_funcs, uni_filter_funcs, time_filter_funcs = load_filters_from_folder(self.filter_bank, self.filters_dir) txt = "" correct_set_problem_part = str(set_id) + "_" + str(problem_id) + "_" + str(part_id) logger.info(user_id) logger.info(correct_set_problem_part) success = False filter_function_name = "" for func in con_filter_funcs: #if func.hint_id in hints_assigned: # continue logger.info("checking conditional") if correct_set_problem_part in func['name']: logger.info(func['name']) success,txt,_ = self.filter_bank.exec_filter(func['name'], answer_data) #self.exec_filter_func(func['code'], answer_data, user_variables) #TODO: remove the length check when things become reliable if txt != "" and success and len(txt) < 100: filter_function_name = func['name'] break else: success = False if not success: logger.info("checking universal") for func in uni_filter_funcs: #logger.info(func['name']) success,txt,_ = self.filter_bank.exec_filter(func['name'], answer_data)#self.exec_filter_func(func['code'], answer_data, user_variables) #TODO: remove the length check when things become reliable if txt != "" and success and len(txt) < 100: filter_function_name = func['name'] break else: success = False if not success: logger.info("checking timebased") # Only run filters if at least 3 answers and at least 3 minutes since first answer try: answer_count = conn.get('''SELECT COUNT(*) as count from {course}_answers_by_part {WHERE};''' .format(course=course, WHERE=self.where_clause('set_id', 'problem_id', 'part_id', 'user_id'))).get('count') first_answer = conn.get('''SELECT timestamp from {course}_answers_by_part {WHERE} ORDER BY timestamp ASC LIMIT 1;''' .format(course=course, WHERE=self.where_clause('set_id', 'problem_id', 'part_id', 'user_id'))).get('timestamp') last_answer = conn.get('''SELECT timestamp from {course}_answers_by_part {WHERE} ORDER BY timestamp DESC LIMIT 1;''' .format(course=course, WHERE=self.where_clause('set_id', 'problem_id', 'part_id', 'user_id'))).get('timestamp') diff = last_answer-first_answer logger.info('diff and count') logger.info(diff) logger.info(answer_count) if answer_count > 3 and diff > timedelta(minutes=3): logger.info("sending time based") for func in time_filter_funcs: if correct_set_problem_part in func['name']: logger.info(func['name']) success,txt,_ = self.filter_bank.exec_filter(func['name'], answer_data)#self.exec_filter_func(func['code'], answer_data, user_variables) #TODO: remove the length check when things become reliable if txt != "" and success and len(txt) < 300: filter_function_name = func['name'] break else: success = False except Exception, e: logger.warn('Error: ' + str(e)) self.write(json.dumps({})) return
def post(self): logger.debug('post starting') self.answer_exps = {} course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = int(self.get_argument('problem_id')) part_id = int(self.get_argument('part_id')) include_finished = (int(self.get_argument('include_finished', 1)) == 1) filter_function = self.get_argument('filter_function') pg_file = get_source(course, set_id, problem_id) # get the variables as set for this user. It seems that there is an alternative code for doing the same thing in the method vars_for_student user_variables = conn.query('''SELECT * from {course}_user_variables WHERE set_id="{set_id}" AND problem_id = {problem_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id)) self.variables_df = pd.DataFrame(user_variables) logger.debug( 'computing user vars. user_variables=%s, self_variables_df=%s' % (str(user_variables), str(self.variables_df))) if len(self.variables_df) == 0: logger.warn( "No user variables saved for assignment %s, please run the save_answers script", set_id) # Get the answer from pg file self.part_answer = get_part_answer(pg_file, part_id) # Get attempts by part if include_finished: query = '''SELECT * from {course}_answers_by_part WHERE set_id="{set_id}" AND problem_id = {problem_id} AND part_id={part_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id) else: # This self join query idea comes from http://stackoverflow.com/a/4519302/90551 # You can do this more clearly with subqueries but it's super slow query = '''SELECT abp.* FROM {course}_answers_by_part as abp LEFT JOIN {course}_answers_by_part AS abp2 ON abp.problem_id = abp2.problem_id AND abp.set_id = abp2.set_id AND abp.part_id = abp2.part_id AND abp.user_id = abp2.user_id AND abp2.score = '1' WHERE abp.set_id='{set_id}' AND abp.problem_id={problem_id} AND abp.part_id={part_id} AND abp2.user_id IS NULL; '''.format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id) logger.debug('before sending sql query') answers = conn.query(query) logger.debug('after sending sql query') a_filter_bank = filter_bank() status = a_filter_bank.add_filter('answer_filter', filter_function) if status != None: print "ERROR LOADING FUNCTION" + status return status _stdout = '' _hints = [] for a in answers: user_id = a['user_id'] attempt = a['answer_string'] ptree, etree = parse_eval(attempt) user_vars = self.variables_df if len(user_vars) > 0: student_vars = dict( user_vars[user_vars['user_id'] == user_id][[ 'name', 'value' ]].values.tolist()) else: student_vars = {} # Replace variable with values for key in student_vars: if key in self.part_answer: self.part_answer = self.part_answer.replace( key, str(student_vars[key])) # Get the correct answer and generate a ptree and an etree for it. self.answer_ptree, self.answer_etree = parse_eval(self.part_answer) ans = self.answer_for_student(user_id) if ptree and etree: status, hint, output = a_filter_bank.exec_filter( 'answer_filter', (attempt, ptree, etree, self.part_answer, self.answer_ptree, self.answer_etree, student_vars)) if status: logger.debug( 'exec_filter succeeded, attempt=%s,hint=%s,output=%s' % (attempt, hint, output)) _hints.append(hint) _stdout += output else: logger.debug( 'exec_filter failed attempt=%s,error=%s output=%s' % (attempt, hint, output)) else: logger.debug('filed to parse attempt=%s, ptree=%s, etree=%s' % (attempt, str(ptree), str(etree))) out = {'output': _stdout, 'matches': _hints} self.write(json.dumps(out)) logger.debug('finished post')
def get(self): ''' Parses all expressions for a given question Sample arguments: course='CSE103_Fall14', set_id='Week1', problem_id=1, part_id=1 include_finished=0 Response: ... ''' # Get correct answers logger.debug('Starting get') self.answer_exps = {} course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = self.get_argument('problem_id') part_id = int(self.get_argument('part_id')) include_finished = (int(self.get_argument('include_finished', 1)) == 1) source_file = conn.query(''' select source_file from {course}_problem where problem_id={problem_id} and set_id="{set_id}"; '''.format(course=course, set_id=set_id, problem_id=problem_id))[0]['source_file'] pg_path = os.path.join(webwork_dir, 'courses', course, 'templates', source_file) with open(pg_path, 'r') as fin: pg_file = fin.read() # name problem_id set_id user_id value user_variables = conn.query('''SELECT * from {course}_user_variables WHERE set_id="{set_id}" AND problem_id = {problem_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id)) self.variables_df = pd.DataFrame(user_variables) if len(self.variables_df) == 0: logger.warn( "No user variables saved for assignment %s, please run the save_answers script", set_id) # raise tornado.web.HTTPError(500) answer_re = re.compile('\[__+\]{(?:Compute\(")?(.+?)(?:"\))?}') answer_boxes = answer_re.findall(pg_file) self.part_answer = answer_boxes[part_id - 1] self.answer_ptree = parse_webwork(self.part_answer) # Get attempts by part if include_finished: query = '''SELECT * from {course}_answers_by_part WHERE set_id="{set_id}" AND problem_id = {problem_id} AND part_id={part_id}; '''.format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id) else: # This self join query idea comes from http://stackoverflow.com/a/4519302/90551 # You can do this more clearly with subqueries but it's super slow query = '''SELECT abp.* FROM {course}_answers_by_part as abp LEFT JOIN {course}_answers_by_part AS abp2 ON abp.problem_id = abp2.problem_id AND abp.set_id = abp2.set_id AND abp.part_id = abp2.part_id AND abp.user_id = abp2.user_id AND abp2.score = '1' WHERE abp.set_id='{set_id}' AND abp.problem_id={problem_id} AND abp.part_id={part_id} AND abp2.user_id IS NULL; '''.format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id) logger.debug('Before sending SQL') answers = conn.query(query) logger.debug('After sending SQL') all_correct_terms = set() correct_terms_map = defaultdict(lambda: defaultdict(list)) incorrect_terms_map = defaultdict(lambda: defaultdict(list)) # Parse and evaluate all answers for a in answers: # {'user_id': u'acpatel', 'timestamp': datetime.datetime(2014, 10, 12, 2, 10, 19), 'id': 284488L, 'score': u'1', 'answer_string': u'C(55,6)', 'part_id': 2L, 'problem_id': 10L, 'set_id': u'Week2', 'answer_id': 199326L} user_id = a['user_id'] ans = self.answer_for_student(user_id) if a['score'] != '1': etree, nums = parsed(a['answer_string']) if etree and nums: correct_terms = self.correct_terms(nums, ans) all_correct_terms |= set(correct_terms) # logger.debug(set(correct_terms)) if a['user_id'] not in correct_terms_map[str( sorted(correct_terms))][a['answer_string']]: correct_terms_map[str( sorted(correct_terms))][a['answer_string']].append( a['user_id']) out = {} out['correct'] = correct_terms_map out['correct_terms'] = sorted(all_correct_terms) out['incorrect'] = incorrect_terms_map out['answer_ptree'] = str(self.answer_ptree) self.write(json.dumps(out, default=serialize_datetime)) logger.debug('Finished')
def post(self): """ Tests one student's answer against the filters defined for the problem part. For any filters which match, returns the matched hint(PGML) which should be inserted into the hint. """ course = self.get_argument('course') set_id = self.get_argument('set_id') problem_id = self.get_argument('problem_id') part_id = int(self.get_argument('part_id')) user_id = self.get_argument('user_id') answer_string = self.get_argument('answer_string') pg_file = self.get_source() # re.compile(r'\[__+\]{(?:Compute\(")?(.+?)(?:"\))?}') answer_re = re.compile('\[__+\]{(?:(?:Compute\(")(.+?)(?:"\))(?:.*)|(.+?))}') answer_boxes = answer_re.findall(pg_file) part_answer = answer_boxes[part_id-1][0] or answer_boxes[part_id-1][1] # Get student's variables, parse their answer, their correct answer user_variables = conn.query('''SELECT * from {course}_user_variables WHERE set_id="{set_id}" AND problem_id = {problem_id} AND user_id = "{user_id}"; '''.format(course=course, set_id=set_id, problem_id=problem_id, user_id=user_id)) user_variables = {row['name']: row['value'] for row in user_variables} # Replace variable with values for var in user_variables: if var['name'] in part_answer: part_answer = part_answer.replace(var['name'], str(var['value'])) answer_ptree, answer_etree = parse_and_eval(part_answer, user_variables) ptree, etree = parse_and_eval(answer_string) answer_data = {'attemp': answer_string, 'attemp_tree': ptree, 'answer': part_answer, 'answer_tree': answer_ptree, 'variables': user_variables} # #get conditional filter functions # conditional_filter_funcs = conn.query('''SELECT ff.id, ff.code, af.hint_id, af.course, af.set_id, # af.problem_id, af.part_id FROM filter_functions as ff # JOIN assigned_filters as af ON af.filter_function_id = ff.id # WHERE af.course='{course}' AND af.set_id='{set_id}' AND af.problem_id={problem_id} AND af.part_id={part_id} AND af.function_type='C';'''. # format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id)) # logger.info("Conditional Filter Functions:") # logger.info(conditional_filter_funcs) # #get universal filter functions # universal_filter_funcs = conn.query('''SELECT ff.id, ff.code, af.hint_id, af.course, af.set_id, # af.problem_id, af.part_id FROM filter_functions as ff # JOIN assigned_filters as af ON af.filter_function_id = ff.id # WHERE af.course='{course}' AND af.set_id='{set_id}' AND af.problem_id={problem_id} AND af.part_id={part_id} AND af.function_type='U';'''. # format(course=course, set_id=set_id, problem_id=problem_id, part_id=part_id)) # logger.info("Universal Filter Functions:") # logger.info(universal_filter_funcs) #logger.debug('Filters: %s', filter_funcs) # load from folder self.filter_bank = filter_bank() basepath = os.path.dirname(__file__) logger.info(basepath) filters_path = os.path.join(basepath, "../filters/") self.filter_bank.import_filters_from_files(filters_path) files = self.filter_bank.get_env_keys() con_filter_funcs = [] uni_filter_funcs = [] time_filter_funcs = [] for f in files: if f[0] != '_': ### remove the doc string from code ### code = self.filter_bank.get_code(filters_path, f) while '\"\"\"' in code: code = code[code.index('\"\"\"')+3:] if f[0] == "C": con_filter_funcs += [{'name': f, 'code': code, 'doc': self.filter_bank.get_docstring(f)}] elif f[0] == "U": uni_filter_funcs += [{'name': f, 'code': code, 'doc': self.filter_bank.get_docstring(f)}] elif f[0] == "T": time_filter_funcs += [{'name': f, 'code': code, 'doc': self.filter_bank.get_docstring(f)}] txt = None for func in con_filter_funcs: #if func.hint_id in hints_assigned: # continue _,txt,_ = self.filter_bank.exec_filter(func['name'], answer_data) #self.exec_filter_func(func['code'], answer_data, user_variables) if txt: break if not txt: for func in uni_filter_funcs: _,txt,_ = self.filter_bank.exec_filter(func['name'], answer_data)#self.exec_filter_func(func['code'], answer_data, user_variables) if txt: break if not txt: # Only run filters if at least 3 answers and at least 5 minutes since first answer try: answer_count = conn.get('''SELECT COUNT(*) as count from {course}_answers_by_part {WHERE};''' .format(course=course, WHERE=self.where_clause('set_id', 'problem_id', 'part_id', 'user_id'))).get('count') first_answer = conn.get('''SELECT timestamp from {course}_answers_by_part {WHERE} ORDER BY timestamp ASC LIMIT 1;''' .format(course=course, WHERE=self.where_clause('set_id', 'problem_id', 'part_id', 'user_id'))).get('timestamp') last_answer = conn.get('''SELECT timestamp from {course}_answers_by_part {WHERE} ORDER BY timestamp DESC LIMIT 1;''' .format(course=course, WHERE=self.where_clause('set_id', 'problem_id', 'part_id', 'user_id'))).get('timestamp') diff = last_answer-first_answer if answer_count > 3 and diff > timedelta(minutes=5): for func in time_filter_funcs: _,txt,_ = self.filter_bank.exec_filter(func['name'], answer_data)#self.exec_filter_func(func['code'], answer_data, user_variables) if txt: break except Exception, e: logger.warn('Error: ' + e) self.write(json.dumps({})) return