diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py index 5a549132b..416a0d0ee 100644 --- a/app/comp/bonus_spo.py +++ b/app/comp/bonus_spo.py @@ -418,17 +418,46 @@ class BonusAmiens(BonusSportAdditif): class BonusBethune(BonusSportMultiplicatif): - """Calcul bonus modules optionnels (sport), règle IUT de Béthune. - - Les points au dessus de la moyenne de 10 apportent un bonus pour le semestre. - Ce bonus est égal au nombre de points divisé par 200 et multiplié par la - moyenne générale du semestre de l'étudiant. + """ + Calcul bonus modules optionnels (sport, culture), règle IUT de Béthune. +

+ Pour le BUT : + La note de sport est sur 20, et on calcule une bonification (en %) + qui va s'appliquer à la moyenne de chaque UE du semestre en appliquant + la formule : bonification (en %) = max(note-10, 0)*(1/500). +

+ La bonification ne s'applique que si la note est supérieure à 10. +

+ (Une note de 10 donne donc 0% de bonif, + 1 point au dessus de 10 augmente la moyenne des UE de 0.2%) +

+

+ Pour le DUT/LP : + La note de sport est sur 20, et on calcule une bonification (en %) + qui va s'appliquer à la moyenne générale du semestre en appliquant + la formule : bonification (en %) = max(note-10, 0)*(1/200). +

+ La bonification ne s'applique que si la note est supérieure à 10. +

+ (Une note de 10 donne donc 0% de bonif, + 1 point au dessus de 10 augmente la moyenne des UE de 0.5%) +

