moodlefeedbackr-exams

How can I upload feedback generated with R/exams into Moodle?


I want to upload the HTML files generated by the nops_eval() command to Moodle.

I have found a very good explanation on how to do a bulk upload of feedback files to a Moodle Assignment: https://telsupport.brookes.ac.uk/articles/how-do-i-bulk-upload-feedback-files-to-a-moodle-assignment/

However, the naming convention that nops_eval() applies to the folders containing the HTML file is not the one I need. When I change a folder by hand to the following naming convention:

Fullname_idnumber_assignsubmission_file_

i.e., for example

Edward Green_6028662_assignsubmission_file_

it works for me (following the instructions above).

Downloading the grading worksheet (CSV) in our university gives me the full name and the id-number in the following format (one line per student, all info in one cell):

Identificativo,Nome,"Codice identificativo","Indirizzo email",Stato,Valutazione,"Valutazione massima","La valutazione può essere cambiata","Ultima modifica (consegna)","Ultima modifica (valutazione)",Commenti
Partecipante6028662,"EDWARD GREEN",789012,egreen@student.unversity.xx,"Nessuna consegna",,"100,00",Sì,-,-,

Is there a way I can tell nops_eval() to format the feedback folders accordingly?


Solution

  • Outline

    The "quick & dirty" solution is to proceed in the following two steps:

    1. Based on the grading CSV file exported from your system you need to prepare a CSV file that can be used as input for nops_eval().
    2. The ZIP output file from nops_eval() can then be loaded, transformed and re-saved to be suitable for import into Moodle.

    CSV preparation

    To obtain a CSV registration file as described in the official NOPS tutorial I will start out from the grading sheet you posted, say grading_sheet.csv, and produce the file Exam-2025-06-25.csv that can then be used as input for nops_eval().

    ## read grading sheet
    x <- read.csv("grading_sheet.csv")
    x <- x[, 3:1]
    names(x) <- c("registration", "name", "id")
    
    ## omit "Partecipante" in id column
    x$id <- gsub("Partecipante", "", x$id, fixed = TRUE)
    
    ## add a leading zero to the registration column
    x$registration <- sprintf("%07d", x$registration)
    
    ## write everything to semicolon-separated CSV file
    write.table(x, file = "Exam-2025-06-25.csv", sep = ";", quote = FALSE, row.names = FALSE)
    

    The output Exam-2025-06-25.csv has then the following format:

    registration;name;id
    0789012;EDWARD GREEN;6028662
    

    The reason for pre-fixing the registration with a zero is that that exams2nops() currently has a minimum seven digits but by using exams2nops(..., reglength = 6) you can at least fix the first digit to be 0. For more details see:

    In your case you could also use the id from Codice identificativo instead of the registration Identificativo for both registration and id. Not sure what is the most intuitive solution for your students.

    (Remark: It would also be possible to convert "EDWARD GREEN" to "Edward Green" programmatically if that is desired.)

    ZIP transformation

    When you run nops_eval() as described in the tutorial linked above, it will produce an output nops_eval.zip that contains an individual HTML report for the students with the file path

    6028662/Exam-2025-06-25.html

    that needs to be converted to

    EDWARD GREEN_6028662_assignsubmission_file_.html

    if I understand you correctly. You can do this via

    transform_zip_for_moodle("Exam-2025-06-25.csv", "nops_eval.zip")
    

    using the following function definition:

    transform_zip_for_moodle <- function(
      register = dir(pattern = "\\.csv$"),
      eval = "nops_eval.zip"
    ) {
      ## set up and switch to temporary directory
      odir <- getwd()
      tdir <- tempfile()
      dir.create(tdir)
      setwd(tdir)
      on.exit(setwd(odir))
    
      ## extract ZIP in temporary directory
      unzip(file.path(odir, "nops_eval.zip"))
    
      ## original and new file names
      x <- read.csv2(file.path(odir, register), colClasses = "character")
      ofile <- file.path(x$id, gsub("\\.csv$", ".html", register))
      nfile <- sprintf("%s_%s_assignsubmission_file_.html", x$name, x$id)
      if(!all(file.exists(ofile))) stop("the 'eval' zip file does not contain the html reports expected from the 'register' csv file")
    
      ## rename files, zip again, overwrite original ZIP file
      for(i in seq_along(ofile)) file.rename(ofile[i], nfile[i])
      zip("nops_eval.zip", nfile)
      file.copy("nops_eval.zip", file.path(odir, "nops_eval.zip"), overwrite = TRUE)
    
      ## return new file names in ZIP invisibly
      invisible(nfile)
    }
    

    Outlook

    Rather than applying the hot fix to the nops_eval.zip after it was created, it would be better to create the correct file in the first place. In principle, this is possible by setting up your own nops_eval_write_moodle() function and to plug that into nops_eval(..., flavor = "moodle"). In fact, one of the reasons that my co-author Kenji Sato modularized the nops_eval_write_xxx() flavors was to be able to plug in a Moodle writer. Unfortunately, we did not complete this work...probably because we wanted to implement too many other convenience features along with it, including a more modular HTML layout etc., and never were satisfied with the details.

    If anyone wants to set up a proper nops_eval_write_moodle() you should be able to write and test this on your own without modifying the exams package. And then you could let us know about this.