Refactoring + nouvelle table recap pour classiques (WIP)

This commit is contained in:
Emmanuel Viennet 2022-03-27 22:25:00 +02:00
parent 13d4da3fea
commit 9596c8cf6f
36 changed files with 343 additions and 743 deletions

View File

@ -17,7 +17,7 @@ from app import models
def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame: def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
"""Charge la matrice des inscriptions aux modules du semestre """Charge la matrice des inscriptions aux modules du semestre
rows: etudid (inscrits au semestre, avec DEM et DEF) rows: etudid (inscrits au semestre, avec DEM et DEF)
columns: moduleimpl_id (en chaîne) columns: moduleimpl_id
value: bool (0/1 inscrit ou pas) value: bool (0/1 inscrit ou pas)
""" """
# méthode la moins lente: une requete par module, merge les dataframes # méthode la moins lente: une requete par module, merge les dataframes

View File

@ -10,21 +10,15 @@ import time
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from flask import g, url_for
from app import log from app import log
from app.comp import moy_ue, moy_sem, inscr_mod from app.comp import moy_ue, moy_sem, inscr_mod
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.comp.bonus_spo import BonusSport from app.comp.bonus_spo import BonusSport
from app.models import ScoDocSiteConfig from app.models import ScoDocSiteConfig
from app.models.etudiants import Identite
from app.models.moduleimpls import ModuleImpl from app.models.moduleimpls import ModuleImpl
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.scodoc import sco_codes_parcours
from app.scodoc.sco_codes_parcours import UE_SPORT from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc import sco_groups
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_users
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -39,7 +33,6 @@ class ResultatsSemestreBUT(NotesTableCompat):
def __init__(self, formsemestre): def __init__(self, formsemestre):
super().__init__(formsemestre) super().__init__(formsemestre)
self.modimpl_coefs_df = None
"""DataFrame, row UEs(sans bonus), cols modimplid, value coef""" """DataFrame, row UEs(sans bonus), cols modimplid, value coef"""
self.sem_cube = None self.sem_cube = None
"""ndarray (etuds x modimpl x ue)""" """ndarray (etuds x modimpl x ue)"""
@ -174,265 +167,11 @@ class ResultatsSemestreBUT(NotesTableCompat):
] ]
return modimpls return modimpls
def get_table_moyennes_triees(self, convert_values=False) -> list: def modimpl_notes(self, modimpl_id: int, ue_id: int) -> np.ndarray:
"""Result: tuple avec """Les notes moyennes des étudiants du sem. à ce modimpl dans cette ue.
- rows: liste de dicts { column_id : value } Utile pour stats bottom tableau recap.
- titles: { column_id : title } Résultat: 1d array of float
- columns_ids: (liste des id de colonnes)
. 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_<ue_id>, ..., les moyennes d'UE
moy_res_<modimpl_id>_<ue_id>, ... les moyennes de ressources dans l'UE
moy_sae_<modimpl_id>_<ue_id>, ... les moyennes de SAE dans l'UE
On ajoute aussi des attributs:
- pour les lignes:
_css_row_class (inutilisé pour le monent)
_<column_id>_class classe css:
- 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_<ue_id>
_<column_id>_order : clé de tri
""" """
i = self.modimpl_coefs_df.columns.get_loc(modimpl_id)
def fmt_note(x): j = self.modimpl_coefs_df.index.get_loc(ue_id)
return scu.fmt_note(x) if convert_values else x return self.sem_cube[:, i, j]
barre_moy = (
self.formsemestre.formation.get_parcours().BARRE_MOY - scu.NOTES_TOLERANCE
)
barre_valid_ue = self.formsemestre.formation.get_parcours().NOTES_BARRE_VALID_UE
NO_NOTE = "-" # contenu des cellules sans notes
rows = []
titles = {"rang": "Rg"} # column_id : title
# les titres en footer: les mêmes, mais avec des bulles et liens:
titles_bot = {}
def add_cell(
row: dict, col_id: str, title: str, content: str, classes: str = ""
):
"Add a row to our table. classes is a list of css class names"
row[col_id] = content
if classes:
row[f"_{col_id}_class"] = classes
if not col_id in titles:
titles[col_id] = title
if classes:
titles[f"_{col_id}_class"] = classes
etuds_inscriptions = self.formsemestre.etuds_inscriptions
ues = self.formsemestre.query_ues(with_sport=True) # avec bonus
modimpl_ids = set() # modimpl effectivement présents dans la table
for etudid in etuds_inscriptions:
etud = Identite.query.get(etudid)
row = {"etudid": etudid}
# --- Rang
add_cell(row, "rang", "Rg", self.etud_moy_gen_ranks[etudid], "rang")
row["_rang_order"] = f"{self.etud_moy_gen_ranks_int[etudid]:05d}"
# --- Identité étudiant
add_cell(row, "civilite_str", "Civ.", etud.civilite_str, "identite_detail")
add_cell(row, "nom_disp", "Nom", etud.nom_disp(), "identite_detail")
add_cell(row, "prenom", "Prénom", etud.prenom, "identite_detail")
add_cell(row, "nom_short", "Nom", etud.nom_short, "identite_court")
row["_nom_short_target"] = url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=self.formsemestre.id,
etudid=etudid,
)
row["_nom_short_target_attrs"] = f'class="etudinfo" id="{etudid}"'
row["_nom_disp_target"] = row["_nom_short_target"]
row["_nom_disp_target_attrs"] = row["_nom_short_target_attrs"]
self._recap_etud_groups_infos(etudid, row, titles)
# --- Moyenne générale
moy_gen = self.etud_moy_gen.get(etudid, False)
note_class = ""
if moy_gen is False:
moy_gen = NO_NOTE
elif isinstance(moy_gen, float) and moy_gen < barre_moy:
note_class = " moy_inf"
add_cell(
row,
"moy_gen",
"Moy",
fmt_note(moy_gen),
"col_moy_gen" + note_class,
)
titles_bot["_moy_gen_target_attrs"] = (
'title="moyenne indicative"' if self.is_apc else ""
)
# --- Moyenne d'UE
for ue in [ue for ue in ues if ue.type != UE_SPORT]:
ue_status = self.get_etud_ue_status(etudid, ue.id)
if ue_status is not None:
col_id = f"moy_ue_{ue.id}"
val = ue_status["moy"]
note_class = ""
if isinstance(val, float):
if val < barre_moy:
note_class = " moy_inf"
elif val >= barre_valid_ue:
note_class = " moy_ue_valid"
add_cell(
row,
col_id,
ue.acronyme,
fmt_note(val),
"col_ue" + note_class,
)
titles_bot[
f"_{col_id}_target_attrs"
] = f"""title="{ue.titre} S{ue.semestre_idx or '?'}" """
# Les moyennes des ressources et SAÉs dans cette UE
for modimpl in self.modimpls_in_ue(ue.id, etudid, with_bonus=False):
if ue_status["is_capitalized"]:
val = "-c-"
else:
modimpl_results = self.modimpls_results.get(modimpl.id)
if modimpl_results: # pas bonus
moys_vers_ue = modimpl_results.etuds_moy_module.get(
ue.id
)
val = (
moys_vers_ue.get(etudid, "?")
if moys_vers_ue is not None
else ""
)
else:
val = ""
col_id = (
f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}"
)
add_cell(
row,
col_id,
modimpl.module.code,
fmt_note(val),
# class col_res mod_ue_123
f"col_{modimpl.module.type_abbrv()} mod_ue_{ue.id}",
)
titles_bot[f"_{col_id}_target"] = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
)
titles_bot[
f"_{col_id}_target_attrs"
] = f"""
title="{modimpl.module.titre}
({sco_users.user_info(modimpl.responsable_id)['nomcomplet']})" """
modimpl_ids.add(modimpl.id)
rows.append(row)
# tri par rang croissant
rows.sort(key=lambda e: e["_rang_order"])
# INFOS POUR FOOTER
bottom_infos = self._recap_bottom_infos(
[ue for ue in ues if ue.type != UE_SPORT], modimpl_ids, fmt_note
)
# --- TABLE FOOTER: ECTS, moyennes, min, max...
footer_rows = []
for bottom_line in bottom_infos:
row = bottom_infos[bottom_line]
# Cases vides à styler:
row["moy_gen"] = row.get("moy_gen", "")
row["_moy_gen_class"] = "col_moy_gen"
# titre de la ligne:
row["prenom"] = row["nom_short"] = bottom_line.capitalize()
row["_tr_class"] = bottom_line.lower()
footer_rows.append(row)
titles_bot.update(titles)
footer_rows.append(titles_bot)
return (
rows,
footer_rows,
titles,
[title for title in titles if not title.startswith("_")],
)
def _recap_bottom_infos(self, ues, modimpl_ids: set, fmt_note) -> dict:
"""Les informations à mettre en bas de la table: min, max, moy, ECTS"""
bottom_infos = { # { key : row } avec key = min, max, moy, coef
"min": {},
"max": {},
"moy": {},
"coef": {},
}
# --- ECTS
row = {}
for ue in ues:
row[f"moy_ue_{ue.id}"] = ue.ects
row[f"_moy_ue_{ue.id}_class"] = "col_ue"
# style cases vides pour borders verticales
bottom_infos["coef"][f"moy_ue_{ue.id}"] = ""
bottom_infos["coef"][f"_moy_ue_{ue.id}_class"] = "col_ue"
row["moy_gen"] = sum([ue.ects or 0 for ue in ues if ue.type != UE_SPORT])
row["_moy_gen_class"] = "col_moy_gen"
bottom_infos["ects"] = row
# --- MIN, MAX, MOY
row_min, row_max, row_moy = {}, {}, {}
row_min["moy_gen"] = fmt_note(self.etud_moy_gen.min())
row_max["moy_gen"] = fmt_note(self.etud_moy_gen.max())
row_moy["moy_gen"] = fmt_note(self.etud_moy_gen.mean())
for ue in [ue for ue in ues if ue.type != UE_SPORT]:
col_id = f"moy_ue_{ue.id}"
row_min[col_id] = fmt_note(self.etud_moy_ue[ue.id].min())
row_max[col_id] = fmt_note(self.etud_moy_ue[ue.id].max())
row_moy[col_id] = fmt_note(self.etud_moy_ue[ue.id].mean())
row_min[f"_{col_id}_class"] = "col_ue"
row_max[f"_{col_id}_class"] = "col_ue"
row_moy[f"_{col_id}_class"] = "col_ue"
for modimpl in self.formsemestre.modimpls_sorted:
if modimpl.id in modimpl_ids:
col_id = f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}"
bottom_infos["coef"][col_id] = fmt_note(
self.modimpl_coefs_df[modimpl.id][ue.id]
)
i = self.modimpl_coefs_df.columns.get_loc(modimpl.id)
j = self.modimpl_coefs_df.index.get_loc(ue.id)
notes = self.sem_cube[:, i, j]
row_min[col_id] = fmt_note(np.nanmin(notes))
row_max[col_id] = fmt_note(np.nanmax(notes))
row_moy[col_id] = fmt_note(np.nanmean(notes))
bottom_infos["min"] = row_min
bottom_infos["max"] = row_max
bottom_infos["moy"] = row_moy
return bottom_infos
def _recap_etud_groups_infos(self, etudid: int, row: dict, titles: dict):
"""Ajoute à row les colonnes sur les groupes pour etud"""
# XXX à remonter dans res_common
# dec = self.get_etud_decision_sem(etudid)
# if dec:
# codes_nb[dec["code"]] += 1
row_class = ""
etud_etat = self.get_etud_etat(etudid)
if etud_etat == sco_codes_parcours.DEM:
gr_name = "Dém."
row_class = "dem"
elif etud_etat == sco_codes_parcours.DEF:
gr_name = "Déf."
row_class = "def"
else:
# XXX probablement à revoir pour utiliser données cachées,
# via get_etud_groups_in_partition ou autre
group = sco_groups.get_etud_main_group(etudid, self.formsemestre.id)
gr_name = group["group_name"] or ""
row["group"] = gr_name
row["_group_class"] = "group"
if row_class:
row["_tr_class"] = " ".join([row.get("_tr_class", ""), row_class])
titles["group"] = "Gr"

