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>
56 lines
2.5 KiB
Python
56 lines
2.5 KiB
Python
"""Service déterministe de calcul de la parité des semaines pour l'agenda théorique.
|
|
|
|
Ce module fournit :class:`WeekParityService`, un service sans état qui détermine
|
|
si la semaine contenant une date donnée est paire ou impaire, à partir d'une
|
|
date d'ancrage dont la parité est connue. L'algorithme repose sur le décalage
|
|
entre les lundis des deux semaines, et non sur les numéros de semaine ISO.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, timedelta
|
|
from typing import Literal
|
|
|
|
|
|
class WeekParityService:
|
|
"""Service déterministe de calcul de la parité des semaines.
|
|
|
|
La parité d'une semaine est déduite d'une date d'ancrage fournie à la
|
|
construction : la semaine contenant cette date a une parité connue
|
|
(paire ou impaire). Le service est immuable après construction et ne
|
|
dépend d'aucun état global ni de l'horloge système.
|
|
"""
|
|
|
|
def __init__(self, anchor_date: date, anchor_type: Literal["even", "odd"]) -> None:
|
|
"""Initialise le service avec la date d'ancrage et sa parité.
|
|
|
|
:param anchor_date: Date de référence dont la semaine a une parité connue.
|
|
:param anchor_type: Parité de la semaine d'ancrage (``"even"`` ou ``"odd"``).
|
|
"""
|
|
self._anchor_monday = anchor_date - timedelta(days=anchor_date.weekday())
|
|
self._anchor_type = anchor_type
|
|
|
|
def parity_for(self, target_date: date) -> Literal["even", "odd"]:
|
|
"""Détermine la parité de la semaine contenant la date cible.
|
|
|
|
Algorithme :
|
|
1. Calculer le lundi de la semaine de la date cible.
|
|
2. Utiliser le lundi de la semaine de la date d'ancrage (stocké à
|
|
l'initialisation).
|
|
3. Calculer le nombre de semaines entre les deux lundis :
|
|
``(target_monday - anchor_monday).days // 7``.
|
|
4. Si le décalage de semaines est pair, la cible a la même parité que
|
|
l'ancrage.
|
|
5. Si le décalage de semaines est impair, la cible a la parité opposée.
|
|
|
|
:param target_date: Date dont il faut déterminer la parité.
|
|
:return: ``"even"`` ou ``"odd"`` selon la parité de la semaine cible.
|
|
:rtype: Literal["even", "odd"]
|
|
"""
|
|
target_monday = target_date - timedelta(days=target_date.weekday())
|
|
week_offset = (target_monday - self._anchor_monday).days // 7
|
|
if week_offset % 2 == 0:
|
|
return self._anchor_type
|
|
# Inverse la parité.
|
|
return "odd" if self._anchor_type == "even" else "even"
|