forked from dubacq/scodoc-cohortes
1048 lines
34 KiB
Python
Executable File
1048 lines
34 KiB
Python
Executable File
#!/usr/bin/python3
|
|
import requests
|
|
from requests.auth import HTTPBasicAuth
|
|
import csv, os, sys
|
|
import json
|
|
from datetime import datetime
|
|
import re
|
|
|
|
debug = True # Not used
|
|
techno = False # global flag
|
|
blocking = True # Die if csv is incorrect
|
|
|
|
|
|
def blockordie():
|
|
if blocking:
|
|
sys.exit(2)
|
|
|
|
|
|
# Read args from the command line
|
|
# then read config from {orderkey}.json
|
|
|
|
depts = []
|
|
orderkey = ""
|
|
|
|
|
|
def cli_check():
|
|
global techno
|
|
global orderkey
|
|
global depts
|
|
index = 1
|
|
if sys.argv[index][:2] == "--":
|
|
if sys.argv[index] == "--techno":
|
|
techno = True
|
|
else:
|
|
print(f"{sys.argv[0]} [--techno] DEPT [DEPT] ...")
|
|
sys.exit(0)
|
|
index += 1
|
|
orderkey = "_".join(sys.argv[index:])
|
|
depts = sys.argv[index:]
|
|
if len(depts) == 0:
|
|
print(f"{sys.argv[0]} [--techno] DEPT [DEPT] ...")
|
|
sys.exit(0)
|
|
|
|
|
|
cli_check()
|
|
|
|
|
|
def read_conf(key):
|
|
if os.path.exists(f"{key}.json"):
|
|
with open(f"{key}.json", "r") as f:
|
|
return json.load(f)
|
|
return {}
|
|
|
|
|
|
conf = read_conf(orderkey)
|
|
|
|
|
|
# Manage default values
|
|
def conf_value(xkey: str):
|
|
if xkey in conf:
|
|
return conf[xkey]
|
|
if xkey == "width":
|
|
return 1300
|
|
if xkey == "height":
|
|
return 900
|
|
if xkey[-9:] == "separator":
|
|
return " "
|
|
if xkey == "nick":
|
|
return "{diplome}{rank}{department}{modalite}{parcours}"
|
|
if xkey == "extnick":
|
|
return "{rank}{diplomenobut}{modaliteshort}"
|
|
if xkey == "orders":
|
|
return [[], [], [], [], []]
|
|
return {}
|
|
|
|
|
|
# READ username, password, server, baseyear from secrets file
|
|
# This file should be kept really safe
|
|
# Currently, only baseyear = 2021 has been tested
|
|
|
|
# TODO: a CSV file would be more declarative and easier to manage
|
|
|
|
from dotenv import dotenv_values
|
|
|
|
|
|
def read_secrets():
|
|
global server, username, password, baseyear
|
|
config = dotenv_values(".env")
|
|
server = config["server"]
|
|
username = config["username"]
|
|
password = config["password"]
|
|
baseyear = int(config["baseyear"])
|
|
|
|
|
|
read_secrets()
|
|
|
|
|
|
student = {}
|
|
CACHE_FILE = "cache.json"
|
|
|
|
|
|
def load_cache(cache_file):
|
|
if os.path.exists(cache_file):
|
|
with open(cache_file, "r") as f:
|
|
return json.load(f)
|
|
return {}
|
|
|
|
|
|
def save_cache(cache, file=None):
|
|
if file == None:
|
|
file = CACHE_FILE
|
|
with open(CACHE_FILE, "w") as f:
|
|
json.dump(cache, f)
|
|
|
|
|
|
cache = load_cache(CACHE_FILE)
|
|
|
|
|
|
# Read color theme
|
|
# There are default color values, so may be it should just join the conf.json file
|
|
|
|
|
|
def read_theme():
|
|
if os.path.exists("theme.csv"):
|
|
with open("theme.csv", newline="") as csvfile:
|
|
csvreader = csv.reader(csvfile, delimiter=",", quotechar='"')
|
|
for row in csvreader:
|
|
if len(row) == 0:
|
|
continue
|
|
elif len(row[0]) == 0:
|
|
print("Wrong line in theme : " + str(row))
|
|
blockordie()
|
|
elif row[0][0] == "#":
|
|
continue
|
|
else:
|
|
colors[row[0]] = row[1]
|
|
|
|
|
|
colors = {
|
|
"+DUT": "#0040C0",
|
|
"QUIT": "#00FF00",
|
|
"SUCCESS": "#0000FF",
|
|
"NORMAL": "#C0C0C0",
|
|
"FAIL": "#FF4040",
|
|
"OLD": "#FF8000",
|
|
"NEW": "#FFFF00",
|
|
"TRANSPARENT": "#FFFFFF.0",
|
|
"RED": "#000000",
|
|
}
|
|
read_theme()
|
|
|
|
# Read redirects
|
|
# Only one file, since various combinations including same departments should
|
|
# use the same redirections ("no jury yet but almost sure it will be ...")
|
|
|
|
|
|
def read_redirects():
|
|
if os.path.exists("redirect.csv"):
|
|
with open("redirect.csv", newline="") as csvfile:
|
|
csvreader = csv.reader(csvfile, delimiter=",", quotechar='"')
|
|
for row in csvreader:
|
|
if len(row) == 0:
|
|
continue
|
|
elif len(row[0]) == 0:
|
|
print("Wrong line in redirect : " + str(row))
|
|
blockordie()
|
|
elif row[0][0] == "#":
|
|
continue
|
|
else:
|
|
redirects[int(row[0])] = row[1]
|
|
|
|
|
|
redirects = {}
|
|
read_redirects()
|
|
|
|
|
|
# Gestion globale d'un jeton pour l'API
|
|
token = None
|
|
|
|
|
|
def get_json(url: str, params=None):
|
|
print(f"Requesting {url}")
|
|
global token
|
|
if token == None:
|
|
url_token = f"{server}/api/tokens"
|
|
response = requests.post(url_token, auth=HTTPBasicAuth(username, password))
|
|
if response.status_code == 200:
|
|
token = response.json().get("token")
|
|
else:
|
|
print(
|
|
f"Erreur de récupération de token: {response.status_code} - {response.text}"
|
|
)
|
|
sys.exit(1)
|
|
headers = {"Authorization": f"Bearer {token}"}
|
|
response = requests.get(url, headers=headers, params=params)
|
|
if response.status_code == 200:
|
|
# Afficher la réponse JSON
|
|
return response.json()
|
|
else:
|
|
print(f"Erreur avec {url}: {response.status_code} - {response.text}")
|
|
sys.exit(1)
|
|
|
|
|
|
formsem_dept = {}
|
|
formsem_department = {}
|
|
|
|
|
|
def get_formsem_from_dept(dept):
|
|
if "formsems" in cache and dept in cache["formsems"]:
|
|
return cache["formsems"][dept]
|
|
if "formsems" not in cache:
|
|
cache["formsems"] = {}
|
|
if "sem" not in cache:
|
|
cache["sem"] = {}
|
|
query_url = f"{server}{dept}/api/formsemestres/query"
|
|
formsemestres = get_json(query_url)
|
|
result = []
|
|
for sem in formsemestres:
|
|
semid = str(sem["formsemestre_id"])
|
|
formsem_dept[semid] = dept
|
|
cache["sem"][semid] = sem
|
|
result.append(semid)
|
|
cache["formsems"][dept] = result
|
|
save_cache(cache)
|
|
return result
|
|
|
|
|
|
def get_formations_from_dept(dept):
|
|
global server
|
|
if "formations" in cache and dept in cache["formations"]:
|
|
return cache["formations"][dept]
|
|
if "formations" not in cache:
|
|
cache["formations"] = {}
|
|
query_url = f"{server}{dept}/api/formations"
|
|
formations = get_json(query_url)
|
|
result = []
|
|
for f in formations:
|
|
if f["type_parcours"] == 700:
|
|
result.append(f["formation_id"])
|
|
cache["formations"][dept] = result
|
|
save_cache(cache)
|
|
return result
|
|
|
|
|
|
def get_etuds_from_formsem(dept, semid):
|
|
if type(semid) == type(0):
|
|
semid = str(semid)
|
|
if "etudlist" in cache and semid in cache["etudlist"]:
|
|
return cache["etudlist"][semid]
|
|
if "etudlist" not in cache:
|
|
cache["etudlist"] = {}
|
|
query_url = f"{server}{dept}/api/formsemestre/{semid}/etudiants/long"
|
|
result = get_json(query_url)
|
|
cache["etudlist"][semid] = result
|
|
save_cache(cache)
|
|
return result
|
|
|
|
|
|
def get_jury_from_formsem(dept, semid):
|
|
if type(semid) == type(0):
|
|
semid = str(semid)
|
|
if "semjury" in cache and semid in cache["semjury"]:
|
|
return cache["semjury"][semid]
|
|
if "semjury" not in cache:
|
|
cache["semjury"] = {}
|
|
|
|
# query_url = f"{server}{dept}/Scolarite/Notes/formsemestre_recapcomplet?formsemestre_id={semid}&mode_jury=1&tabformat=json"
|
|
query_url = f"{server}{dept}/api/formsemestre/{semid}/decisions_jury"
|
|
result = get_json(query_url)
|
|
cache["semjury"][semid] = result
|
|
save_cache(cache)
|
|
return result
|
|
|
|
|
|
def get_override(sem, xkey, default=None):
|
|
overrides = conf_value("override")
|
|
for j in ["titre_num", "titre", "session_id"]:
|
|
if (
|
|
j in sem
|
|
and j in overrides
|
|
and sem[j] in overrides[j]
|
|
and xkey in overrides[j][sem[j]]
|
|
):
|
|
return overrides[j][sem[j]][xkey]
|
|
return default
|
|
|
|
|
|
def analyse_student(semobj, etud, year=None):
|
|
"""Returns the final (department,diplome,rank,modalite,parcours,nickname) tuple from etudid in semid, taking into accounts overrides."""
|
|
session_id = semobj["session_id"].split("-")
|
|
department = session_id[0]
|
|
diplome = session_id[1]
|
|
modalite = session_id[2]
|
|
if year == None:
|
|
if semobj["semestre_id"] < 0:
|
|
rank = 1
|
|
else:
|
|
rank = (semobj["semestre_id"] + 1) // 2
|
|
else:
|
|
rank = year
|
|
parcours = None
|
|
groups = []
|
|
if "groups" in etud:
|
|
for x in etud["groups"]:
|
|
if x["partition_name"] == "Parcours":
|
|
parcours = x["group_name"]
|
|
groups.append(x["group_name"])
|
|
if parcours == None:
|
|
parcours = ""
|
|
parcours = get_override(semobj, "parcours", parcours)
|
|
department = get_override(semobj, "department", department)
|
|
rank = get_override(semobj, "rank", rank)
|
|
diplome = get_override(semobj, "diplome", diplome)
|
|
modalite = get_override(semobj, "modalite", modalite)
|
|
if len(modalite) > 0 and modalite[0] == "G":
|
|
goal = modalite.split(":")[1:]
|
|
modalite = None
|
|
for g in goal:
|
|
gg = g.split("=")
|
|
# print(f"Looking for {gg[0]} yielding {gg[1]} out of {groupes}")
|
|
if gg[0] in groups:
|
|
modalite = gg[1]
|
|
nick = conf_value("nick")
|
|
if len(department) > 0:
|
|
nick = nick.replace(
|
|
"{department}", conf_value("department_separator") + department
|
|
)
|
|
else:
|
|
nick = nick.replace("{department}", "")
|
|
if len(diplome) > 0:
|
|
nick = nick.replace("{diplome}", conf_value("diplome_separator") + diplome)
|
|
else:
|
|
nick = nick.replace("{diplome}", "")
|
|
if len(str(rank)) > 0:
|
|
nick = nick.replace("{rank}", conf_value("rank_separator") + str(rank))
|
|
else:
|
|
nick = nick.replace("{rank}", "")
|
|
if len(modalite) > 0:
|
|
nick = nick.replace("{modalite}", conf_value("modalite_separator") + modalite)
|
|
else:
|
|
nick = nick.replace("{modalite}", "")
|
|
if len(parcours) > 0:
|
|
nick = nick.replace("{parcours}", conf_value("parcours_separator") + parcours)
|
|
else:
|
|
nick = nick.replace("{parcours}", "")
|
|
formsem_department[str(semobj["id"])] = department
|
|
return department, diplome, rank, modalite, parcours, nick
|
|
|
|
|
|
def nick(semobj, etud):
|
|
department, diplome, rank, modalite, parcours, nick = analyse_student(semobj, etud)
|
|
return nick
|
|
|
|
|
|
def get_dept_from_sem(semid):
|
|
return formsem_department[str(semid)]
|
|
|
|
|
|
oldsems = set()
|
|
oldsemsdept = {}
|
|
futuresems = set()
|
|
futuresemsdept = {}
|
|
bacs = set()
|
|
cohort_nip = set()
|
|
|
|
|
|
def analyse_depts():
|
|
for dept in depts:
|
|
formsems = get_formsem_from_dept(dept)
|
|
for semid in formsems:
|
|
# Check if this is a part of the cohort
|
|
# or a future/old semester
|
|
sem = cache["sem"][str(semid)]
|
|
if sem["semestre_id"] < 0:
|
|
year = 1
|
|
else:
|
|
year = (sem["semestre_id"] + 1) // 2
|
|
offset = sem["annee_scolaire"] - baseyear - year + 1
|
|
if offset < 0 and offset > -4:
|
|
oldsems.add(str(semid))
|
|
oldsemsdept[semid] = dept
|
|
if offset > 0 and offset < 4:
|
|
futuresems.add(str(semid))
|
|
futuresemsdept[semid] = dept
|
|
if offset != 0:
|
|
continue
|
|
if offset == 0 and sem["formation"]["type_parcours"] != 700:
|
|
continue
|
|
# This is a BUT semester, part of the cohort
|
|
# 0,1 : preceding year ; 2-7 : cohort ; 8+ : future
|
|
if sem["semestre_id"] < 0:
|
|
bucket = 1
|
|
else:
|
|
bucket = str(int(sem["semestre_id"] - 1))
|
|
# Ici, le semestre est donc un semestre intéressant
|
|
# On prélève tous les étudiants, et on remplit leur cursus
|
|
etuds = get_etuds_from_formsem(dept, semid)
|
|
jurys = get_jury_from_formsem(dept, semid)
|
|
key = sem["titre_num"]
|
|
for etud in etuds:
|
|
etudid = etud["id"]
|
|
if etudid in student:
|
|
studentsummary = student[etudid]
|
|
else:
|
|
studentsummary = {}
|
|
studentsummary["cursus"] = {} # Cursus is semid
|
|
studentsummary["etudid"] = {} # useful when merging students
|
|
studentsummary["pseudodept"] = {} # pseudo-dept for interdept
|
|
studentsummary["diplome"] = {} # diplome name
|
|
studentsummary["rank"] = {} # rank
|
|
studentsummary["modalite"] = {} # modalite
|
|
studentsummary["parcours"] = {} # parcours
|
|
studentsummary["nickname"] = {} # nick
|
|
studentsummary["dept"] = dept # useful when merging students
|
|
studentsummary["bac"] = "" # usually
|
|
department, diplome, rank, modalite, parcours, nick = analyse_student(
|
|
sem, etud, year
|
|
)
|
|
if "bac" in etud["admission"]:
|
|
studentsummary["bac"] = etud["admission"]["bac"]
|
|
else:
|
|
studentsummary["bac"] = "INCONNU"
|
|
bacs.add(studentsummary["bac"])
|
|
# We skip non-techno students if we are in techno mode
|
|
# If we want a mixed reporting, maybe we should change this
|
|
if techno and studentsummary["bac"][:2] != "ST": # TODO: change this
|
|
continue
|
|
if bucket in studentsummary["cursus"]:
|
|
semestreerreur = int(bucket) + 1
|
|
print(
|
|
f"// Élève {etudid} dans deux semestres à la fois : S{semestreerreur}, semestres {studentsummary['cursus'][bucket]} et {semid}"
|
|
)
|
|
if "dept" in studentsummary and studentsummary["dept"] != dept:
|
|
print(
|
|
f"// Élève ayant changé de département {dept},{studentsummary['dept']}"
|
|
)
|
|
# department, diplome, rank, modalite, parcours, nick = analyse_student(
|
|
studentsummary["cursus"][bucket] = semid
|
|
studentsummary["etudid"][bucket] = etudid
|
|
studentsummary["pseudodept"][bucket] = department
|
|
studentsummary["diplome"][bucket] = diplome
|
|
studentsummary["rank"][bucket] = rank
|
|
studentsummary["modalite"][bucket] = modalite
|
|
studentsummary["parcours"][bucket] = parcours
|
|
studentsummary["nickname"][bucket] = nick
|
|
studentsummary["debug"] = etud["sort_key"] # TODO: REMOVE
|
|
studentsummary["unid"] = etud["code_nip"]
|
|
cohort_nip.add(etud["code_nip"])
|
|
student[etudid] = studentsummary
|
|
|
|
|
|
analyse_depts()
|
|
|
|
|
|
def allseeingodin():
|
|
"""This function changes the student lists by peeking in the past and the future to know which students come from another cohort or go into a later cohort."""
|
|
oldstudents = {}
|
|
oldstudentslevel = {}
|
|
futurestudents = {}
|
|
futurestudentslevel = {}
|
|
|
|
# We look for the latest "old semester" in which every (old) student went
|
|
for semid in oldsems:
|
|
sem = cache["sem"][semid]
|
|
semlevel = sem["semestre_id"]
|
|
semlevel = abs(semlevel)
|
|
dept = oldsemsdept[semid]
|
|
etuds = get_etuds_from_formsem(dept, semid)
|
|
for etud in etuds:
|
|
nip = etud["code_nip"]
|
|
if nip not in cohort_nip:
|
|
continue
|
|
if nip not in oldstudentslevel or semlevel > oldstudentslevel[nip]:
|
|
oldstudentslevel[nip] = semlevel
|
|
oldstudents[nip] = [semid, nick(sem, etud)]
|
|
for semid in futuresems:
|
|
sem = cache["sem"][semid]
|
|
if sem["formation"]["type_parcours"] != 700:
|
|
# We are only interested in BUT continuations (for now)
|
|
continue
|
|
semlevel = sem["semestre_id"]
|
|
semlevel = abs(semlevel)
|
|
dept = futuresemsdept[semid]
|
|
etuds = get_etuds_from_formsem(dept, semid)
|
|
for etud in etuds:
|
|
nip = etud["code_nip"]
|
|
if nip not in cohort_nip:
|
|
continue
|
|
if nip not in futurestudentslevel or semlevel > futurestudentslevel[nip]:
|
|
futurestudentslevel[nip] = semlevel
|
|
futurestudents[nip] = nick(sem, etud)
|
|
|
|
unification = {}
|
|
|
|
duplicates = {}
|
|
|
|
for etudid in student.keys():
|
|
unid = student[etudid]["unid"]
|
|
if unid in unification:
|
|
if unid not in duplicates:
|
|
duplicates[unid] = [unification[unid]]
|
|
duplicates[unid].append(etudid)
|
|
unification[unid] = etudid
|
|
if unid in oldstudents:
|
|
student[etudid]["old"] = oldstudents[unid][1]
|
|
student[etudid]["oldsem"] = oldstudents[unid][0]
|
|
if unid in futurestudents:
|
|
student[etudid]["future"] = futurestudents[unid]
|
|
|
|
lastsem = -1
|
|
best = []
|
|
for unid in duplicates:
|
|
for suppidx in duplicates[unid][1:]:
|
|
supp = student[suppidx]
|
|
if str(lastsem) in supp["cursus"]:
|
|
best.append(suppidx)
|
|
for sem in range(5, lastsem, -1):
|
|
if str(sem) in supp["cursus"]:
|
|
lastsem = sem
|
|
best = [suppidx]
|
|
break
|
|
if len(best) > 1:
|
|
print(
|
|
f"// Warning: cannot chose last semester for NIP {unid}: "
|
|
+ ", ".join(best)
|
|
)
|
|
bestid = best[0]
|
|
base = student[bestid]
|
|
for suppidx in duplicates[unid]:
|
|
if suppidx == bestid:
|
|
continue
|
|
supp = student[suppidx]
|
|
for skey in (
|
|
"cursus",
|
|
"etudid",
|
|
"pseudodept",
|
|
"diplome",
|
|
"rank",
|
|
"modalite",
|
|
"parcours",
|
|
"nickname",
|
|
"old",
|
|
"oldsem",
|
|
):
|
|
for bucket in supp[skey]:
|
|
if bucket not in base[skey]:
|
|
base[skey][bucket] = supp[skey][bucket]
|
|
del student[suppidx]
|
|
|
|
|
|
allseeingodin()
|
|
|
|
strange_cases = []
|
|
next = {}
|
|
nextnick = {}
|
|
|
|
for etudid in student.keys():
|
|
etud = student[etudid]
|
|
cursus_array = [None] * 6
|
|
nickname_array = [None] * 6
|
|
etudid_array = [None] * 6
|
|
for i in range(6):
|
|
if str(i) in etud["cursus"]:
|
|
cursus_array[i] = etud["cursus"][str(i)]
|
|
nickname_array[i] = etud["nickname"][str(i)]
|
|
etudid_array[i] = etud["etudid"][str(i)]
|
|
# On va réduire aux semestres pairs, on cherche donc la continuation la plus habituelle pour
|
|
# les élèves qui s'arrêtent sur un semestre impair
|
|
for i in range(0, 5, 2):
|
|
currs = str(cursus_array[i])
|
|
nexts = str(cursus_array[i + 1])
|
|
currn = str(nickname_array[i])
|
|
nextn = str(nickname_array[i + 1])
|
|
if nexts is not None:
|
|
if currs not in next:
|
|
next[currs] = {}
|
|
if nexts not in next[currs]:
|
|
next[currs][nexts] = 1
|
|
else:
|
|
next[currs][nexts] += 1
|
|
if nextn is not None:
|
|
if currn not in nextnick:
|
|
nextnick[currn] = {}
|
|
if nextn not in nextnick[currn]:
|
|
nextnick[currn][nextn] = 1
|
|
else:
|
|
nextnick[currn][nextn] += 1
|
|
etud["cursus_array"] = cursus_array
|
|
etud["nickname_array"] = nickname_array
|
|
etud["etudid_array"] = etudid_array
|
|
|
|
nextbest = {}
|
|
nextnickbest = {}
|
|
for key in next:
|
|
max = 0
|
|
best = None
|
|
for key2 in next[key]:
|
|
if next[key][key2] > max:
|
|
max = next[key][key2]
|
|
best = key2
|
|
nextbest[key] = best
|
|
for key in nextnick:
|
|
max = 0
|
|
best = None
|
|
for key2 in nextnick[key]:
|
|
if nextnick[key][key2] > max:
|
|
max = nextnick[key][key2]
|
|
best = key2
|
|
nextnickbest[key] = best
|
|
|
|
evennicknames = {}
|
|
for etudid in student.keys():
|
|
etud = student[etudid]
|
|
for i in range(1, 6, 2):
|
|
if etud["nickname_array"][i] not in evennicknames:
|
|
evennicknames[etud["nickname_array"][i]] = 1
|
|
else:
|
|
evennicknames[etud["nickname_array"][i]] += 1
|
|
|
|
|
|
for etudid in student.keys():
|
|
etud = student[etudid]
|
|
cursus_short = [None] * 5
|
|
nickname_short = [None] * 5
|
|
etudid_short = [None] * 5
|
|
semend = None
|
|
semstart = None
|
|
for year in range(1, 4):
|
|
sem1 = 2 * year - 2
|
|
sem2 = 2 * year - 1
|
|
finalsem = etud["cursus_array"][sem2]
|
|
nick = etud["nickname_array"][sem2]
|
|
etid = etud["etudid_array"][sem2]
|
|
if finalsem == None:
|
|
finalsem = etud["cursus_array"][sem1]
|
|
nick = etud["nickname_array"][sem1]
|
|
etid = etud["etudid_array"][sem1]
|
|
if finalsem != None:
|
|
# Abandon au premier semestre de cette année
|
|
# print(f"Pour {etudid}, année {year}, abandon au S1")
|
|
if nick not in evennicknames:
|
|
# print( f"Pour {etudid}, année {year}, changement {nick} en {nextnickbest[nick]}" )
|
|
nick = nextnickbest[nick]
|
|
if finalsem != None:
|
|
cursus_short[year] = finalsem
|
|
nickname_short[year] = nick
|
|
etudid_short[year] = etid
|
|
if etud["cursus_array"][sem1] == None:
|
|
# print(f"Pour {etudid}, année {year}, saute-mouton du S1")
|
|
pass
|
|
etud["short"] = cursus_short
|
|
etud["nickshort"] = nickname_short
|
|
etud["etudidshort"] = etudid_short
|
|
|
|
|
|
for etudid in student.keys():
|
|
etud = student[etudid]
|
|
lastyear = 4
|
|
lastsem = None
|
|
while lastsem == None:
|
|
lastyear -= 1
|
|
lastsem = etud["short"][lastyear]
|
|
ddd = get_dept_from_sem(lastsem)
|
|
if ddd not in depts:
|
|
depts.append(ddd)
|
|
|
|
dd = len(depts)
|
|
|
|
badred = {}
|
|
goodred = {}
|
|
failure = {}
|
|
diploma = {}
|
|
reor2 = {}
|
|
reor1 = {}
|
|
unknown = {}
|
|
entries = {}
|
|
redirs = {}
|
|
|
|
for d in depts:
|
|
badred[d] = 0
|
|
goodred[d] = 0
|
|
failure[d] = 0
|
|
diploma[d] = 0
|
|
reor2[d] = 0
|
|
reor1[d] = 0
|
|
unknown[d] = 0
|
|
entries[d] = 0
|
|
redirs[d] = 0
|
|
|
|
strangecases = []
|
|
for etudid in student.keys():
|
|
etud = student[etudid]
|
|
lastyear = 4
|
|
lastsem = None
|
|
while lastsem == None:
|
|
lastyear -= 1
|
|
lastsem = etud["short"][lastyear]
|
|
ddd = get_dept_from_sem(lastsem)
|
|
jury = get_jury_from_formsem(None, lastsem)
|
|
etudid_real = etud["etudidshort"][lastyear]
|
|
if etudid_real != etudid:
|
|
print(f"// Warning {etudid} {etudid_real}")
|
|
resjury = None
|
|
for x in jury:
|
|
if x["etudid"] == etudid_real:
|
|
resjury = x
|
|
break
|
|
if resjury == None:
|
|
print(f"// No jury for {etudid} year {lastyear}")
|
|
continue
|
|
resultyear = None
|
|
if resjury["etat"] == "D":
|
|
resultyear = "DEM"
|
|
if resjury["etat"] == "DEF":
|
|
resultyear = "DEF"
|
|
if (
|
|
"annee" in resjury
|
|
and "code" in resjury["annee"]
|
|
and resjury["annee"]["code"] is not None
|
|
):
|
|
resultyear = resjury["annee"]["code"]
|
|
finaloutput = None
|
|
checkred = False
|
|
if etudid in redirects:
|
|
resultyear = redirects[etudid]
|
|
redirs[ddd] += 1
|
|
strangecases.append(
|
|
f"REDI{lastyear} {server}{ddd}/Scolarite/fiche_etud?etudid={etudid}"
|
|
)
|
|
if resultyear == None:
|
|
finaloutput = "?" + etud["nickshort"][lastyear]
|
|
unknown[ddd] += 1
|
|
strangecases.append(
|
|
f"????{lastyear} {server}{ddd}/Scolarite/fiche_etud?etudid={etudid}"
|
|
)
|
|
elif resultyear in ("RAT", "ATJ"):
|
|
finaloutput = "?" + etud["nickshort"][lastyear]
|
|
unknown[ddd] += 1
|
|
strangecases.append(
|
|
f"ATTE{lastyear} {server}{ddd}/Scolarite/fiche_etud?etudid={etudid}"
|
|
)
|
|
elif resultyear in ("RED", "ABL", "ADSUP"):
|
|
finaloutput = "RED " + etud["nickshort"][lastyear]
|
|
checkred = True
|
|
elif lastyear == 3 and resultyear in ("ADM", "ADJ"):
|
|
finaloutput = "DIPLOME " + etud["nickshort"][lastyear]
|
|
diploma[ddd] += 1
|
|
elif lastyear == 2 and resultyear in ("ADM", "ADJ"):
|
|
finaloutput = "+DUT " + etud["nickshort"][lastyear]
|
|
reor2[ddd] += 1
|
|
elif resultyear in ("PAS1NCI", "PASD"):
|
|
finaloutput = "QUIT " + etud["nickshort"][lastyear]
|
|
reor1[ddd] += 1
|
|
elif lastyear < 2 and resultyear in ("ADM", "ADJ"):
|
|
finaloutput = "QUIT " + etud["nickshort"][lastyear]
|
|
reor1[ddd] += 1
|
|
elif resultyear in ("NAR", "DEM", "DEF", "ABAN"):
|
|
finaloutput = "FAIL " + etud["nickshort"][lastyear]
|
|
failure[ddd] += 1
|
|
elif resjury["annee"]["annee_scolaire"] != baseyear + lastyear - 1:
|
|
finaloutput = "RED " + etud["nickshort"][lastyear]
|
|
checkred = True
|
|
if checkred:
|
|
if "future" not in etud:
|
|
# print(f"// Mauvais redoublement : {etudid}")
|
|
badred[ddd] += 1
|
|
finaloutput = "FAIL" + finaloutput[3:]
|
|
else:
|
|
goodred[ddd] += 1
|
|
etud["nickshort"][lastyear + 1] = finaloutput
|
|
(firstsem, firstyear) = (
|
|
(etud["short"][1], 1)
|
|
if etud["short"][1] != None
|
|
else (
|
|
(etud["short"][2], 2) if etud["short"][2] != None else (etud["short"][3], 3)
|
|
)
|
|
)
|
|
firstdept = cache["sem"][firstsem]["departement"]["acronym"]
|
|
if "old" in etud:
|
|
yearold = cache["sem"][etud["oldsem"]]["annee_scolaire"]
|
|
etud["nickshort"][firstyear - 1] = etud["old"] + " " + str(yearold)
|
|
yy = yearold
|
|
delta = firstyear + baseyear - yy - 2
|
|
for i in range(delta, firstyear - 1):
|
|
etud["nickshort"][i] = etud["nickshort"][firstyear - 1] + "*" * (
|
|
firstyear - 1 - i
|
|
)
|
|
else:
|
|
if etud["cursus"][str(firstyear * 2 - 2)] is not None:
|
|
startsem = str(firstyear * 2 - 2)
|
|
else:
|
|
startsem = str(firstyear * 2 - 1)
|
|
department = etud["pseudodept"][startsem]
|
|
diplome = etud["diplome"][startsem]
|
|
rank = etud["rank"][startsem]
|
|
modalite = etud["modalite"][startsem]
|
|
parcours = etud["parcours"][startsem]
|
|
nick = conf_value("extnick")
|
|
if len(department) > 0:
|
|
nick = nick.replace(
|
|
"{department}", conf_value("department_separator") + department
|
|
)
|
|
else:
|
|
nick = nick.replace("{department}", "")
|
|
if len(diplome) > 0:
|
|
nick = nick.replace("{diplome}", conf_value("diplome_separator") + diplome)
|
|
else:
|
|
nick = nick.replace("{diplome}", "")
|
|
if len(diplome) > 0 and diplome != "BUT":
|
|
nick = nick.replace(
|
|
"{diplomenobut}", conf_value("diplome_separator") + diplome
|
|
)
|
|
else:
|
|
nick = nick.replace("{diplomenobut}", "")
|
|
if len(str(rank)) > 0:
|
|
nick = nick.replace("{rank}", conf_value("rank_separator") + str(rank))
|
|
else:
|
|
nick = nick.replace("{rank}", "")
|
|
if len(modalite) > 0:
|
|
nick = nick.replace(
|
|
"{modalite}", conf_value("modalite_separator") + modalite
|
|
)
|
|
else:
|
|
nick = nick.replace("{modalite}", "")
|
|
if len(modalite) > 0 and modalite != "FI":
|
|
nick = nick.replace("{modaliteshort}", modalite[-1])
|
|
else:
|
|
nick = nick.replace("{modaliteshort}", "")
|
|
if len(parcours) > 0:
|
|
nick = nick.replace(
|
|
"{parcours}", conf_value("parcours_separator") + parcours
|
|
)
|
|
else:
|
|
nick = nick.replace("{parcours}", "")
|
|
if diplome != "BUT":
|
|
nick = "Ecand " + nick
|
|
else:
|
|
nick = "EXT" + nick
|
|
etud["nickshort"][firstyear - 1] = nick
|
|
for i in range(0, firstyear - 1):
|
|
etud["nickshort"][i] = nick + "*" * (firstyear - 1 - i)
|
|
entries[ddd] += 1
|
|
|
|
bags = [{}, {}, {}, {}]
|
|
for etudid in student.keys():
|
|
parc = student[etudid]["nickshort"]
|
|
previouslevels = []
|
|
for i in range(4):
|
|
nstart = parc[i]
|
|
nend = parc[i + 1]
|
|
if nstart != None and nend != None:
|
|
if nstart not in bags[i]:
|
|
bags[i][nstart] = {}
|
|
if nend not in bags[i][nstart]:
|
|
bags[i][nstart][nend] = 1
|
|
else:
|
|
bags[i][nstart][nend] += 1
|
|
|
|
|
|
layers = [[], [], [], [], []]
|
|
finallayers = [[], [], [], [], []]
|
|
alllayers = []
|
|
flatbags = []
|
|
for i in range(4):
|
|
for u in bags[i]:
|
|
if u not in alllayers:
|
|
alllayers.append(u)
|
|
layers[i].append(u)
|
|
for v in bags[i][u]:
|
|
if v not in alllayers:
|
|
alllayers.append(v)
|
|
layers[i + 1].append(v)
|
|
flatbags.append([u, v, bags[i][u][v]])
|
|
allowed = []
|
|
nextallowed = [[], [], [], [], []]
|
|
weights = {}
|
|
|
|
|
|
orders = conf_value("orders")
|
|
|
|
x = set(alllayers)
|
|
y = set()
|
|
for i in orders:
|
|
y = y.union(set(i))
|
|
|
|
for i in range(5):
|
|
if len(orders[i]) > 0:
|
|
allowed.append(orders[i][0])
|
|
for j in orders[i]:
|
|
if j in alllayers:
|
|
nextallowed[i].append(j)
|
|
for j, k in enumerate(orders[i]):
|
|
weights[k] = j + 1
|
|
for u in layers[i]:
|
|
if u not in allowed and u not in nextallowed[i]:
|
|
allowed.append(u)
|
|
else:
|
|
for i in range(5):
|
|
allowed.extend(layers[i])
|
|
|
|
|
|
for bag in flatbags:
|
|
w = 0
|
|
if bag[0] in weights:
|
|
w += weights[bag[0]]
|
|
if bag[1] in weights:
|
|
w += weights[bag[1]]
|
|
bag.append(w)
|
|
flatbags = sorted(flatbags, key=lambda x: x[-1])
|
|
|
|
|
|
orderedflatbags = []
|
|
|
|
while len(flatbags) > 0:
|
|
gotone = False
|
|
for x in flatbags:
|
|
if x[0] in allowed and x[1] in allowed:
|
|
# print(f"{x} est pris")
|
|
gotone = True
|
|
orderedflatbags.append(x)
|
|
flatbags.remove(x)
|
|
# print(f"Choosing {x}")
|
|
for i in range(5):
|
|
if x[0] in layers[i] and x[0] not in finallayers[i]:
|
|
finallayers[i].append(x[0])
|
|
if i < 4 and x[1] in layers[i + 1] and x[1] not in finallayers[i + 1]:
|
|
finallayers[i + 1].append(x[1])
|
|
if x[0] in nextallowed[i]:
|
|
# print(f"[{i}] Removing {x[0]} from {nextallowed[i]}")
|
|
nextallowed[i].remove(x[0])
|
|
if x[1] in nextallowed[i]:
|
|
# print(f"[{i}] Removing {x[1]} from {nextallowed[i]}")
|
|
nextallowed[i].remove(x[1])
|
|
# print(f"[{i}] {nextallowed[i]}")
|
|
if len(nextallowed[i]) > 0 and nextallowed[i][0] not in allowed:
|
|
# print(f"[{i}] Allowing now {nextallowed[i][0]}")
|
|
allowed.append(nextallowed[i][0])
|
|
break
|
|
if not gotone:
|
|
print("BUG")
|
|
print(flatbags)
|
|
print("---", allowed)
|
|
print(nextallowed)
|
|
sys.exit(3)
|
|
|
|
|
|
def printout():
|
|
with open(f"sankeymatic_{orderkey}.txt", "w") as fout:
|
|
|
|
def output(*a, **b):
|
|
b["file"] = fout
|
|
print(*a, **b)
|
|
|
|
date_actuelle = datetime.now()
|
|
date_formatee = date_actuelle.strftime("%m/%d/%Y %H:%M:%S")
|
|
|
|
output(
|
|
f"// SankeyMATIC diagram inputs - Saved: {date_formatee}\n// https://sankeymatic.com/build/\n\n// === Nodes and Flows ===\n\n"
|
|
)
|
|
|
|
output("// THEME INFO")
|
|
for c, cc in colors.items():
|
|
output(f"// !{c}:{cc}")
|
|
output()
|
|
|
|
allnodes = []
|
|
for y in orderedflatbags:
|
|
output(f"{y[0]} [{y[2]}] {y[1]}")
|
|
allnodes.append(y[0])
|
|
allnodes.append(y[1])
|
|
allnodes = list(set(allnodes))
|
|
|
|
nodes = {}
|
|
for x in allnodes:
|
|
color = colors["NORMAL"]
|
|
if x[0:4] == "FAIL":
|
|
color = f"{colors['FAIL']} <<"
|
|
elif x[0:4] == "+DUT":
|
|
color = f"{colors['+DUT']} <<"
|
|
elif x[0:4] == "QUIT":
|
|
color = f"{colors['QUIT']} <<"
|
|
elif x[0:3] == "RED":
|
|
color = f"{colors['RED']} <<"
|
|
elif x[0:4] == "DIPL":
|
|
color = f"{colors['SUCCESS']} <<"
|
|
elif x[0:3] == "EXT":
|
|
color = f"{colors['NEW']} >>"
|
|
elif x[0:3] == "BUT":
|
|
color = f"{colors['NORMAL']}"
|
|
elif x[0:3] == "DUT":
|
|
color = f"{colors['OLD']} >>"
|
|
if x[-1] == "*":
|
|
color = f"{colors['TRANSPARENT']} >>"
|
|
if len(color):
|
|
nodes[x] = color
|
|
|
|
for u in sorted(nodes.keys()):
|
|
output(f":{u} {nodes[u]}")
|
|
|
|
height = conf_value("height")
|
|
width = conf_value("width")
|
|
output("\n\n// === Settings ===\n")
|
|
output(f"size w {width}")
|
|
output(f" h {height}")
|
|
with open("trailer.txt", "r") as fichier:
|
|
contenu = fichier.read()
|
|
output(contenu)
|
|
for ddd in depts:
|
|
p1 = round(100 * diploma[ddd] / entries[ddd])
|
|
p2 = round(100 * (diploma[ddd] + reor2[ddd]) / entries[ddd])
|
|
p3 = round(100 * (failure[ddd] / entries[ddd]))
|
|
p4 = round(100 * (failure[ddd] + badred[ddd] + reor1[ddd]) / entries[ddd])
|
|
|
|
output(f"// Département {ddd}")
|
|
output(f"// {entries[ddd]} Entrées")
|
|
output(f"// {diploma[ddd]} Diplômes")
|
|
output(f"// {reor2[ddd]} DUT")
|
|
output(f"// {p1}-{p2}% de réussite")
|
|
output(f"// {goodred[ddd]} Redoublements")
|
|
output(f"// {reor1[ddd]} départs de la formation")
|
|
output(f"// {badred[ddd]} redoublements autorisés non actés")
|
|
output(f"// {failure[ddd]} échecs")
|
|
output(f"// {p3}-{p4}% d'échecs")
|
|
output(f"// {unknown[ddd]} inconnus")
|
|
for x in strangecases:
|
|
output(f"// {x}")
|
|
|
|
output(f'// orders["{orderkey}"] = {finallayers}')
|
|
output(f"// bacs: {bacs}")
|
|
# output("\nhttps://observablehq.com/@mbostock/flow-o-matic\n\n")
|
|
|
|
# for y in range(4):
|
|
# for u in bags[y]:
|
|
# for v in bags[y][u]:
|
|
# color = ""
|
|
# if v[0:4] == "FAIL":
|
|
# color = ",red"
|
|
# elif v[0:4] == "+DUT":
|
|
# color = ",green"
|
|
# elif v[0:4] == "DIPL":
|
|
# color = ",cyan"
|
|
# elif u[0:3] == "EXT":
|
|
# color = ",yellow"
|
|
# output(f"{u},{v},{bags[y][u][v]}{color}")
|
|
|
|
|
|
printout()
|