View File

@ -16,7 +16,7 @@ from flask import g, url_for
from app import db from app import db
from app import log from app import log
from app.comp import moy_mat, moy_mod, moy_sem, moy_ue, inscr_mod from app.comp import moy_mat, moy_mod, moy_sem, moy_ue, inscr_mod
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.comp.bonus_spo import BonusSport from app.comp.bonus_spo import BonusSport
from app.models import ScoDocSiteConfig from app.models import ScoDocSiteConfig
from app.models.etudiants import Identite from app.models.etudiants import Identite
@ -40,6 +40,8 @@ class ResultatsSemestreClassic(NotesTableCompat):
def __init__(self, formsemestre): def __init__(self, formsemestre):
super().__init__(formsemestre) super().__init__(formsemestre)
self.sem_matrix: np.ndarray = None
"sem_matrix : 2d-array (etuds x modimpls)"
if not self.load_cached(): if not self.load_cached():
t0 = time.time() t0 = time.time()
@ -189,6 +191,19 @@ class ResultatsSemestreClassic(NotesTableCompat):
), ),
} }
def modimpl_notes(
self,
modimpl_id: int,
ue_id: int = None,
) -> np.ndarray:
"""Les notes moyennes des étudiants du sem. à ce modimpl dans cette ue.
Utile pour stats bottom tableau recap.
ue_id n'est pas utilisé ici (formations classiques)
Résultat: 1d array of float
"""
i = self.modimpl_idx[modimpl_id]
return self.sem_matrix[:, i]
def compute_moyennes_matieres(self): def compute_moyennes_matieres(self):
"""Calcul les moyennes par matière. Doit être appelée au besoin, en fin de compute.""" """Calcul les moyennes par matière. Doit être appelée au besoin, en fin de compute."""
self.moyennes_matieres = moy_mat.compute_mat_moys_classic( self.moyennes_matieres = moy_mat.compute_mat_moys_classic(

View File

@ -9,11 +9,8 @@ from functools import cached_property
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from flask import g, flash, url_for from flask import g, url_for
from app import log
from app.comp.aux_stats import StatsMoyenne
from app.comp import moy_sem
from app.comp.res_cache import ResultatsCache from app.comp.res_cache import ResultatsCache
from app.comp import res_sem from app.comp import res_sem
from app.comp.moy_mod import ModuleImplResults from app.comp.moy_mod import ModuleImplResults
@ -21,10 +18,13 @@ from app.models import FormSemestre, FormSemestreUECoef
from app.models import Identite from app.models import Identite
from app.models import ModuleImpl, ModuleImplInscription from app.models import ModuleImpl, ModuleImplInscription
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.scodoc import sco_utils as scu
from app.scodoc.sco_cache import ResultatsSemestreCache from app.scodoc.sco_cache import ResultatsSemestreCache
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF from app.scodoc.sco_codes_parcours import UE_SPORT, DEF, DEM
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import sco_groups
from app.scodoc import sco_users
from app.scodoc import sco_utils as scu
# Il faut bien distinguer # Il faut bien distinguer
# - ce qui est caché de façon persistente (via redis): # - ce qui est caché de façon persistente (via redis):
@ -36,6 +36,10 @@ from app.scodoc.sco_exceptions import ScoValueError
# qui sont notamment les attributs décorés par `@cached_property`` # qui sont notamment les attributs décorés par `@cached_property``
# #
class ResultatsSemestre(ResultatsCache): class ResultatsSemestre(ResultatsCache):
"""Les résultats (notes, ...) d'un formsemestre
Classe commune à toutes les formations (classiques, BUT)
"""
_cached_attrs = ( _cached_attrs = (
"etud_moy_gen_ranks", "etud_moy_gen_ranks",
"etud_moy_gen", "etud_moy_gen",
@ -54,13 +58,18 @@ class ResultatsSemestre(ResultatsCache):
# ResultatsSemestreBUT ou ResultatsSemestreClassic # ResultatsSemestreBUT ou ResultatsSemestreClassic
self.etud_moy_ue = {} self.etud_moy_ue = {}
"etud_moy_ue: DataFrame columns UE, rows etudid" "etud_moy_ue: DataFrame columns UE, rows etudid"
self.etud_moy_gen = {} self.etud_moy_gen: pd.Series = None
self.etud_moy_gen_ranks = {} self.etud_moy_gen_ranks = {}
self.etud_moy_gen_ranks_int = {} self.etud_moy_gen_ranks_int = {}
self.modimpl_inscr_df: pd.DataFrame = None
"Inscriptions: row etudid, col modimlpl_id"
self.modimpls_results: ModuleImplResults = None self.modimpls_results: ModuleImplResults = None
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }" "Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
self.etud_coef_ue_df = None self.etud_coef_ue_df = None
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)""" """coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
self.modimpl_coefs_df: pd.DataFrame = None
"""Coefs APC: rows = UEs (sans bonus), columns = modimpl, value = coef."""
self.validations = None self.validations = None
self.moyennes_matieres = {} self.moyennes_matieres = {}
"""Moyennes de matières, si calculées. { matiere_id : Series, index etudid }""" """Moyennes de matières, si calculées. { matiere_id : Series, index etudid }"""
@ -151,17 +160,24 @@ class ResultatsSemestre(ResultatsCache):
ues = sorted(list(ues), key=lambda x: x.numero or 0) ues = sorted(list(ues), key=lambda x: x.numero or 0)
return ues return ues
def modimpls_in_ue(self, ue_id, etudid) -> list[ModuleImpl]: def modimpls_in_ue(self, ue_id, etudid, with_bonus=True) -> list[ModuleImpl]:
"""Liste des modimpl de cette UE auxquels l'étudiant est inscrit. """Liste des modimpl de cette UE auxquels l'étudiant est inscrit.
Utile en formations classiques, surchargée pour le BUT. Utile en formations classiques, surchargée pour le BUT.
Inclus modules bonus le cas échéant.
""" """
# sert pour l'affichage ou non de l'UE sur le bulletin # sert pour l'affichage ou non de l'UE sur le bulletin
return [ # Méthode surchargée en BUT
modimpls = [
modimpl modimpl
for modimpl in self.formsemestre.modimpls_sorted for modimpl in self.formsemestre.modimpls_sorted
if modimpl.module.ue.id == ue_id if modimpl.module.ue.id == ue_id
and self.modimpl_inscr_df[modimpl.id][etudid] and self.modimpl_inscr_df[modimpl.id][etudid]
] ]
if not with_bonus:
return [
modimpl for modimpl in modimpls if modimpl.module.ue.type != UE_SPORT
]
return modimpls
@cached_property @cached_property
def ue_au_dessus(self, seuil=10.0) -> pd.DataFrame: def ue_au_dessus(self, seuil=10.0) -> pd.DataFrame:
@ -341,437 +357,275 @@ class ResultatsSemestre(ResultatsCache):
# somme des coefs des modules de l'UE auxquels il est inscrit # somme des coefs des modules de l'UE auxquels il est inscrit
return self.compute_etud_ue_coef(etudid, ue) return self.compute_etud_ue_coef(etudid, ue)
# --- TABLEAU RECAP
# Pour raccorder le code des anciens codes qui attendent une NoteTable def get_table_recap(self, convert_values=False):
class NotesTableCompat(ResultatsSemestre): """Result: tuple avec
"""Implementation partielle de NotesTable WIP TODO - rows: liste de dicts { column_id : value }
- titles: { column_id : title }
- columns_ids: (liste des id de colonnes)
Les méthodes définies dans cette classe sont . Si convert_values, transforme les notes en chaines ("12.34").
pour conserver la compatibilité abvec les codes anciens et Les colonnes générées sont:
il n'est pas recommandé de les utiliser dans de nouveaux etudid
développements (API malcommode et peu efficace). rang : rang indicatif (basé sur moy gen)
""" moy_gen : moy gen indicative
moy_ue_<ue_id>, ..., les moyennes d'UE
moy_res_<modimpl_id>_<ue_id>, ... les moyennes de ressources dans l'UE
moy_sae_<modimpl_id>_<ue_id>, ... les moyennes de SAE dans l'UE
_cached_attrs = ResultatsSemestre._cached_attrs + ( On ajoute aussi des attributs:
"bonus", - pour les lignes:
"bonus_ues", _css_row_class (inutilisé pour le monent)
"malus", _<column_id>_class classe css:
"etud_moy_gen_ranks", - la moyenne générale a la classe col_moy_gen
"etud_moy_gen_ranks_int", - les colonnes SAE ont la classe col_sae
"ue_rangs", - 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_<ue_id>
def __init__(self, formsemestre: FormSemestre): _<column_id>_order : clé de tri
super().__init__(formsemestre)
nb_etuds = len(self.etuds)
self.bonus = None # virtuel
self.bonus_ues = None # virtuel
self.ue_rangs = {u.id: (None, nb_etuds) for u in self.ues}
self.mod_rangs = None # sera surchargé en Classic, mais pas en APC
"""{ modimpl_id : (rangs, effectif) }"""
self.moy_min = "NA"
self.moy_max = "NA"
self.moy_moy = "NA"
self.expr_diagnostics = ""
self.parcours = self.formsemestre.formation.get_parcours()
def get_inscrits(self, include_demdef=True, order_by=False) -> list[Identite]:
"""Liste des étudiants inscrits
order_by = False|'nom'|'moy' tri sur nom ou sur moyenne générale (indicative)
Note: pour récupérer les etudids des inscrits, non triés, il est plus efficace
d'utiliser `[ ins.etudid for ins in nt.formsemestre.inscriptions ]`
""" """
etuds = self.formsemestre.get_inscrits(
include_demdef=include_demdef, order=(order_by == "nom")
)
if order_by == "moy":
etuds.sort(
key=lambda e: (
self.etud_moy_gen_ranks_int.get(e.id, 100000),
e.sort_key,
)
)
return etuds
def get_etudids(self) -> list[int]: if convert_values:
"""(deprecated) fmt_note = scu.fmt_note
Liste des etudids inscrits, incluant les démissionnaires.
triée par ordre alphabetique de NOM
(à éviter: renvoie les etudids, mais est moins efficace que get_inscrits)
"""
# Note: pour avoir les inscrits non triés,
# utiliser [ ins.etudid for ins in self.formsemestre.inscriptions ]
return [x["etudid"] for x in self.inscrlist]
@cached_property
def sem(self) -> dict:
"""le formsemestre, comme un gros et gras dict (nt.sem)"""
return self.formsemestre.get_infos_dict()
@cached_property
def inscrlist(self) -> list[dict]: # utilisé par PE
"""Liste des inscrits au semestre (avec DEM et DEF),
sous forme de dict etud,
classée dans l'ordre alphabétique de noms.
"""
etuds = self.formsemestre.get_inscrits(include_demdef=True, order=True)
return [e.to_dict_scodoc7() for e in etuds]
@cached_property
def stats_moy_gen(self):
"""Stats (moy/min/max) sur la moyenne générale"""
return StatsMoyenne(self.etud_moy_gen)
def get_ues_stat_dict(
self, filter_sport=False, check_apc_ects=True
) -> list[dict]: # was get_ues()
"""Liste des UEs, ordonnée par numero.
Si filter_sport, retire les UE de type SPORT.
Résultat: liste de dicts { champs UE U stats moyenne UE }
"""
ues = self.formsemestre.query_ues(with_sport=not filter_sport)
ues_dict = []
for ue in ues:
d = ue.to_dict()
if ue.type != UE_SPORT:
moys = self.etud_moy_ue[ue.id]
else:
moys = None
d.update(StatsMoyenne(moys).to_dict())
ues_dict.append(d)
if check_apc_ects and self.is_apc and not hasattr(g, "checked_apc_ects"):
g.checked_apc_ects = True
if None in [ue.ects for ue in ues if ue.type != UE_SPORT]:
flash(
"""Calcul moyenne générale impossible: ECTS des UE manquants !""",
category="danger",
)
return ues_dict
def get_modimpls_dict(self, ue_id=None) -> list[dict]:
"""Liste des modules pour une UE (ou toutes si ue_id==None),
triés par numéros (selon le type de formation)
"""
modimpls_dict = []
for modimpl in self.formsemestre.modimpls_sorted:
if ue_id == None or modimpl.module.ue.id == ue_id:
d = modimpl.to_dict()
# compat ScoDoc < 9.2: ajoute matières
d["mat"] = modimpl.module.matiere.to_dict()
modimpls_dict.append(d)
return modimpls_dict
def compute_rangs(self):
"""Calcule les classements
Moyenne générale: etud_moy_gen_ranks
Par UE (sauf ue bonus)
"""
(
self.etud_moy_gen_ranks,
self.etud_moy_gen_ranks_int,
) = moy_sem.comp_ranks_series(self.etud_moy_gen)
for ue in self.formsemestre.query_ues():
moy_ue = self.etud_moy_ue[ue.id]
self.ue_rangs[ue.id] = (
moy_sem.comp_ranks_series(moy_ue)[0], # juste en chaine
int(moy_ue.count()),
)
# .count() -> nb of non NaN values
def get_etud_ue_rang(self, ue_id, etudid) -> tuple[str, int]:
"""Le rang de l'étudiant dans cette ue
Result: rang:str, effectif:str
"""
rangs, effectif = self.ue_rangs[ue_id]
if rangs is not None:
rang = rangs[etudid]
else: else:
return "", "" fmt_note = lambda x: x
return rang, effectif
def etud_check_conditions_ues(self, etudid): barre_moy = (
"""Vrai si les conditions sur les UE sont remplies. self.formsemestre.formation.get_parcours().BARRE_MOY - scu.NOTES_TOLERANCE
Ne considère que les UE ayant des notes (moyenne calculée).
(les UE sans notes ne sont pas comptées comme sous la barre)
Prend en compte les éventuelles UE capitalisées.
Pour les parcours habituels, cela revient à vérifier que
les moyennes d'UE sont toutes > à leur barre (sauf celles sans notes)
Pour les parcours non standards (LP2014), cela peut être plus compliqué.
Return: True|False, message explicatif
"""
ue_status_list = []
for ue in self.formsemestre.query_ues():
ue_status = self.get_etud_ue_status(etudid, ue.id)
if ue_status:
ue_status_list.append(ue_status)
return self.parcours.check_barre_ues(ue_status_list)
def all_etuds_have_sem_decisions(self):
"""True si tous les étudiants du semestre ont une décision de jury.
Ne regarde pas les décisions d'UE.
"""
for ins in self.formsemestre.inscriptions:
if ins.etat != scu.INSCRIT:
continue # skip démissionnaires
if self.get_etud_decision_sem(ins.etudid) is None:
return False
return True
def etud_has_decision(self, etudid):
"""True s'il y a une décision de jury pour cet étudiant"""
return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid)
def get_etud_decision_ues(self, etudid: int) -> dict:
"""Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu.
Ne tient pas compte des UE capitalisées.
{ ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : }
Ne renvoie aucune decision d'UE pour les défaillants
"""
if self.get_etud_etat(etudid) == DEF:
return {}
else:
if not self.validations:
self.validations = res_sem.load_formsemestre_validations(
self.formsemestre
)
return self.validations.decisions_jury_ues.get(etudid, None)
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.
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
Si état défaillant, force le code a DEF
"""
if self.get_etud_etat(etudid) == DEF:
return {
"code": DEF,
"assidu": False,
"event_date": "",
"compense_formsemestre_id": None,
}
else:
if not self.validations:
self.validations = res_sem.load_formsemestre_validations(
self.formsemestre
)
return self.validations.decisions_jury.get(etudid, None)
def get_etud_etat(self, etudid: int) -> str:
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
ins = self.formsemestre.etuds_inscriptions.get(etudid, None)
if ins is None:
return ""
return ins.etat
def get_etud_mat_moy(self, matiere_id: int, etudid: int) -> str:
"""moyenne d'un étudiant dans une matière (ou NA si pas de notes)"""
if not self.moyennes_matieres:
return "nd"
return (
self.moyennes_matieres[matiere_id].get(etudid, "-")
if matiere_id in self.moyennes_matieres
else "-"
) )
barre_valid_ue = self.formsemestre.formation.get_parcours().NOTES_BARRE_VALID_UE
NO_NOTE = "-" # contenu des cellules sans notes
rows = []
titles = {"rang": "Rg"} # column_id : title
# les titres en footer: les mêmes, mais avec des bulles et liens:
titles_bot = {}
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float: def add_cell(
"""La moyenne de l'étudiant dans le moduleimpl row: dict, col_id: str, title: str, content: str, classes: str = ""
En APC, il s'agira d'une moyenne indicative sans valeur. ):
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM) "Add a row to our table. classes is a list of css class names"
""" row[col_id] = content
raise NotImplementedError() # virtual method if classes:
row[f"_{col_id}_class"] = classes
if not col_id in titles:
titles[col_id] = title
if classes:
titles[f"_{col_id}_class"] = classes
def get_etud_moy_gen(self, etudid): # -> float | str
"""Moyenne générale de cet etudiant dans ce semestre.
Prend en compte les UE capitalisées.
Si apc, moyenne indicative.
Si pas de notes: 'NA'
"""
return self.etud_moy_gen[etudid]
def get_etud_ects_pot(self, etudid: int) -> dict:
"""
Un dict avec les champs
ects_pot : (float) nb de crédits ECTS qui seraient validés (sous réserve de validation par le jury),
ects_pot_fond: (float) nb d'ECTS issus d'UE fondamentales (non électives)
Ce sont les ECTS des UE au dessus de la barre (10/20 en principe), avant le jury (donc non
encore enregistrées).
"""
# was nt.get_etud_moy_infos
# XXX pour compat nt, à remplacer ultérieurement
ues = self.get_etud_ue_validables(etudid)
ects_pot = 0.0
for ue in ues:
if (
ue.id in self.etud_moy_ue
and ue.ects is not None
and self.etud_moy_ue[ue.id][etudid] > self.parcours.NOTES_BARRE_VALID_UE
):
ects_pot += ue.ects
return {
"ects_pot": ects_pot,
"ects_pot_fond": 0.0, # not implemented (anciennemment pour école ingé)
}
def get_etud_rang(self, etudid: int):
return self.etud_moy_gen_ranks.get(etudid, 99999) # XXX
def get_etud_rang_group(self, etudid: int, group_id: int):
return (None, 0) # XXX unimplemented TODO
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
"""Liste d'informations (compat NotesTable) sur évaluations completes
de ce module.
Évaluation "complete" ssi toutes notes saisies ou en attente.
"""
modimpl = ModuleImpl.query.get(moduleimpl_id)
modimpl_results = self.modimpls_results.get(moduleimpl_id)
if not modimpl_results:
return [] # safeguard
evals_results = []
for e in modimpl.evaluations:
if modimpl_results.evaluations_completes_dict.get(e.id, False):
d = e.to_dict()
d["heure_debut"] = e.heure_debut # datetime.time
d["heure_fin"] = e.heure_fin
d["jour"] = e.jour # datetime
d["notes"] = {
etud.id: {
"etudid": etud.id,
"value": modimpl_results.evals_notes[e.id][etud.id],
}
for etud in self.etuds
}
d["etat"] = {
"evalattente": modimpl_results.evaluations_etat[e.id].nb_attente,
}
evals_results.append(d)
elif e.id not in modimpl_results.evaluations_completes_dict:
# ne devrait pas arriver ? XXX
log(
f"Warning: 220213 get_evals_in_mod {e.id} not in mod {moduleimpl_id} ?"
)
return evals_results
def get_evaluations_etats(self):
"""[ {...evaluation et son etat...} ]"""
# TODO: à moderniser
from app.scodoc import sco_evaluations
if not hasattr(self, "_evaluations_etats"):
self._evaluations_etats = sco_evaluations.do_evaluation_list_in_sem(
self.formsemestre.id
)
return self._evaluations_etats
def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
"""Liste des états des évaluations de ce module"""
# XXX TODO à moderniser: lent, recharge des donénes que l'on a déjà...
return [
e
for e in self.get_evaluations_etats()
if e["moduleimpl_id"] == moduleimpl_id
]
def get_moduleimpls_attente(self):
"""Liste des modimpls du semestre ayant des notes en attente"""
return [
modimpl
for modimpl in self.formsemestre.modimpls_sorted
if self.modimpls_results[modimpl.id].en_attente
]
def get_mod_stats(self, moduleimpl_id: int) -> dict:
"""Stats sur les notes obtenues dans un modimpl
Vide en APC
"""
return {
"moy": "-",
"max": "-",
"min": "-",
"nb_notes": "-",
"nb_missing": "-",
"nb_valid_evals": "-",
}
def get_nom_short(self, etudid):
"formatte nom d'un etud (pour table recap)"
etud = self.identdict[etudid]
return (
(etud["nom_usuel"] or etud["nom"]).upper()
+ " "
+ etud["prenom"].capitalize()[:2]
+ "."
)
@cached_property
def T(self):
return self.get_table_moyennes_triees()
def get_table_moyennes_triees(self) -> list:
"""Result: liste de tuples
moy_gen, moy_ue_0, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
"""
table_moyennes = []
etuds_inscriptions = self.formsemestre.etuds_inscriptions etuds_inscriptions = self.formsemestre.etuds_inscriptions
ues = self.formsemestre.query_ues(with_sport=True) # avec bonus ues = self.formsemestre.query_ues(with_sport=True) # avec bonus
modimpl_ids = set() # modimpl effectivement présents dans la table
for etudid in etuds_inscriptions: for etudid in etuds_inscriptions:
etud = Identite.query.get(etudid)
row = {"etudid": etudid}
# --- Rang
add_cell(row, "rang", "Rg", self.etud_moy_gen_ranks[etudid], "rang")
row["_rang_order"] = f"{self.etud_moy_gen_ranks_int[etudid]:05d}"
# --- Identité étudiant
add_cell(row, "civilite_str", "Civ.", etud.civilite_str, "identite_detail")
add_cell(row, "nom_disp", "Nom", etud.nom_disp(), "identite_detail")
add_cell(row, "prenom", "Prénom", etud.prenom, "identite_detail")
add_cell(row, "nom_short", "Nom", etud.nom_short, "identite_court")
row["_nom_short_target"] = url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=self.formsemestre.id,
etudid=etudid,
)
row["_nom_short_target_attrs"] = f'class="etudinfo" id="{etudid}"'
row["_nom_disp_target"] = row["_nom_short_target"]
row["_nom_disp_target_attrs"] = row["_nom_short_target_attrs"]
self._recap_etud_groups_infos(etudid, row, titles)
# --- Moyenne générale
moy_gen = self.etud_moy_gen.get(etudid, False) moy_gen = self.etud_moy_gen.get(etudid, False)
note_class = ""
if moy_gen is False: if moy_gen is False:
# pas de moyenne: démissionnaire ou def moy_gen = NO_NOTE
t = ( elif isinstance(moy_gen, float) and moy_gen < barre_moy:
["-"] note_class = " moy_inf"
+ ["0.00"] * len(self.ues) add_cell(
+ ["NI"] * len(self.formsemestre.modimpls_sorted) row,
) "moy_gen",
else: "Moy",
moy_ues = [] fmt_note(moy_gen),
ue_is_cap = {} "col_moy_gen" + note_class,
for ue in ues: )
ue_status = self.get_etud_ue_status(etudid, ue.id) titles_bot["_moy_gen_target_attrs"] = (
if ue_status: 'title="moyenne indicative"' if self.is_apc else ""
moy_ues.append(ue_status["moy"]) )
ue_is_cap[ue.id] = ue_status["is_capitalized"] # --- Moyenne d'UE
else: for ue in [ue for ue in ues if ue.type != UE_SPORT]:
moy_ues.append("?") ue_status = self.get_etud_ue_status(etudid, ue.id)
t = [moy_gen] + list(moy_ues) if ue_status is not None:
# Moyennes modules: col_id = f"moy_ue_{ue.id}"
for modimpl in self.formsemestre.modimpls_sorted: val = ue_status["moy"]
if ue_is_cap.get(modimpl.module.ue.id, False): note_class = ""
val = "-c-" if isinstance(val, float):
else: if val < barre_moy:
val = self.get_etud_mod_moy(modimpl.id, etudid) note_class = " moy_inf"
t.append(val) elif val >= barre_valid_ue:
t.append(etudid) note_class = " moy_ue_valid"
table_moyennes.append(t) add_cell(
# tri par moyennes décroissantes, row,
# en laissant les démissionnaires à la fin, par ordre alphabetique col_id,
etuds = [ins.etud for ins in etuds_inscriptions.values()] ue.acronyme,
etuds.sort(key=lambda e: e.sort_key) fmt_note(val),
self._rang_alpha = {e.id: i for i, e in enumerate(etuds)} "col_ue" + note_class,
table_moyennes.sort(key=self._row_key) )
return table_moyennes titles_bot[
f"_{col_id}_target_attrs"
] = f"""title="{ue.titre} S{ue.semestre_idx or '?'}" """
# Les moyennes des modules (ou ressources et SAÉs) dans cette UE
for modimpl in self.modimpls_in_ue(ue.id, etudid, with_bonus=False):
if ue_status["is_capitalized"]:
val = "-c-"
else:
modimpl_results = self.modimpls_results.get(modimpl.id)
if modimpl_results: # pas bonus
if self.is_apc: # BUT
moys_vers_ue = modimpl_results.etuds_moy_module.get(
ue.id
)
val = (
moys_vers_ue.get(etudid, "?")
if moys_vers_ue is not None
else ""
)
else: # classique: Series indépendante de l'UE
val = modimpl_results.etuds_moy_module.get(
etudid, "?"
)
else:
val = ""
def _row_key(self, x): col_id = (
"""clé de tri par moyennes décroissantes, f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}"
en laissant les demissionnaires à la fin, par ordre alphabetique. )
(moy_gen, rang_alpha) add_cell(
""" row,
try: col_id,
moy = -float(x[0]) modimpl.module.code,
except (ValueError, TypeError): fmt_note(val),
moy = 1000.0 # class col_res mod_ue_123
return (moy, self._rang_alpha[x[-1]]) f"col_{modimpl.module.type_abbrv()} mod_ue_{ue.id}",
)
titles_bot[f"_{col_id}_target"] = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
)
titles_bot[
f"_{col_id}_target_attrs"
] = f"""
title="{modimpl.module.titre}
({sco_users.user_info(modimpl.responsable_id)['nomcomplet']})" """
modimpl_ids.add(modimpl.id)
@cached_property rows.append(row)
def identdict(self) -> dict:
"""{ etudid : etud_dict } pour tous les inscrits au semestre""" # tri par rang croissant
return { rows.sort(key=lambda e: e["_rang_order"])
ins.etud.id: ins.etud.to_dict_scodoc7()
for ins in self.formsemestre.inscriptions # INFOS POUR FOOTER
bottom_infos = self._recap_bottom_infos(
[ue for ue in ues if ue.type != UE_SPORT], modimpl_ids, fmt_note
)
# --- TABLE FOOTER: ECTS, moyennes, min, max...
footer_rows = []
for bottom_line in bottom_infos:
row = bottom_infos[bottom_line]
# Cases vides à styler:
row["moy_gen"] = row.get("moy_gen", "")
row["_moy_gen_class"] = "col_moy_gen"
# titre de la ligne:
row["prenom"] = row["nom_short"] = bottom_line.capitalize()
row["_tr_class"] = bottom_line.lower()
footer_rows.append(row)
titles_bot.update(titles)
footer_rows.append(titles_bot)
return (
rows,
footer_rows,
titles,
[title for title in titles if not title.startswith("_")],
)
def _recap_bottom_infos(self, ues, modimpl_ids: set, fmt_note) -> dict:
"""Les informations à mettre en bas de la table: min, max, moy, ECTS"""
bottom_infos = { # { key : row } avec key = min, max, moy, coef
"min": {},
"max": {},
"moy": {},
"coef": {},
} }
# --- ECTS
row = {}
for ue in ues:
row[f"moy_ue_{ue.id}"] = ue.ects
row[f"_moy_ue_{ue.id}_class"] = "col_ue"
# style cases vides pour borders verticales
bottom_infos["coef"][f"moy_ue_{ue.id}"] = ""
bottom_infos["coef"][f"_moy_ue_{ue.id}_class"] = "col_ue"
row["moy_gen"] = sum([ue.ects or 0 for ue in ues if ue.type != UE_SPORT])
row["_moy_gen_class"] = "col_moy_gen"
bottom_infos["ects"] = row
# --- MIN, MAX, MOY
row_min, row_max, row_moy = {}, {}, {}
row_min["moy_gen"] = fmt_note(self.etud_moy_gen.min())
row_max["moy_gen"] = fmt_note(self.etud_moy_gen.max())
row_moy["moy_gen"] = fmt_note(self.etud_moy_gen.mean())
for ue in [ue for ue in ues if ue.type != UE_SPORT]:
col_id = f"moy_ue_{ue.id}"
row_min[col_id] = fmt_note(self.etud_moy_ue[ue.id].min())
row_max[col_id] = fmt_note(self.etud_moy_ue[ue.id].max())
row_moy[col_id] = fmt_note(self.etud_moy_ue[ue.id].mean())
row_min[f"_{col_id}_class"] = "col_ue"
row_max[f"_{col_id}_class"] = "col_ue"
row_moy[f"_{col_id}_class"] = "col_ue"
for modimpl in self.formsemestre.modimpls_sorted:
if modimpl.id in modimpl_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] * (
modimpl.module.coefficient or 0.0
)
else:
coef = modimpl.module.coefficient or 0
bottom_infos["coef"][col_id] = fmt_note(coef)
notes = self.modimpl_notes(modimpl.id, ue.id)
row_min[col_id] = fmt_note(np.nanmin(notes))
row_max[col_id] = fmt_note(np.nanmax(notes))
row_moy[col_id] = fmt_note(np.nanmean(notes))
bottom_infos["min"] = row_min
bottom_infos["max"] = row_max
bottom_infos["moy"] = row_moy
return bottom_infos
def _recap_etud_groups_infos(self, etudid: int, row: dict, titles: dict):
"""Table recap: ajoute à row les colonnes sur les groupes pour cet etud"""
# dec = self.get_etud_decision_sem(etudid)
# if dec:
# codes_nb[dec["code"]] += 1
row_class = ""
etud_etat = self.get_etud_etat(etudid) # dans NotesTableCompat, à revoir
if etud_etat == DEM:
gr_name = "Dém."
row_class = "dem"
elif etud_etat == DEF:
gr_name = "Déf."
row_class = "def"
else:
# XXX probablement à revoir pour utiliser données cachées,
# via get_etud_groups_in_partition ou autre
group = sco_groups.get_etud_main_group(etudid, self.formsemestre.id)
gr_name = group["group_name"] or ""
row["group"] = gr_name
row["_group_class"] = "group"
if row_class:
row["_tr_class"] = " ".join([row.get("_tr_class", ""), row_class])
titles["group"] = "Gr"

