Reuse enonce groups if availabel in annotating_by_label
This commit is contained in:
+1
-6
@@ -1,7 +1,7 @@
|
||||
#+title: Script
|
||||
#+author: Sébastien Miquel
|
||||
#+date: 14-03-2026
|
||||
# Time-stamp: <22-08-26 12:05>
|
||||
# Time-stamp: <22-08-26 12:22>
|
||||
#+OPTIONS:
|
||||
|
||||
* Méta
|
||||
@@ -407,11 +407,6 @@ OU
|
||||
_Needs_ : label_groups file (made automatically by this function),
|
||||
qui dit quelles questions regrouper.
|
||||
|
||||
Dans ces trois modes, les métadonnées JSON sont écrites atomiquement.
|
||||
Lors d'une régénération, les nouvelles sorties sont préparées dans un
|
||||
dossier temporaire voisin. Une erreur de rendu conserve donc la sortie
|
||||
précédente ; pour =BGnot --overwrite=, le dossier complet n'est remplacé
|
||||
que si tous les groupes ont été produits.
|
||||
3. =python export.py Interro BGnot= (gestion perso)
|
||||
Cela déplace les groupes dans le dossier configuré par =EXPORT_DIR=
|
||||
(par défaut =Export=).
|
||||
|
||||
+72
-3
@@ -125,11 +125,80 @@ def _initial_label_groups(labels: list[str]) -> str:
|
||||
return "".join(",".join(items) + "\n" for items in groups.values())
|
||||
|
||||
|
||||
def _gemini_label_groups(
|
||||
workspace: EvaluationWorkspace, labels: list[str]
|
||||
) -> list[list[str]] | None:
|
||||
source = workspace.gemini_exam_items_file
|
||||
if not source.is_file():
|
||||
return None
|
||||
|
||||
groups: list[list[str]] = []
|
||||
current: list[str] = []
|
||||
try:
|
||||
source_lines = source.read_text(encoding="utf-8").splitlines()
|
||||
except (OSError, UnicodeError) as exc:
|
||||
print(f"Warning: could not read Gemini question groups from {source}: {exc}")
|
||||
return None
|
||||
for raw_line in source_lines:
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if line == "---":
|
||||
if current:
|
||||
groups.append(current)
|
||||
current = []
|
||||
continue
|
||||
if " ### " not in line:
|
||||
continue
|
||||
label = line.split(" ### ", 1)[0].strip()
|
||||
if label and label != "CONTEXT":
|
||||
current.append(label)
|
||||
if current:
|
||||
groups.append(current)
|
||||
|
||||
flattened = [label for group in groups for label in group]
|
||||
known = set(labels)
|
||||
if (
|
||||
not flattened
|
||||
or len(flattened) != len(set(flattened))
|
||||
or set(flattened) != known
|
||||
):
|
||||
missing = sorted(known.difference(flattened), key=natural_key)
|
||||
unknown = sorted(set(flattened).difference(known), key=natural_key)
|
||||
details = []
|
||||
if missing:
|
||||
details.append("missing: " + ", ".join(missing))
|
||||
if unknown:
|
||||
details.append("unknown: " + ", ".join(unknown))
|
||||
if len(flattened) != len(set(flattened)):
|
||||
details.append("duplicate labels")
|
||||
print(
|
||||
f"Warning: ignoring incompatible Gemini question groups in {source}"
|
||||
+ (f" ({'; '.join(details)})" if details else "")
|
||||
)
|
||||
return None
|
||||
return groups
|
||||
|
||||
|
||||
def _serialize_label_groups(groups: list[list[str]]) -> str:
|
||||
return "".join(",".join(group) + "\n" for group in groups)
|
||||
|
||||
|
||||
def _load_label_groups(workspace: EvaluationWorkspace, labels: list[str]) -> list[list[str]]:
|
||||
label_groups = workspace.root / "label_groups"
|
||||
label_groups = workspace.label_groups_file
|
||||
if not label_groups.exists():
|
||||
atomic_write_text(label_groups, _initial_label_groups(labels))
|
||||
print(f"Created {label_groups}; review the groups before continuing.")
|
||||
gemini_groups = _gemini_label_groups(workspace, labels)
|
||||
if gemini_groups is not None:
|
||||
initial_content = _serialize_label_groups(gemini_groups)
|
||||
source_description = "the groups selected in gemini_for_enonce.py"
|
||||
else:
|
||||
initial_content = _initial_label_groups(labels)
|
||||
source_description = "the label-prefix fallback"
|
||||
atomic_write_text(label_groups, initial_content)
|
||||
print(
|
||||
f"Created {label_groups} from {source_description}; "
|
||||
"review the groups before continuing."
|
||||
)
|
||||
utils.edit_file_and_enter(label_groups)
|
||||
known_labels = set(labels)
|
||||
groups: list[list[str]] = []
|
||||
|
||||
@@ -93,6 +93,14 @@ class EvaluationWorkspace:
|
||||
def labels_file(self) -> Path:
|
||||
return self.root / "labels"
|
||||
|
||||
@property
|
||||
def label_groups_file(self) -> Path:
|
||||
return self.root / "label_groups"
|
||||
|
||||
@property
|
||||
def gemini_exam_items_file(self) -> Path:
|
||||
return self.root / "Tmp" / "exam_items.txt"
|
||||
|
||||
@property
|
||||
def correction_file(self) -> Path:
|
||||
return self.root / "correction.json"
|
||||
|
||||
@@ -80,6 +80,10 @@ class WorkspaceTests(unittest.TestCase):
|
||||
|
||||
self.assertFalse(workspace.metadata_dir.exists())
|
||||
self.assertEqual(workspace.labels_file, root / "labels")
|
||||
self.assertEqual(workspace.label_groups_file, root / "label_groups")
|
||||
self.assertEqual(
|
||||
workspace.gemini_exam_items_file, root / "Tmp" / "exam_items.txt"
|
||||
)
|
||||
self.assertEqual(workspace.copies_dir, root / "Copies")
|
||||
self.assertEqual(workspace.annotation_dir("grouped"), root / "BGnot")
|
||||
self.assertEqual(workspace.state_database, root / ".copienator" / "state.sqlite3")
|
||||
@@ -1400,6 +1404,65 @@ class StandardCliTests(unittest.TestCase):
|
||||
(previous / "sentinel.txt").read_text(encoding="utf-8"), "old"
|
||||
)
|
||||
|
||||
def test_grouped_annotations_default_to_gemini_question_groups(self) -> None:
|
||||
module = self.modules["annotating_by_label"]
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
evaluation = Path(directory) / "Exam"
|
||||
items = evaluation / "Tmp" / "exam_items.txt"
|
||||
items.parent.mkdir(parents=True)
|
||||
items.write_text(
|
||||
"# edited Gemini groups\n"
|
||||
"Ex 1 ### First question\n"
|
||||
"CONTEXT ### Shared context\n"
|
||||
"Ex 2 ### Second question\n"
|
||||
"\n---\n\n"
|
||||
"Ex 3 ### Third question\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
workspace = EvaluationWorkspace(evaluation)
|
||||
|
||||
with patch.object(module.utils, "edit_file_and_enter") as editor:
|
||||
groups = module._load_label_groups(
|
||||
workspace, ["Ex 1", "Ex 2", "Ex 3"]
|
||||
)
|
||||
|
||||
self.assertEqual(groups, [["Ex 1", "Ex 2"], ["Ex 3"]])
|
||||
self.assertEqual(
|
||||
workspace.label_groups_file.read_text(encoding="utf-8"),
|
||||
"Ex 1,Ex 2\nEx 3\n",
|
||||
)
|
||||
editor.assert_called_once_with(workspace.label_groups_file)
|
||||
|
||||
def test_grouped_annotations_fall_back_when_gemini_was_not_run(self) -> None:
|
||||
module = self.modules["annotating_by_label"]
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
workspace = EvaluationWorkspace(Path(directory) / "Exam")
|
||||
workspace.root.mkdir()
|
||||
labels = ["Ex 1 : a", "Ex 1 : b", "Ex 2"]
|
||||
|
||||
with patch.object(module.utils, "edit_file_and_enter"):
|
||||
groups = module._load_label_groups(workspace, labels)
|
||||
|
||||
self.assertEqual(groups, [["Ex 1 : a", "Ex 1 : b"], ["Ex 2"]])
|
||||
|
||||
def test_existing_label_groups_override_gemini_defaults(self) -> None:
|
||||
module = self.modules["annotating_by_label"]
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
workspace = EvaluationWorkspace(Path(directory) / "Exam")
|
||||
workspace.gemini_exam_items_file.parent.mkdir(parents=True)
|
||||
workspace.gemini_exam_items_file.write_text(
|
||||
"Ex 1 ### First\nEx 2 ### Second\n", encoding="utf-8"
|
||||
)
|
||||
workspace.label_groups_file.write_text(
|
||||
"Ex 1\nEx 2\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
with patch.object(module.utils, "edit_file_and_enter") as editor:
|
||||
groups = module._load_label_groups(workspace, ["Ex 1", "Ex 2"])
|
||||
|
||||
self.assertEqual(groups, [["Ex 1"], ["Ex 2"]])
|
||||
editor.assert_not_called()
|
||||
|
||||
def test_grouped_batching_does_not_split_one_student(self) -> None:
|
||||
module = self.modules["annotating_by_label"]
|
||||
image = Image.new("RGB", (10, 60), "white")
|
||||
|
||||
Reference in New Issue
Block a user