Miscs improvements (Interro02)

This commit is contained in:
2026-09-15 14:18:22 +02:00
parent 0a86403ca6
commit 9b22a8a137
15 changed files with 893 additions and 118 deletions
@@ -9,6 +9,7 @@ from typing import Any
from PIL import Image, ImageDraw
from copienator import configuration
from copienator import (
EvaluationWorkspace,
ExitCode,
@@ -29,9 +30,11 @@ from copienator.commands.reading_annotations import (
has_significant_notes,
)
from copienator.filesystem import staged_files
from copienator.return_answers import save_return_answer_options
LabelNotes = dict[str, dict[str, Any]]
ScanResult = tuple[dict[str, list[dict[str, Any]]], dict[str, LabelNotes]]
SCAN_WORKERS = 2
def get_extra_pdfs_as_images(
@@ -191,9 +194,11 @@ def apply_actions_and_regenerate_grouped(
output_dir = workspace.root / annotation_dir / f"Copie{student_id}"
labels_data = data.get(student_id, {})
apply_checkbox_actions(labels_data, actions, logs.append)
score_path = output_dir / "score.json"
preserve_score_file = update_score and score_path.is_file()
if update_score:
apply_score_overrides(
labels_data, output_dir / "score.json", logs.append
labels_data, score_path, logs.append
)
selected_labels = selected_labels if selected_labels is not None else set()
@@ -350,7 +355,8 @@ def apply_actions_and_regenerate_grouped(
atomic_write_json(staging / "refaire_simple_layout.json", simple_layout)
for label, image in dirty_images.items():
image.save(staging / f"{label}.jpg")
atomic_write_json(staging / "score.json", scores)
if not preserve_score_file:
atomic_write_json(staging / "score.json", scores)
atomic_write_json(staging / "info.json", build_answer_info(
scores, labels_data, answer_labels, touched
))
@@ -364,6 +370,8 @@ def apply_actions_and_regenerate_grouped(
[image for group in filtered_groups for image in group]
)
filtered_image.save(staging / "Concat_F.jpg")
if preserve_score_file:
logs.append(f" Preserved existing score.json in {output_dir}")
logs.append(f" Saved regenerated files in {output_dir}")
status = ExitCode.PARTIAL if incomplete else ExitCode.SUCCESS
return status, "\n".join(logs)
@@ -461,6 +469,9 @@ def run(
refaire: bool = False,
update_score: bool = False,
annotation_dir: str = "BGnot",
return_answers_context: bool | None = None,
return_answers_question: bool | None = None,
return_answers_solution: bool | None = None,
) -> ExitCode:
workspace.require_files("labels", "correction.json")
workspace.require_directories("Copies", "Par label", annotation_dir)
@@ -470,6 +481,25 @@ def run(
workspace.require_files("refaire.json")
workspace.require_directories("BRnot")
refaire_list, refaire_by_student = _read_refaire(workspace)
if configuration.RETURN_ANSWERS_ENABLED:
save_return_answer_options(
workspace.root,
context=(
configuration.RETURN_ANSWERS_CONTEXT
if return_answers_context is None
else return_answers_context
),
question=(
configuration.RETURN_ANSWERS_QUESTION
if return_answers_question is None
else return_answers_question
),
solution=(
configuration.RETURN_ANSWERS_SOLUTION
if return_answers_solution is None
else return_answers_solution
),
)
all_labels = utils.read_all_labels(workspace.root)
loaded = load_annotation_data(workspace)
@@ -494,7 +524,10 @@ def run(
and path.is_dir()
and not path.name.startswith("Copie")
]
with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor:
# Each worker decodes a full-height returned group and its reference image.
# Keep this stage deliberately narrow; answer regeneration below has its own
# parallel executor and a much smaller per-task memory footprint.
with concurrent.futures.ThreadPoolExecutor(max_workers=SCAN_WORKERS) as executor:
futures = [
executor.submit(_scan_annotation_directory, path, only_ids)
for path in group_dirs
@@ -599,7 +632,28 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument(
"--update-score",
action="store_true",
help="Override generated scores with values from existing score.json files",
help=(
"Regenerate images with current statement/solution PDFs while "
"preserving and applying existing score.json values"
),
)
parser.add_argument(
"--return-answers-context",
action=argparse.BooleanOptionalAction,
default=configuration.RETURN_ANSWERS_CONTEXT,
help="Include applicable context pages in individual answer exports",
)
parser.add_argument(
"--return-answers-question",
action=argparse.BooleanOptionalAction,
default=configuration.RETURN_ANSWERS_QUESTION,
help="Include the current question PDF in individual answer exports",
)
parser.add_argument(
"--return-answers-solution",
action=argparse.BooleanOptionalAction,
default=configuration.RETURN_ANSWERS_SOLUTION,
help="Include the current solution PDF in individual answer exports",
)
return parser
@@ -615,6 +669,9 @@ def main(argv: Sequence[str] | None = None) -> int:
refaire=args.refaire,
update_score=args.update_score,
annotation_dir=args.annotation_dir,
return_answers_context=args.return_answers_context,
return_answers_question=args.return_answers_question,
return_answers_solution=args.return_answers_solution,
)
return execute(parser, argv, handle)