# Project export: SlugAdviser

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2024
- Tagline: With enrollment week quietly always coming towards the end of the quarter, we give our bright-eyed first-time students and continuing students the opportunity with the most optimal course schedule!
- Devpost: https://devpost.com/software/slugadviser
- GitHub: https://github.com/AndyKuz/Slug-Adviser
- Team: 4 GitHub contributor(s) — Andrew Kuznetsov (26 commits), Shivam (20 commits), Chad Baker (12 commits), aravindnairtech (2 commits)

## Devpost submission (written by the team)

### Inspiration

We are a group of third-year computer science and engineering students trying to find our most optimal schedule with factors like early/late graduation, quarterly workload, and elective/concentration interests. We wanted to make this process a lot easier for those who come after us and provide a service that can develop the optimal schedule with various factors such as quarterly workload, choice of electives, the difficulty of individual courses, and quarters offered for courses.

### What it does

The main algorithm for finding optimal schedules is the directional graph with nodes featuring a unique class (as an object with properties such as professors, credits, department, and course number). The algorithm keeps in mind the user's preference of preferred workload range of hours per week, preferred course unit range per quarter, and prerequisites of every class. As the algorithm traverses through the graph, the algorithm will find an ideal set of classes for a quarter append it, and move forward to the next quarter.

### How we built it

Attached is our architecture: link We split the tasks as evenly as we could, giving each team member an equal share of work. We then researched and planned our individual tasks out and after we each had our own finished projects we combined them into a finished final project.

### Challenges we ran into

A major challenge for us was getting accurate course information to input into the algorithm we built. We attempted to web-scrape the course catalog to get all the necessary course information, but the way the course catalog was developed made it extremely challenging to do so. We ended up having to manually input course information, but hopefully, in the future, we'd love to work with UCSC to automate that process entirely.

### Accomplishments we're proud of

Creating the algorithm for the project was a very difficult and complex task, as we had to design a custom algorithm from the ground up. We implemented a directed polytree and then took elements from BFS to make our own custom search algorithm.

### What we learned

Making projects that work on a large scale such as the one we created, can be very difficult because of the need to connect different working parts. Having all team members be on the same page about what their code should do is essential for making all these small parts come together

### What's next

As stated previously, we want to work with UCSC to automate the class selection process so we can make our web service accessible to all majors. Furthermore, the project is far from perfect, there are plenty of tweaks to pre-existing features as well as new features to add. We want to add more features to avoid class conflicts, Design Document link

## README (from the GitHub repository)

# Slug-Adviser

### Install Dependencies
- npm install (inside slugadvisorfrontend/)
- pip install flask
- pip install requests
- pip install flask_cors

  ### Running
  - open two terminal windows
  - one inside slugadvisorfrontend/
      npm start
  - one in the main folder
      python preprocess.py


## Detected evidence (automated analysis)

Indexed codebase: 36 recognized source files, 73 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (44 of 44)

```
.DS_Store
course.py
courseInfo.py
digraph.py
output.json
preprocess.py
professor.py
py2jsonconvert.py
ratemyprofessorwebscrape.py
README.md
schedule_maker.py
slugadvisorfrontend/.gitignore
slugadvisorfrontend/output.json
slugadvisorfrontend/package.json
slugadvisorfrontend/public/index.html
slugadvisorfrontend/public/manifest.json
slugadvisorfrontend/public/robots.txt
slugadvisorfrontend/README.md
slugadvisorfrontend/src/App.css
slugadvisorfrontend/src/App.js
slugadvisorfrontend/src/App.test.js
slugadvisorfrontend/src/Components/AccordionComponent.js
slugadvisorfrontend/src/Components/APClassSelector/ApClassesComponent.js
slugadvisorfrontend/src/Components/APClassSelector/APCLassList.js
slugadvisorfrontend/src/Components/APClassSelector/CheckBox.js
slugadvisorfrontend/src/Components/APClassSelector/ScoreDropDown.js
slugadvisorfrontend/src/Components/APClassSelector/Searchbar.js
slugadvisorfrontend/src/Components/GeneratedSchedule/CourseToast.js
slugadvisorfrontend/src/Components/GeneratedSchedule/SampleClasses.json
slugadvisorfrontend/src/Components/GeneratedSchedule/Schedule.js
slugadvisorfrontend/src/Components/MajorSelection.js
slugadvisorfrontend/src/Components/TabComponent.js
slugadvisorfrontend/src/Components/ToastComponent.js
slugadvisorfrontend/src/Components/UCSCoursesTaken/ScoreDropDown.js
slugadvisorfrontend/src/Components/UCSCoursesTaken/Searchbar.js
slugadvisorfrontend/src/Components/UCSCoursesTaken/UCSCClassComponent.js
slugadvisorfrontend/src/Components/UCSCoursesTaken/UCSCCLassList.js
slugadvisorfrontend/src/Components/UserPreference.js
slugadvisorfrontend/src/index.css
slugadvisorfrontend/src/index.js
slugadvisorfrontend/src/reportWebVitals.js
slugadvisorfrontend/src/setupTests.js
slugadvisorfrontend/src/SharedContext.js
studentpreferences.py
```

