feat(M3): modèles de données Pydantic (22 modèles, 10 modules)

Modèles créés :
- models/agenda.py : Status, LessonStatus, HomeworkBlock, Lesson (frozen),
  SchoolEventKind, SchoolEvent (frozen), TheoreticalLesson (frozen)
- models/homework.py : Homework (frozen)
- models/message.py : MessageType, Message (frozen)
- models/blog.py : BlogArticle (frozen), ExternalInfo (mutable)
- models/diff.py : AgendaChangeType, AgendaChange (frozen), AgendaDiff (frozen)
- models/pronote.py : PronoteData (mutable)
- models/sync.py : CalDAVSyncStatus, CalDAVSyncPlan, CalDAVSyncResult (mutable)
- models/synthesis.py : SynthesisInput, SynthesisResult (mutable)
- models/xmpp.py : XmppMessage (frozen, external_info: ExternalInfo | None)
- models/__init__.py : ré-export des 22 modèles via __all__

Conventions appliquées :
- Pydantic v2 : model_config = ConfigDict(frozen=True), pas de json_encoders
- StrEnum au lieu de (str, Enum) (ruff UP042)
- Alias _date/_time pour éviter collision champ date/type date
- Sphinx/reST docstrings sur toutes les classes
- TODO.md : 11 items M3 cochés

Validations (@verifier) :
- ruff check : PASS (0 error)
- ruff format --check : PASS (10 fichiers)
- mypy strict : PASS (10 fichiers)
- bandit : PASS (0 issue)
- import 22 modèles : OK
- sérialisation JSON (frozen + mutable) : OK
- frozen immuable, PronoteData/CalDAVSyncResult mutable : OK
- StrEnum isinstance str : OK
- pytest : 0 test (infrastructure OK)

Co-authored-by: OpenCode/orchestrator <opencode-orchestrator@agents.invalid>
This commit is contained in:
2026-09-05 23:21:39 +02:00
parent c3f76ab91b
commit 2e7dfe6e46
11 changed files with 405 additions and 11 deletions

View File

@@ -0,0 +1,90 @@
"""Modèles de données pour l'agenda Pronote (cours, événements, agenda théorique)."""
from __future__ import annotations
from datetime import date as _date
from datetime import datetime
from datetime import time as _time
from enum import StrEnum
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
class Status(StrEnum):
"""Statut générique pour les événements."""
NORMAL = "normal"
CANCELLED = "cancelled"
MOVED = "moved"
class LessonStatus(StrEnum):
"""Statut d'un cours."""
NORMAL = "normal"
CANCELLED = "cancelled"
MOVED = "moved"
class HomeworkBlock(BaseModel):
"""Représente un bloc de devoir extrait de la description d'un cours."""
kind: Literal["due", "assigned"] = Field(
..., description="Type de bloc (échéance ou attribution)"
)
date: _date = Field(..., description="Date associée au bloc")
text: str = Field(..., description="Texte du devoir (brut)")
html: str = Field(default="", description="Texte du devoir (HTML)")
class Lesson(BaseModel):
"""Représente un cours dans l'agenda Pronote."""
model_config = ConfigDict(frozen=True)
id: str = Field(..., description="UID stable du cours (normalisé)")
start: datetime = Field(..., description="Date/heure de début")
end: datetime = Field(..., description="Date/heure de fin")
subject: str = Field(..., description="Matière")
teachers: list[str] = Field(default_factory=list, description="Liste des professeurs")
rooms: list[str] = Field(default_factory=list, description="Liste des salles")
group: str | None = Field(None, description="Groupe (ex: Classe entière)")
status: LessonStatus = Field(default=LessonStatus.NORMAL, description="Statut du cours")
content: str | None = Field(None, description="Contenu pédagogique")
homework_blocks: list[HomeworkBlock] = Field(
default_factory=list,
description="Blocs de devoirs extraits de la description",
)
class SchoolEventKind(StrEnum):
"""Type d'événement scolaire."""
HOLIDAY = "holiday"
PUBLIC_HOLIDAY = "public_holiday"
class SchoolEvent(BaseModel):
"""Représente un événement scolaire (vacances, jours fériés)."""
model_config = ConfigDict(frozen=True)
kind: SchoolEventKind = Field(..., description="Type d'événement")
label: str = Field(..., description="Libellé (ex: Vacances de Noël)")
from_date: _date = Field(..., description="Date de début (inclusive)")
to_date: _date = Field(..., description="Date de fin (exclusive)")
class TheoreticalLesson(BaseModel):
"""Représente un cours dans l'agenda théorique."""
model_config = ConfigDict(frozen=True)
id: str = Field(..., description="Identifiant unique")
day_of_week: int = Field(..., description="Jour de la semaine (0=lundi, 6=dimanche)")
start_time: _time = Field(..., description="Heure de début")
end_time: _time = Field(..., description="Heure de fin")
subject: str = Field(..., description="Matière")
teachers: list[str] = Field(default_factory=list, description="Liste des professeurs")
rooms: list[str] = Field(default_factory=list, description="Liste des salles")