Files
notytex/backend/schemas/assessment.py
Bertrand Benjamin f76b033d55
All checks were successful
Build and Publish Docker Images / Build Frontend Image (push) Successful in 2m56s
Build and Publish Docker Images / Build Backend Image (push) Successful in 3m5s
Build and Publish Docker Images / Build Summary (push) Successful in 3s
feat(mail): restauration de l'envoie de mail
2025-12-04 06:04:13 +01:00

237 lines
6.0 KiB
Python

"""
Schemas Pydantic pour Assessment, Exercise, GradingElement.
"""
from datetime import date, datetime
from typing import Optional, List, Union
from pydantic import Field, field_validator
from schemas.common import BaseSchema
# GradingElement schemas
class GradingElementBase(BaseSchema):
"""Schema de base pour GradingElement."""
label: str
description: Optional[str] = None
skill: Optional[str] = None
max_points: float
grading_type: str = "notes"
domain_id: Optional[int] = None
class GradingElementRead(GradingElementBase):
"""Schema pour la lecture d'un GradingElement."""
id: int
exercise_id: int
domain_name: Optional[str] = None
domain_color: Optional[str] = None
# Exercise schemas
class ExerciseBase(BaseSchema):
"""Schema de base pour Exercise."""
title: str
description: Optional[str] = None
order: int = 1
class ExerciseRead(ExerciseBase):
"""Schema pour la lecture d'un Exercise."""
id: int
assessment_id: int
grading_elements: List[GradingElementRead] = []
@property
def total_points(self) -> float:
return sum(e.max_points for e in self.grading_elements)
# Assessment schemas
class AssessmentBase(BaseSchema):
"""Schema de base pour Assessment."""
title: str
description: Optional[str] = None
date: date
trimester: int = Field(ge=1, le=3)
coefficient: float = 1.0
class AssessmentRead(AssessmentBase):
"""Schema pour la lecture d'un Assessment."""
id: int
class_group_id: int
class_name: Optional[str] = None
class AssessmentWithProgress(AssessmentRead):
"""Schema avec progression de correction."""
grading_progress: dict = Field(default_factory=dict)
total_max_points: float = 0
class AssessmentDetail(AssessmentWithProgress):
"""Schema détaillé avec exercices."""
exercises: List[ExerciseRead] = []
class AssessmentList(BaseSchema):
"""Schema pour la liste des évaluations."""
assessments: List[AssessmentWithProgress]
total: int
# Schemas pour les résultats
class StudentScore(BaseSchema):
"""Score d'un élève pour une évaluation."""
student_id: int
student_name: str
email: Optional[str] = None # Email de l'élève pour l'envoi des bilans
total_score: float
total_max_points: float
percentage: float
exercise_scores: dict = Field(default_factory=dict)
has_grades: bool = False # Indique si l'élève a au moins une note
class AssessmentStatistics(BaseSchema):
"""Statistiques d'une évaluation."""
count: int
mean: float
median: float
min: float
max: float
std_dev: float
class HeatmapCell(BaseSchema):
"""Cellule d'une heatmap (élève x compétence/domaine)."""
student_id: int
student_name: str
item_name: str # Nom de la compétence ou du domaine
score: float
max_points: float
percentage: float
color: Optional[str] = None
class HeatmapData(BaseSchema):
"""Données pour une heatmap."""
items: List[str] # Liste des compétences ou domaines
students: List[str] # Liste des noms d'élèves
cells: List[HeatmapCell] # Données par cellule
class AssessmentResults(BaseSchema):
"""Résultats complets d'une évaluation."""
assessment_id: int
assessment_title: str
statistics: AssessmentStatistics
students_scores: List[StudentScore]
histogram_data: List[int] = Field(default_factory=list)
competences_heatmap: Optional[HeatmapData] = None
domains_heatmap: Optional[HeatmapData] = None
# Schemas pour les opérations d'écriture (CRUD)
class GradingElementCreate(BaseSchema):
"""Schema pour créer ou mettre à jour un élément de notation."""
id: Optional[int] = None # Si présent, mise à jour; sinon création
label: str
description: Optional[str] = None
skill: Optional[str] = None
max_points: float
grading_type: str = "notes"
domain_id: Optional[int] = None
class ExerciseCreate(BaseSchema):
"""Schema pour créer ou mettre à jour un exercice avec ses éléments."""
id: Optional[int] = None # Si présent, mise à jour; sinon création
title: str
description: Optional[str] = None
order: int = 1
grading_elements: List[GradingElementCreate] = Field(default_factory=list)
class AssessmentCreate(AssessmentBase):
"""Schema pour créer une évaluation complète (unifiée)."""
class_group_id: int
exercises: List[ExerciseCreate] = Field(default_factory=list)
class AssessmentUpdate(BaseSchema):
"""Schema pour modifier une évaluation."""
title: Optional[str] = None
description: Optional[str] = None
date: Optional[str] = None # Accepter string, convertir après
trimester: Optional[int] = None
coefficient: Optional[float] = None
# Pour modification complète avec exercices
exercises: Optional[List[ExerciseCreate]] = None
@field_validator('date', mode='before')
@classmethod
def parse_date(cls, v):
if v is None:
return None
if isinstance(v, date):
return v.isoformat()
return v
class AssessmentResponse(BaseSchema):
"""Schema de réponse après création/modification."""
id: int
title: str
message: str = ""
# Schemas pour l'envoi de bilans par email
class SendReportsRequest(BaseSchema):
"""Requête pour envoyer des bilans par email."""
student_ids: List[int] = Field(..., min_length=1, description="Liste des IDs d'élèves")
custom_message: Optional[str] = Field(None, description="Message personnalisé du professeur")
class SendReportResult(BaseSchema):
"""Résultat de l'envoi pour un élève."""
student_id: int
student_name: str
email: Optional[str] = None
success: bool
error_message: Optional[str] = None
class SendReportsResponse(BaseSchema):
"""Réponse de l'envoi de bilans."""
success: bool
total_sent: int
total_failed: int
results: List[SendReportResult]
message: str