### Dependencies

- slugadvisorfrontend/package.json: @emotion/react@^11.11.3, @emotion/styled@^11.11.0, @mui/material@^5.15.5, @testing-library/jest-dom@^5.17.0, @testing-library/react@^13.4.0, @testing-library/user-event@^13.5.0, bootstrap@^5.3.2, react@^18.2.0, react-bootstrap@^2.10.0, react-dom@^18.2.0, react-scripts@5.0.1, react-select@^5.8.0, web-vitals@^2.1.4

### Recent commits (newest first)

- fixing some minor issues
- Update README.md
- finished hackathon project
- merging
- Merge pull request #5 from AndyKuz/test1
- final working code
- attempts for webscraping
- Merge branch 'test1' of github.com:AndyKuz/Slug-Adviser into test1
- added toasts for the courses
- fixing rmp rating/prof
- adding rmp rating + prof
- Merge branch 'test1' of github.com:AndyKuz/Slug-Adviser into test1
- Making courses objects
- added all the classlist
- fixing some bug
- fixed schedule generation
- fixed preprocess merger conflict
- merge conflicts
- Fixing 0 idxing issue
- debugging left: user preferences + UI

## Key source files (fetched from GitHub, selected and truncated for size)

### slugadvisorfrontend/package.json

```
{
  "name": "slugadvisorfrontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@emotion/react": "^11.11.3",
    "@emotion/styled": "^11.11.0",
    "@mui/material": "^5.15.5",
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "bootstrap": "^5.3.2",
    "react": "^18.2.0",
    "react-bootstrap": "^2.10.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "react-select": "^5.8.0",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

```

### slugadvisorfrontend/src/index.js

```javascript
// index.js or another entry point
import 'bootstrap/dist/css/bootstrap.min.css';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();


```

### slugadvisorfrontend/src/App.js

```javascript
import React from 'react';
import TabComponent from './Components/TabComponent';
import 'bootstrap/dist/css/bootstrap.min.css';
import { SharedProvider } from './SharedContext';
import logo from './images/logo.png'

function App() {
  const backgroundStyle = {
    backgroundImage: `url(${logo})`,
    backgroundSize: 'cover',
    backgroundPosition: 'center',
    height: '100vh', // Adjust the height as needed
    // Add any other styles you want for your background
  };

  return (
    <div className="App" style={backgroundStyle}>
      <SharedProvider>
        <TabComponent />
      </SharedProvider>
    </div>
  );
}

export default App;


```

### professor.py

```python
class Professor:
    def __init__(self, professorName, professorDifficulty, professorRating):
        self.professorName = professorName
        self.professorDifficulty = professorDifficulty
        self.professorRating = professorRating



```

### course.py

