#!/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()