diff --git a/app/but/jury_but_recap.py b/app/but/jury_but_recap.py index 809294408f..2e56ca3c3d 100644 --- a/app/but/jury_but_recap.py +++ b/app/but/jury_but_recap.py @@ -21,6 +21,7 @@ from app.but.jury_but import ( from app.comp.res_but import ResultatsSemestreBUT from app.comp import res_sem from app.models.etudiants import Identite +from app.scodoc.sco_exceptions import ScoNoReferentielCompetences from app.models.formsemestre import FormSemestre from app.scodoc import html_sco_header from app.scodoc.sco_codes_parcours import ( @@ -32,7 +33,79 @@ from app.scodoc.sco_codes_parcours import ( from app.scodoc import sco_formsemestre_status from app.scodoc import sco_pvjury from app.scodoc import sco_utils as scu -from app.scodoc.sco_exceptions import ScoNoReferentielCompetences +from app.scodoc import table_builder as tb + + +class TableJury(tb.Table): + pass + + +class RowJury(tb.Row): + "Ligne de la table saisie jury" + + def add_nb_rcues_cell(self, deca: DecisionsProposeesAnnee): + "cell avec nb niveaux validables / total" + classes = ["col_rcue", "col_rcues_validables"] + if deca.nb_rcues_under_8 > 0: + classes.append("moy_ue_warning") + elif deca.nb_validables < deca.nb_competences: + classes.append("moy_ue_inf") + else: + classes.append("moy_ue_valid") + + if len(deca.rcues_annee) > 0: + # permet un tri par nb de niveaux validables + moyenne gen indicative S_pair + if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen: + moy = deca.res_pair.etud_moy_gen[deca.etud.id] + if np.isnan(moy): + moy_gen_d = "x" + else: + moy_gen_d = f"{int(moy*1000):05}" + else: + moy_gen_d = "x" + order = f"{deca.nb_validables:04d}-{moy_gen_d}" + else: + # étudiants sans RCUE: pas de semestre impair, ... + # les classe à la fin + order = f"{deca.nb_validables:04d}-00000-{deca.etud.sort_key}" + + self.add_cell( + "rcues_validables", + "RCUEs", + f"""{deca.nb_validables}/{deca.nb_competences}""" + + ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""), + group="rcues_validables", + classes=classes, + data={"order": order}, + ) + + def add_ue_cells(self, dec_ue: DecisionsProposeesUE): + "cell de moyenne d'UE" + col_id = f"moy_ue_{dec_ue.ue.id}" + note_class = "" + val = dec_ue.moy_ue + if isinstance(val, float): + if val < BUT_BARRE_UE: + note_class = "moy_inf" + elif val >= BUT_BARRE_UE: + note_class = "moy_ue_valid" + if val < BUT_BARRE_UE8: + note_class = "moy_ue_warning" # notes très basses + self.add_cell( + col_id, + dec_ue.ue.acronyme, + self.fmt_note(val), + group="col_ue", + "col_ue" + note_class, + column_class="col_ue", + ) + self.add_cell( + col_id + "_code", + dec_ue.ue.acronyme, + dec_ue.code_valide or "", + "col_ue_code recorded_code", + column_class="col_ue", + ) def formsemestre_saisie_jury_but( @@ -59,8 +132,6 @@ def formsemestre_saisie_jury_but( # DecisionsProposeesAnnee(etud, formsemestre2) # Pour le 1er etud, faire un check_ues_ready_jury(self) -> page d'erreur # -> rcue .ue_1, .ue_2 -> stroe moy ues, rcue.moy_rcue, etc - # XXX if formsemestre2.semestre_id % 2 != 0: - # raise ScoValueError("Cette page ne fonctionne que sur les semestres pairs") if formsemestre2.formation.referentiel_competence is None: raise ScoNoReferentielCompetences(formation=formsemestre2.formation) @@ -271,62 +342,37 @@ class RowCollector: self.column_classes[col_id] = column_class self.idx += 1 - def add_etud_cells( - self, etud: Identite, formsemestre: FormSemestre, with_links=True - ): - "Les cells code, nom, prénom etc." - # --- Codes (seront cachés, mais exportés en excel) - self.add_cell("etudid", "etudid", etud.id, "codes") - self.add_cell("code_nip", "code_nip", etud.code_nip or "", "codes") - # --- Identité étudiant (adapté de res_common/get_table_recap, à factoriser XXX TODO) - self.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail") - self.add_cell("nom_disp", "Nom", etud.nom_disp(), "identite_detail") - self["_nom_disp_order"] = etud.sort_key - self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail") - self.add_cell("nom_short", "Nom", etud.nom_short, "identite_court") - self["_nom_short_data"] = { - "etudid": etud.id, - "nomprenom": etud.nomprenom, - } - if with_links: - self["_nom_short_order"] = etud.sort_key - self["_nom_short_target"] = url_for( - "notes.formsemestre_bulletinetud", - scodoc_dept=g.scodoc_dept, - formsemestre_id=formsemestre.id, - etudid=etud.id, - ) - self["_nom_short_target_attrs"] = f'class="etudinfo" id="{etud.id}"' - self["_nom_disp_target"] = self["_nom_short_target"] - self["_nom_disp_target_attrs"] = self["_nom_short_target_attrs"] - self.last_etud_cell_idx = self.idx + # def add_etud_cells( + # self, etud: Identite, formsemestre: FormSemestre, with_links=True + # ): + # "Les cells code, nom, prénom etc." + # # --- Codes (seront cachés, mais exportés en excel) + # self.add_cell("etudid", "etudid", etud.id, "codes") + # self.add_cell("code_nip", "code_nip", etud.code_nip or "", "codes") + # # --- Identité étudiant (adapté de res_common/get_table_recap, à factoriser XXX TODO) + # self.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail") + # self.add_cell("nom_disp", "Nom", etud.nom_disp(), "identite_detail") + # self["_nom_disp_order"] = etud.sort_key + # self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail") + # self.add_cell("nom_short", "Nom", etud.nom_short, "identite_court") + # self["_nom_short_data"] = { + # "etudid": etud.id, + # "nomprenom": etud.nomprenom, + # } + # if with_links: + # self["_nom_short_order"] = etud.sort_key + # self["_nom_short_target"] = url_for( + # "notes.formsemestre_bulletinetud", + # scodoc_dept=g.scodoc_dept, + # formsemestre_id=formsemestre.id, + # etudid=etud.id, + # ) + # self["_nom_short_target_attrs"] = f'class="etudinfo" id="{etud.id}"' + # self["_nom_disp_target"] = self["_nom_short_target"] + # self["_nom_disp_target_attrs"] = self["_nom_short_target_attrs"] + # self.last_etud_cell_idx = self.idx - def add_ue_cells(self, dec_ue: DecisionsProposeesUE): - "cell de moyenne d'UE" - col_id = f"moy_ue_{dec_ue.ue.id}" - note_class = "" - val = dec_ue.moy_ue - if isinstance(val, float): - if val < BUT_BARRE_UE: - note_class = " moy_inf" - elif val >= BUT_BARRE_UE: - note_class = " moy_ue_valid" - if val < BUT_BARRE_UE8: - note_class = " moy_ue_warning" # notes très basses - self.add_cell( - col_id, - dec_ue.ue.acronyme, - self.fmt_note(val), - "col_ue" + note_class, - column_class="col_ue", - ) - self.add_cell( - col_id + "_code", - dec_ue.ue.acronyme, - dec_ue.code_valide or "", - "col_ue_code recorded_code", - column_class="col_ue", - ) + def add_rcue_cells(self, dec_rcue: DecisionsProposeesRCUE): "2 cells: moyenne du RCUE, code enregistré" @@ -356,40 +402,6 @@ class RowCollector: column_class="col_rcue", ) - def add_nb_rcues_cell(self, deca: DecisionsProposeesAnnee): - "cell avec nb niveaux validables / total" - klass = " " - if deca.nb_rcues_under_8 > 0: - klass += "moy_ue_warning" - elif deca.nb_validables < deca.nb_competences: - klass += "moy_ue_inf" - else: - klass += "moy_ue_valid" - self.add_cell( - "rcues_validables", - "RCUEs", - f"""{deca.nb_validables}/{deca.nb_competences}""" - + ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""), - "col_rcue col_rcues_validables" + klass, - ) - if len(deca.rcues_annee) > 0: - # permet un tri par nb de niveaux validables + moyenne gen indicative S_pair - if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen: - moy = deca.res_pair.etud_moy_gen[deca.etud.id] - if np.isnan(moy): - moy_gen_d = "x" - else: - moy_gen_d = f"{int(moy*1000):05}" - else: - moy_gen_d = "x" - self["_rcues_validables_order"] = f"{deca.nb_validables:04d}-{moy_gen_d}" - else: - # etudiants sans RCUE: pas de semestre impair, ... - # les classe à la fin - self[ - "_rcues_validables_order" - ] = f"{deca.nb_validables:04d}-00000-{deca.etud.sort_key}" - def get_jury_but_table( formsemestre2: FormSemestre, read_only: bool = False, mode="jury", with_links=True @@ -404,14 +416,14 @@ def get_jury_but_table( "nb_etuds": len(formsemestre2.etuds_inscriptions), "codes_annuels": collections.Counter(), } - column_classes = {} - rows = [] + table = TableJury(res2, mode_jury=True) for etudid in formsemestre2.etuds_inscriptions: etud: Identite = Identite.query.get(etudid) deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre2) - row = RowCollector(titles=titles, column_classes=column_classes) - row.add_etud_cells(etud, formsemestre2, with_links=with_links) - row.idx = 100 # laisse place pour les colonnes de groupes + # XXX row = RowCollector(titles=titles, column_classes=column_classes) + row = RowJury(table, etudid) + table.add_row(row) + row.add_etud(etud) # --- Nombre de niveaux row.add_nb_rcues_cell(deca) # --- Les RCUEs diff --git a/app/comp/res_common.py b/app/comp/res_common.py index fbd12ae587..ce5886efbd 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -433,112 +433,238 @@ class ResultatsSemestre(ResultatsCache): # somme des coefs des modules de l'UE auxquels il est inscrit return self.compute_etud_ue_coef(etudid, ue) - # --- TABLEAU RECAP - def get_table_recap( +# --- TABLEAU RECAP + + +class TableRecap(tb.Table): # was get_table_recap + """Table récap. des résultats. + allow_html: si vrai, peut mettre du HTML dans les valeurs + + Result: Table, le contenu étant une ligne par étudiant. + + + Si convert_values, transforme les notes en chaines ("12.34"). + Les colonnes générées sont: + etudid + rang : rang indicatif (basé sur moy gen) + moy_gen : moy gen indicative + moy_ue_, ..., les moyennes d'UE + moy_res__, ... les moyennes de ressources dans l'UE + moy_sae__, ... les moyennes de SAE dans l'UE + + On ajoute aussi des classes: + - pour les lignes: + selected_row pour l'étudiant sélectionné + - les colonnes: + - la moyenne générale a la classe col_moy_gen + - les colonnes SAE ont la classe col_sae + - les colonnes Resources ont la classe col_res + - les colonnes d'UE ont la classe col_ue + - les colonnes de modules (SAE ou res.) d'une UE ont la classe mod_ue_ + """ + + def __init__( self, + res: ResultatsSemestre, convert_values=False, include_evaluations=False, mode_jury=False, - ) -> tb.Table: - """Table récap. des résultats. - allow_html: si vrai, peut mettre du HTML dans les valeurs + ): + self.res = res + self.include_evaluations = include_evaluations + self.mode_jury = mode_jury - Result: Table, le contenu étant une ligne par étudiant. - - - Si convert_values, transforme les notes en chaines ("12.34"). - Les colonnes générées sont: - etudid - rang : rang indicatif (basé sur moy gen) - moy_gen : moy gen indicative - moy_ue_, ..., les moyennes d'UE - moy_res__, ... les moyennes de ressources dans l'UE - moy_sae__, ... les moyennes de SAE dans l'UE - - On ajoute aussi des classes: - - pour les lignes: - selected_row pour l'étudiant sélectionné - - les colonnes: - - la moyenne générale a la classe col_moy_gen - - les colonnes SAE ont la classe col_sae - - les colonnes Resources ont la classe col_res - - les colonnes d'UE ont la classe col_ue - - les colonnes de modules (SAE ou res.) d'une UE ont la classe mod_ue_ - """ + parcours = self.formsemestre.formation.get_parcours() + self.barre_moy = parcours.BARRE_MOY - scu.NOTES_TOLERANCE + self.barre_valid_ue = parcours.NOTES_BARRE_VALID_UE + self.barre_warning_ue = parcours.BARRE_UE_DISPLAY_WARNING + self.cache_nomcomplet = {} # cache uid : nomcomplet + if convert_values: + self.fmt_note = scu.fmt_note + else: + self.fmt_note = lambda x: x + # couples (modimpl, ue) effectivement présents dans la table: + self.modimpl_ue_ids = set() etuds_inscriptions = self.formsemestre.etuds_inscriptions ues = self.formsemestre.query_ues(with_sport=True) # avec bonus ues_sans_bonus = [ue for ue in ues if ue.type != UE_SPORT] - table = tb.Table() - # Quelques infos stockées dans la Table - parcours = self.formsemestre.formation.get_parcours() - table.barre_moy = parcours.BARRE_MOY - scu.NOTES_TOLERANCE - table.barre_valid_ue = parcours.NOTES_BARRE_VALID_UE - table.barre_warning_ue = parcours.BARRE_UE_DISPLAY_WARNING - table.cache_nomcomplet = {} # cache uid : nomcomplet - if convert_values: - table.fmt_note = scu.fmt_note - else: - table.fmt_note = lambda x: x - # couples (modimpl, ue) effectivement présents dans la table: - table.modimpl_ue_ids = set() - for etudid in etuds_inscriptions: etud = Identite.query.get(etudid) - row = tb.Row(table, etudid) - table.add_row(row) - self._recap_add_etud(row, etud) - self._recap_add_moyennes(row, etud, ues_sans_bonus, mode_jury) + row = RowRecap(self, etudid) + self.add_row(row) + self.recap_add_etud(row, etud) + self._recap_add_moyennes(row, etud, ues_sans_bonus) - self.recap_add_partitions(table) - self.recap_add_cursus(table) - self._recap_add_admissions(table) + self.recap_add_partitions() + self.recap_add_cursus() + self._recap_add_admissions() # tri par rang croissant if not self.formsemestre.block_moyenne_generale: - table.sort_rows(key=lambda row: row.rang_order) + self.sort_rows(key=lambda row: row.rang_order) else: - table.sort_rows(key=lambda row: row.nb_ues_validables, reverse=True) + self.sort_rows(key=lambda row: row.nb_ues_validables, reverse=True) # Lignes footer (min, max, ects, apo, ...) - self._recap_add_bottom_rows(table, ues_sans_bonus) + self.add_bottom_rows(ues_sans_bonus) # Evaluations: if include_evaluations: - self._recap_add_evaluations(table) + self.add_evaluations() - # Ajoute style "col_empty" aux colonnes de modules vides - row_moy = table.get_row_by_id("moy") - for col_id in table.column_ids: + self.mark_empty_cols() + self.add_type_row() + + def mark_empty_cols(self): + """Ajoute style "col_empty" aux colonnes de modules vides""" + # identifie les col. vides par la classe sur leur moyenne + row_moy = self.get_row_by_id("moy") + for col_id in self.column_ids: cell: tb.Cell = row_moy.cells.get(col_id) if cell and "col_empty" in cell.classes: - table.column_classes[col_id].append("col_empty") + self.column_classes[col_id].append("col_empty") - # Ligne avec la classe de chaque colonne + def add_type_row(self): + """Ligne avec la classe de chaque colonne recap.""" # récupère le type à partir des classes css (hack...) row_type = tb.BottomRow( - table, + self, "type_col", left_title="Type col.", left_title_col_ids=["prenom", "nom_short"], category="bottom_infos", classes=["bottom_info"], ) - for col_id in table.column_ids: - group_name = table.column_group.get(col_id, "") + for col_id in self.column_ids: + group_name = self.column_group.get(col_id, "") if group_name.startswith("col_"): group_name = group_name[4:] row_type.add_cell(col_id, None, group_name) - return table + def add_bottom_rows(self, ues): + """Les informations à mettre en bas de la table recap: + min, max, moy, ECTS, Apo.""" + res = self.res + # Ordre des lignes: Min, Max, Moy, Coef, ECTS, Apo + row_min = tb.BottomRow( + self, + "min", + left_title="Min.", + left_title_col_ids=["prenom", "nom_short"], + category="bottom_infos", + classes=["bottom_info"], + ) + row_max = tb.BottomRow( + self, + "max", + left_title="Max.", + left_title_col_ids=["prenom", "nom_short"], + category="bottom_infos", + classes=["bottom_info"], + ) + row_moy = tb.BottomRow( + self, + "moy", + left_title="Moy.", + left_title_col_ids=["prenom", "nom_short"], + category="bottom_infos", + classes=["bottom_info"], + ) + row_coef = tb.BottomRow( + self, + "coef", + left_title="Coef.", + left_title_col_ids=["prenom", "nom_short"], + category="bottom_infos", + classes=["bottom_info"], + ) + row_ects = tb.BottomRow( + self, + "ects", + left_title="ECTS", + left_title_col_ids=["prenom", "nom_short"], + category="bottom_infos", + classes=["bottom_info"], + ) + row_apo = tb.BottomRow( + self, + "apo", + left_title="Code Apogée", + left_title_col_ids=["prenom", "nom_short"], + category="bottom_infos", + classes=["bottom_info"], + ) - def _recap_add_etud(self, row: tb.Row, etud: Identite): + # --- ECTS + # titre (à gauche) sur 2 colonnes pour s'adapter à l'affichage des noms/prenoms + for ue in ues: + col_id = f"moy_ue_{ue.id}" + row_ects.add_cell(col_id, None, ue.ects) + # ajoute cell UE vides sur ligne coef pour borders verticales + # XXX TODO classes dans table sur colonne ajoutées à tous les TD + row_coef.add_cell(col_id, None, "") + row_ects.add_cell( + "moy_gen", + None, + sum([ue.ects or 0 for ue in ues if ue.type != UE_SPORT]), + ) + # --- MIN, MAX, MOY, APO + row_min.add_cell("moy_gen", None, self.fmt_note(res.etud_moy_gen.min())) + row_max.add_cell("moy_gen", None, self.fmt_note(res.etud_moy_gen.max())) + row_moy.add_cell("moy_gen", None, self.fmt_note(res.etud_moy_gen.mean())) + + for ue in ues: + col_id = f"moy_ue_{ue.id}" + row_min.add_cell(col_id, None, self.fmt_note(res.etud_moy_ue[ue.id].min())) + row_max.add_cell(col_id, None, self.fmt_note(res.etud_moy_ue[ue.id].max())) + row_moy.add_cell(col_id, None, self.fmt_note(res.etud_moy_ue[ue.id].mean())) + row_apo.add_cell(col_id, None, ue.code_apogee or "") + + for modimpl in res.formsemestre.modimpls_sorted: + if (modimpl.id, ue.id) in self.modimpl_ue_ids: + col_id = f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}" + if res.is_apc: + coef = res.modimpl_coefs_df[modimpl.id][ue.id] + else: + coef = modimpl.module.coefficient or 0 + row_coef.add_cell( + col_id, + None, + self.fmt_note(coef), + group=f"col_ue_{ue.id}_modules", + ) + notes = res.modimpl_notes(modimpl.id, ue.id) + if np.isnan(notes).all(): + # aucune note valide + row_min.add_cell(col_id, None, np.nan) + row_max.add_cell(col_id, None, np.nan) + moy = np.nan + else: + row_min.add_cell(col_id, None, self.fmt_note(np.nanmin(notes))) + row_max.add_cell(col_id, None, self.fmt_note(np.nanmax(notes))) + moy = np.nanmean(notes) + row_moy.add_cell( + col_id, + None, + self.fmt_note(moy), + # aucune note dans ce module ? + classes=["col_empty" if np.isnan(moy) else ""], + ) + row_apo.add_cell(col_id, None, modimpl.module.code_apogee or "") + + +class RowRecap(tb.Row): + "Ligne de la table recap" + + def add_etud(self, etud: Identite): """Ajoute colonnes étudiant: codes, noms""" + res = self.table.res # --- Codes (seront cachés, mais exportés en excel) - row.add_cell("etudid", "etudid", etud.id, "etud_codes") - row.add_cell( + self.add_cell("etudid", "etudid", etud.id, "etud_codes") + self.add_cell( "code_nip", "code_nip", etud.code_nip or "", @@ -546,26 +672,26 @@ class ResultatsSemestre(ResultatsCache): ) # --- Rang - if not self.formsemestre.block_moyenne_generale: - row.rang_order = self.etud_moy_gen_ranks_int[etud.id] - row.add_cell( + if not res.formsemestre.block_moyenne_generale: + self.rang_order = res.etud_moy_gen_ranks_int[etud.id] + res.add_cell( "rang", "Rg", self.etud_moy_gen_ranks[etud.id], "rang", - data={"order": f"{row.rang_order:05d}"}, + data={"order": f"{self.rang_order:05d}"}, ) else: - row.rang_order = -1 + self.rang_order = -1 # --- Identité étudiant url_bulletin = url_for( "notes.formsemestre_bulletinetud", scodoc_dept=g.scodoc_dept, - formsemestre_id=self.formsemestre.id, + formsemestre_id=res.formsemestre.id, etudid=etud.id, ) - row.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail") - row.add_cell( + self.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail") + self.add_cell( "nom_disp", "Nom", etud.nom_disp(), @@ -574,8 +700,8 @@ class ResultatsSemestre(ResultatsCache): target=url_bulletin, target_attrs={"class": "etudinfo", "id": str(etud.id)}, ) - row.add_cell("prenom", "Prénom", etud.prenom, "identite_detail") - row.add_cell( + self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail") + self.add_cell( "nom_short", "Nom", etud.nom_short, @@ -589,19 +715,18 @@ class ResultatsSemestre(ResultatsCache): target_attrs={"class": "etudinfo", "id": str(etud.id)}, ) - def _recap_add_moyennes( + def add_moyennes( # XXX was _recap_add_moyennes self, row: tb.Row, etud: Identite, ues_sans_bonus: list[UniteEns], - mode_jury=False, ): """Ajoute cols moy_gen moy_ue et tous les modules...""" - table = row.table - + table = self.table + res = table.res # --- Moyenne générale - if not self.formsemestre.block_moyenne_generale: - moy_gen = self.etud_moy_gen.get(etud.id, False) + if not res.formsemestre.block_moyenne_generale: + moy_gen = res.etud_moy_gen.get(etud.id, False) note_class = "" if moy_gen is False: moy_gen = scu.NO_NOTE_STR @@ -615,25 +740,25 @@ class ResultatsSemestre(ResultatsCache): classes=[note_class], ) # Ajoute bulle sur titre du pied de table: - if self.is_apc: + if res.is_apc: table.foot_title_row.cells["moy_gen"].target_attrs[ "title" ] = "moyenne indicative" # --- Moyenne d'UE - row.nb_ues_validables, row.nb_ues_warning = 0, 0 + self.nb_ues_validables, self.nb_ues_warning = 0, 0 for ue in ues_sans_bonus: - ue_status = self.get_etud_ue_status(etud.id, ue.id) + ue_status = res.get_etud_ue_status(etud.id, ue.id) if ue_status is not None: - self._recap_add_ue(row, ue, ue_status) - if mode_jury: + self.add_ue(ue, ue_status) + if table.mode_jury: # pas d'autre colonnes de résultats continue # Bonus (sport) dans cette UE ? # Le bonus sport appliqué sur cette UE - if (self.bonus_ues is not None) and (ue.id in self.bonus_ues): - val = self.bonus_ues[ue.id][etud.id] or "" + if (res.bonus_ues is not None) and (ue.id in res.bonus_ues): + val = res.bonus_ues[ue.id][etud.id] or "" val_fmt = val_fmt_html = table.fmt_note(val) if val: val_fmt_html = f"""{ @@ -648,20 +773,22 @@ class ResultatsSemestre(ResultatsCache): classes=["col_ue_bonus"], ) # Les moyennes des modules (ou ressources et SAÉs) dans cette UE - self._recap_add_ue_modimpls(row, ue, etud, ue_status["is_capitalized"]) + self.add_ue_modimpls( + ue, etud, ue_status["is_capitalized"] + ) # XXX _recap_add_ue_modimpls - row.nb_ues_etud_parcours = len(self.etud_ues_ids(etud.id)) + self.nb_ues_etud_parcours = len(res.etud_ues_ids(etud.id)) ue_valid_txt = ( ue_valid_txt_html - ) = f"{row.nb_ues_validables}/{row.nb_ues_etud_parcours}" - if row.nb_ues_warning: + ) = f"{self.nb_ues_validables}/{self.nb_ues_etud_parcours}" + if self.nb_ues_warning: ue_valid_txt_html += " " + scu.EMO_WARNING # place juste avant moy. gen. table.insert_group("col_ues_validables", before="col_moy_gen") classes = ["col_ue"] - if row.nb_ues_warning: + if self.nb_ues_warning: classes.append("moy_ue_warning") - elif row.nb_ues_validables < len(ues_sans_bonus): + elif self.nb_ues_validables < len(ues_sans_bonus): classes.append("moy_inf") row.add_cell( "ues_validables", @@ -670,13 +797,13 @@ class ResultatsSemestre(ResultatsCache): group="col_ues_validables", classes=classes, raw_content=ue_valid_txt, - data={"order": row.nb_ues_validables}, # tri + data={"order": self.nb_ues_validables}, # tri ) - if mode_jury and self.validations: - if self.is_apc: + if table.mode_jury and res.validations: + if res.is_apc: # formations BUT: pas de code semestre, concatene ceux des UEs - dec_ues = self.validations.decisions_jury_ues.get(etud.id) + dec_ues = res.validations.decisions_jury_ues.get(etud.id) if dec_ues: jury_code_sem = ",".join( [dec_ues[ue_id].get("code", "") for ue_id in dec_ues] @@ -685,58 +812,60 @@ class ResultatsSemestre(ResultatsCache): jury_code_sem = "" else: # formations classiques: code semestre - dec_sem = self.validations.decisions_jury.get(etud.id) + dec_sem = res.validations.decisions_jury.get(etud.id) jury_code_sem = dec_sem["code"] if dec_sem else "" - row.add_cell("jury_code_sem", "Jury", jury_code_sem, "jury_code_sem") - row.add_cell( + self.add_cell("jury_code_sem", "Jury", jury_code_sem, "jury_code_sem") + self.add_cell( "jury_link", "", f"""{("saisir" if not jury_code_sem else "modifier") - if self.formsemestre.etat else "voir"} décisions""", + if res.formsemestre.etat else "voir"} décisions""", "col_jury_link", ) - def _recap_add_ue(self, row: tb.Row, ue: UniteEns, ue_status: dict): + def add_ue(self, ue: UniteEns, ue_status: dict): "Ajoute résultat UE au row (colonne col_ue)" - table = row.table + table = self.table col_id = f"moy_ue_{ue.id}" val = ue_status["moy"] note_class = "" if isinstance(val, float): if val < table.barre_moy: - note_class = " moy_inf" + note_class = "moy_inf" elif val >= table.barre_valid_ue: - note_class = " moy_ue_valid" - row.nb_ues_validables += 1 + note_class = "moy_ue_valid" + self.nb_ues_validables += 1 if val < table.barre_warning_ue: - note_class = " moy_ue_warning" # notes très basses - row.nb_ues_warning += 1 - row.add_cell( + note_class = "moy_ue_warning" # notes très basses + self.nb_ues_warning += 1 + self.add_cell( col_id, ue.acronyme, table.fmt_note(val), group=f"col_ue_{ue.id}", classes=["col_ue", "col_moy_ue", note_class], ) - row.table.foot_title_row.cells[col_id].target_attrs[ + table.foot_title_row.cells[col_id].target_attrs[ "title" ] = f"""{ue.titre} S{ue.semestre_idx or '?'}""" - def _recap_add_ue_modimpls( + def add_ue_modimpls( self, row: tb.Row, ue: UniteEns, etud: Identite, is_capitalized: bool ): """Ajoute à row les moyennes des modules (ou ressources et SAÉs) dans l'UE""" + # pylint: disable=invalid-unary-operand-type table = row.table - for modimpl in self.modimpls_in_ue(ue, etud.id, with_bonus=False): + res = table.res + for modimpl in res.modimpls_in_ue(ue, etud.id, with_bonus=False): if is_capitalized: val = "-c-" else: - modimpl_results = self.modimpls_results.get(modimpl.id) + modimpl_results = res.modimpls_results.get(modimpl.id) if modimpl_results: # pas bonus - if self.is_apc: # BUT + if res.is_apc: # BUT moys_vers_ue = modimpl_results.etuds_moy_module.get(ue.id) val = ( moys_vers_ue.get(etud.id, "?") @@ -754,18 +883,18 @@ class ResultatsSemestre(ResultatsCache): if modimpl.module.module_type == scu.ModuleType.MALUS: if val and not isinstance(val, str) and not np.isnan(val): if val >= 0: - val_fmt_html = f"""+{ + val_fmt_html = f"""+{ val_fmt }""" else: # val_fmt_html = (scu.EMO_RED_TRIANGLE_DOWN + val_fmt) val_fmt_html = f"""-{ - table.fmt_note(-val) - }""" + table.fmt_note(-val)}""" + else: val_fmt = val_fmt_html = "" # inscrit à ce malus, mais sans note - cell = row.add_cell( + cell = self.add_cell( col_id, modimpl.module.code, val_fmt_html, @@ -797,121 +926,6 @@ class ResultatsSemestre(ResultatsCache): ] = f"{modimpl.module.titre} ({nom_resp})" table.modimpl_ue_ids.add((modimpl.id, ue.id)) - def _recap_add_bottom_rows(self, table: tb.Table, ues): - """Les informations à mettre en bas de la table: min, max, moy, ECTS, Apo""" - # Ordre des lignes: Min, Max, Moy, Coef, ECTS, Apo - row_min = tb.BottomRow( - table, - "min", - left_title="Min.", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_max = tb.BottomRow( - table, - "max", - left_title="Max.", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_moy = tb.BottomRow( - table, - "moy", - left_title="Moy.", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_coef = tb.BottomRow( - table, - "coef", - left_title="Coef.", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_ects = tb.BottomRow( - table, - "ects", - left_title="ECTS", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - row_apo = tb.BottomRow( - table, - "apo", - left_title="Code Apogée", - left_title_col_ids=["prenom", "nom_short"], - category="bottom_infos", - classes=["bottom_info"], - ) - - # --- ECTS - # titre (à gauche) sur 2 colonnes pour s'adapter à l'affichage des noms/prenoms - for ue in ues: - col_id = f"moy_ue_{ue.id}" - row_ects.add_cell(col_id, None, ue.ects) - # ajoute cell UE vides sur ligne coef pour borders verticales - # XXX TODO classes dans table sur colonne ajoutées à tous les TD - row_coef.add_cell(col_id, None, "") - row_ects.add_cell( - "moy_gen", - None, - sum([ue.ects or 0 for ue in ues if ue.type != UE_SPORT]), - ) - # --- MIN, MAX, MOY, APO - row_min.add_cell("moy_gen", None, table.fmt_note(self.etud_moy_gen.min())) - row_max.add_cell("moy_gen", None, table.fmt_note(self.etud_moy_gen.max())) - row_moy.add_cell("moy_gen", None, table.fmt_note(self.etud_moy_gen.mean())) - - for ue in ues: - col_id = f"moy_ue_{ue.id}" - row_min.add_cell( - col_id, None, table.fmt_note(self.etud_moy_ue[ue.id].min()) - ) - row_max.add_cell( - col_id, None, table.fmt_note(self.etud_moy_ue[ue.id].max()) - ) - row_moy.add_cell( - col_id, None, table.fmt_note(self.etud_moy_ue[ue.id].mean()) - ) - row_apo.add_cell(col_id, None, ue.code_apogee or "") - - for modimpl in self.formsemestre.modimpls_sorted: - if (modimpl.id, ue.id) in table.modimpl_ue_ids: - col_id = f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}" - if self.is_apc: - coef = self.modimpl_coefs_df[modimpl.id][ue.id] - else: - coef = modimpl.module.coefficient or 0 - row_coef.add_cell( - col_id, - None, - table.fmt_note(coef), - group=f"col_ue_{ue.id}_modules", - ) - notes = self.modimpl_notes(modimpl.id, ue.id) - if np.isnan(notes).all(): - # aucune note valide - row_min.add_cell(col_id, None, np.nan) - row_max.add_cell(col_id, None, np.nan) - moy = np.nan - else: - row_min.add_cell(col_id, None, table.fmt_note(np.nanmin(notes))) - row_max.add_cell(col_id, None, table.fmt_note(np.nanmax(notes))) - moy = np.nanmean(notes) - row_moy.add_cell( - col_id, - None, - table.fmt_note(moy), - # aucune note dans ce module ? - classes=["col_empty" if np.isnan(moy) else ""], - ) - row_apo.add_cell(col_id, None, modimpl.module.code_apogee or "") - def _recap_etud_groups_infos( self, etudid: int, row: dict, titles: dict ): # XXX non utilisé diff --git a/sco_version.py b/sco_version.py index 16ec61569d..f450245ee7 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.36" +SCOVERSION = "9.4.37" SCONAME = "ScoDoc"