```python
from flask import Flask, jsonify

class Course:
    def __init__(self, dptmnt, dptmnt_num, course_title, num_credits, incoming_prereqs,
                  hours=30, outgoing_prereqs=None, quarters=None, profs=None, best_prof="", 
                  best_prof_rating=2.5):
        self.dptmnt = dptmnt
        self.dptmnt_num = dptmnt_num
        self.course_title = course_title
        self.num_credits = num_credits
        self.hours = hours
        self.incoming_prereqs = incoming_prereqs
        self.outgoing_prereqs = outgoing_prereqs if outgoing_prereqs is not None else []
        self.quarters = quarters if quarters is not None else []
        self.profs = profs if profs is not None else []
        self.best_prof = best_prof
        self.best_prof_rating = best_prof_rating
        self.priority_rating = 0
    
    def set_priority_rating(self):
        self.priority_rating = (5 - len(self.quarters)) + len(self.outgoing_prereqs)

    def to_dict(self):
        incoming_prereqs_dict = (
            [prereq.to_dict() for prereq in self.incoming_prereqs]
            if isinstance(self.incoming_prereqs, list)
            else []
        )

        return {
            'dptmnt': self.dptmnt,
            'dptmnt_num': self.dptmnt_num,
            'course_title': self.course_title,
            'best_prof': self.best_prof,
            'best_prof_rating': self.best_prof_rating,
            'num_credits': self.num_credits,
            'incoming_prereqs': incoming_prereqs_dict,
            'quarters': self.quarters,
            'profs': self.profs
        }

class Quarter:
    def __init__(self, college_year, quarter_type, course_list):
        self.college_year = college_year
        self.quarter_type = quarter_type
        self.course_list = course_list
    
    def to_dict(self):
        return {
            'college_year': self.college_year,
            'quarter_type': self.quarter_type,
            'courses': [course.to_dict() for course in self.course_list]
        }
```

### py2jsonconvert.py

```python
import json
from course import Quarter, Course
from typing  import List

# def convert(qlist: List[Quarter]):
#     plan_data = []

#     # Group quarters by year
#     quarters_by_year = {}
#     for quarter in qlist:
#         if quarter.college_year not in quarters_by_year:
#             quarters_by_year[quarter.college_year] = []
#         quarters_by_year[quarter.college_year].append(quarter)

#     # Create JSON format
#     for year, quarters in quarters_by_year.items():
#         year_data = {"year": year, "quarters": []}
#         for quarter in quarters:
#             quarter_data = {
#                 "quarter": quarter.quarter_type,
#                 "courses": quarter.course_list
#             }
#             year_data["quarters"].append(quarter_data)
#         plan_data.append(year_data)

#     return plan_data
def convert(qlist: List[Quarter]):
    plan_data = []

    # Group quarters by year
    quarters_by_year = {}
    for quarter in qlist:
        if quarter.college_year not in quarters_by_year:
            quarters_by_year[quarter.college_year] = []
        quarters_by_year[quarter.college_year].append(quarter)

    # Create JSON format
    for year, quarters in quarters_by_year.items():
        year_data = {"year": year, "quarters": []}
        for quarter in quarters:
            quarter_data = {
                "quarter": quarter.quarter_type,
                "major_courses": [course for course in quarter.course_list if isinstance(course, Course)],
                "placeholder_courses": [course for course in quarter.course_list]
            }
            year_data["quarters"].append(quarter_data)
        plan_data.append(year_data)

    return plan_data
    # with open('output.json', 'w') as json_file:
    #     json.dump({"planData": plan_data}, json_file, indent=4, sort_keys=False)

'''   
qtest1 = Quarter("First year", "Fall", [])
qtest2 = Quarter("First year", "Winter", ["cse 12", "cse 16"])
qtest3 = Quarter("First year", "Spring", ["cse 120", "cse 16"])
qtest4 = Quarter("Second year", "Fall", ["cse 12", "cse 16"])
qtest5 = Quarter("Second year", "Winter", ["cse 12", "cse 16"])
qtest6 = Quarter("Second year", "Spring", ["cse 12", "cse 16"])
qlist = [qtest1, qtest2, qtest3, qtest4, qtest5, qtest6]
convert(qlist)
'''
```

### ratemyprofessorwebscrape.py

