diff --git a/jarvis/followup/models.py b/jarvis/followup/models.py index a355c3c..3f6abcb 100644 --- a/jarvis/followup/models.py +++ b/jarvis/followup/models.py @@ -519,136 +519,74 @@ class Intensity(Markdownizable, Seasonisable): average_time_by_passe = models.DecimalField(max_digits=4, decimal_places=3) def compute_average_training_quality(self): - """ - Calcul de la qualité d'un entrainement sur base des 4 données pratiques encodées : Temps, + """Calcul de la qualité d'un entrainement sur base des 4 données pratiques encodées : Temps, # de passage, # de saut et Difficulté. Si les 4 données pratiques sont inférieures ou égales aux données théoriques, une moyenne pondérée (D*4, p*3, S*2 et T) est calculée. Si une (ou plusieurs) données pratiques sont supérieures aux données théorique... ? - Pour les cas non traités, une moyenne arithmétique est calculée. - - TODO: - - trouver un calcul d'efficacité qui tienne compte des statistiques (notamment le temps par - passage) """ - # Si les 4 données pratiques sont inférieures ou égales aux données théoriques, une moyenne - # pondérée (D*4, p*3, S*2 et T) est calculée. - if ( - self.time <= self.theorical_time - and self.number_of_passes <= self.number_of_passes_asked - and self.difficulty <= self.difficulty_asked - and self.quantity_of_skill <= self.quantity_of_skill_asked - ): - return ( - self.time_quality - + (self.quantity_of_skill_quality * 2) - + (self.number_of_passes_quality * 3) - + (self.difficulty_quality * 4) - ) / 10 - - # if self.difficulty > self.difficulty_asked: - # if ( - # self.time <= self.theorical_time - # and self.number_of_passes <= self.number_of_passes_asked - # and self.quantity_of_skill <= self.quantity_of_skill_asked - # ): - # return self.difficulty_quality - - if ( - self.time <= self.theorical_time - and self.number_of_passes <= self.number_of_passes_asked - ): - if ( - self.difficulty >= self.difficulty_asked - and self.quantity_of_skill >= self.quantity_of_skill_asked - ): - return ( - (self.difficulty_quality * 2) + self.quantity_of_skill_quality - ) / 3 - - # Pour les cas non traités, une moyenne arithmétique est calculée. return ( self.time_quality - + self.difficulty_quality - + self.quantity_of_skill_quality - + self.number_of_passes_quality - ) / 4 + + (self.quantity_of_skill_quality * 2) + + (self.number_of_passes_quality * 3) + + (self.difficulty_quality * 4) + ) / 10 def save(self, *args, **kwargs): - """Calcule les informations de qualité de l'intensité de entraînement et sauve les informations.""" - # self.average_time_by_skill = self.time / self.quantity_of_skill - self.time_quality = round((self.time / self.theorical_time) * 100, 3) - self.difficulty_quality = round( - (self.difficulty / self.difficulty_asked) * 100, 3 - ) - self.quantity_of_skill_quality = round( - (self.quantity_of_skill / self.quantity_of_skill_asked) * 100, 3 - ) - self.number_of_passes_quality = round( - (self.number_of_passes / self.number_of_passes_asked) * 100, 3 - ) - self.average_training_quality = round( - self.compute_average_training_quality(), 3 - ) - self.average_time_by_passe = round(self.time / self.number_of_passes, 3) + """Calculate quality metrics for training intensity and save the information.""" + + def calculate_quality(actual, expected): + """Helper function to calculate quality percentage.""" + if expected > 0: + return round((actual / expected) * 100, 3) + return 0 # Return 0 or some other appropriate value if expected is zero + + # Calculate quality metrics + self.time_quality = calculate_quality(self.time, self.theorical_time) + self.difficulty_quality = calculate_quality(self.difficulty, self.difficulty_asked) + self.quantity_of_skill_quality = calculate_quality(self.quantity_of_skill, self.quantity_of_skill_asked) + self.number_of_passes_quality = calculate_quality(self.number_of_passes, self.number_of_passes_asked) + + # Calculate average training quality and time per pass + self.average_training_quality = round(self.compute_average_training_quality(), 3) + if self.number_of_passes > 0: + self.average_time_by_passe = round(self.time / self.number_of_passes, 3) + else: + self.average_time_by_passe = 0 # Handle zero passes appropriately even if it should not append. + super().save(*args, **kwargs) def __str__(self): return f"{self.gymnast} - {self.date} : {self.time_quality} - {self.difficulty_quality} - {self.quantity_of_skill_quality} - {self.number_of_passes_quality} - {self.average_training_quality} - {self.average_time_by_passe}" # pylint: disable=line-too-long - # @property def passes_quality_for_gymnast(self): - """Calcule la qualité de passage pour un entraînement. On calcule le temps pour un gymnaste - en additionnant le passage théorique optimale d'un passage (90 secondes) et un temps de - fonctionnement (pour monter, descendre, communiquer, …) équivalent racine cubique du nombre - de gymnaste) ; le tout multiplié par le nombre de gymnaste du groupe. Le tout calculé en - seconde puis ramener en minute. + """Calculate the quality of passes for a training session. This calculates the time for a gymnast + by adding the optimal theoretical time for a pass (90 seconds) and a operational time (for getting on and off, communicating, etc.) (calculated as the cube root of the number of gymnasts), multiplied by the number of gymnasts in the group, and then converted from seconds to minutes. - La qualité de passage représente donc le temps nécessaire pour que tous les gymnastes du - groupe ait fait chacun un passage. + The quality of passes represents the time needed for all gymnasts in the group to each make a pass. """ - optimal_time_by_gymnast = 90 - average_passe_time = ( - (optimal_time_by_gymnast + pow(100, 1 / self.number_of_gymnast)) - * self.number_of_gymnast - ) / 60 + if self.number_of_gymnast == 0: + return 0 # Handle case where there are no gymnasts to avoid division by zero + optimal_time_by_gymnast = 90 + operational_time = pow(100, 1 / self.number_of_gymnast) + total_time_per_gymnast = optimal_time_by_gymnast + operational_time + total_group_time_seconds = total_time_per_gymnast * self.number_of_gymnast + average_passe_time = total_group_time_seconds / 60 + + # Calculate the threshold for a 5% increase + threshold_time = average_passe_time * 1.05 + + # Determine the quality based on the average time per pass if self.average_time_by_passe <= average_passe_time: return 1 - - if self.average_time_by_passe <= (average_passe_time * 1.05): + elif self.average_time_by_passe <= threshold_time: return 2 - - if self.average_time_by_passe >= (average_passe_time * 1.05): + else: return 3 - # Theorical statistics - # @property - # def average_time_by_passe_theorical(self): - # return self.theorical_time / self.number_of_passes_asked - - # @property - # def average_quantity_of_skill_by_time_theorical(self): - # return self.quantity_of_skill_asked / self.theorical_time - - # @property - # def average_time_by_skill_theorical(self): - # return self.theorical_time / self.quantity_of_skill_asked - - # @property - # def average_difficulty_by_passe_theorical(self): - # return self.difficulty_asked / self.number_of_passes_asked - - # @property - # def average_quantity_of_skill_by_passe_theorical(self): - # return self.quantity_of_skill_asked / self.number_of_passes_asked - - # @property - # def average_difficulty_by_skill_theorical(self): - # return self.difficulty_asked / self.quantity_of_skill_asked - # Real statistics @property def average_time_by_skill(self): diff --git a/jarvis/followup/tests_models.py b/jarvis/followup/tests_models.py index 8ed1f51..de84188 100644 --- a/jarvis/followup/tests_models.py +++ b/jarvis/followup/tests_models.py @@ -17,6 +17,7 @@ from jarvis.followup.models import ( SeasonInformation, CompetitivePointsStats, ) +from datetime import date from jarvis.followup.models import ( CHRONO_TYPE_CHOICE, @@ -47,16 +48,16 @@ class TestModels(TestCase): expected_str = f"{gymnast} - 13.000 ({today} - 0)" self.assertEqual(str(chrono), expected_str, "The __str__ method does not return the expected string.") - # def test_chrono_timeline_representation(self): - # gymnast = Gymnast.objects.get(last_name="Pauchou") - # chrono = Chrono.objects.get(gymnast=gymnast) - # today = pendulum.now().date() - # self.assertEqual( - # chrono.timeline_representation, - # f"