""" name = "bonus_iutbethune" displayed_name = "IUT de Béthune" - seuil_moy_gen = 10.0 - amplitude = 0.005 + seuil_moy_gen = 10.0 # points comptés au dessus de 10. + + def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): + """calcul du bonus""" + if self.formsemestre.formation.is_apc(): + self.amplitude = 0.002 + else: + self.amplitude = 0.005 + + return super().compute_bonus( + sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan + ) class BonusBezier(BonusSportAdditif): diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index eea357e8f..13829a392 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -92,6 +92,8 @@ class ModuleImplResults: ou NaN si les évaluations (dans lesquelles l'étudiant a des notes) ne donnent pas de coef vers cette UE. """ + self.evals_etudids_sans_note = {} + """dict: evaluation_id : set des etudids non notés dans cette eval, sans les démissions.""" self.load_notes() self.etuds_use_session2 = pd.Series(False, index=self.evals_notes.index) """1 bool par etud, indique si sa moyenne de module vient de la session2""" @@ -142,12 +144,13 @@ class ModuleImplResults: # ou évaluation déclarée "à prise en compte immédiate" # Les évaluations de rattrapage et 2eme session sont toujours incomplètes # car on calcule leur moyenne à part. + etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem. is_complete = (evaluation.evaluation_type == scu.EVALUATION_NORMALE) and ( - evaluation.publish_incomplete - or (not (inscrits_module - set(eval_df.index))) + evaluation.publish_incomplete or (not etudids_sans_note) ) self.evaluations_completes.append(is_complete) self.evaluations_completes_dict[evaluation.id] = is_complete + self.evals_etudids_sans_note[evaluation.id] = etudids_sans_note # NULL en base => ABS (= -999) eval_df.fillna(scu.NOTES_ABSENCE, inplace=True) @@ -193,7 +196,9 @@ class ModuleImplResults: return eval_df def _etudids(self): - """L'index du dataframe est la liste de tous les étudiants inscrits au semestre""" + """L'index du dataframe est la liste de tous les étudiants inscrits au semestre + (incluant les DEM et DEF) + """ return [ inscr.etudid for inscr in ModuleImpl.query.get( diff --git a/app/comp/res_common.py b/app/comp/res_common.py index 6e37a8c2d..7bc199ead 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -18,7 +18,7 @@ from app.auth.models import User from app.comp.res_cache import ResultatsCache from app.comp import res_sem from app.comp.moy_mod import ModuleImplResults -from app.models import FormSemestre, FormSemestreUECoef +from app.models import FormSemestre, FormSemestreUECoef, formsemestre from app.models import Identite from app.models import ModuleImpl, ModuleImplInscription from app.models.ues import UniteEns @@ -388,7 +388,9 @@ class ResultatsSemestre(ResultatsCache): # --- TABLEAU RECAP - def get_table_recap(self, convert_values=False, include_evaluations=False): + def get_table_recap( + self, convert_values=False, include_evaluations=False, modejury=False + ): """Result: tuple avec - rows: liste de dicts { column_id : value } - titles: { column_id : title } @@ -538,21 +540,25 @@ class ResultatsSemestre(ResultatsCache): titles_bot[ f"_{col_id}_target_attrs" ] = f"""title="{ue.titre} S{ue.semestre_idx or '?'}" """ + if modejury: + # 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 "" - val_fmt = fmt_note(val) + val_fmt = val_fmt_html = fmt_note(val) if val: - val_fmt = f'{val_fmt}' + val_fmt_html = f'{val_fmt}' idx = add_cell( row, f"bonus_ue_{ue.id}", f"Bonus {ue.acronyme}", - val_fmt, + val_fmt_html, "col_ue_bonus", idx, ) + row[f"_bonus_ue_{ue.id}_xls"] = val_fmt # Les moyennes des modules (ou ressources et SAÉs) dans cette UE idx_malus = idx # place pour colonne malus à gauche des modules idx += 1 @@ -581,20 +587,21 @@ class ResultatsSemestre(ResultatsCache): col_id = ( f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}" ) - val_fmt = fmt_note(val) + val_fmt = val_fmt_html = fmt_note(val) if modimpl.module.module_type == scu.ModuleType.MALUS: - val_fmt = ( + val_fmt_html = ( (scu.EMO_RED_TRIANGLE_DOWN + val_fmt) if val else "" ) idx = add_cell( row, col_id, modimpl.module.code, - val_fmt, + val_fmt_html, # class col_res mod_ue_123 f"col_{modimpl.module.type_abbrv()} mod_ue_{ue.id}", idx, ) + row[f"_{col_id}_xls"] = val_fmt if modimpl.module.module_type == scu.ModuleType.MALUS: titles[f"_{col_id}_col_order"] = idx_malus titles_bot[f"_{col_id}_target"] = url_for( @@ -611,22 +618,37 @@ class ResultatsSemestre(ResultatsCache): f"_{col_id}_target_attrs" ] = f""" title="{modimpl.module.titre} ({nom_resp})" """ modimpl_ids.add(modimpl.id) - ue_valid_txt = f"{nb_ues_validables}/{len(ues_sans_bonus)}" + ue_valid_txt = ( + ue_valid_txt_html + ) = f"{nb_ues_validables}/{len(ues_sans_bonus)}" if nb_ues_warning: - ue_valid_txt += " " + scu.EMO_WARNING + ue_valid_txt_html += " " + scu.EMO_WARNING add_cell( row, "ues_validables", "UEs", - ue_valid_txt, + ue_valid_txt_html, "col_ue col_ues_validables", 29, # juste avant moy. gen. ) + row["_ues_validables_xls"] = ue_valid_txt if nb_ues_warning: row["_ues_validables_class"] += " moy_ue_warning" elif nb_ues_validables < len(ues_sans_bonus): row["_ues_validables_class"] += " moy_inf" row["_ues_validables_order"] = nb_ues_validables # pour tri + if modejury: + idx = add_cell( + row, + "jury_link", + "", + f"""saisir décision""", + "col_jury_link", + 1000, + ) rows.append(row) self._recap_add_partitions(rows, titles) @@ -658,7 +680,7 @@ class ResultatsSemestre(ResultatsCache): row["_moy_gen_class"] = "col_moy_gen" # titre de la ligne: row["prenom"] = row["nom_short"] = ( - row.get(f"_title", "") or bottom_line.capitalize() + row.get("_title", "") or bottom_line.capitalize() ) row["_tr_class"] = bottom_line.lower() + ( (" " + row["_tr_class"]) if "_tr_class" in row else "" @@ -840,20 +862,27 @@ class ResultatsSemestre(ResultatsCache): "_tr_class": "bottom_info", "_title": "Description évaluation", } - first = True + first_eval = True + index_col = 9000 # à droite for modimpl in self.formsemestre.modimpls_sorted: evals = self.modimpls_results[modimpl.id].get_evaluations_completes(modimpl) eval_index = len(evals) - 1 inscrits = {i.etudid for i in modimpl.inscriptions} - klass = "evaluation first" if first else "evaluation" - first = False - for i, e in enumerate(evals): + first_eval_of_mod = True + for e in evals: cid = f"eval_{e.id}" titles[ cid ] = f'{modimpl.module.code} {eval_index} {e.jour.isoformat() if e.jour else ""}' + klass = "evaluation" + if first_eval: + klass += " first" + elif first_eval_of_mod: + klass += " first_of_mod" titles[f"_{cid}_class"] = klass - titles[f"_{cid}_col_order"] = 9000 + i # à droite + first_eval_of_mod = first_eval = False + titles[f"_{cid}_col_order"] = index_col + index_col += 1 eval_index -= 1 notes_db = sco_evaluation_db.do_evaluation_get_all_notes( e.evaluation_id @@ -867,8 +896,21 @@ class ResultatsSemestre(ResultatsCache): # Note manquante mais prise en compte immédiate: affiche ATT val = scu.NOTES_ATTENTE row[cid] = scu.fmt_note(val) - row[f"_{cid}_class"] = klass + row[f"_{cid}_class"] = klass + { + "ABS": " abs", + "ATT": " att", + "EXC": " exc", + }.get(row[cid], "") + else: + row[cid] = "ni" + row[f"_{cid}_class"] = klass + " non_inscrit" + bottom_infos["coef"][cid] = e.coefficient bottom_infos["min"][cid] = "0" bottom_infos["max"][cid] = scu.fmt_note(e.note_max) bottom_infos["descr_evaluation"][cid] = e.description or "" + bottom_infos["descr_evaluation"][f"_{cid}_target"] = url_for( + "notes.evaluation_listenotes", + scodoc_dept=g.scodoc_dept, + evaluation_id=e.id, + ) diff --git a/app/models/groups.py b/app/models/groups.py index f6452cf7c..9cf5f2364 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -74,6 +74,10 @@ class GroupDescr(db.Model): f"""<{self.__class__.__name__} {self.id} "{self.group_name or '(tous)'}">""" ) + def get_nom_with_part(self) -> str: + "Nom avec partition: 'TD A'" + return f"{self.partition.partition_name or ''} {self.group_name or '-'}" + group_membership = db.Table( "group_membership", diff --git a/app/models/moduleimpls.py b/app/models/moduleimpls.py index 292ec8ffd..7574ed7e8 100644 --- a/app/models/moduleimpls.py +++ b/app/models/moduleimpls.py @@ -9,7 +9,6 @@ from app.comp import df_cache from app.models.etudiants import Identite from app.models.modules import Module -import app.scodoc.notesdb as ndb from app.scodoc import sco_utils as scu diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py index 7f5531c6f..2136ee841 100644 --- a/app/scodoc/gen_tables.py +++ b/app/scodoc/gen_tables.py @@ -209,7 +209,8 @@ class GenTable(object): omit_hidden_lines=False, pdf_mode=False, # apply special pdf reportlab processing pdf_style_list=[], # modified: list of platypus table style commands - ): + xls_mode=False, # get xls content if available + ) -> list: "table data as a list of lists (rows)" T = [] line_num = 0 # line number in input data @@ -237,9 +238,14 @@ class GenTable(object): # if colspan_count > 0: # continue # skip cells after a span if pdf_mode: - content = row.get(f"_{cid}_pdf", "") or row.get(cid, "") or "" + content = row.get(f"_{cid}_pdf", False) or row.get(cid, "") + elif xls_mode: + content = row.get(f"_{cid}_xls", False) or row.get(cid, "") else: - content = row.get(cid, "") or "" # nota: None converted to '' + content = row.get(cid, "") + # Convert None to empty string "" + content = "" if content is None else content + colspan = row.get("_%s_colspan" % cid, 0) if colspan > 1: pdf_style_list.append( @@ -299,7 +305,7 @@ class GenTable(object): return self.xml() elif format == "json": return self.json() - raise ValueError("GenTable: invalid format: %s" % format) + raise ValueError(f"GenTable: invalid format: {format}") def _gen_html_row(self, row, line_num=0, elem="td", css_classes=""): "row is a dict, returns a string ..." @@ -479,23 +485,23 @@ class GenTable(object): def excel(self, wb=None): """Simple Excel representation of the table""" if wb is None: - ses = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb) + sheet = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb) else: - ses = wb.create_sheet(sheet_name=self.xls_sheet_name) - ses.rows += self.xls_before_table + sheet = wb.create_sheet(sheet_name=self.xls_sheet_name) + sheet.rows += self.xls_before_table style_bold = sco_excel.excel_make_style(bold=True) style_base = sco_excel.excel_make_style() - ses.append_row(ses.make_row(self.get_titles_list(), style_bold)) - for line in self.get_data_list(): - ses.append_row(ses.make_row(line, style_base)) + sheet.append_row(sheet.make_row(self.get_titles_list(), style_bold)) + for line in self.get_data_list(xls_mode=True): + sheet.append_row(sheet.make_row(line, style_base)) if self.caption: - ses.append_blank_row() # empty line - ses.append_single_cell_row(self.caption, style_base) + sheet.append_blank_row() # empty line + sheet.append_single_cell_row(self.caption, style_base) if self.origin: - ses.append_blank_row() # empty line - ses.append_single_cell_row(self.origin, style_base) + sheet.append_blank_row() # empty line + sheet.append_single_cell_row(self.origin, style_base) if wb is None: - return ses.generate() + return sheet.generate() def text(self): "raw text representation of the table" diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py index 53efbfc95..c700c1b80 100644 --- a/app/scodoc/html_sidebar.py +++ b/app/scodoc/html_sidebar.py @@ -86,9 +86,9 @@ def sidebar(): f"""