Refaire session.
This commit is contained in:
+60
-1
@@ -25,6 +25,7 @@ from .refaire import (
|
||||
resolve_layout,
|
||||
validate_selection,
|
||||
)
|
||||
from .refaire_sessions import RESTART_WARNING, begin_pass
|
||||
from .runner import ProcessRunner
|
||||
from .state import StateStore
|
||||
from .workflow import (
|
||||
@@ -470,7 +471,8 @@ class CopienatorApp(tk.Tk):
|
||||
if any(char in pattern for char in "*?["):
|
||||
exists = next(evaluation.glob(pattern), None) is not None
|
||||
else:
|
||||
exists = (evaluation / pattern).exists()
|
||||
path = EvaluationWorkspace(evaluation).annotation_dir("refaire") if pattern == "BRnot" else evaluation / pattern
|
||||
exists = path.exists()
|
||||
if not exists:
|
||||
missing.append(pattern)
|
||||
return missing
|
||||
@@ -635,6 +637,7 @@ class CopienatorApp(tk.Tk):
|
||||
evaluation = self.evaluation
|
||||
if step.id in {"refaire_selection", "refaire_correct"}:
|
||||
row = self._correction_folder_buttons(row)
|
||||
row = self._refaire_restart_controls(row)
|
||||
if step.id == "refaire_selection" and evaluation:
|
||||
choices, preferred = self._annotation_directory_choices("export")
|
||||
draft = self.state_store.step(step.id).get("values", {})
|
||||
@@ -699,6 +702,50 @@ class CopienatorApp(tk.Tk):
|
||||
row += 1
|
||||
return row
|
||||
|
||||
def _refaire_restart_controls(self, row: int) -> int:
|
||||
controls = ttk.Frame(self.form)
|
||||
controls.grid(row=row, column=0, columnspan=2, sticky="w", pady=(0, 5))
|
||||
running = bool(self.active_step_id) or self.runner.running
|
||||
ttk.Button(controls, text="Nouvelle reprise", command=lambda: self._start_refaire_pass(False),
|
||||
state="disabled" if running else "normal").pack(side="left", padx=(0, 6))
|
||||
ttk.Button(controls, text="Refaire la même sélection", command=lambda: self._start_refaire_pass(True),
|
||||
state="disabled" if running else "normal").pack(side="left")
|
||||
ttk.Label(self.form, text="« Nouvelle reprise » : à utiliser après avoir importé les résultats et mis à jour les copies finales.",
|
||||
wraplength=580, justify="left").grid(row=row + 1, column=0, columnspan=2, sticky="w", pady=(0, 6))
|
||||
return row + 2
|
||||
|
||||
def _start_refaire_pass(self, keep_selection: bool) -> None:
|
||||
workspace = self.state_store.workspace
|
||||
if workspace is None:
|
||||
messagebox.showerror("Évaluation absente", "Chargez d’abord une évaluation.")
|
||||
return
|
||||
if self.active_step_id or self.runner.running:
|
||||
messagebox.showwarning("Traitement en cours", "Attendez la fin du traitement avant de commencer une reprise.")
|
||||
return
|
||||
try:
|
||||
if keep_selection:
|
||||
load_selection(workspace.root)
|
||||
title = "Refaire la même sélection" if keep_selection else "Nouvelle reprise"
|
||||
message = RESTART_WARNING
|
||||
if self.state_store.step("refaire_merge").get("status") != "success":
|
||||
message += "\n\nLa reprise actuelle n’est pas marquée comme fusionnée. Les résultats non fusionnés ne seront pas intégrés aux copies finales."
|
||||
message += "\n\nLes fichiers de la reprise précédente seront conservés. Continuer ?"
|
||||
if not messagebox.askyesno(title, message, icon="warning", default="no"):
|
||||
return
|
||||
self._save_current_form()
|
||||
updated, ident = begin_pass(workspace, self.state_store.data, keep_selection=keep_selection)
|
||||
self.state_store.data = updated
|
||||
# Do not save the old form over the fresh pass during navigation.
|
||||
self.current_step = None
|
||||
self.refaire_panel = None
|
||||
self.arg_vars.clear()
|
||||
self._populate_tree()
|
||||
self.tree.selection_set("refaire_selection")
|
||||
self.tree.see("refaire_selection")
|
||||
self.info_var.set(f"Nouvelle reprise : {ident}. Enregistrez la sélection pour continuer.")
|
||||
except (OSError, ValueError, TypeError) as exc:
|
||||
messagebox.showerror("Nouvelle reprise impossible", str(exc))
|
||||
|
||||
def _correction_folder_buttons(self, row: int) -> int:
|
||||
buttons = ttk.Frame(self.form)
|
||||
buttons.grid(row=row, column=0, columnspan=2, sticky="w", pady=(0, 6))
|
||||
@@ -804,11 +851,23 @@ class CopienatorApp(tk.Tk):
|
||||
if not self.refaire_panel or not self.evaluation:
|
||||
return
|
||||
try:
|
||||
if self.state_store.step("refaire_merge").get("status") == "success":
|
||||
messagebox.showinfo("Reprise terminée", "Utilisez « Nouvelle reprise » ou « Refaire la même sélection » pour commencer un autre passage.")
|
||||
return
|
||||
values = self.refaire_panel.values()
|
||||
entries = validate_selection(values["selection"], available_copies(self.evaluation), EvaluationWorkspace(self.evaluation).read_labels())
|
||||
source = values["annotation_dir"]
|
||||
if source not in detected_annotation_directories(self.evaluation):
|
||||
raise ValueError("Choisissez un dossier existant pour le passage principal.")
|
||||
workspace = EvaluationWorkspace(self.evaluation)
|
||||
output = workspace.annotation_dir("refaire")
|
||||
if output.is_dir() and any(output.iterdir()) and workspace.refaire_file.is_file():
|
||||
from copienator import read_json
|
||||
if read_json(workspace.refaire_file) != entries:
|
||||
raise ValueError("Des PDF de vérification existent déjà. Importez et fusionnez leurs résultats, puis utilisez « Nouvelle reprise » pour changer de sélection.")
|
||||
if workspace.refaire_session_dir is not None:
|
||||
atomic_write_json(workspace.refaire_session_dir / "refaire.json", entries)
|
||||
atomic_write_json(workspace.refaire_session_dir / "session.json", {"id": workspace.refaire_session_id, "values": values})
|
||||
atomic_write_json(self.evaluation / "refaire.json", entries)
|
||||
self._mark_step("success")
|
||||
self._move_selection_from("refaire_selection", 1)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Preserve completed redo passes and activate a fresh working directory."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from copienator import EvaluationWorkspace, atomic_write_json, read_json
|
||||
from copienator.filesystem import staged_files
|
||||
|
||||
RESTART_WARNING = (
|
||||
"Appelez « Nouvelle reprise » seulement après avoir importé les résultats "
|
||||
"de la reprise en cours et exécuté « Mettre à jour les copies finales ». "
|
||||
"Cette consigne s’applique aussi à « Refaire la même sélection »."
|
||||
)
|
||||
|
||||
|
||||
def _identifier() -> str:
|
||||
return f"reprise-{datetime.now().astimezone():%Y%m%d-%H%M%S}-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
|
||||
def begin_pass(
|
||||
workspace: EvaluationWorkspace,
|
||||
state: dict[str, Any],
|
||||
*,
|
||||
keep_selection: bool,
|
||||
) -> tuple[dict[str, Any], str]:
|
||||
"""Activate a pass atomically with its selection and reset GUI state.
|
||||
|
||||
Existing review files remain in place. Legacy BRnot is copied once into
|
||||
the archive; failures before activation leave the old pass active.
|
||||
"""
|
||||
selection = read_json(workspace.refaire_file, default=[])
|
||||
previous = workspace.refaire_session_dir
|
||||
if previous is None and (
|
||||
workspace.refaire_file.exists() or workspace.annotation_dir("refaire").exists()
|
||||
):
|
||||
previous = workspace.root / "Reprises" / _identifier()
|
||||
previous.mkdir(parents=True)
|
||||
legacy = workspace.annotation_dir("refaire")
|
||||
if legacy.is_dir():
|
||||
shutil.copytree(legacy, previous / "BRnot")
|
||||
if previous is not None:
|
||||
atomic_write_json(previous / "refaire.json", selection)
|
||||
atomic_write_json(
|
||||
previous / "progression.json",
|
||||
{
|
||||
name: entry
|
||||
for name, entry in state.get("steps", {}).items()
|
||||
if name.startswith("refaire_")
|
||||
},
|
||||
)
|
||||
|
||||
ident = _identifier()
|
||||
directory = workspace.root / "Reprises" / ident
|
||||
directory.mkdir(parents=True)
|
||||
new_selection = selection if keep_selection else []
|
||||
updated = copy.deepcopy(state)
|
||||
values = dict(
|
||||
updated.get("steps", {}).get("refaire_selection", {}).get("values", {})
|
||||
)
|
||||
values["selection"] = new_selection
|
||||
updated["steps"] = {
|
||||
name: entry
|
||||
for name, entry in updated.get("steps", {}).items()
|
||||
if not name.startswith("refaire_")
|
||||
}
|
||||
updated["steps"]["refaire_selection"] = {"values": values}
|
||||
updated.setdefault("history", []).append(
|
||||
{
|
||||
"step": "refaire_selection",
|
||||
"action": "repeat" if keep_selection else "new",
|
||||
"session": ident,
|
||||
"timestamp": datetime.now().astimezone().isoformat(),
|
||||
}
|
||||
)
|
||||
atomic_write_json(directory / "refaire.json", new_selection)
|
||||
atomic_write_json(directory / "session.json", {"id": ident, "values": values})
|
||||
with staged_files(workspace.root) as staging:
|
||||
atomic_write_json(staging / workspace.refaire_file.name, new_selection)
|
||||
atomic_write_json(staging / workspace.gui_state_file.name, updated)
|
||||
atomic_write_json(staging / "refaire-session.json", {"id": ident})
|
||||
return updated, ident
|
||||
Reference in New Issue
Block a user