```python
import requests
from bs4 import BeautifulSoup
from googlesearch import search
from professor import Professor
import course
import studentpreferences
import certifi
import urllib3
http = urllib3.PoolManager()


    
def find_professor_url(professor_name, site):
    query = f'{professor_name} site:{site}'

    # Perform a Google search
    search_results = search(query, num=1, stop=1, pause=2)

    # Iterate through the search results
    for result in search_results:
        if site in result:
            return result

    # except Exception as e:
    #     print(f"An error occurred: {e}")

    return None



def rmp_exec(professorName):
    # print("R U N N I N G RMP EXEC()")
    # professorName = 'Linsey Kuper'
    # site_to_search = 'ratemyprofessors.com'

    PROFESSOR_URL = find_professor_url(professorName, 'ratemyprofessors.com')
    flag = False
    if PROFESSOR_URL:
        flag = True
        # print(f"The URL for {professorName} on {'ratemyprofessors.com'} is: {PROFESSOR_URL}")
    else: #error handling
        flag = False
        # print(f"No URL found for {professorName} on {'ratemyprofessors.com'}.")


    RMP_URL = PROFESSOR_URL
    # response = requests.get(RMP_URL, verify=False)
    response = http.request('GET', RMP_URL, retries=False, verify=False)


    if response.status_code == 200:
        soup = BeautifulSoup(response.text, 'html.parser')

        nameElement = soup.find('div', {'class': 'NameTitle__Name-dowf0z-0'})
        feedbackElements = soup.find_all('div', {'class': 'FeedbackItem__StyledFeedbackItem-uof32n-0'})
        ratingElement = soup.find('div', {'class': 'RatingValue__AvgRatingWrapper-qw8sqy-3'})

        if nameElement:
            firstName = nameElement.find('span').text.strip()
            lastName = nameElement.find('span', {'class': 'NameTitle__LastNameWrapper-dowf0z-2'}).text.strip()

            fullName = f"{firstName} {lastName}"

            # print("TEST - Professor's Full Name:", fullName)
        else: #Error Handling
            print("Name element not found.")

        if len(feedbackElements) >= 2:
            difficultyElement = feedbackElements[1].find('div', {'class': 'FeedbackItem__FeedbackDescription-uof32n-2', 'class': 'kkESWs'})
            if difficultyElement:
                professorDifficulty = difficultyElement.text.strip()
                # print("TEST - Level of Difficulty:", professorDifficulty)
            else: #Error Handling
                print("Difficulty element not found.")
        else: #Error Handling
            print("Insufficient feedback elements found.")

        if ratingElement:
            professorRating = ratingElement.find('div', {'class': 'RatingValue__Numerator-qw8sqy-2'}).text.strip()
            # print("TEST - Professor Rating:", professorRating)
        else: #Error Handling
            print("Rating element not found.")

        ratingDifficulty = [professorRating, professorDifficulty]
        return ratingDifficulty
    else: #Error Handling
        print("Failed to retrieve the webpage. Status code:", response.status_code)


# def main():
#     professorKuper = Professor('1','2', '3')
#     professorKuper.professorName = 'Lindsey Kuper'
#     ratingDifficulty = rmp_exec(professorKuper.professorName)
#     professorKuper.professorRating = ratingDifficulty[0]
#     professorKuper.professorDifficulty = ratingDifficulty[1]

#     # print("TESTING MAIN KUPER RATING: ", professorKuper.professorRating)
#     # print("TESTING MAIN KUPER DIFFICULTY: ", professorKuper.professorDifficulty)
#     # print("TESTING THIS IS KUPER: ", professorKuper.professorName)

# if __name__ == '__main__':
#     main()
```

### preprocess.py

