Créer un QCM et les corriger automatiquement, c’est une chose que l’on peut faire avec https://www.auto-multiple-choice.net/fr/install/, mais ceci n’est installable facilement que sur Linux ou MacOS. L’installer sous windows nécessite d’installer un émulateur Linux.
J’envisage donc de me lancer dans un projet, mais je ne sais pas trop où je vais aller…
Créer un QCM et les corriger automatiquement: l’idée générale
Dans cet article, je vous expliquais comment créer un QCM en \(\LaTeX\) à l’aide de Python, mais ce dernier n’était pas interactif.
Là, le projet est de créer des QCM interactifs (donc, sur lesquels ont peut choisir une réponse ou insérer un texte) qui pourraient être corriger automatiquement.
L’idée est de ne pas s’embêter, donc de partir d’un fichier texte classique avec une structure de la forme:
title=QCM sur n'importe quoi
style=text
Saisir un chiffre impair
1,3,5,7,9
style=qcm
Le triangle de dimensions 3 cm, 4 cm et 5 cm est-il rectangle ?
Oui,Non
Oui
Chaque question est déterminée par la ligne : « style=…« . On aurait le choix entre:
- « text » si la réponse attendue est à saisir au clavier
- « qcm » si la question est à choix multiple
Créer un QCM et les corriger automatiquement: le traitement en Python
Assumons le fait que le fichier précédent soit nommé questionnaire.txt.
Le premier script Python serait chargé de lire ce fichier, de construire un fichier \(\LaTeX\) et de le compiler.
Ensuite, il faut un second script Python pour lire le PDF complété et vérifier si les réponses sont correctes ou non.
Pour les deux scripts, il me faut une fonction commune, celle qui permet de lire le fichier texte (questionnaire.txt pour notre exemple), qui en extrait les données essentielles et les renvoie sous forme de dictionnaire. Ce sera la fonction suivante, que je vais mettre dans le fichier funcaux.py:
def parse_questionnaire(file_path): with open(file_path, 'r') as file: lines = file.readlines() data = {} current_style = None question = None options = None for line in lines: line = line.strip() if not line: continue if line.startswith('title='): data['title'] = line.split('=', 1)[1].strip() elif line.startswith('style='): current_style = line.split('=', 1)[1].strip() else: if current_style == 'text': if question is None: question = line else: data[question] = {'type': current_style, 'answers': line} question = None elif current_style == 'qcm': if question is None: question = line elif options is None: options = line.split(',') else: if question not in data: data[question] = {'type': current_style, 'options': options, 'correct_answer': line} question = None options = None return data
Construction du QCM en \(\LaTeX\)
Le premier script Python sera mis dans le fichier create_qcm.py:
import os from funcaux import parse_questionnaire """ Fonction qui créé le fichier tex """ def tex(dic, answer = False): title = False tex = r"""\documentclass{article} \usepackage[T1]{fontenc} \usepackage{fourier} \usepackage{hyperref} \usepackage{tabularx} \usepackage{cellspace} \setlength{\cellspacetoplimit}{3mm} \setlength{\cellspacebottomlimit}{1mm} \usepackage[hmargin=1cm, vmargin=2cm]{geometry} \newcounter{questions} \setcounter{questions}{0} \newcommand{\question}{\stepcounter{questions}\arabic{questions}} \setlength{\parindent}{0pt} \begin{document} """ if 'title' in questionnaire_data.keys(): tex += "\\title{"+dic['title']+"}\n\\date{}\\maketitle\n\begin{Form}\n" del dic['title'] elif answer == True: tex += "\\title{Correction}\n\\date{}\\maketitle\n" tex += r"""\begin{tabularx}{\linewidth}{|Sc|X|Sl|} \hline""" for key, value in dic.items(): if value['type'] == 'text': if answer == False: tex += "\\question & " + key + " & \\TextField[name=q\\arabic{questions},align=1, height=0.4em]{} \\\\\n\hline\n" else: tex += "\\question & " + key + " & "+value['answers']+" \\\\\n\hline\n" elif value['type'] == 'qcm': tex += "\\question & " + key + " & \\begin{minipage}{30mm}\n" if answer == False: tex += "\\ChoiceMenu[combo,name=q\\arabic{questions}, width=1em]{}{" for v in value['options']: tex += v + "," tex = tex[:-1] # pour supprimer la dernière virgule tex += "}\n" else: tex += value['correct_answer'] + "\n" tex += "\\end{minipage}\\\\\n" tex += r"""\hline \end{tabularx}""" if answer == False: tex += "\end{Form}\n" tex += "\end{document}" return tex if __name__ == '__main__': file_path = 'questionnaire.txt' questionnaire_data = parse_questionnaire(file_path) doc_tex = tex(questionnaire_data) with open('questionnaire.tex', 'w', encoding='utf-8') as file: file.write(doc_tex) # compilation PdfLaTeX dans le répertoire courant cmd = 'echo Compilation du fichier '+doc_tex cmd = "pdflatex -shell-escape -synctex=1 -interaction=nonstopmode questionnaire.tex" os.system(cmd) # Pour le QCM corrigé doc_tex_corr = tex(questionnaire_data,answer=True) with open('questionnaire_corr.tex', 'w', encoding='utf-8') as file: file.write(doc_tex_corr) # compilation PdfLaTeX dans le répertoire courant cmd = 'echo Compilation du fichier '+doc_tex cmd = "pdflatex -shell-escape -synctex=1 -interaction=nonstopmode questionnaire_corr.tex" os.system(cmd)
Le script génère via pdfLaTeX le PDF. Avec le fichier texte précédent, cela donne:
Si on choisit de le sauvegarder sous le nom de questionnaire_complete.pdf, on aura ceci:
Traitement du PDF complété en Python
Le second script est pis dans le fichier correction.py:
import pypdf import re from funcaux import parse_questionnaire def clean_value(value): # Supprimer les caractères de contrôle et les séquences d'échappement if isinstance(value, str): # Utiliser une expression régulière pour supprimer les caractères non alphabétiques value = re.sub(r'[^a-zA-Z]', '', value) return value def format_pdf_fields(fields): formatted_output = {} for key, value in fields.items(): if value.get('/FT') == '/Tx': # Champ de texte formatted_output[key] = value.get('/V', '') elif value.get('/FT') == '/Ch': # Champ de choix (combo ou liste) if '/V' in value: formatted_output[key] = value['/V'] return formatted_output def correction(file_path): # Récupération du formulaire du PDF rendu rendu = 'questionnaire_complete.pdf' reader = pypdf.PdfReader(rendu) output = reader.get_fields() # Formatage de la sortie formatted_output = format_pdf_fields(output) #print( formatted_output ) # Lecture du fichier texte questionnaire_data = parse_questionnaire(file_path) #print(questionnaire_data) # Analyse des réponses dico_correction = {} question = 0 for key, value in questionnaire_data.items(): if key != 'title': question += 1 key_output = f"q{question}" if value['type'] == 'text': possible_answers = [x for x in value['answers'].split(',')] if formatted_output[key_output] in possible_answers: dico_correction[key_output] = True else: dico_correction[key_output] = False if value['type'] == 'qcm': if formatted_output[key_output] == value['correct_answer']: dico_correction[key_output] = True else: dico_correction[key_output] = False return dico_correction if __name__ == '__main__': print( correction( 'questionnaire.txt' ) )
Ce dernier affiche:
{'q1': True, 'q2': True}
Et la suite…
Ce n’est qu’une base et si cela intéresse beaucoup de personnes, alors je pourrais faire évoluer ce projet.
On peut imaginer par exemple que chaque élève sauvegarde sont PDF avec le nom et le prénom, et l’envoie à l’enseignant(e). L’enseignant peut alors mettre les QCM dans un répertoire et faire tourner un script chargé de tout corriger.