diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index 57faa89bf..d2832ba66 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -372,12 +372,38 @@ def str_to_time(time_str: str) -> time: def get_assiduites_stats( assiduites: Query, metric: str = "all", filtered: dict[str, object] = None ) -> dict[str, int | float]: - """Compte les assiduités en fonction des filtres""" - # XXX TODO-assiduite : documenter !!! - # Que sont les filtres ? Quelles valeurs ? - # documenter permet de faire moins de bug: qualité du code non satisfaisante. - # - # + on se perd entre les clés en majuscules et en minuscules. Pourquoi + """ + Calcule les statistiques sur les assiduités + (nombre de jours, demi-journées et heures passées, + non justifiées, justifiées et total) + + Les filtres : + - etat : filtre les assiduités par leur état + valeur : (absent, present, retard) + - date_debut/date_fin : prend les assiduités qui se trouvent entre les dates + valeur : datetime.datetime + - moduleimpl_id : filtre les assiduités en fonction du moduleimpl_id + valeur : int | None + - formsemestre : prend les assiduités du formsemestre donné + valeur : FormSemestre + - formsemestre_modimpls : prend les assiduités avec un moduleimpl du formsemestre + valeur : FormSemestre + - est_just : filtre les assiduités en fonction de si elles sont justifiées ou non + valeur : bool + - user_id : filtre les assiduités en fonction de l'utilisateur qui les a créées + valeur : int + - split : effectue un comptage par état d'assiduité + valeur : str (du moment que la clé est présente dans filtered) + + Les métriques : + - journee : comptage en nombre de journée + - demi : comptage en nombre de demi journée + - heure : comptage en heure + - compte : nombre d'objets + - all : renvoi toute les métriques + + + """ if filtered is not None: deb, fin = None, None @@ -414,34 +440,71 @@ def get_assiduites_stats( calculator: CountCalculator = CountCalculator() calculator.compute_assiduites(assiduites) + # S'il n'y a pas de filtre ou que le filtre split n'est pas dans les filtres if filtered is None or "split" not in filtered: + # On récupère le comptage total + # only_total permet de ne récupérer que le total count: dict = calculator.to_dict(only_total=True) + + # On ne garde que les métriques demandées for key, val in count.items(): if key in metrics: output[key] = val + # On renvoie le total si on a rien demandé (ou que metrics == ["all"]) return output if output else count - # Récupération des états - etats: list[str] = ( - filtered["etat"].split(",") if "etat" in filtered else scu.EtatAssiduite.all() - ) - - # être sur que les états sont corrects - etats = [etat for etat in etats if etat.upper() in scu.EtatAssiduite.all()] - # Préparation du dictionnaire de retour avec les valeurs du calcul count: dict = calculator.to_dict(only_total=False) + + # Récupération des états depuis la saisie utilisateur + etats: list[str] = ( + filtered["etat"].split(",") if "etat" in filtered else scu.EtatAssiduite.all() + ) for etat in etats: - # TODO-assiduite: on se perd entre les lower et upper. - # Pourquoi EtatAssiduite est en majuscules si tout le reste est en minuscules ? - etat = etat.lower() + # On vérifie que l'état est bien un état d'assiduité + # sinon on passe à l'état suivant + if not scu.EtatAssiduite.contains(etat): + continue + + # On récupère le comptage pour chaque état if etat != "present": output[etat] = count[etat] output[etat]["justifie"] = count[etat + "_just"] output[etat]["non_justifie"] = count[etat + "_non_just"] else: output[etat] = count[etat] + output["total"] = count["total"] + + # le dictionnaire devrait ressembler à : + # { + # "absent": { + # "journee": 1, + # "demi": 2, + # "heure": 3, + # "compte": 4, + # "justifie": { + # "journee": 1, + # "demi": 2, + # "heure": 3, + # "compte": 4 + # }, + # "non_justifie": { + # "journee": 1, + # "demi": 2, + # "heure": 3, + # "compte": 4 + # } + # }, + # ... + # "total": { + # "journee": 1, + # "demi": 2, + # "heure": 3, + # "compte": 4 + # } + # } + return output diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index b0ae6ed57..55fa10cbc 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -202,6 +202,7 @@ class BiDirectionalEnum(Enum): """Permet la recherche inverse d'un enum Condition : les clés et les valeurs doivent être uniques les clés doivent être en MAJUSCULES + => (respect de la convention des constantes) """ @classmethod @@ -213,10 +214,17 @@ class BiDirectionalEnum(Enum): return attr.upper() in cls._member_names_ @classmethod - def all(cls, keys=True): - """Retourne toutes les clés de l'enum""" - # pylint: disable-next=no-member - return cls._member_names_ if keys else list(cls._value2member_map_.keys()) + def all(cls, keys=True) -> tuple[str | object]: + """Retourne toutes les clés de l'enum (en minuscules) ou les valeurs""" + return ( + tuple( + k.lower() + # pylint: disable-next=no-member + for k in cls._member_names_ + ) # renvoie les clés en minuscules + if keys + else tuple(cls._value2member_map_.keys()) # renvoie les valeurs + ) @classmethod def get(cls, attr: str, default: any = None): diff --git a/app/templates/assiduites/pages/signal_assiduites_diff.j2 b/app/templates/assiduites/pages/signal_assiduites_diff.j2 index f7b37a21f..c201f1bcc 100644 --- a/app/templates/assiduites/pages/signal_assiduites_diff.j2 +++ b/app/templates/assiduites/pages/signal_assiduites_diff.j2 @@ -616,7 +616,7 @@ main(); - Bouton conflit si conflit de période ---> -
+