forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into dev93
This commit is contained in:
commit
9cc7a80fb0
@ -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 d’ajout 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.
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -66,8 +66,9 @@ 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,
|
||||||
<ul>"""
|
car les sessions suivantes l'utilisent:</p>
|
||||||
|
<ul>"""
|
||||||
)
|
)
|
||||||
for sem in sems:
|
for sem in sems:
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -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,46 +933,58 @@ 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])))
|
||||||
semestre_id = semestre_ids[0]
|
if len(semestre_ids) > 0:
|
||||||
|
semestre_id = semestre_ids[0]
|
||||||
|
else:
|
||||||
|
# c'est ennuyeux: dans ce cas, on pourrait demander à indiquer explicitement
|
||||||
|
# le semestre ? ou affecter le malus au semestre 1 ???
|
||||||
|
raise ScoValueError(
|
||||||
|
"Impossible d'ajouter un malus s'il n'y a pas d'autres modules"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# c'est ennuyeux: dans ce cas, on pourrait demander à indiquer explicitement
|
semestre_id = ue.semestre_idx
|
||||||
# le semestre ? ou affecter le malus au semestre 1 ???
|
|
||||||
raise ScoValueError(
|
|
||||||
"Impossible d'ajouter un malus s'il n'y a pas d'autres modules"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user