Files
Copies/Readme.org
T

20 KiB
Raw Blame History

Script

Méta

Quézaco

Ce dépôt contient un certain nombre de script Python que j'utilise pour faire corriger des copies par Gemini.

  1. Les copies sont découpées suivant les labels des questions.
  2. Des requêtes de correction sont faites à Gemini, qui met une note entre 0 et 4 à la question, ajoute des commentaires, et encadre certaines parties de la réponse.
  3. Une version pdf annotée de la copie est générée, faite pour être éditée sur tablette tactile. Le correcteur humain peut modifier la note, supprimer des commentaires de l'IA, ou ajouter des remarques manuscrites.
  4. Ces annotations manuscrites sont lues et recompilées en une version de la copie pour l'élève.

Limitations

Pour l'instant, la correction est faite question par question : le LLM n'a accès qu'à la partie de la copie correspondant à une question fixée. Ça ne gère donc pas les cas où un argument a été donné dans une question précédente ou autre.

Noms de labels sous Windows

Les labels de questions sont utilisés comme noms de fichiers. Les labels contenant notamment : ne sont pas acceptés par Windows.

Requirements

Python

Libraries :

pip install numpy pandas matplotlib pillow pydantic pypdf pdf2image reportlab img2pdf pymupdf ftfy ezodf google

Poppler (for pdf2image)

Accès à Gemini

Il faut créer une clef API pour Gemini (pas facile).

Puis ajouter GEMINI_API_KEY à l'environnement avec :

export GEMINI_API_KEY=

ou éventuellement, la renseigner directement dans le fichier `config.py`.

Configuration

Copier `default_config.py` en `config.py`. Éventuellement le modifier.

Correction d'un paquet de copies

  1. Créer un fichier names dans le dossier courant, avec les noms/prénoms des élèves, un par ligne
  2. Créer un dossier correspondant à l'évaluation (Interro dans la suite)
  3. Suivre les instructions suivantes

Interface graphique

Lancer l'assistant avec :

python gui.py

On peut aussi ouvrir directement une évaluation avec python gui.py Interro. L'interface conserve l'état et l'historique des étapes dans Interro/.copienator-gui.json, et les sorties complètes dans Interro/.copienator/logs/. Une relance d'une étape antérieure ne supprime aucun résultat ; les étapes suivantes sont seulement marquées comme étant à revalider.

Après la réussite d'un script, l'interface sélectionne automatiquement l'étape suivante. Découper les réponses par question et Regrouper les réponses démarrent automatiquement lors de leur première visite. Avec le mode Correction immédiate, les trois étapes batch sont alors marquées Ignorée à leur première visite. L'étape de résolution manuelle est traitée de la même façon si manual_resolutions.txt est absent, vide ou ne contient que des commentaires. Ces automatismes ne se répètent pas lors d'un retour en arrière.

Le bouton Diagnostic… vérifie les modules Python, Poppler, LaTeX, PDF Arranger et la configuration Gemini. Sous Windows, les exécutables externes doivent être accessibles depuis PATH. Quand la création de liens symboliques ou physiques n'est pas autorisée, l'export et la préparation de A Rendre utilisent automatiquement une copie normale.

Les chemins des étapes personnelles peuvent être adaptés avec CURRENT_SCORE_ODS_PATH, FINAL_SCORE_ODS_PATH, FINAL_SCORE_OUTPUT_DIR et FINAL_SCORE_FONT_PATH dans config.py.

API commune pour les scripts

Le paquet copienator centralise les chemins d'une évaluation et les écritures JSON sûres. Un script ne devrait donc plus reconstruire les chemins partagés à la main :

from copienator import EvaluationWorkspace, atomic_write_json

workspace = EvaluationWorkspace("Interro")
atomic_write_json(workspace.correction_file, corrections)

