Export Apogée: setup via test unitaire, maquette de test. Corrige cas 2 modules avec même code.

This commit is contained in:
Emmanuel Viennet 2023-07-01 17:42:04 +02:00
parent 0dda9157eb
commit da7f9a334f
8 changed files with 218 additions and 40 deletions

View File

@ -526,6 +526,11 @@ class FormSemestre(db.Model):
return ""
return ", ".join(sorted([etape.etape_apo for etape in self.etapes if etape]))
def add_etape(self, etape_apo: str):
"Ajoute une étape"
etape = FormSemestreEtape(formsemestre_id=self.id, etape_apo=etape_apo)
db.session.add(etape)
def regroupements_coherents_etud(self) -> list[tuple[UniteEns, UniteEns]]:
"""Calcule la liste des regroupements cohérents d'UE impliquant ce
formsemestre.

View File

@ -216,7 +216,12 @@ class ApoEtud(dict):
break
self.col_elts[code] = elt
if elt is None:
self.new_cols[col_id] = self.cols[col_id]
try:
self.new_cols[col_id] = self.cols[col_id]
except KeyError as exc:
raise ScoFormatError(
f"""Fichier Apogee invalide : ligne mal formatée ? <br>colonne <tt>{col_id}</tt> non déclarée ?"""
) from exc
else:
try:
self.new_cols[col_id] = sco_elts[code][
@ -343,14 +348,17 @@ class ApoEtud(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(",")
}:
if (
res.modimpl_inscr_df[modimpl["moduleimpl_id"]][etudid]
and module["code_apogee"]
and code in {x.strip() for x in module["code_apogee"].split(",")}
):
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:
module_code_found = True
if module_code_found:
return VOID_APO_RES
#

View File

@ -295,8 +295,15 @@ class ApoCSVReadWrite:
filename=self.get_filename(),
)
cols = {} # { col_id : value }
for i, field in enumerate(fields):
cols[self.col_ids[i]] = field
try:
for i, field in enumerate(fields):
cols[self.col_ids[i]] = field
except IndexError as exc:
raise
raise ScoFormatError(
f"Fichier Apogee incorrect (colonnes excédentaires ? (<tt>{i}/{field}</tt>))",
filename=self.get_filename(),
) from exc
etud_tuples.append(
ApoEtudTuple(
nip=fields[0], # id etudiant

View File

@ -1,6 +1,7 @@
[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
apo
but_gb
but_gccd
but_mlt

View File

@ -0,0 +1,76 @@
XX-APO_TITRES-XX
apoC_annee 2021/2022
apoC_cod_dip DIPTIS2
apoC_Cod_Exp 2
apoC_cod_vdi 17
apoC_Fichier_Exp export.txt
apoC_lib_dip BUT INFO TEST
apoC_Titre1 Maquette pour tests unitaires sur un BUT Info S2
apoC_Titre2
XX-APO_TYP_RES-XX
10 AB1 AB2 ABI ABJ ADM AJ AJRO C1 DEF DIF
18 AB1 AB2 ABI ABJ ADM ADMC ADMD AJ AJAC AJAR AJRO ATT B1 C1 COMP DEF DIF NAR
45 ABI ABJ ADAC ADM ADMC ADMD AIR AJ AJAR AJCP AJRO AJS ATT B1 B2 C1 COMP CRED DEF DES DETT DIF ENER ENRA EXC INFA INFO INST LC MACS N1 N2 NAR NON NSUI NVAL OUI SUIV SUPS TELE TOEF TOIE VAL VALC VALR
10 ABI ABJ ADMC COMP DEF DIS NVAL VAL VALC VALR
AB1 : Ajourné en B2 mais admis en B1 AB2 : ADMIS en B1 mais ajourné en B2 ABI : Absence ABJ : Absence justifiée ADM : Admis AJ : Ajourné AJRO : Ajourné - Réorientation Obligatoire C1 : Niveau C1 DEF : Défaillant DIF : Décision différée
AB1 : Ajourné en B2 mais admis en B1 AB2 : ADMIS en B1 mais ajourné en B2 ABI : Absence ABJ : Absence justifiée ADM : Admis ADMC : Admis avec compensation ADMD : Admis (passage avec dette) AJ : Ajourné AJAC : Ajourné mais accès autorisé à étape sup. AJAR : Ajourné et Admis A Redoubler AJRO : Ajourné - Réorientation Obligatoire ATT : En attente de décison B1 : Niveau B1 C1 : Niveau C1 COMP : Compensé DEF : Défaillant DIF : Décision différée NAR : Ajourné non admis à redoubler
ABI : Absence ABJ : Absence justifiée ADAC : Admis avant choix ADM : Admis ADMC : Admis avec compensation ADMD : Admis (passage avec dette) AIR : Ingénieur spécialité Informatique appr AJ : Ajourné AJAR : Ajourné et Admis A Redoubler AJCP : Ajourné mais autorisé à compenser AJRO : Ajourné - Réorientation Obligatoire AJS : Ajourné (note éliminatoire) ATT : En attente de décison B1 : Niveau B1 B2 : Niveau B2 C1 : Niveau C1 COMP : Compensé CRED : Eléments en crédits DEF : Défaillant DES : Désistement DETT : Eléments en dettes DIF : Décision différée ENER : Ingénieur spécialité Energétique ENRA : Ingénieur spécialité Energétique appr EXC : Exclu INFA : Ingénieur spécialité Informatique appr INFO : Ingénieur spécialié Informatique INST : Ingénieur spécialité Instrumentation LC : Liste complémentaire MACS : Ingénieur spécialité MACS N1 : Compétences CLES N2 : Niveau N2 NAR : Ajourné non admis à redoubler NON : Non NSUI : Non suivi(e) NVAL : Non Validé(e) OUI : Oui SUIV : Suivi(e) SUPS : Supérieur au seuil TELE : Ingénieur spéciailté Télécommunications TOEF : TOEFL TOIE : TOEIC VAL : Validé(e) VALC : Validé(e) par compensation VALR : Validé(e) Retrospectivement
ABI : Absence ABJ : Absence justifiée ADMC : Admis avec compensation COMP : Compensé DEF : Défaillant DIS : Dispense examen NVAL : Non Validé(e) VAL : Validé(e) VALC : Validé(e) par compensation VALR : Validé(e) Retrospectivement
XX-APO_COLONNES-XX
apoL_a01_code Type Objet Code Version Année Session Admission/Admissibilité Type Rés. Etudiant Numéro
apoL_a02_nom Nom
apoL_a03_prenom Prénom
apoL_a04_naissance Session Admissibilité Naissance
APO_COL_VAL_DEB
apoL_c0001 ELP V1INFU21 2021 0 1 N V1INFU21 - UE 2.1 Réaliser 0 1 Note
apoL_c0002 ELP V1INFU21 2021 0 1 B 0 1 Barème
apoL_c0003 ELP V1INFU21 2021 0 1 J 0 1 Pts Jury
apoL_c0004 ELP V1INFU21 2021 0 1 R 0 1 Résultat
apoL_c0005 ELP V1INFU22 2021 0 1 N V1INFU22 - UE 2.2 Optimiser 0 1 Note
apoL_c0006 ELP V1INFU22 2021 0 1 B 0 1 Barème
apoL_c0007 ELP V1INFU22 2021 0 1 J 0 1 Pts Jury
apoL_c0008 ELP V1INFU22 2021 0 1 R 0 1 Résultat
apoL_c0009 ELP V1INFU23 2021 0 1 N V1INFU23 - UE 2.3 Administrer 0 1 Note
apoL_c0010 ELP V1INFU23 2021 0 1 B 0 1 Barème
apoL_c0011 ELP V1INFU23 2021 0 1 J 0 1 Pts Jury
apoL_c0012 ELP V1INFU23 2021 0 1 R 0 1 Résultat
apoL_c0013 ELP V1INFU24 2021 0 1 N V1INFU24 - UE 2.4 Gérer 0 1 Note
apoL_c0014 ELP V1INFU24 2021 0 1 B 0 1 Barème
apoL_c0015 ELP V1INFU24 2021 0 1 J 0 1 Pts Jury
apoL_c0016 ELP V1INFU24 2021 0 1 R 0 1 Résultat
apoL_c0017 ELP V1INFU25 2021 0 1 N V1INFU25 - UE 2.5 Conduire 0 1 Note
apoL_c0018 ELP V1INFU25 2021 0 1 B 0 1 Barème
apoL_c0019 ELP V1INFU25 2021 0 1 J 0 1 Pts Jury
apoL_c0020 ELP V1INFU25 2021 0 1 R 0 1 Résultat
apoL_c0021 ELP V1INFU26 2021 0 1 N V1INFU26 - UE 2.6 Travailler 0 1 Note
apoL_c0022 ELP V1INFU26 2021 0 1 B 0 1 Barème
apoL_c0023 ELP V1INFU26 2021 0 1 J 0 1 Pts Jury
apoL_c0024 ELP V1INFU26 2021 0 1 R 0 1 Résultat
apoL_c0025 ELP VINFR201 2021 0 1 N VINFR201 - Développement orienté objets 0 1 Note
apoL_c0026 ELP VINFR201 2021 0 1 B 0 1 Barème
apoL_c0027 ELP VINFR207 2021 0 1 N VINFR207 - Graphes 0 1 Note
apoL_c0028 ELP VINFR207 2021 0 1 B 0 1 Barème
apoL_c0029 ELP VINFPOR2 2021 0 1 N VINFPOR2 - Portfolio 0 1 Note
apoL_c0030 ELP VINFPOR2 2021 0 1 B 0 1 Barème
apoL_c0031 ELP TIRW2 2021 0 1 N TIRW2 - Semestre 2 BUT INFO 2 0 1 Note
apoL_c0032 ELP TIRW2 2021 0 1 B 0 1 Barème
apoL_c0033 ELP TIRW2 2021 0 1 J 0 1 Pts Jury
apoL_c0034 ELP TIRW2 2021 0 1 R 0 1 Résultat
apoL_c0035 ELP TIRO 2021 0 1 N TIRO - Année BUT 1 RT 0 1 Note
apoL_c0036 ELP TIRO 2021 0 1 B 0 1 Barème
apoL_c0037 VET TI1 117 2021 0 1 N TI1 - BUT INFO an1 0 1 Note
apoL_c0038 VET TI1 117 2021 0 1 B 0 1 Barème
apoL_c0039 VET TI1 117 2021 0 1 J 0 1 Pts Jury
apoL_c0040 VET TI1 117 2021 0 1 R 0 1 Résultat
APO_COL_VAL_FIN
apoL_c0041 APO_COL_VAL_FIN
XX-APO_VALEURS-XX
apoL_a01_code apoL_a02_nom apoL_a03_prenom apoL_a04_naissance apoL_c0001 apoL_c0002 apoL_c0003 apoL_c0004 apoL_c0005 apoL_c0006 apoL_c0007 apoL_c0008 apoL_c0009 apoL_c0010 apoL_c0011 apoL_c0012 apoL_c0013 apoL_c0014 apoL_c0015 apoL_c0016 apoL_c0017 apoL_c0018 apoL_c0019 apoL_c0020 apoL_c0021 apoL_c0022 apoL_c0023 apoL_c0024 apoL_c0025 apoL_c0026 apoL_c0027 apoL_c0028 apoL_c0029 apoL_c0030 apoL_c0031 apoL_c0032 apoL_c0033 apoL_c0034 apoL_c0035 apoL_c0036 apoL_c0037 apoL_c0038 apoL_c0039 apoL_c0040
1001 ex_a1 Jean 10/01/2003
1002 ex_a2 Lucie 11/01/2003
1003 ex_b1 Hélène 11/01/2003
1004 ex_b2 Rose 11/01/2003

View File

@ -1,4 +1,4 @@
# Tests unitaires
# Tests unitaires
# Le BUT Info a 4 parcours qui partagent certains niveaux de compétences
# mais à ces niveaux sont associés des UEs dont les coefficients des ressources
# varient selon le parcours.
@ -14,58 +14,58 @@ Formation:
# nota: les associations UE/Niveaux sont déjà données dans ce fichier XML.
ues:
# S1
'UE11':
"UE11":
annee: BUT1
'UE12':
"UE12":
annee: BUT1
'UE13':
"UE13":
annee: BUT1
'UE14':
"UE14":
annee: BUT1
'UE15':
"UE15":
annee: BUT1
'UE16':
"UE16":
annee: BUT1
# S2
'UE21':
"UE21":
annee: BUT1
'UE22':
"UE22":
annee: BUT1
'UE23':
"UE23":
annee: BUT1
'UE24':
"UE24":
annee: BUT1
'UE25':
"UE25":
annee: BUT1
'UE26':
"UE26":
annee: BUT1
# S3
'UE31':
"UE31":
annee: BUT2
'UE32':
"UE32":
annee: BUT2
'UE33':
"UE33":
annee: BUT2
'UE34':
"UE34":
annee: BUT2
'UE35':
"UE35":
annee: BUT2
'UE36':
"UE36":
annee: BUT2
# S4
'UE41-A': # UE pour le parcours A
"UE41-A": # UE pour le parcours A
annee: BUT2
'UE41-B': # UE pour le parcours B (même contenu, coefs différents)
"UE41-B": # UE pour le parcours B (même contenu, coefs différents)
annee: BUT2
'UE42':
"UE42":
annee: BUT2
'UE43':
"UE43":
annee: BUT2
'UE44':
"UE44":
annee: BUT2
'UE45':
"UE45":
annee: BUT2
'UE46':
"UE46":
annee: BUT2
FormSemestres:
@ -74,37 +74,41 @@ FormSemestres:
idx: 1
date_debut: 2021-09-01
date_fin: 2022-01-15
codes_parcours: ['A', 'B']
codes_parcours: ["A", "B"]
S2:
idx: 2
date_debut: 2022-01-16
date_fin: 2022-06-30
codes_parcours: ['A', 'B']
codes_parcours: ["A", "B"]
elt_sem_apo: TIRW2
elt_annee_apo: TIRO
etape_apo: TI1!117
S3:
idx: 3
date_debut: 2022-09-01
date_fin: 2023-01-15
codes_parcours: ['A', 'B']
codes_parcours: ["A", "B"]
S4:
idx: 4
date_debut: 2023-01-16
date_fin: 2023-06-30
codes_parcours: ['A', 'B']
codes_parcours: ["A", "B"]
S5:
idx: 5
date_debut: 2023-09-01
date_fin: 2024-01-15
codes_parcours: ['A', 'B']
codes_parcours: ["A", "B"]
S6:
idx: 6
date_debut: 2024-01-16
date_fin: 2024-06-30
codes_parcours: ['A', 'B']
codes_parcours: ["A", "B"]
Etudiants:
ex_a1: # cursus S1 -> S6, valide tout
prenom: Jean
civilite: M
code_nip: 1001
formsemestres:
# on ne note que le portfolio, qui affecte toutes les UEs
S1:
@ -115,6 +119,7 @@ Etudiants:
parcours: A
notes_modules:
"P2": 12
"R2.04-A": 16
S3:
parcours: A
notes_modules:
@ -135,6 +140,7 @@ Etudiants:
ex_a2: # cursus S1 -> S6, valide tout sauf S5
prenom: Lucie
civilite: F
code_nip: 1002
formsemestres:
# on ne note que le portfolio, qui affecte toutes les UEs
S1:
@ -145,6 +151,7 @@ Etudiants:
parcours: A
notes_modules:
"P2": 12
"R2.04-A": 17
S3:
parcours: A
notes_modules:
@ -161,10 +168,11 @@ Etudiants:
parcours: A
notes_modules:
"P6-A": 16
ex_b1: # cursus S1 -> S6, valide tout
prenom: Hélène
civilite: F
code_nip: 1003
formsemestres:
# on ne note que le portfolio, qui affecte toutes les UEs
S1:
@ -175,6 +183,7 @@ Etudiants:
parcours: B
notes_modules:
"P2": 12
"R2.04-B": 18
S3:
parcours: B
notes_modules:
@ -191,10 +200,11 @@ Etudiants:
parcours: B
notes_modules:
"P6-B": 16
ex_b2: # cursus S1 -> S6, valide tout sauf S6
prenom: Rose
civilite: F
code_nip: 1004
formsemestres:
# on ne note que le portfolio, qui affecte toutes les UEs
S1:
@ -205,6 +215,7 @@ Etudiants:
parcours: B
notes_modules:
"P2": 12
"R2.04-B": 19
S3:
parcours: B
notes_modules:

View File

@ -0,0 +1,54 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
""" Test export Apogéee
Ces tests sont généralement lents (construction de la base),
et donc marqués par `@pytest.mark.slow`.
Certains sont aussi marqués par @pytest.mark.lemans ou @pytest.mark.lyon
pour lancer certains tests spécifiques seulement.
Exemple utilisation spécifique:
# test sur "apo" seulement:
pytest --pdb -m apo tests/unit/test_apogee_export.py
Elements Apogée simulés:
- UEs : TIU2x
- Ressources: R2.xy : TIRxy (VRETR201 -> TIR201)
"""
import pytest
from tests.unit import yaml_setup, yaml_setup_but
import app
from app.but.jury_but_validation_auto import formsemestre_validation_auto_but
from app.models import Formation, FormSemestre, UniteEns
from config import TestConfig
DEPT = TestConfig.DEPT_TEST
@pytest.mark.skip # Ce "test" est utilisé comme setup pour développer, pas comme test unitaire routinier
@pytest.mark.slow
@pytest.mark.apo
def test_refcomp_niveaux_info(test_client):
"""Test niveaux / parcours / UE pour un BUT INFO
avec parcours A et B, même compétences mais coefs différents
selon le parcours.
"""
# WIP
# pour le moment juste le chargement de la formation, du ref. comp, et des UE du S4.
app.set_sco_dept(DEPT)
doc, formation, formsemestre_titres = yaml_setup.setup_from_yaml(
"tests/ressources/yaml/cursus_but_info.yaml"
)
for formsemestre_titre in formsemestre_titres:
formsemestre = yaml_setup.create_formsemestre_with_etuds(
doc, formation, formsemestre_titre
)
#

View File

@ -99,6 +99,9 @@ def create_formsemestre(
titre: str,
date_debut: str,
date_fin: str,
elt_sem_apo: str = None,
elt_annee_apo: str = None,
etape_apo: str = None,
) -> FormSemestre:
"Création d'un formsemestre, avec ses modimpls et évaluations"
assert formation.is_apc() or not parcours # parcours seulement si APC
@ -110,11 +113,15 @@ def create_formsemestre(
semestre_id=semestre_id,
date_debut=date_debut,
date_fin=date_fin,
elt_sem_apo=elt_sem_apo,
elt_annee_apo=elt_annee_apo,
)
# set responsable (list)
a_user = User.query.first()
formsemestre.responsables = [a_user]
db.session.add(formsemestre)
db.session.flush()
formsemestre.add_etape(etape_apo)
# Ajoute tous les modules du semestre sans parcours OU avec l'un des parcours indiqués
sem_parcours_ids = {p.id for p in parcours}
modules = [
@ -228,6 +235,10 @@ def setup_formsemestre(
assert parcour is not None
parcours.append(parcour)
elt_sem_apo = infos.get("elt_sem_apo")
elt_annee_apo = infos.get("elt_annee_apo")
etape_apo = infos.get("etape_apo")
formsemestre = create_formsemestre(
formation,
parcours,
@ -235,6 +246,9 @@ def setup_formsemestre(
formsemestre_titre,
infos["date_debut"],
infos["date_fin"],
elt_sem_apo=elt_sem_apo,
elt_annee_apo=elt_annee_apo,
etape_apo=etape_apo,
)
db.session.flush()
@ -257,6 +271,7 @@ def inscrit_les_etudiants(doc: dict, formsemestre_titre: str = ""):
# Création des étudiants (sauf si déjà existants)
prenom = infos.get("prenom", "prénom")
civilite = infos.get("civilite", "X")
code_nip = infos.get("code_nip", None)
etud = Identite.query.filter_by(
nom=nom, prenom=prenom, civilite=civilite
).first()
@ -266,6 +281,7 @@ def inscrit_les_etudiants(doc: dict, formsemestre_titre: str = ""):
nom=nom,
prenom=prenom,
civilite=civilite,
code_nip=code_nip,
)
db.session.add(etud)
db.session.commit()