Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into dev93

This commit is contained in:
Emmanuel Viennet 2022-06-09 07:40:10 +02:00
commit 9cc7a80fb0
6 changed files with 122 additions and 104 deletions

View File

@ -730,7 +730,7 @@ class BonusIUTRennes1(BonusSportAdditif):
seuil_moy_gen = 10.0 seuil_moy_gen = 10.0
proportion_point = 1 / 20.0 proportion_point = 1 / 20.0
classic_use_bonus_ues = False classic_use_bonus_ues = False
# Adapté de BonusTarbes, mais s'applique aussi en classic # S'applique aussi en classic, sur la moy. gen.
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan): def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus""" """calcul du bonus"""
# Prend la note de chaque modimpl, sans considération d'UE # Prend la note de chaque modimpl, sans considération d'UE
@ -771,18 +771,59 @@ class BonusLaRochelle(BonusSportAdditif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle. """Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle.
<ul> <ul>
<li>Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.</li> <li>Si la note de sport est comprise entre 0 et 10 : pas dajout de point.</li>
<li>Si la note de sport est comprise entre 10 et 20 : ajout de 1% de cette <li>Si la note de sport est comprise entre 10 et 20 :
note sur la moyenne générale du semestre (ou sur les UE en BUT).</li> <ul>
<li>Pour le BUT, application pour chaque UE du semestre :
<ul>
<li>pour une note entre 18 et 20 => + 0,10 points</li>
<li>pour une note entre 16 et 17,99 => + 0,08 points</li>
<li>pour une note entre 14 et 15,99 => + 0,06 points</li>
<li>pour une note entre 12 et 13,99 => + 0,04 points</li>
<li>pour une note entre 10 et 11,99 => + 0,02 points</li>
</ul>
</li>
<li>Pour les DUT/LP :
ajout de 1% de la note sur la moyenne générale du semestre
</li>
</ul>
</li>
</ul> </ul>
""" """
name = "bonus_iutlr" name = "bonus_iutlr"
displayed_name = "IUT de La Rochelle" displayed_name = "IUT de La Rochelle"
seuil_moy_gen = 10.0 # si bonus > 10, seuil_moy_gen = 10.0 # si bonus > 10,
seuil_comptage = 0.0 # tous les points sont comptés seuil_comptage = 0.0 # tous les points sont comptés
proportion_point = 0.01 # 1% proportion_point = 0.01 # 1%
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus"""
# La date du semestre ?
if self.formsemestre.formation.is_apc():
if 0 in sem_modimpl_moys_inscrits.shape:
# pas d'étudiants ou pas d'UE ou pas de module...
return
# Calcule moyenne pondérée des notes de sport:
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
bonus_moy_arr = np.sum(
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
np.nan_to_num(bonus_moy_arr, nan=0.0, copy=False)
bonus_moy_arr[bonus_moy_arr < 10.0] = 0.0
bonus_moy_arr[bonus_moy_arr >= 18.0] = 0.10
bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.08
bonus_moy_arr[bonus_moy_arr >= 14.0] = 0.06
bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.04
bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.02
self.bonus_additif(bonus_moy_arr)
else:
# DUT et LP:
return super().compute_bonus(
sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan
)
class BonusLeHavre(BonusSportAdditif): class BonusLeHavre(BonusSportAdditif):
"""Bonus sport IUT du Havre sur les moyennes d'UE """Bonus sport IUT du Havre sur les moyennes d'UE
@ -1067,14 +1108,15 @@ class BonusStNazaire(BonusSportMultiplicatif):
factor_max = 0.1 # 10% max factor_max = 0.1 # 10% max
class BonusTarbes(BonusSportAdditif): class BonusTarbes(BonusIUTRennes1):
"""Calcul bonus optionnels (sport, culture), règle IUT de Tarbes. """Calcul bonus optionnels (sport, culture), règle IUT de Tarbes.
<ul> <ul>
<li>Les étudiants opeuvent suivre un ou plusieurs activités optionnelles notées. <li>Les étudiants opeuvent suivre un ou plusieurs activités optionnelles notées.
La meilleure des notes obtenue est prise en compte, si elle est supérieure à 10/20. La meilleure des notes obtenue est prise en compte, si elle est supérieure à 10/20.
</li> </li>
<li>Le trentième des points au dessus de 10 est ajouté à la moyenne des UE. <li>Le trentième des points au dessus de 10 est ajouté à la moyenne des UE en BUT,
ou à la moyenne générale en DUT et LP.
</li> </li>
<li> Exemple: un étudiant ayant 16/20 bénéficiera d'un bonus de (16-10)/30 = 0,2 points <li> Exemple: un étudiant ayant 16/20 bénéficiera d'un bonus de (16-10)/30 = 0,2 points
sur chaque UE. sur chaque UE.
@ -1088,29 +1130,6 @@ class BonusTarbes(BonusSportAdditif):
proportion_point = 1 / 30.0 proportion_point = 1 / 30.0
classic_use_bonus_ues = True classic_use_bonus_ues = True
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus"""
# Prend la note de chaque modimpl, sans considération d'UE
if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
# ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
ues = self.formsemestre.query_ues(with_sport=False).all()
ues_idx = [ue.id for ue in ues]
if self.formsemestre.formation.is_apc(): # --- BUT
bonus_moy_arr = np.where(
note_bonus_max > self.seuil_moy_gen,
(note_bonus_max - self.seuil_moy_gen) * self.proportion_point,
0.0,
)
self.bonus_ues = pd.DataFrame(
np.stack([bonus_moy_arr] * len(ues)).T,
index=self.etuds_idx,
columns=ues_idx,
dtype=float,
)
class BonusTours(BonusDirect): class BonusTours(BonusDirect):
"""Calcul bonus sport & culture IUT Tours. """Calcul bonus sport & culture IUT Tours.

View File

@ -41,7 +41,8 @@ from app import db
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_codes_parcours import UE_SPORT from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc import sco_cache
from app.scodoc.sco_exceptions import ScoBugCatcher
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
@ -423,7 +424,9 @@ def moduleimpl_is_conforme(
if nb_ues == 0: if nb_ues == 0:
return False # situation absurde (pas d'UE) return False # situation absurde (pas d'UE)
if len(modules_coefficients) != nb_ues: if len(modules_coefficients) != nb_ues:
raise ValueError("moduleimpl_is_conforme: nb ue incoherent") # il arrive (#bug) que le cache ne soit pas à jour...
sco_cache.invalidate_formsemestre()
raise ScoBugCatcher("moduleimpl_is_conforme: nb ue incoherent")
module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0 module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0
check = all( check = all(
(modules_coefficients[moduleimpl.module_id].to_numpy() != 0) (modules_coefficients[moduleimpl.module_id].to_numpy() != 0)

View File

@ -79,6 +79,15 @@ class UniteEns(db.Model):
return sco_edit_ue.ue_is_locked(self.id) return sco_edit_ue.ue_is_locked(self.id)
def can_be_deleted(self) -> bool:
"""True si l'UE n'est pas utilisée dans des formsemestre
et n'a pas de module rattachés
"""
# "pas un seul module de cette UE n'a de modimpl...""
return (self.modules.count() == 0) or not any(
m.modimpls.all() for m in self.modules
)
def guess_semestre_idx(self) -> None: def guess_semestre_idx(self) -> None:
"""Lorsqu'on prend une ancienne formation non APC, """Lorsqu'on prend une ancienne formation non APC,
les UE n'ont pas d'indication de semestre. les UE n'ont pas d'indication de semestre.

