Keep empty answers in the final individual outputs.

This commit is contained in:
sebastien committed 2026-10-13 10:15:57 +02:00
1 parent b1f1e0bc86
commit e7094d850e
8 files changed
+164 -37

No files matched your search

+1 -2
View File
@@ -550,8 +550,7 @@ def process_student(student_id, labels_data, root_dir, all_labels, overwrite):
canvas.paste(image, (0, current_y))
current_y += image.height
canvas.save(staging / "Concat.jpg")
elif labels_data:
problems = True
# An all-empty copy still has valid individual return images.
return "partial" if problems else "success"
+24 -9
View File
@@ -157,10 +157,14 @@ def apply_actions_and_regenerate(
"""Apply annotations and atomically merge the regenerated student files."""
output_dir = workspace.annotation_dir("checks") / f"Copie{student_id}"
bnote_path = output_dir / "bnote.json"
if not bnote_path.is_file():
only_empty = bool(data[student_id]) and all(
content["result"].get("error") == "empty-answer"
for content in data[student_id].values()
)
if not bnote_path.is_file() and not only_empty:
print(f" Missing {bnote_path}")
return ExitCode.PARTIAL
bnote_data = read_json(bnote_path)
bnote_data = read_json(bnote_path) if bnote_path.is_file() else {"images": []}
if not isinstance(bnote_data, dict):
raise TypeError(f"Expected a JSON object in {bnote_path}")
@@ -180,7 +184,14 @@ def apply_actions_and_regenerate(
filtered: list[Image.Image] = []
incomplete = False
for image_info in bnote_data.get("images", []):
review_images = list(bnote_data.get("images", []))
reviewed_labels = {str(item.get("label", "")) for item in review_images if isinstance(item, dict)}
# Empty answers are omitted from the review sheet, but still need return images.
review_images.extend(
{"label": label} for label, content in labels_data.items()
if label not in reviewed_labels and content["result"].get("error") == "empty-answer"
)
for image_info in review_images:
if not isinstance(image_info, dict):
incomplete = True
continue
@@ -191,11 +202,10 @@ def apply_actions_and_regenerate(
content = labels_data[label]
result = content["result"]
scores[label] = str(result.get("score", 0))
if result.get("error") == "empty-answer":
continue
is_empty = result.get("error") == "empty-answer"
sub_note = None
if notes_layer is not None:
if notes_layer is not None and "hmin" in image_info:
hmin = int(image_info.get("hmin", 0))
hmax = int(image_info.get("hmax", 0))
sub_note = notes_layer.crop((0, hmin, notes_layer.width, hmax))
@@ -213,6 +223,7 @@ def apply_actions_and_regenerate(
result,
content["coordinates"][0],
with_error=False,
with_empty=True,
)
if final_image is None:
incomplete = True
@@ -230,6 +241,8 @@ def apply_actions_and_regenerate(
dirty_images[label] = final_image
header_heights[label] = new_header_height
if is_empty:
continue
answer_labels.append(label)
concatenated.append(final_image)
if float(scores[label]) != 4.0 or result.get("feedback", []):
@@ -302,13 +315,15 @@ def run(
status = ExitCode.PARTIAL if loaded.warnings else ExitCode.SUCCESS
for student_id in sorted(loaded.data, key=utils.natural_key):
output_dir = workspace.annotation_dir("checks") / f"Copie{student_id}"
if not output_dir.is_dir():
only_empty = all(content["result"].get("error") == "empty-answer"
for content in loaded.data[student_id].values())
if not output_dir.is_dir() and not only_empty:
print(f"Warning: missing annotation directory {output_dir}")
status = ExitCode.PARTIAL
continue
print(f"Processing annotations for: {student_id}")
actions, notes = detect_checks_and_notes(output_dir)
if notes is None and not actions and not update_score:
actions, notes = ([], None) if only_empty else detect_checks_and_notes(output_dir)
if notes is None and not actions and not update_score and not only_empty:
print(" No readable annotation input found.")
status = ExitCode.PARTIAL
continue
@@ -292,8 +292,7 @@ def apply_actions_and_regenerate_grouped(
result["score"] = old_scores[label]
scores[label] = str(result.get("score", 0))
touched[label] = False
if result.get("error") == "empty-answer":
continue
is_empty = result.get("error") == "empty-answer"
saved_image = output_dir / f"{label}.jpg"
baseline_entry = (previous_baseline or {}).get("images", {}).get(label)
if selected_labels and label not in selected_labels and (saved_image.is_file() or baseline_entry):
@@ -319,6 +318,8 @@ def apply_actions_and_regenerate_grouped(
)
dirty_images[label] = final_image
scores[label] = str(old_scores.get(label, scores[label]))
if is_empty:
continue
concat_images.append(final_image)
answer_labels.append(label)
filtered_labels.append(label)
@@ -344,6 +345,7 @@ def apply_actions_and_regenerate_grouped(
result,
content["coordinates"][0],
with_error=False,
with_empty=True,
)
if final_image is None:
incomplete = True
@@ -368,6 +370,8 @@ def apply_actions_and_regenerate_grouped(
# Persist every final block, including unchanged answers, for returns.
dirty_images[label] = final_image
header_heights[label] = new_header_height
if is_empty:
continue
answer_labels.append(label)
concat_images.append(final_image)