✨ Changements majeurs: - Suppression complète du code Flask legacy - Migration backend FastAPI vers racine /backend - Migration frontend Vue.js vers racine /frontend - Suppression de notytex-v2/ (code monté à la racine) ✅ Validations: - Backend démarre correctement (port 8000) - API /api/v2/health répond healthy - 99/99 tests unitaires passent - Frontend configuré avec proxy Vite 📝 Documentation: - README.md réécrit pour v2 - Instructions de démarrage mises à jour - .gitignore adapté pour backend/frontend/ 🎯 Architecture finale: notytex/ ├── backend/ # FastAPI + SQLAlchemy + Pydantic ├── frontend/ # Vue 3 + Vite + TailwindCSS ├── docs/ # Documentation └── school_management.db # Base de données (inchangée) Jalon 6 complété: Application v2 prête pour utilisation!
237 lines
6.0 KiB
Python
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
|
|
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")
|
|
include_class_stats: bool = Field(True, description="Inclure les statistiques de classe")
|
|
|
|
|
|
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
|