Add per label remark, post correction
This commit is contained in:
1 parent
3abd7873e6
commit
8918d64452
13 files changed
+343
-23
No files matched your search
+19
-4
@@ -1086,7 +1086,7 @@ class CopienatorApp(tk.Tk):
|
||||
return row
|
||||
if step.id == "edit_scores" and self.evaluation:
|
||||
self.score_editor_panel = ScoreEditorPanel(
|
||||
self.form, self.evaluation, self._score_changed
|
||||
self.form, self.evaluation, self._score_changed, self._remark_changed
|
||||
)
|
||||
self.score_editor_panel.grid(row=row, column=0, columnspan=2, sticky="ew", pady=8)
|
||||
row += 1
|
||||
@@ -1644,7 +1644,16 @@ class CopienatorApp(tk.Tk):
|
||||
if (step.is_manual or step.id == "personal_create"
|
||||
or (step.id == "personal_read" and review_command is None)):
|
||||
self._mark_step("success")
|
||||
self._move_selection_from(step.id, 1)
|
||||
if step.id == "edit_scores":
|
||||
entry = self.state_store.step("read_annotations")
|
||||
values = dict(entry.get("values", {}))
|
||||
values["update_score"] = True
|
||||
self.state_store.update_step("read_annotations", status="stale", values=values)
|
||||
self._populate_tree()
|
||||
self.tree.selection_set("read_annotations")
|
||||
self.tree.see("read_annotations")
|
||||
else:
|
||||
self._move_selection_from(step.id, 1)
|
||||
return
|
||||
if os.name == "nt" and step.id != "statement":
|
||||
labels_path = evaluation / "labels"
|
||||
@@ -1799,13 +1808,19 @@ class CopienatorApp(tk.Tk):
|
||||
self._populate_tree()
|
||||
self.info_var.set("Renommage validé — aucun fichier modifié.")
|
||||
|
||||
def _score_changed(self, copy_id: str, label: str, previous: str, current: str) -> None:
|
||||
def _remark_changed(self, copy_id: str, label: str, previous: str, current: str) -> None:
|
||||
self._score_changed(copy_id, label, previous, current, remark=True)
|
||||
|
||||
def _score_changed(self, copy_id: str, label: str, previous: str, current: str,
|
||||
*, remark: bool = False) -> None:
|
||||
step = self.step_by_id["edit_scores"]
|
||||
self._invalidate_after_step(step, "default")
|
||||
self.state_store.update_step(step.id, status="detected")
|
||||
self.state_store.update_step("read_annotations", status="stale")
|
||||
self.state_store.add_history({
|
||||
"step": step.id, "copy_id": copy_id, "label": label,
|
||||
"previous_score": previous, "score": current, "manual": True,
|
||||
("previous_remark" if remark else "previous_score"): previous,
|
||||
("remark" if remark else "score"): current, "manual": True,
|
||||
})
|
||||
# Refresh statuses without rebuilding the form between score edits.
|
||||
for item in self.steps:
|
||||
|
||||
@@ -8,6 +8,7 @@ from tkinter import messagebox, ttk
|
||||
|
||||
from copienator import EvaluationWorkspace, atomic_update_json, read_json
|
||||
from copienator.utils import natural_key
|
||||
from copienator.custom_remarks import remarks_for_copy, set_remark
|
||||
from .giving_names import RETURN_FOLDER_PATTERN
|
||||
|
||||
|
||||
@@ -61,10 +62,11 @@ def set_score(path: Path, label: str, value: str) -> str:
|
||||
|
||||
|
||||
class ScoreEditorPanel(ttk.Frame):
|
||||
def __init__(self, parent, evaluation: Path, on_change):
|
||||
def __init__(self, parent, evaluation: Path, on_change, on_remark_change=None):
|
||||
super().__init__(parent)
|
||||
self.evaluation = evaluation
|
||||
self.on_change = on_change
|
||||
self.on_remark_change = on_remark_change
|
||||
self.current_path = None
|
||||
self.busy = False
|
||||
self.id_var = tk.StringVar()
|
||||
@@ -81,10 +83,17 @@ class ScoreEditorPanel(ttk.Frame):
|
||||
ttk.Label(self, text="Score").grid(row=2, column=0, sticky="w", padx=(0, 8), pady=4)
|
||||
self.score_entry = ttk.Entry(self, textvariable=self.score_var)
|
||||
self.score_entry.grid(row=2, column=1, sticky="ew", pady=4)
|
||||
self.set_button = ttk.Button(self, text="Set", command=self.save_score)
|
||||
self.set_button = ttk.Button(self, text="Enregistrer le score", command=self.save_score)
|
||||
self.set_button.grid(row=2, column=2, padx=(8, 0), pady=4)
|
||||
ttk.Label(self, text="Remarque personnelle").grid(row=3, column=0, sticky="nw", pady=4)
|
||||
self.remark_entry = tk.Text(self, height=4, width=45, wrap="word", undo=True)
|
||||
self.remark_entry.grid(row=3, column=1, sticky="ew", pady=4)
|
||||
self.remark_button = ttk.Button(self, text="Enregistrer la remarque", command=self.save_remark)
|
||||
self.remark_button.grid(row=3, column=2, padx=(8, 0), pady=4)
|
||||
ttk.Label(self, text="Videz le champ puis enregistrez pour supprimer votre remarque.").grid(
|
||||
row=4, column=1, columnspan=2, sticky="w")
|
||||
ttk.Label(self, textvariable=self.status_var, wraplength=650).grid(
|
||||
row=3, column=0, columnspan=3, sticky="w", pady=8
|
||||
row=5, column=0, columnspan=3, sticky="w", pady=8
|
||||
)
|
||||
self.id_var.trace_add("write", self.load_score)
|
||||
self.label_var.trace_add("write", self.load_score)
|
||||
@@ -94,6 +103,8 @@ class ScoreEditorPanel(ttk.Frame):
|
||||
def load_score(self, *_args):
|
||||
self.current_path = None
|
||||
self.score_var.set("")
|
||||
self.remark_entry.configure(state="normal")
|
||||
self.remark_entry.delete("1.0", "end")
|
||||
self.label_entry.configure(values=())
|
||||
try:
|
||||
path = score_path_for_id(self.evaluation, self.id_var.get())
|
||||
@@ -105,11 +116,14 @@ class ScoreEditorPanel(ttk.Frame):
|
||||
elif label not in scores:
|
||||
self.status_var.set(f"Label absent de score.json : {label}")
|
||||
else:
|
||||
remark = remarks_for_copy(self.evaluation, self.id_var.get()).get(label, "")
|
||||
self.remark_entry.insert("1.0", remark)
|
||||
self.current_path = path
|
||||
self.score_var.set(str(scores[label]))
|
||||
self.status_var.set(path.parent.name)
|
||||
except (OSError, ValueError) as exc:
|
||||
self.status_var.set(str(exc))
|
||||
self.remark_entry.edit_reset()
|
||||
self.set_busy(self.busy)
|
||||
|
||||
def set_busy(self, busy: bool):
|
||||
@@ -119,6 +133,8 @@ class ScoreEditorPanel(ttk.Frame):
|
||||
state = "normal" if self.current_path is not None and not busy else "disabled"
|
||||
self.score_entry.configure(state=state)
|
||||
self.set_button.configure(state=state)
|
||||
self.remark_entry.configure(state=state)
|
||||
self.remark_button.configure(state=state)
|
||||
|
||||
def save_score(self):
|
||||
if self.current_path is None or self.busy:
|
||||
@@ -130,6 +146,21 @@ class ScoreEditorPanel(ttk.Frame):
|
||||
self.score_var.set(current)
|
||||
if previous != current:
|
||||
self.on_change(self.id_var.get().strip(), label, previous, current)
|
||||
self.status_var.set(f"Score enregistré : {self.current_path.parent.name} — {label} : {current}")
|
||||
self.status_var.set(f"Score enregistré : {self.current_path.parent.name} — {label} : {current}. Régénération nécessaire.")
|
||||
except (OSError, ValueError) as exc:
|
||||
messagebox.showerror("Modification du score", str(exc))
|
||||
|
||||
def save_remark(self):
|
||||
if self.current_path is None or self.busy:
|
||||
return
|
||||
label = self.label_var.get().strip()
|
||||
try:
|
||||
if label not in read_scores(self.current_path):
|
||||
raise ValueError(f"Label absent de score.json : {label}")
|
||||
current = self.remark_entry.get("1.0", "end-1c").strip()
|
||||
previous = set_remark(self.evaluation, self.id_var.get(), label, current)
|
||||
if previous != current and self.on_remark_change:
|
||||
self.on_remark_change(self.id_var.get().strip(), label, previous, current)
|
||||
self.status_var.set("Remarque enregistrée — régénération nécessaire dans « Lire les annotations manuscrites ».")
|
||||
except (OSError, ValueError) as exc:
|
||||
messagebox.showerror("Modification de la remarque", str(exc))
|
||||
@@ -682,10 +682,10 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
||||
arg_target("le dossier de l’évaluation"),
|
||||
ArgumentSpec(
|
||||
"update_score",
|
||||
"Régénérer avec les nouveaux énoncés/corrigés et les score.json",
|
||||
"Régénérer avec les nouveaux énoncés/corrigés, scores et remarques",
|
||||
"bool",
|
||||
"--update-score",
|
||||
help="Régénère les images avec les PDF actuels d’énoncé et de correction ; pour chaque label, le score.json existant prévaut sur le score relu dans l’annotation manuscrite.",
|
||||
help="Régénère les images avec les PDF actuels d’énoncé et de correction ; pour chaque label, le score.json existant prévaut sur le score relu dans l’annotation manuscrite. Les remarques personnelles sont ajoutées aux commentaires conservés.",
|
||||
),
|
||||
ArgumentSpec(
|
||||
"refaire",
|
||||
@@ -756,10 +756,11 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
||||
StepDefinition(
|
||||
"edit_scores",
|
||||
"Finalisation",
|
||||
"Modifier des scores",
|
||||
"Modifier des scores et remarques",
|
||||
"Saisissez l’id de copie, puis choisissez ou saisissez un label. "
|
||||
"Le score actuel est chargé automatiquement. Modifiez-le puis cliquez sur Set "
|
||||
"pour l’enregistrer dans score.json. Cliquez sur « Marquer terminée » quand vous avez fini.",
|
||||
"Le score et la remarque personnelle sont chargés automatiquement. Enregistrez chacun "
|
||||
"séparément, puis relancez « Lire les annotations manuscrites » avec « Régénérer ». "
|
||||
"Videz la remarque et enregistrez pour la supprimer. Cliquez sur « Marquer terminée » quand vous avez fini.",
|
||||
(manual("default"),),
|
||||
optional=True,
|
||||
requires=("A Rendre",),
|
||||
|
||||
Reference in new issue
Block a user