diff --git a/degrees.py b/degrees.py index 5c25942..8ef56db 100644 --- a/degrees.py +++ b/degrees.py @@ -1,6 +1,15 @@ from lark import Lark, Transformer, v_args import json, sys, re, codecs -import lark + + +debug_out = codecs.open("cache/degrees_debug.txt", "w", "utf-8") + +def d(s): + #if type(s) == tuple or type(s) == list: + # debug_out.write(" ".join(str(s)) + "\n") + debug_out.write(str(s) + "\n") + + """describe college courses, their number of units, any other courses that are prerequisites, as well as various degrees which @@ -10,32 +19,6 @@ consist of groups of courses that must be taken. The groups have rules associate - take at least n units from the following list (so the taken course's unit value must add up to n or greater) - and so on.""" - - -def num(s): - if s == '': return 0 - try: - return int(s) - except ValueError: - return float(s) - -def num_units(s): - if s == '': return 0 - m = re.match(r'^(\d+)\.?(\d+)?\s?(units)?$',s) - if m: - if m.group(1) and m.group(2): - if m.group(2) == '0': - return int(m.group(1)) - return float(f"{m.group(1)}.{m.group(2)}") - elif m.group(1): - return int(m.group(1)) - else: - try: - return int(s) - except ValueError: - return float(s) - - class CourseOr: def __init__(self, subs): self.names = subs @@ -45,68 +28,46 @@ class CourseOr: class Course: - def __init__(self, name, units, prerequisites=[]): + def __init__(self, name): self.name = name - self.units = units - self.prerequisites = prerequisites def __repr__(self): - #return f"{self.name} ({self.units} units)" return f"{self.name}" class CourseList: - def __init__(self, name, course_list): + def __init__(self, name, courses): self.name = name - self.course_list = course_list - self.l = len(course_list) + self.courses = courses def __iter__(self): - return iter(self.course_list) + return iter(self.courses) def __len__(self): - print("What is this len? ", len(self.course_list), str(list(self.course_list)), str(type(self.course_list))) - i = 1 - for c in self.course_list: - i += 1 - return i - #return len(self.course_list) + return len(self.courses) def __repr__(self): - return ", ".join([f"\"{str(x)}\"" for x in self.course_list]) # .course_list - #return f"{s}" - #return f"CourseList({self.name}, {s})" + return ", ".join([f"\"{str(x)}\"" for x in self.courses]) class DegreeRule: + rule_count = 1 def __init__(self, rule_type, course_list): self.rule_type = rule_type if 'n' in rule_type.__dict__: self.n = rule_type.n self.course_list = course_list - print("COURSELIST: ", type(self.course_list), self.course_list) - def mz_rule(self,n): - s = ",".join( [ f"{x}" for x in self.course_list]) - i = 0 - for c in self.course_list: - i += 1 - #print("What is this len? ", len(self.course_list), str(list(self.course_list)), str(type(self.course_list))) - t = len(list(self.course_list)) - if not type(self.course_list)==tuple: - t = self.course_list.l - return f"array[1..{t}] of string: rule_{n}_courses = [{s}];" + self.rule_number = DegreeRule.rule_count + DegreeRule.rule_count += 1 def __repr__(self): - return self.mz_rule(1) - #return f"DegreeRule({self.rule_type}, {self.course_list})" + s = ",".join( [ f"{x}" for x in self.course_list]) + the_rule = f"% {self.rule_type}\n" + return the_rule + f"array[1..{len(self.course_list)}] of string: rule_{self.rule_number}_courses = [{s}];" class Degree: def __init__(self, name, degree_rules): self.name = name self.degree_rules = degree_rules - def mz_rule(self): + def __repr__(self): n = 1 s = f"% Degree: {self.name}\n" for r in self.degree_rules: - s += r.mz_rule(n) + "\n" + s += str(r) + "\n" n += 1 return s - def __repr__(self): - #x = ", ".join([str(x) for x in self.degree_rules]) - #return f"Degree({self.name}, {x})" - return self.mz_rule() class RuleType(): def __init__(self,what,n=0): @@ -143,94 +104,70 @@ class DSLTransformer(Transformer): self.lists = {} def course_declaration(self, name, units): - #return Course(name, int(units), prerequisites.children) - #print(items) - #if type(items[0]) == str: - c = Course(name.value, units) - self.courses[name.value] = c - #else: - #options = [x.value for x in items[0].children] - #label = "[OR " + options[0] + "...]" - #c = CourseOr(label, options) - #self.courses[label] = c - - - '''def program(self, name, rules): - self.degrees[name.value] = Degree(name.value, rules) - return self.degrees[name.value]''' - - def rule_type(self, items): - what = items[0] - n = None - if len(items) == 2: - n = int(items[1]) - return RuleType(what, n) + d("\ncourse_declaration") + d(name) + d(units) def take_all(self): + d("\ntake_all") return TakeAll() def take_n_courses(self, n): + d("\ntake_n_courses") + d(n) return TakeN(n) def take_n_units(self, n): + d("\ntake_n_units") + d(n) return TakeNUnits(n) - def degree_rule(self, rule_type, *course_list): - #if type(course_list[0])==type([]): - # return DegreeRule(rule_type, course_list[0]) - print("making DegreeRule: ", rule_type, type(course_list), course_list) - return DegreeRule(rule_type, course_list) + def degree_rule(self, rule_type, course_list): + d("\ndegree_rule") + d(rule_type) + d(course_list) + return DegreeRule(rule_type, course_list) - def list_declaration(self, name, course_list): - print("declared course list: ", name.value, course_list) - cl = CourseList(name.value, course_list) - self.lists[name.value] = cl - return cl - def list_ref(self, name): - print("referenced course list: ", name.value, type(self.lists[name.value]), self.lists[name.value]) - return self.lists[name.value] + d("\nlist_ref") + d(name) + return self.lists[name] - def course(self, *items): - return Course(items[0].value,"") + def course(self, c): + d("\ncourse") + d(c) + return Course(c.value) def course_or(self, *items): - return CourseOr([x.value for x in items]) + d("\ncourse_or") + d(items) + return CourseOr(items) def course_list(self, *items): - #print(str(items)) - print("course list: ", items) - return CourseList("temp", items) - #return [c for c in items] + if items[-1] == None: items = items[:-1] + d("\ncourse_list") + d(items) + return items - def program(self, *items): - #print(items) - name = items[0].value - rules = items[1:] - self.degrees[name] = Degree(name, rules) - return self.degrees[name] - -""" -dsl = DSLTransformer() -dsl.course('CS101', 3) -dsl.course('CS102', 3, prerequisites=['CS101']) -dsl.course('MATH101', 3) -dsl.degree('CS', [ - dsl.take_all(['CS101', 'CS102']), - dsl.take_n_units(['CS101', 'CS102', 'MATH101'], 9), - dsl.take_n(['CS101', 'CS102', 'MATH101'], 2), -]) - -print(dsl.courses) -print(dsl.degrees) -""" + def list_declaration(self, name, course_list): + d("\nlist_declaration") + d(name) + d(course_list) + c = CourseList(name, course_list) + self.lists[name] = c + return c + + def program(self, name, *rules): + d("\nprogram") + d(name) + d(rules) + dg = Degree(name, rules) + self.degrees[name] = dg + return dg -#(course ","?)* - - grammar = """ start: _spec+ @@ -273,25 +210,9 @@ grammar = """ def parser(): dsl = """ - course CSIS1 2 units - course CSIS2 4 units - course ACCT120 3 units - course ACCT20 4 units - course BUS1 3 units - course ENGL250 3 units - course ACCT105 3 units - course ACCT121 3 units - course BOT100 3 units - course BOT105B 3 units - list a1: CMUN1A, CMUN5, CMUN8, CMUN10 list a2: ENGL1A list a3: PHIL2, PHIL4, ENGL1C, CMUN3 - list b1: ASTR1, CHEM1A, CHEM1B, CHEM12A, CHEM12B - list b2: AH15, ANTH1, BIO1, BIO2, BIO4, BIO5 - list b3: CHEM1A, ANTH1, BIO4, BIO5 - list b4: BUS14, BUS11 or ECON11, ECON14, MATH1A, MATH1B - list c1: BUS1, ENGL250, ACCT120, BOT100, ACCT121, BOT105B program BusinessAccountingOption @@ -312,11 +233,11 @@ def parser(): def do_parse(dsl): parser = Lark(grammar) - print(parser.parse(dsl).pretty()) + #d(parser.parse(dsl).pretty()) - print("\n\n\n") + #d("\n\n\n") - print("\nTRANSFORMER: ") + #d("\nTRANSFORMER: ") parser = Lark(grammar) transformer = DSLTransformer() @@ -337,53 +258,11 @@ def do_parse(dsl): [print(c) for c in transformer.courses] print() print("\nDEGREES: ") - for d,i in transformer.degrees.items(): + for deg,i in transformer.degrees.items(): print(str(i)) print() - [print(d) for d in transformer.lists] - - - - - -def rule_check_units(): - # Define the course units and passing grades - course_units = { - "A": 1, - "B": 2, - "C": 1, - "D": 3 - } - - # Define the students and their passed courses - students = { - "Peter": ["A", "B", "C"], - "Mark": ["D", "B"], - "Susan": ["A", "B"] - } - - # Define the minimum units required to graduate - min_units_to_graduate = 4 - - calcs = {} - for student, passed_courses in students.items(): - print("\n", student, passed_courses) - for c in passed_courses: - print(f"\t{c:15} {course_units[c]}") - print(f"\t{'Total':15} {sum(course_units[c] for c in passed_courses)}") - - # Determine the graduating students - graduating_students = [ - student for student, passed_courses in students.items() - if sum(course_units[course] for course in passed_courses) >= min_units_to_graduate - ] - - # Print the graduating students - if graduating_students: - print("The graduating students are:", ", ".join(graduating_students)) - else: - print("No students meet the graduation requirements.") + [print(deg) for deg in transformer.lists] @@ -391,27 +270,6 @@ def rule_check_units(): -""" - - -def compute_degree_progress(student_courses, degree): - total_units_completed = 0 - total_required_units = 0 - for course in degree['required_courses']: - if course['id'] in student_courses: - total_units_completed += course['units'] - if 'additional_rules' in course: - for rule in course['additional_rules']: - if rule['type'] == 'at_least_n_units_from_list': - units_from_list = sum([c['units'] for c in degree['courses'] if c['id'] in student_courses and c['id'] in rule['course_list']]) - if units_from_list < rule['n']: - break - total_required_units += course['units'] - progress_percent = total_units_completed / total_required_units * 100 if total_required_units > 0 else 0 - return progress_percent - -""" - word_to_num = {'course':1, 'One':1, 'one':1, 'two':2, 'three':3, 'four':4, 'five':5, 'six':6, 'seven':7, 'eight':8, 'nine':9, 'ten':10, 'eleven':11,} @@ -510,7 +368,7 @@ def check_ands_ors_pbd(award, pbd, verbose=0): if verbose: print() -def build_catalog(): +def build_program_rules(): cfile = "cache/courses/courses_active_built.json" pfile = "cache/programs/programs_built.json" @@ -630,7 +488,7 @@ def build_catalog(): if ok: okay.append(p) - print("These programs are okay:") + print("\n\n\n\nThese programs are okay:") for p in okay: for l in p: print(l) @@ -646,20 +504,16 @@ def build_catalog(): print(f"not okay: {len(notokay)}") return do_parse('\n'.join(course_spec)) - -## TODO or and and -## How to see this as a 'fake' course: Eligible for English 1A ? -## How to include this as part of a list: 'ANY COURSE NOT USED IN LIST A (3 units)' + if __name__ == "__main__": options = { 1: ['parsing example',parser] , - 2: ['rule check by units',rule_check_units] , - 3: ['load all programs',build_catalog] , + 2: ['build program rules', build_program_rules], } print ('') diff --git a/degrees_take1.py b/degrees_take1.py new file mode 100644 index 0000000..b99bbe9 --- /dev/null +++ b/degrees_take1.py @@ -0,0 +1,680 @@ +from lark import Lark, Transformer, v_args +import json, sys, re, codecs +import lark + + +"""describe college courses, their number of units, any other courses that are prerequisites, as well as various degrees which +consist of groups of courses that must be taken. The groups have rules associated with them, such as: +- take all (the following courses) +- take n (where n=3 or 5 or something) +- take at least n units from the following list (so the taken course's unit value must add up to n or greater) +- and so on.""" + + + +def num(s): + if s == '': return 0 + try: + return int(s) + except ValueError: + return float(s) + +def num_units(s): + if s == '': return 0 + m = re.match(r'^(\d+)\.?(\d+)?\s?(units)?$',s) + if m: + if m.group(1) and m.group(2): + if m.group(2) == '0': + return int(m.group(1)) + return float(f"{m.group(1)}.{m.group(2)}") + elif m.group(1): + return int(m.group(1)) + else: + try: + return int(s) + except ValueError: + return float(s) + + +class CourseOr: + def __init__(self, subs): + self.names = subs + self.name = " or ".join(self.names) + def __repr__(self): + return f"[{self.name}]" + + +class Course: + def __init__(self, name, units, prerequisites=[]): + self.name = name + self.units = units + self.prerequisites = prerequisites + def __repr__(self): + #return f"{self.name} ({self.units} units)" + return f"{self.name}" + +class CourseList: + def __init__(self, name, course_list): + self.name = name + self.course_list = course_list + self.l = len(course_list) + def __iter__(self): + return iter(self.course_list) + def __len__(self): + print("What is this len? ", len(self.course_list), str(list(self.course_list)), str(type(self.course_list))) + i = 1 + for c in self.course_list: + i += 1 + return i + #return len(self.course_list) + def __repr__(self): + return ", ".join([f"\"{str(x)}\"" for x in self.course_list]) # .course_list + #return f"{s}" + #return f"CourseList({self.name}, {s})" + +class DegreeRule: + def __init__(self, rule_type, course_list): + self.rule_type = rule_type + if 'n' in rule_type.__dict__: self.n = rule_type.n + self.course_list = course_list + print("COURSELIST: ", type(self.course_list), self.course_list) + def mz_rule(self,n): + s = ",".join( [ f"{x}" for x in self.course_list]) + i = 0 + for c in self.course_list: + i += 1 + #print("What is this len? ", len(self.course_list), str(list(self.course_list)), str(type(self.course_list))) + t = len(list(self.course_list)) + if not type(self.course_list)==tuple: + t = self.course_list.l + return f"array[1..{t}] of string: rule_{n}_courses = [{s}];" + def __repr__(self): + return self.mz_rule(1) + #return f"DegreeRule({self.rule_type}, {self.course_list})" + +class Degree: + def __init__(self, name, degree_rules): + self.name = name + self.degree_rules = degree_rules + def mz_rule(self): + n = 1 + s = f"% Degree: {self.name}\n" + for r in self.degree_rules: + s += r.mz_rule(n) + n += 1 + return s + def __repr__(self): + #x = ", ".join([str(x) for x in self.degree_rules]) + #return f"Degree({self.name}, {x})" + return self.mz_rule() + +class RuleType(): + def __init__(self,what,n=0): + self.what = what + self.n = n + def __repr__(self): + return "" #f"RuleType({self.what}, {self.n})" + +class Rule: + pass + +class TakeAll(Rule): + def __repr__(self): + return f"Take all:" + +class TakeN(Rule): + def __init__(self, n): + self.n = n + def __repr__(self): + return f"Take {self.n} courses:" + +class TakeNUnits(Rule): + def __init__(self, n): + self.n = n + def __repr__(self): + return f"Take {self.n} units:" + + +@v_args(inline=True) +class DSLTransformer(Transformer): + def __init__(self): + self.courses = {} + self.degrees = {} + self.lists = {} + + def course_declaration(self, name, units): + #return Course(name, int(units), prerequisites.children) + #print(items) + #if type(items[0]) == str: + c = Course(name.value, units) + self.courses[name.value] = c + #else: + #options = [x.value for x in items[0].children] + #label = "[OR " + options[0] + "...]" + #c = CourseOr(label, options) + #self.courses[label] = c + + + '''def program(self, name, rules): + self.degrees[name.value] = Degree(name.value, rules) + return self.degrees[name.value]''' + + def rule_type(self, items): + what = items[0] + n = None + if len(items) == 2: + n = int(items[1]) + return RuleType(what, n) + + def take_all(self): + return TakeAll() + + def take_n_courses(self, n): + return TakeN(n) + + def take_n_units(self, n): + return TakeNUnits(n) + + def degree_rule(self, rule_type, *course_list): + #if type(course_list[0])==type([]): + # return DegreeRule(rule_type, course_list[0]) + print("making DegreeRule: ", rule_type, type(course_list), course_list) + return DegreeRule(rule_type, course_list) + + def list_declaration(self, name, course_list): + print("declared course list: ", name.value, course_list) + cl = CourseList(name.value, course_list) + self.lists[name.value] = cl + return cl + + def list_ref(self, name): + print("referenced course list: ", name.value, type(self.lists[name.value]), self.lists[name.value]) + return self.lists[name.value] + + def course(self, *items): + return Course(items[0].value,"") + + def course_or(self, *items): + return CourseOr([x.value for x in items]) + + def course_list(self, *items): + #print(str(items)) + print("course list: ", items) + return CourseList("temp", items) + #return [c for c in items] + + def program(self, *items): + #print(items) + name = items[0].value + rules = items[1:] + self.degrees[name] = Degree(name, rules) + return self.degrees[name] + +""" +dsl = DSLTransformer() +dsl.course('CS101', 3) +dsl.course('CS102', 3, prerequisites=['CS101']) +dsl.course('MATH101', 3) +dsl.degree('CS', [ + dsl.take_all(['CS101', 'CS102']), + dsl.take_n_units(['CS101', 'CS102', 'MATH101'], 9), + dsl.take_n(['CS101', 'CS102', 'MATH101'], 2), +]) + +print(dsl.courses) +print(dsl.degrees) +""" + + + + +#(course ","?)* + + + +grammar = """ + start: _spec+ + + _spec: program | course_declaration | list_declaration + + program: "program" PROGRAMNAME degree_rule* + + degree_rule: "take" rule_type [course_list | list_ref] + + list_ref: LISTNAME + + rule_type: "all from" -> take_all + | "at least" INT "units from" -> take_n_units + | "at least" INT "courses from" -> take_n_courses + | "at least" INT "course from" -> take_n_courses + + list_declaration: "list" LISTNAME ":" course_list + + + course_declaration: "course" COURSECODE UNITAMOUNT "units" ["prerequisites" (COURSECODE ","?)*] + + course_list: [course | course_or] ["," [course | course_or]]* + + course: COURSECODE + + course_or: COURSECODE "or" COURSECODE + + COURSECODE: ("A".."Z")+ INT+ ["A".."Z"]* + PROGRAMNAME: ("A".."Z" | "a".."z")+ + LISTNAME: ("a".."z"["A".."Z" | "a".."z" | INT]+) + + UNITAMOUNT: NUMUNITS | NUMUNITS "-" NUMUNITS + NUMUNITS: INT | INT "." INT + %import common.INT + %import common.WS + %ignore WS +""" + + +def parser(): + dsl = """ + course CSIS1 2 units + course CSIS2 4 units + course ACCT120 3 units + course ACCT20 4 units + course BUS1 3 units + course ENGL250 3 units + course ACCT105 3 units + course ACCT121 3 units + course BOT100 3 units + course BOT105B 3 units + + list a1: CMUN1A, CMUN5, CMUN8, CMUN10 + list a2: ENGL1A + list a3: PHIL2, PHIL4, ENGL1C, CMUN3 + list b1: ASTR1, CHEM1A, CHEM1B, CHEM12A, CHEM12B + list b2: AH15, ANTH1, BIO1, BIO2, BIO4, BIO5 + list b3: CHEM1A, ANTH1, BIO4, BIO5 + list b4: BUS14, BUS11 or ECON11, ECON14, MATH1A, MATH1B + + list c1: BUS1, ENGL250, ACCT120, BOT100, ACCT121, BOT105B + + program BusinessAccountingOption + take all from CSIS1, CSIS2 + take at least 6 units from ACCT120, ACCT20 or ECON20 + take at least 3 courses from MUS5, MUS10, BUS1, ENGL250, ACCT120, BOT100, ACCT121, BOT105B + take at least 1 course from c1 + program CSUGenEd + take at least 1 course from a1 + take at least 1 course from a2 + take at least 1 course from a3 + """ + do_parse(dsl) + + + + + +def do_parse(dsl): + parser = Lark(grammar) + print(parser.parse(dsl).pretty()) + + print("\n\n\n") + + print("\nTRANSFORMER: ") + + parser = Lark(grammar) + transformer = DSLTransformer() + + def parse_dsl(dsl): + tree = parser.parse(dsl) + return transformer.transform(tree) + + + result = parse_dsl(dsl) + + print(transformer.courses) + print() + print(transformer.degrees) + print() + print(transformer.lists) + print() + [print(c) for c in transformer.courses] + print() + print("\nDEGREES: ") + for d,i in transformer.degrees.items(): + print(str(i)) + + print() + [print(d) for d in transformer.lists] + + + + + +def rule_check_units(): + # Define the course units and passing grades + course_units = { + "A": 1, + "B": 2, + "C": 1, + "D": 3 + } + + # Define the students and their passed courses + students = { + "Peter": ["A", "B", "C"], + "Mark": ["D", "B"], + "Susan": ["A", "B"] + } + + # Define the minimum units required to graduate + min_units_to_graduate = 4 + + calcs = {} + for student, passed_courses in students.items(): + print("\n", student, passed_courses) + for c in passed_courses: + print(f"\t{c:15} {course_units[c]}") + print(f"\t{'Total':15} {sum(course_units[c] for c in passed_courses)}") + + # Determine the graduating students + graduating_students = [ + student for student, passed_courses in students.items() + if sum(course_units[course] for course in passed_courses) >= min_units_to_graduate + ] + + # Print the graduating students + if graduating_students: + print("The graduating students are:", ", ".join(graduating_students)) + else: + print("No students meet the graduation requirements.") + + + + + + + +""" + + +def compute_degree_progress(student_courses, degree): + total_units_completed = 0 + total_required_units = 0 + for course in degree['required_courses']: + if course['id'] in student_courses: + total_units_completed += course['units'] + if 'additional_rules' in course: + for rule in course['additional_rules']: + if rule['type'] == 'at_least_n_units_from_list': + units_from_list = sum([c['units'] for c in degree['courses'] if c['id'] in student_courses and c['id'] in rule['course_list']]) + if units_from_list < rule['n']: + break + total_required_units += course['units'] + progress_percent = total_units_completed / total_required_units * 100 if total_required_units > 0 else 0 + return progress_percent + +""" + +word_to_num = {'course':1, 'One':1, 'one':1, 'two':2, 'three':3, 'four':4, 'five':5, 'six':6, + 'seven':7, 'eight':8, 'nine':9, 'ten':10, 'eleven':11,} + +def word2num(word, verbose=0): + word = word.lower() + ret = word_to_num[word] if word in word_to_num else word + if verbose: + print(f" word2num({word}) -> {ret}") + return ret + +lab_classes = {} + +def load_lab_classes(): + global lab_classes + if lab_classes: + return lab_classes + for c in json.loads(codecs.open('cache/courses/courses_built.json','r','utf-8').read()).values(): + #print(c) + if 'min_lab_hour' in c and float(c['min_lab_hour']) > 0: + lab_classes[c['dept'] + c['number']] = 1 + print(lab_classes) + return lab_classes + +def is_lab_class(c): + lab_classes = load_lab_classes() + if c in lab_classes: + return True + return False + + +def is_noncourse_new_section(noncourse_line): + from degree_vars import note_true_section, note_false_section + for non in note_false_section: + if re.search(non, noncourse_line): + #print(f"- {noncourse_line}") + return False + for yes in note_true_section: + if re.search(yes, noncourse_line): + #print(f"+ {noncourse_line}") + return True + print(f" -> should this start a new rule/section? [{noncourse_line}]") + return False + + +rule_lookup = { + 'take_all_prereq': ['RN PROGRAM PREREQUISITES', 'PREREQUISITES', ], + 'take at least n courses': ['(\d+) courses total', 'SELECT (ONE|TWO) OF THE FOLLOWING', 'Select (one|two|three)', 'Select (\d+) courses', + 'Choose (one) or more', 'Choose (one|two|three) of the classes listed', + 'Choose (\w+) of the following','Choose (one|two|three)', 'Choose ([\d\w]+) courses from', + 'ANY (COURSE) NOT USED IN', 'Select (1)', 'Select (one) of the following REQUIRED CORE', + '(One) of the following:', 'LIST [AB]: Select (\d)', 'Choose (One) Course:', ], + 'take at least n units': ['LIST A \((\d+) units\)', 'LIST B \((\d+) units\)', 'LIST C \- Any course .*\((\d+) units\)', '(\d+) units total', 'Select (\d+) units', 'Any combination totaling (\d+) units', + 'Choose (\w+) units from classes listed', 'Choose a minimum of ([\w\d]+) units from', + 'Choose any combination of courses for a minimum of ([\w\d]+) units', + 'Choose ([\w\d]+) units', 'Choose courses for at least ([\w\d]+) units', + 'Choose a minimum of (\d+) units', 'Select any (\d+)\-\d+ units from the following'], + 'electives': ['Electives', 'Recommended electives?:', ], + 'take_all': ['RN PROGRAM', 'REQUIRED CORE', 'CORE COURSES', 'ADDITIONAL REQUIREMENTS','REQUIREMENTS:', 'Requirements', 'Core Requirements', + 'Required Core', 'REQUIRED', 'LVN PROGRAM', 'Student Teaching Practicum', '^LIST A:?$', '^LIST B:$', + 'Program Requirements', 'Required Courses:', 'PROGRAM REQUIREMENTS (5 Units)', 'PROGRAM REQUIREMENTS (162 Hours)', + ], +} + +def lookup_rule(line): + verbose = 0 + for key in rule_lookup.keys(): + for each in rule_lookup[key]: + m = re.search(each, line) + if m: + num = None + try: + if m.group(1): + num = m.group(1) + except Exception as e: + pass + if verbose: print(f"line: {line} matched: {each} with {num}") + return key,num + return None,None + +def examine(li,award, verbose=0): + summary = [x[0] for x in li] + if summary[1] in ['and','or']: + if verbose: print(" - ", summary) + + +def check_ands_ors_pbd(award, pbd, verbose=0): + verbose = 0 + if verbose: print(f"check_ands_ors_pbd({award}, ...)") + summary = [x[0] for x in pbd] + if verbose: print(" ", summary) + + # iterate through in groups of 3, from 0/1/2 up to 3/4/5 + # (for length 6. length n: n-2, n-1, n) + for i in range(len(pbd)-2): + examine(pbd[i:i+3], award, verbose) + if verbose: print() + + +def build_catalog(): + cfile = "cache/courses/courses_active_built.json" + pfile = "cache/programs/programs_built.json" + + courses = json.loads(codecs.open(cfile,'r','utf-8').read()) + programs = json.loads(codecs.open(pfile,'r','utf-8').read()) + + course_spec = [] + + for index,c in courses.items(): + try: + d = c['dept'] + n = c['number'] + name = c['name'] + u2 = num_units(c['min_units']) + if 'max_units' in c: + u1 = num_units(c['max_units']) + else: + u1 = u2 + + if u1 == u2: + units = {'units': u1} + u = u1 + else: + units = {'min_units': u2, 'max_units': u1} + u = f"{u2}-{u1}" + + course_spec.append(f"course {d}{n} {u} units") + except Exception as e: + pass + #print(e) + #print(json.dumps(c,indent=2)) + + d_out = codecs.open('cache/prog_debug.txt','w','utf-8') + def d(s): d_out.write(str(s) + "\n") + + + for index,p in programs.items(): + v2 = 0 # print debugging stuff + # Each award (degree or certificate) + award = p['award'] + " " + p['program_title'] + d("\n" + p['award'] + " " + p['program_title']) + print("\n" + award) + this_program = p['award'] + " " + p['program_title'] + this_rule = "" + r = p['requirements'] + course_count = 0 + + # Each numbered chunk (k) in the requirements section + for k in sorted(r.keys()): + + # Each 'program block definition' + # 1st is dict with unit totals, rest are lists. + + check_ands_ors_pbd( award, sorted( r[k][1:], key=lambda x: float(x[1])) ) + for each_r in sorted( r[k][1:], key=lambda x: float(x[1])): + + if each_r[0] in ['and','or']: + #print(' ', each_r[0],each_r[1]) + pass + if isinstance(each_r, list): + #print(each_r) + if each_r[0] == 'h3' or (each_r[0]=='noncourse' and is_noncourse_new_section(each_r[2])): + # This is a rule title + if this_rule and course_count: + d(this_rule) # + f" ({course_count}) " + course_count = 0 + raw_rule = each_r[2] + good_rule, num = lookup_rule(raw_rule) + if good_rule: + #print(f"\t{good_rule}") + n = word2num(num) if num else "" + if v2: print(f"\tn = {n}") + actual_rule = re.sub(r'\sn\s',f' {n} ',good_rule) + if v2: print(f"\tactual rule is: {actual_rule}") + #this_rule = f"{good_rule} ({n}) [{raw_rule}] from " + this_rule = f"{actual_rule} from " + else: + #print(f"\t{raw_rule}") + if not this_rule: + this_rule = "take_all from " + this_rule = " * " + raw_rule + " " + "from " + #elif each_r[0] == 'noncourse': # also a rule title, some kind of sub-rule? + # d( f" ++ (noncourse) {each_r[2]}") + elif each_r[0] == 'course': # course in a rule + if not this_rule: + this_rule = "take_all from " + is_lab = '[L]' if is_lab_class(each_r[2]['code']) else '' + #this_rule += f"{each_r[2]['code']}{is_lab}({each_r[1]}), " + this_rule += f"{each_r[2]['code']}{is_lab}, " + if v2: print(f"\t\tthis rule is now: {this_rule}") + course_count += 1 + if course_count: + d(this_rule) # + f" ({course_count})" + + + d_out.close() + d_in = codecs.open('cache/prog_debug.txt','r','utf-8').readlines() + progs = [] + this_prog = [] + for line in d_in: + if line.strip() == '': + if this_prog: + progs.append(this_prog) + this_prog = [] + else: + this_prog.append(line.strip()) + + okay = [] + notokay = [] + for p in progs: + ok =1 + for line in p: + if line[0] == '*': + notokay.append(p) + ok = 0 + continue + if ok: + okay.append(p) + + print("These programs are okay:") + for p in okay: + for l in p: + print(l) + print() + + print("\n\nThese programs are not okay:") + for p in notokay: + for l in p: + print(l) + print() + + print(f"okay: {len(okay)}") + print(f"not okay: {len(notokay)}") + return + do_parse('\n'.join(course_spec)) + + + + +## TODO or and and +## How to see this as a 'fake' course: Eligible for English 1A ? + +## How to include this as part of a list: 'ANY COURSE NOT USED IN LIST A (3 units)' + + +if __name__ == "__main__": + options = { 1: ['parsing example',parser] , + 2: ['rule check by units',rule_check_units] , + 3: ['load all programs',build_catalog] , + } + print ('') + + if len(sys.argv) > 1 and re.search(r'^\d+',sys.argv[1]): + resp = int(sys.argv[1]) + print("\n\nPerforming: %s\n\n" % options[resp][0]) + + else: + print ('') + for key in options: + print(str(key) + '.\t' + options[key][0]) + + print('') + resp = input('Choose: ') + + # Call the function in the options dict + options[ int(resp)][1]() + diff --git a/stats.py b/stats.py index 88e8ebc..51ef202 100644 --- a/stats.py +++ b/stats.py @@ -500,7 +500,7 @@ def reorganize_grades_student(): output.writerow(row) -def all_course_names(): +def all_course_names_setup(): cc = json.loads(codecs.open('cache/courses/courses_built.json','r','utf-8').read()) courses = {} for C in cc.values(): @@ -533,6 +533,82 @@ def all_course_names(): #co.write( json.dumps( {'unknown':unknown, 'coursenames':courses}, indent=2 )) +lookup = {} +names = {} + +def shell2course(shell): + global lookup, names + if not lookup: + cr = json.loads(codecs.open('cache/courses/names.json','r','utf-8').read()) + lookup = cr['unknown'] + allcourses = cr['coursenames'] + names = allcourses.keys() + + if shell in names: + return shell + if shell in lookup: + c = lookup[shell] + if c in names: + return c + #print(f"Can't find course: {shell}") + return "" + + + + +def stu_record_line(line): + line = line.strip() + parts = line.split(',') + stu_id = parts[0] + courses = [] + for C in parts[1:]: + courses.append(C.split('|')) + return stu_id, courses + +def stu_record_to_vector(line): + id, courses = stu_record_line(line) + + template = json.loads(codecs.open('cache/courses/course_main_record.json','r','utf-8').read()) + lookup = {} + for i,c in enumerate(template): + lookup[c] = i + vector = [0 for x in range(len(template))] + for C in courses: + goodname = shell2course(C[0]) + if goodname: + vector[lookup[goodname]] = 1 # C[1] # score + return id,vector + + +def grades_to_vectors(): + grades = codecs.open('cache/courses_student_scores.csv','r','utf-8').readlines() + for L in grades: + id, vector = stu_record_to_vector(L) + print(id, vector) + + +def all_course_names(): + + + complete_list = {} + missing_names = {} + with open(student_courses_scores,'r') as input_f: + for L in input_f: + stu_id, courses = stu_record_line(L) + for C in courses: + real_name = shell2course(C[0]) + if real_name: + complete_list[real_name] = 1 + else: + missing_names[C[0]] = 1 + master_record = sorted(complete_list.keys()) + print(f"Found {len(master_record)} courses") + print(master_record) + print(f"Missing {len(missing_names)} courses") + print(missing_names) + mr = codecs.open('cache/courses/course_main_record.json','w','utf-8') + mr.write(json.dumps(master_record,indent=2)) + @@ -544,7 +620,8 @@ if __name__ == "__main__": 4: ['test shortname parse',nametest] , 5: ['test sem codes',codetest] , 6: ['get student data from orientations', get_student_orientations], - 7: ['manage course master list', all_course_names] + 7: ['manage course master list', all_course_names], + 8: ['grades to vectors', grades_to_vectors] } print ('') diff --git a/tasks.py b/tasks.py index 89b0f34..1727beb 100644 --- a/tasks.py +++ b/tasks.py @@ -17,7 +17,7 @@ import pysftp, os, datetime, requests, re, json, sqlite3, codecs, csv, sys import funcy, os.path, shutil, urllib from datetime import datetime from collections import defaultdict -from datetime import strptime +#from datetime import strptime from time import mktime from canvas_secrets import badgr_target, badgr_hd @@ -30,7 +30,7 @@ if os.name != 'posix': from docxtpl import DocxTemplate import xlwt -from pipelines import header, url, fetch, convert_roster_files, move_to_folder, fetch_dict +from pipelines import header, url, fetch, convert_roster_files, move_to_folder from courses import course_enrollment from users import teacherRolesCache from util import match59, partition @@ -1303,8 +1303,19 @@ def file_renamer(): os.rename( where+F, where+nn ) print("ok") +def list_auth(): + r = fetch( url + '/api/v1/accounts/1/authentication_providers') + print(json.dumps(r,indent=2)) - + +def update_auth(): + #r = fetch( url + '/api/v1/accounts/1/authentication_providers') + u = url + '/api/v1/accounts/1/authentication_providers/104' + opt = {"metadata_uri": r'https://eis-prod.ec.gavilan.edu/saml/idp-metadataxxx.xml'} + r2 = requests.put(u, headers=header, data=opt) + print ( r2.text ) + + #print(json.dumps(r,indent=2)) if __name__ == "__main__": @@ -1318,6 +1329,8 @@ if __name__ == "__main__": 8: ['collate all profile pics for db',index_pics] , 9: ['process schedule csv file from web',parse_schedule] , 10: ['dumb rename images mistake',file_renamer] , + 11: ['list auth', list_auth], + 12: ['update auth', update_auth], } if len(sys.argv) > 1 and re.search(r'^\d+',sys.argv[1]): diff --git a/zinc.py b/zinc.py index dbfa180..64c1022 100644 --- a/zinc.py +++ b/zinc.py @@ -1,16 +1,16 @@ from minizinc import Instance, Model, Solver -# Load -nqueens = Model("cache/playground.mzn") +# Load n-Queens model from file +nqueens = Model("./cache/sample_degree_check.mzn") +#nqueens = Model("./cache/nqueens.mzn") # Find the MiniZinc solver configuration for Gecode gecode = Solver.lookup("gecode") # Create an Instance of the n-Queens model for Gecode instance = Instance(gecode, nqueens) # Assign 4 to n #instance["n"] = 4 -#result = instance.solve() -result = instance.solve(all_solutions=True) + +result = instance.solve() # Output the array q -#print(result) -for i in range(len(result)): - print(result[i]) \ No newline at end of file +print(result) +#print(result["rule_1_needed","rule_2_needed"]) \ No newline at end of file