View File

@ -47,7 +47,7 @@ import os
from zipfile import ZipFile from zipfile import ZipFile
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
from app.scodoc.gen_tables import GenTable, SeqGenTable from app.scodoc.gen_tables import GenTable, SeqGenTable

View File

@ -38,11 +38,10 @@ Created on Fri Sep 9 09:15:05 2016
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
from app.models.moduleimpls import ModuleImpl from app.models.moduleimpls import ModuleImpl
from app.models.ues import UniteEns
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.pe import pe_tagtable from app.pe import pe_tagtable

View File

@ -34,7 +34,7 @@ from flask import url_for, g, request, abort
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import Identite, FormSemestre from app.models import Identite, FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb

View File

@ -97,7 +97,7 @@ from chardet import detect as chardet_detect
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, Identite from app.models import FormSemestre, Identite
from app.models.config import ScoDocSiteConfig from app.models.config import ScoDocSiteConfig
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu

View File

@ -39,7 +39,7 @@ from app import email
from app import log from app import log
from app.but import bulletin_but from app.but import bulletin_but
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, Identite, ModuleImplInscription from app.models import FormSemestre, Identite, ModuleImplInscription
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc.sco_exceptions import AccessDenied, ScoValueError

View File

@ -33,7 +33,7 @@ import json
from app.but import bulletin_but from app.but import bulletin_but
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
from app.models.etudiants import Identite from app.models.etudiants import Identite

