Standardisation 5

This commit is contained in:
2026-08-20 14:53:06 +02:00
parent bcba5facc8
commit 644e287586
5 changed files with 648 additions and 466 deletions
+141
View File
@@ -302,6 +302,10 @@ class StandardCliTests(unittest.TestCase):
"reading_grouped_annotations": load_script_module(
"reading_grouped_annotations.py", "reading_grouped_annotations"
),
"cutleft": load_script_module("cutleft.py", "cutleft"),
"splitting_int": load_script_module(
"splitting_int.py", "splitting_int"
),
"copies_tools": load_script_module(
"copies_tools.py", "copienator_copies_tools_test"
),
@@ -339,6 +343,8 @@ class StandardCliTests(unittest.TestCase):
"annotating_by_label": [missing],
"reading_annotations": [missing],
"reading_grouped_annotations": [missing],
"cutleft": [missing],
"splitting_int": [missing],
}
for name, arguments in invocations.items():
with self.subTest(script=name), redirect_stderr(io.StringIO()):
@@ -431,6 +437,16 @@ class StandardCliTests(unittest.TestCase):
"grouped",
{"target": evaluation, "update_score": True, "refaire": True},
),
"cutleft": (
"cutleft",
"default",
{"target": evaluation, "fullpage": True},
),
"splitting_int": (
"splitting",
"default",
{"target": evaluation},
),
}
for module_name, (step_id, variant_id, values) in cases.items():
step = steps[step_id]
@@ -513,6 +529,131 @@ class StandardCliTests(unittest.TestCase):
self.assertEqual(module.main([str(evaluation)]), 0)
self.assertTrue((evaluation / "Par label").is_dir())
def test_cutleft_can_target_one_copy_without_rendering_at_import(self) -> None:
module = self.modules["cutleft"]
with tempfile.TemporaryDirectory() as directory:
evaluation = Path(directory) / "Exam"
copy_pdf = evaluation / "Copies" / "Copie01.pdf"
copy_pdf.parent.mkdir(parents=True)
copy_pdf.write_bytes(b"pdf")
with patch.object(module, "ImageReviewer") as reviewer:
self.assertEqual(module.main([str(copy_pdf), "--fullpage"]), 0)
files, output_dir = reviewer.call_args.args[:2]
self.assertEqual(files, [copy_pdf])
self.assertEqual(output_dir, evaluation / "Cutleft")
self.assertEqual(reviewer.call_args.kwargs["default_max_per_file"], 1)
def test_cutleft_atomic_save_removes_only_obsolete_copy_outputs(self) -> None:
module = self.modules["cutleft"]
with tempfile.TemporaryDirectory() as directory:
output_dir = Path(directory) / "Cutleft"
output_dir.mkdir()
(output_dir / "Copie01_01.jpg").write_bytes(b"old-one")
(output_dir / "Copie01_02.jpg").write_bytes(b"old-two")
(output_dir / "Copie02_01.jpg").write_bytes(b"other-copy")
(output_dir / "Copie01_schema.json").write_text(
"{}", encoding="utf-8"
)
image = Image.new("RGB", (5, 5), "white")
result = (
image,
[image],
{
"original_filename": "Copie01.pdf",
"total_pages": 1,
"number_of_files": 1,
"columns_per_file": [1],
},
)
module.save_results(result, Path("Copie01.pdf"), output_dir)
self.assertTrue((output_dir / "Copie01_01.jpg").is_file())
self.assertFalse((output_dir / "Copie01_02.jpg").exists())
self.assertEqual(
(output_dir / "Copie02_01.jpg").read_bytes(), b"other-copy"
)
self.assertEqual(
read_json(output_dir / "Copie01_schema.json")["total_pages"], 1
)
def test_splitting_missing_json_is_partial(self) -> None:
module = self.modules["splitting_int"]
with tempfile.TemporaryDirectory() as directory:
evaluation = Path(directory) / "Exam"
copy_pdf = evaluation / "Copies" / "Copie01.pdf"
copy_pdf.parent.mkdir(parents=True)
copy_pdf.write_bytes(b"pdf")
(evaluation / "labels").write_text("Ex 1\n", encoding="utf-8")
self.assertEqual(module.main([str(evaluation)]), 4)
def test_splitting_failure_preserves_previous_copy_outputs(self) -> None:
module = self.modules["splitting_int"]
with tempfile.TemporaryDirectory() as directory:
evaluation = Path(directory) / "Exam"
copy_pdf = evaluation / "Copies" / "Copie01.pdf"
output_dir = evaluation / "Copies" / "Copie01"
output_dir.mkdir(parents=True)
copy_pdf.write_bytes(b"pdf")
(output_dir / "sentinel.pdf").write_bytes(b"old")
workspace = EvaluationWorkspace(evaluation)
with patch.object(
module,
"_render_split_outputs",
side_effect=RuntimeError("render failed"),
), self.assertRaises(RuntimeError):
module.split_an_interro(workspace, copy_pdf, [])
self.assertEqual((output_dir / "sentinel.pdf").read_bytes(), b"old")
def test_splitting_moves_obsolete_outputs_to_missing_on_commit(self) -> None:
module = self.modules["splitting_int"]
with tempfile.TemporaryDirectory() as directory:
evaluation = Path(directory) / "Exam"
copy_pdf = evaluation / "Copies" / "Copie01.pdf"
output_dir = evaluation / "Copies" / "Copie01"
missing_dir = output_dir / "Missing"
missing_dir.mkdir(parents=True)
copy_pdf.write_bytes(b"pdf")
(output_dir / "Old.pdf").write_bytes(b"obsolete")
(missing_dir / "Earlier.pdf").write_bytes(b"earlier")
def render(_pdf, _coordinates, staging):
(staging / "Ex 1.pdf").write_bytes(b"new")
return {"Ex 1.pdf"}
with patch.object(module, "_render_split_outputs", side_effect=render):
module.split_an_interro(EvaluationWorkspace(evaluation), copy_pdf, [])
self.assertEqual((output_dir / "Ex 1.pdf").read_bytes(), b"new")
self.assertEqual(
(output_dir / "Missing" / "Old.pdf").read_bytes(), b"obsolete"
)
self.assertEqual(
(output_dir / "Missing" / "Earlier.pdf").read_bytes(), b"earlier"
)
def test_splitting_renders_a_real_one_page_answer(self) -> None:
module = self.modules["splitting_int"]
with tempfile.TemporaryDirectory() as directory:
evaluation = Path(directory) / "Exam"
copy_pdf = evaluation / "Copies" / "Copie01.pdf"
copy_pdf.parent.mkdir(parents=True)
document = module.fitz.open()
page = document.new_page(width=600, height=800)
page.insert_text((100, 200), "Student answer")
document.save(copy_pdf)
document.close()
(evaluation / "labels").write_text("Ex 1\n", encoding="utf-8")
atomic_write_json(
copy_pdf.with_suffix(".json"),
{
"name": "Copie01",
"list": [{"label": "Ex 1", "box_2d": [100, 100, 300, 300]}],
},
)
self.assertEqual(module.main([str(evaluation)]), 0)
answer = evaluation / "Copies" / "Copie01" / "Ex 1.pdf"
self.assertTrue(answer.is_file())
self.assertEqual(len(PdfReader(answer).pages), 1)
def test_post_correction_main_cleans_json_atomically(self) -> None:
module = self.modules["post_correction"]
with tempfile.TemporaryDirectory() as directory: