Compare commits

..

No commits in common. "26d02b098774ff43f080b2bee05323d83dcd45a7" and "81be54b5f7f7aeb102da184b6570769816ac6cbd" have entirely different histories.

7 changed files with 16 additions and 83 deletions

View File

@ -1,7 +1,7 @@
#+title: Script #+title: Script
#+author: Sébastien Miquel #+author: Sébastien Miquel
#+date: 14-03-2026 #+date: 14-03-2026
# Time-stamp: <07-05-26 11:33> # Time-stamp: <01-05-26 22:35>
#+OPTIONS: #+OPTIONS:
* Quézaco * Quézaco
@ -205,8 +205,7 @@ OU
1. =python from_tablette.py Interro= (gestion perso) 1. =python from_tablette.py Interro= (gestion perso)
_Before_ : delete =~/SyncCopies/Annotées=, copy or sync from the _Before_ : delete =~/SyncCopies/Annotées=, copy from the tablette to here.
tablette to here.
Une fois les corrections manuelles appliquées aux fichiers Une fois les corrections manuelles appliquées aux fichiers
=Concat.pdf=, il faut enregistrer le fichier annoté au même endroit, =Concat.pdf=, il faut enregistrer le fichier annoté au même endroit,
@ -243,13 +242,9 @@ OU
5. (gestion perso) 5. (gestion perso)
+ Deploy =miqmacs-copies-assets=, and + Deploy =miqmacs-copies-assets=, and
+ update the copies from =miqmacs.fr/admin=. + update the copies from =miqmacs.fr/admin=.
6. (gestion perso) Impression d'une copie. Via Evince » print to pdf.
* Recorrection d'une seule copie (peu testé) * Recorrection d'une seule copie (peu testé)
!! Attention, refaire ne marchera pas si tu fais une annotation non !! Attention, refaire ne marchera pas si tu fais une annotation non
groupée into refaire !! groupée into refaire !!
@ -257,8 +252,8 @@ groupée into refaire !!
+ =python plotting.py InterroTest/Copie01.pdf= + =python plotting.py InterroTest/Copie01.pdf=
+ =python splitting_int.py InterroTest/Copie20.pdf= + =python splitting_int.py InterroTest/Copie20.pdf=
2. Créer =refaire.json=, avec un contenu comme 2. Créer =refaire.json=, avec un contenu comme
: [["Copie01", []], [["Copie01", []],
: ["Copie01", ["Ex 1 : 1)"]]] ["Copie01", ["Ex 1 : 1)"]]]
3. Appeler =correction= avec --refaire. Il doit créer des groupes 3. Appeler =correction= avec --refaire. Il doit créer des groupes
individuels, faire des requêtes, et remplacer les corrections individuels, faire des requêtes, et remplacer les corrections
précédentes (à sauver ailleurs). précédentes (à sauver ailleurs).

View File

@ -582,7 +582,7 @@ Here is a list of all possible labels. You need to answer with a list one of the
height = grouping.get_pdf_height(str(new_pdf_path)) height = grouping.get_pdf_height(str(new_pdf_path))
grouping.create_jpg(add_label, idx, [(pid, str(new_pdf_path), height)], INPUT_DIR) grouping.create_jpg(add_label, idx, [(pid, str(new_pdf_path), height)], INPUT_DIR)
new_tasks.append((str(Path(INPUT_DIR) / add_label / f"Group_{idx+1}.jpg"), new_tasks.append((str(Path(INPUT_DIR) / add_label / f"Group_{idx+1}.jpg"),
add_label, False, f"{label}(->)")) add_label, False))
error += f"(->){add_label}" error += f"(->){add_label}"
keep_error = True keep_error = True
else: else:
@ -603,7 +603,6 @@ def process_single_task(task_tuple, precomputed_response=None):
file_path = task_tuple[0] file_path = task_tuple[0]
label = task_tuple[1] label = task_tuple[1]
can_spawn_tasks = task_tuple[2] if len(task_tuple) > 2 else True can_spawn_tasks = task_tuple[2] if len(task_tuple) > 2 else True
injected_error = task_tuple[3] if len(task_tuple) > 3 else ""
group_name = os.path.splitext(file_path)[0] group_name = os.path.splitext(file_path)[0]
json_path = group_name + '.json' json_path = group_name + '.json'
@ -649,15 +648,6 @@ def process_single_task(task_tuple, precomputed_response=None):
for p in json_data: for p in json_data:
pid = p["id"] pid = p["id"]
res = p["result"] res = p["result"]
# Inject additional error if present
if injected_error:
if res["error"]:
res["error"] = f"{injected_error} {res['error']}"
else:
res["error"] = injected_error
yming, ymaxg, width_r = d_data[pid] yming, ymaxg, width_r = d_data[pid]
pdf_path = Path(INPUT_DIR) / f"Copie{pid}" / f"{label}.pdf" pdf_path = Path(INPUT_DIR) / f"Copie{pid}" / f"{label}.pdf"
@ -720,8 +710,6 @@ def process_single_task(task_tuple, precomputed_response=None):
tprint(f"Error decoding JSON for {file_path}", file=sys.stderr) tprint(f"Error decoding JSON for {file_path}", file=sys.stderr)
except Exception as e: except Exception as e:
error_msg = f"Exception processing {file_path}: {e}" error_msg = f"Exception processing {file_path}: {e}"
import traceback
traceback.print_exc() # <--- Add this line to see the real crash
print(error_msg, file=sys.stderr) print(error_msg, file=sys.stderr)
with io_lock: with io_lock:
errors_summary.append((error_msg, file_path)) errors_summary.append((error_msg, file_path))