View File

@ -45,7 +45,7 @@ from xml.etree import ElementTree
from xml.etree.ElementTree import Element from xml.etree.ElementTree import Element
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app import log from app import log

View File

@ -33,7 +33,7 @@ from flask import url_for, g, request
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb

View File

@ -39,7 +39,7 @@ from flask import request
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu

View File

@ -30,7 +30,7 @@
from flask import url_for, g, request from flask import url_for, g, request
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu

View File

@ -38,7 +38,7 @@ from flask import url_for, g, request
from flask_login import current_user from flask_login import current_user
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb

View File

@ -33,7 +33,7 @@ import flask
from flask import url_for, g, request from flask import url_for, g, request
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app import log from app import log

View File

@ -36,7 +36,7 @@ from flask_login import current_user
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import Module from app.models import Module
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu

View File

@ -37,7 +37,7 @@ import app.scodoc.sco_utils as scu
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
from app.models.notes import etud_has_notes_attente from app.models.notes import etud_has_notes_attente

View File

@ -46,7 +46,7 @@ from flask import url_for, make_response
from app import db from app import db
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, formsemestre from app.models import FormSemestre, formsemestre
from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
from app.models.groups import Partition from app.models.groups import Partition

View File

@ -38,7 +38,7 @@ from app import models
from app.comp import res_sem from app.comp import res_sem
from app.comp import moy_mod from app.comp import moy_mod
from app.comp.moy_mod import ModuleImplResults from app.comp.moy_mod import ModuleImplResults
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_but import ResultatsSemestreBUT
from app.models import FormSemestre from app.models import FormSemestre
from app.models.evaluations import Evaluation from app.models.evaluations import Evaluation

