forked from ScoDoc/ScoDoc
Refactoring + nouvelle table recap pour classiques (WIP)
This commit is contained in:
parent
13d4da3fea
commit
9596c8cf6f
@ -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
|
||||||
|
@ -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"
|
|
||||||
|
@ -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(
|
||||||
|
@ -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 là
|
. 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"
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user