Migration v1 (Flask) -> v2 (FastAPI + Vue.js) complétée
✨ 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!
This commit is contained in:
113
backend/domain/value_objects/score.py
Normal file
113
backend/domain/value_objects/score.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Value Objects pour les scores et notes.
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Optional, Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExerciseScore:
|
||||
"""
|
||||
Score d'un élève pour un exercice.
|
||||
|
||||
Attributes:
|
||||
exercise_id: ID de l'exercice
|
||||
title: Titre de l'exercice
|
||||
score: Score obtenu
|
||||
max_points: Points maximum possibles
|
||||
"""
|
||||
exercise_id: int
|
||||
title: str
|
||||
score: float
|
||||
max_points: float
|
||||
|
||||
@property
|
||||
def percentage(self) -> float:
|
||||
"""Calcule le pourcentage de réussite."""
|
||||
if self.max_points == 0:
|
||||
return 0.0
|
||||
return round((self.score / self.max_points) * 100, 1)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convertit en dictionnaire pour la sérialisation."""
|
||||
return {
|
||||
"score": round(self.score, 2),
|
||||
"max": self.max_points
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class StudentScore:
|
||||
"""
|
||||
Score complet d'un élève pour une évaluation.
|
||||
|
||||
Attributes:
|
||||
student_id: ID de l'élève
|
||||
student_name: Nom complet de l'élève
|
||||
total_score: Score total obtenu
|
||||
total_max_points: Points maximum totaux
|
||||
exercise_scores: Scores par exercice
|
||||
"""
|
||||
student_id: int
|
||||
student_name: str
|
||||
total_score: float
|
||||
total_max_points: float
|
||||
exercise_scores: Dict[int, ExerciseScore] = field(default_factory=dict)
|
||||
|
||||
@property
|
||||
def percentage(self) -> float:
|
||||
"""Calcule le pourcentage global de réussite."""
|
||||
if self.total_max_points == 0:
|
||||
return 0.0
|
||||
return round((self.total_score / self.total_max_points) * 100, 1)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convertit en dictionnaire pour la sérialisation."""
|
||||
return {
|
||||
"student_id": self.student_id,
|
||||
"student_name": self.student_name,
|
||||
"total_score": round(self.total_score, 2),
|
||||
"total_max_points": self.total_max_points,
|
||||
"percentage": self.percentage,
|
||||
"exercise_scores": {
|
||||
ex_id: ex_score.to_dict()
|
||||
for ex_id, ex_score in self.exercise_scores.items()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GradeValue:
|
||||
"""
|
||||
Représentation d'une valeur de note avec ses métadonnées.
|
||||
|
||||
Attributes:
|
||||
raw_value: Valeur brute saisie
|
||||
numeric_value: Valeur numérique calculée (None si dispensé)
|
||||
is_special: True si valeur spéciale (., d, a)
|
||||
counts_in_total: True si compte dans le total
|
||||
"""
|
||||
raw_value: str
|
||||
numeric_value: Optional[float]
|
||||
is_special: bool
|
||||
counts_in_total: bool
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> "GradeValue":
|
||||
"""Crée une valeur vide (pas de note saisie)."""
|
||||
return cls(
|
||||
raw_value="",
|
||||
numeric_value=None,
|
||||
is_special=False,
|
||||
counts_in_total=False
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dispensed(cls, raw_value: str = "d") -> "GradeValue":
|
||||
"""Crée une valeur pour un élève dispensé."""
|
||||
return cls(
|
||||
raw_value=raw_value,
|
||||
numeric_value=None,
|
||||
is_special=True,
|
||||
counts_in_total=False
|
||||
)
|
||||
Reference in New Issue
Block a user