canvasapp/degrees.py

165 lines
4.4 KiB
Python

from lark import Lark, Transformer, v_args
"""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."""
grammar = """
start: program
program: course_declaration*
course_declaration: "course" CNAME INT "prerequisites" (CNAME ","?)* "degree" CNAME "{" degree_rule* "}"
degree_rule: "take" ("all" | "at" INT "from") ("the" "following") "courses" course_list
course_list: (CNAME ","?)*
%import common.CNAME
%import common.INT
%import common.WS
%ignore WS
"""
class Course:
def __init__(self, name, units, prerequisites, degree):
self.name = name
self.units = units
self.prerequisites = prerequisites
self.degree = degree
class DegreeRule:
def __init__(self, verb, n, course_list):
self.verb = verb
self.n = n
self.course_list = course_list
class Degree:
def __init__(self, name, degree_rules):
self.name = name
self.degree_rules = degree_rules
class Rule:
pass
class TakeAll(Rule):
def __init__(self, courses):
self.courses = courses
class TakeN(Rule):
def __init__(self, courses, n):
self.courses = courses
self.n = n
class TakeNUnits(Rule):
def __init__(self, courses, n):
self.courses = courses
self.n = n
@v_args(inline=True)
class DSLTransformer(Transformer):
def __init__(self):
self.courses = {}
self.degrees = {}
def course_declaration(self, name, units, prerequisites, degree):
return Course(name, int(units), prerequisites.children, degree)
def degree(self, name, required_courses):
self.degrees[name] = Degree(name, required_courses)
def take_all(self, courses):
return TakeAll(courses)
def take_n(self, courses, n):
return TakeN(courses, n)
def take_n_units(self, courses, n):
return TakeNUnits(courses, n)
def degree_rule(self, items):
verb = items[0]
n = None
course_list = items[-1]
if len(items) == 4:
n = int(items[2])
return DegreeRule(verb, n, course_list.children)
def course_list(self, items):
return items[:-1]
def program(self, items):
courses = [item for item in items if isinstance(item, Course)]
degrees = [item for item in items if isinstance(item, Degree)]
return courses, degrees
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)
parser = Lark(grammar)
transformer = DSLTransformer()
def parse_dsl(dsl):
tree = parser.parse(dsl)
return transformer.transform(tree)
dsl = """
course CS101 3 prerequisites none degree CS {
take all the following courses {
CS102, CS103
}
take at least 6 from {
MATH101, MATH102, MATH103
}
}
"""
courses, degrees = parse_dsl(dsl)
print(courses)
print(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