forked from ScoDoc/ScoDoc
261 lines
9.2 KiB
Python
261 lines
9.2 KiB
Python
##############################################################################
|
|
# ScoDoc
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
# See LICENSE
|
|
##############################################################################
|
|
|
|
"""Stockage des décisions de jury
|
|
"""
|
|
import pandas as pd
|
|
import sqlalchemy as sa
|
|
|
|
from app import db
|
|
from app.comp.res_cache import ResultatsCache
|
|
from app.models import (
|
|
ApcValidationAnnee,
|
|
ApcValidationRCUE,
|
|
Formation,
|
|
FormSemestre,
|
|
Identite,
|
|
ScolarAutorisationInscription,
|
|
ScolarFormSemestreValidation,
|
|
UniteEns,
|
|
)
|
|
from app.scodoc import sco_cache
|
|
from app.scodoc import codes_cursus
|
|
from app.scodoc import sco_utils as scu
|
|
|
|
|
|
class ValidationsSemestre(ResultatsCache):
|
|
"""Les décisions de jury pour un semestre"""
|
|
|
|
_cached_attrs = (
|
|
"decisions_jury",
|
|
"decisions_jury_ues",
|
|
"ue_capitalisees",
|
|
)
|
|
|
|
def __init__(self, formsemestre: FormSemestre):
|
|
super().__init__(formsemestre, sco_cache.ValidationsSemestreCache)
|
|
|
|
self.decisions_jury = {}
|
|
"""Décisions prises dans ce semestre:
|
|
{ etudid : { 'code' : None|ATT|..., 'assidu' : 0|1 }}"""
|
|
self.decisions_jury_ues = {}
|
|
"""Décisions sur des UEs dans ce semestre:
|
|
{ etudid : { ue_id : { 'code' : Note|ADM|CMP, 'event_date': "d/m/y", "ects" : x}}}
|
|
"""
|
|
self.ue_capitalisees: pd.DataFrame = None
|
|
"""DataFrame, index etudid
|
|
formsemestre_id : origine de l'UE capitalisée
|
|
is_external : vrai si validation effectuée dans un semestre extérieur
|
|
ue_id : dans le semestre origine (pas toujours de la même formation)
|
|
ue_code : code de l'UE
|
|
moy_ue : note enregistrée
|
|
event_date : date de la validation (jury)"""
|
|
|
|
if not self.load_cached():
|
|
self.compute()
|
|
self.store()
|
|
|
|
def compute(self):
|
|
"Charge les résultats de jury et UEs capitalisées"
|
|
self.ue_capitalisees = formsemestre_get_ue_capitalisees(self.formsemestre)
|
|
self.comp_decisions_jury()
|
|
|
|
def comp_decisions_jury(self):
|
|
"""Cherche les decisions du jury pour le semestre (pas les RCUE).
|
|
Calcule les attributs:
|
|
decisions_jury = { etudid : { 'code' : None|ATT|..., 'assidu' : 0|1 }}
|
|
decision_jury_ues={ etudid :
|
|
{ ue_id : { 'code' : Note|ADM|CMP, 'event_date' : "d/m/y", 'ects' : x }}
|
|
}
|
|
Si la décision n'a pas été prise, la clé etudid n'est pas présente.
|
|
Si l'étudiant est défaillant, pas de décisions d'UE.
|
|
"""
|
|
# repris de NotesTable.comp_decisions_jury pour la compatibilité
|
|
decisions_jury_q = ScolarFormSemestreValidation.query.filter_by(
|
|
formsemestre_id=self.formsemestre.id
|
|
)
|
|
decisions_jury = {}
|
|
for decision in decisions_jury_q.filter(
|
|
db.text("ue_id is NULL") # slt dec. sem.
|
|
):
|
|
decisions_jury[decision.etudid] = {
|
|
"code": decision.code,
|
|
"assidu": decision.assidu,
|
|
"compense_formsemestre_id": decision.compense_formsemestre_id,
|
|
"event_date": decision.event_date.strftime(scu.DATE_FMT),
|
|
}
|
|
self.decisions_jury = decisions_jury
|
|
|
|
# UEs: { etudid : { ue_id : {"code":, "ects":, "event_date":} }}
|
|
decisions_jury_ues = {}
|
|
# Parcoure les décisions d'UE:
|
|
for decision in (
|
|
decisions_jury_q.filter(db.text("ue_id is not NULL"))
|
|
.join(UniteEns)
|
|
.order_by(UniteEns.numero, UniteEns.acronyme)
|
|
):
|
|
if decision.etudid not in decisions_jury_ues:
|
|
decisions_jury_ues[decision.etudid] = {}
|
|
# Calcul des ECTS associés à cette UE:
|
|
if codes_cursus.code_ue_validant(decision.code) and decision.ue:
|
|
ects = decision.ue.ects or 0.0 # 0 if None
|
|
else:
|
|
ects = 0.0
|
|
|
|
decisions_jury_ues[decision.etudid][decision.ue.id] = {
|
|
"code": decision.code,
|
|
"ects": ects, # 0. si UE non validée
|
|
"event_date": decision.event_date.strftime(scu.DATE_FMT),
|
|
}
|
|
|
|
self.decisions_jury_ues = decisions_jury_ues
|
|
|
|
def has_decision(self, etud: Identite) -> bool:
|
|
"""Vrai si etud a au moins une décision enregistrée depuis
|
|
ce semestre (quelle qu'elle soit)
|
|
"""
|
|
return (etud.id in self.decisions_jury_ues) or (etud.id in self.decisions_jury)
|
|
|
|
|
|
def formsemestre_get_ue_capitalisees(formsemestre: FormSemestre) -> pd.DataFrame:
|
|
"""Liste des UE capitalisées (ADM) utilisables dans ce formsemestre
|
|
|
|
Recherche dans les semestres des formations de même code, avec le même semestre_id
|
|
et une date de début antérieure que celle du formsemestre.
|
|
Prend aussi les UE externes validées.
|
|
|
|
Attention: fonction très coûteuse, cacher le résultat.
|
|
|
|
Résultat: DataFrame avec
|
|
etudid (index)
|
|
formsemestre_id : origine de l'UE capitalisée
|
|
is_external : vrai si validation effectuée dans un semestre extérieur
|
|
ue_id : dans le semestre origine (pas toujours de la même formation)
|
|
ue_code : code de l'UE
|
|
moy_ue :
|
|
event_date :
|
|
} ]
|
|
"""
|
|
|
|
# Note: pour récupérer aussi les UE validées en CMp ou ADJ, changer une ligne
|
|
# and ( SFV.code = 'ADM' or SFV.code = 'ADJ' or SFV.code = 'CMP' )
|
|
|
|
query = sa.text(
|
|
"""
|
|
SELECT DISTINCT SFV.*, ue.ue_code
|
|
FROM
|
|
notes_ue ue,
|
|
notes_formations nf,
|
|
notes_formations nf2,
|
|
scolar_formsemestre_validation SFV,
|
|
notes_formsemestre sem,
|
|
notes_formsemestre_inscription ins
|
|
|
|
WHERE ue.formation_id = nf.id
|
|
and nf.formation_code = nf2.formation_code
|
|
and nf2.id=:formation_id
|
|
and ins.etudid = SFV.etudid
|
|
and ins.formsemestre_id = :formsemestre_id
|
|
|
|
and SFV.ue_id = ue.id
|
|
and SFV.code = 'ADM'
|
|
|
|
and ( (sem.id = SFV.formsemestre_id
|
|
and sem.date_debut < :date_debut
|
|
and sem.semestre_id = :semestre_id )
|
|
or (
|
|
((SFV.formsemestre_id is NULL) OR (SFV.is_external)) -- les UE externes ou "anterieures"
|
|
AND (SFV.semestre_id is NULL OR SFV.semestre_id=:semestre_id)
|
|
) )
|
|
"""
|
|
)
|
|
params = {
|
|
"formation_id": formsemestre.formation.id,
|
|
"formsemestre_id": formsemestre.id,
|
|
"semestre_id": formsemestre.semestre_id,
|
|
"date_debut": formsemestre.date_debut,
|
|
}
|
|
|
|
with db.engine.begin() as connection:
|
|
df = pd.read_sql_query(query, connection, params=params, index_col="etudid")
|
|
return df
|
|
|
|
|
|
def erase_decisions_annee_formation(
|
|
etud: Identite, formation: Formation, annee: int, delete=False
|
|
) -> list:
|
|
"""Efface toutes les décisions de jury de l'étudiant dans les formations de même code
|
|
que celle donnée pour cette année de la formation:
|
|
UEs, RCUEs de l'année BUT, année BUT, passage vers l'année suivante.
|
|
Ne considère pas l'origine de la décision.
|
|
annee: entier, 1, 2, 3, ...
|
|
Si delete est faux, renvoie la liste des validations qu'il faudrait effacer, sans y toucher.
|
|
"""
|
|
sem1, sem2 = annee * 2 - 1, annee * 2
|
|
# UEs
|
|
validations = (
|
|
ScolarFormSemestreValidation.query.filter_by(etudid=etud.id)
|
|
.join(UniteEns)
|
|
.filter(db.or_(UniteEns.semestre_idx == sem1, UniteEns.semestre_idx == sem2))
|
|
.join(Formation)
|
|
.filter_by(formation_code=formation.formation_code)
|
|
.order_by(
|
|
UniteEns.acronyme, UniteEns.numero
|
|
) # acronyme d'abord car 2 semestres
|
|
.all()
|
|
)
|
|
# RCUEs (a priori inutile de matcher sur l'ue2_id)
|
|
validations += (
|
|
ApcValidationRCUE.query.filter_by(etudid=etud.id)
|
|
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue1_id)
|
|
.filter_by(semestre_idx=sem1)
|
|
.join(Formation)
|
|
.filter_by(formation_code=formation.formation_code)
|
|
.order_by(UniteEns.acronyme, UniteEns.numero)
|
|
.all()
|
|
)
|
|
# Validation de semestres classiques
|
|
validations += (
|
|
ScolarFormSemestreValidation.query.filter_by(etudid=etud.id, ue_id=None)
|
|
.join(
|
|
FormSemestre,
|
|
FormSemestre.id == ScolarFormSemestreValidation.formsemestre_id,
|
|
)
|
|
.filter(
|
|
db.or_(FormSemestre.semestre_id == sem1, FormSemestre.semestre_id == sem2)
|
|
)
|
|
.join(Formation)
|
|
.filter_by(formation_code=formation.formation_code)
|
|
.all()
|
|
)
|
|
# Année BUT
|
|
validations += ApcValidationAnnee.query.filter_by(
|
|
etudid=etud.id,
|
|
ordre=annee,
|
|
referentiel_competence_id=formation.referentiel_competence_id,
|
|
).all()
|
|
# Autorisations vers les semestres suivants ceux de l'année:
|
|
validations += (
|
|
ScolarAutorisationInscription.query.filter_by(
|
|
etudid=etud.id, formation_code=formation.formation_code
|
|
)
|
|
.filter(
|
|
db.or_(
|
|
ScolarAutorisationInscription.semestre_id == sem1 + 1,
|
|
ScolarAutorisationInscription.semestre_id == sem2 + 1,
|
|
)
|
|
)
|
|
.all()
|
|
)
|
|
|
|
if delete:
|
|
for validation in validations:
|
|
db.session.delete(validation)
|
|
db.session.commit()
|
|
sco_cache.invalidate_formsemestre_etud(etud)
|
|
return []
|
|
return validations
|