View File

@ -66,7 +66,8 @@ def formation_delete(formation_id=None, dialog_confirmed=False):
sems = sco_formsemestre.do_formsemestre_list({"formation_id": formation_id}) sems = sco_formsemestre.do_formsemestre_list({"formation_id": formation_id})
if sems: if sems:
H.append( H.append(
"""<p class="warning">Impossible de supprimer cette formation, car les sessions suivantes l'utilisent:</p> """<p class="warning">Impossible de supprimer cette formation,
car les sessions suivantes l'utilisent:</p>
<ul>""" <ul>"""
) )
for sem in sems: for sem in sems:

View File

@ -204,7 +204,7 @@ def module_delete(module_id=None):
H = [ H = [
html_sco_header.sco_header(page_title="Suppression d'un module"), html_sco_header.sco_header(page_title="Suppression d'un module"),
f"""<h2>Suppression du module {module.titre} ({module.code})</h2>""", f"""<h2>Suppression du module {module.titre or "<em>sans titre</em>"} ({module.code})</h2>""",
] ]
dest_url = url_for( dest_url = url_for(
@ -917,21 +917,13 @@ def module_count_moduleimpls(module_id):
def formation_add_malus_modules(formation_id, titre=None, redirect=True): def formation_add_malus_modules(formation_id, titre=None, redirect=True):
"""Création d'un module de "malus" dans chaque UE d'une formation""" """Création d'un module de "malus" dans chaque UE d'une formation"""
from app.scodoc import sco_edit_ue
ues = sco_edit_ue.ue_list(args={"formation_id": formation_id}) formation = Formation.query.get_or_404(formation_id)
for ue in ues: for ue in formation.ues:
# Un seul module de malus par UE: ue_add_malus_module(ue, titre=titre)
nb_mod_malus = len(
[ formation.invalidate_cached_sems()
mod
for mod in module_list(args={"ue_id": ue["ue_id"]})
if mod["module_type"] == scu.ModuleType.MALUS
]
)
if nb_mod_malus == 0:
ue_add_malus_module(ue["ue_id"], titre=titre)
if redirect: if redirect:
return flask.redirect( return flask.redirect(
@ -941,20 +933,22 @@ def formation_add_malus_modules(formation_id, titre=None, redirect=True):
) )
def ue_add_malus_module(ue_id, titre=None, code=None): def ue_add_malus_module(ue: UniteEns, titre=None, code=None) -> int:
"""Add a malus module in this ue""" """Add a malus module in this ue.
from app.scodoc import sco_edit_ue If already exists, do nothing.
Returns id of malus module.
"""
modules_malus = [m for m in ue.modules if m.module_type == scu.ModuleType.MALUS]
if len(modules_malus) > 0:
return modules_malus[0].id # déjà existant
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0] titre = titre or ""
code = code or f"MALUS{ue.numero}"
if titre is None:
titre = ""
if code is None:
code = "MALUS%d" % ue["numero"]
# Tout module doit avoir un semestre_id (indice 1, 2, ...) # Tout module doit avoir un semestre_id (indice 1, 2, ...)
semestre_ids = sco_edit_ue.ue_list_semestre_ids(ue) if ue.semestre_idx is None:
if semestre_ids: semestre_ids = sorted(list(set([m.semestre_id for m in ue.modules])))
if len(semestre_ids) > 0:
semestre_id = semestre_ids[0] semestre_id = semestre_ids[0]
else: else:
# c'est ennuyeux: dans ce cas, on pourrait demander à indiquer explicitement # c'est ennuyeux: dans ce cas, on pourrait demander à indiquer explicitement
@ -962,25 +956,35 @@ def ue_add_malus_module(ue_id, titre=None, code=None):
raise ScoValueError( raise ScoValueError(
"Impossible d'ajouter un malus s'il n'y a pas d'autres modules" "Impossible d'ajouter un malus s'il n'y a pas d'autres modules"
) )
else:
semestre_id = ue.semestre_idx
# Matiere pour placer le module malus # Matiere pour placer le module malus
Matlist = sco_edit_matiere.matiere_list(args={"ue_id": ue_id}) titre_matiere_malus = "Malus"
numero = max([mat["numero"] for mat in Matlist]) + 10
matiere_id = sco_edit_matiere.do_matiere_create(
{"ue_id": ue_id, "titre": "Malus", "numero": numero}
)
module_id = do_module_create( matieres_malus = [mat for mat in ue.matieres if mat.titre == titre_matiere_malus]
{ if len(matieres_malus) > 0:
"titre": titre, # matière Malus déjà existante, l'utilise
"code": code, matiere = matieres_malus[0]
"coefficient": 0.0, # unused else:
"ue_id": ue_id, if ue.matieres.count() > 0:
"matiere_id": matiere_id, numero = max([mat.numero for mat in ue.matieres]) + 10
"formation_id": ue["formation_id"], else:
"semestre_id": semestre_id, numero = 0
"module_type": scu.ModuleType.MALUS, matiere = Matiere(ue_id=ue.id, titre=titre_matiere_malus, numero=numero)
}, db.session.add(matiere)
)
return module_id module = Module(
titre=titre,
code=code,
coefficient=0.0,
ue=ue,
matiere=matiere,
formation=ue.formation,
semestre_id=semestre_id,
module_type=scu.ModuleType.MALUS,
)
db.session.add(module)
db.session.commit()
return module.id

View File

@ -143,14 +143,6 @@ def do_ue_create(args):
return ue_id return ue_id
def can_delete_ue(ue: UniteEns) -> bool:
"""True si l'UE n'est pas utilisée dans des formsemestre
et n'a pas de module rattachés
"""
# "pas un seul module de cette UE n'a de modimpl...""
return (ue.modules.count() == 0) and not any(m.modimpls.all() for m in ue.modules)
def do_ue_delete(ue_id, delete_validations=False, force=False): def do_ue_delete(ue_id, delete_validations=False, force=False):
"delete UE and attached matieres (but not modules)" "delete UE and attached matieres (but not modules)"
from app.scodoc import sco_formations from app.scodoc import sco_formations
@ -159,9 +151,9 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
ue = UniteEns.query.get_or_404(ue_id) ue = UniteEns.query.get_or_404(ue_id)
formation_id = ue.formation_id formation_id = ue.formation_id
semestre_idx = ue.semestre_idx semestre_idx = ue.semestre_idx
if not can_delete_ue(ue): if not ue.can_be_deleted():
raise ScoNonEmptyFormationObject( raise ScoNonEmptyFormationObject(
"UE", f"UE (id={ue.id}, dud)",
msg=ue.titre, msg=ue.titre,
dest_url=url_for( dest_url=url_for(
"notes.ue_table", "notes.ue_table",
@ -553,9 +545,9 @@ def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
semestre_idx=ue.semestre_idx, semestre_idx=ue.semestre_idx,
), ),
) )
if not can_delete_ue(ue): if not ue.can_be_deleted():
raise ScoNonEmptyFormationObject( raise ScoNonEmptyFormationObject(
"UE", f"UE",
msg=ue.titre, msg=ue.titre,
dest_url=url_for( dest_url=url_for(
"notes.ue_table", "notes.ue_table",
@ -1367,16 +1359,6 @@ def ue_is_locked(ue_id):
return len(r) > 0 return len(r) > 0
def ue_list_semestre_ids(ue: dict):
"""Liste triée des numeros de semestres des modules dans cette UE
Il est recommandable que tous les modules d'une UE aient le même indice de semestre.
Mais cela n'a pas toujours été le cas dans les programmes pédagogiques officiels,
aussi ScoDoc laisse le choix.
"""
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
return sorted(list(set([mod["semestre_id"] for mod in modules])))
UE_PALETTE = [ UE_PALETTE = [
"#B80004", # rouge "#B80004", # rouge
"#F97B3D", # Orange Crayola "#F97B3D", # Orange Crayola