canvasapp/degrees.py

220 lines
5.7 KiB
Python

from lark import Lark, Transformer, v_args
import json
"""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."""
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)"
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
def __repr__(self):
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 __repr__(self):
return f"Degree({self.name}, {self.degree_rules})"
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 = {}
def course_declaration(self, name, units):
#return Course(name, int(units), prerequisites.children)
self.courses[name.value] = Course(name.value, int(units))
'''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):
return DegreeRule(rule_type, course_list)
def course_list(self, *courses):
return [c.value for c in courses]
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)
"""
grammar = """
start: _spec+
_spec: program | course_declaration
program: "program" PROGRAMNAME degree_rule*
degree_rule: "take" rule_type course_list
rule_type: "all from" -> take_all
| "at least" INT "units from" -> take_n_units
| "at least" INT "courses from" -> take_n_courses
course_declaration: "course" COURSECODE INT "units" ["prerequisites" (COURSECODE ","?)*]
course_list: (COURSECODE ","?)*
COURSECODE: ("A".."Z")+ INT+ ["A".."Z"]*
PROGRAMNAME: ("A".."Z" | "a".."z")+
%import common.INT
%import common.WS
%ignore WS
"""
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
program BusinessAccountingOption
take all from CSIS1, CSIS2
take at least 6 units from ACCT120, ACCT20
take at least 3 courses from BUS1, ENGL250, ACCT120, BOT100, ACCT121, BOT105B
"""
parser = Lark(grammar)
print(parser.parse(dsl).pretty())
print("\n\n\n")
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(c) for c in transformer.courses]
print()
[print(d) for d in transformer.degrees]
"""
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
"""