diff options
Diffstat (limited to 'analyze.py')
-rw-r--r-- | analyze.py | 102 |
1 files changed, 70 insertions, 32 deletions
@@ -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 <lanhui@zjnu.edu.cn>
+# Contact the author if you encounter any problems: Lan Hui <lanhui@zjnu.edu.cn>
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))
-
|