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:
@@ -4873,12 +4873,12 @@ Les autres étapes (`normalize_step`, `compare_step`, etc.) suivent le même pri
|
||||
tests/
|
||||
├── __init__.py
|
||||
├── conftest.py # Fixtures pytest partagées
|
||||
├── fixtures/ # Fichiers de fixtures (iCal, CSV, XML, etc.)
|
||||
│ ├── pronote-4e.ics # Flux iCal Pronote anonymisé (4ème)
|
||||
│ ├── pronote-6e.ics # Flux iCal Pronote anonymisé (6ème)
|
||||
│ ├── theoretical.ics # Agenda théorique iCal
|
||||
│ ├── theoretical.csv # Agenda théorique CSV
|
||||
│ └── blog_rss.xml # Flux RSS du blog anonymisé
|
||||
├── fixtures/ # Fichiers de fixtures (iCal, JSON, XML, etc.)
|
||||
│ ├── pronote-4e.ics # Flux iCal Pronote anonymisé (4ème)
|
||||
│ ├── pronote-6e.ics # Flux iCal Pronote anonymisé (6ème)
|
||||
│ ├── theoretical.json # Agenda théorique JSON
|
||||
│ ├── school_holidays.json # Vacances scolaires JSON
|
||||
│ └── blog_rss.xml # Flux RSS du blog anonymisé
|
||||
├── unit/ # Tests unitaires
|
||||
│ ├── test_models.py # Tests des modèles Pydantic
|
||||
│ ├── test_parsing.py # Tests du parsing iCal
|
||||
@@ -4951,17 +4951,49 @@ END:VCALENDAR
|
||||
- **Données réalistes** : Structure identique aux flux réels Pronote.
|
||||
|
||||
|
||||
#### 12.3.2 Exemple de fichier CSV théorique (`tests/fixtures/theoretical.csv`)
|
||||
#### 12.3.2 Exemple de fichier JSON théorique (`tests/fixtures/theoretical.json`)
|
||||
|
||||
```csv
|
||||
jour,semaine,heure_debut,heure_fin,matiere,professeur,salle
|
||||
lundi,1,08:00,09:00,Mathématiques,M. Dupont,204
|
||||
lundi,1,09:00,10:00,Français,Mme Martin,205
|
||||
lundi,1,10:00,11:00,Histoire-Géographie,M. Bernard,206
|
||||
mardi,1,08:00,09:00,Physique-Chimie,Mme Durand,301
|
||||
...
|
||||
```json
|
||||
{
|
||||
"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": "even",
|
||||
"day_of_week": 1,
|
||||
"start_time": "10:00",
|
||||
"end_time": "11:00",
|
||||
"subject": "Anglais",
|
||||
"teachers": ["Mme Bernard"],
|
||||
"rooms": ["201"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Champs clés** :
|
||||
- `week` : `all` (toutes les semaines), `even` (semaines paires) ou `odd` (semaines impaires).
|
||||
- `day_of_week` : jour de la semaine (0 = lundi, 4 = vendredi).
|
||||
- `start_time` / `end_time` : créneau horaire au format `HH:MM`.
|
||||
|
||||
|
||||
### 12.4 Configuration pytest (`tests/conftest.py`)
|
||||
|
||||
@@ -5203,7 +5235,7 @@ def sample_settings():
|
||||
app=AppSettings(
|
||||
dry_run=True,
|
||||
log_level="DEBUG",
|
||||
theoretical_agenda_path="./tests/fixtures/theoretical.ics",
|
||||
theoretical_agenda_path="./tests/fixtures/theoretical.json",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -5254,7 +5286,7 @@ def test_pipeline_full(mock_requests_get, mock_caldav_client, mock_ai_provider,
|
||||
from pronote_sync.sources.pronote.fallback import PronoteFetcher
|
||||
from pronote_sync.sync.caldav import CalDAVClient
|
||||
from pronote_sync.sync.diff import AgendaComparator
|
||||
from pronote_sync.sources.theoretical.file import CSVTheoreticalAgendaProvider
|
||||
from pronote_sync.sources.theoretical.file import JsonTheoreticalAgendaProvider
|
||||
|
||||
# Configurer le fetcher Pronote
|
||||
pronote_client = PronoteClient(sample_settings.pronote)
|
||||
@@ -5269,7 +5301,7 @@ def test_pipeline_full(mock_requests_get, mock_caldav_client, mock_ai_provider,
|
||||
)
|
||||
|
||||
# Configurer le comparateur d'agenda
|
||||
theoretical_provider = CSVTheoreticalAgendaProvider(
|
||||
theoretical_provider = JsonTheoreticalAgendaProvider(
|
||||
file_path=sample_settings.app.theoretical_agenda_path
|
||||
)
|
||||
comparator = AgendaComparator(theoretical_provider)
|
||||
|
||||
Reference in New Issue
Block a user