diff --git a/app/but/jury_but.py b/app/but/jury_but.py
index 822a237fc8..a889c57da7 100644
--- a/app/but/jury_but.py
+++ b/app/but/jury_but.py
@@ -296,6 +296,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
[rcue for rcue in rcues_avec_niveau if rcue.est_validable()]
)
"le nombre de comp. validables (éventuellement par compensation)"
+ self.nb_rcue_valides = len(
+ [rcue for rcue in rcues_avec_niveau if rcue.code_valide()]
+ )
+ "le nombre de niveaux validés (déc. jury prise)"
self.nb_rcues_under_8 = len(
[rcue for rcue in rcues_avec_niveau if not rcue.est_suffisant()]
)
@@ -393,7 +397,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
RCUEs: {html.escape(str(self.rcues_annee))}
nb_competences: {getattr(self, "nb_competences", "-")}
- nb_nb_validables: {getattr(self, "nb_validables", "-")}
+ nb_validables: {getattr(self, "nb_validables", "-")}
codes: {self.codes}
explanation: {self.explanation}
diff --git a/app/comp/res_compat.py b/app/comp/res_compat.py
index fb8ba7f814..dfd051c191 100644
--- a/app/comp/res_compat.py
+++ b/app/comp/res_compat.py
@@ -299,7 +299,7 @@ class NotesTableCompat(ResultatsSemestre):
return sum([d.get("ects", 0.0) for d in decisions_ues.values()])
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.
+ """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
"""
diff --git a/app/models/but_validations.py b/app/models/but_validations.py
index d84339e00d..d5e622ecea 100644
--- a/app/models/but_validations.py
+++ b/app/models/but_validations.py
@@ -196,8 +196,8 @@ class RegroupementCoherentUE:
)
def est_validable(self) -> bool:
- """Vrai si ce RCU satisfait les conditions pour être validé
- Pour cela, il suffit que la moyenne des UE qui le constitue soit > 10
+ """Vrai si ce RCUE satisfait les conditions pour être validé,
+ c'est à dire que la moyenne des UE qui le constituent soit > 10
"""
return (self.moy_rcue is not None) and (
self.moy_rcue > sco_codes.BUT_BARRE_RCUE
diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py
index 96dd0bad2b..096cc84900 100644
--- a/app/scodoc/sco_etud.py
+++ b/app/scodoc/sco_etud.py
@@ -610,7 +610,7 @@ def log_unknown_etud():
log(f"unknown student: args={etud_args}")
-def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
+def get_etud_info(etudid=False, code_nip=False, filled=False) -> list[dict]:
"""infos sur un etudiant (API). If not found, returns empty list.
On peut spécifier etudid ou code_nip
ou bien cherche dans les arguments de la requête courante:
diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py
index c16eb1db34..bf9905f94c 100644
--- a/app/scodoc/sco_formsemestre.py
+++ b/app/scodoc/sco_formsemestre.py
@@ -167,9 +167,9 @@ def _formsemestre_enrich(sem):
sem["semestre_id"],
) # eg "DUT Informatique semestre 2"
- sem["dateord"] = ndb.DateDMYtoISO(sem["date_debut"])
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
sem["date_fin_iso"] = ndb.DateDMYtoISO(sem["date_fin"])
+ sem["dateord"] = sem["date_debut_iso"] # pour les tris
try:
mois_debut, annee_debut = sem["date_debut"].split("/")[1:]
except:
diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py
index 97f01f756c..6978e2eb3b 100644
--- a/app/scodoc/sco_report.py
+++ b/app/scodoc/sco_report.py
@@ -39,12 +39,14 @@ from operator import itemgetter
from flask import url_for, g, request
import pydot
+from app.but import jury_but
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, ScolarAutorisationInscription
+from app.models import FormationModalite
+from app.models.etudiants import Identite
import app.scodoc.sco_utils as scu
-from app.models import FormationModalite
from app.scodoc import notesdb as ndb
from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours
@@ -52,6 +54,7 @@ from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_preferences
+from app.scodoc import sco_pvjury
import sco_version
from app.scodoc.gen_tables import GenTable
from app import log
@@ -59,17 +62,28 @@ from app.scodoc.sco_codes_parcours import code_semestre_validant
MAX_ETUD_IN_DESCR = 20
+LEGENDES_CODES_BUT = {
+ "Nb_rcue_valides": "nb RCUE validés",
+ "decision_annee": "code jury annuel BUT",
+}
-def formsemestre_etuds_stats(sem, only_primo=False):
+
+def formsemestre_etuds_stats(sem: dict, only_primo=False):
"""Récupère liste d'etudiants avec etat et decision."""
- formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
+ formsemestre: FormSemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
T = nt.get_table_moyennes_triees()
+
+ # Décisions de jury BUT pour les semestres pairs seulement
+ jury_but_mode = (
+ formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0
+ )
# Construit liste d'étudiants du semestre avec leur decision
etuds = []
for t in T:
etudid = t[-1]
+ etudiant: Identite = Identite.query.get(etudid)
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
decision = nt.get_etud_decision_sem(etudid)
if decision:
@@ -87,6 +101,11 @@ def formsemestre_etuds_stats(sem, only_primo=False):
autorisations.sort()
autorisations_str = ", ".join(autorisations)
etud["devenir"] = autorisations_str
+ # Décisions de jury BUT (APC)
+ if jury_but_mode:
+ deca = jury_but.DecisionsProposeesAnnee(etudiant, formsemestre)
+ etud["nb_rcue_valides"] = deca.nb_rcue_valides
+ etud["decision_annee"] = deca.code_valide
# Ajout clé 'bac-specialite'
bs = []
if etud["bac"]:
@@ -100,13 +119,22 @@ def formsemestre_etuds_stats(sem, only_primo=False):
return etuds
-def is_primo_etud(etud, sem):
- """Determine si un (filled) etud a ete inscrit avant ce semestre.
- Regarde la liste des semestres dans lesquels l'étudiant est inscrit
+def is_primo_etud(etud: dict, sem: dict):
+ """Determine si un (filled) etud a été inscrit avant ce semestre.
+ Regarde la liste des semestres dans lesquels l'étudiant est inscrit.
+ Si semestre pair, considère comme primo-entrants ceux qui étaient
+ primo dans le précédent (S_{2n-1}).
"""
- now = sem["dateord"]
+ debut_cur = sem["date_debut_iso"]
+ # si semestre impair et sem. précédent contigu, recule date debut
+ if (
+ (len(etud["sems"]) > 1)
+ and (sem["semestre_id"] % 2 == 0)
+ and (etud["sems"][1]["semestre_id"] == (sem["semestre_id"] - 1))
+ ):
+ debut_cur = etud["sems"][1]["date_debut_iso"]
for s in etud["sems"]: # le + recent d'abord
- if s["dateord"] < now:
+ if s["date_debut_iso"] < debut_cur:
return False
return True
@@ -176,21 +204,20 @@ def _results_by_category(
# ajout titre ligne:
for (cat, l) in zip(categories, C):
- l["row_title"] = cat or "?"
+ l["row_title"] = cat if cat is not None else "?"
#
codes.append("sum")
codes.append("sumpercent")
- # on veut { ADM : ADM, ... }, était peu elegant en python 2.3:
- # titles = {}
- # map( lambda x,titles=titles: titles.__setitem__(x[0],x[1]), zip(codes,codes) )
- # Version moderne:
+ # on veut { ADM : ADM, ... }
titles = {x: x for x in codes}
+ # sauf pour
+ titles.update(LEGENDES_CODES_BUT)
titles["sum"] = "Total"
titles["sumpercent"] = "%"
titles["DEM"] = "Dém." # démissions
- titles["row_title"] = category_name
+ titles["row_title"] = titles.get(category_name, category_name)
return GenTable(
titles=titles,
columns_ids=codes,
@@ -234,14 +261,11 @@ def formsemestre_report(
#
tab.filename = scu.make_filename("stats " + sem["titreannee"])
- tab.origin = (
- "Généré par %s le " % sco_version.SCONAME + scu.timedate_human_repr() + ""
+ tab.origin = f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}"
+ tab.caption = (
+ f"Répartition des résultats par {category_name}, semestre {sem['titreannee']}"
)
- tab.caption = "Répartition des résultats par %s, semestre %s" % (
- category_name,
- sem["titreannee"],
- )
- tab.html_caption = "Répartition des résultats par %s." % category_name
+ tab.html_caption = f"Répartition des résultats par {category_name}."
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
if only_primo:
tab.base_url += "&only_primo=on"
@@ -265,17 +289,32 @@ def formsemestre_report(
def formsemestre_report_counts(
- formsemestre_id,
+ formsemestre_id: int,
format="html",
- category="bac",
- result="codedecision",
- allkeys=False,
- only_primo=False,
+ category: str = "bac",
+ result: str = None,
+ allkeys: bool = False,
+ only_primo: bool = False,
):
"""
Tableau comptage avec choix des categories
+ category: attribut en lignes
+ result: attribut en colonnes
+ only_primo: restreint aux primo-entrants (= non redoublants)
+ allkeys: pour le menu du choix de l'attribut en colonnes:
+ si vrai, toutes les valeurs présentes dans les données
+ sinon liste prédéfinie (voir ci-dessous)
"""
+ formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
+ # Décisions de jury BUT pour les semestres pairs seulement
+ jury_but_mode = (
+ formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0
+ )
+
+ if result is None:
+ result = "statut" if formsemestre.formation.is_apc() else "codedecision"
+
category_name = category.capitalize()
title = "Comptages " + category_name
etuds = formsemestre_etuds_stats(sem, only_primo=only_primo)
@@ -311,6 +350,8 @@ def formsemestre_report_counts(
"type_admission",
"boursier_prec",
]
+ if jury_but_mode:
+ keys += ["nb_rcue_valides", "decision_annee"]
keys.sort(key=scu.heterogeneous_sorting_key)
F = [
"""