EvaluationWorkspace.discover(path) retrouve également la racine d'une évaluation à partir d'un fichier ou d'un sous-dossier. Sa construction ne crée aucun fichier. La création explicite de .copienator/logs/ et .copienator/runs/ se fait avec workspace.ensure_control_directories(). Le chemin .copienator/state.sqlite3 est réservé à une future couche d'état transactionnelle.

atomic_write_json écrit d'abord dans un fichier temporaire situé dans le même dossier, synchronise son contenu, puis remplace la destination. Une interruption ne laisse donc pas un JSON partiellement écrit. Pour une modification concurrente de type lire-modifier-écrire, utiliser atomic_update_json : cet utilitaire protège l'opération complète avec un verrou inter-processus Linux/Windows.

Convention des scripts standardisés

Les scripts standardisés exposent build_parser(), run(...) et main(argv=None). Leur import ne lance aucun traitement. Ils acceptent le dossier d'évaluation comme premier argument positionnel, utilisent EvaluationWorkspace pour les chemins partagés et peuvent afficher la trace complète d'une erreur avec --verbose.

Les codes de sortie communs sont :

Code Signification
0 réussite
1 erreur de traitement
2 arguments invalides
3 évaluation ou prérequis invalides
4 traitement partiel, avec avertissements
130 interruption par l'utilisateur

Le GUI distingue notamment un traitement partiel d'un échec. Les scripts migrés vers cette convention sont actuellement :

  • copies_tools.py, grouping.py et verify_groups.py ;
  • post-correction.py et resolve_manual.py ;
  • page_splitter.py, cutleft.py, plotting.py et splitting_int.py ;
  • gemini_for_labels.py ;
  • gemini_for_enonce.py et enonce_info.py ;
  • correction.py, submit_batches.py, batch_status.py et fetch_batched_results.py ;
  • annotating.py, annotating_with_checks.py et annotating_by_label.py ;
  • reading_annotations.py et reading_grouped_annotations.py ;
  • export.py, import.py et giving_names.py.

Étapes et Script

Utiliser `python gui.py` ou `python gui.py Interro` pour lancer un GUI qui suit automatiquement les étapes décrites ci-dessous.

Prétraitement de l'énoncé

Dans le dossier de l'évaluation, mettre les fichiers suivants de l'évaluation :

`enonce.pdf`, `enonce.tex`, `correction.tex`.

  • `python gemini_for_enonce.py Interro` or `python gemini_for_enonce.py Interro restart`

    À partir des trois fichiers précédents, se charge de détecter les labels des questions et leur contenu.

    Les questions vont également être regroupées. Par la suite, quand des requêtes de corrections seront effectuées sur une question, seulement les énoncés des questions du groupe seront envoyés (et le corrigé de la question). Il faut donc que chaque groupe contienne si possible le contexte nécessaire pour comprendre la question.

    Une fenêtre s'ouvre pour permettre d'éditer le résultat. Ne pas hésiter à faire des groupes plus gros que les groupes par défaut.

    Après relecture le script génère :

    • un fichier `labels` avec les labels des questions
    • Un dossier `Text` avec le contenu textuel des questions, regroupées.
    • Un dossier `Sol` avec le contenu textuel du corrigé, question par question.
    • Un dossier `Text2`, qui compile un fichier `.tex` pour chaque question (utilisé pour compiler un rendu pdf du corrigé pour chaque question)
    • Un dossier `Sol2`, qui compile un fichier `.tex` pour chaque correction de chaque question.
    • Un dossier `Persp` avec des instruction de barème pour chaque question.

    Éventuellement : vérifier et modifier les barèmes dans `Persp`.

  • Alternative personnelle : `python enonce_info.py Interro` Ces deux commandes suivent la convention des scripts standardisés. Leur import ne lance aucun traitement et les erreurs partielles sont distinguées des échecs. Les réponses d'extraction mises en cache par gemini_for_enonce.py et le fichier labels sont publiés atomiquement.

Prétraitement des copies

