diff --git a/backend/degree/management/commands/import_degreeworks.py b/backend/degree/management/commands/import_degreeworks.py new file mode 100644 index 000000000..cb8e4ec47 --- /dev/null +++ b/backend/degree/management/commands/import_degreeworks.py @@ -0,0 +1,18 @@ +import tqdm +from django.core.management.base import BaseCommand + +from degree.models import DegreePlan, Rule +from degree.utils.request_degreeworks import audit, degree_plans_of, get_programs, write_dp + + +class Command(BaseCommand): + # TODO: ADD HELP TEXT + help = "" + + def handle(self, *args, **kwargs): + for year in range(2017, 2023 + 1): + print(year) + for program in get_programs(year=year): + print("\t" + program) + for degree_plan in tqdm(degree_plans_of(program), year=year): + write_dp(degree_plan, audit(degree_plan)) diff --git a/backend/degree/migrations/0003_auto_20231001_1145.py b/backend/degree/migrations/0003_auto_20231001_1145.py new file mode 100644 index 000000000..97f706323 --- /dev/null +++ b/backend/degree/migrations/0003_auto_20231001_1145.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.20 on 2023-10-01 15:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("degree", "0002_auto_20230924_1412"), + ] + + operations = [ + 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=32, + ), + ), + migrations.RemoveField( + model_name="requirement", + name="degree_plan", + ), + migrations.AddField( + model_name="requirement", + name="degree_plan", + field=models.ManyToManyField( + help_text="\nThe degree plan(s) that have this requirement.\n", + to="degree.DegreePlan", + ), + ), + ] diff --git a/backend/degree/migrations/0004_auto_20231006_1754.py b/backend/degree/migrations/0004_auto_20231006_1754.py new file mode 100644 index 000000000..b951af59b --- /dev/null +++ b/backend/degree/migrations/0004_auto_20231006_1754.py @@ -0,0 +1,84 @@ +# Generated by Django 3.2.20 on 2023-10-06 21:54 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("degree", "0003_auto_20231001_1145"), + ] + + operations = [ + migrations.RemoveField( + model_name="rule", + name="max_cus", + ), + migrations.RemoveField( + model_name="rule", + name="max_num", + ), + migrations.RemoveField( + model_name="rule", + name="min_cus", + ), + migrations.RemoveField( + model_name="rule", + name="min_num", + ), + migrations.RemoveField( + model_name="rule", + name="requirement", + ), + migrations.AddField( + model_name="rule", + name="cus", + field=models.DecimalField( + decimal_places=1, + help_text="\nThe minimum number of CUs required for this rule. Only non-null\nif this is a Rule leaf.\n", + max_digits=4, + null=True, + ), + ), + migrations.AddField( + model_name="rule", + name="degree_plan", + field=models.ForeignKey( + default=0, + help_text="\nThe degree plan that has this rule.\n", + on_delete=django.db.models.deletion.CASCADE, + to="degree.degreeplan", + ), + preserve_default=False, + ), + migrations.AddField( + model_name="rule", + name="num", + field=models.IntegerField( + help_text="\nThe minimum number of courses or subrules required for this rule.\n", + null=True, + ), + ), + migrations.AddField( + model_name="rule", + name="parent", + field=models.ForeignKey( + help_text="\nThis rule's parent Rule if it has one.\n", + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="degree.rule", + ), + ), + 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-empty if this is a Rule leaf.\n", + max_length=1000, + ), + ), + migrations.DeleteModel( + name="Requirement", + ), + ] diff --git a/backend/degree/models.py b/backend/degree/models.py index 37893ff2f..4ec4e6bbe 100644 --- a/backend/degree/models.py +++ b/backend/degree/models.py @@ -1,8 +1,5 @@ from django.db import models from textwrap import dedent -from django.contrib.auth import get_user_model - -from courses.models import Topic, Course, string_dict_to_html class DegreePlan(models.Model): @@ -58,114 +55,65 @@ class DegreePlan(models.Model): ) def __str__(self) -> str: - return f"DegreePlan: {self.program} {self.degree} in {self.major} with conc. {self.concentration} ({self.year})" + return f"{self.program} {self.degree} in {self.major} with conc. {self.concentration} ({self.year})" -class Requirement(models.Model): +class Rule(models.Model): """ - This model represents a degree requirement. + This model represents a degree requirement rule. """ - name = models.CharField( - max_length=256, - help_text=dedent( - """ - The name of this requirement, e.g., General Education, Foundations - """ - ), - ) - code = models.CharField( - max_length=32, + num = models.IntegerField( + null=True, help_text=dedent( """ - The canonical code for this requirement, e.g., U-GE-FND + The minimum number of courses or subrules required for this rule. """ ), ) - min_cus = models.DecimalField( + + cus = models.DecimalField( decimal_places=1, max_digits=4, null=True, help_text=dedent( """ - The minimum number of CUs required to qualify for this degree requirement + The minimum number of CUs required for this rule. Only non-null + if this is a Rule leaf. """ ), ) + degree_plan = models.ForeignKey( DegreePlan, on_delete=models.CASCADE, help_text=dedent( """ - The degree plan that has this requirement. + The degree plan that has this rule. """ ), ) - def __str__(self) -> str: - return f"Requirement: {self.name} ({self.code}), min_cus={self.min_cus}, degree_plan={self.degree_plan}" - - -class Rule(models.Model): - """ - This model represents a degree requirement rule. A rule has a Q object - representing courses that can fulfill this rule and a number of required - courses, number of required CUs, or both. - """ - q = models.TextField( max_length=1000, help_text=dedent( """ - String representing a Q() object that returns the set of courses satisfying this rule. - """ - ), - ) - min_num = models.IntegerField( - null=True, - help_text=dedent( - """ - The minimum number of courses required for this rule. - """ - ), - ) - max_num = models.IntegerField( - null=True, - help_text=dedent( - """ - The maximum number of courses required for this rule. + String representing a Q() object that returns the set of courses + satisfying this rule. Only non-empty if this is a Rule leaf. """ ), ) - min_cus = models.DecimalField( - decimal_places=1, - max_digits=4, - null=True, - help_text=dedent( - """ - The minimum number of CUs required for this rule. - """ - ), - ) - max_cus = models.DecimalField( - decimal_places=1, - max_digits=4, + + parent = models.ForeignKey( + "self", null=True, - help_text=dedent( - """ - The maximum number of CUs required for this rule. - """ - ), - ) - requirement = models.ForeignKey( - Requirement, on_delete=models.CASCADE, help_text=dedent( """ - The degree requirement that has this rule. + This rule's parent Rule if it has one. """ ), ) def __str__(self) -> str: - return f"Rule: {self.q}, min_num={self.min_num}, max_num={self.max_num}, min_cus={self.min_cus}, max_cus={self.max_cus}, requirement={self.requirement}" + return f"{self.q}, num={self.num}, cus={self.cus}, degree_plan={self.degree_plan}" diff --git a/backend/degree/util.py b/backend/degree/util.py deleted file mode 100644 index 298a06ae0..000000000 --- a/backend/degree/util.py +++ /dev/null @@ -1,7 +0,0 @@ -from degree.models import DegreeRequirement - - -def walk_requirements(degree_requirement: DegreeRequirement): - """ - Walk the requirements tree and yield all - """ diff --git a/backend/degree/utils/parse_degreeworks.py b/backend/degree/utils/parse_degreeworks.py new file mode 100644 index 000000000..55c6b08a1 --- /dev/null +++ b/backend/degree/utils/parse_degreeworks.py @@ -0,0 +1,378 @@ +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 + + +def parse_coursearray(courseArray) -> Q: + """ + Parses a Course rule's courseArray and returns a Q filter to find valid courses. + """ + q = Q() + for course in courseArray: + course_q = Q() + match course["discipline"], course["number"], course.get("numberEnd"): + # an @ is a placeholder meaning any + case ("@", "@", end) | ("PSEUDO@", "@", end): + assert end is None + pass + case discipline, "@", end: + assert end is None + course_q &= Q(department__code=discipline) + case discipline, number, None: + if number.isdigit(): + course_q &= Q(full_code=f"{discipline}-{number}") + elif number[:-1].isdigit() and number[-1] == "@": + course_q &= Q(full_code__startswith=f"{discipline}-{number[:-1]}") + else: + print(f"WARNING: non-integer course number: {number}") + case discipline, number, end: + try: + int(number) + int(end) + except ValueError: + print("WARNING: non-integer course number or numberEnd") + course_q &= Q( + department__code=discipline, + code__gte=int(number), + code__lte=end, + ) + + connector = "AND" # the connector to the next element; and by default + if "withArray" in course: + for filter in course["withArray"]: + assert filter["connector"] in ["", "AND", "OR"] + match filter["code"]: + case "ATTRIBUTE": + sub_q = Q(attributes__code__in=filter["valueList"]) + case "DWTERM": + assert len(filter["valueList"]) == 1 + semester, year = filter["valueList"][0].split() + match semester: + case "Spring": + sub_q = Q(semester__code=f"{year}A") + case "Summer": + sub_q = Q(semester__code=f"{year}B") + case "Fall": + sub_q = Q(semester__code=f"{year}C") + case _: + raise LookupError(f"Unknown semester in withArray: {semester}") + case "DWCOLLEGE": + assert len(filter["valueList"]) == 1 + match filter["valueList"][0]: + case "E" | "EU": + sub_q = Q(course__department__code__in=E_DEPTS) + case "A" | "AU": + sub_q = Q(course__department__code__in=A_DEPTS) + case "W" | "WU": + sub_q = Q(course__department__code__in=W_DEPTS) + case _: + raise ValueError( + f"Unsupported college in withArray: {filter['valueList'][0]}" + ) + case "DWRESIDENT": + print("WARNING: ignoring DWRESIDENT") + sub_q = Q() + case "DWGRADE": + print("WARNING: ignoring DWGRADE") + sub_q = Q() + case _: + raise LookupError(f"Unknown filter type in withArray: {filter['code']}") + match filter[ + "connector" + ]: # TODO: this assumes the connector is to the next element (ie we use the previous filter's connector here) + case "AND" | "": + course_q &= sub_q + case "OR": + course_q |= sub_q + case _: + raise LookupError(f"Unknown connector type in withArray: {connector}") + + connector = filter["connector"] + + if len(course_q) == 0: + print("Warning: empty course query") + continue + + match course.get("connector"): + case "AND" | "+": + q &= course_q + case "OR" | "" | None: + q |= course_q + case _: + raise LookupError(f"Unknown connector type in courseArray: {course['connector']}") + + if len(q) == 0: + print("Warning: empty query") + + return q + + +def evaluate_condition(condition, degree_plan) -> bool: + if "connector" in condition: + left = evaluate_condition(condition["leftCondition"], degree_plan) + if "rightCondition" not in condition: + return left + + right = evaluate_condition(condition["rightCondition"], degree_plan) + match condition["connector"]: + case "AND": + return left and right + case "OR": + return right or left + case _: + raise LookupError(f"Unknown connector type in ifStmt: {condition['connector']}") + elif "relationalOperator" in condition: + comparator = condition["relationalOperator"] + match comparator["left"]: + case "MAJOR": + attribute = degree_plan.major + case "CONC" | "CONCENTRATION": + attribute = degree_plan.concentration + case "PROGRAM": + attribute = degree_plan.program + case _: + raise ValueError(f"Unknowable left type in ifStmt: {comparator['left']}") + match comparator["operator"]: + case "=": + return attribute == comparator["right"] + case "<>": + return attribute != comparator["right"] + case ">=" | "<=" | ">" | "<": + raise LookupError(f"Unsupported comparator in ifStmt: {comparator}") + case _: + raise LookupError( + f"Unknown relational operator in ifStmt: {comparator['operator']}" + ) + else: + raise LookupError(f"Unknown condition type in ifStmt: {condition.keys()}") + + +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. + """ + rules = [] + for rule in ruleArray: + rule_req = rule["requirement"] + assert ( + rule["ruleType"] == "Group" or rule["ruleType"] == "Subset" or "ruleArray" not in rule + ) + match rule["ruleType"]: + case "Course": + """ + A Course rule can either specify a number (or range) of classes required or a number (or range) of CUs + required, or both. + """ + # check the number of classes/credits + num = ( + int(rule_req.get("classesBegin")) + if rule_req.get("classesBegin") is not None + else None + ) + max_num = ( + int(rule_req.get("classesEnd")) + if rule_req.get("classesEnd") is not None + else None + ) + cus = ( + float(rule_req.get("creditsBegin")) + if rule_req.get("creditsBegin") is not None + else None + ) + max_cus = ( + float(rule_req.get("creditsEnd")) + if rule_req.get("creditsEnd") is not None + else None + ) + + if num is None and cus is None: + raise ValueError("No classesBegin or creditsBegin in Course requirement") + + if (num is None and max_num) or (cus is None and max_cus): + raise ValueError(f"Course requirement specified end without begin: {rule_req}") + + # TODO: What is the point of this? + if max_num is None and max_cus is None: + if not (num and cus): + assert rule_req["classCreditOperator"] == "OR" + else: + assert rule_req["classCreditOperator"] == "AND" + + rules.append( + Rule( + q=str(parse_coursearray(rule_req["courseArray"])), + min_num=num, + max_num=max_num, + min_cus=cus, + max_cus=max_cus, + ) + ) + case "IfStmt": + assert "rightCondition" not in rule_req + try: + evaluation = evaluate_condition(rule_req["leftCondition"], degree_plan) + except ValueError as e: + assert e.args[0].startswith("Unknowable left type in ifStmt") + print("Warning: " + e.args[0]) + continue # do nothing if we can't evaluate the condition bc of insufficient information + + match rule["booleanEvaluation"]: + case "False": + degreeworks_eval = False + case "True": + degreeworks_eval = True + case "Unknown": + degreeworks_eval = None + case _: + raise LookupError( + f"Unknown boolean evaluation in ifStmt: {rule['booleanEvaluation']}" + ) + + assert degreeworks_eval is None or evaluation == bool(degreeworks_eval) + + # add if part or else part, depending on evaluation of the condition + if evaluation: + rules += parse_rulearray(rule_req["ifPart"]["ruleArray"], degree_plan) + elif "elsePart" in rule_req: + rules += parse_rulearray(rule_req["elsePart"]["ruleArray"], degree_plan) + case "Block" | "Blocktype": # headings + pass + case "Complete" | "Incomplete": + assert "ifElsePart" in rule # this is a nested requirement + continue # do nothing + case "Noncourse": + continue # this is a presentation or something else that's required + case "Subset": # what does this mean + if "ruleArray" in rule: + rules += parse_rulearray(rule["ruleArray"], degree_plan) + 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)] + + # TODO: Indicate somehow that this is a group + rules.append( + Rule( + q=str(q), + min_num=rule_req["numberOfGroups"], + min_cus=None, + max_cus=None, + max_num=None, + ) + ) + case _: + raise LookupError(f"Unknown rule type {rule['ruleType']}") + return rules + + +# TODO: Make the function names more descriptive +def parse_degreeworks(json: str, degree_plan: DegreePlan) -> list[Rule]: + """ + Returns a list of Rules given a DegreeWorks JSON audit and a DegreePlan. + """ + blockArray = json.get("blockArray") + + # TODO: Instead of creating a list assign each Requirement to DegreePlan + degree_reqs = [] + + for requirement in blockArray: + degree_req = Rule( + name=requirement["title"], + code=requirement["requirementValue"], + # TODO: parse min_cus + min_cus=0, + ) + + # TODO: Should associate each Rule here with this Requirement + print(parse_rulearray(requirement["ruleArray"], degree_plan)) + + degree_reqs.append(degree_req) + return degree_reqs diff --git a/backend/degree/utils/request_degreeworks.py b/backend/degree/utils/request_degreeworks.py new file mode 100644 index 000000000..30f468470 --- /dev/null +++ b/backend/degree/utils/request_degreeworks.py @@ -0,0 +1,2366 @@ +import json +from pathlib import Path + +from requests import Session + +from degree.models import DegreePlan + + +DEGREEWORKS_HOST = "degreeworks-prod-j.isc-seo.upenn.edu:9904" + +with open("degreeworks_env.json") as f: + env = json.load(f) + +cookies = { + "REFRESH_TOKEN": env["REFRESH_TOKEN"], + "NAME": env["NAME"], + "X-AUTH-TOKEN": env["X-AUTH-TOKEN"], +} + +headers = { + "Host": DEGREEWORKS_HOST, + "Origin": f"https://{DEGREEWORKS_HOST}", +} + +s = Session() +s.cookies.update(cookies) +s.headers.update(headers) + + +def get_programs(timeout: int = 30, year: int = 2023) -> list[str]: + """ + Returns a list of program codes for each program in the given year + """ + goals_payload = [ + { + "id": "programCollection", + "description": "Program", + "isExpandable": False, + "goals": [ + { + "name": "catalogYear", + "description": "Catalog year", + "entityName": "catalogYears", + "isDisabled": False, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": True, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "api/catalogYears", + "errorMessage": "", + "choices": [], + "selectedChoices": [str(year)], + "ruleGoalCode": None, + "links": [], + }, + { + "name": "program", + "description": "Program", + "entityName": "programs", + "isDisabled": False, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [ + { + "key": "NP_PHD_JOINT", + "description": "*Nursing Joint PhD", + "isVisibleInWhatif": True, + }, + { + "key": "AU_BA_BIOD_U", + "description": "*Seven Yr Bio-Dent Pgrm Pre-Maj", + "isVisibleInWhatif": True, + }, + { + "key": "CP_MA", + "description": "Annenberg - MA (PhD)", + "isVisibleInWhatif": True, + }, + { + "key": "CP_PHD", + "description": "Annenberg - PhD", + "isVisibleInWhatif": True, + }, + { + "key": "AU_BA", + "description": "Arts & Sciences - BA", + "isVisibleInWhatif": True, + }, + { + "key": "AU_BA_UNDC", + "description": "Arts & Sciences - BA - Pre-Major", + "isVisibleInWhatif": True, + }, + { + "key": "AP_PHD_JOINT", + "description": "Arts & Sciences - Joint PhD Degree", + "isVisibleInWhatif": True, + }, + { + "key": "AP_MA", + "description": "Arts & Sciences - MA", + "isVisibleInWhatif": True, + }, + { + "key": "AP_MPHIL", + "description": "Arts & Sciences - MPhil", + "isVisibleInWhatif": True, + }, + { + "key": "AP_MS", + "description": "Arts & Sciences - MS", + "isVisibleInWhatif": True, + }, + { + "key": "AP_PHD", + "description": "Arts & Sciences - PhD", + "isVisibleInWhatif": True, + }, + { + "key": "DY_CERTF_DM", + "description": "Dental - Certificate (Post-Graduate FinAid)", + "isVisibleInWhatif": True, + }, + { + "key": "DY_CERT_DM", + "description": "Dental - Certificate (Post-Graduate)", + "isVisibleInWhatif": True, + }, + { + "key": "DM_DMD", + "description": "Dental - Doctor of Dental Medicine", + "isVisibleInWhatif": True, + }, + { + "key": "DM_DMD_BIOD", + "description": "Dental - Doctor of Dental Medicine (7-yr)", + "isVisibleInWhatif": True, + }, + { + "key": "DM_DMD_PASS", + "description": "Dental - Doctor of Dental Medicine (Advanced)", + "isVisibleInWhatif": True, + }, + { + "key": "DY_DSCD", + "description": "Dental - Doctor of Science in Dentistry", + "isVisibleInWhatif": True, + }, + { + "key": "DY_MADS", + "description": "Dental - Master of Advance Dental Studies", + "isVisibleInWhatif": True, + }, + { + "key": "DY_MOHS", + "description": "Dental - Master of Oral Health Science", + "isVisibleInWhatif": True, + }, + { + "key": "DY_MSOB", + "description": "Dental - Master of Science in Oral Biology", + "isVisibleInWhatif": True, + }, + { + "key": "FP_CERT_GR", + "description": "Design - Certificate (Graduate)", + "isVisibleInWhatif": True, + }, + { + "key": "FM_CERT_PR", + "description": "Design - Certificate (Professional)", + "isVisibleInWhatif": True, + }, + { + "key": "FP_MA", + "description": "Design - MA", + "isVisibleInWhatif": True, + }, + { + "key": "FP_MS", + "description": "Design - MS", + "isVisibleInWhatif": True, + }, + { + "key": "FM_MARCH", + "description": "Design - Master of Architecture", + "isVisibleInWhatif": True, + }, + { + "key": "FM_MCP", + "description": "Design - Master of City Planning", + "isVisibleInWhatif": True, + }, + { + "key": "FM_MEBD", + "description": "Design - Master of Environmental Building Design", + "isVisibleInWhatif": True, + }, + { + "key": "FM_MFA", + "description": "Design - Master of Fine Arts", + "isVisibleInWhatif": True, + }, + { + "key": "FM_MLA", + "description": "Design - Master of Landscape Architecture", + "isVisibleInWhatif": True, + }, + { + "key": "FM_MSDES", + "description": "Design - Master of Science in Design", + "isVisibleInWhatif": True, + }, + { + "key": "FM_MSHP", + "description": "Design - Master of Science in Historic Preserv", + "isVisibleInWhatif": True, + }, + { + "key": "FM_MUSA", + "description": "Design - Master of Urban Spatial Analytics", + "isVisibleInWhatif": True, + }, + { + "key": "FP_PHD", + "description": "Design - PhD", + "isVisibleInWhatif": True, + }, + { + "key": "AU_BA_CMPC_U", + "description": "Dual Degree - Computer & Cog Sci - BA - Pre-Major", + "isVisibleInWhatif": True, + }, + { + "key": "AU_BA_HUNTS", + "description": "Dual Degree - Huntsman - BA - Arts & Sciences", + "isVisibleInWhatif": True, + }, + { + "key": "WU_BS_HUNTS", + "description": "Dual Degree - Huntsman - BS - Wharton", + "isVisibleInWhatif": True, + }, + { + "key": "AP_MA_LAUD", + "description": "Dual Degree - Lauder - MA - Arts & Sciences", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA_LAUD", + "description": "Dual Degree - Lauder - MBA - Wharton", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BAS_MANT", + "description": "Dual Degree - M & T - BAS - SEAS", + "isVisibleInWhatif": True, + }, + { + "key": "WU_BS_MANT", + "description": "Dual Degree - M & T - BS - Wharton", + "isVisibleInWhatif": True, + }, + { + "key": "WU_BS_MAT_U", + "description": "Dual Degree - M & T - BS - Wharton (Pre-Conc)", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BSE_MANT", + "description": "Dual Degree - M & T - BSE - SEAS", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BSE_MAT_U", + "description": "Dual Degree - M & T - BSE - SEAS", + "isVisibleInWhatif": True, + }, + { + "key": "WU_BS_NHCM", + "description": "Dual Degree - Nursing & HC Mgmt - BS - Wharton", + "isVisibleInWhatif": True, + }, + { + "key": "NU_BSN_NHCM", + "description": "Dual Degree - Nursing & HC Mgmt - BSN - Nursing", + "isVisibleInWhatif": True, + }, + { + "key": "AU_BA_VIPR_U", + "description": "Dual Degree - VIPER - BA - A & S - Pre-Major", + "isVisibleInWhatif": True, + }, + { + "key": "AU_BA_VIPER", + "description": "Dual Degree - VIPER - BA - Arts & Sciences", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BAS_VIPER", + "description": "Dual Degree - VIPER - BAS - SEAS", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BSE_VIPER", + "description": "Dual Degree - VIPER - BSE - SEAS", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BSE_VIP_U", + "description": "Dual Degree - VIPER - BSE - SEAS - Curric Defer", + "isVisibleInWhatif": True, + }, + { + "key": "AU_BA_VAGL_U", + "description": "Dual Degree - Vagelos LSM - BA - A & S - Pre-Major", + "isVisibleInWhatif": True, + }, + { + "key": "AU_BA_VAGL", + "description": "Dual Degree - Vagelos LSM - BA - Arts & Sciences", + "isVisibleInWhatif": True, + }, + { + "key": "WU_BS_VAGL", + "description": "Dual Degree - Vagelos LSM - BS - Wharton", + "isVisibleInWhatif": True, + }, + { + "key": "WU_BS_VAGL_U", + "description": "Dual Degree - Vagelos LSM - BS - Wharton-Pre-Conc", + "isVisibleInWhatif": True, + }, + { + "key": "GM_CERT_CE", + "description": "GSE - Certificate (Continuing Ed.)", + "isVisibleInWhatif": True, + }, + { + "key": "GM_CERT_ONL", + "description": "GSE - Certificate (Online Continuing Ed.)", + "isVisibleInWhatif": True, + }, + { + "key": "GM_CERT_PR", + "description": "GSE - Certificate (Professional)", + "isVisibleInWhatif": True, + }, + { + "key": "GP_CERT_GR", + "description": "GSE - Certificate (Research)", + "isVisibleInWhatif": True, + }, + { + "key": "GM_CERTF_PR", + "description": "GSE - Certificate - UTRP/Sch Leadership (Graduate)", + "isVisibleInWhatif": True, + }, + { + "key": "GM_EDD", + "description": "GSE - Doctor of Education (EdD)", + "isVisibleInWhatif": True, + }, + { + "key": "GM_MSED", + "description": "GSE - MS in Education (MSEd)", + "isVisibleInWhatif": True, + }, + { + "key": "GM_MSED_ONL", + "description": "GSE - MS in Education - Online (MSED)", + "isVisibleInWhatif": True, + }, + { + "key": "GM_MPHILE", + "description": "GSE - Master of Philosophy in Education", + "isVisibleInWhatif": True, + }, + { + "key": "GP_PHD", + "description": "GSE - PhD", + "isVisibleInWhatif": True, + }, + { + "key": "GP_PHD_JOINT", + "description": "GSE Joint Phd", + "isVisibleInWhatif": True, + }, + { + "key": "GP_MSED", + "description": "GSE Master of Sci in Education", + "isVisibleInWhatif": True, + }, + { + "key": "GP_MS", + "description": "GSE Master of Science", + "isVisibleInWhatif": True, + }, + { + "key": "LR_JD_JDMBA", + "description": "LAW JD/MBA", + "isVisibleInWhatif": True, + }, + { + "key": "AB_BAAS_ONL", + "description": "LPS - Bachelor of Applied Arts & Sciences", + "isVisibleInWhatif": True, + }, + { + "key": "AL_BFA", + "description": "LPS - Bachelor of Fine Arts", + "isVisibleInWhatif": True, + }, + { + "key": "AM_CERTF_PR", + "description": "LPS - Certificate (Professional FinAid)", + "isVisibleInWhatif": True, + }, + { + "key": "AM_CERT_PR", + "description": "LPS - Certificate (Professional)", + "isVisibleInWhatif": True, + }, + { + "key": "AB_CERTA_OL", + "description": "LPS - Certificate (Undergraduate Advanced Online)", + "isVisibleInWhatif": True, + }, + { + "key": "AL_CERTF_UG", + "description": "LPS - Certificate (Undergraduate FinAid)", + "isVisibleInWhatif": True, + }, + { + "key": "AB_CRT_UG_OL", + "description": "LPS - Certificate (Undergraduate Online)", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MPHIL", + "description": "LPS - MPhil", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MSAG", + "description": "LPS - MS in Applied Geosciences", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MSOD", + "description": "LPS - MS in Organizational Dynamics", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MAPP", + "description": "LPS - Master of Applied Positive Psychology", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MBDS", + "description": "LPS - Master of Behavioral & Decision Sciences", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MCS", + "description": "LPS - Master of Chemical Sciences", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MES", + "description": "LPS - Master of Environmental Studies", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MLIBA", + "description": "LPS - Master of Liberal Arts", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MPA", + "description": "LPS - Master of Public Administration", + "isVisibleInWhatif": True, + }, + { + "key": "AM_MSAG_ONL", + "description": "LPS MS in Appl Geosci Online", + "isVisibleInWhatif": True, + }, + { + "key": "LY_CERT", + "description": "Law - Certificate (Professional)", + "isVisibleInWhatif": True, + }, + { + "key": "LD_SJD", + "description": "Law - Doctorate of the Science of Law (SJD)", + "isVisibleInWhatif": True, + }, + { + "key": "LR_JD", + "description": "Law - Juris Doctor (JD)", + "isVisibleInWhatif": True, + }, + { + "key": "LY_MLAW", + "description": "Law - Master in Law (ML)", + "isVisibleInWhatif": True, + }, + { + "key": "LY_LLM", + "description": "Law - Master of Laws (LLM)", + "isVisibleInWhatif": True, + }, + { + "key": "LY_LLCM", + "description": "Law - Masters in Comparative Law (LLCM)", + "isVisibleInWhatif": True, + }, + { + "key": "MP_CERT_GR", + "description": "Medicine - Certificate (Graduate)", + "isVisibleInWhatif": True, + }, + { + "key": "MM_CERT_ONL", + "description": "Medicine - Certificate (Online Professional)", + "isVisibleInWhatif": True, + }, + { + "key": "MM_CERTF_PR", + "description": "Medicine - Certificate (Professional FinAid)", + "isVisibleInWhatif": True, + }, + { + "key": "MM_CERT_PR", + "description": "Medicine - Certificate (Professional)", + "isVisibleInWhatif": True, + }, + { + "key": "MR_MD", + "description": "Medicine - MD", + "isVisibleInWhatif": True, + }, + { + "key": "MP_MS", + "description": "Medicine - MS", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MBE", + "description": "Medicine - Master of Bioethics", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MBMI", + "description": "Medicine - Master of Biomedical Informatics", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MHCI_ONL", + "description": "Medicine - Master of Health Care Innovation", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MHQS", + "description": "Medicine - Master of Healthcare Quality and Safety", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MPH", + "description": "Medicine - Master of Public Health", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MRA", + "description": "Medicine - Master of Regulatory Affairs", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MRA_ONL", + "description": "Medicine - Master of Regulatory Affairs (ONL)", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MSCE", + "description": "Medicine - Master of Science in Clin Epidemiology", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MSGC", + "description": "Medicine - Master of Science in Genetic Counseling", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MPR", + "description": "Medicine - Master of Science in Health Pol Resrch", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MSME", + "description": "Medicine - Master of Science in Medical Ethics", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MSMP", + "description": "Medicine - Master of Science in Medical Physics", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MSRS", + "description": "Medicine - Master of Science in Regulatory Science", + "isVisibleInWhatif": True, + }, + { + "key": "MM_MSTR", + "description": "Medicine - Master of Science in Translatnal Resrch", + "isVisibleInWhatif": True, + }, + { + "key": "MP_PHD", + "description": "Medicine - PhD", + "isVisibleInWhatif": True, + }, + { + "key": "NU_BSN", + "description": "Nursing - BSN", + "isVisibleInWhatif": True, + }, + { + "key": "NU_BSN_NAP", + "description": "Nursing - BSN (Accelerated)", + "isVisibleInWhatif": True, + }, + { + "key": "NM_PMN", + "description": "Nursing - Certificate (Post-Masters)", + "isVisibleInWhatif": True, + }, + { + "key": "NM_CERT_PR", + "description": "Nursing - Certificate (Professional)", + "isVisibleInWhatif": True, + }, + { + "key": "ND_DNP", + "description": "Nursing - Doctor of Nursing Practice", + "isVisibleInWhatif": True, + }, + { + "key": "ND_DNP_ONL", + "description": "Nursing - Doctor of Nursing Practice (Online)", + "isVisibleInWhatif": True, + }, + { + "key": "NP_MS", + "description": "Nursing - MS (PhD)", + "isVisibleInWhatif": True, + }, + { + "key": "NM_MSN", + "description": "Nursing - MSN", + "isVisibleInWhatif": True, + }, + { + "key": "NP_PHD", + "description": "Nursing - PhD", + "isVisibleInWhatif": True, + }, + { + "key": "MP_PHD_MDPHD", + "description": "PSOM Doctor of Philosophy/MD", + "isVisibleInWhatif": True, + }, + { + "key": "MP_PHD_VRPHD", + "description": "PSOM Doctor of Philosophy/VMD", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BSE", + "description": "SEAS - BSE", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BSE_CD", + "description": "SEAS - BSE - Curric Defer", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BAS", + "description": "SEAS - Bachelor of Applied Science", + "isVisibleInWhatif": True, + }, + { + "key": "EU_BAS_CD", + "description": "SEAS - Bachelor of Applied Science - Curric Defer", + "isVisibleInWhatif": True, + }, + { + "key": "EX_MSE", + "description": "SEAS - Executive MS", + "isVisibleInWhatif": True, + }, + { + "key": "EM_MCIT", + "description": "SEAS - MCIT (On Campus)", + "isVisibleInWhatif": True, + }, + { + "key": "EM_MCIT_ONL", + "description": "SEAS - MCIT (Online)", + "isVisibleInWhatif": True, + }, + { + "key": "EM_MSE", + "description": "SEAS - MSE", + "isVisibleInWhatif": True, + }, + { + "key": "EM_MBIOT", + "description": "SEAS - Master of Biotechnology", + "isVisibleInWhatif": True, + }, + { + "key": "EM_MIPD", + "description": "SEAS - Master of Intg Prod Design", + "isVisibleInWhatif": True, + }, + { + "key": "EP_PHD", + "description": "SEAS - PhD", + "isVisibleInWhatif": True, + }, + { + "key": "SP_PHD_JOINT", + "description": "SP2 Joint Phd", + "isVisibleInWhatif": True, + }, + { + "key": "SM_MSNPL", + "description": "Social Policy & Prac - MS in Non-Profit Leadership", + "isVisibleInWhatif": True, + }, + { + "key": "SM_MSNPL_ONL", + "description": "Social Policy & Prac - MS in Non-Profit Leadership", + "isVisibleInWhatif": True, + }, + { + "key": "SD_DSW_ONL", + "description": "Social Policy & Practice - Doctor of Social Work", + "isVisibleInWhatif": True, + }, + { + "key": "SM_MSSP", + "description": "Social Policy & Practice - MS in Social Policy", + "isVisibleInWhatif": True, + }, + { + "key": "SM_MSW", + "description": "Social Policy & Practice - Master of Social Work", + "isVisibleInWhatif": True, + }, + { + "key": "SP_PHD", + "description": "Social Policy & Practice - PhD in Social Welfare", + "isVisibleInWhatif": True, + }, + { + "key": "VM_MSAWB_ONL", + "description": "VET Mstr of Sci Ani Wlfr Bhvr", + "isVisibleInWhatif": True, + }, + { + "key": "VM_CERT_ONL", + "description": "Vet - Certificate (Professional)", + "isVisibleInWhatif": True, + }, + { + "key": "VR_VMD", + "description": "Vet - PhD/Doctor of Veterinary Medicine", + "isVisibleInWhatif": True, + }, + { + "key": "VP_VMD", + "description": "Vet - Veterinariae Medicinae Doctoris-VMD", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA_JDMBA", + "description": "WH JD/MBA", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA_EBMBA", + "description": "WH MBA/MBIOT", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA_ECMBA", + "description": "WH MBA/MCIT", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA_FMMBA", + "description": "WH MBA/MFA", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA_EIMBA", + "description": "WH MBA/MIPD", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA_EMMBA", + "description": "WH MBA/MSEng", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA_MDMBA", + "description": "WH MD/MBA", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA_VRMBA", + "description": "WH VMD/MBA", + "isVisibleInWhatif": True, + }, + { + "key": "WU_BS", + "description": "Wharton - BS", + "isVisibleInWhatif": True, + }, + { + "key": "WX_MBA_PHL", + "description": "Wharton - Executive MBA (Philadelphia)", + "isVisibleInWhatif": True, + }, + { + "key": "WX_MBA_SFO", + "description": "Wharton - Executive MBA (San Francisco)", + "isVisibleInWhatif": True, + }, + { + "key": "WP_PHD_JOINT", + "description": "Wharton - Joint PhD Degree", + "isVisibleInWhatif": True, + }, + { + "key": "WP_MA", + "description": "Wharton - MA", + "isVisibleInWhatif": True, + }, + { + "key": "WM_MBA", + "description": "Wharton - MBA", + "isVisibleInWhatif": True, + }, + { + "key": "WP_MS", + "description": "Wharton - MS", + "isVisibleInWhatif": True, + }, + { + "key": "WP_PHD", + "description": "Wharton - PhD", + "isVisibleInWhatif": True, + }, + { + "key": "WU_BS_WUNG", + "description": "Wharton - Pre Concentration (Undergraduate)", + "isVisibleInWhatif": True, + }, + ], + "selectedChoices": ["EU_BSE"], + "ruleGoalCode": "PROGRAM", + "links": [], + }, + { + "name": "school", + "description": "Level", + "entityName": "schools", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [ + { + "key": "UG", + "description": "Undergraduate", + "isVisibleInWhatif": True, + } + ], + "selectedChoices": ["UG"], + "ruleGoalCode": "SCHOOL", + "links": [], + }, + { + "name": "college", + "description": "College", + "entityName": "colleges", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [ + { + "key": "EU", + "description": "SEAS Undergraduate", + "isVisibleInWhatif": True, + } + ], + "selectedChoices": ["EU"], + "ruleGoalCode": "COLLEGE", + "links": [], + }, + { + "name": "degree", + "description": "Degree", + "entityName": "degrees", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [ + { + "key": "BSE", + "description": "Bachelor of Sci in Engineering", + "isVisibleInWhatif": True, + } + ], + "selectedChoices": ["BSE"], + "ruleGoalCode": "DEGREE", + "links": [], + }, + ], + }, + { + "id": "curriculumCollection", + "description": "Areas of study", + "isExpandable": False, + "goals": [ + { + "name": "major", + "description": "Major", + "entityName": "majors", + "isDisabled": False, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [ + { + "key": "AFRC", + "description": "Africana Studies", + "isVisibleInWhatif": True, + }, + { + "key": "ANCH", + "description": "Ancient History", + "isVisibleInWhatif": True, + }, + { + "key": "ANTH", + "description": "Anthropology", + "isVisibleInWhatif": True, + }, + { + "key": "ARCH", + "description": "Architecture", + "isVisibleInWhatif": True, + }, + { + "key": "BCHE", + "description": "Biochemistry", + "isVisibleInWhatif": True, + }, + { + "key": "BE", + "description": "Bioengineering", + "isVisibleInWhatif": True, + }, + { + "key": "BIOL", + "description": "Biology", + "isVisibleInWhatif": True, + }, + { + "key": "BIOP", + "description": "Biophysics", + "isVisibleInWhatif": True, + }, + { + "key": "CBSC", + "description": "Chem & Biomolecular Science", + "isVisibleInWhatif": True, + }, + { + "key": "CBE", + "description": "Chemical & Biomolecular Eng", + "isVisibleInWhatif": True, + }, + { + "key": "CHEM", + "description": "Chemistry", + "isVisibleInWhatif": True, + }, + { + "key": "CIMS", + "description": "Cinema and Media Studies", + "isVisibleInWhatif": True, + }, + { + "key": "CLST", + "description": "Classical Studies", + "isVisibleInWhatif": True, + }, + { + "key": "COGS", + "description": "Cognitive Science", + "isVisibleInWhatif": True, + }, + { + "key": "COMM", + "description": "Communication", + "isVisibleInWhatif": True, + }, + { + "key": "CMPL", + "description": "Comparative Literature", + "isVisibleInWhatif": True, + }, + { + "key": "CMPE", + "description": "Computer Engineering", + "isVisibleInWhatif": True, + }, + { + "key": "CSCI", + "description": "Computer Science", + "isVisibleInWhatif": True, + }, + { + "key": "CRIM", + "description": "Criminology", + "isVisibleInWhatif": True, + }, + { + "key": "DSGN", + "description": "Design", + "isVisibleInWhatif": True, + }, + { + "key": "DMD", + "description": "Digital Media Design", + "isVisibleInWhatif": True, + }, + { + "key": "EASC", + "description": "Earth Sciences", + "isVisibleInWhatif": True, + }, + { + "key": "EALC", + "description": "East Asian Lang & Civilization", + "isVisibleInWhatif": True, + }, + { + "key": "ECOQ", + "description": "Economics", + "isVisibleInWhatif": True, + }, + { + "key": "EE", + "description": "Electrical Engineering", + "isVisibleInWhatif": True, + }, + { + "key": "ENGL", + "description": "English", + "isVisibleInWhatif": True, + }, + { + "key": "ENVS", + "description": "Environmental Studies", + "isVisibleInWhatif": True, + }, + { + "key": "FNAR", + "description": "Fine Arts", + "isVisibleInWhatif": True, + }, + { + "key": "FRFS", + "description": "French and Francophone Studies", + "isVisibleInWhatif": True, + }, + { + "key": "GSWS", + "description": "Gen, Sexuality & Womens Sts", + "isVisibleInWhatif": True, + }, + { + "key": "GRMN", + "description": "German", + "isVisibleInWhatif": True, + }, + { + "key": "HSOC", + "description": "Health and Societies", + "isVisibleInWhatif": True, + }, + { + "key": "HSPN", + "description": "Hispanic Studies", + "isVisibleInWhatif": True, + }, + { + "key": "HIST", + "description": "History", + "isVisibleInWhatif": True, + }, + { + "key": "ARTH", + "description": "History of Art", + "isVisibleInWhatif": True, + }, + { + "key": "INDM", + "description": "Individualized", + "isVisibleInWhatif": True, + }, + { + "key": "INTR", + "description": "International Relations", + "isVisibleInWhatif": True, + }, + { + "key": "ITST", + "description": "Italian Studies", + "isVisibleInWhatif": True, + }, + { + "key": "JWST", + "description": "Jewish Studies", + "isVisibleInWhatif": True, + }, + { + "key": "LALX", + "description": "Latin American and Latinx Stds - LALX", + "isVisibleInWhatif": True, + }, + { + "key": "LING", + "description": "Linguistics", + "isVisibleInWhatif": True, + }, + { + "key": "LOGC", + "description": "Logic Info & Computation", + "isVisibleInWhatif": True, + }, + { + "key": "MSE", + "description": "Materials Science & Engineering", + "isVisibleInWhatif": True, + }, + { + "key": "MAEC", + "description": "Mathematical Economics", + "isVisibleInWhatif": True, + }, + { + "key": "MATH", + "description": "Mathematics", + "isVisibleInWhatif": True, + }, + { + "key": "MEAM", + "description": "Mech Engr & Appl Mechanics", + "isVisibleInWhatif": True, + }, + { + "key": "MMES", + "description": "Modern Middle Eastern Studies", + "isVisibleInWhatif": True, + }, + { + "key": "MUSC", + "description": "Music", + "isVisibleInWhatif": True, + }, + { + "key": "NELC", + "description": "Near Eastern Lang & Civilizatn", + "isVisibleInWhatif": True, + }, + { + "key": "NETS", + "description": "Networked And Social Systems", + "isVisibleInWhatif": True, + }, + { + "key": "NRSC", + "description": "Neuroscience", + "isVisibleInWhatif": True, + }, + { + "key": "NTSC", + "description": "Nutrition Science", + "isVisibleInWhatif": True, + }, + { + "key": "PHIL", + "description": "Philosophy", + "isVisibleInWhatif": True, + }, + { + "key": "PPE", + "description": "Philosophy Politics & Econ", + "isVisibleInWhatif": True, + }, + { + "key": "PHYS", + "description": "Physics", + "isVisibleInWhatif": True, + }, + { + "key": "PSCI", + "description": "Political Science", + "isVisibleInWhatif": True, + }, + { + "key": "PSYC", + "description": "Psychology", + "isVisibleInWhatif": True, + }, + { + "key": "RELS", + "description": "Religious Studies", + "isVisibleInWhatif": True, + }, + { + "key": "ROML", + "description": "Romance Languages", + "isVisibleInWhatif": True, + }, + { + "key": "REES", + "description": "Russian& East European Studies", + "isVisibleInWhatif": True, + }, + { + "key": "STSC", + "description": "Science Technology & Society", + "isVisibleInWhatif": True, + }, + { + "key": "SOCI", + "description": "Sociology", + "isVisibleInWhatif": True, + }, + { + "key": "SAST", + "description": "South Asia Studies", + "isVisibleInWhatif": True, + }, + { + "key": "SSE", + "description": "Systems Science & Engineering", + "isVisibleInWhatif": True, + }, + { + "key": "THAR", + "description": "Theatre Arts", + "isVisibleInWhatif": True, + }, + { + "key": "URBS", + "description": "Urban Studies", + "isVisibleInWhatif": True, + }, + { + "key": "VLST", + "description": "Visual Studies", + "isVisibleInWhatif": True, + }, + ], + "selectedChoices": ["BE"], + "ruleGoalCode": "MAJOR", + "links": [], + }, + { + "name": "concentration", + "description": "Concentration", + "entityName": "concentrations", + "isDisabled": False, + "isDriver": False, + "isError": False, + "isMultiple": False, + "isRequired": False, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [ + { + "key": "BDS", + "description": "Biomed Data Sci&Cmptationl Med", + "isVisibleInWhatif": True, + }, + { + "key": "BIR", + "description": "Biomed Imgng&Radiation Physics", + "isVisibleInWhatif": True, + }, + { + "key": "BDV", + "description": "Biomedical Devices", + "isVisibleInWhatif": True, + }, + { + "key": "CEB", + "description": "Cellular/Tissue Engin & Biomat", + "isVisibleInWhatif": True, + }, + { + "key": "MSB", + "description": "Multiscale Biomechanics", + "isVisibleInWhatif": True, + }, + { + "key": "NRE", + "description": "Neuroengineering", + "isVisibleInWhatif": True, + }, + { + "key": "NONE", + "description": "Non Designated", + "isVisibleInWhatif": True, + }, + { + "key": "SSB", + "description": "Systems and Synthetic Biology", + "isVisibleInWhatif": True, + }, + { + "key": "TDN", + "description": "Therapeutics,Drug Dliv&Nanomed", + "isVisibleInWhatif": True, + }, + ], + "selectedChoices": [], + "ruleGoalCode": "CONC", + "links": [], + }, + { + "name": "minor", + "description": "Minor", + "entityName": "minors", + "isDisabled": False, + "isDriver": False, + "isError": False, + "isMultiple": False, + "isRequired": False, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [ + { + "key": "ACRL", + "description": "Actuarial Mathematics", + "isVisibleInWhatif": True, + }, + { + "key": "AFRC", + "description": "Africana Studies", + "isVisibleInWhatif": True, + }, + { + "key": "AMPP", + "description": "American Public Policy", + "isVisibleInWhatif": True, + }, + { + "key": "ASL", + "description": "American Sign Lang/Deaf Stds", + "isVisibleInWhatif": True, + }, + { + "key": "ANCH", + "description": "Ancient History", + "isVisibleInWhatif": True, + }, + { + "key": "ANEN", + "description": "Ancient Near East", + "isVisibleInWhatif": True, + }, + { + "key": "ANTH", + "description": "Anthropology", + "isVisibleInWhatif": True, + }, + { + "key": "AHSN", + "description": "Arabic & Hebrew Studies", + "isVisibleInWhatif": True, + }, + { + "key": "AISN", + "description": "Arabic & Islamic Studies", + "isVisibleInWhatif": True, + }, + { + "key": "CAAM", + "description": "Archaeological Science", + "isVisibleInWhatif": True, + }, + { + "key": "ARCH", + "description": "Architecture", + "isVisibleInWhatif": True, + }, + { + "key": "ASAM", + "description": "Asian American Studies", + "isVisibleInWhatif": True, + }, + { + "key": "BIOE", + "description": "Bioethics", + "isVisibleInWhatif": True, + }, + { + "key": "BIOL", + "description": "Biology", + "isVisibleInWhatif": True, + }, + { + "key": "BIOP", + "description": "Biophysics", + "isVisibleInWhatif": True, + }, + { + "key": "CBE", + "description": "Chemical & Biomolecular Eng", + "isVisibleInWhatif": True, + }, + { + "key": "CHEM", + "description": "Chemistry", + "isVisibleInWhatif": True, + }, + { + "key": "CIMS", + "description": "Cinema and Media Studies", + "isVisibleInWhatif": True, + }, + { + "key": "CLST", + "description": "Classical Studies", + "isVisibleInWhatif": True, + }, + { + "key": "COGS", + "description": "Cognitive Science", + "isVisibleInWhatif": True, + }, + { + "key": "CMPL", + "description": "Comparative Literature", + "isVisibleInWhatif": True, + }, + { + "key": "CNSC", + "description": "Computational Neuroscience", + "isVisibleInWhatif": True, + }, + { + "key": "CSCI", + "description": "Computer Science", + "isVisibleInWhatif": True, + }, + { + "key": "CNPS", + "description": "Consumer Psychology", + "isVisibleInWhatif": True, + }, + { + "key": "DATS", + "description": "Data Science", + "isVisibleInWhatif": True, + }, + { + "key": "DSGN", + "description": "Design", + "isVisibleInWhatif": True, + }, + { + "key": "DHUM", + "description": "Digital Humanities", + "isVisibleInWhatif": True, + }, + { + "key": "DMD", + "description": "Digital Media Design", + "isVisibleInWhatif": True, + }, + { + "key": "EAST", + "description": "East Asian Area Studies", + "isVisibleInWhatif": True, + }, + { + "key": "EALJ", + "description": "East Asian Lang Civil/Jpn", + "isVisibleInWhatif": True, + }, + { + "key": "EALN", + "description": "East Asian Lang & Civil/Chns", + "isVisibleInWhatif": True, + }, + { + "key": "EALK", + "description": "East Asian Lang & Civil/Korean", + "isVisibleInWhatif": True, + }, + { + "key": "ECES", + "description": "East Central European Studies", + "isVisibleInWhatif": True, + }, + { + "key": "EPOL", + "description": "Economic Policy", + "isVisibleInWhatif": True, + }, + { + "key": "ECON", + "description": "Economics", + "isVisibleInWhatif": True, + }, + { + "key": "EE", + "description": "Electrical Engineering", + "isVisibleInWhatif": True, + }, + { + "key": "ENSU", + "description": "Energy & Sustainability", + "isVisibleInWhatif": True, + }, + { + "key": "EENT", + "description": "Engineering Entrepreneurship", + "isVisibleInWhatif": True, + }, + { + "key": "ENGL", + "description": "English", + "isVisibleInWhatif": True, + }, + { + "key": "ENVH", + "description": "Environmental Humanities", + "isVisibleInWhatif": True, + }, + { + "key": "EVSC", + "description": "Environmental Science", + "isVisibleInWhatif": True, + }, + { + "key": "ENVS", + "description": "Environmental Studies", + "isVisibleInWhatif": True, + }, + { + "key": "EURO", + "description": "European Studies", + "isVisibleInWhatif": True, + }, + { + "key": "FNAR", + "description": "Fine Arts", + "isVisibleInWhatif": True, + }, + { + "key": "FRFS", + "description": "French and Francophone Studies", + "isVisibleInWhatif": True, + }, + { + "key": "GSWS", + "description": "Gen, Sexuality & Women's Sts", + "isVisibleInWhatif": True, + }, + { + "key": "GEOL", + "description": "Geology", + "isVisibleInWhatif": True, + }, + { + "key": "GRMN", + "description": "German", + "isVisibleInWhatif": True, + }, + { + "key": "GMST", + "description": "Global Medieval Studies", + "isVisibleInWhatif": True, + }, + { + "key": "HEBN", + "description": "Hebrew & Judaica", + "isVisibleInWhatif": True, + }, + { + "key": "HSPN", + "description": "Hispanic Studies", + "isVisibleInWhatif": True, + }, + { + "key": "HIST", + "description": "History", + "isVisibleInWhatif": True, + }, + { + "key": "ARTH", + "description": "History of Art", + "isVisibleInWhatif": True, + }, + { + "key": "PSCD", + "description": "International Development", + "isVisibleInWhatif": True, + }, + { + "key": "INTR", + "description": "International Relations", + "isVisibleInWhatif": True, + }, + { + "key": "ITCL", + "description": "Italian Culture", + "isVisibleInWhatif": True, + }, + { + "key": "ITLT", + "description": "Italian Literature", + "isVisibleInWhatif": True, + }, + { + "key": "JAZZ", + "description": "Jazz & Popular Music Studies", + "isVisibleInWhatif": True, + }, + { + "key": "JWST", + "description": "Jewish Studies", + "isVisibleInWhatif": True, + }, + { + "key": "JRNL", + "description": "Journalistic Writing", + "isVisibleInWhatif": True, + }, + { + "key": "LANS", + "description": "Landscape Studies", + "isVisibleInWhatif": True, + }, + { + "key": "LALX", + "description": "Latin American and Latinx Stds", + "isVisibleInWhatif": True, + }, + { + "key": "LAWS", + "description": "Law and Society", + "isVisibleInWhatif": True, + }, + { + "key": "LSHS", + "description": "Legal Studies & History", + "isVisibleInWhatif": True, + }, + { + "key": "LING", + "description": "Linguistics", + "isVisibleInWhatif": True, + }, + { + "key": "LOGC", + "description": "Logic Info & Computation", + "isVisibleInWhatif": True, + }, + { + "key": "MSE", + "description": "Materials Science & Engin", + "isVisibleInWhatif": True, + }, + { + "key": "MATH", + "description": "Mathematics", + "isVisibleInWhatif": True, + }, + { + "key": "MEAM", + "description": "Mech Engr & Appl Mechanics", + "isVisibleInWhatif": True, + }, + { + "key": "MSOC", + "description": "Medical Sociology", + "isVisibleInWhatif": True, + }, + { + "key": "ATCH", + "description": "Minor In Architectural History", + "isVisibleInWhatif": True, + }, + { + "key": "MMES", + "description": "Modern Middle Eastern Studies", + "isVisibleInWhatif": True, + }, + { + "key": "MUSC", + "description": "Music", + "isVisibleInWhatif": True, + }, + { + "key": "NANO", + "description": "Nanotechnology", + "isVisibleInWhatif": True, + }, + { + "key": "NAIS", + "description": "Native American And Indigenous", + "isVisibleInWhatif": True, + }, + { + "key": "NELC", + "description": "Near Eastern Lang & Civilizatn", + "isVisibleInWhatif": True, + }, + { + "key": "NHMG", + "description": "Neurosci & Health Care Mgmt", + "isVisibleInWhatif": True, + }, + { + "key": "NRSC", + "description": "Neuroscience", + "isVisibleInWhatif": True, + }, + { + "key": "NHSM", + "description": "Nursing & Hlth Services Mgmt", + "isVisibleInWhatif": True, + }, + { + "key": "NUTR", + "description": "Nutrition", + "isVisibleInWhatif": True, + }, + { + "key": "APEN", + "description": "Persian Language & Literature", + "isVisibleInWhatif": True, + }, + { + "key": "PHIL", + "description": "Philosophy", + "isVisibleInWhatif": True, + }, + { + "key": "PHYS", + "description": "Physics", + "isVisibleInWhatif": True, + }, + { + "key": "PSCI", + "description": "Political Science", + "isVisibleInWhatif": True, + }, + { + "key": "PSYS", + "description": "Psychoanalytic Studies", + "isVisibleInWhatif": True, + }, + { + "key": "PSYC", + "description": "Psychology", + "isVisibleInWhatif": True, + }, + { + "key": "RELS", + "description": "Religious Studies", + "isVisibleInWhatif": True, + }, + { + "key": "ROML", + "description": "Romance Languages", + "isVisibleInWhatif": True, + }, + { + "key": "RULA", + "description": "Russ Lang.,Lit.,&Culture", + "isVisibleInWhatif": True, + }, + { + "key": "RUCH", + "description": "Russian Culture & History", + "isVisibleInWhatif": True, + }, + { + "key": "STSC", + "description": "Science Technology & Society", + "isVisibleInWhatif": True, + }, + { + "key": "SOCI", + "description": "Sociology", + "isVisibleInWhatif": True, + }, + { + "key": "SARS", + "description": "South Asia Regional Studies", + "isVisibleInWhatif": True, + }, + { + "key": "SAST", + "description": "South Asia Studies", + "isVisibleInWhatif": True, + }, + { + "key": "SPAN", + "description": "Spanish", + "isVisibleInWhatif": True, + }, + { + "key": "STAT", + "description": "Statistics", + "isVisibleInWhatif": True, + }, + { + "key": "SRDA", + "description": "Survey Res & Data Analytics", + "isVisibleInWhatif": True, + }, + { + "key": "SEVM", + "description": "Sustainability & Envl Mgmt", + "isVisibleInWhatif": True, + }, + { + "key": "SE", + "description": "Systems Engineering", + "isVisibleInWhatif": True, + }, + { + "key": "SSE", + "description": "Systems Science & Engineering", + "isVisibleInWhatif": True, + }, + { + "key": "THAR", + "description": "Theatre Arts", + "isVisibleInWhatif": True, + }, + { + "key": "URED", + "description": "Urban Education", + "isVisibleInWhatif": True, + }, + { + "key": "URRE", + "description": "Urban Real Estate & Dvpmt", + "isVisibleInWhatif": True, + }, + { + "key": "URBS", + "description": "Urban Studies", + "isVisibleInWhatif": True, + }, + { + "key": "WSTD", + "description": "Womens Studies", + "isVisibleInWhatif": True, + }, + ], + "selectedChoices": ["AHSN"], + "ruleGoalCode": "MINOR", + "links": [], + }, + ], + }, + { + "id": "secondaryCurriculumCollection", + "description": "Additional areas of study", + "isExpandable": False, + "goals": [ + { + "name": "secondaryProgram", + "description": "Program", + "selectedChoices": [], + "choices": [], + }, + { + "name": "secondaryCollege", + "description": "College", + "choices": [], + "selectedChoices": [], + }, + { + "name": "secondaryDegree", + "description": "Degree", + "choices": [], + "selectedChoices": [], + }, + { + "name": "secondaryMajor", + "description": "Major", + "choices": [], + "selectedChoices": [], + }, + { + "name": "secondaryConcentration", + "description": "Concentration", + "choices": [], + "selectedChoices": [], + }, + { + "name": "secondaryMinor", + "description": "Minor", + "choices": [], + "selectedChoices": [], + }, + ], + }, + ] + res = s.post( + f"https://{DEGREEWORKS_HOST}/api/goals", + headers=headers, + cookies=cookies, + json=goals_payload, + timeout=timeout, + ) + res.raise_for_status() + + return [program["key"] for program in res.json()[0]["goals"][1]["choices"]] + + +def degree_plans_of(program_code): + goals_payload = [ + { + "id": "programCollection", + "description": "Program", + "isExpandable": False, + "goals": [ + { + "name": "catalogYear", + "selectedChoices": ["2023"], + "ruleGoalCode": None, + "links": [], + }, + { + "name": "program", + "description": "Program", + "entityName": "programs", + "isDisabled": False, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "selectedChoices": ["AU_BA_BIOD_U"], + "ruleGoalCode": "PROGRAM", + "links": [], + }, + { + "name": "school", + "description": "Level", + "entityName": "schools", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [ + { + "key": "UG", + "description": "Undergraduate", + "isVisibleInWhatif": True, + } + ], + "selectedChoices": ["UG"], + "ruleGoalCode": "SCHOOL", + "links": [], + }, + { + "name": "college", + "description": "College", + "entityName": "colleges", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "selectedChoices": ["AU"], + "ruleGoalCode": "COLLEGE", + "links": [], + }, + { + "name": "degree", + "description": "Degree", + "entityName": "degrees", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [ + { + "key": "BA", + "description": "Bachelor of Arts", + "isVisibleInWhatif": True, + } + ], + "selectedChoices": ["BA"], + "ruleGoalCode": "DEGREE", + "links": [], + }, + ], + }, + { + "id": "curriculumCollection", + "description": "Areas of study", + "isExpandable": False, + "goals": [ + { + "name": "major", + "description": "Major", + "entityName": "majors", + "isDisabled": False, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": True, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "selectedChoices": ["ANTH"], + "ruleGoalCode": "MAJOR", + "links": [], + }, + { + "name": "concentration", + "description": "Concentration", + "entityName": "concentrations", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": False, + "isStatic": True, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "choices": [], + "selectedChoices": [], + "ruleGoalCode": None, + "links": [], + }, + { + "name": "minor", + "description": "Minor", + "entityName": "minors", + "isDisabled": False, + "isDriver": False, + "isError": False, + "isMultiple": False, + "isRequired": False, + "isStatic": False, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "", + "errorMessage": "", + "selectedChoices": [], + "ruleGoalCode": "PROGRAM", + "links": [], + }, + { + "name": "secondaryCollege", + "description": "College", + "entityName": "colleges", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": False, + "isStatic": True, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "api/colleges", + "errorMessage": "", + "choices": [], + "selectedChoices": [], + "ruleGoalCode": None, + "links": [], + }, + { + "name": "secondaryDegree", + "description": "Degree", + "entityName": "degrees", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": False, + "isStatic": True, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "api/degrees", + "errorMessage": "", + "choices": [], + "selectedChoices": [], + "ruleGoalCode": None, + "links": [], + }, + { + "name": "secondaryMajor", + "description": "Major", + "entityName": "majors", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": False, + "isStatic": True, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "api/majors-whatif", + "errorMessage": "", + "choices": [], + "selectedChoices": [], + "ruleGoalCode": None, + "links": [], + }, + { + "name": "secondaryConcentration", + "description": "Concentration", + "entityName": "concentrations", + "isDisabled": True, + "isDriver": False, + "isError": False, + "isMultiple": False, + "isRequired": False, + "isStatic": True, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "api/concentrations", + "errorMessage": "", + "choices": [], + "selectedChoices": [], + "ruleGoalCode": None, + "links": [], + }, + { + "name": "secondaryMinor", + "description": "Minor", + "entityName": "minors", + "isDisabled": True, + "isDriver": True, + "isError": False, + "isMultiple": False, + "isRequired": False, + "isStatic": True, + "isVisible": True, + "isNoValidOptionsWarning": False, + "source": "api/minors-whatif", + "errorMessage": "", + "choices": [], + "selectedChoices": [], + "ruleGoalCode": None, + "links": [], + }, + ], + }, + ] + + degree_plans: list[DegreePlan] = [] + # Set program code + goals_payload[0]["goals"][1]["selectedChoices"] = [program_code] + + res = s.post( + f"https://{DEGREEWORKS_HOST}/api/goals", + headers=headers, + cookies=cookies, + json=goals_payload, + ) + + # LEVEL + levels = res.json()[0]["goals"][2]["choices"] + if len([choice for choice in levels if choice["key"] == "UG"]) < 1: + print("No undergraduate degree for program", program_code) + return + goals_payload[0]["goals"][2]["selectedChoices"] = ["UG"] + + # DEGREE + degrees = res.json()[0]["goals"][4]["choices"] + for degree in degrees: + degree_code = degree["key"] + print(program_code, " : ", degree_code) + assert degree_code.startswith("B") # i.e., is a bachelor's degree + + # set degree + goals_payload[0]["goals"][4]["selectedChoices"] = [degree_code] + + res = s.post( + f"https://{DEGREEWORKS_HOST}/api/goals", + headers=headers, + cookies=cookies, + json=goals_payload, + ) + + majors = res.json()[1]["goals"][0]["choices"] + + # MAJOR + for major in majors: + major_code = major["key"] + print("\t", major_code) + + goals_payload[1]["goals"][0]["selectedChoices"] = [major_code] + + res = s.post( + f"https://{DEGREEWORKS_HOST}/api/goals", + headers=headers, + cookies=cookies, + json=goals_payload, + ) + + # CONCENTRATION + concentrations = res.json()[1]["goals"][1]["choices"] + if len(concentrations) == 0: + degree_plans.append( + DegreePlan( + program=program_code, + degree=degree_code, + major=major_code, + concentration=None, + year=2023, + ) + ) + continue + for concentration in concentrations: + concentration_code = concentration["key"] + print("\t\t", concentration_code) + degree_plans.append( + DegreePlan( + program=program_code, + degree=degree_code, + major=major_code, + concentration=concentration_code, + year=2023, + ) + ) + + return degree_plans + + +def audit(degree_plan: DegreePlan, timeout=30): + payload = { + "studentId": env["PENN_ID"], + "isIncludeInprogress": True, + "isIncludePreregistered": True, + "isKeepCurriculum": False, + "school": "UG", + "degree": degree_plan.degree, + "catalogYear": degree_plan.year, + "goals": [ + {"code": "MAJOR", "value": degree_plan.major}, + {"code": "CONC", "value": degree_plan.concentration}, + {"code": "PROGRAM", "value": degree_plan.program}, + {"code": "COLLEGE", "value": degree_plan.program.split("_")[0]}, + ], + "classes": [], + } + + res = s.post( + f"https://{DEGREEWORKS_HOST}/api/audit", + headers=headers, + cookies=cookies, + json=payload, + timeout=timeout, + ) + + res.raise_for_status() + return res.json() + + +def write_dp(dp: DegreePlan, json: dict, dir: str | Path = "degreeplans"): + with open(Path(dir, f"{dp.year}-{dp.program}-{dp.degree}-{dp.major}-{dp.concentration}")) as f: + json.dump(json, f, indent=4)