| 1 | from builder.exam import Exam
|
|---|
| 2 | from flexoentity import EntityState, EntityType, Domain
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 | class ExamManager:
|
|---|
| 6 | def __init__(self):
|
|---|
| 7 | self._exams = {}
|
|---|
| 8 | self.active_exam_id = None
|
|---|
| 9 |
|
|---|
| 10 | def create_exam(self, title: str, author: str = "unknown", domain: str = "EX"):
|
|---|
| 11 | """Create and register a new Exam entity."""
|
|---|
| 12 | exam = Exam.with_domain(domain)
|
|---|
| 13 | exam.author = author
|
|---|
| 14 | exam.title = title
|
|---|
| 15 | exam._update_fingerprint
|
|---|
| 16 | exam_id = exam.flexo_id
|
|---|
| 17 |
|
|---|
| 18 | if exam.flexo_id in self._exams:
|
|---|
| 19 | raise ValueError(f"Exam {exam.flexo_id} already exists.")
|
|---|
| 20 |
|
|---|
| 21 | self._exams[exam_id] = exam
|
|---|
| 22 | self.active_exam_id = exam.flexo_id
|
|---|
| 23 | return exam
|
|---|
| 24 |
|
|---|
| 25 | @property
|
|---|
| 26 | def exams(self):
|
|---|
| 27 | return self._exams
|
|---|
| 28 |
|
|---|
| 29 | def add_exam(self, exam):
|
|---|
| 30 | """Add an externally loaded Exam (e.g., from JSON)."""
|
|---|
| 31 | self._exams[exam.flexo_id] = exam
|
|---|
| 32 | self.active_exam_id = exam.flexo_id
|
|---|
| 33 |
|
|---|
| 34 | def list_exams(self):
|
|---|
| 35 | return list(self.exams.keys())
|
|---|
| 36 |
|
|---|
| 37 | def list_titles(self):
|
|---|
| 38 | """Return titles for GUI dropdowns."""
|
|---|
| 39 | return [exam.title for exam in self._exams.values()]
|
|---|
| 40 |
|
|---|
| 41 | def get_active(self):
|
|---|
| 42 | return self.exams.get(self.active_exam_id)
|
|---|
| 43 |
|
|---|
| 44 | def get_active_id(self) -> str | None:
|
|---|
| 45 | return self.active_exam_id
|
|---|
| 46 |
|
|---|
| 47 | def set_active(self, exam_id: str):
|
|---|
| 48 | if exam_id not in self._exams:
|
|---|
| 49 | raise ValueError(f"No exam with id {exam_id}")
|
|---|
| 50 | self.active_exam_id = exam_id
|
|---|
| 51 |
|
|---|
| 52 | def set_active_by_title(self, title: str):
|
|---|
| 53 | """Convenience for GUI dropdown selection."""
|
|---|
| 54 | for exam in self._exams.values():
|
|---|
| 55 | if exam.title == title:
|
|---|
| 56 | self.active_exam_id = exam.flexo_id.id
|
|---|
| 57 | return
|
|---|
| 58 | raise ValueError(f"No exam with title '{title}' found")
|
|---|
| 59 |
|
|---|
| 60 | def remove_exam(self, exam_id: str) -> bool:
|
|---|
| 61 | if exam_id not in self._exams:
|
|---|
| 62 | return False
|
|---|
| 63 | del self._exams[exam_id]
|
|---|
| 64 | if self.active_exam_id == exam_id:
|
|---|
| 65 | self.active_exam_id = None
|
|---|
| 66 | return True
|
|---|
| 67 |
|
|---|
| 68 | # ────────────────────────────────────────────────
|
|---|
| 69 | # Persistence helpers
|
|---|
| 70 | # ────────────────────────────────────────────────
|
|---|
| 71 | def save_all(self, directory: str):
|
|---|
| 72 | """Save all exams to JSON files in a given directory."""
|
|---|
| 73 | from pathlib import Path
|
|---|
| 74 |
|
|---|
| 75 | directory = Path(directory)
|
|---|
| 76 | directory.mkdir(parents=True, exist_ok=True)
|
|---|
| 77 | for exam in self._exams.values():
|
|---|
| 78 | filename = directory / f"{exam.title}_{exam.flexo_id.id}.json"
|
|---|
| 79 | exam.to_json_file(filename)
|
|---|
| 80 |
|
|---|
| 81 | def load_all(self, directory: str):
|
|---|
| 82 | """Load all exams from JSON files in a given directory."""
|
|---|
| 83 | from pathlib import Path
|
|---|
| 84 | import json
|
|---|
| 85 |
|
|---|
| 86 | directory = Path(directory)
|
|---|
| 87 | if not directory.exists():
|
|---|
| 88 | return
|
|---|
| 89 | for path in directory.glob("*.json"):
|
|---|
| 90 | with open(path, "r", encoding="utf-8") as f:
|
|---|
| 91 | data = json.load(f)
|
|---|
| 92 | exam = Exam.from_dict(data)
|
|---|
| 93 | self.add_exam(exam)
|
|---|