Mettre les copies scannées au format pdf dans Interro.

  1. python copies_tools.py rotate Interro (facultatif) Retourne tous les pdf de 180°, si la photocopie a été faite à l'envers.
  2. python copies_tools.py rename Interro change le nom des copies en Copie{id}.pdf
  3. python page_splitter.py Interro

    Découpe des copies A3 en pages A4, en retirant les pages vides.

    Pour chaque page double il est possible

    • de garder les deux pages
    • de ne garder qu'une des deux pages.
    • de jeter les deux pages
    • de déplacer la délimitation à droite/gauche

    Fix issues with python page_splitter.py Interro14/Copies/Copie01.pdf

    Le PDF transformé est construit dans un dossier temporaire. La copie produite et la sauvegarde dans Copies Originales sont ensuite installées avec rollback : une erreur conserve les deux versions précédentes. Une relance ciblée lit directement la sauvegarde originale sans la déplacer au préalable.

  4. python cutleft.py Interro Découpe la partie gauche des copies, là où il devrait y avoir les labels des exercices/questions. python cutleft.py Interro --fullpage to use the full page always. Rerun on a single file with python cutleft.py Interro/Copies/Copie01.pdf Les images et le fichier _schema.json d'une copie sont remplacés ensemble. Fermer l'outil juste après la dernière validation ne peut donc plus interrompre un thread de sauvegarde en arrière-plan.

Labelisation et regroupement

Optional : Set proxy with export HTTPS_PROXY="http://10.0.0.1:3128"

  1. python gemini_for_labels.py Interro, avec éventuellement --overwrite Fait des requêtes à Gemini pour identifier les labels des questions dans images générées à partir des parties gauches des copies. Une copie PDF ou une image précise de Cutleft peut également être ciblée. Plusieurs cibles de la même évaluation sont acceptées. Les parties d'une copie restent traitées séquentiellement afin de conserver les labels précédents comme contexte, tandis que les copies différentes sont traitées en parallèle. Chaque réponse JSON validée est écrite atomiquement. Une cible sans image correspondante produit le code de sortie 4.
  2. python plotting.py Interro

    Permet de vérifier visuellement les labels trouvés.

    • Sous linux, on peut faire e pour ouvrir le fichier .json et l'éditer a la main.
    • Quand un label est manquant, il est possible de cliquer sur l'image, ce qui copie les coordonnées dans le presse papier (sous linux…), puis on peut l'ajouter à la main.
    • Utilisation de `_`, `|…` et `…|` :

      • `|…` n'est pas arrêté verticalement par son type opposé.
      • `…|` est stoppé horizontalement par le `|…` le plus proche.

    Pour modifier une seule copie : python plotting.py Interro/Copies/Copie01.pdf

    Les coordonnées agrégées sont écrites atomiquement dans le JSON de la copie. Fermer la fenêtre avant la fin d'une copie ne remplace pas son JSON par un résultat incomplet.

    It also generates les Copie01.json, à partir des Copie01_01.json En cas de soucis, (par exemple les pages ne sont pas dans le bon ordre)

    • Réordonner les pages du fichier pdf
    • Rerun python cutleft.py Interro/Copie{id}
    • Rerun python gemini_for_labels.py Interro/Copie{id}
  3. python splitting_int.py Interro Découpe les copies suivant les exercices Peut-être appelé avec une seule copie. Les réponses d'une copie sont préparées dans un dossier temporaire, puis remplacent ensemble le dossier précédent. En cas d'erreur, l'ancienne version est conservée. Les réponses devenues obsolètes restent archivées dans le sous-dossier Missing.
  4. python grouping.py Interro Regroupe les mêmes questions de différentes copies en groupes de tailles raisonnables.
  5. Facultatif : python verify_groups.py Interro Vérifie que chaque réponse PDF apparaît bien dans les métadonnées des groupes. La commande renvoie un code non nul si une réponse est absente ou si la vérification est incomplète.

Correction et annotation