```python
# from flask import Flask, request, jsonify
# from flask_cors import CORS

from flask import Flask, request, jsonify
from flask_cors import CORS
from studentpreferences import StudentPreferences
import json
import courseInfo
import schedule_maker
import py2jsonconvert

app = Flask(__name__)
CORS(app)

# json_file_path = "./output.json"

# with open(json_file_path, 'r') as json_file:
#     data = json.load(json_file)
    
# def get_plan_data():
#     return(data.get('planData'))

# @app.route('/api/plan-data', methods=['GET'])
# def plan_data():
#     try:
#         data = get_plan_data()
#         return jsonify(data)
#     except Exception as e:
#         return jsonify({'error': 'Failed to fetch plan data'}), 500

@app.route('/saveData', methods=['POST'])
def save_data():
    # try:
        user_data = request.get_json()
        user_pref = parse_user_pref(user_data)
        
        major_courses = []
        placeholder_courses = []
        if user_pref.major == "Computer Science":
            major_courses = courseInfo.get_cs_courses()
            placeholder_courses = courseInfo.get_cs_placeholders()
        elif user_pref.major == "Computer Engineering":
            major_courses = courseInfo.get_ce_courses()
            placeholder_courses = courseInfo.get_ce_placeholder()
        elif user_pref.major == "Electrical Engineering":
            major_courses = courseInfo.get_ee_courses()
            placeholder_courses = courseInfo.get_ee_placeholder()
        else:
            print("Error major not recognized")
            
        college_year = 0
        if user_pref.currentYear == "Freshman":
            college_year = 0
        elif user_pref.currentYear == "Sophomore":
            college_year = 1
        elif user_pref.currentYear == "Junior":
            college_year = 2
        elif user_pref.currentYear == "Senior":
            college_year = 3
        
        print("placeholder_courses: ", placeholder_courses)

        quarters = schedule_maker.create_schedule(major_courses, user_pref.get_courses_taken(), college_year, placeholder_courses)
        plan_data = py2jsonconvert.convert(quarters)

        return jsonify(plan_data), 200
    # except Exception as e:
    #     print('Error processing data:', str(e))
    #     return jsonify({'error': f'Failed to process data {str(e)}'}), 500

def parse_user_pref(data):
    # Parse the JSON data
    # data = json.loads(json_data)

    # Extracting information and assigning to variables
    major = data.get("Major", "")
    courses_taken = data.get("CoursesTaken", []) #fix course issue
    ap_scores_3 = data.get("APScores3", [])
    ap_scores_4 = data.get("APScores4", [])
    ap_scores_5 = data.get("APScores5", [])
    min_hours = int(data.get("MinHoursPerWeek", 0))
    max_hours = int(data.get("MaxHoursPerWeek", 0))
    min_credits = int(data.get("MinCredits", 0))
    max_credits = int(data.get("MaxCredits", 0))
    current_year = data.get("CurrentYear", "")


    userPref = StudentPreferences(minHoursWork=min_hours, maxHoursWork=max_hours, 
                                  minCreditsQuarter=min_credits, maxCreditsQuarter=max_credits,
                                  major=major, coursesTaken=courses_taken, currentYear=current_year, 
                                  APScore3=ap_scores_3, APScore4=ap_scores_4, APScore5=ap_scores_5)
    
    return userPref

# class AlgoInfo:
#     def __init__(self, userPref):
#         self.courses_passed = list(set(ApIbConverter(userPref) + userPref.coursesTaken))
#         self.min_hours = userPref.minHoursWork
#         self.max_hours = userPref.maxHoursWork
#         self.min_credits = userPref.minCreditsQuarter
#         self.max_credits = userPref.maxCreditsQuarter

#     def get_courses_passed(self):
#         return self._courses_passed

#     def get_min_hours(self):
#         return self._min_hours

#     def get_max_hours(self):
#         return self._max_hours

#     def get_min_credits(self):
#         return self._min_credits

#     def get_max_credits(self):
#         return self._max_credits
def main():
    app.run(debug=True)

main()
```

### digraph.py

