chore(integration) : intégrer les PR Pronote critiques dans main #35

Merged
Codex merged 9 commits from fix/target-school-day-30 into main 2026-09-12 15:00:33 +02:00
2 changed files with 28 additions and 21 deletions
Showing only changes of commit 59e6a1a451 - Show all commits
+14 -21
View File
@@ -43,16 +43,16 @@ def collapse_replaced_lessons(lessons: list[Lesson]) -> list[Lesson]:
"""Supprime l'ancien exemplaire annulé lorsqu'un cours déplacé est présent.
Deux cours sont considérés comme les exemplaires d'une même modification
lorsqu'ils partagent les dates, la matière, les enseignants et le groupe.
Une annulation isolée est toujours conservée ; seule l'annulation associée
à un exemplaire ``MOVED`` est retirée.
lorsqu'ils partagent les dates, la matière, les enseignants et le groupe,
et qu'une seule annulation et un seul déplacement se distinguent par leur
salle. Une annulation isolée ou ambiguë est toujours conservée.
:param lessons: Cours issus d'une source Pronote.
:return: Cours consolidés dans leur ordre d'origine.
:rtype: list[Lesson]
"""
grouped: dict[tuple[object, ...], list[Lesson]] = defaultdict(list)
for lesson in lessons:
grouped: dict[tuple[object, ...], list[tuple[int, Lesson]]] = defaultdict(list)
for index, lesson in enumerate(lessons):
key = (
lesson.start,
lesson.end,
@@ -60,20 +60,13 @@ def collapse_replaced_lessons(lessons: list[Lesson]) -> list[Lesson]:
tuple(teacher.casefold() for teacher in lesson.teachers),
(lesson.group or "").casefold(),
)
grouped[key].append(lesson)
grouped[key].append((index, lesson))
result: list[Lesson] = []
for lesson in lessons:
key = (
lesson.start,
lesson.end,
lesson.subject.casefold(),
tuple(teacher.casefold() for teacher in lesson.teachers),
(lesson.group or "").casefold(),
)
siblings = grouped[key]
has_moved = any(sibling.status == LessonStatus.MOVED for sibling in siblings)
if has_moved and lesson.status == LessonStatus.CANCELLED:
continue
result.append(lesson)
return result
replaced_indexes: set[int] = set()
for siblings in grouped.values():
cancelled = [item for item in siblings if item[1].status == LessonStatus.CANCELLED]
moved = [item for item in siblings if item[1].status == LessonStatus.MOVED]
if len(cancelled) == 1 and len(moved) == 1 and cancelled[0][1].rooms != moved[0][1].rooms:
replaced_indexes.add(cancelled[0][0])
return [lesson for index, lesson in enumerate(lessons) if index not in replaced_indexes]
+14
View File
@@ -75,3 +75,17 @@ def test_collapse_replaced_lessons_keeps_real_cancellation() -> None:
cancelled = _lesson(LessonStatus.CANCELLED, "101")
assert collapse_replaced_lessons([cancelled]) == [cancelled]
def test_collapse_replaced_lessons_keeps_ambiguous_cancellations() -> None:
"""Conserve les annulations lorsqu'une paire de remplacement est ambiguë.
:return: None
"""
cancelled_old = _lesson(LessonStatus.CANCELLED, "101")
cancelled_real = _lesson(LessonStatus.CANCELLED, "305")
moved = _lesson(LessonStatus.MOVED, "204")
result = collapse_replaced_lessons([cancelled_old, cancelled_real, moved])
assert result == [cancelled_old, cancelled_real, moved]