From 8a5b6e6deef9ce34a5b20c7da8a0ac43a4d44d45 Mon Sep 17 00:00:00 2001 From: Lan Hui Date: Mon, 17 Jan 2022 16:44:11 +0800 Subject: analyze.py: refactoring --- analyze.py | 102 ++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 32 deletions(-) (limited to 'analyze.py') diff --git a/analyze.py b/analyze.py index a040e45..e657ce5 100644 --- a/analyze.py +++ b/analyze.py @@ -40,9 +40,9 @@ # could break down the task into several sub-tasks such that each # sub-task corresponds to only one course objective. # -# Copyright (C) 2019, 2020, 2021 Hui Lan +# Copyright (C) 2019, 2020, 2021 Lan Hui # -# Contact the author if you encounter any problems: Hui Lan +# Contact the author if you encounter any problems: Lan Hui import json import os @@ -55,12 +55,37 @@ _locale._getdefaultlocale = (lambda *args: ['zh_CN', 'utf8']) def get_task_information(fname): + ''' + Return a dictionary in the following form: + + { + "course.objectives": [ + "co1", + "co2", + "co3" + ], + "tasks": { + "lab": { + "co1": 30 + }, + "project": { + "co2": 20 + }, + "exam": { + "co3": 50 + } + } + } + ''' with open(fname) as json_data: d = json.load(json_data) return d def get_student_information(fname): + ''' + Return a list of tuples where each tuple contains (student_number, student_name) + ''' result = [] lineno = 1 with open(fname) as f: @@ -78,7 +103,7 @@ def get_student_information(fname): def get_max_score(d): ''' Return the maximum allowable score in a task. The maximum score is the sum of all values across all - course objectives. + course objectives (co's) associated to that task. ''' result = 0 for k in d: @@ -87,6 +112,7 @@ def get_max_score(d): def get_student_number(fname): + '''Return a dictionary where the keys are student numbers, and values are the scores for that task''' d = {} # If a file has BOM (Byte Order Marker) charater, stop. @@ -110,6 +136,7 @@ def get_student_number(fname): def make_individual_grade_files(grade_dir, task_dict, student_lst): if not os.path.exists(grade_dir): os.mkdir(grade_dir) + # Create a grade file for each task. If the task is exam, then exam.txt will be created in folder grade_dir for task in task_dict['tasks']: fname = task + '.txt' max_score = get_max_score(task_dict['tasks'][task]) @@ -159,6 +186,13 @@ def make_individual_grade_files(grade_dir, task_dict, student_lst): def make_score_dict(fname): + '''Return a dictionary in the following form: + { + "201930220235": "30", + "201930220320": "15" + }, + where the key is a student number, and the value is the student's score for that task + ''' d = {} f = open(fname) lines = f.readlines() @@ -174,6 +208,13 @@ def make_score_dict(fname): def get_scores_for_each_student(grade_dir, task_dict): + ''' + Return a dictionary where each key is a task, and each value is + a dictionary of students and their scores for each task. Example: + {'lab': {'201930220235': '30', '201930220320': '15'}, + 'project': {'201930220235': '20', '201930220320': '10'}, + 'exam': {'201930220235': '50', '201930220320': '25'}} + ''' d = {} for task in task_dict['tasks']: fname = task + '.txt' @@ -206,7 +247,7 @@ def get_objective_total(d): return result # [(objective1, value1), (objective2, value2), ...] -def check_availability(fname): +def check_file_availability(fname): if not os.path.exists(fname): print('The required file %s does not exist.' % (fname)) sys.exit() @@ -236,12 +277,14 @@ def all_gone(student_lst, lst): # main GRADE_DIR = 'grade' +GRADE_FILE = 'grade_file.xls' # output TASK_FILE = 'tasks.json' # required file containing course objectives and tasks. -check_availability(TASK_FILE) STUDENT_FILE = 'students.txt' # required file containing student numbers and student names. -check_availability(STUDENT_FILE) EXCLUDE_FILE = 'exclude.txt' -GRADE_FILE = 'grade_file.xls' # output +check_file_availability(TASK_FILE) +check_file_availability(STUDENT_FILE) + +# Make software banner software_information = 'Course Objective Fulfillment Calculator\nCopyright (C) 2019,2020,2022 Lan Hui (lanhui@zjnu.edu.cn)' n = max([len(s) for s in software_information.split('\n')]) banner = '%s\n%s\n%s' % ('-' * n, software_information, '-' * n) @@ -252,20 +295,19 @@ student_lst = get_student_information(STUDENT_FILE) make_individual_grade_files(GRADE_DIR, task_dict, student_lst) print('Seen %d students.' % (len(student_lst))) -# output results to terminal and file +# Output results to terminal head_lst = ['student.no', 'student.name'] task_lst = sorted(task_dict['tasks']) head_lst.extend(task_lst) -file_content = '\t'.join(head_lst) + '\tTotal\n' +file_content = '\t'.join(head_lst) + '\tTotal\n' # for a summary grade file score_dict = get_scores_for_each_student(GRADE_DIR, task_dict) -course_object_cumulative_score = {} -for co in task_dict['course.objectives']: - course_object_cumulative_score[co] = 0 +print(json.dumps(task_dict, indent=4)) +print(json.dumps(score_dict, indent=4)) -# Exclude some students from student list student_lst. The excluded students are specified in a file called exclude.txt. +# Exclude some students from the student list student_lst. The excluded students are specified in a file called exclude.txt. if os.path.exists(EXCLUDE_FILE): lst = [] print('Read excluded students from %s.' % (EXCLUDE_FILE)) @@ -276,45 +318,42 @@ if os.path.exists(EXCLUDE_FILE): if sno != '': lst.append(sno) for s in lst: - print('Ignore %s' % (s)) + print('Ignore student %s' % (s)) remove_a_student(student_lst, s) - assert all_gone(student_lst, lst) == True + assert all_gone(student_lst, lst) print('Still have %d students after excluding the undesired students specified in %s.' % (len(student_lst), EXCLUDE_FILE)) # Do statistics +course_object_cumulative_score = {} +for co in task_dict['course.objectives']: + course_object_cumulative_score[co] = 0 + for s in student_lst: sno = s[0] sname = s[1] - result = '%s\t%s\n' % (sno, sname) file_content += '%s\t%s' % (sno, sname) - total = 0 + total = 0 # total score for this student for task in task_lst: score = score_dict[task][sno] - total_score = get_max_score(task_dict['tasks'][task]) - if float(score) > total_score: - print('Warning: student %s\'s score greater than maximum score in task %s' % (sno + '_' + sname, task)) + max_task_score = get_max_score(task_dict['tasks'][task]) + if float(score) > max_task_score: + print('Error: student %s\'s score greater than maximum score in task %s' % (sno + '_' + sname, task)) sys.exit() else: - result += ' %s:%s\t' % (task, score) file_content += '\t%s' % (score) total += float(score) for co in task_dict['course.objectives']: if co in task_dict['tasks'][task]: - my_share = 1.0 * float(score) * task_dict['tasks'][task][co] / total_score + my_share = 1.0 * float(score) * (task_dict['tasks'][task][co] / max_task_score) course_object_cumulative_score[co] += my_share - result += ' [%4.1f] ' % (my_share) - else: - result += ' [%4.1f] ' % (0) - result += '\n' - result += ' ---\n Total:%4.1f\n' % (total) file_content += '\t%4.1f\n' % (total) -f = open(GRADE_FILE, 'w') -f.write(file_content) -f.close() -print('Check spreadsheet %s.' % (GRADE_FILE)) +# Make the summary grade file +with open(GRADE_FILE, 'w', encoding='utf-8') as f: + f.write(file_content) + print('Check the spreadsheet %s.' % (GRADE_FILE)) objective_total = get_objective_total(task_dict) @@ -327,4 +366,3 @@ for x in objective_total: print('Course objective %s is %.0f%% satisfied.' % (co, percentage)) except: print('Error: value = %4.1f, num_student = %4.1f' % (value, num_student)) - -- cgit v1.2.1