```python
import random

class Digraph:
    def __init__(self):
        self.root_courses = []
        self.all_courses = []  # could possibly remove?

    # adds a node with no outgoing prereqs
    def add_leaf_node(self, c):
        if c not in self.all_courses:
            self.all_courses.append(c)
        if c not in self.root_courses:
            self.root_courses.append(c)
            self.sort_root_courses_by_outgoing()

    # adds a directional edge from c1 to c2
    def add_edge(self, c1, c2):
        if c1 not in self.all_courses:
            self.all_courses.append(c1)
        if c2 not in self.all_courses:
            self.all_courses.append(c2)

        # adds c1 to root nodes
        if c1 not in self.root_courses and c1.incoming_prereqs == []:
            self.root_courses.append(c1)
            self.sort_root_courses_by_outgoing()

        # if c2 was in root_nodes remove it
        if c2 in self.root_courses:
            self.root_courses.remove(c2)
            self.sort_root_courses_by_outgoing()

        if c2 not in c1.outgoing_prereqs:
            c1.outgoing_prereqs.append(c2)
        if c1 not in c2.incoming_prereqs:
            c2.incoming_prereqs.append(c1)

    def del_edge(self, c1, c2):
        if c2 in c1.outgoing_prereqs:
            c1.outgoing_prereqs.remove(c2)
        if c1 in c2.incoming_prereqs:
            c2.incoming_prereqs.remove(c1)

    # only use for root nodes
    def del_course(self, c):
        while len(c.outgoing_prereqs) != 0:
            c_outgoing = c.outgoing_prereqs[0]
            self.del_edge(c, c_outgoing)
            if len(c_outgoing.incoming_prereqs) == 0:
                self.root_courses.append(c_outgoing)
                self.sort_root_courses_by_outgoing()
    
        if c in self.root_courses:
            self.root_courses.remove(c)
            self.sort_root_courses_by_outgoing()
        if c in self.all_courses:
            self.all_courses.remove(c)

    # chooses the classes for a given quarter
    # quarter: 0 -> Fall, 1 -> Winter, 2 -> Spring 
    def choose_quarter(self, num_courses, min_credits, max_credits, min_hours, max_hours, quarter):
        chosen_courses = []
        
        # NOTE: implementation for one random root course per quarter
        """if len(self.root_courses) == 0:
            return -1
        elif len(self.root_courses) == 1:
            curr_course = 0
        else:
            curr_course = random.randrange(0, len(self.root_courses)-1)
        """

        if len(self.root_courses) == 0:
            return -1
        else:
            curr_course = 0
        
        # randomly iterates through root courses until it finds one that works for the "quarter"
        while True:
            if quarter in self.root_courses[curr_course].quarters:
                chosen_courses.append(self.root_courses[curr_course])
                break
            else:
                curr_course = random.randrange(0, len(self.root_courses))
    
        # iterates once or twice depending on num_classes specified
        for c in self.root_courses:
            if c in chosen_courses: continue
            if self.check_class_compatibility(chosen_courses, c, min_credits, max_credits, min_hours, max_hours, quarter, num_courses):
                chosen_courses.append(c)
                if len(chosen_courses) == num_courses:
                    break

        # delete classes that were chosen
        for c in chosen_courses:
            self.del_course(c)
        self.sort_root_courses_by_outgoing()
        return chosen_courses


    # checks compatibility btwn two classes by checking if they fit into range of credits and hours   
    def check_class_compatibility(self, chosen_courses, new_course, min_credits, max_credits, min_hours, max_hours, quarter, num_courses):
        # adjusts when we are only looking at 2 courses but 3 course overall quarter
        adjusted_min_credits = (min_credits - 5) if len(chosen_courses) == 1 and num_courses == 3 else min_credits
        adjusted_max_credits = (max_credits - 5) if len(chosen_courses) == 1 and num_courses == 3 else max_credits
        
        if quarter not in new_course.quarters:
            return False
        
        total_class_credits = new_course.num_credits
        for c in chosen_courses:
            total_class_credits += c.num_credits

        if total_class_credits >= adjusted_min_credits and total_class_credits <= adjusted_max_credits:
            return True
        return False
    
    # sorts root_courses by outgoing_prereqs
    def sort_root_courses_by_outgoing(self):
        get_outgoing_prereqs = lambda course: course.priority_rating
        self.root_courses.sort(key=get_outgoing_prereqs, reverse=True)



            




```

### studentpreferences.py

