diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 822a237fc8..a889c57da7 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -296,6 +296,10 @@ class DecisionsProposeesAnnee(DecisionsProposees): [rcue for rcue in rcues_avec_niveau if rcue.est_validable()] ) "le nombre de comp. validables (éventuellement par compensation)" + self.nb_rcue_valides = len( + [rcue for rcue in rcues_avec_niveau if rcue.code_valide()] + ) + "le nombre de niveaux validés (déc. jury prise)" self.nb_rcues_under_8 = len( [rcue for rcue in rcues_avec_niveau if not rcue.est_suffisant()] ) @@ -393,7 +397,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
  • RCUEs: {html.escape(str(self.rcues_annee))}
  • nb_competences: {getattr(self, "nb_competences", "-")}
  • -
  • nb_nb_validables: {getattr(self, "nb_validables", "-")}
  • +
  • nb_validables: {getattr(self, "nb_validables", "-")}
  • codes: {self.codes}
  • explanation: {self.explanation}
  • diff --git a/app/comp/res_compat.py b/app/comp/res_compat.py index fb8ba7f814..dfd051c191 100644 --- a/app/comp/res_compat.py +++ b/app/comp/res_compat.py @@ -299,7 +299,7 @@ class NotesTableCompat(ResultatsSemestre): return sum([d.get("ects", 0.0) for d in decisions_ues.values()]) def get_etud_decision_sem(self, etudid: int) -> dict: - """Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu. + """Decision du jury semestre prise pour cet etudiant, ou None s'il n'y en pas eu. { 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id } Si état défaillant, force le code a DEF """ diff --git a/app/models/but_validations.py b/app/models/but_validations.py index d84339e00d..d5e622ecea 100644 --- a/app/models/but_validations.py +++ b/app/models/but_validations.py @@ -196,8 +196,8 @@ class RegroupementCoherentUE: ) def est_validable(self) -> bool: - """Vrai si ce RCU satisfait les conditions pour être validé - Pour cela, il suffit que la moyenne des UE qui le constitue soit > 10 + """Vrai si ce RCUE satisfait les conditions pour être validé, + c'est à dire que la moyenne des UE qui le constituent soit > 10 """ return (self.moy_rcue is not None) and ( self.moy_rcue > sco_codes.BUT_BARRE_RCUE diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 96dd0bad2b..096cc84900 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -610,7 +610,7 @@ def log_unknown_etud(): log(f"unknown student: args={etud_args}") -def get_etud_info(etudid=False, code_nip=False, filled=False) -> list: +def get_etud_info(etudid=False, code_nip=False, filled=False) -> list[dict]: """infos sur un etudiant (API). If not found, returns empty list. On peut spécifier etudid ou code_nip ou bien cherche dans les arguments de la requête courante: diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index c16eb1db34..bf9905f94c 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -167,9 +167,9 @@ def _formsemestre_enrich(sem): sem["semestre_id"], ) # eg "DUT Informatique semestre 2" - sem["dateord"] = ndb.DateDMYtoISO(sem["date_debut"]) sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"]) sem["date_fin_iso"] = ndb.DateDMYtoISO(sem["date_fin"]) + sem["dateord"] = sem["date_debut_iso"] # pour les tris try: mois_debut, annee_debut = sem["date_debut"].split("/")[1:] except: diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py index 97f01f756c..6978e2eb3b 100644 --- a/app/scodoc/sco_report.py +++ b/app/scodoc/sco_report.py @@ -39,12 +39,14 @@ from operator import itemgetter from flask import url_for, g, request import pydot +from app.but import jury_but from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre, ScolarAutorisationInscription +from app.models import FormationModalite +from app.models.etudiants import Identite import app.scodoc.sco_utils as scu -from app.models import FormationModalite from app.scodoc import notesdb as ndb from app.scodoc import html_sco_header from app.scodoc import sco_codes_parcours @@ -52,6 +54,7 @@ from app.scodoc import sco_etud from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_preferences +from app.scodoc import sco_pvjury import sco_version from app.scodoc.gen_tables import GenTable from app import log @@ -59,17 +62,28 @@ from app.scodoc.sco_codes_parcours import code_semestre_validant MAX_ETUD_IN_DESCR = 20 +LEGENDES_CODES_BUT = { + "Nb_rcue_valides": "nb RCUE validés", + "decision_annee": "code jury annuel BUT", +} -def formsemestre_etuds_stats(sem, only_primo=False): + +def formsemestre_etuds_stats(sem: dict, only_primo=False): """Récupère liste d'etudiants avec etat et decision.""" - formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) + formsemestre: FormSemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) T = nt.get_table_moyennes_triees() + + # Décisions de jury BUT pour les semestres pairs seulement + jury_but_mode = ( + formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0 + ) # Construit liste d'étudiants du semestre avec leur decision etuds = [] for t in T: etudid = t[-1] + etudiant: Identite = Identite.query.get(etudid) etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] decision = nt.get_etud_decision_sem(etudid) if decision: @@ -87,6 +101,11 @@ def formsemestre_etuds_stats(sem, only_primo=False): autorisations.sort() autorisations_str = ", ".join(autorisations) etud["devenir"] = autorisations_str + # Décisions de jury BUT (APC) + if jury_but_mode: + deca = jury_but.DecisionsProposeesAnnee(etudiant, formsemestre) + etud["nb_rcue_valides"] = deca.nb_rcue_valides + etud["decision_annee"] = deca.code_valide # Ajout clé 'bac-specialite' bs = [] if etud["bac"]: @@ -100,13 +119,22 @@ def formsemestre_etuds_stats(sem, only_primo=False): return etuds -def is_primo_etud(etud, sem): - """Determine si un (filled) etud a ete inscrit avant ce semestre. - Regarde la liste des semestres dans lesquels l'étudiant est inscrit +def is_primo_etud(etud: dict, sem: dict): + """Determine si un (filled) etud a été inscrit avant ce semestre. + Regarde la liste des semestres dans lesquels l'étudiant est inscrit. + Si semestre pair, considère comme primo-entrants ceux qui étaient + primo dans le précédent (S_{2n-1}). """ - now = sem["dateord"] + debut_cur = sem["date_debut_iso"] + # si semestre impair et sem. précédent contigu, recule date debut + if ( + (len(etud["sems"]) > 1) + and (sem["semestre_id"] % 2 == 0) + and (etud["sems"][1]["semestre_id"] == (sem["semestre_id"] - 1)) + ): + debut_cur = etud["sems"][1]["date_debut_iso"] for s in etud["sems"]: # le + recent d'abord - if s["dateord"] < now: + if s["date_debut_iso"] < debut_cur: return False return True @@ -176,21 +204,20 @@ def _results_by_category( # ajout titre ligne: for (cat, l) in zip(categories, C): - l["row_title"] = cat or "?" + l["row_title"] = cat if cat is not None else "?" # codes.append("sum") codes.append("sumpercent") - # on veut { ADM : ADM, ... }, était peu elegant en python 2.3: - # titles = {} - # map( lambda x,titles=titles: titles.__setitem__(x[0],x[1]), zip(codes,codes) ) - # Version moderne: + # on veut { ADM : ADM, ... } titles = {x: x for x in codes} + # sauf pour + titles.update(LEGENDES_CODES_BUT) titles["sum"] = "Total" titles["sumpercent"] = "%" titles["DEM"] = "Dém." # démissions - titles["row_title"] = category_name + titles["row_title"] = titles.get(category_name, category_name) return GenTable( titles=titles, columns_ids=codes, @@ -234,14 +261,11 @@ def formsemestre_report( # tab.filename = scu.make_filename("stats " + sem["titreannee"]) - tab.origin = ( - "Généré par %s le " % sco_version.SCONAME + scu.timedate_human_repr() + "" + tab.origin = f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}" + tab.caption = ( + f"Répartition des résultats par {category_name}, semestre {sem['titreannee']}" ) - tab.caption = "Répartition des résultats par %s, semestre %s" % ( - category_name, - sem["titreannee"], - ) - tab.html_caption = "Répartition des résultats par %s." % category_name + tab.html_caption = f"Répartition des résultats par {category_name}." tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id) if only_primo: tab.base_url += "&only_primo=on" @@ -265,17 +289,32 @@ def formsemestre_report( def formsemestre_report_counts( - formsemestre_id, + formsemestre_id: int, format="html", - category="bac", - result="codedecision", - allkeys=False, - only_primo=False, + category: str = "bac", + result: str = None, + allkeys: bool = False, + only_primo: bool = False, ): """ Tableau comptage avec choix des categories + category: attribut en lignes + result: attribut en colonnes + only_primo: restreint aux primo-entrants (= non redoublants) + allkeys: pour le menu du choix de l'attribut en colonnes: + si vrai, toutes les valeurs présentes dans les données + sinon liste prédéfinie (voir ci-dessous) """ + formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id) + # Décisions de jury BUT pour les semestres pairs seulement + jury_but_mode = ( + formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0 + ) + + if result is None: + result = "statut" if formsemestre.formation.is_apc() else "codedecision" + category_name = category.capitalize() title = "Comptages " + category_name etuds = formsemestre_etuds_stats(sem, only_primo=only_primo) @@ -311,6 +350,8 @@ def formsemestre_report_counts( "type_admission", "boursier_prec", ] + if jury_but_mode: + keys += ["nb_rcue_valides", "decision_annee"] keys.sort(key=scu.heterogeneous_sorting_key) F = [ """

    @@ -322,7 +363,10 @@ def formsemestre_report_counts( selected = "selected" else: selected = "" - F.append('' % (k, selected, k)) + F.append( + '' + % (k, selected, LEGENDES_CODES_BUT.get(k, k)) + ) F.append("") F.append(' Lignes: ") if only_primo: checked = 'checked="1"'