Files
notytex/backend/domain/value_objects/score.py
Bertrand Benjamin 2b08eb534a 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!
2025-11-25 21:09:47 +01:00

114 lines
3.1 KiB
Python

"""
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
)