diff --git a/app/scodoc/sco_report_but.py b/app/scodoc/sco_report_but.py index b920079fed..ea042f5b65 100644 --- a/app/scodoc/sco_report_but.py +++ b/app/scodoc/sco_report_but.py @@ -38,6 +38,7 @@ from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre from app.models.etudiants import Identite +from app.models.formsemestre import FormSemestreInscription import app.scodoc.sco_utils as scu from app.scodoc import html_sco_header @@ -51,10 +52,15 @@ from app.scodoc.gen_tables import GenTable # Titres, ordonnés INDICATEUR_NAMES = { - "nb_inscr": "Inscrits initiaux", - "nb_dem": "Démissions", - "nb_def": "Défaillants", - "nb_actifs": "Inscrits finals", + "nb_inscr_S1": "Inscrits initiaux S1", + "nb_dem_S1": "Démissions S1", + "nb_def_S1": "Défaillants S1", + "nb_actifs_S1": "Inscrits finals S1", + "nb_inscr_S2": "Inscrits initiaux S2", + "nb_dem_S2": "Démissions S2", + "nb_def_S2": "Défaillants S2", + "nb_actifs_S2": "Inscrits finals S2", + "nb_no_decision": "Sans décision de jury BUT", "nb_nar": "NAR", "nb_passe_manque_rcue": "Passant avec RCUE non validé", "nb_red_avec_rcue": "Redoublant avec au moins un RCUE validé", @@ -130,98 +136,136 @@ def but_indicateurs_by_bac(formsemestre: FormSemestre) -> dict[str:dict]: ) if formsemestre.semestre_id % 2: raise ScoValueError("Ce rapport doit être généré à partir d'un semestre PAIR.") - # Le semestre suivant (pour compter les passages) + # Les semestres suivant/precedent (pour compter les passages et redoublements) next_sem_idx = formsemestre.semestre_id + 1 + red_sem_idx = formsemestre.semestre_id - 1 - res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - etuds = formsemestre.get_inscrits(include_demdef=True) # Ventilation par bac - etuds_by_bac = defaultdict(list) # bac : etuds - for etud in etuds: - adm = etud.admission.first() - bac = adm.get_bac().abbrev() if adm else "?" - etuds_by_bac[bac].append(etud) + inscriptions_by_bac = _formsemestre_inscriptions_by_bac(formsemestre) indicateurs_by_bac = {} - for bac in etuds_by_bac: + decisions_annee_tous = {} + for bac in inscriptions_by_bac: decisions_annee = { - etud.id: jury_but.DecisionsProposeesAnnee(etud, formsemestre) - for etud in etuds_by_bac[bac] - if res.get_etud_etat(etud.id) == scu.INSCRIT + inscr.etud.id: jury_but.DecisionsProposeesAnnee(inscr.etud, formsemestre) + for inscr in inscriptions_by_bac[bac] } indicateurs_by_bac[bac] = _indicateurs_enquete_but( - res, etuds_by_bac[bac], decisions_annee, next_sem_idx + inscriptions_by_bac[bac], decisions_annee, red_sem_idx, next_sem_idx ) + decisions_annee_tous.update(decisions_annee) # refait pour tous - decisions_annee = { - etud.id: jury_but.DecisionsProposeesAnnee(etud, formsemestre) - for etud in etuds - if res.get_etud_etat(etud.id) == scu.INSCRIT - } indicateurs_by_bac["Total"] = _indicateurs_enquete_but( - res, etuds, decisions_annee, next_sem_idx + formsemestre.inscriptions, decisions_annee_tous, red_sem_idx, next_sem_idx ) + # Comptages sur semestre(s) précédent(s) + # en effet, les étudiants de ce semestre pair peuvent venir de + # différents S1 + formsemestre_id_precedents = { + deca.formsemestre_impair.id + for deca in decisions_annee_tous.values() + if deca and deca.formsemestre_impair + } + for formsemestre_id_precedent in formsemestre_id_precedents: + formsemestre_impair = FormSemestre.query.get(formsemestre_id_precedent) + suffix = ( + f"S{formsemestre_impair.semestre_id}" + if len(formsemestre_id_precedents) == 1 + else formsemestre_impair.session_id() + ) + indicateurs_by_bac["Total"].update( + _indicateurs_enquete_but_inscrits(formsemestre_impair.inscriptions, suffix) + ) + for bac, inscriptions in _formsemestre_inscriptions_by_bac( + formsemestre_impair + ).items(): + indicateurs_by_bac[bac].update( + _indicateurs_enquete_but_inscrits(inscriptions, suffix) + ) return indicateurs_by_bac +def _formsemestre_inscriptions_by_bac(formsemestre: FormSemestre) -> defaultdict: + "liste d'inscriptions, par type de bac" + inscriptions_by_bac = defaultdict(list) # bac : etuds + for inscr in formsemestre.inscriptions: + adm = inscr.etud.admission.first() + bac = adm.get_bac().abbrev() if adm else "?" + inscriptions_by_bac[bac].append(inscr) + return inscriptions_by_bac + + +def _indicateurs_enquete_but_inscrits( + inscriptions: list[FormSemestreInscription], suffix: str +) -> dict: + """Nombre d'incrits, DEM, DEF. + Suffixe les clés avec _suffix""" + if suffix: + suffix = "_" + suffix + return { + "nb_inscr" + suffix: len(inscriptions), + "nb_actifs" + suffix: len([i for i in inscriptions if i.etat == scu.INSCRIT]), + "nb_def" + suffix: len([i for i in inscriptions if i.etat == scu.DEF]), + "nb_dem" + suffix: len([i for i in inscriptions if i.etat == scu.DEMISSION]), + } + + def _indicateurs_enquete_but( - res: NotesTableCompat, - etuds: list[Identite], + inscriptions: list[FormSemestreInscription], decisions_annee: dict[jury_but.DecisionsProposeesAnnee], + red_sem_idx: int, next_sem_idx: int, ) -> dict: """Calcule les indicateurs de l'enquête ADIUT 2022""" - indicateurs = { - "nb_inscr": len(etuds), - "nb_actifs": len( - [etud for etud in etuds if res.get_etud_etat(etud.id) == scu.INSCRIT] - ), - "nb_def": len( - [etud for etud in etuds if res.get_etud_etat(etud.id) == scu.DEF] - ), - "nb_dem": len( - [etud for etud in etuds if res.get_etud_etat(etud.id) == scu.DEMISSION] - ), - "nb_nar": len( - [ - True - for deca in decisions_annee.values() - if deca.code_valide == sco_codes_parcours.NAR - ] - ), - # Redoublants sans aucune RCUE - "nb_red_sans_rcue": len( - [ - True - for deca in decisions_annee.values() - if (deca.nb_rcue_valides == 0) - and (next_sem_idx not in deca.get_autorisations_passage()) - ] - ), - # Redoublants avec au moins une RCUE - "nb_red_avec_rcue": len( - [ - True - for deca in decisions_annee.values() - if (deca.nb_rcue_valides > 0) - and (next_sem_idx not in deca.get_autorisations_passage()) - ] - ), - # Passant (en BUT2) sans avoir validé tous les RCUE - "nb_passe_manque_rcue": len( - [ - True - for deca in decisions_annee.values() - if (deca.nb_rcue_valides < deca.nb_competences) - and (next_sem_idx in deca.get_autorisations_passage()) - ] - ), - # Ayant validé tous les RCUE - "nb_valide_tt_rcue": len( - [ - True - for deca in decisions_annee.values() - if (deca.nb_rcue_valides >= deca.nb_competences) - ] - ), - } + indicateurs = _indicateurs_enquete_but_inscrits(inscriptions, suffix="S2") + indicateurs.update( + { + "nb_no_decision": len( + [True for deca in decisions_annee.values() if deca.code_valide is None] + ), + "nb_nar": len( + [ + True + for deca in decisions_annee.values() + if deca.code_valide == sco_codes_parcours.NAR + ] + ), + # Redoublants sans aucune RCUE + "nb_red_sans_rcue": len( + [ + True + for deca in decisions_annee.values() + if (deca.nb_rcue_valides == 0) + and (next_sem_idx not in deca.get_autorisations_passage()) + and (red_sem_idx in deca.get_autorisations_passage()) + ] + ), + # Redoublants avec au moins une RCUE + "nb_red_avec_rcue": len( + [ + True + for deca in decisions_annee.values() + if (deca.nb_rcue_valides > 0) + and (next_sem_idx not in deca.get_autorisations_passage()) + and (red_sem_idx in deca.get_autorisations_passage()) + ] + ), + # Passant (en BUT2) sans avoir validé tous les RCUE + "nb_passe_manque_rcue": len( + [ + True + for deca in decisions_annee.values() + if (deca.nb_rcue_valides < deca.nb_competences) + and (next_sem_idx in deca.get_autorisations_passage()) + ] + ), + # Ayant validé tous les RCUE + "nb_valide_tt_rcue": len( + [ + True + for deca in decisions_annee.values() + if (deca.nb_rcue_valides >= deca.nb_competences) + ] + ), + } + ) return indicateurs diff --git a/sco_version.py b/sco_version.py index 36005233b6..ff69c18121 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.3.44" +SCOVERSION = "9.3.45" SCONAME = "ScoDoc"