Apo: export BUT element annuel

This commit is contained in:
Emmanuel Viennet 2023-04-19 11:51:58 +02:00
parent 54bcbff179
commit 8f844f5191
3 changed files with 156 additions and 101 deletions

View File

@ -322,7 +322,8 @@ class NotesTableCompat(ResultatsSemestre):
def get_etud_decision_sem(self, etudid: int) -> dict:
"""Decision du jury semestre 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
Si état défaillant, force le code a DEF.
Toujours None en BUT.
"""
if self.get_etud_etat(etudid) == DEF:
return {

View File

@ -265,7 +265,6 @@ class ApoEtud(dict):
"Vrai si BUT"
self.col_elts = {}
"{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}"
self.new_cols = {} # { col_id : value to record in csv }
self.etud: Identite = None
"etudiant ScoDoc associé"
self.etat = None # ETUD_OK, ...
@ -283,6 +282,17 @@ class ApoEtud(dict):
self.fmt_note = functools.partial(
_apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
)
# Initialisés par associate_sco:
self.autre_sem: dict = None
self.autre_res: NotesTableCompat = None
self.cur_sem: dict = None
self.cur_res: NotesTableCompat = None
self.new_cols = {}
"{ col_id : value to record in csv }"
# Pour le BUT:
self.validation_annee_but: ApcValidationAnnee = None
"validation de jury annuelle BUT, ou None"
def __repr__(self):
return f"""ApoEtud( nom='{self["nom"]}', nip='{self["nip"]}' )"""
@ -336,18 +346,17 @@ class ApoEtud(dict):
sco_elts = {} # valeurs trouvées dans ScoDoc code : { N, B, J, R }
for col_id in apo_data.col_ids[4:]:
code = apo_data.cols[col_id]["Code"] # 'V1RT'
el = sco_elts.get(
code, None
) # {'R': ADM, 'J': '', 'B': 20, 'N': '12.14'}
if el is None: # pas déjà trouvé
cur_sem, autre_sem = self.etud_semestres_de_etape(apo_data)
elt = sco_elts.get(code, None)
# elt est {'R': ADM, 'J': '', 'B': 20, 'N': '12.14'}
if elt is None: # pas déjà trouvé
self.etud_set_semestres_de_etape(apo_data)
for sem in apo_data.sems_etape:
el = self.search_elt_in_sem(code, sem, cur_sem, autre_sem)
if el is not None:
sco_elts[code] = el
elt = self.search_elt_in_sem(code, sem)
if elt is not None:
sco_elts[code] = elt
break
self.col_elts[code] = el
if el is None:
self.col_elts[code] = elt
if elt is None:
self.new_cols[col_id] = self.cols[col_id]
else:
try:
@ -373,7 +382,7 @@ class ApoEtud(dict):
# codes = set([apo_data.cols[col_id].code for col_id in apo_data.col_ids])
# return codes - set(sco_elts)
def search_elt_in_sem(self, code, sem, cur_sem, autre_sem) -> dict:
def search_elt_in_sem(self, code, sem) -> dict:
"""
VET code jury etape (en BUT, le code annuel)
ELP élément pédagogique: UE, module
@ -387,20 +396,29 @@ class ApoEtud(dict):
code (str): code apo de l'element cherché
sem (dict): semestre dans lequel on cherche l'élément
cur_sem (dict): semestre "courant" pour résultats annuels (VET)
autre_sem (dict): autre semestre utilisé pour calculé les résultats annuels (VET)
autre_sem (dict): autre semestre utilisé pour calculer les résultats annuels (VET)
Returns:
dict: with N, B, J, R keys, ou None si elt non trouvé
"""
etudid = self.etud["etudid"]
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if not self.cur_res:
log("search_elt_in_sem: no cur_res !")
return None
if sem["formsemestre_id"] == self.cur_res.formsemestre.id:
res = self.cur_res
elif (
self.autre_res and sem["formsemestre_id"] == self.autre_res.formsemestre.id
):
res = self.autre_res
else:
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if etudid not in nt.identdict:
if etudid not in res.identdict:
return None # etudiant non inscrit dans ce semestre
decision = nt.get_etud_decision_sem(etudid)
if not self.export_res_sdj and not decision:
if not self.export_res_sdj and not res.etud_has_decision(etudid):
# pas de decision de jury, on n'enregistre rien
# (meme si démissionnaire)
if not self.has_logged_no_decision:
@ -408,43 +426,46 @@ class ApoEtud(dict):
self.has_logged_no_decision = True
return VOID_APO_RES
if decision and decision["code"] == NAR:
self.is_NAR = True
if res.is_apc: # export BUT
self._but_load_validation_annuelle()
else:
decision = res.get_etud_decision_sem(etudid)
if decision and decision["code"] == NAR:
self.is_NAR = True
# Element semestre: (non BUT donc)
if code in {x.strip() for x in sem["elt_sem_apo"].split(",")}:
if self.export_res_sem:
return self.comp_elt_semestre(res, decision, etudid)
else:
return VOID_APO_RES
# Element etape (annuel ou non):
if sco_formsemestre.sem_has_etape(sem, code) or (
code in {x.strip() for x in sem["elt_annee_apo"].split(",")}
):
export_res_etape = self.export_res_etape
if (not export_res_etape) and cur_sem:
if (not export_res_etape) and self.cur_sem:
# exporte toujours le résultat de l'étape si l'étudiant est diplômé
Se = sco_cursus.get_situation_etud_cursus(
self.etud, cur_sem["formsemestre_id"]
self.etud, self.cur_sem["formsemestre_id"]
)
export_res_etape = Se.all_other_validated()
if export_res_etape:
return self.comp_elt_annuel(etudid, cur_sem, autre_sem)
return self.comp_elt_annuel(etudid)
else:
self.log.append("export étape désactivé")
return VOID_APO_RES
# Element semestre:
if code in {x.strip() for x in sem["elt_sem_apo"].split(",")}:
if self.export_res_sem:
return self.comp_elt_semestre(nt, decision, etudid)
else:
return VOID_APO_RES
# Elements UE
decisions_ue = nt.get_etud_decisions_ue(etudid)
for ue in nt.get_ues_stat_dict():
decisions_ue = res.get_etud_decisions_ue(etudid)
for ue in res.get_ues_stat_dict():
if ue["code_apogee"] and code in {
x.strip() for x in ue["code_apogee"].split(",")
}:
if self.export_res_ues:
if decisions_ue and ue["ue_id"] in decisions_ue:
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
ue_status = res.get_etud_ue_status(etudid, ue["ue_id"])
code_decision_ue = decisions_ue[ue["ue_id"]]["code"]
return dict(
N=self.fmt_note(ue_status["moy"] if ue_status else ""),
@ -459,14 +480,14 @@ class ApoEtud(dict):
return VOID_APO_RES
# Elements Modules
modimpls = nt.get_modimpls_dict()
modimpls = res.get_modimpls_dict()
module_code_found = False
for modimpl in modimpls:
module = modimpl["module"]
if module["code_apogee"] and code in {
x.strip() for x in module["code_apogee"].split(",")
}:
n = nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
n = res.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
if n != "NI" and self.export_res_modules:
return dict(N=self.fmt_note(n), B=20, J="", R="")
else:
@ -480,8 +501,7 @@ class ApoEtud(dict):
"""Calcul résultat apo semestre.
Toujours vide pour en BUT/APC.
"""
if self.is_apc:
# pas de code semestre en APC !
if self.is_apc: # garde fou: pas de code semestre en APC !
return dict(N="", B=20, J="", R="", M="")
if decision is None:
etud = Identite.get_etud(etudid)
@ -498,7 +518,7 @@ class ApoEtud(dict):
note_str = self.fmt_note(note)
return dict(N=note_str, B=20, J="", R=decision_apo, M="")
def comp_elt_annuel(self, etudid, cur_sem, autre_sem):
def comp_elt_annuel(self, etudid):
"""Calcul resultat annuel (VET) à partir du semestre courant
et de l'autre (le suivant ou le précédent complétant l'année scolaire)
En BUT, c'est la décision de jury annuelle (ApcValidationAnnee).
@ -516,18 +536,16 @@ class ApoEtud(dict):
# XXX cette règle est discutable, à valider
# log('comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id']))
if not cur_sem:
if not self.cur_sem:
# l'étudiant n'a pas de semestre courant ?!
self.log.append("pas de semestre courant")
log(f"comp_elt_annuel: etudid {etudid} has no cur_sem")
return VOID_APO_RES
cur_formsemestre = FormSemestre.query.get_or_404(cur_sem["formsemestre_id"])
cur_nt: NotesTableCompat = res_sem.load_formsemestre_results(cur_formsemestre)
if self.is_apc:
cur_decision = {} # comp_elt_semestre sera vide.
else:
cur_decision = cur_nt.get_etud_decision_sem(etudid)
cur_decision = self.cur_res.get_etud_decision_sem(etudid)
if not cur_decision:
# pas de decision => pas de résultat annuel
return VOID_APO_RES
@ -536,21 +554,17 @@ class ApoEtud(dict):
# ne touche pas aux RATs
return VOID_APO_RES
if not autre_sem:
if not self.autre_sem:
# formations monosemestre, ou code VET semestriel,
# ou jury intermediaire et etudiant non redoublant...
return self.comp_elt_semestre(cur_nt, cur_decision, etudid)
return self.comp_elt_semestre(self.cur_res, cur_decision, etudid)
autre_formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"])
autre_nt: NotesTableCompat = res_sem.load_formsemestre_results(
autre_formsemestre
)
# --- Traite le BUT à part:
if self.is_apc:
return self.comp_elt_annuel_apc(cur_nt, autre_nt, etudid)
return self.comp_elt_annuel_apc()
# --- Formations classiques
decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"])
autre_decision = autre_nt.get_etud_decision_sem(etudid)
autre_decision = self.autre_res.get_etud_decision_sem(etudid)
if not autre_decision:
# pas de decision dans l'autre => pas de résultat annuel
return VOID_APO_RES
@ -566,8 +580,8 @@ class ApoEtud(dict):
):
note_str = "0,01" # note non nulle pour les démissionnaires
else:
note = cur_nt.get_etud_moy_gen(etudid)
autre_note = autre_nt.get_etud_moy_gen(etudid)
note = self.cur_res.get_etud_moy_gen(etudid)
autre_note = self.autre_res.get_etud_moy_gen(etudid)
# print 'note=%s autre_note=%s' % (note, autre_note)
try:
moy_annuelle = (note + autre_note) / 2
@ -582,40 +596,46 @@ class ApoEtud(dict):
return dict(N=note_str, B=20, J="", R=decision_apo_annuelle, M="")
def comp_elt_annuel_apc(
self,
cur_res: ResultatsSemestreBUT,
autre_res: ResultatsSemestreBUT,
etudid: int,
):
def comp_elt_annuel_apc(self):
"""L'élément Apo pour un résultat annuel BUT.
cur_res : les résultats du semestre sur lequel a été appelé l'export.
self.cur_res == résultats du semestre sur lequel a été appelé l'export.
"""
if not self.validation_annee_but:
# pas de décision ou pas de sem. impair
return VOID_APO_RES
return dict(
N="",
B=20,
J="",
R=ScoDocSiteConfig.get_code_apo(self.validation_annee_but.code),
M="",
)
def _but_load_validation_annuelle(self):
"charge la validation de jury BUT annuelle"
# le semestre impair de l'année scolaire
if cur_res.formsemestre.semestre_id % 2:
formsemestre = cur_res.formsemestre
if self.cur_res.formsemestre.semestre_id % 2:
formsemestre = self.cur_res.formsemestre
elif (
autre_res
and autre_res.formsemestre.annee_scolaire()
== cur_res.formsemestre.annee_scolaire()
self.autre_res
and self.autre_res.formsemestre.annee_scolaire()
== self.cur_res.formsemestre.annee_scolaire()
):
formsemestre = autre_res.formsemestre
formsemestre = self.autre_res.formsemestre
assert formsemestre.semestre_id % 2
else:
# ne trouve pas de semestre impair
return VOID_APO_RES
validation: ApcValidationAnnee = ApcValidationAnnee.query.filter_by(
formsemestre_id=formsemestre.id, etudid=etudid
).first()
if validation is None:
return VOID_APO_RES
return dict(
N="", B=20, J="", R=ScoDocSiteConfig.get_code_apo(validation.code), M=""
self.validation_annee_but = None
return
self.validation_annee_but: ApcValidationAnnee = (
ApcValidationAnnee.query.filter_by(
formsemestre_id=formsemestre.id, etudid=self.etud["etudid"]
).first()
)
def etud_semestres_de_etape(self, apo_data):
"""
def etud_set_semestres_de_etape(self, apo_data):
"""Set .cur_sem and .autre_sem et charge les résultats.
Lorsqu'on a une formation semestrialisée mais avec un code étape annuel,
il faut considérer les deux semestres ((S1,S2) ou (S3,S4)) pour calculer
le code annuel (VET ou VRT1A (voir elt_annee_apo)).
@ -623,7 +643,7 @@ class ApoEtud(dict):
Pour les jurys intermediaires (janvier, S1 ou S3): (S2 ou S4) de la même
étape lors d'une année précédente ?
Renvoie le semestre "courant" et l'autre semestre, ou None s'il n'y en a pas.
Set cur_sem: le semestre "courant" et autre_sem, ou None s'il n'y en a pas.
"""
# Cherche le semestre "courant":
cur_sems = [
@ -648,19 +668,29 @@ class ApoEtud(dict):
cur_sem = None
for sem in cur_sems:
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
has_decision = nt.etud_has_decision(self.etud["etudid"])
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
has_decision = res.etud_has_decision(self.etud["etudid"])
if has_decision:
cur_sem = sem
self.cur_res = res
break
if cur_sem is None:
cur_sem = cur_sems[0] # aucun avec décision, prend le plus recent
if res.formsemestre.id == cur_sem["formsemestre_id"]:
self.cur_res = res
else:
formsemestre = FormSemestre.query.get_or_404(
cur_sem["formsemestre_id"]
)
self.cur_res = res_sem.load_formsemestre_results(formsemestre)
self.cur_sem = cur_sem
if apo_data.cur_semestre_id <= 0:
return (
cur_sem,
None,
) # "autre_sem" non pertinent pour sessions sans semestres
# "autre_sem" non pertinent pour sessions sans semestres:
self.autre_sem = None
self.autre_res = None
return
if apo_data.jury_intermediaire: # jury de janvier
# Le semestre suivant: exemple 2 si on est en jury de S1
@ -678,7 +708,7 @@ class ApoEtud(dict):
courant_annee_debut = apo_data.annee_scolaire + 1
courant_mois_debut = 1 # ou 2 (fev-jul)
else:
raise ValueError("invalid pediode value !") # bug ?
raise ValueError("invalid periode value !") # bug ?
courant_date_debut = "%d-%02d-01" % (
courant_annee_debut,
courant_mois_debut,
@ -709,15 +739,24 @@ class ApoEtud(dict):
autre_sem = None
for sem in autres_sems:
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
decision = nt.get_etud_decision_sem(self.etud["etudid"])
if decision:
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if res.is_apc:
has_decision = res.etud_has_decision(self.etud["etudid"])
else:
has_decision = res.get_etud_decision_sem(self.etud["etudid"])
if has_decision:
autre_sem = sem
break
if autre_sem is None:
autre_sem = autres_sems[0] # aucun avec decision, prend le plus recent
return cur_sem, autre_sem
self.autre_sem = autre_sem
# Charge les résultats:
if autre_sem:
formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"])
self.autre_res = res_sem.load_formsemestre_results(formsemestre)
else:
self.autre_res = None
class ApoData:
@ -911,8 +950,12 @@ class ApoData:
return elts # { code apo : ApoElt }
def apo_read_etuds(self, f) -> list[ApoEtud]:
"""Lecture des etudiants (et resultats) du fichier CSV Apogée"""
L = []
"""Lecture des étudiants (et résultats) du fichier CSV Apogée.
Les lignes "étudiant" commencent toujours par
`12345678 NOM PRENOM 15/05/2003`
le premier code étant le NIP.
"""
apo_etuds = []
while True:
line = f.readline()
if not line:
@ -921,10 +964,15 @@ class ApoData:
continue # silently ignore blank lines
line = line.strip(APO_NEWLINE)
fields = line.split(APO_SEP)
if len(fields) < 4:
raise ScoValueError(
"""Ligne étudiant invalide
(doit commencer par 'NIP NOM PRENOM dd/mm/yyyy')"""
)
cols = {} # { col_id : value }
for i, field in enumerate(fields):
cols[self.col_ids[i]] = field
L.append(
apo_etuds.append(
ApoEtud(
nip=fields[0], # id etudiant
nom=fields[1],
@ -940,7 +988,7 @@ class ApoData:
)
)
return L
return apo_etuds
def get_etape_apogee(self):
"""Le code etape: 'V1RT', donné par le code de l'élément VET"""

View File

@ -87,12 +87,12 @@ class SemSet(dict):
self.formsemestre_ids = []
cnx = ndb.GetDBConnexion()
if semset_id: # read existing set
L = semset_list(cnx, args={"semset_id": semset_id})
if not L:
semsets = semset_list(cnx, args={"semset_id": semset_id})
if not semsets:
raise ScoValueError(f"Ensemble inexistant ! (semset {semset_id})")
self["title"] = L[0]["title"]
self["annee_scolaire"] = L[0]["annee_scolaire"]
self["sem_id"] = L[0]["sem_id"]
self["title"] = semsets[0]["title"]
self["annee_scolaire"] = semsets[0]["annee_scolaire"]
self["sem_id"] = semsets[0]["sem_id"]
r = ndb.SimpleDictFetch(
"SELECT formsemestre_id FROM notes_semset_formsemestre WHERE semset_id = %(semset_id)s",
{"semset_id": semset_id},
@ -307,7 +307,7 @@ class SemSet(dict):
if self["sem_id"] == 1:
periode = "1re période (S1, S3)"
elif self["sem_id"] == 1:
elif self["sem_id"] == 2:
periode = "2de période (S2, S4)"
else:
periode = "non semestrialisée (LP, ...). Incompatible avec BUT."
@ -441,8 +441,14 @@ def do_semset_add_sem(semset_id, formsemestre_id):
if formsemestre_id == "":
raise ScoValueError("pas de semestre choisi !")
semset = SemSet(semset_id=semset_id)
semset.add(formsemestre.id)
return flask.redirect("apo_semset_maq_status?semset_id=%s" % semset_id)
semset.add(formsemestre_id)
return flask.redirect(
url_for(
"notes.apo_semset_maq_status",
scodoc_dept=g.scodoc_dept,
semset_id=semset_id,
)
)
def do_semset_remove_sem(semset_id, formsemestre_id):