feat: add temporal student gestion
This commit is contained in:
@@ -51,9 +51,14 @@ class StudentEvaluationService:
|
||||
|
||||
def calculate_student_trimester_average(self, student_id: int, trimester: int) -> Optional[float]:
|
||||
"""Calcule la moyenne d'un élève pour un trimestre donné."""
|
||||
# Récupérer la classe actuelle de l'élève avec la logique temporelle
|
||||
student = Student.query.get(student_id)
|
||||
current_class = student.get_current_class()
|
||||
if not current_class:
|
||||
return None
|
||||
|
||||
assessments = self.assessment_repo.find_completed_by_class_trimester(
|
||||
# On récupère d'abord la classe de l'élève
|
||||
Student.query.get(student_id).class_group_id,
|
||||
current_class.id,
|
||||
trimester
|
||||
)
|
||||
|
||||
@@ -74,10 +79,20 @@ class StudentEvaluationService:
|
||||
def get_student_trimester_summary(self, student_id: int, trimester: int) -> StudentTrimesterSummary:
|
||||
"""Génère le résumé d'un élève pour un trimestre."""
|
||||
student = Student.query.get(student_id)
|
||||
current_class = student.get_current_class()
|
||||
if not current_class:
|
||||
return StudentTrimesterSummary(
|
||||
student=student,
|
||||
overall_average=None,
|
||||
assessment_count=0,
|
||||
grades_by_assessment={},
|
||||
appreciation=None,
|
||||
performance_status='no_data'
|
||||
)
|
||||
|
||||
# Récupérer les évaluations du trimestre
|
||||
assessments = self.assessment_repo.find_by_class_trimester_with_details(
|
||||
student.class_group_id, trimester
|
||||
current_class.id, trimester
|
||||
)
|
||||
|
||||
# Calculer les scores par évaluation
|
||||
@@ -96,7 +111,7 @@ class StudentEvaluationService:
|
||||
# Récupérer l'appréciation existante
|
||||
appreciation_repo = AppreciationRepository()
|
||||
appreciation = appreciation_repo.find_by_student_trimester(
|
||||
student_id, student.class_group_id, trimester
|
||||
student_id, current_class.id, trimester
|
||||
)
|
||||
|
||||
# Calculer les données de compétences et domaines
|
||||
@@ -121,9 +136,10 @@ class StudentEvaluationService:
|
||||
)
|
||||
|
||||
def get_students_summaries(self, class_group_id: int, trimester: int) -> List[StudentTrimesterSummary]:
|
||||
"""Génère les résumés de tous les élèves d'une classe pour un trimestre."""
|
||||
student_repo = StudentRepository()
|
||||
students = student_repo.find_by_class_group(class_group_id)
|
||||
"""Génère les résumés de tous les élèves actuellement dans une classe pour un trimestre."""
|
||||
from repositories.temporal_student_repository import TemporalStudentRepository
|
||||
temporal_repo = TemporalStudentRepository()
|
||||
students = temporal_repo.find_current_students_in_class(class_group_id)
|
||||
|
||||
summaries = []
|
||||
for student in students:
|
||||
@@ -148,9 +164,13 @@ class StudentEvaluationService:
|
||||
if not student:
|
||||
return {'competences': [], 'domains': []}
|
||||
|
||||
# Récupérer toutes les évaluations du trimestre
|
||||
# Récupérer la classe actuelle et toutes les évaluations du trimestre
|
||||
current_class = student.get_current_class()
|
||||
if not current_class:
|
||||
return {'competences': [], 'domains': []}
|
||||
|
||||
assessments = self.assessment_repo.find_by_class_trimester_with_details(
|
||||
student.class_group_id, trimester
|
||||
current_class.id, trimester
|
||||
)
|
||||
|
||||
# Structures pour accumuler les données
|
||||
@@ -375,8 +395,18 @@ class StudentEvaluationService:
|
||||
global_details = self.grade_repo.get_special_values_details_by_student_trimester(student_id, trimester)
|
||||
|
||||
# 2. Comptes par évaluation
|
||||
student = Student.query.get(student_id)
|
||||
current_class = student.get_current_class()
|
||||
if not current_class:
|
||||
return {
|
||||
'global': {},
|
||||
'by_assessment': {},
|
||||
'total_special_values': 0,
|
||||
'has_special_values': False
|
||||
}
|
||||
|
||||
assessments = self.assessment_repo.find_by_class_trimester_with_details(
|
||||
Student.query.get(student_id).class_group_id, trimester
|
||||
current_class.id, trimester
|
||||
)
|
||||
|
||||
by_assessment = {}
|
||||
|
||||
207
services/temporal_assessment_services.py
Normal file
207
services/temporal_assessment_services.py
Normal file
@@ -0,0 +1,207 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Tuple
|
||||
from collections import defaultdict
|
||||
from models import db, Grade, GradingElement, Exercise
|
||||
from repositories.temporal_student_repository import TemporalStudentRepository
|
||||
from services.assessment_services import AssessmentProgressService, StudentScoreCalculator, ProgressResult, StudentScore
|
||||
from providers.concrete_providers import SQLAlchemyDatabaseProvider
|
||||
from sqlalchemy import and_, func
|
||||
|
||||
|
||||
@dataclass
|
||||
class TemporalProgressResult(ProgressResult):
|
||||
"""Extension avec information temporelle."""
|
||||
eligible_students_count: int
|
||||
total_students_in_class: int
|
||||
|
||||
|
||||
class TemporalAssessmentProgressService(AssessmentProgressService):
|
||||
"""
|
||||
Service de progression avec logique temporelle.
|
||||
Hérite du service existant et ajoute la logique temporelle.
|
||||
"""
|
||||
|
||||
def __init__(self, db_provider: SQLAlchemyDatabaseProvider, student_repo: TemporalStudentRepository):
|
||||
super().__init__(db_provider)
|
||||
self.student_repo = student_repo
|
||||
|
||||
def calculate_grading_progress(self, assessment) -> TemporalProgressResult:
|
||||
"""
|
||||
Calcule la progression en ne comptant que les élèves éligibles.
|
||||
"""
|
||||
# Étudiants éligibles à la date de l'évaluation
|
||||
eligible_students = self.student_repo.find_eligible_for_assessment(assessment)
|
||||
current_students = self.student_repo.find_current_students_in_class(assessment.class_group_id)
|
||||
|
||||
if not eligible_students:
|
||||
return TemporalProgressResult(
|
||||
percentage=0,
|
||||
completed=0,
|
||||
total=0,
|
||||
status='no_eligible_students',
|
||||
students_count=0,
|
||||
eligible_students_count=0,
|
||||
total_students_in_class=len(current_students)
|
||||
)
|
||||
|
||||
# Calcul basé uniquement sur les élèves éligibles
|
||||
eligible_student_ids = [s.id for s in eligible_students]
|
||||
|
||||
# Requête optimisée pour compter les notes des élèves éligibles uniquement
|
||||
grading_elements_data = self._get_grading_elements_for_eligible_students(
|
||||
assessment.id, eligible_student_ids
|
||||
)
|
||||
|
||||
total_elements = 0
|
||||
completed_elements = 0
|
||||
|
||||
for element_data in grading_elements_data:
|
||||
total_elements += len(eligible_students)
|
||||
completed_elements += element_data.completed_grades_count
|
||||
|
||||
if total_elements == 0:
|
||||
return TemporalProgressResult(
|
||||
percentage=0,
|
||||
completed=0,
|
||||
total=0,
|
||||
status='no_elements',
|
||||
students_count=len(eligible_students),
|
||||
eligible_students_count=len(eligible_students),
|
||||
total_students_in_class=len(current_students)
|
||||
)
|
||||
|
||||
percentage = round((completed_elements / total_elements) * 100)
|
||||
status = self._determine_status(percentage)
|
||||
|
||||
return TemporalProgressResult(
|
||||
percentage=percentage,
|
||||
completed=completed_elements,
|
||||
total=total_elements,
|
||||
status=status,
|
||||
students_count=len(eligible_students),
|
||||
eligible_students_count=len(eligible_students),
|
||||
total_students_in_class=len(current_students)
|
||||
)
|
||||
|
||||
def _get_grading_elements_for_eligible_students(self, assessment_id: int, eligible_student_ids: List[int]):
|
||||
"""Requête optimisée pour récupérer les notes des élèves éligibles."""
|
||||
|
||||
return db.session.query(
|
||||
GradingElement.id,
|
||||
func.count(Grade.id).label('completed_grades_count')
|
||||
).select_from(GradingElement)\
|
||||
.join(Exercise, GradingElement.exercise_id == Exercise.id)\
|
||||
.outerjoin(
|
||||
Grade,
|
||||
and_(
|
||||
Grade.grading_element_id == GradingElement.id,
|
||||
Grade.student_id.in_(eligible_student_ids),
|
||||
Grade.value.isnot(None),
|
||||
Grade.value != ''
|
||||
)
|
||||
)\
|
||||
.filter(Exercise.assessment_id == assessment_id)\
|
||||
.group_by(GradingElement.id)\
|
||||
.all()
|
||||
|
||||
|
||||
class TemporalStudentScoreCalculator(StudentScoreCalculator):
|
||||
"""Calculateur de scores avec logique temporelle."""
|
||||
|
||||
def __init__(self,
|
||||
grading_calculator,
|
||||
db_provider: SQLAlchemyDatabaseProvider,
|
||||
student_repo: TemporalStudentRepository):
|
||||
super().__init__(grading_calculator, db_provider)
|
||||
self.student_repo = student_repo
|
||||
|
||||
def calculate_student_scores(self, assessment) -> Tuple[Dict[int, StudentScore], Dict[int, Dict[int, float]]]:
|
||||
"""
|
||||
Calcule les scores uniquement pour les élèves éligibles.
|
||||
"""
|
||||
# Récupérer uniquement les élèves éligibles
|
||||
eligible_students = self.student_repo.find_eligible_for_assessment(assessment)
|
||||
|
||||
if not eligible_students:
|
||||
return {}, {}
|
||||
|
||||
# Requête optimisée pour les notes des élèves éligibles
|
||||
eligible_student_ids = [s.id for s in eligible_students]
|
||||
grades_data = self._get_grades_for_eligible_students(assessment.id, eligible_student_ids)
|
||||
|
||||
students_scores = {}
|
||||
exercise_scores = defaultdict(lambda: defaultdict(float))
|
||||
|
||||
# Calcul pour chaque élève éligible
|
||||
for student in eligible_students:
|
||||
student_score = self._calculate_single_student_score(
|
||||
student, assessment, grades_data
|
||||
)
|
||||
students_scores[student.id] = student_score
|
||||
|
||||
for exercise_id, exercise_data in student_score.exercises.items():
|
||||
exercise_scores[exercise_id][student.id] = exercise_data['score']
|
||||
|
||||
return students_scores, dict(exercise_scores)
|
||||
|
||||
def _get_grades_for_eligible_students(self, assessment_id: int, eligible_student_ids: List[int]):
|
||||
"""Requête optimisée pour les notes des élèves éligibles."""
|
||||
|
||||
grades = db.session.query(Grade)\
|
||||
.join(GradingElement, Grade.grading_element_id == GradingElement.id)\
|
||||
.join(Exercise, GradingElement.exercise_id == Exercise.id)\
|
||||
.filter(
|
||||
Exercise.assessment_id == assessment_id,
|
||||
Grade.student_id.in_(eligible_student_ids)
|
||||
).all()
|
||||
|
||||
# Convertir en format dict compatible avec le service parent
|
||||
return [
|
||||
{
|
||||
'student_id': grade.student_id,
|
||||
'grading_element_id': grade.grading_element_id,
|
||||
'value': grade.value,
|
||||
'grading_type': grade.grading_element.grading_type,
|
||||
'max_points': grade.grading_element.max_points
|
||||
}
|
||||
for grade in grades
|
||||
]
|
||||
|
||||
|
||||
class TemporalClassStatisticsService:
|
||||
"""Service pour calculer les statistiques de classe avec logique temporelle."""
|
||||
|
||||
def __init__(self, student_repo: TemporalStudentRepository):
|
||||
self.student_repo = student_repo
|
||||
|
||||
def get_class_enrollment_summary(self, class_group_id: int, trimester: int = None):
|
||||
"""Résumé des inscriptions pour une classe."""
|
||||
current_students = self.student_repo.find_current_students_in_class(class_group_id)
|
||||
|
||||
# Calcul des mouvements si on a un trimestre spécifique
|
||||
movements = {}
|
||||
if trimester:
|
||||
from datetime import date
|
||||
# Approximation des dates de trimestre
|
||||
trimester_dates = {
|
||||
1: (date(2024, 9, 1), date(2024, 12, 31)),
|
||||
2: (date(2025, 1, 1), date(2025, 3, 31)),
|
||||
3: (date(2025, 4, 1), date(2025, 6, 30))
|
||||
}
|
||||
|
||||
if trimester in trimester_dates:
|
||||
start_date, end_date = trimester_dates[trimester]
|
||||
students_with_movements = self.student_repo.find_students_with_movements_in_period(
|
||||
start_date, end_date
|
||||
)
|
||||
movements = {
|
||||
'arrivals': [],
|
||||
'departures': [],
|
||||
'total_movements': len(students_with_movements)
|
||||
}
|
||||
|
||||
return {
|
||||
'current_count': len(current_students),
|
||||
'current_students': current_students,
|
||||
'movements': movements
|
||||
}
|
||||
70
services/temporal_facade.py
Normal file
70
services/temporal_facade.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from typing import List
|
||||
from models import Student
|
||||
from repositories.temporal_student_repository import TemporalStudentRepository
|
||||
from services.temporal_assessment_services import (
|
||||
TemporalAssessmentProgressService,
|
||||
TemporalStudentScoreCalculator,
|
||||
TemporalClassStatisticsService,
|
||||
TemporalProgressResult
|
||||
)
|
||||
from services.assessment_services import AssessmentStatisticsService, UnifiedGradingCalculator
|
||||
from providers.concrete_providers import ConfigManagerProvider, SQLAlchemyDatabaseProvider
|
||||
|
||||
|
||||
class TemporalAssessmentServicesFacade:
|
||||
"""
|
||||
Facade temporelle qui remplace la facade standard.
|
||||
Intègre toute la logique temporelle de manière transparente.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
config_provider = None,
|
||||
db_provider = None):
|
||||
# Injection des repositories temporels
|
||||
self.student_repo = TemporalStudentRepository()
|
||||
|
||||
# Providers par défaut si non fournis
|
||||
if config_provider is None:
|
||||
config_provider = ConfigManagerProvider()
|
||||
if db_provider is None:
|
||||
db_provider = SQLAlchemyDatabaseProvider()
|
||||
|
||||
# Services temporels
|
||||
self.grading_calculator = UnifiedGradingCalculator(config_provider)
|
||||
self.progress_service = TemporalAssessmentProgressService(db_provider, self.student_repo)
|
||||
self.score_calculator = TemporalStudentScoreCalculator(
|
||||
self.grading_calculator, db_provider, self.student_repo
|
||||
)
|
||||
self.statistics_service = AssessmentStatisticsService(self.score_calculator)
|
||||
self.class_statistics_service = TemporalClassStatisticsService(self.student_repo)
|
||||
|
||||
def get_eligible_students(self, assessment) -> List[Student]:
|
||||
"""Nouvelle méthode pour récupérer les élèves éligibles."""
|
||||
return self.student_repo.find_eligible_for_assessment(assessment)
|
||||
|
||||
def get_grading_progress(self, assessment) -> TemporalProgressResult:
|
||||
"""Progression avec logique temporelle."""
|
||||
return self.progress_service.calculate_grading_progress(assessment)
|
||||
|
||||
def get_class_enrollment_summary(self, class_group_id: int, trimester: int = None):
|
||||
"""Résumé des inscriptions pour une classe."""
|
||||
return self.class_statistics_service.get_class_enrollment_summary(class_group_id, trimester)
|
||||
|
||||
# Compatibilité avec l'interface existante
|
||||
def get_assessment_statistics(self, assessment):
|
||||
"""Statistiques d'évaluation (héritée du service existant)."""
|
||||
return self.statistics_service.get_assessment_statistics(assessment)
|
||||
|
||||
|
||||
class TemporalAssessmentServicesFactory:
|
||||
"""Factory pour créer les services temporels."""
|
||||
|
||||
@staticmethod
|
||||
def create_temporal_facade(config_provider = None) -> TemporalAssessmentServicesFacade:
|
||||
"""Crée une facade temporelle."""
|
||||
return TemporalAssessmentServicesFacade(config_provider)
|
||||
|
||||
@staticmethod
|
||||
def create_temporal_student_repository() -> TemporalStudentRepository:
|
||||
"""Crée un repository d'élèves temporel."""
|
||||
return TemporalStudentRepository()
|
||||
Reference in New Issue
Block a user