feat(class): improve class/id

This commit is contained in:
2025-12-03 06:20:17 +01:00
parent 5b87f24b5b
commit ab86bbb2e1
7 changed files with 1526 additions and 126 deletions

View File

@@ -35,8 +35,12 @@ from schemas.class_group import (
HistogramBin,
DomainStats,
CompetenceStats,
AssessmentScore,
DomainStudentStats,
CompetenceStudentStats,
)
from domain.services.grading_calculator import GradingCalculator
from domain.services.class_statistics_service import ClassStatisticsService
from schemas.student import StudentWithClass, StudentList
from schemas.csv_import import (
CSVImportResponse,
@@ -221,10 +225,10 @@ async def get_class_stats(
Récupère les statistiques complètes d'une classe pour un trimestre.
Inclut:
- Moyennes par élève
- Moyennes par élève avec détail par évaluation
- Statistiques globales (moyenne, médiane, écart-type)
- Histogramme des moyennes
- Analyse par domaines et compétences
- Analyse par domaines et compétences (nombre d'évaluations + points)
"""
# Vérifier que la classe existe
class_query = select(ClassGroup).where(ClassGroup.id == class_id)
@@ -247,26 +251,34 @@ async def get_class_stats(
students_result = await session.execute(students_query)
students = students_result.scalars().all()
# Récupérer les évaluations du trimestre
assessments_query = select(Assessment).where(
Assessment.class_group_id == class_id,
Assessment.trimester == trimester
# Récupérer les évaluations du trimestre avec leurs relations
assessments_query = (
select(Assessment)
.options(
selectinload(Assessment.exercises).selectinload(Exercise.grading_elements)
)
.where(
Assessment.class_group_id == class_id,
Assessment.trimester == trimester
)
.order_by(Assessment.date)
)
assessments_result = await session.execute(assessments_query)
assessments = assessments_result.scalars().all()
# Calculer les moyennes de chaque élève
calculator = GradingCalculator()
student_averages = []
all_averages = []
# Récupérer les domaines et compétences
domains_query = select(Domain).order_by(Domain.name)
domains_result = await session.execute(domains_query)
domains = domains_result.scalars().all()
competences_query = select(Competence).order_by(Competence.order_index)
competences_result = await session.execute(competences_query)
competences = competences_result.scalars().all()
# Récupérer toutes les notes en une seule requête pour optimiser
grades_by_student_assessment = {}
for student in students:
weighted_sum = 0.0
total_coefficient = 0.0
assessment_count = 0
for assessment in assessments:
# Récupérer les notes de l'élève pour cette évaluation
grades_query = (
select(Grade, GradingElement)
.join(GradingElement, Grade.grading_element_id == GradingElement.id)
@@ -277,45 +289,30 @@ async def get_class_stats(
)
)
grades_result = await session.execute(grades_query)
grades_data = grades_result.all()
grades_by_student_assessment[(student.id, assessment.id)] = grades_result.all()
if grades_data:
total_score = 0.0
total_max_points = 0.0
# 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,
)
for grade, element in grades_data:
if grade.value:
score = calculator.calculate_score(
grade.value, element.grading_type, element.max_points
)
if score is not None and calculator.is_counted_in_total(grade.value):
total_score += score
total_max_points += element.max_points
if total_max_points > 0:
# Ramener sur 20
score_on_20 = total_score / total_max_points * 20
weighted_sum += score_on_20 * assessment.coefficient
total_coefficient += assessment.coefficient
assessment_count += 1
# Calculer la moyenne pondérée
average = None
if total_coefficient > 0:
average = round(weighted_sum / total_coefficient, 2)
all_averages.append(average)
student_averages.append(StudentAverage(
student_id=student.id,
first_name=student.first_name,
last_name=student.last_name,
full_name=f"{student.first_name} {student.last_name}",
average=average,
assessment_count=assessment_count
))
# Calculer les statistiques domaines/compétences depuis les éléments de notation
# Perspective enseignant : ce qui a été évalué, pas les résultats des élèves
domains_stats, competences_stats = stats_service.calculate_domain_competence_from_elements(
assessments=assessments,
domains=domains,
competences=competences,
)
# Calculer les statistiques globales
all_averages = [s.average for s in student_averages if s.average is not None]
mean = median = std_dev = min_score = max_score = None
if all_averages:
mean = round(sum(all_averages) / len(all_averages), 2)
sorted_averages = sorted(all_averages)
@@ -345,9 +342,10 @@ async def get_class_stats(
count=count
))
# Ajouter le dernier bin pour 20
count_20 = sum(1 for avg in all_averages if avg == 20)
if count_20 > 0:
histogram[-1].count += count_20
if histogram:
count_20 = sum(1 for avg in all_averages if avg == 20)
if count_20 > 0:
histogram[-1].count += count_20
# Compter les évaluations par statut
assessments_completed = 0
@@ -379,36 +377,6 @@ async def get_class_stats(
elif grades_count > 0:
assessments_in_progress += 1
# Statistiques par domaine et compétence (simplifié)
domains_stats = []
competences_stats = []
# Récupérer les domaines
domains_query = select(Domain).order_by(Domain.name)
domains_result = await session.execute(domains_query)
domains = domains_result.scalars().all()
for domain in domains:
domains_stats.append(DomainStats(
id=domain.id,
name=domain.name,
color=domain.color,
mean=None,
elements_count=0
))
# Récupérer les compétences
competences_query = select(Competence).order_by(Competence.order_index)
competences_result = await session.execute(competences_query)
competences = competences_result.scalars().all()
for competence in competences:
competences_stats.append(CompetenceStats(
id=competence.id,
name=competence.name,
color=competence.color,
mean=None,
elements_count=0
))
return ClassDashboardStats(
class_id=class_id,
class_name=cls.name,