diff options
| -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))
 -
 | 
