"""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, model_validator 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.""" model_config = ConfigDict(frozen=True) 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: tuple[str, ...] = Field(default=(), description="Liste des professeurs") rooms: tuple[str, ...] = Field(default=(), 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: tuple[HomeworkBlock, ...] = Field( default=(), description="Blocs de devoirs extraits de la description", ) @model_validator(mode="after") def _validate_time_order(self) -> Lesson: """Valide que l'heure de fin est postérieure à l'heure de début. :return: L'instance validée. :rtype: Lesson :raises ValueError: Si ``end`` n'est pas postérieur à ``start``. """ if self.end <= self.start: raise ValueError("end doit être postérieur à start") return self 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)") @model_validator(mode="after") def _validate_date_order(self) -> SchoolEvent: """Valide que la date de fin est postérieure ou égale à la date de début. :return: L'instance validée. :rtype: SchoolEvent :raises ValueError: Si ``to_date`` est antérieur à ``from_date``. """ if self.to_date < self.from_date: raise ValueError("to_date doit être postérieur ou égal à from_date") return self 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( ..., ge=0, le=6, 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: tuple[str, ...] = Field(default=(), description="Liste des professeurs") rooms: tuple[str, ...] = Field(default=(), description="Liste des salles") @model_validator(mode="after") def _validate_time_order(self) -> TheoreticalLesson: """Valide que l'heure de fin est postérieure à l'heure de début. :return: L'instance validée. :rtype: TheoreticalLesson :raises ValueError: Si ``end_time`` n'est pas postérieur à ``start_time``. """ if self.end_time <= self.start_time: raise ValueError("end_time doit être postérieur à start_time") return self