Optional : Set proxy with export HTTPS_PROXY="http://10.0.0.1:3128"

  1. Il faut créer des persp, pour indication de comment corriger, et relancer enonce_info.py
  2. python correction.py Interro --limit 240 OU python correction.py Interro/Par\ label/Ex\ 2/Group_1.jpg OU python correction.py Interro --overwrite

    Fais les requêtes de correction à Gemini.

    correction.py peut être relancé sans supprimer son état. Les fichiers correction.json et correction_progress.json sont mis à jour atomiquement. Avec --overwrite, leur version précédente reste en place jusqu'à la première écriture réussie de la nouvelle exécution. --reset est la seule option qui supprime explicitement cet état et restaure les fichiers *_old.pdf.

    L'argument limit limite le nombre de requêtes à Gemini Pro (chères), pour une version low cost, passer --limit 0, toutes les requêtes seront sur Gemini Flash.

    Pour diminuer le coût, il est possible de batch les requêtes, qui seront alors traitées sous au plus 24h.

    • python correction.py Interro --batch
    • OU python correction.py Interro --batch-from 'Ex 4'
    • python submit_batches.py Interro
    • python batch_status.py
    • python fetch_batched_results.py Interro
    • python correction.py Interro --deal-with-batched

    Les quatre commandes de ce flux suivent la convention des scripts standardisés. Les fichiers de requêtes et le résultat JSONL combiné sont publiés atomiquement : une interruption ne laisse pas de fichier final partiellement écrit. submit_batches.py conserve aussi les identifiants distants dans batch_jobs.json ; la récupération les utilise en priorité et garde la recherche par nom pour les anciens batchs. batch_status.py --download JOB --output resultat.jsonl permet aussi de télécharger atomiquement le résultat d'un job particulier.

  3. python post-correction.py Interro

    • Essaye de corriger des erreurs d'encodage/d'accents dans correction.json.
    • aussi échappe les `_` en dehors du mode math, pour LaTeX.
  4. Résolution manuel de conflits, s'il y en a.

    Edit `manual_resolutions.txt`. Use :

    • `->` or `x>` : Here set a pipe `|` before or after the new_label name
    • `-x` : replace the goal
    • `ss` : do nothing
    • `sx` : stay, and remove goal.
    • `xx` : move to goal.
    • `xs` : remove old, keep goal.

    Then call `python resolve_manual.py Interro`

  5. Call `python correction.py Interro refaire`.

Génération des copies annotées

  1. python annotating.py Interro (facultatif) Ajoute les annotations Gemini aux copies, enregistrées dans le dossier Anot. On peut passer l'argument --overwrite.

OU

  1. python annotating_with_checks.py Interro Ajoute les annotations Gemini, et des checkboxes à cocher. Enregistrées dans le dossier Bnot, --overwrite Une seule copie peut être ciblée avec, par exemple, python annotating_with_checks.py Interro/Copies/Copie01.pdf. Le mode --refaire exige un fichier refaire.json et écrit dans BRnot.

OU

  1. python annotating_by_label.py Interro dans BGnot Ajoute les annotations Gemini, et des checkboxes, et regroupe les réponses par question. Enregistrées dans BGnot Needs : label_groups file (made automatically by this function), qui dit quelles questions regrouper.

Dans ces trois modes, les métadonnées JSON sont écrites atomiquement. Lors d'une régénération, les nouvelles sorties sont préparées dans un dossier temporaire voisin. Une erreur de rendu conserve donc la sortie précédente ; pour BGnot --overwrite, le dossier complet n'est remplacé que si tous les groupes ont été produits.

  1. python export.py Interro BGnot (gestion perso) Cela déplace les groupes dans le dossier configuré par EXPORT_DIR (par défaut Export). Le second argument peut être BGnot, Bnot ou Anot et reste facultatif (BGnot par défaut). Anot exporte Concat.jpg ; les deux autres modes exportent Concat.pdf. Dans le GUI, seuls les dossiers présents sont proposés et le mode de la dernière génération d'annotations est présélectionné.

