# Améliorations du Dashboard de Classe **Date**: 3 décembre 2025 **Version**: 2.0 **Fichiers modifiés**: 4 fichiers (3 backend, 1 frontend) ## 📋 Objectifs des Modifications ### Tableau des Élèves 1. ✅ Permettre le tri sur toutes les colonnes 2. ✅ Afficher toutes les notes (une colonne par évaluation) 3. ✅ Supprimer les indicateurs de performance (badges Excellent/Bon/Moyen/Insuffisant) ### Tableau Domaines/Compétences 1. ✅ Afficher le nombre de fois qu'ils ont été évalués 2. ✅ Afficher le nombre de points attribués (total obtenu/total possible) 3. ✅ Supprimer les moyennes --- ## 🔧 Modifications Backend ### 1. Nouveaux Schemas (`backend/schemas/class_group.py`) #### Schemas ajoutés **AssessmentScore** ```python class AssessmentScore(BaseSchema): """Score d'un élève pour une évaluation.""" assessment_id: int assessment_title: str score: Optional[float] = None # Score brut (ex: 15.5) max_points: float = 0.0 # Points maximum (ex: 20) score_on_20: Optional[float] = None # Score ramené sur 20 ``` **DomainStudentStats** ```python class DomainStudentStats(BaseSchema): """Statistiques d'un élève pour un domaine.""" domain_id: int evaluation_count: int = 0 # Nombre de fois évalué sur ce domaine total_points_obtained: float = 0.0 # Total des points obtenus total_points_possible: float = 0.0 # Total des points possibles ``` **CompetenceStudentStats** ```python class CompetenceStudentStats(BaseSchema): """Statistiques d'un élève pour une compétence.""" competence_id: int evaluation_count: int = 0 total_points_obtained: float = 0.0 total_points_possible: float = 0.0 ``` #### Schemas modifiés **DomainStats** - Avant vs Après ```python # AVANT class DomainStats(BaseSchema): id: int name: str color: str mean: Optional[float] = None # ❌ Supprimé elements_count: int = 0 # APRÈS class DomainStats(BaseSchema): id: int name: str color: str evaluation_count: int = 0 # ✅ Nombre d'évaluations total_points_obtained: float = 0.0 # ✅ Points obtenus total_points_possible: float = 0.0 # ✅ Points possibles ``` **CompetenceStats** - Même structure que DomainStats **StudentAverage** - Enrichi avec 3 nouveaux champs ```python class StudentAverage(BaseSchema): student_id: int first_name: str last_name: str full_name: str average: Optional[float] = None assessment_count: int = 0 # ✅ NOUVEAUX CHAMPS assessment_scores: Dict[int, AssessmentScore] = {} # Scores par évaluation domain_stats: Dict[int, DomainStudentStats] = {} # Stats par domaine competence_stats: Dict[int, CompetenceStudentStats] = {} # Stats par compétence ``` ### 2. Nouveau Service (`backend/domain/services/class_statistics_service.py`) **ClassStatisticsService** - Service de calcul des statistiques de classe #### Méthode 1: `calculate_student_statistics()` **Signature**: ```python async def calculate_student_statistics( students: List[Student], assessments: List[Assessment], grades_by_student_assessment: Dict[Tuple[int, int], List[Tuple[Grade, GradingElement]]], domains: List[Domain], competences: List[Competence], ) -> List[StudentAverage] ``` **Rôle**: Calcule toutes les statistiques pour chaque élève - Score par évaluation (brut + sur 20) - Moyenne pondérée par coefficient - Statistiques par domaine (nombre d'évaluations + points) - Statistiques par compétence (via `element.skill`) **Logique**: 1. Pour chaque élève: - Initialiser les dictionnaires de stats par domaine/compétence - Pour chaque évaluation: - Calculer le score total et max_points - Ramener sur 20 pour la moyenne pondérée - Pour chaque note: - Mettre à jour les stats du domaine associé - Mettre à jour les stats de la compétence associée (via skill) #### Méthode 2: `aggregate_domain_competence_stats()` **Signature**: ```python def aggregate_domain_competence_stats( student_averages: List[StudentAverage], domains: List[Domain], competences: List[Competence], ) -> Tuple[List[DomainStats], List[CompetenceStats]] ``` **Rôle**: Agrège les statistiques de tous les élèves par domaine/compétence **Logique**: 1. Pour chaque domaine: - Sommer evaluation_count de tous les élèves - Sommer total_points_obtained de tous les élèves - Sommer total_points_possible de tous les élèves 2. Même chose pour les compétences ### 3. Endpoint Refactorisé (`backend/api/routes/classes.py`) **GET `/classes/{class_id}/stats?trimester={1|2|3}`** #### Modifications principales **Avant** (ancien code): ```python # Calculer les moyennes de chaque élève calculator = GradingCalculator() student_averages = [] for student in students: # ... calcul simple de la moyenne student_averages.append(StudentAverage( student_id=student.id, average=average, assessment_count=assessment_count )) # Statistiques domaines/compétences simplifiées (vides) domains_stats = [] for domain in domains: domains_stats.append(DomainStats( id=domain.id, name=domain.name, color=domain.color, mean=None, # ❌ Pas calculé elements_count=0 # ❌ Pas calculé )) ``` **Après** (nouveau code): ```python # Récupérer toutes les notes en une passe grades_by_student_assessment = {} for student in students: for assessment in assessments: grades_query = (...) grades_by_student_assessment[(student.id, assessment.id)] = grades_result.all() # Utiliser le service pour calculer les statistiques stats_service = ClassStatisticsService() student_averages = await stats_service.calculate_student_statistics( students=students, assessments=assessments, grades_by_student_assessment=grades_by_student_assessment, domains=domains, competences=competences, ) # Agréger les statistiques domaines/compétences domains_stats, competences_stats = stats_service.aggregate_domain_competence_stats( student_averages=student_averages, domains=domains, competences=competences, ) ``` #### Avantages - ✅ Code modulaire et testable - ✅ Séparation des responsabilités (service vs controller) - ✅ Statistiques complètes calculées automatiquement - ✅ Données enrichies retournées au frontend --- ## 🎨 Modifications Frontend ### 1. Script Vue.js (`frontend/src/views/ClassDashboardView.vue`) #### Variables ajoutées ```javascript const sortColumn = ref('name') // Colonne de tri active const sortDirection = ref('asc') // Direction du tri ``` #### Computed ajoutés **assessments** - Extraction des évaluations ```javascript const assessments = computed(() => { if (!stats.value?.student_averages?.length) return [] const firstStudent = stats.value.student_averages[0] if (!firstStudent?.assessment_scores) return [] // Extraire et trier les évaluations par ID return Object.values(firstStudent.assessment_scores) .sort((a, b) => a.assessment_id - b.assessment_id) }) ``` **sortedStudents** - Tri dynamique des élèves ```javascript const sortedStudents = computed(() => { if (!stats.value?.student_averages) return [] const students = [...stats.value.student_averages] students.sort((a, b) => { let valA, valB if (sortColumn.value === 'name') { valA = `${a.last_name} ${a.first_name}`.toLowerCase() valB = `${b.last_name} ${b.first_name}`.toLowerCase() } else if (sortColumn.value === 'average') { valA = a.average ?? -1 valB = b.average ?? -1 } else if (sortColumn.value.startsWith('assessment_')) { const assessmentId = parseInt(sortColumn.value.split('_')[1]) valA = a.assessment_scores?.[assessmentId]?.score ?? -1 valB = b.assessment_scores?.[assessmentId]?.score ?? -1 } const comparison = valA > valB ? 1 : valA < valB ? -1 : 0 return sortDirection.value === 'asc' ? comparison : -comparison }) return students }) ``` #### Fonctions ajoutées **sortBy(column)** - Gestion du tri ```javascript function sortBy(column) { if (sortColumn.value === column) { // Inverser la direction si même colonne sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc' } else { // Nouvelle colonne : tri ascendant sortColumn.value = column sortDirection.value = 'asc' } } ``` **getAssessmentScore(student, assessmentId)** - Formatage des notes ```javascript function getAssessmentScore(student, assessmentId) { const score = student.assessment_scores?.[assessmentId] if (!score || score.score === null) return '-' return `${score.score.toFixed(1)}/${score.max_points.toFixed(0)}` } ``` **getSortIcon(column)** - Indicateur visuel ```javascript function getSortIcon(column) { if (sortColumn.value !== column) return '' return sortDirection.value === 'asc' ? '▲' : '▼' } ``` #### Fonctions supprimées ```javascript // ❌ Supprimé function getPerformanceClass(average) { ... } function getPerformanceLabel(average) { ... } ``` ### 2. Template - Tableau des Élèves #### Avant ```html
| Élève | Moyenne | Performance |
|---|---|---|
| {{ student.last_name }} {{ student.first_name }} | {{ student.average?.toFixed(2) || '-' }} | {{ getPerformanceLabel(student.average) }} |
| Élève {{ getSortIcon('name') }} | Moyenne {{ getSortIcon('average') }} |
{{ assessment.assessment_title }}
{{ getSortIcon(`assessment_${assessment.assessment_id}`) }}
|
|---|---|---|
| {{ student.last_name }} {{ student.first_name }} | {{ student.average?.toFixed(2) || '-' }} | {{ getAssessmentScore(student, assessment.assessment_id) }} |
{{ domain.elements_count }} éléments évalués
{{ domain.evaluation_count }} évaluations