View File

@ -34,7 +34,7 @@ from flask import url_for, g, request
from flask_login import current_user from flask_login import current_user
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb

View File

@ -34,8 +34,7 @@ from flask_login import current_user
from app.auth.models import User from app.auth.models import User
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models import ModuleImpl from app.models import ModuleImpl
from app.models.evaluations import Evaluation from app.models.evaluations import Evaluation
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu

View File

@ -29,7 +29,7 @@
""" """
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, UniteEns from app.models import FormSemestre, UniteEns
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu

View File

@ -34,7 +34,7 @@ import collections
from flask import url_for, g, request from flask import url_for, g, request
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import sco_abs from app.scodoc import sco_abs

View File

@ -36,7 +36,7 @@ from flask import request
from flask_login import current_user from flask_login import current_user
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, Identite from app.models import FormSemestre, Identite
from app.scodoc import sco_abs from app.scodoc import sco_abs
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours

View File

@ -55,7 +55,7 @@ import flask
from flask import url_for, g, request from flask import url_for, g, request
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, UniteEns from app.models import FormSemestre, UniteEns
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu

View File

@ -38,15 +38,13 @@ from flask import make_response, url_for
from app import log from app import log
from app.but import bulletin_but from app.but import bulletin_but
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_compat import NotesTableCompat
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.evaluations import Evaluation from app.models.evaluations import Evaluation
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_bac
from app.scodoc import sco_bulletins_json from app.scodoc import sco_bulletins_json
from app.scodoc import sco_bulletins_xml from app.scodoc import sco_bulletins_xml
from app.scodoc import sco_bulletins, sco_excel from app.scodoc import sco_bulletins, sco_excel
@ -225,12 +223,9 @@ def do_formsemestre_recapcomplet(
): ):
"""Calcule et renvoie le tableau récapitulatif.""" """Calcule et renvoie le tableau récapitulatif."""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if ( if format == "html" and not modejury:
formsemestre.formation.is_apc() res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
and format not in ("xml", "json") data, filename = gen_formsemestre_recapcomplet_html(formsemestre, res)
and not modejury
):
data, filename = make_formsemestre_recapcomplet_apc(formsemestre, format=format)
else: else:
data, filename, format = make_formsemestre_recapcomplet( data, filename, format = make_formsemestre_recapcomplet(
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
@ -1037,14 +1032,13 @@ def _gen_row(keys: list[str], row, elt="td"):
return f'<tr {tr_class}>{"".join([_gen_cell(key, row, elt) for key in keys])}</tr>' return f'<tr {tr_class}>{"".join([_gen_cell(key, row, elt) for key in keys])}</tr>'
def make_formsemestre_recapcomplet_apc(formsemestre: FormSemestre, format="html"): def gen_formsemestre_recapcomplet_html(
formsemestre: FormSemestre, res: NotesTableCompat
):
"""Construit table recap pour le BUT """Construit table recap pour le BUT
Return: data, filename Return: data, filename
""" """
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre) rows, footer_rows, titles, column_ids = res.get_table_recap(convert_values=True)
rows, footer_rows, titles, column_ids = res.get_table_moyennes_triees(
convert_values=True
)
H = ['<div class="table_recap"><table class="table_recap">'] H = ['<div class="table_recap"><table class="table_recap">']
# header # header
H.append( H.append(

View File

@ -40,7 +40,7 @@ from flask import url_for, g, request
import pydot import pydot
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu

View File

@ -37,7 +37,7 @@ from flask import g, url_for, request
from flask_login import current_user from flask_login import current_user
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType

View File

@ -43,7 +43,7 @@ import flask
from flask import g from flask import g
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_cache from app.scodoc import sco_cache

View File

@ -38,7 +38,7 @@ import http
from flask import g, url_for from flask import g, url_for
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb

View File

@ -62,7 +62,7 @@ from flask_login import current_user
from app import log from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.decorators import ( from app.decorators import (
scodoc, scodoc,
scodoc7func, scodoc7func,

View File

@ -42,7 +42,7 @@ from flask_login import current_user
from werkzeug.utils import redirect from werkzeug.utils import redirect
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
from app.models.formsemestre import FormSemestreUEComputationExpr from app.models.formsemestre import FormSemestreUEComputationExpr
from app.models.ues import UniteEns from app.models.ues import UniteEns

View File

@ -14,7 +14,7 @@ from flask import current_app, g
import app import app
from app import db from app import db
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations

View File

@ -11,7 +11,7 @@ from flask import g
import app import app
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
from app.scodoc import sco_bulletins, sco_formsemestre from app.scodoc import sco_bulletins, sco_formsemestre
from app.scodoc import sco_cache from app.scodoc import sco_cache

View File

@ -20,7 +20,7 @@ from tests.unit import sco_fake_gen
import app import app
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre from app.models import FormSemestre
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_abs from app.scodoc import sco_abs