View File

@ -55,10 +55,7 @@ def compile_to_pdf(text, output_pdf_path): # 21 cm + 3.8 (dimension de la marge
def fetch_and_save_sub_text(ex_id, indices, label, text_path): def fetch_and_save_sub_text(ex_id, indices, label, text_path):
"""Fetches text for a specific sub-question and saves it to Text/{label}.tex""" """Fetches text for a specific sub-question and saves it to Text/{label}.tex"""
qinds = ",".join(map(str, indices)) qinds = ",".join(map(str, indices))
if qinds: url = f"http://localhost:8080/exercices/exo_q_text/{ex_id}/{qinds}"
url = f"http://localhost:8080/exercices/exo_q_text/{ex_id}/{qinds}"
else:
url = f"http://localhost:8080/exercices/exo_q_text/{ex_id}"
try: try:
with urllib.request.urlopen(url) as response: with urllib.request.urlopen(url) as response:
content = response.read().decode('utf-8') content = response.read().decode('utf-8')
@ -74,10 +71,7 @@ def fetch_and_save_sub_text(ex_id, indices, label, text_path):
def fetch_and_save_sub_sol(ex_id, indices, label, sol_path): def fetch_and_save_sub_sol(ex_id, indices, label, sol_path):
"""Fetches text for a specific sub-question and saves it to Text/{label}.tex""" """Fetches text for a specific sub-question and saves it to Text/{label}.tex"""
qinds = ",".join(map(str, indices)) qinds = ",".join(map(str, indices))
if qinds: url = f"http://localhost:8080/exercices/exo_q_sol/{ex_id}/{qinds}"
url = f"http://localhost:8080/exercices/exo_q_sol/{ex_id}/{qinds}"
else:
url = f"http://localhost:8080/exercices/exo_q_sol/{ex_id}"
try: try:
with urllib.request.urlopen(url) as response: with urllib.request.urlopen(url) as response:
content = response.read().decode('utf-8') content = response.read().decode('utf-8')
@ -174,11 +168,11 @@ def process_directory(directory):
if not tex_files: if not tex_files:
print(f"No .tex file found in {directory}. Looking in /Staging/Interro/") print(f"No .tex file found in {directory}. Looking in /Staging/Interro/")
int_name = directory[:-1] if directory.endswith("/") else directory int_name = directory[:-1] if directory.endswith("/") else directory
tex_path = os.path.join(os.path.expanduser("~"), "Prépa/Staging/Interro", f"{int_name}.tex") tex_path = os.path.join(os.path.expanduser("~"), "Prépa/Staging/Interro/", int_name, ".tex")
if os.path.exists(tex_path): if os.path.exists(tex_path):
tex_file = tex_path tex_file = tex_path
else: else:
print("Not found in ", tex_path) print("Not found.")
return return
else: else:
tex_file = tex_files[0] tex_file = tex_files[0]

View File

