""" Service de calcul des scores des élèves. Calcule les scores pour chaque élève d'une évaluation, par exercice et au total. """ from typing import Dict, List, Optional, Any from dataclasses import dataclass from domain.value_objects import StudentScore, ExerciseScore from domain.services.grading_calculator import GradingCalculator @dataclass class GradeData: """Données d'une note pour le calcul.""" student_id: int grading_element_id: int value: Optional[str] grading_type: str max_points: float exercise_id: int exercise_title: str @dataclass class StudentData: """Données d'un élève pour le calcul.""" id: int first_name: str last_name: str @property def full_name(self) -> str: """Nom complet au format 'Nom Prénom'.""" return f"{self.last_name} {self.first_name}" class StudentScoreCalculator: """ Calculateur de scores par élève. Utilise le GradingCalculator pour calculer les scores individuels et agrège les résultats par élève et par exercice. """ def __init__(self, grading_calculator: Optional[GradingCalculator] = None): """ Initialise le calculateur. Args: grading_calculator: Calculateur de notes (créé si non fourni) """ self.grading_calculator = grading_calculator or GradingCalculator() def calculate_all_scores( self, students: List[StudentData], grades: List[GradeData], exercises: List[Dict[str, Any]] ) -> Dict[int, StudentScore]: """ Calcule les scores de tous les élèves pour une évaluation. Args: students: Liste des élèves grades: Liste des notes exercises: Liste des exercices avec leurs éléments Returns: Dictionnaire {student_id: StudentScore} """ # Indexer les notes par (student_id, element_id) grades_index: Dict[tuple, GradeData] = {} for grade in grades: key = (grade.student_id, grade.grading_element_id) grades_index[key] = grade # Calculer pour chaque élève result = {} for student in students: student_score = self._calculate_student_score( student, grades_index, exercises ) result[student.id] = student_score return result def _calculate_student_score( self, student: StudentData, grades_index: Dict[tuple, GradeData], exercises: List[Dict[str, Any]] ) -> StudentScore: """ Calcule le score d'un seul élève. Args: student: Données de l'élève grades_index: Index des notes (student_id, element_id) -> GradeData exercises: Liste des exercices Returns: StudentScore avec les scores par exercice """ total_score = 0.0 total_max_points = 0.0 exercise_scores = {} for exercise in exercises: exercise_id = exercise["id"] exercise_title = exercise["title"] elements = exercise.get("elements", []) ex_score = 0.0 ex_max = 0.0 for element in elements: element_id = element["id"] grading_type = element["grading_type"] max_points = element["max_points"] # Récupérer la note grade_data = grades_index.get((student.id, element_id)) if grade_data and grade_data.value: value = grade_data.value.strip() # Calculer le score calculated = self.grading_calculator.calculate_score( value, grading_type, max_points ) # Vérifier si compte dans le total if self.grading_calculator.is_counted_in_total(value): if calculated is not None: # Pas dispensé ex_score += calculated ex_max += max_points # Stocker le score de l'exercice exercise_scores[exercise_id] = ExerciseScore( exercise_id=exercise_id, title=exercise_title, score=ex_score, max_points=ex_max ) total_score += ex_score total_max_points += ex_max return StudentScore( student_id=student.id, student_name=student.full_name, total_score=round(total_score, 2), total_max_points=total_max_points, exercise_scores=exercise_scores ) def calculate_from_raw_data( self, assessment_data: Dict[str, Any], students_data: List[Dict[str, Any]], grades_data: List[Dict[str, Any]] ) -> Dict[int, StudentScore]: """ Calcule les scores à partir de données brutes (dicts). Méthode utilitaire pour simplifier l'utilisation avec des données provenant directement de la base de données. Args: assessment_data: Données de l'évaluation avec exercises et elements students_data: Liste des élèves [{id, first_name, last_name}] grades_data: Liste des notes [{student_id, element_id, value, ...}] Returns: Dictionnaire des scores par élève """ # Convertir les données students = [ StudentData( id=s["id"], first_name=s["first_name"], last_name=s["last_name"] ) for s in students_data ] # Préparer les exercices avec leurs éléments exercises = [] element_info = {} # Cache des infos d'éléments for ex in assessment_data.get("exercises", []): elements = [] for elem in ex.get("grading_elements", []): element_info[elem["id"]] = { "grading_type": elem["grading_type"], "max_points": elem["max_points"], "exercise_id": ex["id"], "exercise_title": ex["title"] } elements.append({ "id": elem["id"], "grading_type": elem["grading_type"], "max_points": elem["max_points"] }) exercises.append({ "id": ex["id"], "title": ex["title"], "elements": elements }) # Convertir les notes grades = [] for g in grades_data: elem_id = g["grading_element_id"] info = element_info.get(elem_id, {}) grades.append(GradeData( student_id=g["student_id"], grading_element_id=elem_id, value=g.get("value"), grading_type=info.get("grading_type", "notes"), max_points=info.get("max_points", 0), exercise_id=info.get("exercise_id", 0), exercise_title=info.get("exercise_title", "") )) return self.calculate_all_scores(students, grades, exercises) class ProgressCalculator: """ Calculateur de progression de correction. Calcule le pourcentage de notes saisies pour une évaluation. """ @staticmethod def calculate_progress( grades_count: int, total_elements: int, students_count: int ) -> Dict[str, Any]: """ Calcule la progression de correction. Args: grades_count: Nombre de notes saisies total_elements: Nombre d'éléments de notation students_count: Nombre d'élèves Returns: Dict avec percentage, completed, total, status, students_count """ total = total_elements * students_count completed = grades_count if total == 0: percentage = 0 status = "not_started" else: percentage = round((completed / total) * 100) if percentage == 0: status = "not_started" elif percentage == 100: status = "completed" else: status = "in_progress" return { "percentage": percentage, "completed": completed, "total": total, "status": status, "students_count": students_count }