935 lines
34 KiB
Python
935 lines
34 KiB
Python
from datetime import date
|
||
from django.db import models
|
||
from django.contrib.auth import get_user_model
|
||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||
|
||
from jarvis.core.global_vars import (
|
||
INJURY_MECHANISM_CHOICE,
|
||
INJURY_BODY_SIDE_CHOICE,
|
||
INJURY_TYPE_CHOICE,
|
||
INJURY_LOCATION_CHOICE,
|
||
ROUTINE_TYPE_CHOICE,
|
||
LEARNING_STEP_CHOICES,
|
||
CHRONO_TYPE_CHOICE,
|
||
SCORE_TYPE_CHOICE,
|
||
CATEGORY_CHOICES,
|
||
AGE_CATOGORY_CHOICES,
|
||
NOTE_STATUS_CHOICES,
|
||
)
|
||
|
||
from jarvis.tools.models import Markdownizable, Seasonisable
|
||
from jarvis.people.models import Gymnast, GENDER_CHOICES
|
||
from jarvis.planning.models import Event
|
||
from jarvis.objective.models import Educative, Skill, Routine
|
||
from jarvis.location.models import Club
|
||
|
||
User = get_user_model()
|
||
|
||
|
||
class Chrono(Seasonisable):
|
||
"""
|
||
Représente les chronos (de chandelles ou de série) enregistrés pour un(e) gymnaste.
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Chrono"
|
||
verbose_name_plural = "Chronos"
|
||
ordering = ["date", "gymnast"]
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast,
|
||
verbose_name="gymnast",
|
||
related_name="chronos",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
chrono_type = models.PositiveSmallIntegerField(
|
||
choices=CHRONO_TYPE_CHOICE, verbose_name="Routine type"
|
||
)
|
||
score_type = models.PositiveSmallIntegerField(
|
||
choices=SCORE_TYPE_CHOICE, verbose_name="Score type"
|
||
)
|
||
score = models.DecimalField(max_digits=5, decimal_places=3)
|
||
tof = models.DecimalField(max_digits=5, decimal_places=3, blank=True, null=True)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} - {self.tof} ({self.date} - {self.chrono_type})"
|
||
|
||
def timeline_representation(self):
|
||
return f"<li>{self.date:%d %b %Y} - New personel best {CHRONO_TYPE_CHOICE[self.chrono_type][1]}: {self.score}' ({self.tof}')</li>" # pylint: disable=line-too-long
|
||
|
||
@staticmethod
|
||
def compute_tof(value):
|
||
tof = round((value * 13) / 15, 3) * 1000
|
||
return tof / 1000
|
||
|
||
|
||
class ChronoDetails(models.Model):
|
||
"""
|
||
Class permettant d'enregistrer des détails d'un chrono : le temps de chaque saut.
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Chronos Details"
|
||
verbose_name_plural = "Chronos Details"
|
||
ordering = ["chrono", "order"]
|
||
unique_together = ["chrono", "order"]
|
||
|
||
chrono = models.ForeignKey(Chrono, on_delete=models.CASCADE, related_name="details")
|
||
order = models.SmallIntegerField()
|
||
value = models.DecimalField(max_digits=5, decimal_places=3)
|
||
|
||
|
||
class Injury(Markdownizable, Seasonisable):
|
||
"""
|
||
La classe `Injury` permet d'indiquer qu'un gymnaste a eu un blessure, en liaison avec un
|
||
skill ou non.
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Injury"
|
||
verbose_name_plural = "Injuries"
|
||
# unique_together = ("gymnast", "skill", "date")
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast,
|
||
verbose_name="Gymnast",
|
||
related_name="injuries",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
skill = models.ForeignKey(
|
||
"objective.Skill",
|
||
verbose_name="Skill",
|
||
related_name="injuries",
|
||
on_delete=models.SET_NULL,
|
||
default=None,
|
||
blank=True,
|
||
null=True,
|
||
)
|
||
location = models.SmallIntegerField(
|
||
choices=INJURY_LOCATION_CHOICE, verbose_name="Location"
|
||
)
|
||
injury_type = models.SmallIntegerField(
|
||
choices=INJURY_TYPE_CHOICE, verbose_name="Type"
|
||
)
|
||
body_side = models.PositiveSmallIntegerField(
|
||
choices=INJURY_BODY_SIDE_CHOICE, verbose_name="Body side"
|
||
)
|
||
mechanism = models.PositiveSmallIntegerField(
|
||
choices=INJURY_MECHANISM_CHOICE, verbose_name="Mechanism"
|
||
)
|
||
nb_week_off = models.SmallIntegerField(default=0, verbose_name="# week off")
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} ({self.date})"
|
||
|
||
def timeline_representation(self):
|
||
return f"<li>{self.date:%d %b %Y} - Accident ({self.skill}): {self.nb_week_off} (weeks off)</li>" # pylint: disable=line-too-long
|
||
|
||
|
||
class LearnedSkill(Seasonisable):
|
||
"""
|
||
Représente la capacité d'un gymnaste à savori faire un skill de la ligne d'apprentissage.
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Learned Skill"
|
||
verbose_name_plural = "Learned Skills"
|
||
unique_together = ("gymnast", "skill", "date", "learning_step")
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast,
|
||
verbose_name="gymnast",
|
||
related_name="known_skills",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
skill = models.ForeignKey(
|
||
Skill,
|
||
verbose_name="Skill",
|
||
related_name="known_by",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
learning_step = models.PositiveSmallIntegerField(
|
||
choices=LEARNING_STEP_CHOICES, verbose_name="Can do type"
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} - {self.date} - {self.learning_step} - {self.skill}"
|
||
|
||
def timeline_representation(self):
|
||
return f"<li>{self.date:%d %b %Y} - learning of {self.skill.long_label} ({self.skill.short_label}): {LEARNING_STEP_CHOICES[self.learning_step][1]}</li>" # pylint: disable=line-too-long
|
||
|
||
|
||
class Plan(Seasonisable, Markdownizable):
|
||
"""
|
||
Classe représentant les objectifs qu'un gymnaste devra savoir faire pour une date donnée.
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Plan"
|
||
verbose_name_plural = "Plans"
|
||
ordering = ["date", "educative", "gymnast"]
|
||
unique_together = ("gymnast", "educative")
|
||
|
||
gymnast = models.ForeignKey(
|
||
"people.Gymnast",
|
||
verbose_name="Gymnast",
|
||
related_name="todo",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
educative = models.ForeignKey(
|
||
Educative,
|
||
verbose_name="Educative",
|
||
related_name="plan",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
learning_step = models.PositiveSmallIntegerField(
|
||
choices=LEARNING_STEP_CHOICES, verbose_name="Can do type", default=3
|
||
)
|
||
is_done = models.BooleanField(default=0)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} - {self.educative.short_label} - {self.date}"
|
||
|
||
|
||
class Point(models.Model):
|
||
"""
|
||
Représente les points obtenus lors d'une compétition.
|
||
"""
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast, on_delete=models.CASCADE, default=None, related_name="points"
|
||
)
|
||
event = models.ForeignKey(Event, on_delete=models.CASCADE, default=None)
|
||
routine_type = models.PositiveSmallIntegerField(choices=ROUTINE_TYPE_CHOICE)
|
||
point_execution = models.DecimalField(
|
||
max_digits=5, decimal_places=3, verbose_name="Execution"
|
||
)
|
||
point_difficulty = models.DecimalField(
|
||
max_digits=3, decimal_places=1, verbose_name="Difficulty"
|
||
)
|
||
point_time_of_flight = models.DecimalField(
|
||
max_digits=5, decimal_places=3, verbose_name="ToF"
|
||
)
|
||
point_horizontal_displacement = models.DecimalField(
|
||
max_digits=4, decimal_places=3, verbose_name="HD"
|
||
)
|
||
penality = models.DecimalField(max_digits=3, decimal_places=1)
|
||
total = models.DecimalField(max_digits=6, decimal_places=3)
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created")
|
||
updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated")
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} - {self.total}"
|
||
|
||
|
||
class WellBeing(Markdownizable, Seasonisable):
|
||
"""
|
||
Représente l'état psychologique/physique d'un gymnaste
|
||
"""
|
||
|
||
class Meta:
|
||
ordering = ["date", ]
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast, on_delete=models.CASCADE, default=None, related_name="wellbeings"
|
||
)
|
||
event = models.ForeignKey(
|
||
Event,
|
||
on_delete=models.SET_NULL,
|
||
default=None,
|
||
blank=True,
|
||
null=True,
|
||
related_name="wellbeings",
|
||
)
|
||
mindstate = models.PositiveSmallIntegerField(
|
||
verbose_name="Mindstate",
|
||
validators=[MinValueValidator(1), MaxValueValidator(10)],
|
||
)
|
||
sleep = models.PositiveSmallIntegerField(
|
||
verbose_name="Sleep",
|
||
null=True,
|
||
blank=True,
|
||
default=None,
|
||
validators=[MinValueValidator(1), MaxValueValidator(10)],
|
||
)
|
||
stress = models.PositiveSmallIntegerField(
|
||
verbose_name="Stress",
|
||
validators=[MinValueValidator(1), MaxValueValidator(10)],
|
||
)
|
||
fatigue = models.PositiveSmallIntegerField(
|
||
verbose_name="Fatigue",
|
||
null=True,
|
||
blank=True,
|
||
default=None,
|
||
validators=[MinValueValidator(1), MaxValueValidator(10)],
|
||
)
|
||
muscle_soreness = models.PositiveSmallIntegerField(
|
||
verbose_name="Muscle Soreness",
|
||
null=True,
|
||
blank=True,
|
||
default=None,
|
||
validators=[MinValueValidator(1), MaxValueValidator(10)],
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} - {self.date} : {self.mindstate} | {self.sleep} | {self.stress} | {self.fatigue} | {self.muscle_soreness}" # pylint: disable=line-too-long
|
||
|
||
@property
|
||
def get_inversed_stress(self):
|
||
return 10 - self.stress
|
||
|
||
@property
|
||
def get_inversed_fatigue(self):
|
||
return 10 - self.fatigue
|
||
|
||
@property
|
||
def get_inversed_muscle_soreness(self):
|
||
return 10 - self.muscle_soreness
|
||
|
||
|
||
class GymnastHasRoutine(models.Model):
|
||
"""Représente le lien entre les gymnastes et leurs séries.
|
||
|
||
TODO: il y a un champ "date_begin" et un champ "date_end" --> peut hérité de Temporizable
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Gymnast Has Routine"
|
||
verbose_name_plural = "Gymnast Has Routines"
|
||
# unique_together()
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast,
|
||
verbose_name="Gymnast",
|
||
related_name="has_routine",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
routine = models.ForeignKey(
|
||
Routine,
|
||
verbose_name="Routine",
|
||
related_name="done_by_gymnast",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
routine_type = models.PositiveSmallIntegerField(
|
||
choices=ROUTINE_TYPE_CHOICE, verbose_name="Type", default="1"
|
||
)
|
||
date_begin = models.DateField(default=date.today, verbose_name="Date begin")
|
||
date_end = models.DateField(verbose_name="Date end", null=True, blank=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} - {self.routine_type} : {self.routine}"
|
||
|
||
|
||
class NumberOfRoutineDone(Seasonisable):
|
||
"""
|
||
Classe permettant de suivre le nombre de séries faites par le gymnaste.
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Number of routine done"
|
||
verbose_name_plural = "Number of routines done"
|
||
unique_together = ("gymnast", "date", "routine_type")
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast,
|
||
verbose_name="Gymnast",
|
||
related_name="number_of_routine_done",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
routine = models.ForeignKey(
|
||
Routine,
|
||
verbose_name="Routine",
|
||
related_name="number_of_try",
|
||
on_delete=models.SET_NULL, # TODO: analyser la pertinence de cette option
|
||
null=True, # TODO: analyser la pertinence de cette option
|
||
blank=True, # TODO: analyser la pertinence de cette option
|
||
)
|
||
routine_type = models.PositiveSmallIntegerField(
|
||
choices=ROUTINE_TYPE_CHOICE, verbose_name="Type", default="1"
|
||
)
|
||
number_of_try = models.PositiveSmallIntegerField(
|
||
verbose_name="Number of try", default=0
|
||
)
|
||
number_of_successes = models.PositiveSmallIntegerField(
|
||
verbose_name="number of successes", default=0
|
||
)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} - {self.routine_type} ({self.routine}) : {self.number_of_try} | {self.number_of_successes}" # pylint: disable=line-too-long
|
||
|
||
|
||
class HeightWeight(Seasonisable):
|
||
"""
|
||
Classe permettant de suivre le poids et la taille d'un gymnaste.
|
||
|
||
Voici les autres formules mathématiques les plus connues, valables pour les adultes :
|
||
- La formule de Broca est très simple. Le poids idéal correspond à la différence entre votre taille en cm et 100. Vous mesurez 170 cm et votre poids idéal est de 70 kg.
|
||
- La formule de Lorentz tient compte du sexe. Pour une femme, on calcule taille en cm – 100 – [(taille – 150)/2,5]. On divisera par 4 pour un homme. Si vous mesurez 170 cm, le résultat est 62 kg.
|
||
- La formule de Creff classe la morphologie en trois catégories, très subjectives : normal, large et gracile. Le poids idéal est augmenté de 10 % pour les larges et réduit de 10 % pour les graciles. Pour une silhouette normale, la formule est taille en cm – 100 + (âge/10) X 0,9. Pour une femme d’une quarantaine d’années et de 1,70 cm, on obtient 66,6 kg.
|
||
- La formule de Monnerot-Dumaine prend en compte l’ossature en se basant sur la circonférence du poignet. Le calcul : (Taille en cm – 100) + (4 x Circonférence du poignet en cm) / 2. Pour notre femme de 170 cm avec un poignet de 16 cm, le résultat est 67 kg.
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Height & weight"
|
||
verbose_name_plural = "Heights & weights"
|
||
unique_together = ("gymnast", "date")
|
||
ordering = ["date",]
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast,
|
||
verbose_name="Gymnast",
|
||
related_name="height_weight",
|
||
on_delete=models.CASCADE,
|
||
)
|
||
height = models.DecimalField(max_digits=4, decimal_places=1, verbose_name="Height")
|
||
hips_height = models.DecimalField(
|
||
max_digits=4,
|
||
decimal_places=1,
|
||
verbose_name="Hips height",
|
||
null=True,
|
||
blank=True,
|
||
)
|
||
weight = models.DecimalField(max_digits=4, decimal_places=1, verbose_name="Weight")
|
||
|
||
@property
|
||
def bmi(self):
|
||
height = self.height / 100
|
||
return self.weight / (height * height)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} : {self.height}/{self.hips_height} - {self.weight}"
|
||
|
||
|
||
class Note(Markdownizable, Seasonisable):
|
||
"""
|
||
Notes relatives à un gymnaste
|
||
"""
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast, on_delete=models.CASCADE, related_name="remarks"
|
||
)
|
||
coach = models.ForeignKey(
|
||
User, on_delete=models.SET_NULL, blank=True, null=True, related_name="notes"
|
||
)
|
||
status = models.PositiveSmallIntegerField(
|
||
choices=NOTE_STATUS_CHOICES, verbose_name="Status", default=0
|
||
)
|
||
title = models.CharField(
|
||
default="Note of the week", verbose_name="Title", max_length=255
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} - {self.created_at} : {self.coach}"
|
||
|
||
|
||
class Intensity(Markdownizable, Seasonisable):
|
||
"""Classe représentant l'intensité d'un entraînement
|
||
L'intensité va prendre 9 valeurs :
|
||
- le temps (en minute)
|
||
- le temps théorique (en minute)
|
||
- la difficulté (en 10ème)
|
||
- la difficulté demandé (en 10ème)
|
||
- la quantité de figure
|
||
- la quantité de figure demandée
|
||
- le nombre de passage
|
||
- le nombre de passage demandé
|
||
- le nombre de gymnaste par trampoline
|
||
- qualité de temps (calculé automatiquement)
|
||
- qualité de difficulté (calculé automatiquement)
|
||
- qualité du nombre de figure (calculé automatiquement)
|
||
- qualité du nombre de passage (calculé automatiquement)
|
||
- qualité moyenne (calculé automatiquement)
|
||
|
||
Elle va également calculer 6 statistiques (et/ou leur corollaire) :
|
||
- la difficulté moyenne par minute (d/T)
|
||
- la quantité moyenne de figure par minute (S/T)
|
||
- le temps moyen par passage (T/p)
|
||
- le temps moyen par figure (T/S) (corollaire de la 2ème)
|
||
- la difficulté moyenne par passage (d/p)
|
||
- la difficulté moyenne par figure (d/S)
|
||
- la quantité moyenne de figures par passage (S/p)
|
||
"""
|
||
|
||
class Meta:
|
||
verbose_name = "Intensity"
|
||
verbose_name_plural = "Intensities"
|
||
# unique_together = ("gymnast", "date")
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast, on_delete=models.CASCADE, related_name="intensities"
|
||
)
|
||
time = models.PositiveSmallIntegerField(verbose_name="Time (in minutes)")
|
||
theorical_time = models.PositiveSmallIntegerField(
|
||
verbose_name="Theorical time (in minutes)"
|
||
)
|
||
difficulty = models.PositiveSmallIntegerField(verbose_name="Difficulty (in tenths)")
|
||
difficulty_asked = models.PositiveSmallIntegerField(
|
||
verbose_name="Difficulty asked (in tenths)"
|
||
)
|
||
quantity_of_skill = models.PositiveSmallIntegerField()
|
||
quantity_of_skill_asked = models.PositiveSmallIntegerField()
|
||
number_of_passes = models.PositiveSmallIntegerField()
|
||
number_of_passes_asked = models.PositiveSmallIntegerField()
|
||
number_of_gymnast = models.PositiveSmallIntegerField()
|
||
time_quality = models.DecimalField(max_digits=6, decimal_places=3)
|
||
difficulty_quality = models.DecimalField(max_digits=6, decimal_places=3)
|
||
quantity_of_skill_quality = models.DecimalField(max_digits=6, decimal_places=3)
|
||
number_of_passes_quality = models.DecimalField(max_digits=6, decimal_places=3)
|
||
average_training_quality = models.DecimalField(max_digits=6, decimal_places=3)
|
||
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,
|
||
# 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
|
||
|
||
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)
|
||
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.
|
||
|
||
La qualité de passage représente donc le temps nécessaire pour que tous les gymnastes du
|
||
groupe ait fait chacun un passage.
|
||
"""
|
||
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.average_time_by_passe <= average_passe_time:
|
||
return 1
|
||
|
||
if self.average_time_by_passe <= (average_passe_time * 1.05):
|
||
return 2
|
||
|
||
if self.average_time_by_passe >= (average_passe_time * 1.05):
|
||
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):
|
||
return self.time / self.quantity_of_skill
|
||
|
||
@property
|
||
def average_difficulty_by_passe(self):
|
||
return self.difficulty / self.number_of_passes
|
||
|
||
@property
|
||
def average_difficulty_by_passe_in_unit(self):
|
||
return self.average_difficulty_by_passe / 10
|
||
|
||
@property
|
||
def average_quantity_of_skill_by_time(self):
|
||
return self.quantity_of_skill / self.time
|
||
|
||
@property
|
||
def average_quantity_of_skill_by_passe(self):
|
||
return self.quantity_of_skill / self.number_of_passes
|
||
|
||
@property
|
||
def average_difficulty_by_skill(self):
|
||
return self.difficulty / self.quantity_of_skill
|
||
|
||
@property
|
||
def average_difficulty_by_skill_in_unit(self):
|
||
return self.average_difficulty_by_skill / 10
|
||
|
||
# Human readeable scores
|
||
@property
|
||
def average_time_by_passe_readable(self):
|
||
average_time_in_sec = round((self.average_time_by_passe % 1) * 60)
|
||
if average_time_in_sec < 10:
|
||
average_time_in_sec = "0" + str(average_time_in_sec)
|
||
else:
|
||
average_time_in_sec = str(average_time_in_sec)
|
||
return str(int(self.average_time_by_passe)) + "min" + average_time_in_sec
|
||
|
||
@property
|
||
def difficulty_in_unit(self):
|
||
return self.difficulty / 10
|
||
|
||
@property
|
||
def difficulty_asked_in_unit(self):
|
||
return self.difficulty_asked / 10
|
||
|
||
|
||
class SeasonInformation(models.Model):
|
||
"""Classe représentant l'intensité d'un entraînement"""
|
||
|
||
class Meta:
|
||
verbose_name = "Season Information"
|
||
verbose_name_plural = "Season Informations"
|
||
unique_together = ("gymnast", "season")
|
||
|
||
CATEGORY_CHOICES_ARRAY = list(CATEGORY_CHOICES.items())
|
||
|
||
gymnast = models.ForeignKey(
|
||
Gymnast, on_delete=models.CASCADE, related_name="season_informations"
|
||
)
|
||
season = models.CharField(max_length=9)
|
||
number_of_training_sessions_per_week = models.PositiveSmallIntegerField(
|
||
verbose_name="# Training/w"
|
||
)
|
||
number_of_hours_per_week = models.DecimalField(
|
||
verbose_name="# Hours/w", max_digits=3, decimal_places=1
|
||
)
|
||
number_of_s_and_c_sessions_per_week = models.PositiveSmallIntegerField(
|
||
verbose_name="# S&C training/w",
|
||
blank=True,
|
||
null=True,
|
||
)
|
||
number_of_s_and_c_hours_per_week = models.DecimalField(
|
||
verbose_name="# S&C hours/w",
|
||
blank=True,
|
||
null=True,
|
||
max_digits=3,
|
||
decimal_places=1,
|
||
)
|
||
category = models.PositiveSmallIntegerField(
|
||
choices=CATEGORY_CHOICES_ARRAY,
|
||
verbose_name="Category",
|
||
)
|
||
club = models.ForeignKey(
|
||
Club, null=True, on_delete=models.SET_NULL, related_name="season_informations"
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.gymnast} - {self.season} : {self.number_of_training_sessions_per_week} - {self.number_of_hours_per_week} - {self.category} - {self.club}" # pylint: disable=line-too-long
|
||
|
||
|
||
class CompetitivePointsStats(Markdownizable, Seasonisable):
|
||
"""Class représentant des points de références de compétitions"""
|
||
|
||
TYPE_OF_STAT = (
|
||
(0, "precise"),
|
||
(1, "mean + 4 * standard deviation"),
|
||
(2, "mean + 2 * standard deviation"),
|
||
(3, "mean + standard deviation"),
|
||
(4, "mean + ½ standard deviation"),
|
||
(5, "mean + ¼ standard deviation"),
|
||
(6, "mean"),
|
||
(7, "mean - ¼ standard deviation"),
|
||
(8, "mean - ½ standard deviation"),
|
||
(9, "mean - standard deviation"),
|
||
(10, "mean - 2 * standard deviation"),
|
||
(11, "mean - 4 * standard deviation"),
|
||
)
|
||
|
||
label = models.CharField(max_length=40, null=False, blank=False)
|
||
gender = models.PositiveSmallIntegerField(
|
||
choices=GENDER_CHOICES, verbose_name="Gender"
|
||
)
|
||
age_category = models.PositiveSmallIntegerField(
|
||
choices=AGE_CATOGORY_CHOICES, verbose_name="Age category"
|
||
)
|
||
statistic_type = models.PositiveSmallIntegerField(
|
||
choices=TYPE_OF_STAT, verbose_name="Type of statistic"
|
||
)
|
||
point_execution = models.DecimalField(
|
||
max_digits=5, decimal_places=3, verbose_name="Execution"
|
||
)
|
||
point_difficulty = models.DecimalField(
|
||
max_digits=3, decimal_places=1, verbose_name="Difficulty"
|
||
)
|
||
point_time_of_flight = models.DecimalField(
|
||
max_digits=5, decimal_places=3, verbose_name="ToF"
|
||
)
|
||
point_horizontal_displacement = models.DecimalField(
|
||
max_digits=4, decimal_places=3, verbose_name="HD"
|
||
)
|
||
total = models.DecimalField(max_digits=6, decimal_places=3)
|
||
place = models.PositiveSmallIntegerField(verbose_name="Place")
|
||
event = models.ForeignKey(
|
||
Event, on_delete=models.SET_NULL, default=None, blank=True, null=True
|
||
)
|
||
routine_type = models.PositiveSmallIntegerField(choices=ROUTINE_TYPE_CHOICE)
|
||
informations = models.TextField(
|
||
blank=True,
|
||
null=True,
|
||
help_text="Information about even or statistics (mean, standard deviation, …).",
|
||
)
|
||
|
||
def __str__(self):
|
||
return f"{self.age_category} - {self.gender} - {self.routine_type} : {self.total} ({self.statistic_type})" # pylint: disable=line-too-long
|
||
|
||
|
||
# class Stability(Seasonisable):
|
||
# """Classe représentant la stabilité (Stability)"""
|
||
|
||
# class Meta:
|
||
# verbose_name = "Stability"
|
||
# verbose_name_plural = "Stabilities"
|
||
# unique_together = ["gymnast", "date"]
|
||
|
||
# gymnast = models.ForeignKey(
|
||
# Gymnast, on_delete=models.CASCADE, related_name="stabilitytests"
|
||
# )
|
||
# anterior_chain = models.PositiveSmallIntegerField(verbose_name="Anterior Chain")
|
||
# posterior_chain_left = models.PositiveSmallIntegerField(
|
||
# verbose_name="Posterior Chain Left"
|
||
# )
|
||
# posterior_chain_right = models.PositiveSmallIntegerField(
|
||
# verbose_name="Posterior Chain Right"
|
||
# )
|
||
# leg_lowering = models.PositiveSmallIntegerField(
|
||
# choices=LUMBAR_STABILITY_CHOICE,
|
||
# verbose_name="Leg Lowering: lumbar stability",
|
||
# )
|
||
# sl_bridge = models.ManyToManyField(
|
||
# SLBridge, related_name="stabilitytests", symmetrical=False
|
||
# )
|
||
# side_plank_leg_raise = models.ManyToManyField(
|
||
# SidePlankLegRaise, related_name="stabilitytests", symmetrical=False
|
||
# )
|
||
|
||
|
||
# class QualityOfMovement(Seasonisable):
|
||
# """Classe représentant les tests des qualité de mouvement (Quality of Movement)"""
|
||
|
||
# class Meta:
|
||
# verbose_name = "Quality Of Movement"
|
||
# verbose_name_plural = "Qualities Of Movement"
|
||
# unique_together = ["gymnast", "date"]
|
||
|
||
# gymnast = models.ForeignKey(
|
||
# Gymnast, on_delete=models.CASCADE, related_name="qualities_of_movement"
|
||
# )
|
||
# overhead_squat = models.ManyToManyField(
|
||
# OverheadSquat, related_name="qualities_of_movement", symmetrical=False
|
||
# )
|
||
# single_leg_drop_jump = models.ManyToManyField(
|
||
# DropJump, related_name="qualities_of_movement", symmetrical=False
|
||
# )
|
||
# single_leg_stability_right = models.PositiveSmallIntegerField(
|
||
# verbose_name="Single leg stability right"
|
||
# )
|
||
# single_leg_stability_left = models.PositiveSmallIntegerField(
|
||
# verbose_name="Single leg stability right"
|
||
# )
|
||
|
||
# def __str__(self):
|
||
# return f"{self.gymnast} - {self.date}: {self.overhead_squat}"
|
||
|
||
|
||
# class Strength(Seasonisable):
|
||
# """Classe représentant les tests de force (Strength)"""
|
||
|
||
# class Meta:
|
||
# verbose_name = "Strength"
|
||
# verbose_name_plural = "Strength"
|
||
# unique_together = ["gymnast", "date"]
|
||
|
||
# gymnast = models.ForeignKey(
|
||
# Gymnast, on_delete=models.CASCADE, related_name="strength"
|
||
# )
|
||
# harmstring_left_prone = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# harmstring_right_prone = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# quadriceps_left_seated = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# quadriceps_right_seated = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# hip_adductor_left = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# hip_adductor_right = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# hip_abductor_left = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# hip_abductor_right = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# shoulder_external_rotator_left = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# shoulder_external_rotator_right = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# shoulder_internal_rotator_left = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# shoulder_internal_rotator_right = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
|
||
# def __str__(self):
|
||
# return f"{self.gymnast} - {self.date}"
|
||
|
||
|
||
# class MobilityFlexibility(Seasonisable):
|
||
# """Classe représentant les tests de Mobilité et flexibilité (Mobility and Flexibility)"""
|
||
|
||
# class Meta:
|
||
# verbose_name = "Mobility & Flexibility"
|
||
# verbose_name_plural = "Mobility & Flexibility"
|
||
# unique_together = ["gymnast", "date"]
|
||
|
||
# gymnast = models.ForeignKey(
|
||
# Gymnast, on_delete=models.CASCADE, related_name="mobility_fexibility"
|
||
# )
|
||
|
||
# hip_internal_rotation_right = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# hip_internal_rotation_left = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# harmstring_aket_right = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# harmstring_aket_left = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# elys_test_right = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# elys_test_left = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# bent_knee_fall_out_test_right = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# bent_knee_fall_out_test_left = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# knee_to_wall_test_right = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# knee_to_wall_test_left = models.PositiveSmallIntegerField(null=True, blank=True)
|
||
# shoulder_anteversion_supine_right = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# shoulder_anteversion_supine_left = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# shoulder_external_rotation_right = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# shoulder_external_rotation_left = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# shoulder_internal_rotation_right = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# shoulder_internal_rotation_left = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# functional_external_rotation_right = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
# functional_external_rotation_left = models.PositiveSmallIntegerField(
|
||
# null=True, blank=True
|
||
# )
|
||
|
||
# mod_thomas_test_right = models.PositiveSmallIntegerField(
|
||
# choices=MOD_THOMAS_TEST_CHOICES, default=0
|
||
# )
|
||
# mod_thomas_test_left = models.PositiveSmallIntegerField(
|
||
# choices=MOD_THOMAS_TEST_CHOICES, default=0
|
||
# )
|
||
# lombo_pelvic_control = models.PositiveSmallIntegerField(
|
||
# choices=LOMBO_PELVIC_AND_CERVICAL_CONTROL_CHOICES, default=0
|
||
# )
|
||
# cervical_control = models.PositiveSmallIntegerField(
|
||
# choices=LOMBO_PELVIC_AND_CERVICAL_CONTROL_CHOICES, default=0
|
||
# )
|
||
|
||
# def __str__(self):
|
||
# return f"{self.gymnast} - {self.date}"
|