Skip to content

Commit

Permalink
Start updating fetch_degreeworks
Browse files Browse the repository at this point in the history
  • Loading branch information
AaDalal committed Oct 13, 2023
1 parent b014d62 commit a699a3a
Show file tree
Hide file tree
Showing 7 changed files with 2,440 additions and 2,444 deletions.
8 changes: 6 additions & 2 deletions backend/degree/management/commands/fetch_degreeplans.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.core.management.base import BaseCommand

from degree.degreeworks.request_degreeworks import DegreeworksClient
from backend.degree.utils.request_degreeworks import DegreeworksClient

from degree.degreeworks.parse_degreeworks import parse_degreeworks

Expand All @@ -13,6 +13,8 @@

from courses.util import get_current_semester

from django.db import transaction

class Command(BaseCommand):
help = dedent("""
Lists the available degreeplans for a semester.
Expand Down Expand Up @@ -72,7 +74,9 @@ def handle(self, *args, **kwargs):
name=name
)


for year in range(since_year, to_year + 1):
for program in client.get_programs(year=year):
for degree_plan in client.degree_plans_of(program, year=year):
pprint(parse_degreeworks(client.audit(degree_plan), degree_plan))
with transaction.atomic():
parse_degreeworks(client.audit(degree_plan), degree_plan)
10 changes: 1 addition & 9 deletions backend/degree/management/commands/list_degreeplans.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@

from textwrap import dedent

from dataclasses import asdict

from django.core.management.base import BaseCommand

from degree.degreeworks.request_degreeworks import DegreeworksClient

from backend.degree.utils.request_degreeworks import DegreeworksClient
from os import getenv

from tqdm import tqdm

from pprint import pprint

from courses.util import get_current_semester

class Command(BaseCommand):
Expand Down
57 changes: 57 additions & 0 deletions backend/degree/migrations/0006_auto_20231013_1702.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Generated by Django 3.2.20 on 2023-10-13 21:02

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('degree', '0005_auto_20231012_1226'),
]

operations = [
migrations.AddField(
model_name='rule',
name='title',
field=models.CharField(blank=True, help_text='\nThe title for this rule.\n', max_length=200),
),
migrations.AlterField(
model_name='degreeplan',
name='concentration',
field=models.CharField(help_text='\nThe concentration code for this degree plan, e.g., BMAT\n', max_length=4, null=True),
),
migrations.AlterField(
model_name='degreeplan',
name='degree',
field=models.CharField(help_text='\nThe degree code for this degree plan, e.g., BSE\n', max_length=4),
),
migrations.AlterField(
model_name='degreeplan',
name='major',
field=models.CharField(help_text='\nThe major code for this degree plan, e.g., BIOL\n', max_length=4),
),
migrations.AlterField(
model_name='degreeplan',
name='program',
field=models.CharField(choices=[('EU_BSE', 'Engineering BSE'), ('EU_BAS', 'Engineering BAS'), ('AU_BA', 'College BA'), ('WU_BS', 'Wharton BS')], help_text='\nThe program code for this degree plan, e.g., EU_BSE\n', max_length=10),
),
migrations.AlterField(
model_name='rule',
name='credits',
field=models.DecimalField(decimal_places=2, help_text='\nThe minimum number of CUs required for this rule. Only non-null\nif this is a Rule leaf. Can be \n', max_digits=4, null=True),
),
migrations.AlterField(
model_name='rule',
name='num_courses',
field=models.PositiveSmallIntegerField(help_text='\nThe minimum number of courses or subrules required for this rule. Only non-null\nif this is a Rule leaf.\n', null=True),
),
migrations.AlterField(
model_name='rule',
name='q',
field=models.TextField(help_text='\nString representing a Q() object that returns the set of courses\nsatisfying this rule. Only non-null/non-empty if this is a Rule leaf.\nThis Q object is expected to be normalized before it is serialized\nto a string.\n', max_length=1000),
),
migrations.AddConstraint(
model_name='rule',
constraint=models.CheckConstraint(check=models.Q(models.Q(('credits__isnull', True), ('credits__gt', 0), _connector='OR'), models.Q(('num_courses__isnull', True), ('num_courses__gt', 0), _connector='OR')), name='num_course_credits_gt_0'),
),
]
11 changes: 10 additions & 1 deletion backend/degree/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ class Rule(models.Model):
"""
This model represents a degree requirement rule.
"""
title = models.CharField(
max_length=200,
blank=True,
help_text=dedent(
"""
The title for this rule.
"""
)
)

num_courses = models.PositiveSmallIntegerField(
null=True,
Expand Down Expand Up @@ -162,7 +171,7 @@ def evaluate(self, full_codes: Iterable[str]) -> bool:
return False

# TODO: run some extra checks...

return True

assert self.children.all().exists()
Expand Down
133 changes: 35 additions & 98 deletions backend/degree/utils/parse_degreeworks.py
Original file line number Diff line number Diff line change
@@ -1,92 +1,8 @@
from django.db.models import Q

from degree.models import DegreePlan, Rule

# TODO: these should not be hardcoded, but rather added to the database
E_DEPTS = ["BE", "CIS", "CMPE", "EAS", "ESE", "MEAM", "MSE", "NETS", "ROBO"] # SEAS
A_DEPTS = [
"AFST",
"AMCS",
"ANTH",
"ARAB",
"ARCH",
"ARTH",
"ASAM",
"ASTR",
"BIBB",
"BIOE",
"BIOL",
"BIOM",
"BMIN",
"CAMB",
"CHEM",
"CHIN",
"CIMS",
"CINE",
"CIS",
"CLST",
"COLL",
"COML",
"CRIM",
"CSCI",
"EALC",
"ECON",
"ENGL",
"ENVS",
"FNAR",
"FOLK",
"FREN",
"GDES",
"GEOL",
"GRMN",
"GSWS",
"HCMG",
"HEBR",
"HIST",
"HSOC",
"IGGS",
"INTL",
"ITAL",
"JPAN",
"LALS",
"LATN",
"LGIC",
"LING",
"MATH",
"MUSC",
"NELC",
"PHIL",
"PHYS",
"PPE",
"PSCI",
"PSYC",
"RELS",
"RUSS",
"SAST",
"SOCI",
"SPAN",
"STAT",
"STSC",
"SWRK",
"TAML",
"THAR",
"TURK",
"URBS",
"URDU",
"WRIT",
] # SAS
W_DEPTS = [
"ACCT",
"BEPP",
"FNCE",
"HCMG",
"LGST",
"MKTG",
"OIDD",
"REAL",
"STAT",
] # Wharton

from pprint import pprint
from constants import ENG_DEPTS, SAS_DEPTS, WH_DEPTS

def parse_coursearray(courseArray) -> Q:
"""
Expand Down Expand Up @@ -145,11 +61,11 @@ def parse_coursearray(courseArray) -> Q:
assert len(filter["valueList"]) == 1
match filter["valueList"][0]:
case "E" | "EU":
sub_q = Q(course__department__code__in=E_DEPTS)
sub_q = Q(course__department__code__in=ENG_DEPTS)
case "A" | "AU":
sub_q = Q(course__department__code__in=A_DEPTS)
sub_q = Q(course__department__code__in=SAS_DEPTS)
case "W" | "WU":
sub_q = Q(course__department__code__in=W_DEPTS)
sub_q = Q(course__department__code__in=WH_DEPTS)
case _:
raise ValueError(
f"Unsupported college in withArray: {filter['valueList'][0]}"
Expand Down Expand Up @@ -232,7 +148,7 @@ def evaluate_condition(condition, degree_plan) -> bool:
raise LookupError(f"Unknown condition type in ifStmt: {condition.keys()}")


def parse_rulearray(ruleArray, degree_plan, parent_rule=None) -> list[Rule]:
def parse_rulearray(ruleArray, degree_plan, parent: Rule=None) -> list[Rule]:
"""
Logic to parse a single degree ruleArray in a blockArray requirement.
A ruleArray consists of a list of rule objects that contain a requirement object.
Expand Down Expand Up @@ -287,10 +203,7 @@ def parse_rulearray(ruleArray, degree_plan, parent_rule=None) -> list[Rule]:
rules.append(
Rule(
q=str(parse_coursearray(rule_req["courseArray"])),
min_num=num,
max_num=max_num,
min_cus=cus,
max_cus=max_cus,
credits=
)
)
case "IfStmt":
Expand Down Expand Up @@ -334,7 +247,6 @@ def parse_rulearray(ruleArray, degree_plan, parent_rule=None) -> list[Rule]:
else:
print("WARNING: subset has no ruleArray")
case "Group": # TODO: this is nested
assert parent_rule is None or parent_rule["ruleType"] != "Group"
q = Q()
[q := q & rule.q for rule in parse_rulearray(rule["ruleArray"], degree_plan)]

Expand All @@ -354,7 +266,7 @@ def parse_rulearray(ruleArray, degree_plan, parent_rule=None) -> list[Rule]:


# TODO: Make the function names more descriptive
def parse_degreeworks(json: str, degree_plan: DegreePlan) -> list[Rule]:
def parse_degreeworks(json: dict, degree_plan: DegreePlan) -> list[Rule]:
"""
Returns a list of Rules given a DegreeWorks JSON audit and a DegreePlan.
"""
Expand All @@ -368,11 +280,36 @@ def parse_degreeworks(json: str, degree_plan: DegreePlan) -> list[Rule]:
name=requirement["title"],
code=requirement["requirementValue"],
# TODO: parse min_cus
min_cus=0,
credits=0,
)

# TODO: Should associate each Rule here with this Requirement
print(parse_rulearray(requirement["ruleArray"], degree_plan))
parse_rulearray(requirement["ruleArray"], degree_plan)

degree_reqs.append(degree_req)
return degree_reqs

if __name__ == "__main__":
from backend.degree.utils.constants import E_BAS_DEGREE_PLANS, W_DEGREE_PLANS, A_DEGREE_PLANS
from backend.degree.utils.request_degreeworks import DegreeworksClient
from os import getenv

pennid = getenv("PENN_ID")
assert pennid is not None
auth_token = getenv("X-AUTH-TOKEN")
assert pennid is not None
refresh_token = getenv("REFRESH_TOKEN")
assert refresh_token is not None
name = getenv("NAME")
assert name is not None

client = DegreeworksClient(
pennid=pennid,
auth_token=auth_token,
refresh_token=refresh_token,
name=name
)

for i, degree_plan in enumerate(E_BAS_DEGREE_PLANS):
print(degree_plan)
pprint(parse_degreeworks(client.audit(degree_plan), degree_plan))
Loading

0 comments on commit a699a3a

Please sign in to comment.