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:
110
backend/domain/value_objects/statistics.py
Normal file
110
backend/domain/value_objects/statistics.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
Value Objects pour les statistiques.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StatisticsResult:
|
||||
"""
|
||||
Résultat des calculs statistiques.
|
||||
|
||||
Attributes:
|
||||
count: Nombre de valeurs
|
||||
mean: Moyenne
|
||||
median: Médiane
|
||||
min: Minimum
|
||||
max: Maximum
|
||||
std_dev: Écart-type
|
||||
"""
|
||||
count: int
|
||||
mean: float
|
||||
median: float
|
||||
min: float
|
||||
max: float
|
||||
std_dev: float
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convertit en dictionnaire pour la sérialisation."""
|
||||
return {
|
||||
"count": self.count,
|
||||
"mean": self.mean,
|
||||
"median": self.median,
|
||||
"min": self.min,
|
||||
"max": self.max,
|
||||
"std_dev": self.std_dev
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> "StatisticsResult":
|
||||
"""Crée un résultat vide."""
|
||||
return cls(
|
||||
count=0,
|
||||
mean=0.0,
|
||||
median=0.0,
|
||||
min=0.0,
|
||||
max=0.0,
|
||||
std_dev=0.0
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HistogramBin:
|
||||
"""
|
||||
Un bin d'histogramme.
|
||||
|
||||
Attributes:
|
||||
range_start: Début de l'intervalle
|
||||
range_end: Fin de l'intervalle
|
||||
count: Nombre de valeurs dans l'intervalle
|
||||
"""
|
||||
range_start: float
|
||||
range_end: float
|
||||
count: int
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
"""Génère le label pour l'affichage."""
|
||||
if self.range_end == float('inf'):
|
||||
return f"{int(self.range_start)}+"
|
||||
return f"{int(self.range_start)}-{int(self.range_end)}"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convertit en dictionnaire pour la sérialisation."""
|
||||
return {
|
||||
"range": self.label,
|
||||
"count": self.count
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class HistogramData:
|
||||
"""
|
||||
Données d'histogramme.
|
||||
|
||||
Attributes:
|
||||
bins: Liste des bins
|
||||
max_count: Nombre maximum dans un bin (pour l'affichage)
|
||||
"""
|
||||
bins: List[HistogramBin]
|
||||
|
||||
@property
|
||||
def max_count(self) -> int:
|
||||
"""Retourne le count maximum parmi tous les bins."""
|
||||
if not self.bins:
|
||||
return 0
|
||||
return max(bin.count for bin in self.bins)
|
||||
|
||||
@property
|
||||
def total_count(self) -> int:
|
||||
"""Retourne le nombre total d'éléments."""
|
||||
return sum(bin.count for bin in self.bins)
|
||||
|
||||
def to_list(self) -> List[int]:
|
||||
"""Convertit en liste simple de counts (pour Chart.js)."""
|
||||
return [bin.count for bin in self.bins]
|
||||
|
||||
def to_dict_list(self) -> List[dict]:
|
||||
"""Convertit en liste de dictionnaires."""
|
||||
return [bin.to_dict() for bin in self.bins]
|
||||
Reference in New Issue
Block a user