Il faut ensuite annoter les fichiers dans `EXPORT_DIR` avec une tablette graphique.

Lecture de la correction manuscrite

Before : vider le dossier configuré par IMPORT_DIR (par défaut Import), puis y copier ou synchroniser les fichiers depuis la tablette.

  1. python import.py Interro BGnot Une fois les corrections manuelles appliquées aux fichiers Concat.pdf, il faut enregistrer le fichier annoté au même endroit, sous le nom Concat_annotated.pdf. Comme pour l'export, le second argument accepte BGnot, Bnot ou Anot. Le GUI présélectionne le dossier utilisé lors du dernier export. Pour Anot, l'image est importée sous le nom Concat_annotated.jpg.
  2. python reading_annotations.py Interro Lit les Concat_annotated dans Bnot, regénère les copies avec les modifications. Les fichiers générés (score.json, Concat.jpg, etc.) sont préparés séparément puis installés ensemble. Les entrées du dossier Bnot restent en place et une erreur de génération conserve les anciennes sorties.

OU

  1. python reading_grouped_annotations.py Interro Idem, mais pour BGnot. Les tâches parallèles remontent leurs erreurs au processus principal au lieu de les ignorer.
  2. python giving_names.py Interro BGnot

    Crée un dossier A Rendre avec des liens symboliques vers

    • La copie à rendre
    • un fichier score.json qui contient les notes par question

    Si un nom est Unknown : renommer à la main le dossier et le fichier dedans.

  3. Éventuellement, faire des modifications manuelles aux score.json.

    Puis

    • `python reading_annotations.py update-score Interro`
    • `python reading_grouped_annotations.py update-score Interro`

    pour mettre à jour les scores dans les images.

  4. (gestion perso)

    • gestion_classe ne pour créer l'interro puis
    • gestion_classe we (set barème here)
    • python update_ods.py Interro ou python update_ods.py Interro --sum (en l'absence de barème)
    • gestion_classe re
    • gestion_classe wsent
    • python add_final_score.py Interro21 (this makes files in Server/copies)
  5. (gestion perso)

    • Deploy miqmacs-copies-assets, and
    • update the copies from miqmacs.fr/admin.
  6. (gestion perso) Impression d'une copie. Via Evince » print to pdf.

Autres

Recorrection d'une seule copie (peu testé)

!! Attention, refaire ne marchera pas si tu fais une annotation non groupée into refaire !!

  1. Redécoupage

    • python plotting.py InterroTest/Copie01.pdf
    • python splitting_int.py InterroTest/Copie20.pdf
  2. Créer refaire.json, avec un contenu comme

    [["Copie02", []],
     ["Copie01", ["Ex 1 : 1)"]]]
    
  3. Appeler correction avec refaire. Il doit créer des groupes individuels, faire des requêtes, et remplacer les corrections précédentes (à sauver ailleurs). Ou non, si tu veux le faire à la main.
  4. ?? Si je fais refaire, avant d'avoir créer les annotating with checks, que se passe-t-il ???
  5. Appeler annotating_with_checks.py --refaire --overwrite
  6. python export.py --refaire Interro24
  7. python import.py --refaire Interro24
  8. python reading_grouped_annotations.py --refaire Interro24 Avec --refaire, refaire.json et le dossier BRnot sont des prérequis obligatoires ; leur absence produit le code de sortie 3.

Exemple de replotting, refaire d'une copie

  1. replot it.
  2. `python splitting_int.py DS09VA/Copies/Copie25.pdf` this will get rid of old/new. !! Attention, et si ça dégage un new : bad bad bad.
  3. Make `refaire.json`, avec la copie, et les labels à refaire.
  4. `python correction.py DS09VA refaire`
  5. `python annotating_with_checks.py DS09VA refaire`
  6. `python import.py Interro24 refaire`