feat(M6): agenda théorique JSON avec parité des semaines et vacances scolaires

Implémentation complète de la source d'agenda théorique :

- model.py : modèles Pydantic de parsing JSON (TheoreticalLessonEntry,
  TheoreticalAgendaFile) avec validation des formats d'heure et de l'ordre
  début/fin.
- parity.py : WeekParityService déterministe calculant la parité d'une
  semaine (paire/impaire) à partir d'une date de référence.
- holidays.py : SchoolHolidayCalendar lisant un fichier JSON de vacances
  scolaires (zone A) avec bornes inclusives.
- provider.py : protocole TheoreticalAgendaProvider (get_lessons,
  get_lessons_for_range).
- file.py : JsonTheoreticalAgendaProvider implémentant le protocole :
  filtrage par parité et vacances, génération d'IDs déterministes
  incluant le type de semaine, validation de l'unicité des IDs,
  tri stable par identifiant.
- __init__.py : factory get_theoretical_provider câblant la
  configuration (None si désactivé, erreur si config de parité partielle).
- Fixtures : theoretical.json (9 leçons all/even/odd) et
  school_holidays.json (zone A, 4 périodes).
- 57 tests unitaires couvrant parsing, parité, vacances, provider,
  factory, déduplication de range, collisions d'IDs.
- Guide : §8 et §12 alignés avec le format JSON.

Co-authored-by: opencode/coder <coder@agents.invalid>
Co-authored-by: opencode/test-engineer <test-engineer@agents.invalid>
This commit is contained in:
2026-09-06 23:06:29 +02:00
parent 4ec827a945
commit f9a1a5aa43
15 changed files with 1992 additions and 19 deletions

87
tests/fixtures/theoretical.json vendored Normal file
View File

@@ -0,0 +1,87 @@
{
"version": 1,
"lessons": [
{
"id": "theoretical-maths-monday-1",
"week": "all",
"day_of_week": 0,
"start_time": "08:00",
"end_time": "09:00",
"subject": "Mathématiques",
"teachers": ["Mme Martin"],
"rooms": ["101"]
},
{
"week": "all",
"day_of_week": 0,
"start_time": "09:00",
"end_time": "10:00",
"subject": "Français",
"teachers": ["M. Dupont"],
"rooms": ["102"]
},
{
"week": "all",
"day_of_week": 0,
"start_time": "11:00",
"end_time": "12:00",
"subject": "Histoire-Géographie",
"teachers": ["Mme Petit"],
"rooms": ["103"]
},
{
"week": "even",
"day_of_week": 1,
"start_time": "10:00",
"end_time": "11:00",
"subject": "Anglais",
"teachers": ["Mme Bernard"],
"rooms": ["201"]
},
{
"week": "even",
"day_of_week": 1,
"start_time": "10:00",
"end_time": "11:00",
"subject": "Technologie",
"teachers": [],
"rooms": []
},
{
"week": "odd",
"day_of_week": 1,
"start_time": "10:00",
"end_time": "11:00",
"subject": "Espagnol",
"teachers": ["M. Garcia"],
"rooms": ["202"]
},
{
"week": "odd",
"day_of_week": 1,
"start_time": "10:00",
"end_time": "11:00",
"subject": "Éducation musicale",
"teachers": [],
"rooms": []
},
{
"week": "all",
"day_of_week": 2,
"start_time": "14:00",
"end_time": "15:00",
"subject": "Sciences",
"teachers": [],
"rooms": ["203"]
},
{
"week": "all",
"day_of_week": 4,
"start_time": "09:00",
"end_time": "10:00",
"subject": "Arts plastiques",
"teachers": [],
"rooms": []
}
]
}