Copyright 2005 Peter Gacs Licensed under the Academic Free Licence version 2.1 A COURSE MANAGEMENT SYSTEM Version 1.0 Peter Gacs The system I use is a combination of the following types of technical ingredients: - the Unix system of granting access privileges, including a setuid program. But I suppose that one could find substitutes under other operating systems. - Certain directories, with appropriate names and permissions - A setuid program called /usr/local/bin/gsubmit - Some Python scrips and modules. Python is a scripting language like Perl, but more self-explanatory: you can probably just read the scripts and understand them. None of the programs is interactive, and none of them uses the Web. Currently, I feel that this is an advantage, but I may change my opinion later. HOMEWORK ASSIGNMENTS There must be a group cs410grades (if the course is cs410). The files connected with grading will have permissions for this group that they do not have for others. If the course is $course then currently, there is a directory $homework = /cs/course/$course/current/homework. There is a directory called $spool and a log directory called $log: $spool = $homework/spool, $log = $homework/log. File $homework/to-grader instructs the grader about the system in general. There is a grading directory: $grading = $HOME/$course/grading. Assume that the assignment is $hw and the login name of the student is $login. If there is a directory $submittable = $homework/submittable then only files (possibly subdirectories) whose names match the name of a file in $submittable can be submitted. The assignment has been submitted via gsubmit. This program is installed in the system, it is also in $HOME/src/grading/gsubmit/gsubmit. You had to type gsubmit $course $hw where $hw is also the name of a sub directory of your current directory, and all the files to be submitted are in it. If the student is $login then the assignment will be in $spool/$login/$hw. For each file and subdirectory submitted inside $hw, there will be an entry in $log/$login showing the submission time. The teacher uses several programs (Python scripts). These are in $lib = $HOME/lib/grading which can be put into the teacher's path. If you are reading in an ftp directory, then $lib = scripts These scripts also use a module called Util.py which is in the directory ../python The teacher uses the program $lib/cp_to_each_spool to place a file GRADE-$hw into each submission directory, e.g. into $spool/$login/$hw. If the student made a mistake and there is no such directory then GRADE-$hw will be in $spool/$login. The grade file contains lines like ... (30): The grader writes scores after the colons. The assignment $hw is originally in the directory $HOME/$course/hw/$hw. This directory contains a file specifying the assignment: typically, it has the name $hw.txt For the grader, there is a directory $homework/hw/$hw. It contains the following files and directories. Here, if the solution has several parts, $part is one of those parts. $grader is the username of the grader. $hw.txt to-grade, a file explaining how to grade this assignment solution-$part testfiles-$part testdir-$grader, linked to "sandbox". gr_start, a script setting up the grading gr_start typically does not change from one assignmnent to the next, but you can customize it. There is a script called $lib/hw_gr_setup which recreates all these items from a similar structure in $HOME/$course/hw/$hw. It is laborious and error-prone to do all this copying and setting the correct group ownerships by hand. gr_start --student $student [--part $part] creates or cleans testdir-$grader, then copies there the submission files of the student for part $part (from $spool/$student/$hw), and the testfiles from testfiles-$part. A prototype gr_start is also in $lib. The grader can then run the student's programs on the prepared testfiles in sandbox. The grader should make the notes on the gradefile and sourcefiles that are in the original student submission directory. It is best to make a small note after every non-full score in the grade file, since students will ask for the reason anyway. The grader is possibly helped by some other scripts in testfiles-$part automating the compilation and testing. These are described in to-grade. E.g., t 0 would compile the programs, t 1, t 2, etc. would run them on various kinds of input and would explain themselves. The grader will sometimes mess up the permissions and ownership of files you edit in the submission directories. He should run $homework/ch_grpown (the full pathname must be given) after every grading session. This looks up each file and subdirectory under $spool owned by the grader, and changes its group to the grades group name. It also makes the file group-readable, -writeable, and if a directory, -executable and with the setgid bit and sticky bit. The program ch_grpown is also to be found in $lib. There is a file $grading/due-dates which shows for each homework the maximum score, the due date and the late date. A typical due-dates file is included in the directory ./sample-files The program $lib/late_discount does the following: - adds up the scores, and writes a line in GRADE-03 showing it. - computes the discounted score and writes a line in GRADE-03 showing it. The score decreases linearly to 0 between the due date and the late date. - Writes a file $grading/$hw in which it records the login name and the homework score of each student. - If the log file does not contain entries of the form "submitted $hw/..." then the comment "No submission record" is added to the line. To check for possible problems, the file $grading/$hw.trouble contains the login name of each student who has a submitted homework without a corresponding logfile entry. EXAMS AND GRADE COMPUTATION Get a classlist from the UIS www page, copy it into $grading/class_list Use $lib/convert_bu_rst < class_list > rst which converts classlist to the format needed for all the later scripts. So, $grading/rst is the student list which is the starting point of other grade lists. A typical rst file is like the file ./sample-files/midterm with blank in place of the score, grade and comment column. There is a Python module called $lib/course It handles the reading and writing of student records for the exams, and the grade computation. It uses the following kinds of files, each in the directory $grading. exams, containing a list of exams (including individual homeworks like 01, 02, ... as well as computed "exams", like "homework", or "course", and also possibly "participation"). There is also an exam called rst, which is simply a list of student data (login, name, buid). For some exams, there is an associated weight, written after the name. A typical exams file is in ./sample-files. For each exam $exam: a file called $grading/$exam which contains lines with the following information: Logname Score Grade Name Buid Comment where Name and Comment have no format restrictions, and Score and Grade may be missing (but when Grade is there, it must be preceded by Score). The record ID is the login name. The program late_discount (see above) creates such files for the homework. For each exam, there may also be a file called $grading/$exam.xcp. This is useful when $exam is created by some computation that needs to be overridden for some students. Whatever is in $exam.xcp overrides whatever is in $exam. A file $grading/$exam.cuts containing lines of the form B+ 83 where 83 is the score above which B+ begins. A typical cuts file is in ./sample-files. You use the course module via Python scripts. A sample Python script doing everything (when you uncomment and modify suitable lines) is in ./sample-files/grcomp Here is its short help message: Usage: grcomp --help This message. grcomp --custom The script contains some custom action which it will perform. grcomp [--course ] --exam When --course is not given, it is assumed that the current directory is ~//grading. The scores of exam are sorted. If a cuts file is present, the grades are also computed. can also be the name of a list of exams. In this case, each exam in the list will be processed. grcomp [--course ] --target The target exam will be (re)computed from its constituents. Adding the option --student will display the calculations for this particular student. Compute grades for exam $exam Write in the scores. Use grcomp --exam $exam to sort them. Look at $exam and decide the cuts. Write $exam.cuts. Use grcomp --exam $exam again to get the grades written in. You can show the students the $exam.cuts file. Its rightmost column shows the grade distribution. It is also possible to produce a sorted list of all the scores. For, say, the midterm, the command course.get_exam("midtem").print_records(only_distr = 1) will produce the file midterm.distr containing exactly this. Publish grades for exam $exam Create some exam objects and correponding exam files with new names, like course_grade, midterm_exam, etc: (an entry in the "exams" file and a corresponding file of records). These exams will not be changed by later recomputations, and can contain the final record of the grades. For example, you can write into them an I grade, which the scripts cannot handle. Then use method grcomp --mail from grcomp to mail the grades of whatever exams you want. For this, first go into grcomp and rewrite the mail_exams to correspond to the exams you want mailed, and to possibly change the beginning message of the mailing. There is also a grcomp --mail-test, for testing the mailing first on yourself. (For this, add yourself as a student.) Or, use course.submit_grades() in grcomp. This puts the grade for the exam into the student's submission directory, in a file GRADE-$exam, so the student can look at it using gsubmit. Compute the total homework grade Add up the homework scores for each student, forming the file $grading/homework sorted by score. One can specify that the worst homework score be deleted. (This serves usefully in place of sickness exceptions. Actually, I delete the worst one of homeworks 1 to 5, but not 6, since otherwise almost nobody would do 6). If in a $hw file, a student record has the comment "Delete." then that homework will be deleted for that student, before the worst score is deleted (this may be punishment for cheating). All the above can be done by the functions proc_hw() and proc1to5() in grcomp, or simply using grcomp --target hw1to5 grcomp --target homework Compute the final score The weighted average of all exams will be computed. Each exam score will be divided by the cut score for B, then multiplied by the weight and added up. For some students who missed an exam of an assignment, the weight of one exam may be increased compared to the weight of another. This is done by adding a comment like "weight: 25" to the student's line in the exams whose weight changes. The number in the comment overrides the original weight of the exam for this student. The result should be in the exam file course The cuts in file course.cuts are computed in the same way from the individual exam and homework cuts. The grades are then computed on the basis of the cuts, the result is in course All this is done with grcomp --target course You can override the cuts in file course.cuts.xcp (since course.cuts will be recomputed if your repeat the process) and call grcomp --target course again. It is recommended to save this final result into another file called e.g. course_grade, to shield it from the effect of possible subsequent recomputations. Compute the maximum of two midterm exams If the final midterm grade is the higher of two midterm grades, the "maximum" of the midterm scores is also needed. It is not obvious what it should be since the two midterms may have different maxima and statistics. In method course.exams_better() it is computed as follows. The average of the midterm cuts is determined by averaging the cuts of the two midterms. The score value of the new grade is the point between the ends of its own interval of average cuts obtained proportionally from the score of the better grade (from its position within its own interval). If both grades were the same, the better proportion is taken.