...
 
Commits (8)
  • Olivier's avatar
    M2: handful of improvements · 4e0f1000
    Olivier authored
    The code in this commit is contributed by Germán Santa Maria.
    
    * two ID numbers are created for each assignment: one for the assignment, one for the grading
    * custom ID number(s) now generated with email, so matriculation number is no longer required
    * use UTF-8 encoding in input/output
    4e0f1000
  • Olivier's avatar
    M6: handful of improvements · 72105c95
    Olivier authored
    72105c95
  • Olivier's avatar
    M2: modify hash function to work with email input, not matriculation · ca225e68
    Olivier authored
    The code in this commit is contributed by Germán Santa Maria.
    
    The hash function is modified to use the email address as input,
    instead of the matriculation number. It also outputs two code
    numbers (one for submission, one for grading) instead of one.
    This commit goes together with commit 4e0f1000
    ca225e68
  • Olivier's avatar
    Project is now funded with grant \o/ · 2515693d
    Olivier authored
    2515693d
  • Olivier's avatar
    Module M4: send assignments · a03e4bab
    Olivier authored
    The code in this commit is contributed by Germán Santa Maria.
    
    New module M4, able to send personalized emails with unique
    assignments as attachments
    a03e4bab
  • Olivier's avatar
    M2: cleanup · fa5cab8e
    Olivier authored
    * clarify nomenclature for first name, last name, full name
    * simplify file names
    * hint at how exercise is coded in practice (with key code redacted)
    * cleanup redundant documentation in lead comments
    * re-organize parts of code & expand search-and-replace section,
      following Germán’s work
    fa5cab8e
  • Olivier's avatar
    M3: greatly simplified structure, following improvements in M2 · 033fe089
    Olivier authored
    * the input of multiple tex files is no longer necessary
    * file names are simpler
    * Tex code is compiled twice, to allow for references in PDF
    
    Unfortunately, the README and the sample input and output folders
    are falling behind; I hope to fix this when time permits
    033fe089
  • Olivier's avatar
    M4: simpler file names, better email title · 1dfce1c2
    Olivier authored
    1dfce1c2