```python
import courseInfo

def ApIbConverter(APScore3, APScore4, APScore5):
    # print("ENTERED AP IB CONVERTER METHOD")
    
    # userPreferences = parse_json(FRONT_END_DATA)


    apCourses = APScore3 + APScore4 + APScore5
    # print("AP COURSES: ", apCourses)
    coursesEligible = []
    courseInfoDict = courseInfo.get_courseInfo()

    for courseIter in apCourses:
        # print("COURSEITER: ", courseIter)
        if courseIter ==  'AP Art History': 
            pass
        elif courseIter ==  'AP Biology':
            # coursesEligible.append()
            # coursesEligible.append(bioTwentyB)
            pass #till bio classes are released
        elif courseIter ==  'AP Calculus AB':
            if courseIter in APScore3:
                coursesEligible.append(courseInfoDict['math_3'])
                # coursesEligible.append(amThree)
            else:
                coursesEligible.append(courseInfoDict['math_3'])
                # coursesEligible.append(amThree)
                # coursesEligible.append(mathElevenA)
                coursesEligible.append(courseInfoDict['math_19a'])
        elif courseIter == 'AP Calculus BC':
            if courseIter in APScore3:
                coursesEligible.append(courseInfoDict['math_3'])
                # coursesEligible.append(amThree)
                # coursesEligible.append(mathElevenA)
                coursesEligible.append(courseInfoDict['math_19a'])
            else:
                coursesEligible.append(courseInfoDict['math_3'])
                # coursesEligible.append(amThree)
                # coursesEligible.append(mathElevenA)
                # coursesEligible.append(mathElevenB)
                coursesEligible.append(courseInfoDict['math_19a'])
                coursesEligible.append(courseInfoDict['math_19b'])
        elif courseIter ==  'AP Computer Science A':
            # coursesEligible.append(cseTen)
            # coursesEligible.append(cseFiveJ)
            pass
        elif courseIter ==  'AP Computer Science Principles':
            # coursesEligible.append(cseTen)
            pass
        elif courseIter ==  'AP Macroeconomics':
            # coursesEligible.append(econTwo)
            pass
        elif courseIter ==  'AP Microeconomics':
            # coursesEligible.append(econOne)
            pass
        elif courseIter ==  'AP Physics C: Electricity and Magnetism':
            # coursesEligible.append(courseInfoDict['phys_6a']) #'phys_6a'
            # coursesEligible.append(courseInfoDict['phys_6c']) #'phys_6c'
            coursesEligible.append(courseInfoDict['phys_5a']) #'phys_5a'
            coursesEligible.append(courseInfoDict['phys_5c']) #'phys_5c'
        elif courseIter ==  'AP Physics C: Mechanics':
            # coursesEligible.append(courseInfoDict['phys_6a']) #'phys_6a'
            # coursesEligible.append(courseInfoDict['phys_6c']) #'phys_6c'
            coursesEligible.append(courseInfoDict['phys_5a']) #'phys_5a'
            coursesEligible.append(courseInfoDict['phys_5c']) #'phys_5c'
        elif courseIter ==  'AP Psychology':
            # coursesEligible.append(psychOne)
            pass
        elif courseIter ==  'AP Statistics':
            # coursesEligible.append(statFive)
            # coursesEligible.append(psychTwo)
            # coursesEligible.append(socThreeB)
            pass
    return list(set(coursesEligible))

class StudentPreferences:
    def __init__(self, minHoursWork, maxHoursWork, minCreditsQuarter, maxCreditsQuarter, major, coursesTaken, currentYear, APScore3, APScore4, APScore5):
        courseInfoDict = courseInfo.get_courseInfo()
        self.minHoursWork = minHoursWork
        self.maxHoursWork = maxHoursWork
        self.minCreditsQuarter = minCreditsQuarter
        self.maxCreditsQuarter = maxCreditsQuarter
        self.APScore3 = APScore3
        self.APScore4 = APScore4
        self.APScore5 = APScore5
        self.coursesTaken = list(set(ApIbConverter(APScore3, APScore4, APScore5) + [courseInfoDict.get(c['className'].split(':')[0].lower().replace(" ", "_")) for c in coursesTaken]))
        self.currentYear = currentYear
        # self.summerClasses = summerClasses
        self.major = major

    def get_courses_taken(self):
        return self.coursesTaken

    def get_min_hours_work(self):
        return self.minHoursWork

    def get_max_hours_work(self):
        return self.maxHoursWork

    def get_min_credits_quarter(self):
        return self.minCreditsQuarter

    def get_max_credits_quarter(self):
        return self.maxCreditsQuarter

    def get_major(self):
        return self.major
    
    def get_current_year(self):
        return self.currentYear

    def get_APScore3(self):
        return self.APScore3

    def get_APScore4(self):
        return self.APScore4

    def get_APScore5(self):
        return self.APScore5

```

[23 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]