@ -314,7 +314,7 @@ def process_copy_group(group_key, files):
# Run ThreadPool on GROUPS (Copies), not individual files # Run ThreadPool on GROUPS (Copies), not individual files
# Each thread handles one student's full exam copy sequentially # Each thread handles one student's full exam copy sequentially
with ThreadPoolExecutor(max_workers=16) as executor: with ThreadPoolExecutor(max_workers=12) as executor:
# Convert dict items to arguments for map # Convert dict items to arguments for map
# executor.map expects a function and an iterable. # executor.map expects a function and an iterable.
# We use a lambda or separate function to unpack the tuple if needed, # We use a lambda or separate function to unpack the tuple if needed,

View File

@ -4358,8 +4358,6 @@ conjoncture
conjugaison conjugaison
conjugal conjugal
conjuguer conjuguer
conjugué
conjugués
conjuration conjuration
conjuré conjuré
conjurer conjurer
@ -6099,7 +6097,6 @@ détenus
détérioration détérioration
détériorer détériorer
déterminant déterminant
déterminants
déterminante déterminante
détermination détermination
détermine détermine
@ -10282,7 +10279,6 @@ inacceptable
inacceptables inacceptables
inaccessible inaccessible
inaccoutumé inaccoutumé
inachevé
inactif inactif
inaction inaction
inadmissible inadmissible

View File