......@@ -4,97 +4,133 @@ Author: German Santa Maria
Script: Assignment Customizer
Description:
This script takes a tex file with marked parameters and a list of students
matriculation numbers to produce 1) a new tex file with custom parameters for
This script takes a tex file with marked parameters and a list of student
emails to produce 1) two new tex files with custom parameters for
each student and 2) a summary csv file with all the output assignments data.
"""
"""-------------------------Hard coded input--------------------------------"""
# Input files paths
parameters_path = 'script_input/parameters.tex'
canoniccl_path = 'script_input/canonical.csv'
assignment_info_path = 'script_input/assignment_info.csv'
# Output files paths
output_csv_path = 'script_output/output_assignments.csv'
output_parameters_path = 'script_output/parameters_ID_'
"""-------------------------------------------------------------------------"""
# Import modules
import csv # For reading csv files
import math
from hash_function import hash_function # Import hash_function
# Initialize arrays to store data
names = [] # Student first name
last_name = [] # Student last name
first_names = [] # Student first name
last_names = [] # Student last name
emails = [] # Student email address
matrikel_num = [] # Student matrikel number
markers = [] # Marker for the number to customize
range_min = [] # Maximum value that the custom number can take
range_max = [] # Minimum value that the custom number can take
decimals = [] # Number of decimals by which to round off the custom numbers
custom_ID = [] # Custom ID for the student
markers = [] # Marker for the number to customize
custom_ID_A = [] # Custom ID of the assignment for the student
custom_ID_G = [] # Custom ID of the grading file for the student
rand_matrix = [] # These are the random numbers to customize the assignments
custom_values = [] # Matrix with all custom values for each student assingment
# Open and read original parameters tex file
with open(parameters_path,'r') as f:
parameters = f.read()
"""-------------------------Hard coded input--------------------------------"""
# Input files paths
assignment_path = 'script_input/hot_air_exhaust_assignment.tex'
assig_solution_path = 'script_input/hot_air_exhaust_solution.tex'
canoniccl_path = 'script_input/canonical.csv'
# Output files paths
output_csv_path = 'script_output/output_assignments.csv'
output_assignment_path = 'script_output/assignment1_'
output_solution_path = 'script_output/solution1_'
# Markers for values
markers = ['XXspeed_in1XX', 'XXspeed_in2XX'
]
# Markers for student custom ID
marker_customID_A = 'XXcustomID_AXX'
marker_customID_G = 'XXcustomID_GXX'
marker_firstname = 'XXstudentfirstnameXX'
marker_lastname = 'XXstudentlastnameXX'
marker_fullname = 'XXstudentfullnameXX'
"""-------------------------------------------------------------------------"""
# Open and read the template assignment tex file
with open(assignment_path,'r', encoding="utf-8") as f:
assignment = f.read()
# Open and read the template solution tex file
with open(assig_solution_path,'r', encoding="utf-8") as f:
assig_solution = f.read()
# Store students data from csv file into variables
with open(canoniccl_path, newline='') as f:
with open(canoniccl_path, newline='', encoding="utf-8") as f:
canoniccl_reader = csv.reader(f)
next(canoniccl_reader) # Skip header [name, last name, email, matrikel_num]
next(canoniccl_reader) # Skip header (first line)
canoniccl_data = [row for row in canoniccl_reader]
for row in canoniccl_data:
names.append(row[0])
last_name.append(row[1])
first_names.append(row[0])
last_names.append(row[1])
emails.append(row[2])
matrikel_num.append(row[3])
# Store assignment data from csv file into variables
with open(assignment_info_path, newline='') as f:
assignment_info_reader = csv.reader(f)
next(assignment_info_reader) # Skip header [marker, min, max, decimals]
assignment_info_data = [row for row in assignment_info_reader]
for row in assignment_info_data:
markers.append(row[0])
range_min.append(float(row[1]))
range_max.append(float(row[2]))
decimals.append(int(row[3]))
# Generate anonymous ID and six specific numbers for each student
for number in matrikel_num:
[ID, rand_numbers]=hash_function(number)
custom_ID.append(ID)
for email in emails:
[ID_A, ID_G, rand_numbers]=hash_function(email)
custom_ID_A.append(ID_A)
custom_ID_G.append(ID_G)
rand_matrix.append(rand_numbers)
"""- (HARD CODED) parameters and solutions are specific for this excercise -"""
# Generate custom values for each student from the semi-random numbers
for i in range (0,len(names)):
counter = 0 # Initialize counter for random numbers indexes
temp_array = [] # Initialize temporary array
for j in range (0,len(markers)):
if counter>5: # This is for the case when len(markers)>random numbers
counter=0
temp_array.append(round((rand_matrix[i][counter]*(range_max[j]-
range_min[j])+range_min[j]), decimals[j]))
counter = counter+1
custom_values.append(temp_array)
for i in range (0,len(first_names)):
# Inlet #1 air speed [m/s]
min_speed_in1 = 10
max_speed_in1 = 20
speed_in1 = min_speed_in1+(max_speed_in1-min_speed_in1)*rand_matrix[i][0]
speed_in1 = round(speed_in1) # round-off decimals = 0
# (code redacted)
###########################################################################
# Store the custom assignment data fo all the students in a csv file
with open (output_csv_path,'w',newline='') as f:
# Store the calculated custom numbers for the assignments
custom_values.append([speed_in1, speed_in2,
])
"""-------------------------------------------------------------------------"""
# Store the custom assignment data for all the students in a csv file
with open (output_csv_path,'w',newline='', encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(['name','lastname','email','matrikel','custom_ID']+markers)
for i in range(0,len(names)):
writer.writerow([names[i],last_name[i],emails[i],matrikel_num[i],
custom_ID[i]]+custom_values[i])
writer.writerow(['first name','last name','email','custom_ID_A','custom_ID_G'] +
markers)
for i in range(0,len(first_names)):
writer.writerow([first_names[i],last_names[i],emails[i], custom_ID_A[i],
custom_ID_G[i]]+custom_values[i])
# Generate a tex file with custom parameters for each student
for i in range (0,len(names)): # For each student
temp_param=parameters
for i in range (0,len(first_names)): # For each student
temp_assig = assignment
for j in range (0, len(markers)): # Replace each marker by a custom value
temp_assig = temp_assig.replace(markers[j],str(custom_values[i][j]))
# Mark assignment with the student custom ID
temp_assig = temp_assig.replace(marker_customID_A, str(custom_ID_A[i]))
temp_assig = temp_assig.replace(marker_customID_G, str(custom_ID_G[i]))
# Mark assignment with the student first name
temp_assig = temp_assig.replace(marker_firstname, str(first_names[i]))
# Mark assignment with the student first name and last name
full_name = str(first_names[i]) + ' ' + str(last_names[i])
temp_assig = temp_assig.replace(marker_fullname, full_name)
# Produce the custom tex file for each student
with open ((output_assignment_path+custom_ID_A[i]+'.tex'),'w', encoding="utf-8") as f:
for line in temp_assig:
f.write(line)
# Generate modified tex files with solution to the assignment for each student
for i in range (0,len(first_names)): # For each student
temp_sol = assig_solution
for j in range (0, len(markers)): # Replace each marker by a custom value
temp_param=temp_param.replace(markers[j],str(custom_values[i][j]))
with open ((output_parameters_path+custom_ID[i]+'.tex'),'w') as f:
for line in temp_param:
temp_sol = temp_sol.replace(markers[j],str(custom_values[i][j]))
# Mark solution with the student custom ID
temp_sol = temp_sol.replace(marker_customID_A, str(custom_ID_A[i]))
temp_sol = temp_sol.replace(marker_customID_G, str(custom_ID_G[i]))
# Mark assignment with the student first name
temp_sol = temp_sol.replace(marker_firstname, str(first_names[i]))
# Mark solution with the student name
temp_sol = temp_sol.replace(marker_fullname, full_name)
# Produce the custom tex file for each student
with open ((output_solution_path+custom_ID_G[i]+'.tex'),'w', encoding="utf-8") as f:
for line in temp_sol:
f.write(line)
# End of script
......@@ -4,28 +4,35 @@ Author: German Santa Maria
Script: Hash Function
Description:
This script takes a matriculation number (for example:123456)
and uses a cryptographic hash function and that generates an eight-digit
anonymous ID to identify the assignments submitted by the students.
Based on the matriculation number, the script also generates an array
This script takes an email address (for example: student1@example.com)
and uses a cryptographic hash function and that generates two nine-digit
anonymous IDs to identify the student assignment and grade respectively.
Based on the input email address, the script also generates an array
with six random numbers that take values from 0 to 1.
"""
def hash_function(matrikel_num):
def hash_function(student_email):
import hashlib
salt = "insert_your_salt_here" # Salt for the hash function
hash_input = salt + str(matrikel_num) # Input for hash function
hash_input = salt + str(student_email) # Input for hash function
hf = hashlib.md5(hash_input.encode('utf-8')).hexdigest() # Digest hash
hf = str(int(hf,16)) # Convert from hexadecimal to integer string
custom_ID = hf[1:9] # Custom ID for student
# Generate Custom ID #1 for the student assignment
custom_ID_A = hf[1:9]
custom_ID_A = "A"+str(custom_ID_A)
# Generate Custom ID #2 for the student grade
custom_ID_G = hf[10:18]
custom_ID_G = "G"+str(custom_ID_G)
rand_n = [hf[9:12],hf[12:15],hf[15:18],hf[18:21],hf[21:24],hf[24:27]]
rand_n = list(map(int, rand_n)) # Random numbers from list to number
for i in range(len(rand_n)): # Turn random numbers into values from 0 to 1
rand_n[i] = rand_n[i]*0.001
return [custom_ID, rand_n]
return [custom_ID_A, custom_ID_G, rand_n]
# End of script
......@@ -3,61 +3,40 @@
# Input parameters
inputfolder='script_input'
outputfolder='script_output'
assignmentsourcefile="$inputfolder/assignment.tex"
solutionsourcefile="$inputfolder/solution.tex"
# Cleanup output folder
mkdir -v $outputfolder/old
rm -rvf $outputfolder/old/*
mv -vf $outputfolder/* $outputfolder/old/
# Create links to the source tex files in the current folder
# syntax: ln -s destination linkname
ln -svf "$assignmentsourcefile" assignment.tex
ln -svf "$solutionsourcefile" solution.tex
# Copy every tex file in the input folder into the output folder
cp -vf $inputfolder/*.tex $outputfolder/
cp -vf $inputfolder/*.png $outputfolder/
# Pick up all the files in input folder one by one
for parameterfile in "$inputfolder"/parameters_ID_*.tex; do
# What is the code number?
# Pick up parameter file name, strip all but number, call it $codenumber
codenumber=$(echo $parameterfile | sed "s/$inputfolder\/parameters_ID_//" | sed 's/\.tex//')
cd $outputfolder
for assignmentfile in assignment*.tex; do
echo "#"
echo "#"
echo "building PDFs with code number of $codenumber..."
# Define input and output file names for this problem ID
parameterfilepath=$(echo $inputfolder/parameters_ID_$codenumber.tex)
assignmentfilepath=$(echo $outputfolder/assignment_ID_$codenumber.pdf)
solutionfilepath=$(echo $outputfolder/solution_ID_$codenumber.pdf)
# Make a link called "parameters.tex" that points to the specific
# parameters file for this ID
ln -svf "$parameterfilepath" parameters.tex
# Add LaTeX variable at the end of parameters.tex, which is just
# the custom ID:
echo "\newcommand{\codenumber}{$codenumber}" >> parameters.tex
echo "building assignment PDF for $assignmentfile …"
# Empty up the tmp subfolder
rm -rf tmp/*
# Empty up the tmp subfolder, if it exists
rm -rvf tmp/*
# Make a tmp folder
mkdir -pv tmp
# Compile the actual PDF files:
# compile them with pdflatex
# then move the result to the output folder
pdflatex assignment.tex
mv -vf assignment.pdf $assignmentfilepath
pdflatex solution.tex
mv -vf solution.pdf $solutionfilepath
# Compile the actual PDF file:
pdflatex $assignmentfile
pdflatex $assignmentfile
# Cleanup after pdflatex: move temporary files to tmp subfolder
mv -vf *.aux *.bbl *.bcf *.blg *.ilg *.log *.nav *.nlo *.nls *.run.xml *.snm *.toc *.out *.fls *.fdb_latexmk *.spl *-blx.bib *-eps-converted-to.pdf *bib.bak tmp/
done
cd ../
# Remove remaining symlinks in main folder
rm -v parameters.tex assignment.tex solution.tex
# Remove tex files from output folder
rm -vf $outputfolder/*.tex
echo "end of script."
Script: email_assignments.py
============================
## Description
This script sends personalized emails with attachments.
## Script input
1. `output_assignments.csv`: This file contains the data from the students including their custom ID. It is the output file from the script [assign_customizer.py](../M2_customize_assignments/assign_customizer.py) from [module 2](../M2_customize_assignments).
1. All the pdf files with the assignments for the students, for example: `A12345678.pdf`. The contents of this folder are the output from the script [create_pdfs.sh](../M3_generate_assignments/create_pdfs.sh) from [module 3](../M3_generate_assignments).
1. Email settings are hard-coded in this script, except for the password. Sender email password is requested in a message box when the script is runing.
## Script output
1. One personalized email per student, at a controlled rate in order not to exceed local network limitations;
1. `sent_emails.csv` : file with the log of the sent emails
This script takes a list of the students who received customized assignments and the pdf files sent by them. Then it assigns two peers (reviewers) to each student, except to students that did not submit any solution to their assignment.
The output of the script is a `csv` file containing the consolidated data.
The input files should be placed in the `script_input` folder which is in the same path as the script. The output files will
be saved in the `script_output` folder.
## Script input
1. `output_assignments.csv`: This file contains the data from the students including their custom ID. It is the output file from the script [assign_customizer.py](../M2_customize_assignments/assign_customizer.py) from [module 2](../M2_customize_assignments).
1. All the `pdf` files with the solutions submitted by the students, named as: `customID.pdf` (for example `12345678.pdf`).
## Script output
1. `peers.csv`: file with the assigned peers for each student.
1. renamed pdfs, ready to be sent for grading: `ID_A.pdf``ID_G.pdf`
# -*- coding: utf-8 -*-
"""
Author: Germán Santa Maria
Script: Email Assignments
"""
"""-------------------------Hard coded input--------------------------------"""
# Input files paths
input_csv_path = 'script_input/output_assignments.csv'
assignments_pdfs_path = 'script_input/'
# Output files paths
output_csv_path = 'script_output/sent_emails.csv'
# (Hard-coded email settings)
host="myhost.example.com"
port = 587
sender_email = "sender@example.com"
sender_username = "username"
cc = "cc_address@example.com"
password = input("Type your password and press enter:")
# Delay time between sending two emails, in seconds
# For example, set 240 seconds if you are limited to less than 30 emails/hour
# and 40 seconds if the limit is less than 30 emails/10 min
delay_time = 45
"""-------------------------------------------------------------------------"""
# Import email sender function
import csv # For reading cvs files
import pathlib # For working with local paths
import smtplib, ssl # For email
import time # For timer
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# Initialize arrays to store data
names = [] # Student first name
last_name = [] # Student last name
emails = [] # Student email address
custom_ID_A = [] # Custom ID of the assignment for the student
custom_ID_G = [] # Custom ID of the grading file for the student
attachments = [] # Name of the attachment for each student
email_bodies = [] # Body of the email for each student
text_emails = [] # Array with the complete email text for each student
email_log = [] # Array with the log of successfully sent emails
# Store students data from csv file into variables
with open(input_csv_path, newline='') as f:
student_info_reader = csv.reader(f)
next(student_info_reader) # Skip header [name, last name, email, ...]
student_data = [row for row in student_info_reader]
for row in student_data:
names.append(row[0])
last_name.append(row[1])
emails.append(row[2])
custom_ID_A.append(row[3])
custom_ID_G.append(row[4])
for ID in custom_ID_A:
attachment_name = "assignment1_"+ID+".pdf"
attachments.append(attachment_name)
# Make sure that all the attachments are in the script_input folder by looking
# for a file with the correct file name
for i in range(0, len(custom_ID_A)):
file_to_search = pathlib.Path(assignments_pdfs_path+attachments[i])
if file_to_search.exists() != True:
raise Exception('The file ' +
assignments_pdfs_path+attachments[i]+
' was not found, no emails were sent')
"""-----------------Build email text for each student-----------------------"""
# Write a personalized email body for each student
standard_body = """Dear XXstudent_nameXX,
I have the pleasure of handing you (attached) your first coursework assignment in our course. I hope you will participate in the program, and have fun doing so! The coursework program has a total of three assignments, and is managed entirely by e-mail. Below, I explain how the program works and what to expect.
(insert email body here)
%%% Format of your answer
Please send one PDF file containing your complete answer. The PDF file should be named:
XXsubmissionfilenameXX
Send your file to this email address as an attachment (the title and content of the email do not matter; they will not be read).
Your answer will be graded anonymously, and so it must not contain *any* personally-identifying information: no name, no pseudonym, no matriculation number of any kind.
You can write your answer as you like (i.e. by hand, or by typing it up), and create a PDF with your preferred tools. It only matters that your answer arrives as a clearly-legible PDF document.
%%% Conclusion
This is a lot of explanations, but in principle, this should be fun, and the coursework is not very hard. If you have any question about how this program works, you can contact me at (redacted). I look forward to receiving your answer!
With best regards,
(author)
"""
for i in range(0, len(names)):
temp_body = standard_body.replace("XXstudent_nameXX", names[i])
submission_file_name = custom_ID_A[i] + ".pdf"
temp_body = temp_body.replace("XXsubmissionfilenameXX", submission_file_name)
email_bodies.append(temp_body)
# Compose email text for each student
for i in range(0, len(names)):
# Reciever email
receiver_email = emails[i]
# Email subject
subject = "Custom Assignment #1 ID_"+custom_ID_A[i]
# Email body
body = email_bodies[i]
# Attachment
filename = attachments[i]
# Set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Cc"] = cc
# Add body to email
message.attach(MIMEText(body, "plain"))
# Add attachment
attachment_path = assignments_pdfs_path+filename # Attachment folder path
# Open PDF file in binary mode
with open(attachment_path, "rb") as attachment:
# Add file as application/octet-stream
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header("Content-Disposition",
f"attachment; filename= {filename}",)
# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()
text_emails.append(text)
"""-------------------------------------------------------------------------"""
# Send an email to each student with the corresponding attachment
"""------------------------------Send emails--------------------------------"""
# Create a secure SSL context
context = ssl.create_default_context()
# Check if the login data is correct
try :
server = smtplib.SMTP(host = host, port = port)
server.starttls(context = context) #Encryption
server.login(sender_username, password)
print('Login to: '+ sender_email + ' was successful ')
except Exception as e:
# Print error message
print('Login to: '+ sender_email +
' was not successful, no emails were sent ')
raise Exception(e)
server.close() # Logout
# Send email to each student
for i in range(0, len(names)):
try :
# Reciever email
receiver_email = emails[i]
# Email content
text = text_emails[i]
# Logging into email server
server = smtplib.SMTP(host = host, port = port)
server.starttls(context = context) #Encryption
server.login(sender_username, password)
# Send email
server.sendmail(sender_email, [receiver_email, cc], text)
# Logout
server.close() # Logout
# Print email log
local_time = time.time()
time_sent = time.ctime(local_time)
log_m = 'Email was sent to: ' + receiver_email + ' time: ' + time_sent
print(log_m)
except Exception as e:
# Print error
log_m = 'Error while trying to send email to: ' + receiver_email
print(log_m)
print(e)
log_m = 'Email could not be sent, error: ' + str(e)
# Save log
email_log.append(log_m)
# Wait before sending the next email
time.sleep(delay_time)
# Store email log in a csv file
with open (output_csv_path,'w',newline='') as f:
writer = csv.writer(f)
writer.writerow(['name','lastname','email','custom_ID_A','custom_ID_G',
'log'])
for i in range(0,len(names)):
writer.writerow([names[i],last_name[i],emails[i],custom_ID_A[i],
custom_ID_G[i], email_log[i]])
"""-------------------------------------------------------------------------"""
# End of the script
......@@ -14,8 +14,10 @@ be saved in the `script_output` folder.
1. `output_assignments.csv`: This file contains the data from the students including their custom ID. It is the output file from the script [assign_customizer.py](../M2_customize_assignments/assign_customizer.py) from [module 2](../M2_customize_assignments).
1. All the `pdf` files with the solutions submitted by the students, named as: `submission_customID.pdf` (for example `submission_12345678.pdf`).
1. All the `pdf` files with the solutions submitted by the students, named as: `customID.pdf` (for example `12345678.pdf`).
## Script output
1. `peers.csv`: file with the assigned peers for each student.
1. renamed pdfs, ready to be sent for grading: `ID_A.pdf``ID_G.pdf`
......@@ -7,7 +7,8 @@ Script: Match Peers
"""-------------------------Hard coded input--------------------------------"""
# Input files paths
students_info_path = 'script_input/output_assignments.csv'
submission_pdfs_path = 'script_input/submission_' # pdf name : submission_ID.pdf'
submission_pdfs_name = 'submission_a1_' # Name of the submitted PDFs
submission_pdfs_path = 'script_input/' + submission_pdfs_name #pdf path
# Output files paths
peers_csv_path = 'script_output/peers.csv'
"""-------------------------------------------------------------------------"""
......@@ -16,16 +17,17 @@ peers_csv_path = 'script_output/peers.csv'
import csv # For reading csv files
import pathlib # For working with local paths
import random # For shuffling elements in arrays
from shutil import copyfile # To copy and rename the solution PDF files
# Initialize arrays to store data
names = [] # Student first name
last_name = [] # Student last name
emails = [] # Student email address
matrikel_num = [] # Student matrikel number
custom_ID = [] # Custom ID for the student
custom_ID_A = [] # ID that the student uses to submit the solution
custom_ID_G = [] # ID that the student uses to grade the peers
did_their_homework = [] # List of students that actually submitted a solution
ID_peer1 = [] # List of peers (first peer)
ID_peer2 = [] # List of peers (second peer)
email_peer1 = [] # List of peers (first peer)
email_peer2 = [] # List of peers (second peer)
random_peers1 = [] # Temporary list of possible peers to assign
random_peers2 = [] # Temporary list of possible peers to assign
......@@ -38,28 +40,28 @@ with open(students_info_path, newline='') as f:
names.append(row[0])
last_name.append(row[1])
emails.append(row[2])
matrikel_num.append(row[3])
custom_ID.append(row[4])
custom_ID_A.append(row[3])
custom_ID_G.append(row[4])
# Check which students actually submitted their solution to their assignment by looking
# for a file in the "script_input" folder with the name "submission_ID.pdf"
for i in range(0, len(custom_ID)):
file_to_search = pathlib.Path(submission_pdfs_path+custom_ID[i]+'.pdf')
# for a file in the "script_input" folder with the name "ID.pdf"
for i in range(0, len(custom_ID_A)):
file_to_search = pathlib.Path(submission_pdfs_path+custom_ID_A[i]+'.pdf')
did_their_homework.append(file_to_search.exists())
########## Assign two peers to each student who submitted a solution ##########
###############################################################################
# Allocate size of arrays "ID_peer1" and "ID_peer2"
ID_peer1 = ['']*len(names)
ID_peer2 = ['']*len(names)
email_peer1 = ['']*len(emails)
email_peer2 = ['']*len(emails)
# Create two temporary arrays with the peers to be assigned
for i in range(0,len(custom_ID)):
# Create two temporal arrays with the peers to be assigned
for i in range(0,len(emails)):
# Only students who submitted solution can recieve a peer role
if did_their_homework[i]==True:
random_peers1.append(custom_ID[i])
random_peers2.append(custom_ID[i])
random_peers1.append(emails[i])
random_peers2.append(emails[i])
copy_randp1 = random_peers1 # Save a copy of the array "random_peers1"
copy_randp2 = random_peers2 # Save a copy of the array "random_peers2"
......@@ -73,16 +75,16 @@ while counter<len(names) and iterations<10000:
# Make sure that peers are assigned only to students who submitted solution
if did_their_homework[counter]==False:
ID_peer1[counter] = 'None'
ID_peer2[counter] = 'None'
email_peer1[counter] = 'None'
email_peer2[counter] = 'None'
counter = counter + 1 # Update counter
iterations = iterations + 1 # Update iterations counter
continue # This jumps to the begining of the while loop
# Make sure that the last student in the list does not end up wihout a peer,
# in the case that the last peer to be assigned is themselves.
if len(random_peers1) == 1 and (custom_ID[counter] == random_peers1[0] or
custom_ID[counter] == random_peers2[0] or
if len(random_peers1) == 1 and (emails[counter] == random_peers1[0] or
emails[counter] == random_peers2[0] or
random_peers1[0] == random_peers2[0]):
random_peers1 = copy_randp1 # Resets "random_peers1" array
random_peers2 = copy_randp2 # Resets "random_peers2" array
......@@ -91,16 +93,16 @@ while counter<len(names) and iterations<10000:
continue
# If student==peer or peer1==peer2 repeat iteration
if (custom_ID[counter] == random_peers1[0] or
custom_ID[counter] == random_peers2[0] or
if (emails[counter] == random_peers1[0] or
emails[counter] == random_peers2[0] or
random_peers1[0] == random_peers2[0]):
iterations = iterations + 1 # Update iterations counter
continue # "counter" is not updated, iteration will be repeated
# If no conflicts are found assign peer1 and peer2
ID_peer1[counter] = random_peers1[0] # Assign first peer
email_peer1[counter] = random_peers1[0] # Assign first peer
random_peers1.pop(0) # Remove peer from "random_peers1"
ID_peer2[counter] = random_peers2[0] # Assign second peer
email_peer2[counter] = random_peers2[0] # Assign second peer
random_peers2.pop(0) # Remove peer from "random_peers2"
counter = counter + 1 # Update counter
iterations = iterations + 1 # Update iterations counter
......@@ -111,10 +113,17 @@ while counter<len(names) and iterations<10000:
# Store the students and their respective peers data in a csv file
with open (peers_csv_path,'w',newline='') as f:
writer = csv.writer(f)
writer.writerow(['name','lastname','email','matrikel','custom_ID',
'submitted','ID_peer1','ID_peer2'])
writer.writerow(['name','lastname','email','custom_ID_A','custom_ID_G',
'submitted','email_peer1','email_peer2'])
for i in range(0,len(names)):
writer.writerow([names[i],last_name[i],emails[i],matrikel_num[i],
custom_ID[i],str(did_their_homework[i]),ID_peer1[i],ID_peer2[i]])
writer.writerow([names[i],last_name[i],emails[i],custom_ID_A[i],
custom_ID_G[i],str(did_their_homework[i]),email_peer1[i],
email_peer2[i]])
# Copy and rename the submission PDF files (custom_ID_A -> custom_ID_G)
for i in range(0, len(custom_ID_A)):
if did_their_homework[i]==True:
copyfile(submission_pdfs_path + custom_ID_A[i] + ".pdf",
"script_output/" + submission_pdfs_name + custom_ID_G[i] + ".pdf")
# End of script
......@@ -9,11 +9,11 @@ A collection of scripts (in Python, bash, LaTeX) to build and assign peer-graded
1. grade those two answers, and submit them by email;
1. receive their own peer-assigned grade and the solution to their own exercise.
This tool is being developed in order to experiment with peer-graded coursework in the [Fluid Mechanics course](https://fluidmech.ninja/) of the Chemical & Energy Engineering program in the [Otto von Guericke University Magdeburg](https://www.ovgu.de), Germany.
This tool is being developed in order to experiment with peer-graded coursework in the [Fluid Mechanics course](https://fluidmech.ninja/) of the Chemical & Energy Engineering program in the [Otto von Guericke University Magdeburg](https://www.ovgu.de), Germany. Part of this work is funded by a grant of the University’s “Innovation in Studien und Lehre” program.
## Authors
Germán Santa-Maria, [Olivier Cleynen](https://ariadacapo.net/), with the kind financial support of the [LSS laboratory](http://www.lss.ovgu.de) of the university. Inspiration and guidance were gratefully received from [Mathias Magdowski](http://www.imt.ovgu.de/Das+Institut/Mitarbeiterinnen+und+Mitarbeiter/visitenkarten/magdowski__mathias-kat-lehrveranstaltungen-p-71.html) (see [[1]](https://www.slideshare.net/MathiasMagdowski/personalisierbare-aufgaben-und-anonymer-peerreview/), [[2]](https://www.researchgate.net/publication/330243310_Personalisierbare_Aufgaben_und_anonymer_Peer-Review)).
Germán Santa-Maria, [Olivier Cleynen](https://ariadacapo.net/), with the kind financial support of the [LSS laboratory](http://www.lss.ovgu.de) of the university, and of the university itself. Inspiration and guidance were gratefully received from [Mathias Magdowski](http://www.imt.ovgu.de/Das+Institut/Mitarbeiterinnen+und+Mitarbeiter/visitenkarten/magdowski__mathias-kat-lehrveranstaltungen-p-71.html) (see [[1]](https://www.slideshare.net/MathiasMagdowski/personalisierbare-aufgaben-und-anonymer-peerreview/), [[2]](https://www.researchgate.net/publication/330243310_Personalisierbare_Aufgaben_und_anonymer_Peer-Review)).
## License
......@@ -24,7 +24,7 @@ The code in this repository is published under the [GNU GPL v3](href="https://ww
A total of nine modules (six of which being locally-run scripts) are needed to run the project.
[ ] Module 1: Collect participant information
(in Moodle or through email)
(through email)
[x] [Module 2](M2_customize_assignments): Generate custom variables for assignments
(in Python)
......@@ -32,11 +32,11 @@ A total of nine modules (six of which being locally-run scripts) are needed to r
[x] [Module 3](M3_generate_assignments): Generate assignments and solutions
(in bash + LaTeX)
[ ] Module 4: Send assignments to students
[x] [Module 4](M4_send_assignments): Send assignments to students
(in Python)
[ ] Module 5: Receive submissions from students
(in Moodle or through email)
(through email)
[x] [Module 6](M6_match_peers): Match peers
(in Python)
......