forked from dubacq/scodoc-cohortes
Rehaul completely option management
This commit is contained in:
parent
3b7a3df51c
commit
d98be6618c
599
get.py
599
get.py
@ -9,6 +9,7 @@ import pdb # used for debugging
|
|||||||
import inspect # used for debugging
|
import inspect # used for debugging
|
||||||
import random
|
import random
|
||||||
import sys
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
# Third-party libs
|
# Third-party libs
|
||||||
import requests
|
import requests
|
||||||
@ -34,8 +35,10 @@ def debug(*args):
|
|||||||
debug.counter += 1
|
debug.counter += 1
|
||||||
caller_frame = inspect.currentframe().f_back
|
caller_frame = inspect.currentframe().f_back
|
||||||
where = str(caller_frame.f_lineno) + "@" + caller_frame.f_code.co_name
|
where = str(caller_frame.f_lineno) + "@" + caller_frame.f_code.co_name
|
||||||
if len(args) > 0:
|
if len(args) > 1:
|
||||||
print(f"[DEBUG {debug.counter}:{where}] " + str(args[0]), args[1:])
|
print(f"[DEBUG {debug.counter}:{where}] " + str(args[0]), args[1:])
|
||||||
|
elif len(args) > 0:
|
||||||
|
print(f"[DEBUG {debug.counter}:{where}] " + str(args[0]))
|
||||||
else:
|
else:
|
||||||
print(f"[DEBUG {debug.counter}:{where}] (no reason given)")
|
print(f"[DEBUG {debug.counter}:{where}] (no reason given)")
|
||||||
|
|
||||||
@ -70,10 +73,6 @@ API_URL = f"{SCODOC_SERVER}/ScoDoc/api"
|
|||||||
DEBUG = True # Not used
|
DEBUG = True # Not used
|
||||||
BLOCKING = True # Die if csv is incorrect
|
BLOCKING = True # Die if csv is incorrect
|
||||||
|
|
||||||
# TODO : refactor / put globals in a class, eg Config
|
|
||||||
depts = []
|
|
||||||
orderkey = ""
|
|
||||||
|
|
||||||
|
|
||||||
def blockordie(reason: str = "", status: int = 2):
|
def blockordie(reason: str = "", status: int = 2):
|
||||||
if reason:
|
if reason:
|
||||||
@ -84,73 +83,439 @@ def blockordie(reason: str = "", status: int = 2):
|
|||||||
sys.exit(status)
|
sys.exit(status)
|
||||||
|
|
||||||
|
|
||||||
class Options:
|
class Filter:
|
||||||
pass
|
# Filter on students to be considered
|
||||||
|
# 1 consider only technological baccalaureates, statistics are always asked
|
||||||
|
# 2 consider only women, because gender statistics are frequently asked
|
||||||
|
# 4 consider only incoming students (primo-entrants) in first year of the cohort
|
||||||
|
# 8 consider only people having a first year, not parallel entries
|
||||||
|
TECHNO = 1
|
||||||
|
WOMAN = 2
|
||||||
|
PRIMO = 4
|
||||||
|
MAIN = 8
|
||||||
|
|
||||||
|
|
||||||
|
class OptionSet:
|
||||||
|
def __init__(self, values={}):
|
||||||
|
# Initialise un dictionnaire interne pour stocker les options
|
||||||
|
if type(values) == type({}):
|
||||||
|
self._options = values
|
||||||
|
else:
|
||||||
|
self._options = {}
|
||||||
|
self._orderkey = None
|
||||||
|
self._main_filter = None
|
||||||
|
self._secondary_filter = None
|
||||||
|
self._depts = []
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
# Récupère la valeur correspondant à la clé
|
||||||
|
return self._options[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# Assigne la valeur à la clé donnée
|
||||||
|
self._options[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
# Supprime la clé spécifiée
|
||||||
|
if key in self._options:
|
||||||
|
del self._options[key]
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
# Permet l'utilisation de 'in' pour vérifier l'existence d'une clé
|
||||||
|
return key in self._options
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
try:
|
||||||
|
return self._options[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(f"'Options' object has no attribute '{name}'")
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
if name[0] == "_":
|
||||||
|
super().__setattr__(name, value)
|
||||||
|
else:
|
||||||
|
self._options[name] = value
|
||||||
|
|
||||||
|
def __delattr__(self, name):
|
||||||
|
# Appelé quand un attribut est supprimé
|
||||||
|
if name in self._options:
|
||||||
|
del self._options[name]
|
||||||
|
else:
|
||||||
|
raise AttributeError(f"'Options' object has no attribute '{name}'")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Options({self._options})"
|
||||||
|
|
||||||
|
def asDict(self):
|
||||||
|
return self._options
|
||||||
|
|
||||||
|
def asCLI(self, excludeDefault=True, onlyOrders=False, depts=True):
|
||||||
|
cli = []
|
||||||
|
if not onlyOrders:
|
||||||
|
for opt in self.__class__.choiceoptions:
|
||||||
|
if self[opt[0]] == 0 and excludeDefault:
|
||||||
|
continue
|
||||||
|
cli.append("--" + opt[1][self[opt[0]]])
|
||||||
|
for opt in self.__class__.stringoptions:
|
||||||
|
if excludeDefault and self[opt[0]] == opt[1]:
|
||||||
|
continue
|
||||||
|
cli.append("--" + opt[0])
|
||||||
|
cli.append(self[opt[0]])
|
||||||
|
for opt in self.__class__.posint_options:
|
||||||
|
if excludeDefault and self[opt[0]] == opt[1]:
|
||||||
|
continue
|
||||||
|
cli.append("--" + opt[0])
|
||||||
|
cli.append(self[opt[0]])
|
||||||
|
for opt in self.__class__.booleanoptions:
|
||||||
|
if excludeDefault and self[opt[0]] == opt[1]:
|
||||||
|
continue
|
||||||
|
if self[opt[0]]:
|
||||||
|
cli.append("--" + opt[0])
|
||||||
|
else:
|
||||||
|
cli.append("--no-" + opt[0])
|
||||||
|
if "override" in self._options:
|
||||||
|
for FIELD, FIELDdict in self._options["override"].items():
|
||||||
|
for FIELDVALUE, FIELDVALUEdict in FIELDdict.items():
|
||||||
|
for key, val in FIELDVALUEdict.items():
|
||||||
|
cli.extend(["--override", FIELD, FIELDVALUE, key, val])
|
||||||
|
if "orders" in self._options:
|
||||||
|
orders = self._options["orders"]
|
||||||
|
cli.append("--orders")
|
||||||
|
for i, column in enumerate(orders):
|
||||||
|
if i > 0:
|
||||||
|
cli.append("/")
|
||||||
|
for row in column:
|
||||||
|
cli.append(row)
|
||||||
|
cli.append(".")
|
||||||
|
if depts:
|
||||||
|
cli.extend(self._depts)
|
||||||
|
return cli
|
||||||
|
|
||||||
|
def orderkey(self, filters=False):
|
||||||
|
if filters:
|
||||||
|
d = self._depts.copy()
|
||||||
|
d.append(str(self.filter()))
|
||||||
|
d.append(str(self.filter(main=False)))
|
||||||
|
return "_".join(d)
|
||||||
|
if self._orderkey is not None:
|
||||||
|
return self._orderkey
|
||||||
|
self._orderkey = "_".join(self._depts)
|
||||||
|
return self._orderkey
|
||||||
|
|
||||||
|
def depts(self, xset=None):
|
||||||
|
if xset:
|
||||||
|
self._depts = xset
|
||||||
|
return self._depts
|
||||||
|
|
||||||
|
def filter(self, main: bool = True):
|
||||||
|
r = 0
|
||||||
|
stem = "base"
|
||||||
|
if not main:
|
||||||
|
if self._secondary_filter is not None:
|
||||||
|
return self._secondary_filter
|
||||||
|
stem = "secondary"
|
||||||
|
else:
|
||||||
|
if self._main_filter is not None:
|
||||||
|
return self._main_filter
|
||||||
|
for suffix, f in {"techno": Filter.TECHNO, "women": Filter.WOMAN}.items():
|
||||||
|
option = f"{stem}_{suffix}"
|
||||||
|
if option in self._options and self._options[option]:
|
||||||
|
r |= f
|
||||||
|
if main:
|
||||||
|
self._main_filter = r
|
||||||
|
return r
|
||||||
|
self._secondary_filter = r | self.filter()
|
||||||
|
return self._secondary_filter
|
||||||
|
|
||||||
|
stringoptions = [
|
||||||
|
[
|
||||||
|
"department_separator",
|
||||||
|
" ",
|
||||||
|
"Separator before department in semester designation/display/origin designation",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"diplome_separator",
|
||||||
|
"",
|
||||||
|
"Separator before diploma in semester designation/display/origin designation",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"modalite_separator",
|
||||||
|
" ",
|
||||||
|
"Separator before modality in semester designation/display/origin designation",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"parcours_separator",
|
||||||
|
"/",
|
||||||
|
"Separator before parcours in semester designation/display/origin designation",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"rank_separator",
|
||||||
|
"",
|
||||||
|
"Separator before rank (~year of progress) in cursus designation/display/origin designation",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"year_separator",
|
||||||
|
" ",
|
||||||
|
"Separator before year in semester designation/display/origin designation",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"nick",
|
||||||
|
"{diplome}{rank}{multidepartment}{modalite}{parcours}{year}",
|
||||||
|
"Yearly cursus designation (should be unique for each distinguisable cursus choice)",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"displayname",
|
||||||
|
"{diplome}{rank}{multidepartment}{modaliteshort}{parcours}",
|
||||||
|
"Yearly cursus origin (used only for captionning the flow)",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"extnick",
|
||||||
|
"{ext}{rank}{multidepartment}{diplomenobut}{modaliteshort}",
|
||||||
|
"Origin designation (should be unique for each distinguisable origin of students)",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
choiceoptions = [["algo", ["optimize", "reuse", "restart"]]]
|
||||||
|
|
||||||
|
booleanoptions = [
|
||||||
|
["base_techno", False, "Base population includes only techno students"],
|
||||||
|
["base_women", False, "Base population includes only women students"],
|
||||||
|
[
|
||||||
|
"secondary_techno",
|
||||||
|
True,
|
||||||
|
"Secondary (focused) population includes only techno students",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"secondary_women",
|
||||||
|
False,
|
||||||
|
"Secondary (focused) population includes only women students",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
posint_options = [
|
||||||
|
["spacing", 14, 0, 30, "Spacing between groups in the same column"],
|
||||||
|
["thickness", 6, 0, 30, "Width of the group bars in columns"],
|
||||||
|
["hmargin", 20, 0, 50, "Global margin around the graph"],
|
||||||
|
["fontsize_name", 10, 0, 30, "Font size of the group name"],
|
||||||
|
["fontsize_count", 14, 0, 30, "Font size of the population marks"],
|
||||||
|
["width", 1300, 800, None, "Width of the graphics (not counting captions)"],
|
||||||
|
["statwidth", 300, 0, None, "Width of the side caption"],
|
||||||
|
["height", 0, 0, None, "Height of the graphics (0 = automaticd)"],
|
||||||
|
["loops", 300, 0, 1000, "Number of loops of the optimization algorithm"],
|
||||||
|
["baseyear", 2021, 2000, None, "Base year (start of the cohort)"],
|
||||||
|
]
|
||||||
|
|
||||||
|
shortcuts = {"baseyear": ["--base", "-b"], "loops": ["-l"]}
|
||||||
|
|
||||||
|
|
||||||
|
def range_limited_int_type(arg, MIN_VAL, MAX_VAL):
|
||||||
|
"""Type function for argparse - an integer within some predefined bounds"""
|
||||||
|
try:
|
||||||
|
f = int(arg)
|
||||||
|
except ValueError:
|
||||||
|
raise argparse.ArgumentTypeError("Must be an integer point number")
|
||||||
|
if MIN_VAL is not None and f < MIN_VAL:
|
||||||
|
raise argparse.ArgumentTypeError(
|
||||||
|
"Argument must be larger or equal to " + str(MIN_VAL)
|
||||||
|
)
|
||||||
|
if MAX_VAL is not None and f > MAX_VAL:
|
||||||
|
raise argparse.ArgumentTypeError(
|
||||||
|
"Argument must be smaller or equal to " + str(MAX_VAL)
|
||||||
|
)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def format_for_shell(strings):
|
||||||
|
# Regex pour détecter les caractères spéciaux
|
||||||
|
special_chars = re.compile(r"[^+/.a-zA-Z0-9_-]")
|
||||||
|
|
||||||
|
formatted_strings = []
|
||||||
|
for ss in strings:
|
||||||
|
s = str(ss)
|
||||||
|
if special_chars.search(s): # Si la chaîne contient des caractères spéciaux
|
||||||
|
formatted_s = "'" + s.replace("'", "'\"'\"'") + "'"
|
||||||
|
elif len(s) == 0:
|
||||||
|
formatted_s = "''"
|
||||||
|
else:
|
||||||
|
formatted_s = s
|
||||||
|
formatted_strings.append(formatted_s)
|
||||||
|
|
||||||
|
# Concatène les chaînes pour qu'elles soient prêtes à copier-coller dans le shell
|
||||||
|
return " ".join(formatted_strings)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
def cli_check():
|
def cli_check():
|
||||||
"""Read args from the command line
|
"""Read args from the command line
|
||||||
then read config from {orderkey}.json
|
then read config from {orderkey}.json
|
||||||
"""
|
"""
|
||||||
global orderkey # TODO: globales à supprimer
|
parser = argparse.ArgumentParser(
|
||||||
global depts
|
usage="""
|
||||||
|
%(prog)s [--options] DEPARTEMENTS...
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Process some departments.")
|
OR
|
||||||
parser.add_argument("depts", nargs="*", help="List of departments")
|
|
||||||
|
%(prog)s FILE.json
|
||||||
|
""",
|
||||||
|
description="Create a sankey diagram for the evolution of students through some departments.",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--base",
|
"--orders",
|
||||||
"-b",
|
nargs="+",
|
||||||
type=int,
|
help="Start of orders list with subgroups separated by / ended by .",
|
||||||
choices=range(2000, 2067),
|
|
||||||
default=2021,
|
|
||||||
help="base year for the cohort (integer between 2000 and 2666)",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument("depts", nargs="*", help="List of departments")
|
||||||
|
|
||||||
|
# STRING OPTIONS
|
||||||
|
for opt in OptionSet.stringoptions:
|
||||||
|
xopt = ["--" + opt[0]]
|
||||||
|
if opt[0] in OptionSet.shortcuts:
|
||||||
|
xopt.extend(OptionSet.shortcuts[opt[0]])
|
||||||
|
parser.add_argument(*xopt, type=str, default=opt[1], help=opt[2])
|
||||||
|
|
||||||
|
# POSITIVE INTEGERS OPTIONS
|
||||||
|
for opt in OptionSet.posint_options:
|
||||||
|
xopt = ["--" + opt[0]]
|
||||||
|
if opt[0] in OptionSet.shortcuts:
|
||||||
|
xopt.extend(OptionSet.shortcuts[opt[0]])
|
||||||
|
optrange = ""
|
||||||
|
if opt[3] == None and opt[2] != None:
|
||||||
|
optrange = f" larger or equal to {opt[2]}"
|
||||||
|
elif opt[2] == None and opt[3] != None:
|
||||||
|
optrange = f" smaller than {opt[3]}"
|
||||||
|
elif opt[2] != None and opt[3] != None:
|
||||||
|
optrange = f" (between {opt[2]} and {opt[3]})"
|
||||||
|
|
||||||
|
def rangefactory(y, z):
|
||||||
|
return lambda x: range_limited_int_type(x, y, z)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
*xopt,
|
||||||
|
type=rangefactory(opt[2], opt[3]),
|
||||||
|
default=opt[1],
|
||||||
|
help=opt[4] + optrange,
|
||||||
|
)
|
||||||
|
|
||||||
|
# BOOLEAN OPTIONS
|
||||||
|
for opt in OptionSet.booleanoptions:
|
||||||
|
g = parser.add_mutually_exclusive_group()
|
||||||
|
xopt = ["--" + opt[0]]
|
||||||
|
if opt[0] in OptionSet.shortcuts:
|
||||||
|
xopt.extend(OptionSet.shortcuts[opt[0]])
|
||||||
|
g.add_argument(*xopt, action="store_true", default=opt[1], help=opt[2])
|
||||||
|
xopt = ["--no-" + opt[0]]
|
||||||
|
if "no-" + opt[0] in OptionSet.shortcuts:
|
||||||
|
xopt.extend(OptionSet.shortcuts["no-" + opt[0]])
|
||||||
|
g.add_argument(
|
||||||
|
*xopt,
|
||||||
|
action="store_true",
|
||||||
|
# help=opt[2].replace("includes only", "doesn't care about"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# OTHER OPTIONS
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--override",
|
||||||
|
nargs=4,
|
||||||
|
metavar=(
|
||||||
|
"FIELD",
|
||||||
|
"FIELD_VALUE",
|
||||||
|
"REPLACEMENT_FIELD",
|
||||||
|
"REPLACEMENT_FIELD_VALUE",
|
||||||
|
),
|
||||||
|
help="Override a specific field with a fixed value in some specific semester(s) selected by FIELD=FIELD_VALUE",
|
||||||
|
)
|
||||||
|
|
||||||
optimize_group = parser.add_mutually_exclusive_group()
|
optimize_group = parser.add_mutually_exclusive_group()
|
||||||
optimize_group.add_argument(
|
optimize_group.add_argument("--reuse", "-r", action="store_true", help="Reuse mode")
|
||||||
"--reuse", action="store_true", help="Reuse mode, sets value to 0"
|
|
||||||
)
|
|
||||||
optimize_group.add_argument(
|
optimize_group.add_argument(
|
||||||
"--optimize",
|
"--optimize",
|
||||||
type=str,
|
"-o",
|
||||||
nargs="?",
|
action="store_true",
|
||||||
const="100", # Default value if --optimize is used without specifying n
|
help="Use algorithm to enhance graph (using last result)",
|
||||||
help="Optimize mode, takes an optional integer (default is 100, or 300 if no optimization option specified)",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
optimize_group.add_argument(
|
optimize_group.add_argument(
|
||||||
"--restart",
|
"--restart",
|
||||||
type=str,
|
"-R",
|
||||||
nargs="?",
|
action="store_true",
|
||||||
const="300", # Default value if --restart is used without specifying n
|
help="Use algorithm to enhance graph (starting from random)",
|
||||||
help="Restart & Optimize mode, takes an optional integer (default is 300)",
|
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
if len(sys.argv) > 1 and sys.argv[1].endswith(".json"):
|
||||||
Options.restart = False
|
|
||||||
if args.reuse:
|
|
||||||
Options.optimize = 0
|
|
||||||
elif args.restart is not None:
|
|
||||||
Options.restart = True
|
|
||||||
try:
|
try:
|
||||||
Options.optimize = -int(args.restart)
|
json_file = sys.argv[1]
|
||||||
except (TypeError, ValueError):
|
with open(json_file, "r") as f:
|
||||||
Options.optimize = -300
|
fakeclisource = json.load(f)
|
||||||
if args.restart:
|
fakecli = [str(x) for x in fakeclisource]
|
||||||
args.depts.insert(0, args.restart)
|
except FileNotFoundError:
|
||||||
|
die(f"Error: File '{json_file}' not found.", 1)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
die(f"Error: File '{json_file}' is not valid JSON.", 1)
|
||||||
|
args = parser.parse_args(args=fakecli)
|
||||||
else:
|
else:
|
||||||
try:
|
args = parser.parse_args()
|
||||||
Options.optimize = int(args.optimize)
|
if len(args.depts) == 0 and (
|
||||||
except (TypeError, ValueError):
|
args.orders is None or args.orders[-1] == "." or "." not in args.orders
|
||||||
Options.optimize = 300
|
):
|
||||||
if args.optimize:
|
|
||||||
args.depts.insert(0, args.optimize)
|
|
||||||
|
|
||||||
Options.base_year = args.base
|
|
||||||
depts = args.depts
|
|
||||||
orderkey = "_".join(depts)
|
|
||||||
|
|
||||||
if len(depts) == 0:
|
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def options_from_args(args):
|
||||||
|
Options = OptionSet()
|
||||||
|
# Gestion de --orders pour construire le tableau 2D
|
||||||
|
orders = []
|
||||||
|
if args.depts:
|
||||||
|
depts = args.depts
|
||||||
|
else:
|
||||||
|
depts = []
|
||||||
|
if args.orders:
|
||||||
|
current_order = []
|
||||||
|
l = args.orders.copy()
|
||||||
|
idx = 0
|
||||||
|
while len(l) > idx:
|
||||||
|
item = l[idx]
|
||||||
|
idx += 1
|
||||||
|
if item == "/":
|
||||||
|
# Nouvelle ligne à chaque "--next"
|
||||||
|
orders.append(current_order)
|
||||||
|
current_order = []
|
||||||
|
elif item == ".":
|
||||||
|
# Fin de la liste d'ordres
|
||||||
|
orders.append(current_order)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Ajouter l'élément au sous-groupe en cours
|
||||||
|
current_order.append(item)
|
||||||
|
depts.extend(l[idx:])
|
||||||
|
Options["orders"] = orders
|
||||||
|
dargs = vars(args)
|
||||||
|
for opt in OptionSet.posint_options:
|
||||||
|
if opt[0] in dargs:
|
||||||
|
Options[opt[0]] = dargs[opt[0]]
|
||||||
|
for opt in OptionSet.stringoptions:
|
||||||
|
if opt[0] in dargs:
|
||||||
|
Options[opt[0]] = dargs[opt[0]]
|
||||||
|
for opt in OptionSet.booleanoptions:
|
||||||
|
if opt[0] in dargs:
|
||||||
|
if "no_" + opt[0] in dargs and dargs["no_" + opt[0]]:
|
||||||
|
Options[opt[0]] = not dargs["no_" + opt[0]]
|
||||||
|
else:
|
||||||
|
Options[opt[0]] = dargs[opt[0]]
|
||||||
|
if not (args.reuse or args.restart or args.optimize):
|
||||||
|
Options.algo = 0
|
||||||
|
else:
|
||||||
|
Options.algo = 0 if args.optimize else (1 if args.reuse else 2)
|
||||||
|
Options.depts(depts)
|
||||||
|
return Options
|
||||||
|
|
||||||
|
|
||||||
|
def merge_options(Options, jsondict):
|
||||||
|
if "override" in jsondict:
|
||||||
|
Options["override"] = jsondict["override"]
|
||||||
|
if "orders" in jsondict:
|
||||||
|
Options["orders"] = jsondict["orders"]
|
||||||
|
|
||||||
|
|
||||||
def api_url(dept: str | None = None):
|
def api_url(dept: str | None = None):
|
||||||
@ -161,9 +526,6 @@ def api_url(dept: str | None = None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
cli_check()
|
|
||||||
|
|
||||||
|
|
||||||
def read_conf(key):
|
def read_conf(key):
|
||||||
if os.path.exists(f"{key}.json"):
|
if os.path.exists(f"{key}.json"):
|
||||||
with open(f"{key}.json", "r") as f:
|
with open(f"{key}.json", "r") as f:
|
||||||
@ -177,44 +539,11 @@ def write_conf(key, obj):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
conf = read_conf(orderkey)
|
Options = options_from_args(cli_check())
|
||||||
|
orderkey = Options.orderkey()
|
||||||
|
depts = Options.depts()
|
||||||
|
|
||||||
defaults = {
|
defaults = {}
|
||||||
"spacing": 14,
|
|
||||||
"thickness": 6,
|
|
||||||
"fontsize_name": 10,
|
|
||||||
"fontsize_count": 14,
|
|
||||||
"width": 1300,
|
|
||||||
"height": 0,
|
|
||||||
"hmargin": 20,
|
|
||||||
"parcours_separator": "/",
|
|
||||||
"year_separator": " ",
|
|
||||||
"rank_separator": "",
|
|
||||||
"diplome_separator": "",
|
|
||||||
"reuse": "yes",
|
|
||||||
"optimize": "yes",
|
|
||||||
"main_filter": 0,
|
|
||||||
"secondary_filter": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def conf_value(xkey: str):
|
|
||||||
"""Manage default values"""
|
|
||||||
if xkey in conf:
|
|
||||||
return conf[xkey]
|
|
||||||
if xkey in defaults:
|
|
||||||
return defaults[xkey]
|
|
||||||
if xkey[-9:] == "separator":
|
|
||||||
return " "
|
|
||||||
if xkey == "nick":
|
|
||||||
return "{diplome}{rank}{multidepartment}{modalite}{parcours}{year}"
|
|
||||||
if xkey == "displayname":
|
|
||||||
return "{diplome}{rank}{multidepartment}{modaliteshort}{parcours}"
|
|
||||||
if xkey == "extnick":
|
|
||||||
return "{ext}{rank}{multidepartment}{diplomenobut}{modaliteshort}"
|
|
||||||
if xkey == "orders":
|
|
||||||
return [[], [], [], [], []]
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
student = {}
|
student = {}
|
||||||
@ -393,7 +722,9 @@ def get_jury_from_formsem(dept: str, semid):
|
|||||||
|
|
||||||
|
|
||||||
def get_override(sem, xkey, default=None):
|
def get_override(sem, xkey, default=None):
|
||||||
overrides = conf_value("override")
|
if "overrides" not in Options:
|
||||||
|
return default
|
||||||
|
overrides = Options.override
|
||||||
for j in ["titre_num", "titre", "session_id"]:
|
for j in ["titre_num", "titre", "session_id"]:
|
||||||
if (
|
if (
|
||||||
j in sem
|
j in sem
|
||||||
@ -406,46 +737,44 @@ def get_override(sem, xkey, default=None):
|
|||||||
|
|
||||||
|
|
||||||
def nick_replace(
|
def nick_replace(
|
||||||
department, diplome, rank, modalite, parcours, nick, year=Options.base_year
|
department, diplome, rank, modalite, parcours, nick, year=Options.baseyear
|
||||||
):
|
):
|
||||||
if type(rank) != int:
|
if type(rank) != int:
|
||||||
rank = 0
|
rank = 0
|
||||||
if len(department) > 0:
|
if len(department) > 0:
|
||||||
nick = nick.replace(
|
nick = nick.replace("{department}", Options.department_separator + department)
|
||||||
"{department}", conf_value("department_separator") + department
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
nick = nick.replace("{department}", "")
|
nick = nick.replace("{department}", "")
|
||||||
if len(department) > 0 and len(depts) > 1:
|
if len(department) > 0 and len(depts) > 1:
|
||||||
nick = nick.replace(
|
nick = nick.replace(
|
||||||
"{multidepartment}", conf_value("department_separator") + department
|
"{multidepartment}", Options.department_separator + department
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
nick = nick.replace("{multidepartment}", "")
|
nick = nick.replace("{multidepartment}", "")
|
||||||
if len(diplome) > 0:
|
if len(diplome) > 0:
|
||||||
nick = nick.replace("{diplome}", conf_value("diplome_separator") + diplome)
|
nick = nick.replace("{diplome}", Options.diplome_separator + diplome)
|
||||||
else:
|
else:
|
||||||
nick = nick.replace("{diplome}", "")
|
nick = nick.replace("{diplome}", "")
|
||||||
if len(diplome) > 0 and diplome != "BUT":
|
if len(diplome) > 0 and diplome != "BUT":
|
||||||
nick = nick.replace("{diplomenobut}", conf_value("diplome_separator") + diplome)
|
nick = nick.replace("{diplomenobut}", Options.diplome_separator + diplome)
|
||||||
else:
|
else:
|
||||||
nick = nick.replace("{diplomenobut}", "")
|
nick = nick.replace("{diplomenobut}", "")
|
||||||
if rank > 0:
|
if rank > 0:
|
||||||
nick = nick.replace("{rank}", conf_value("rank_separator") + str(rank))
|
nick = nick.replace("{rank}", Options.rank_separator + str(rank))
|
||||||
else:
|
else:
|
||||||
nick = nick.replace("{rank}", "")
|
nick = nick.replace("{rank}", "")
|
||||||
nick = nick.replace(
|
nick = nick.replace(
|
||||||
"{year}", conf_value("year_separator") + str(Options.base_year + rank - 1)
|
"{year}", Options.year_separator + str(Options.baseyear + rank - 1)
|
||||||
)
|
)
|
||||||
if diplome != "BUT":
|
if diplome != "BUT":
|
||||||
nick = nick.replace(
|
nick = nick.replace(
|
||||||
"{yearnobut}",
|
"{yearnobut}",
|
||||||
conf_value("year_separator") + str(Options.base_year + rank - 1),
|
Options.year_separator + str(Options.baseyear + rank - 1),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
nick = nick.replace("{yearnobut}", "")
|
nick = nick.replace("{yearnobut}", "")
|
||||||
if len(modalite) > 0:
|
if len(modalite) > 0:
|
||||||
nick = nick.replace("{modalite}", conf_value("modalite_separator") + modalite)
|
nick = nick.replace("{modalite}", Options.modalite_separator + modalite)
|
||||||
else:
|
else:
|
||||||
nick = nick.replace("{modalite}", "")
|
nick = nick.replace("{modalite}", "")
|
||||||
if len(modalite) > 0 and modalite != "FI":
|
if len(modalite) > 0 and modalite != "FI":
|
||||||
@ -453,7 +782,7 @@ def nick_replace(
|
|||||||
else:
|
else:
|
||||||
nick = nick.replace("{modaliteshort}", "")
|
nick = nick.replace("{modaliteshort}", "")
|
||||||
if len(parcours) > 0:
|
if len(parcours) > 0:
|
||||||
nick = nick.replace("{parcours}", conf_value("parcours_separator") + parcours)
|
nick = nick.replace("{parcours}", Options.parcours_separator + parcours)
|
||||||
else:
|
else:
|
||||||
nick = nick.replace("{parcours}", "")
|
nick = nick.replace("{parcours}", "")
|
||||||
extname = "Ecand "
|
extname = "Ecand "
|
||||||
@ -499,9 +828,9 @@ def analyse_student(semobj, etud, univ_year=None):
|
|||||||
gg = g.split("=")
|
gg = g.split("=")
|
||||||
if gg[0] in groups:
|
if gg[0] in groups:
|
||||||
modalite = gg[1]
|
modalite = gg[1]
|
||||||
nick = conf_value("nick")
|
nick = Options.nick
|
||||||
nick = nick_replace(department, diplome, rank, modalite, parcours, nick, year)
|
nick = nick_replace(department, diplome, rank, modalite, parcours, nick, year)
|
||||||
displayname = conf_value("displayname")
|
displayname = Options.displayname
|
||||||
displayname = nick_replace(
|
displayname = nick_replace(
|
||||||
department, diplome, rank, modalite, parcours, displayname, year
|
department, diplome, rank, modalite, parcours, displayname, year
|
||||||
)
|
)
|
||||||
@ -538,7 +867,7 @@ def analyse_depts():
|
|||||||
year = 1
|
year = 1
|
||||||
else:
|
else:
|
||||||
year = (sem["semestre_id"] + 1) // 2
|
year = (sem["semestre_id"] + 1) // 2
|
||||||
offset = sem["annee_scolaire"] - Options.base_year - year + 1
|
offset = sem["annee_scolaire"] - Options.baseyear - year + 1
|
||||||
if offset < 0 and offset > -4:
|
if offset < 0 and offset > -4:
|
||||||
oldsems.add(str(semid))
|
oldsems.add(str(semid))
|
||||||
oldsemsdept[semid] = dept
|
oldsemsdept[semid] = dept
|
||||||
@ -992,7 +1321,7 @@ for etudid in student.keys():
|
|||||||
elif resultyear in ("NAR", "DEM", "DEF", "ABAN"):
|
elif resultyear in ("NAR", "DEM", "DEF", "ABAN"):
|
||||||
finaloutput = "FAIL"
|
finaloutput = "FAIL"
|
||||||
failure[ddd] += 1
|
failure[ddd] += 1
|
||||||
elif resjury["annee"]["annee_scolaire"] != Options.base_year + lastyear - 1:
|
elif resjury["annee"]["annee_scolaire"] != Options.baseyear + lastyear - 1:
|
||||||
finaloutput = "RED"
|
finaloutput = "RED"
|
||||||
checkred = True
|
checkred = True
|
||||||
if checkred:
|
if checkred:
|
||||||
@ -1019,7 +1348,7 @@ for etudid in student.keys():
|
|||||||
yearold = cache["sem"][etud["oldsem"]]["annee_scolaire"]
|
yearold = cache["sem"][etud["oldsem"]]["annee_scolaire"]
|
||||||
etud["nickshort"][firstyear - 1] = etud["old"]
|
etud["nickshort"][firstyear - 1] = etud["old"]
|
||||||
# yy = yearold
|
# yy = yearold
|
||||||
# delta = firstyear + Options.base_year - yy - 2
|
# delta = firstyear + Options.baseyear - yy - 2
|
||||||
# for i in range(delta, firstyear - 1):
|
# for i in range(delta, firstyear - 1):
|
||||||
# etud["nickshort"][i] = etud["nickshort"][firstyear - 1] + "*" * (
|
# etud["nickshort"][i] = etud["nickshort"][firstyear - 1] + "*" * (
|
||||||
# firstyear - 1 - i
|
# firstyear - 1 - i
|
||||||
@ -1037,9 +1366,9 @@ for etudid in student.keys():
|
|||||||
rank = etud["rank"][startsem]
|
rank = etud["rank"][startsem]
|
||||||
modalite = etud["modalite"][startsem]
|
modalite = etud["modalite"][startsem]
|
||||||
parcours = etud["parcours"][startsem]
|
parcours = etud["parcours"][startsem]
|
||||||
nick = "EXT" + conf_value("nick")
|
nick = "EXT" + Options.nick
|
||||||
nick = nick_replace(department, diplome, rank, modalite, parcours, nick)
|
nick = nick_replace(department, diplome, rank, modalite, parcours, nick)
|
||||||
displayname = conf_value("extnick")
|
displayname = Options.extnick
|
||||||
displayname = nick_replace(
|
displayname = nick_replace(
|
||||||
department, diplome, rank, modalite, parcours, displayname
|
department, diplome, rank, modalite, parcours, displayname
|
||||||
)
|
)
|
||||||
@ -1052,18 +1381,6 @@ for etudid in student.keys():
|
|||||||
entries[ddd] += 1
|
entries[ddd] += 1
|
||||||
|
|
||||||
|
|
||||||
class Filter:
|
|
||||||
# Filter on students to be considered
|
|
||||||
# 1 consider only technological baccalaureates, statistics are always asked
|
|
||||||
# 2 consider only women, because gender statistics are frequently asked
|
|
||||||
# 4 consider only incoming students (primo-entrants) in first year of the cohort
|
|
||||||
# 8 consider only people having a first year, not parallel entries
|
|
||||||
TECHNO = 1
|
|
||||||
WOMAN = 2
|
|
||||||
PRIMO = 4
|
|
||||||
MAIN = 8
|
|
||||||
|
|
||||||
|
|
||||||
def bags_from_students(student, filter=0):
|
def bags_from_students(student, filter=0):
|
||||||
bags = []
|
bags = []
|
||||||
for etudid in student.keys():
|
for etudid in student.keys():
|
||||||
@ -1244,7 +1561,7 @@ def ordernodes(layers, orders, edges):
|
|||||||
for j, n in enumerate(layernodes):
|
for j, n in enumerate(layernodes):
|
||||||
node_position[n] = j
|
node_position[n] = j
|
||||||
node_layer[n] = layer
|
node_layer[n] = layer
|
||||||
debug(crossweight(node_position, node_layer, edges))
|
debug("Solution has weight" + str(crossweight(node_position, node_layer, edges)))
|
||||||
return node_position, node_layer, newls
|
return node_position, node_layer, newls
|
||||||
|
|
||||||
|
|
||||||
@ -1361,10 +1678,10 @@ def nodestructure_from_bags(bags, sbags=None):
|
|||||||
|
|
||||||
def compute_svg(height, padding, realdensity, node_structure):
|
def compute_svg(height, padding, realdensity, node_structure):
|
||||||
unit_ratio = 96 / 72
|
unit_ratio = 96 / 72
|
||||||
thickness = conf_value("thickness")
|
thickness = Options.thickness
|
||||||
fontsize_name = conf_value("fontsize_name")
|
fontsize_name = Options.fontsize_name
|
||||||
fontsize_count = conf_value("fontsize_count")
|
fontsize_count = Options.fontsize_count
|
||||||
width = conf_value("width")
|
width = Options.width
|
||||||
columns = []
|
columns = []
|
||||||
l = 0
|
l = 0
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
@ -1525,30 +1842,32 @@ def compute_svg(height, padding, realdensity, node_structure):
|
|||||||
|
|
||||||
def printsvg():
|
def printsvg():
|
||||||
padding = 4
|
padding = 4
|
||||||
spacing = conf_value("spacing")
|
spacing = Options.spacing
|
||||||
height = conf_value("height")
|
height = Options.height
|
||||||
hmargin = conf_value("hmargin")
|
hmargin = Options.hmargin
|
||||||
bags = bags_from_students(student, conf_value("main_filter"))
|
bags = bags_from_students(student, Options.filter())
|
||||||
sbags = bags_from_students(student, conf_value("secondary_filter"))
|
sbags = bags_from_students(student, Options.filter(main=False))
|
||||||
node_structure, layers, edges = nodestructure_from_bags(bags, sbags)
|
node_structure, layers, edges = nodestructure_from_bags(bags, sbags)
|
||||||
filename = "best-" + orderkey
|
filename = "best-" + orderkey
|
||||||
if Options.restart:
|
if Options.algo == 2:
|
||||||
try:
|
try:
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
if Options.optimize >= 0:
|
if Options.algo < 2:
|
||||||
lastorders = read_conf(filename)
|
lastorders = read_conf(filename)
|
||||||
else:
|
else:
|
||||||
lastorders = {}
|
lastorders = {}
|
||||||
node_position, node_layer, newls = ordernodes(layers, lastorders, edges)
|
node_position, node_layer, newls = ordernodes(layers, lastorders, edges)
|
||||||
if Options.optimize != 0:
|
if Options.algo != 1:
|
||||||
orders = genetic_optimize(
|
orders = genetic_optimize(node_position, node_layer, edges, loops=Options.loops)
|
||||||
node_position, node_layer, edges, loops=abs(Options.optimize)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
orders = newls
|
orders = newls
|
||||||
write_conf("best-" + orderkey, orders)
|
write_conf("best-" + orderkey, orders)
|
||||||
|
info(format_for_shell(OptionSet({"orders": orders}).asCLI(onlyOrders=True)))
|
||||||
|
Options.orders = orders
|
||||||
|
write_conf(Options.orderkey(filters=True), Options.asCLI())
|
||||||
|
info(format_for_shell(OptionSet({"orders": orders}).asCLI(onlyOrders=True)))
|
||||||
node_position, node_layer, newls = ordernodes(layers, orders, edges)
|
node_position, node_layer, newls = ordernodes(layers, orders, edges)
|
||||||
realdensity, height, layer_structure = get_layer_structure(
|
realdensity, height, layer_structure = get_layer_structure(
|
||||||
newls, node_structure, spacing, hmargin, height
|
newls, node_structure, spacing, hmargin, height
|
||||||
|
Loading…
Reference in New Issue
Block a user