@ -9,7 +9,7 @@ from tkinter import messagebox
from pathlib import Path from pathlib import Path
from PIL import Image, ImageDraw, ImageFont, ImageTk from PIL import Image, ImageDraw, ImageFont, ImageTk
print("o to open pdf, O original pdf, e to emacs part, p to go back, i to interro, click for coordinates") print("o to open pdf, O original pdf, e to emacs part, i to interro, click for coordinates")
# --- Configuration & Globals --- # --- Configuration & Globals ---
padding = 60 padding = 60
@ -79,8 +79,6 @@ def prepare_image(image_path: str, bounding_boxes, all_labels, nb_pages, last_la
current_index = all_labels.index(label) current_index = all_labels.index(label)
if current_index < last_label_index or (last_label_index == -1 and current_index != 0): if current_index < last_label_index or (last_label_index == -1 and current_index != 0):
color = "red" color = "red"
elif current_index > last_label_index + 1:
color = "orange"
last_label_index = current_index last_label_index = current_index
draw.rectangle(((abs_x_min, abs_y_min), (abs_x_max, abs_y_max)), outline=color, width=4) draw.rectangle(((abs_x_min, abs_y_min), (abs_x_max, abs_y_max)), outline=color, width=4)
@ -172,15 +170,8 @@ class ImageViewer:
self.active_copie_name = None self.active_copie_name = None
self.accumulated_results = None # Dict with "name" and "list" self.accumulated_results = None # Dict with "name" and "list"
# To go back
self.history = []
self.forward_stack = []
self.current_pil_image = None
# Bindings # Bindings
self.root.bind('<Return>', self.on_enter) self.root.bind('<Return>', self.on_enter)
self.root.bind('p', self.on_previous)
self.root.bind('e', self.on_edit) self.root.bind('e', self.on_edit)
self.root.bind('o', self.on_open_pdf) self.root.bind('o', self.on_open_pdf)
self.root.bind('i', self.on_open_interro) self.root.bind('i', self.on_open_interro)
@ -193,11 +184,7 @@ class ImageViewer:
def poll_queue(self): def poll_queue(self):
if not self.is_viewing: if not self.is_viewing:
try: try:
# pil_image, json_path, metadata = image_queue.get_nowait() pil_image, json_path, metadata = image_queue.get_nowait()
if self.forward_stack:
pil_image, json_path, metadata = self.forward_stack.pop()
else:
pil_image, json_path, metadata = image_queue.get_nowait()
# Handle End of Stream # Handle End of Stream
if pil_image is None: if pil_image is None:
@ -212,7 +199,6 @@ class ImageViewer:
# Start new batch # Start new batch
self.active_copie_name = metadata["copie"] self.active_copie_name = metadata["copie"]
self.accumulated_results = {"name": metadata["name"], "list": []} self.accumulated_results = {"name": metadata["name"], "list": []}
self.history.clear()
self.display_image(pil_image, json_path, metadata) self.display_image(pil_image, json_path, metadata)
except queue.Empty: except queue.Empty:
@ -228,24 +214,7 @@ class ImageViewer:
json.dump(self.accumulated_results, f) json.dump(self.accumulated_results, f)
self.accumulated_results = None self.accumulated_results = None
def on_previous(self, event):
if self.is_viewing and self.history:
print("Going back to previous image...")
prev_pil, prev_json, prev_meta, num_added = self.history.pop()
# Undo the accumulation to prevent duplicates when we hit Enter again
if self.accumulated_results and num_added > 0:
self.accumulated_results["list"] = self.accumulated_results["list"][:-num_added]
# Push current image to the forward stack so we don't lose it
self.forward_stack.append((self.current_pil_image,
self.current_json_path, self.current_meta))
# Display the previous image immediately
self.display_image(prev_pil, prev_json, prev_meta)
def display_image(self, pil_image, json_path, metadata): def display_image(self, pil_image, json_path, metadata):
self.current_pil_image = pil_image # ADD THIS LINE
self.orig_size = pil_image.size self.orig_size = pil_image.size
self.scale_factor = 1.0 self.scale_factor = 1.0
screen_h = self.root.winfo_screenheight() - 100 screen_h = self.root.winfo_screenheight() - 100
@ -264,7 +233,6 @@ class ImageViewer:
def on_enter(self, event): def on_enter(self, event):
if self.is_viewing: if self.is_viewing:
print(f"Committing data for {self.current_json_path.name}...") print(f"Committing data for {self.current_json_path.name}...")
num_added = 0 # ADD THIS LINE
try: try:
with open(self.current_json_path, 'r') as f: with open(self.current_json_path, 'r') as f:
@ -277,8 +245,6 @@ class ImageViewer:
self.current_meta["schema"] self.current_meta["schema"]
) )
num_added = len(converted_items)
# Add to accumulator # Add to accumulator
if self.accumulated_results: if self.accumulated_results:
self.accumulated_results["list"].extend(converted_items) self.accumulated_results["list"].extend(converted_items)
@ -293,9 +259,6 @@ class ImageViewer:
messagebox.showerror("JSON Error", msg) messagebox.showerror("JSON Error", msg)
return # Abort advancement return # Abort advancement
self.history.append((self.current_pil_image, self.current_json_path,
self.current_meta, num_added))
# Advance UI # Advance UI
self.is_viewing = False self.is_viewing = False
self.label.config(image="", text="Loading next...") self.label.config(image="", text="Loading next...")
@ -307,13 +270,13 @@ class ImageViewer:
print(f"Opening {pdf_path}") print(f"Opening {pdf_path}")
subprocess.Popen(['xdg-open', str(pdf_path.absolute())]) subprocess.Popen(['xdg-open', str(pdf_path.absolute())])
def on_open_interro(self, event): def on_open_ori_pdf(self, event):
if self.is_viewing and self.current_json_path: if self.is_viewing and self.current_json_path:
pdf_path = "/home/sebastien/Prépa/Staging/Interro/" + str(base_dir) + ".pdf" pdf_path = "/home/sebastien/Staging/Interro/" + str(base_dir) + "pdf"
print(f"Opening {pdf_path}") print(f"Opening {pdf_path}")
subprocess.Popen(['xdg-open', pdf_path]) subprocess.Popen(['xdg-open', pdf_path])
def on_open_ori_pdf(self, event): def on_open_interro(self, event):
if self.is_viewing and self.current_json_path: if self.is_viewing and self.current_json_path:
new_filename = self.current_json_path.stem.split('_')[0] + ".pdf" new_filename = self.current_json_path.stem.split('_')[0] + ".pdf"
pdf_path = self.current_json_path.parent / "Copies Originales" / new_filename pdf_path = self.current_json_path.parent / "Copies Originales" / new_filename

View File

@ -14,10 +14,7 @@ def enonce_total(base_dir):
if not text_dir.is_dir(): if not text_dir.is_dir():
return "" return ""
# Exclude .tex and .pdf files files = [f for f in text_dir.iterdir() if f.is_file()]
files = [f for f in text_dir.iterdir()
if f.is_file() and f.suffix.lower() not in ('.tex', '.pdf')]
files.sort(key=lambda f: natural_key(f.name)) files.sort(key=lambda f: natural_key(f.name))
output = [] output = []