diff --git a/pittapi/textbook.py b/pittapi/textbook.py
index d608b92..197314a 100644
--- a/pittapi/textbook.py
+++ b/pittapi/textbook.py
@@ -1,377 +1,225 @@
-import grequests
-import requests
-
-from typing import Any, Callable, Generator
-
-BASE_URL = "http://pitt.verbacompare.com/"
-
-CODES = [
- "ADMJ",
- "ADMPS",
- "AFRCNA",
- "AFROTC",
- "ANTH",
- "ARABIC",
- "ARTSC",
- "ASL",
- "ASTRON",
- "ATHLTR",
- "BACC",
- "BCHS",
- "BECN",
- "BFIN",
- "BHRM",
- "BIND",
- "BIOENG",
- "BIOETH",
- "BIOINF",
- "BIOSC",
- "BIOST",
- "BMIS",
- "BMKT",
- "BOAH",
- "BORG",
- "BQOM",
- "BSEO",
- "BSPP",
- "BUS",
- "BUSACC",
- "BUSADM",
- "BUSBIS",
- "BUSECN",
- "BUSENV",
- "BUSERV",
- "BUSFIN",
- "BUSHRM",
- "BUSMKT",
- "BUSORG",
- "BUSQOM",
- "BUSSCM",
- "BUSSPP",
- "CDACCT",
- "CDENT",
- "CEE",
- "CGS",
- "CHE",
- "CHEM",
- "CHIN",
- "CLASS",
- "CLRES",
- "CLST",
- "CMMUSIC",
- "CMPBIO",
- "COE",
- "COEA",
- "COEE",
- "COMMRC",
- "CS",
- "CSD",
- "DENHYG",
- "DENT",
- "DIASCI",
- "DSANE",
- "EAS",
- "ECE",
- "ECON",
- "EDUC",
- "ELI",
- "EM",
- "ENDOD",
- "ENGCMP",
- "ENGFLM",
- "ENGLIT",
- "ENGR",
- "ENGSCI",
- "ENGWRT",
- "ENRES",
- "EOH",
- "EPIDEM",
- "FACDEV",
- "FILMG",
- "FILMST",
- "FP",
- "FR",
- "FTADMA",
- "FTDA",
- "FTDB",
- "FTDC",
- "FTDR",
- "GEOL",
- "GER",
- "GERON",
- "GREEK",
- "GREEKM",
- "GSWS",
- "HAA",
- "HIM",
- "HINDI",
- "HIST",
- "HONORS",
- "HPA",
- "HPM",
- "HPS",
- "HRS",
- "HUGEN",
- "IDM",
- "IE",
- "IL",
- "IMB",
- "INFSCI",
- "INTBP",
- "IRISH",
- "ISB",
- "ISSP",
- "ITAL",
- "JPNSE",
- "JS",
- "KOREAN",
- "LATIN",
- "LAW",
- "LCTL",
- "LDRSHP",
- "LEGLST",
- "LING",
- "LIS",
- "LSAP",
- "MATH",
- "ME",
- "MED",
- "MEDEDU",
- "MEMS",
- "MILS",
- "MOLBPH",
- "MSCBIO",
- "MSCBMP",
- "MSCMP",
- "MSE",
- "MSIMM",
- "MSMBPH",
- "MSMGDB",
- "MSMPHL",
- "MSMVM",
- "MSNBIO",
- "MUSIC",
- "NEURO",
- "NPHS",
- "NROSCI",
- "NUR",
- "NURCNS",
- "NURNM",
- "NURNP",
- "NURSAN",
- "NURSP",
- "NUTR",
- "ODO",
- "OLLI",
- "ORBIOL",
- "ORSUR",
- "OT",
- "PAS",
- "PEDC",
- "PEDENT",
- "PERIO",
- "PERS",
- "PETE",
- "PHARM",
- "PHIL",
- "PHYS",
- "PIA",
- "POLISH",
- "PORT",
- "PROSTH",
- "PS",
- "PSY",
- "PSYC",
- "PSYED",
- "PT",
- "PUBHLT",
- "PUBSRV",
- "REHSCI",
- "REL",
- "RELGST",
- "RESTD",
- "RUSS",
- "SA",
- "SERCRO",
- "SLAV",
- "SLOVAK",
- "SOC",
- "SOCWRK",
- "SPAN",
- "STAT",
- "SWAHIL",
- "SWBEH",
- "SWCOSA",
- "SWE",
- "SWGEN",
- "SWINT",
- "SWRES",
- "SWWEL",
- "TELCOM",
- "THEA",
- "TURKSH",
- "UKRAIN",
- "URBNST",
- "VIET",
-]
-KEYS = ["isbn", "citation", "title", "edition", "author"]
-QUERIES = {
- "courses": "compare/courses/?id={}&term_id={}",
- "books": "compare/books?id={}",
-}
-LOOKUP_ERRORS = {
- 1: "section {1}.",
- 2: "instructor {2}.",
- 3: "section {1} or instructor {2}.",
-}
-
-
-def _construct_query(query: str, *args) -> str:
- """Constructs query based on which one is requested
- and fills the query in with the given arguments
- """
- return QUERIES[query].format(*args)
-
-
-def _validate_term(term: str) -> str:
- """Validates term is a string and check if it is valid."""
- if len(term) == 4 and term.isdigit():
- return term
- raise ValueError("Invalid term")
-
-
-def _validate_course(course: str) -> str:
- """Validates course is a four digit number,
- otherwise adds zero(s) to create four digit number or,
- raises an exception.
- """
- if len(course) > 4 or not course.isdigit():
- raise ValueError("Invalid course number")
- elif len(course) == 4:
- return course
- return "0" * (4 - len(course)) + course
-
-
-def _filter_dictionary(d: dict[Any, Any], keys: list[Any]) -> dict[Any, Any]:
- """Creates new dictionary from selecting certain
- key value pairs from another dictionary
- """
- return dict((k, d[k]) for k in keys if k in d)
-
-
-def _find_item(id_key, data_key, error_item) -> Callable[[dict[Any, Any], Any], Any]:
- """Finds a dictionary in a list based on its id key, and
- returns a piece of data from the dictionary based on a data key.
- """
-
- def find(data, value):
- for item in data:
- if item[id_key] == value:
- return item[data_key]
- raise LookupError("Can't find {} {}.".format(error_item, str(value)))
-
- return find
+from __future__ import annotations
-
-_find_sections = _find_item("id", "sections", "course")
-_find_course_id_by_instructor = _find_item("instructor", "id", "instructor")
-_find_course_id_by_section = _find_item("name", "id", "section")
+# grequests must be imported before all other libraries, especially requests,
+# because grequests uses gevent, which in turn uses monkey-patching to implement concurrency
+import grequests
+import warnings
+
+from dataclasses import dataclass
+from requests import ConnectionError
+from requests_html import HTMLResponse, HTMLSession
+from typing import Any, NamedTuple
+
+BASE_URL = "https://pitt.verbacompare.com/"
+
+SUBJECTS_URL = BASE_URL + "compare/departments/?term={term_id}"
+COURSES_URL = BASE_URL + "compare/courses/?id={dept_id}&term_id={term_id}"
+BOOKS_URL = BASE_URL + "compare/books?id={section_id}"
+
+CURRENT_TERM_ID = 78104 # Term ID for fall 2024, TODO: figure out how this ID is generated
+MAX_REQUEST_ATTEMPTS = 3
+
+sess = HTMLSession()
+request_headers: dict[str, str] | None = None
+subject_map: dict[str, str] | None = None
+
+
+@dataclass # No dataclass slots because they're not supported in Python 3.9
+class CourseInfo:
+ subject: str
+ course_num: str
+ instructor: str | None = None
+ section_num: str | None = None
+
+ def __post_init__(self) -> None:
+ if not subject_map:
+ _update_subject_map()
+ assert subject_map
+
+ self.subject = self.subject.upper()
+ if self.subject not in subject_map:
+ raise LookupError(f"{self.subject} is not a valid subject")
+ if len(self.course_num) > 4 or not self.course_num.isdigit():
+ raise ValueError("Invalid course number")
+ self.course_num = "0" * (4 - len(self.course_num)) + self.course_num
+ if self.instructor:
+ self.instructor = self.instructor.upper()
+ if self.section_num and (len(self.section_num) != 4 or not self.section_num.isdigit()):
+ raise ValueError("Invalid section number")
+
+
+class Textbook(NamedTuple):
+ title: str | None
+ author: str | None
+ edition: str | None
+ isbn: str | None
+ citation: str | None
+
+ @classmethod
+ def from_json(cls, json: dict[str, Any]) -> Textbook | None:
+ parsed_textbook = cls(
+ title=json.get("title"),
+ author=json.get("author"),
+ edition=json.get("edition"),
+ isbn=json.get("isbn"),
+ citation=json.get("citation"),
+ )
+ # If all fields are None, then don't bother returning a Textbook object
+ return parsed_textbook if any(field for field in parsed_textbook) else None
+
+
+def _update_headers() -> None:
+ for i in range(MAX_REQUEST_ATTEMPTS):
+ base_response: HTMLResponse = sess.get(BASE_URL)
+ if base_response.status_code == 200:
+ break
+ warnings.warn(f"Attempt {i + 1} to connect to textbook site failed, trying again")
+ if base_response.status_code != 200: # Request failed too many times
+ raise ConnectionError(f"Failed to connect to textbook site after {MAX_REQUEST_ATTEMPTS} attempts")
+
+ elements = base_response.html.find("meta")
+ assert isinstance(elements, list)
+ for element in elements:
+ if element.attrs.get("name") == "csrf-token":
+ csrf_token: str = element.attrs["content"]
+ global request_headers
+ request_headers = {"X-CSRF-Token": csrf_token}
+ return
+ raise ConnectionError("Unable to find valid request credentials, cannot connect to textbook site")
+
+
+def _update_subject_map() -> None:
+ if not request_headers:
+ _update_headers()
+
+ for i in range(MAX_REQUEST_ATTEMPTS):
+ subject_response = sess.get(SUBJECTS_URL.format(term_id=CURRENT_TERM_ID), headers=request_headers)
+ if subject_response.status_code == 200:
+ break
+ warnings.warn(f"Attempt {i + 1} to retrieve list of subjects failed, trying again")
+ _update_headers() # Try again with new CSRF token
+ if subject_response.status_code != 200: # Request failed too many times
+ raise ConnectionError(f"Failed to retrieve list of subjects after {MAX_REQUEST_ATTEMPTS} attempts")
+
+ subject_json: list[dict[str, str]] = subject_response.json()
+ global subject_map
+ subject_map = {entry["name"]: entry["id"] for entry in subject_json}
+
+
+def _find_section_from_json(sections: list[dict[str, str]], instructor: str | None, section_num: str | None) -> str:
+ if section_num:
+ for section in sections:
+ if section["name"] == section_num:
+ return section["id"]
+ raise LookupError(f"No section found with given {section_num=}")
+ if instructor:
+ for section in sections:
+ if section["instructor"] == instructor:
+ return section["id"]
+ raise LookupError(f"No section found with given {instructor=}")
+
+ # Not enough info provided, so try to deduce the correct section:
+ # - If there's only 1 section of the course, then the sole section must be the correct one
+ # - If all sections of the course are taught by the same instructor, then we can assume that all sections will have the
+ # same textbook, meaning that the exact section doesn't matter
+ instructors = {section["instructor"] for section in sections}
+ if len(sections) == 1 or len(instructors) == 1:
+ return sections[0]["id"]
+ raise LookupError(
+ "Cannot determine section ID from given arguments, please provide the instructor's name and/or the section number"
+ )
-def _extract_id(response, course: str, instructor: str, section: str) -> str:
- """Gathers sections from departments and finds course id by
- instructor name or section number.
- """
- sections = _find_sections(response.json(), course)
- error = 0
- try:
- if section is not None:
- return _find_course_id_by_section(sections, section)
- except LookupError:
- error += 1
- try:
- if instructor is not None:
- return _find_course_id_by_instructor(sections, instructor.upper())
- except LookupError:
- error += 2
- raise LookupError("Unable to find course by " + LOOKUP_ERRORS[error].format(section, instructor))
-
-
-def _extract_books(ids: list[str]) -> list[dict[str, str]]:
+def _get_textbooks_for_ids(ids: list[str]) -> list[Textbook]:
"""Fetches a course's textbook information and returns a list
of textbooks for the given course.
"""
- responses = grequests.imap([grequests.get(BASE_URL + _construct_query("books", section_id)) for section_id in ids])
- books = [_filter_dictionary(book, KEYS) for response in responses for book in response.json()]
- return books
-
-
-# Meant to force a return of None instead of raising a KeyError
-# when using a nonexistent key
-class DefaultDict(dict):
- def __missing__(self, key):
- return None
-
-
-def _fetch_course(
- courses: list[dict[str, str]], departments: dict[str, str]
-) -> Generator[tuple[str, str, str, str], None, None]:
- """Generator for fetching a courses information in order"""
- for course in courses:
- course = DefaultDict(course)
- yield (
- departments[course["department"]],
- course["department"] + _validate_course(course["course"]),
- course["instructor"],
- course["section"],
+ if not request_headers:
+ _update_headers()
+
+ responses = grequests.imap(grequests.get(BOOKS_URL.format(section_id=id), headers=request_headers) for id in ids)
+ books = []
+ for response in responses:
+ for book_json in response.json():
+ book = Textbook.from_json(book_json)
+ if book:
+ books.append(book)
+ else:
+ warnings.warn(f"No textbook info found for {response}")
+ return [book for book in books if book] # Drop all None values
+
+
+def _get_textbooks_from_json(
+ course_json: list[dict[str, Any]], subject: str, course_num: str, instructor: str | None, section_num: str | None
+) -> list[Textbook]:
+ for course in course_json:
+ if course["id"] == subject + course_num:
+ section_id = _find_section_from_json(course["sections"], instructor, section_num)
+ return _get_textbooks_for_ids([section_id])
+ raise LookupError(f"{subject} {course_num} is not a valid course")
+
+
+def get_textbooks_for_course(course: CourseInfo) -> list[Textbook]:
+ if not request_headers:
+ _update_headers()
+ if not subject_map:
+ _update_subject_map()
+ assert subject_map
+
+ for i in range(MAX_REQUEST_ATTEMPTS):
+ course_response = sess.get(
+ COURSES_URL.format(dept_id=subject_map[course.subject], term_id=CURRENT_TERM_ID), headers=request_headers
)
+ if course_response.status_code == 200:
+ break
+ warnings.warn(f"Attempt {i} to retrieve list of {course.subject} courses failed, trying again")
+ _update_headers() # Try again with new CSRF token
+ if course_response.status_code != 200: # Request failed too many times
+ raise ConnectionError(f"Failed to retrieve list of {course.subject} courses from textbook site")
+
+ return _get_textbooks_from_json(
+ course_json=course_response.json(),
+ subject=course.subject,
+ course_num=course.course_num,
+ instructor=course.instructor,
+ section_num=course.section_num,
+ )
-def _get_department_number(department_code: str) -> int:
- """Temporary solution to finding a department.
- There will be a new method to getting department information
- at a later time.
- """
- department_number = CODES.index(department_code) + 22399
- if department_number > 22462:
- department_number += 2 # between codes DSANE and EAS 2 id numbers are skipped.
- if department_number > 22580:
- department_number += 1 # between codes PUBSRV and REHSCI 1 id number is skipped.
- return department_number
-
-
-def get_textbooks(term: str, courses: list[dict[str, str]]) -> list[dict[str, str]]:
- """Retrieves textbooks for multiple courses in the same term."""
- departments = {course["department"] for course in courses}
- responses = grequests.map(
- [
- grequests.get(
- BASE_URL + _construct_query("courses", _get_department_number(department), _validate_term(term)),
- timeout=10,
+def get_textbooks_for_courses(courses_info: list[CourseInfo]) -> list[Textbook]:
+ if not request_headers:
+ _update_headers()
+ if not subject_map:
+ _update_subject_map()
+ assert subject_map
+
+ # Precompute list of unique subjects to avoid unnecessary API requests
+ subjects = {course_info.subject for course_info in courses_info}
+ courses_for_subjects: dict[str, list[dict[str, Any]]] = {}
+ for subject in subjects:
+ for i in range(MAX_REQUEST_ATTEMPTS):
+ course_response = sess.get(
+ COURSES_URL.format(dept_id=subject_map[subject], term_id=CURRENT_TERM_ID), headers=request_headers
+ )
+ if course_response.status_code == 200:
+ break
+ warnings.warn(f"Attempt {i} to retrieve list {subject} courses failed, trying again")
+ _update_headers() # Try again with new CSRF token
+ if course_response.status_code != 200: # Request failed too many times
+ raise ConnectionError(f"Failed to retrieve list of {subject} courses from textbook site")
+
+ courses_for_subjects[subject] = course_response.json()
+
+ textbooks = []
+ for course_info in courses_info:
+ course_json = courses_for_subjects[course_info.subject]
+ textbooks.extend(
+ _get_textbooks_from_json(
+ course_json=course_json,
+ subject=course_info.subject,
+ course_num=course_info.course_num,
+ instructor=course_info.instructor,
+ section_num=course_info.section_num,
)
- for department in departments
- ]
- )
- section_ids = [
- _extract_id(*course)
- for course in _fetch_course(
- courses,
- dict(
- zip(
- sorted(departments),
- sorted(responses, key=lambda resp: resp.json()[0]["name"]),
- )
- ),
)
- ]
- return _extract_books(section_ids)
-
-
-def get_textbook(term: str, department: str, course: str, instructor: str = None, section: str = None) -> list[dict[str, str]]:
- """Retrieves textbooks for a given course."""
- has_section_or_instructor = (instructor is not None) or (section is not None)
- if not has_section_or_instructor:
- raise TypeError("get_textbook() is missing a instructor or section argument")
- response = requests.get(BASE_URL + _construct_query("courses", _get_department_number(department), _validate_term(term)))
- section_id = _extract_id(response, department + _validate_course(course), instructor, section)
- return _extract_books([section_id])
+ return textbooks
diff --git a/tests/samples/textbook_base_page.html b/tests/samples/textbook_base_page.html
new file mode 100644
index 0000000..7c342b8
--- /dev/null
+++ b/tests/samples/textbook_base_page.html
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+Compare Textbook Prices Online | University Store on Fifth
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/samples/textbook_courses_CS.json b/tests/samples/textbook_courses_CS.json
index 0ddfbc2..c19c409 100644
--- a/tests/samples/textbook_courses_CS.json
+++ b/tests/samples/textbook_courses_CS.json
@@ -1 +1,1594 @@
-[{"name":"CS 0004","id":"CS0004","sections":[{"id":"2096252","name":"1070","instructor":"DEVINE"},{"id":"2096253","name":"7010","instructor":"COSTANTINO"}]},{"name":"CS 0007","id":"CS0007","sections":[{"id":"2096254","name":"1050","instructor":"DELANEY"},{"id":"2096255","name":"1060","instructor":null},{"id":"2096256","name":"1090","instructor":null},{"id":"2096257","name":"1100","instructor":"DELANEY"},{"id":"2096258","name":"1105","instructor":null},{"id":"2096259","name":"1110","instructor":null},{"id":"2096260","name":"1200","instructor":"FILIPSKI"},{"id":"2096261","name":"1210","instructor":null},{"id":"2096262","name":"1220","instructor":null},{"id":"2096263","name":"1250","instructor":"FILIPSKI"},{"id":"2096264","name":"1255","instructor":null},{"id":"2096265","name":"1260","instructor":null},{"id":"2096266","name":"1300","instructor":"KREBS"},{"id":"2096267","name":"1310","instructor":"KREBS"},{"id":"2096268","name":"1320","instructor":"KREBS"}]},{"name":"CS 0008","id":"CS0008","sections":[{"id":"2096269","name":"1020","instructor":"MOHAMED KHATTAB"},{"id":"2096270","name":"1030","instructor":null},{"id":"2096271","name":"1040","instructor":null},{"id":"2096272","name":"1230","instructor":"NOVACKY"},{"id":"2096273","name":"1235","instructor":null},{"id":"2096274","name":"1240","instructor":null},{"id":"2096275","name":"1300","instructor":"PRODONOVICH"},{"id":"2096276","name":"1310","instructor":null},{"id":"2096277","name":"1320","instructor":null}]},{"name":"CS 0090","id":"CS0090","sections":[{"id":"2096278","name":"1040","instructor":"DEVINE"}]},{"name":"CS 0134","id":"CS0134","sections":[{"id":"2096279","name":"1060","instructor":"CHANG"},{"id":"2096280","name":"1200","instructor":"BILLINGSLEY"}]},{"name":"CS 0334","id":"CS0334","sections":[{"id":"2096281","name":"1020","instructor":"DENARDIS"}]},{"name":"CS 0401","id":"CS0401","sections":[{"id":"2096282","name":"1030","instructor":"HOFFMAN"},{"id":"2096283","name":"1035","instructor":null},{"id":"2096284","name":"1040","instructor":null},{"id":"2096285","name":"1045","instructor":"HOFFMAN"},{"id":"2096286","name":"1060","instructor":"RAMIREZ"},{"id":"2096287","name":"1070","instructor":null},{"id":"2096288","name":"1080","instructor":null},{"id":"2096289","name":"1090","instructor":null},{"id":"2096290","name":"1100","instructor":"RAMIREZ"},{"id":"2096291","name":"1105","instructor":null},{"id":"2096292","name":"1110","instructor":null},{"id":"2096293","name":"1115","instructor":null},{"id":"2096294","name":"1200","instructor":"HOFFMAN"},{"id":"2096295","name":"1210","instructor":null},{"id":"2096296","name":"1220","instructor":"HOFFMAN"},{"id":"2096297","name":"1300","instructor":"DICK"},{"id":"2096298","name":"1310","instructor":null},{"id":"2096299","name":"1315","instructor":null},{"id":"2096300","name":"1320","instructor":"HOFFMAN"},{"id":"2096301","name":"1325","instructor":null},{"id":"2096302","name":"1330","instructor":null}]},{"name":"CS 0441","id":"CS0441","sections":[{"id":"2096303","name":"1070","instructor":"BONIDIE"},{"id":"2096304","name":"1075","instructor":null},{"id":"2096305","name":"1080","instructor":null},{"id":"2096306","name":"1085","instructor":null},{"id":"2096307","name":"1090","instructor":"BONIDIE"},{"id":"2096308","name":"1095","instructor":null},{"id":"2096309","name":"1100","instructor":null},{"id":"2096310","name":"1101","instructor":null},{"id":"2096311","name":"1110","instructor":"GARRISON III"},{"id":"2096312","name":"1120","instructor":null},{"id":"2096313","name":"1130","instructor":null},{"id":"2096314","name":"1135","instructor":null},{"id":"2096315","name":"1200","instructor":"GARRISON III"},{"id":"2096316","name":"1210","instructor":null},{"id":"2096317","name":"1220","instructor":null},{"id":"2198583","name":"1230","instructor":"GARRISON III"}]},{"name":"CS 0445","id":"CS0445","sections":[{"id":"2096318","name":"1030","instructor":"GARRISON III"},{"id":"2096319","name":"1035","instructor":null},{"id":"2096320","name":"1040","instructor":null},{"id":"2096321","name":"1045","instructor":null},{"id":"2096322","name":"1100","instructor":"GARRISON III"},{"id":"2096323","name":"1105","instructor":null},{"id":"2096324","name":"1110","instructor":null},{"id":"2096325","name":"1115","instructor":null},{"id":"2096326","name":"1200","instructor":"MOHAMED KHATTAB"},{"id":"2096327","name":"1205","instructor":null},{"id":"2096328","name":"1210","instructor":null},{"id":"2096329","name":"1220","instructor":null},{"id":"2106366","name":"1240","instructor":"JAMES"},{"id":"2106367","name":"1242","instructor":null},{"id":"2106368","name":"1244","instructor":null}]},{"name":"CS 0447","id":"CS0447","sections":[{"id":"2096330","name":"1070","instructor":"KOSIYATRAKUL"},{"id":"2096331","name":"1080","instructor":null},{"id":"2096332","name":"1090","instructor":null},{"id":"2096333","name":"1095","instructor":null},{"id":"2096334","name":"1120","instructor":"BILLINGSLEY"},{"id":"2096335","name":"1130","instructor":null},{"id":"2096336","name":"1140","instructor":null},{"id":"2096337","name":"1145","instructor":null},{"id":"2096338","name":"1200","instructor":"ZHANG"},{"id":"2096339","name":"1205","instructor":null},{"id":"2096340","name":"1210","instructor":null},{"id":"2198983","name":"1215","instructor":"KOSIYATRAKUL"}]},{"name":"CS 0449","id":"CS0449","sections":[{"id":"2096341","name":"1070","instructor":"AHN"},{"id":"2096342","name":"1080","instructor":null},{"id":"2096343","name":"1090","instructor":null},{"id":"2096344","name":"1095","instructor":null},{"id":"2096345","name":"1110","instructor":"AHN"},{"id":"2096346","name":"1120","instructor":null},{"id":"2096347","name":"1125","instructor":null},{"id":"2096348","name":"1130","instructor":null},{"id":"2096349","name":"1135","instructor":"MISURDA"},{"id":"2096350","name":"1140","instructor":null},{"id":"2096351","name":"1145","instructor":null}]},{"name":"CS 0590","id":"CS0590","sections":[{"id":"2096352","name":"1020","instructor":"QUIRIN"},{"id":"2096353","name":"1040","instructor":"BRANDON"}]},{"name":"CS 0699","id":"CS0699","sections":[{"id":"2096354","name":"1350","instructor":"FRANOLICH"}]},{"name":"CS 1501","id":"CS1501","sections":[{"id":"2096355","name":"1040","instructor":"FARNAN"},{"id":"2096356","name":"1050","instructor":null},{"id":"2096357","name":"1055","instructor":null},{"id":"2096358","name":"1060","instructor":"FARNAN"},{"id":"2096359","name":"1065","instructor":null},{"id":"2096360","name":"1075","instructor":null},{"id":"2096361","name":"1150","instructor":"FARNAN"},{"id":"2096362","name":"1170","instructor":"FARNAN"},{"id":"2096363","name":"1172","instructor":null},{"id":"2096364","name":"1174","instructor":null},{"id":"2096365","name":"1190","instructor":null},{"id":"2096366","name":"1195","instructor":null},{"id":"2096367","name":"1200","instructor":"WEHAR"},{"id":"2096368","name":"1205","instructor":null},{"id":"2096369","name":"1210","instructor":null},{"id":"2096370","name":"1250","instructor":"WEHAR"},{"id":"2096371","name":"1255","instructor":null},{"id":"2096372","name":"1260","instructor":null},{"id":"2096373","name":"1270","instructor":"FARNAN"},{"id":"2096374","name":"1272","instructor":null},{"id":"2096375","name":"1275","instructor":null},{"id":"2096376","name":"1280","instructor":"FARNAN"},{"id":"2096377","name":"1282","instructor":null},{"id":"2096378","name":"1285","instructor":null},{"id":"2096379","name":"1300","instructor":"FARNAN"},{"id":"2096380","name":"1305","instructor":"FARNAN"},{"id":"2096381","name":"1310","instructor":"ZHANG"},{"id":"2096382","name":"1315","instructor":"ZHANG"},{"id":"2096383","name":"1320","instructor":"ZHANG"},{"id":"2096384","name":"1330","instructor":"ZHANG"},{"id":"2096385","name":"1400","instructor":"WEHAR"},{"id":"2096386","name":"1405","instructor":null},{"id":"2096387","name":"1406","instructor":null},{"id":"2096388","name":"1410","instructor":"WEHAR"},{"id":"2096389","name":"1415","instructor":null},{"id":"2096390","name":"1420","instructor":null},{"id":"2198744","name":"ALL SECTIONS","instructor":"ALL INSTRUCTORS"}]},{"name":"CS 1502","id":"CS1502","sections":[{"id":"2096391","name":"1070","instructor":"MOHAMED KHATTAB"},{"id":"2096392","name":"1075","instructor":null},{"id":"2096393","name":"1080","instructor":null},{"id":"2096394","name":"1085","instructor":null},{"id":"2096395","name":"1200","instructor":"KOSIYATRAKUL"},{"id":"2096396","name":"1210","instructor":null},{"id":"2096397","name":"1220","instructor":null}]},{"name":"CS 1510","id":"CS1510","sections":[{"id":"2096398","name":"1040","instructor":"PRUHS"},{"id":"2096399","name":"1050","instructor":null}]},{"name":"CS 1520","id":"CS1520","sections":[{"id":"2096400","name":"1100","instructor":"BOWYTZ"},{"id":"2096401","name":"1105","instructor":null},{"id":"2096402","name":"1110","instructor":null},{"id":"2106419","name":"1050","instructor":null},{"id":"2106420","name":"1055","instructor":null},{"id":"2106421","name":"1060","instructor":null}]},{"name":"CS 1530","id":"CS1530","sections":[{"id":"2096403","name":"1070","instructor":"LABOON"},{"id":"2096404","name":"1080","instructor":"LABOON"}]},{"name":"CS 1541","id":"CS1541","sections":[{"id":"2096405","name":"1070","instructor":"MELHEM"}]},{"name":"CS 1550","id":"CS1550","sections":[{"id":"2096406","name":"1040","instructor":"MISURDA"},{"id":"2096407","name":"1050","instructor":null},{"id":"2096408","name":"1060","instructor":null},{"id":"2096409","name":"1080","instructor":"MISURDA"},{"id":"2096410","name":"1085","instructor":null},{"id":"2096411","name":"1090","instructor":null}]},{"name":"CS 1555","id":"CS1555","sections":[{"id":"2096412","name":"1100","instructor":"CHRYSANTHIS"},{"id":"2096413","name":"1110","instructor":null},{"id":"2096414","name":"1120","instructor":null}]},{"name":"CS 1566","id":"CS1566","sections":[{"id":"2096415","name":"1100","instructor":"KOSIYATRAKUL"},{"id":"2096416","name":"1105","instructor":null},{"id":"2096417","name":"1110","instructor":null}]},{"name":"CS 1571","id":"CS1571","sections":[{"id":"2096418","name":"1070","instructor":"LITMAN"}]},{"name":"CS 1621","id":"CS1621","sections":[{"id":"2096419","name":"1270","instructor":"ZHANG"}]},{"name":"CS 1622","id":"CS1622","sections":[{"id":"2096420","name":"1010","instructor":"MISURDA"}]},{"name":"CS 1632","id":"CS1632","sections":[{"id":"2096421","name":"1270","instructor":"LABOON"}]},{"name":"CS 1640","id":"CS1640","sections":[{"id":"2096422","name":"1050","instructor":"LABOON"}]},{"name":"CS 1645","id":"CS1645","sections":[{"id":"2096423","name":"1020","instructor":"BIGRIGG"}]},{"name":"CS 1652","id":"CS1652","sections":[{"id":"2096424","name":"1300","instructor":"LANGE"}]},{"name":"CS 1653","id":"CS1653","sections":[{"id":"2096425","name":"1140","instructor":"LEE"}]},{"name":"CS 1656","id":"CS1656","sections":[{"id":"2096426","name":"1130","instructor":"LABRINIDIS"},{"id":"2096427","name":"1140","instructor":null},{"id":"2096428","name":"1145","instructor":null}]},{"name":"CS 1675","id":"CS1675","sections":[{"id":"2096429","name":"1160","instructor":"HAUSKRECHT"}]},{"name":"CS 1699","id":"CS1699","sections":[{"id":"2096430","name":"1280","instructor":"ZNATI"}]},{"name":"CS 1900","id":"CS1900","sections":[{"id":"2096431","name":"1010","instructor":"LABOON"}]},{"name":"CS 1902","id":"CS1902","sections":[{"id":"2096432","name":"1050","instructor":"RAMIREZ"}]},{"name":"CS 1950","id":"CS1950","sections":[{"id":"2096433","name":"1010","instructor":"LABOON"},{"id":"2096434","name":"1020","instructor":"PRUHS"}]},{"name":"CS 1980","id":"CS1980","sections":[{"id":"2096435","name":"1200","instructor":"LABOON"}]},{"name":"CS 1981","id":"CS1981","sections":[{"id":"2096436","name":"1050","instructor":"LABOON"}]},{"name":"CS 2000","id":"CS2000","sections":[{"id":"2096437","name":"1010","instructor":"CHANG"},{"id":"2096438","name":"1020","instructor":null},{"id":"2096439","name":"1030","instructor":"CHILDERS"},{"id":"2096440","name":"1040","instructor":"AHN"},{"id":"2096441","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096442","name":"1060","instructor":null},{"id":"2096443","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096444","name":"1080","instructor":"HWA"},{"id":"2096445","name":"1090","instructor":"LABRINIDIS"},{"id":"2096446","name":"1100","instructor":"LEE"},{"id":"2096447","name":"1110","instructor":"LITMAN"},{"id":"2096448","name":"1120","instructor":"KOVASHKA"},{"id":"2096449","name":"1130","instructor":"MELHEM"},{"id":"2096450","name":"1140","instructor":"MOSSE"},{"id":"2096451","name":"1150","instructor":"PRUHS"},{"id":"2096452","name":"1160","instructor":"WIEBE"},{"id":"2096453","name":"1170","instructor":"ZHANG"},{"id":"2096454","name":"1180","instructor":"ZNATI"},{"id":"2096455","name":"1190","instructor":"WANG"},{"id":"2096456","name":"1200","instructor":"LANGE"}]},{"name":"CS 2001","id":"CS2001","sections":[{"id":"2096457","name":"1040","instructor":"ZNATI"}]},{"name":"CS 2003","id":"CS2003","sections":[{"id":"2096458","name":"1060","instructor":"LEE"}]},{"name":"CS 2012","id":"CS2012","sections":[{"id":"2096459","name":"1040","instructor":"PRUHS"},{"id":"2096460","name":"1050","instructor":null}]},{"name":"CS 2045","id":"CS2045","sections":[{"id":"2096461","name":"1020","instructor":"BIGRIGG"}]},{"name":"CS 2053","id":"CS2053","sections":[{"id":"2132958","name":"1140","instructor":"LEE"}]},{"name":"CS 2055","id":"CS2055","sections":[{"id":"2096462","name":"1100","instructor":"CHRYSANTHIS"},{"id":"2096463","name":"1110","instructor":null},{"id":"2096464","name":"1120","instructor":null}]},{"name":"CS 2310","id":"CS2310","sections":[{"id":"2096465","name":"1020","instructor":"CHANG"}]},{"name":"CS 2410","id":"CS2410","sections":[{"id":"2096466","name":"1040","instructor":"CHILDERS"}]},{"name":"CS 2510","id":"CS2510","sections":[{"id":"2096467","name":"1040","instructor":"LANGE"}]},{"name":"CS 2610","id":"CS2610","sections":[{"id":"2096468","name":"1140","instructor":"WANG"}]},{"name":"CS 2710","id":"CS2710","sections":[{"id":"2096469","name":"1050","instructor":"HAUSKRECHT"}]},{"name":"CS 2900","id":"CS2900","sections":[{"id":"2096470","name":"1010","instructor":"CHANG"},{"id":"2096471","name":"1020","instructor":null},{"id":"2096472","name":"1030","instructor":"CHILDERS"},{"id":"2096473","name":"1040","instructor":"AHN"},{"id":"2096474","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096475","name":"1060","instructor":null},{"id":"2096476","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096477","name":"1080","instructor":"HWA"},{"id":"2096478","name":"1090","instructor":"LABRINIDIS"},{"id":"2096479","name":"1100","instructor":"LEE"},{"id":"2096480","name":"1110","instructor":"LITMAN"},{"id":"2096481","name":"1120","instructor":"KOVASHKA"},{"id":"2096482","name":"1130","instructor":"MELHEM"},{"id":"2096483","name":"1140","instructor":"MOSSE"},{"id":"2096484","name":"1150","instructor":"PRUHS"},{"id":"2096485","name":"1160","instructor":"WIEBE"},{"id":"2096486","name":"1170","instructor":"ZHANG"},{"id":"2096487","name":"1180","instructor":"ZNATI"},{"id":"2096488","name":"1190","instructor":"LANGE"},{"id":"2096489","name":"1200","instructor":"WANG"}]},{"name":"CS 2910","id":"CS2910","sections":[{"id":"2096490","name":"1010","instructor":"CHANG"},{"id":"2096491","name":"1020","instructor":null},{"id":"2096492","name":"1030","instructor":"CHILDERS"},{"id":"2096493","name":"1040","instructor":"AHN"},{"id":"2096494","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096495","name":"1060","instructor":null},{"id":"2096496","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096497","name":"1080","instructor":"HWA"},{"id":"2096498","name":"1090","instructor":"LABRINIDIS"},{"id":"2096499","name":"1100","instructor":"LEE"},{"id":"2096500","name":"1110","instructor":"LITMAN"},{"id":"2096501","name":"1120","instructor":"KOVASHKA"},{"id":"2096502","name":"1130","instructor":"MELHEM"},{"id":"2096503","name":"1140","instructor":"MOSSE"},{"id":"2096504","name":"1150","instructor":"PRUHS"},{"id":"2096505","name":"1160","instructor":"WIEBE"},{"id":"2096506","name":"1170","instructor":"ZHANG"},{"id":"2096507","name":"1180","instructor":"ZNATI"},{"id":"2096508","name":"1190","instructor":"WANG"},{"id":"2096509","name":"1200","instructor":"LANGE"}]},{"name":"CS 2990","id":"CS2990","sections":[{"id":"2096510","name":"1010","instructor":"CHANG"},{"id":"2096511","name":"1015","instructor":"WIEBE"},{"id":"2096512","name":"1020","instructor":null},{"id":"2096513","name":"1030","instructor":"CHILDERS"},{"id":"2096514","name":"1040","instructor":"AHN"},{"id":"2096515","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096516","name":"1060","instructor":null},{"id":"2096517","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096518","name":"1080","instructor":"HWA"},{"id":"2096519","name":"1090","instructor":"LABRINIDIS"},{"id":"2096520","name":"1100","instructor":"LEE"},{"id":"2096521","name":"1110","instructor":"LITMAN"},{"id":"2096522","name":"1120","instructor":"KOVASHKA"},{"id":"2096523","name":"1130","instructor":"MELHEM"},{"id":"2096524","name":"1140","instructor":"MOSSE"},{"id":"2096525","name":"1150","instructor":"PRUHS"},{"id":"2096526","name":"1160","instructor":"ZHANG"},{"id":"2096527","name":"1170","instructor":"ZNATI"},{"id":"2096528","name":"1180","instructor":"WANG"},{"id":"2096529","name":"1190","instructor":"LANGE"},{"id":"2096530","name":"1200","instructor":null}]},{"name":"CS 3000","id":"CS3000","sections":[{"id":"2096531","name":"1010","instructor":"CHANG"},{"id":"2096532","name":"1020","instructor":null},{"id":"2096533","name":"1030","instructor":"CHILDERS"},{"id":"2096534","name":"1040","instructor":"AHN"},{"id":"2096535","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096536","name":"1060","instructor":null},{"id":"2096537","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096538","name":"1080","instructor":"HWA"},{"id":"2096539","name":"1090","instructor":"LABRINIDIS"},{"id":"2096540","name":"1100","instructor":"LEE"},{"id":"2096541","name":"1110","instructor":"LITMAN"},{"id":"2096542","name":"1120","instructor":"KOVASHKA"},{"id":"2096543","name":"1130","instructor":"MELHEM"},{"id":"2096544","name":"1140","instructor":"MOSSE"},{"id":"2096545","name":"1150","instructor":"PRUHS"},{"id":"2096546","name":"1160","instructor":"WIEBE"},{"id":"2096547","name":"1170","instructor":"ZHANG"},{"id":"2096548","name":"1180","instructor":"ZNATI"},{"id":"2096549","name":"1190","instructor":"LANGE"},{"id":"2096550","name":"1200","instructor":"WANG"}]},{"name":"CS 3580","id":"CS3580","sections":[{"id":"2096551","name":"1010","instructor":"MELHEM"}]},{"name":"CS 3900","id":"CS3900","sections":[{"id":"2096552","name":"1010","instructor":"CHANG"},{"id":"2096553","name":"1020","instructor":null},{"id":"2096554","name":"1030","instructor":"CHILDERS"},{"id":"2096555","name":"1040","instructor":"AHN"},{"id":"2096556","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096557","name":"1060","instructor":null},{"id":"2096558","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096559","name":"1080","instructor":"HWA"},{"id":"2096560","name":"1090","instructor":"LABRINIDIS"},{"id":"2096561","name":"1100","instructor":"LEE"},{"id":"2096562","name":"1110","instructor":"LITMAN"},{"id":"2096563","name":"1120","instructor":"KOVASHKA"},{"id":"2096564","name":"1130","instructor":"MELHEM"},{"id":"2096565","name":"1140","instructor":"MOSSE"},{"id":"2096566","name":"1150","instructor":"PRUHS"},{"id":"2096567","name":"1160","instructor":"WIEBE"},{"id":"2096568","name":"1170","instructor":"ZHANG"},{"id":"2096569","name":"1180","instructor":"ZNATI"},{"id":"2096570","name":"1190","instructor":"LANGE"},{"id":"2096571","name":"1200","instructor":"WANG"}]}]
+[
+ {
+ "name": "CS 0007",
+ "id": "CS0007",
+ "sections": [
+ {
+ "id": "4558013",
+ "name": "1050",
+ "instructor": "HOFFMAN"
+ },
+ {
+ "id": "4558014",
+ "name": "1061",
+ "instructor": "DEVINE"
+ },
+ {
+ "id": "4558015",
+ "name": "1100",
+ "instructor": "BONIDIE"
+ },
+ {
+ "id": "4558016",
+ "name": "1200",
+ "instructor": "DEVINE"
+ },
+ {
+ "id": "4558017",
+ "name": "1250",
+ "instructor": "HOFFMAN"
+ },
+ {
+ "id": "4558018",
+ "name": "1300",
+ "instructor": "XU"
+ }
+ ]
+ },
+ {
+ "name": "CS 0011",
+ "id": "CS0011",
+ "sections": [
+ {
+ "id": "4558019",
+ "name": "1000",
+ "instructor": "DE LIMA BARBOSA"
+ },
+ {
+ "id": "4558020",
+ "name": "1010",
+ "instructor": "HEDGES"
+ },
+ {
+ "id": "4558021",
+ "name": "1035",
+ "instructor": "SHARMA"
+ }
+ ]
+ },
+ {
+ "name": "CS 0012",
+ "id": "CS0012",
+ "sections": [
+ {
+ "id": "4558022",
+ "name": "1005",
+ "instructor": "DE LIMA BARBOSA"
+ },
+ {
+ "id": "4558023",
+ "name": "1010",
+ "instructor": "DENARDIS"
+ },
+ {
+ "id": "4558024",
+ "name": "8020",
+ "instructor": "YURASITS"
+ }
+ ]
+ },
+ {
+ "name": "CS 0134",
+ "id": "CS0134",
+ "sections": [
+ {
+ "id": "4558025",
+ "name": "1060",
+ "instructor": "DENARDIS"
+ },
+ {
+ "id": "4558026",
+ "name": "8085",
+ "instructor": "LAYE"
+ },
+ {
+ "id": "4558027",
+ "name": "8155",
+ "instructor": "SOLTER"
+ }
+ ]
+ },
+ {
+ "name": "CS 0207",
+ "id": "CS0207",
+ "sections": [
+ {
+ "id": "4558028",
+ "name": "1100",
+ "instructor": "RAMIREZ"
+ }
+ ]
+ },
+ {
+ "name": "CS 0441",
+ "id": "CS0441",
+ "sections": [
+ {
+ "id": "4558029",
+ "name": "1090",
+ "instructor": "BONIDIE"
+ },
+ {
+ "id": "4558030",
+ "name": "1230",
+ "instructor": "FARNAN"
+ },
+ {
+ "id": "4558031",
+ "name": "1245",
+ "instructor": "GARRISON III"
+ },
+ {
+ "id": "4558032",
+ "name": "1250",
+ "instructor": "MURRUGARRA LLERENA"
+ },
+ {
+ "id": "4637596",
+ "name": "1150",
+ "instructor": "MURRUGARRA LLERENA"
+ }
+ ]
+ },
+ {
+ "name": "CS 0445",
+ "id": "CS0445",
+ "sections": [
+ {
+ "id": "4558033",
+ "name": "1100",
+ "instructor": "BARSKY"
+ },
+ {
+ "id": "4558034",
+ "name": "1200",
+ "instructor": "MOHAMED KHATTAB"
+ },
+ {
+ "id": "4558035",
+ "name": "1400",
+ "instructor": "ELLIS"
+ },
+ {
+ "id": "4637597",
+ "name": "1240",
+ "instructor": "JAMES"
+ }
+ ]
+ },
+ {
+ "name": "CS 0447",
+ "id": "CS0447",
+ "sections": [
+ {
+ "id": "4558036",
+ "name": "1070",
+ "instructor": "BILLINGSLEY"
+ },
+ {
+ "id": "4558037",
+ "name": "1200",
+ "instructor": "MOHAMED KHATTAB"
+ },
+ {
+ "id": "4637598",
+ "name": "1215",
+ "instructor": "BILLINGSLEY"
+ },
+ {
+ "id": "4637599",
+ "name": "1400",
+ "instructor": "BILLINGSLEY"
+ }
+ ]
+ },
+ {
+ "name": "CS 0449",
+ "id": "CS0449",
+ "sections": [
+ {
+ "id": "4558038",
+ "name": "1110",
+ "instructor": "NUNES QUARESMA DE OL"
+ },
+ {
+ "id": "4558039",
+ "name": "1135",
+ "instructor": "NUNES QUARESMA DE OL"
+ },
+ {
+ "id": "4558040",
+ "name": "1150",
+ "instructor": "NUNES QUARESMA DE OL"
+ }
+ ]
+ },
+ {
+ "name": "CS 0590",
+ "id": "CS0590",
+ "sections": [
+ {
+ "id": "4558041",
+ "name": "1020",
+ "instructor": "QUIRIN"
+ },
+ {
+ "id": "4558042",
+ "name": "1090",
+ "instructor": "QUIRIN"
+ },
+ {
+ "id": "4558043",
+ "name": "1300",
+ "instructor": "QUIRIN"
+ }
+ ]
+ },
+ {
+ "name": "CS 1501",
+ "id": "CS1501",
+ "sections": [
+ {
+ "id": "4558044",
+ "name": "1060",
+ "instructor": "FARNAN"
+ },
+ {
+ "id": "4558045",
+ "name": "1065",
+ "instructor": "MOHAMED KHATTAB"
+ },
+ {
+ "id": "4558046",
+ "name": "1150",
+ "instructor": "MOHAMED KHATTAB"
+ },
+ {
+ "id": "4558047",
+ "name": "1200",
+ "instructor": "FARNAN"
+ }
+ ]
+ },
+ {
+ "name": "CS 1502",
+ "id": "CS1502",
+ "sections": [
+ {
+ "id": "4558048",
+ "name": "1070",
+ "instructor": "KOSIYATRAKUL"
+ },
+ {
+ "id": "4558049",
+ "name": "1200",
+ "instructor": "KOSIYATRAKUL"
+ },
+ {
+ "id": "4558050",
+ "name": "1210",
+ "instructor": "KOSIYATRAKUL"
+ }
+ ]
+ },
+ {
+ "name": "CS 1503",
+ "id": "CS1503",
+ "sections": [
+ {
+ "id": "4558051",
+ "name": "1200",
+ "instructor": "BARSKY"
+ },
+ {
+ "id": "4558052",
+ "name": "1250",
+ "instructor": "BARSKY"
+ }
+ ]
+ },
+ {
+ "name": "CS 1510",
+ "id": "CS1510",
+ "sections": [
+ {
+ "id": "4558053",
+ "name": "1040",
+ "instructor": "PRUHS"
+ },
+ {
+ "id": "4558054",
+ "name": "1050",
+ "instructor": "PRUHS"
+ }
+ ]
+ },
+ {
+ "name": "CS 1520",
+ "id": "CS1520",
+ "sections": [
+ {
+ "id": "4558055",
+ "name": "1050",
+ "instructor": "FERREIRA"
+ },
+ {
+ "id": "4558056",
+ "name": "1100",
+ "instructor": "FERREIRA"
+ }
+ ]
+ },
+ {
+ "name": "CS 1530",
+ "id": "CS1530",
+ "sections": [
+ {
+ "id": "4558057",
+ "name": "1070",
+ "instructor": "VON FRANKENBERG UND"
+ }
+ ]
+ },
+ {
+ "name": "CS 1550",
+ "id": "CS1550",
+ "sections": [
+ {
+ "id": "4558058",
+ "name": "1040",
+ "instructor": "MISURDA"
+ },
+ {
+ "id": "4558059",
+ "name": "1080",
+ "instructor": "MISURDA"
+ },
+ {
+ "id": "4558060",
+ "name": "1095",
+ "instructor": "MISURDA"
+ }
+ ]
+ },
+ {
+ "name": "CS 1555",
+ "id": "CS1555",
+ "sections": [
+ {
+ "id": "4558061",
+ "name": "1100",
+ "instructor": "NIXON"
+ }
+ ]
+ },
+ {
+ "name": "CS 1566",
+ "id": "CS1566",
+ "sections": [
+ {
+ "id": "4558062",
+ "name": "1100",
+ "instructor": "KOSIYATRAKUL"
+ }
+ ]
+ },
+ {
+ "name": "CS 1571",
+ "id": "CS1571",
+ "sections": [
+ {
+ "id": "4637600",
+ "name": "1010",
+ "instructor": "SINGH"
+ }
+ ]
+ },
+ {
+ "name": "CS 1622",
+ "id": "CS1622",
+ "sections": [
+ {
+ "id": "4558063",
+ "name": "1200",
+ "instructor": "AHN"
+ }
+ ]
+ },
+ {
+ "name": "CS 1632",
+ "id": "CS1632",
+ "sections": [
+ {
+ "id": "4558064",
+ "name": "1080",
+ "instructor": "AHN"
+ },
+ {
+ "id": "4558065",
+ "name": "1270",
+ "instructor": "AHN"
+ }
+ ]
+ },
+ {
+ "name": "CS 1635",
+ "id": "CS1635",
+ "sections": [
+ {
+ "id": "4558066",
+ "name": "1010",
+ "instructor": "BIEHL"
+ }
+ ]
+ },
+ {
+ "name": "CS 1640",
+ "id": "CS1640",
+ "sections": [
+ {
+ "id": "4637601",
+ "name": "1050",
+ "instructor": "JORDAN"
+ }
+ ]
+ },
+ {
+ "name": "CS 1645",
+ "id": "CS1645",
+ "sections": [
+ {
+ "id": "4599605",
+ "name": "1010",
+ "instructor": "TANG"
+ }
+ ]
+ },
+ {
+ "name": "CS 1651",
+ "id": "CS1651",
+ "sections": [
+ {
+ "id": "4558067",
+ "name": "1200",
+ "instructor": "NUNES QUARESMA DE OL"
+ }
+ ]
+ },
+ {
+ "name": "CS 1653",
+ "id": "CS1653",
+ "sections": [
+ {
+ "id": "4558068",
+ "name": "1140",
+ "instructor": "GARRISON III"
+ }
+ ]
+ },
+ {
+ "name": "CS 1656",
+ "id": "CS1656",
+ "sections": [
+ {
+ "id": "4558069",
+ "name": "1130",
+ "instructor": "JIA"
+ },
+ {
+ "id": "4558070",
+ "name": "1200",
+ "instructor": "YU"
+ }
+ ]
+ },
+ {
+ "name": "CS 1660",
+ "id": "CS1660",
+ "sections": [
+ {
+ "id": "4558071",
+ "name": "1300",
+ "instructor": "MAHONEY"
+ }
+ ]
+ },
+ {
+ "name": "CS 1666",
+ "id": "CS1666",
+ "sections": [
+ {
+ "id": "4558072",
+ "name": "1040",
+ "instructor": "FARNAN"
+ }
+ ]
+ },
+ {
+ "name": "CS 1674",
+ "id": "CS1674",
+ "sections": [
+ {
+ "id": "4649455",
+ "name": "1010",
+ "instructor": "MURRUGARRA LLERENA"
+ }
+ ]
+ },
+ {
+ "name": "CS 1675",
+ "id": "CS1675",
+ "sections": [
+ {
+ "id": "4558073",
+ "name": "1100",
+ "instructor": "SKEBA"
+ }
+ ]
+ },
+ {
+ "name": "CS 1678",
+ "id": "CS1678",
+ "sections": [
+ {
+ "id": "4639988",
+ "name": "1000",
+ "instructor": "JORDAN"
+ }
+ ]
+ },
+ {
+ "name": "CS 1684",
+ "id": "CS1684",
+ "sections": [
+ {
+ "id": "4647043",
+ "name": "1010",
+ "instructor": "LIU"
+ }
+ ]
+ },
+ {
+ "name": "CS 1699",
+ "id": "CS1699",
+ "sections": [
+ {
+ "id": "4637602",
+ "name": "1200",
+ "instructor": "SHI"
+ }
+ ]
+ },
+ {
+ "name": "CS 1900",
+ "id": "CS1900",
+ "sections": [
+ {
+ "id": "4558074",
+ "name": "1010",
+ "instructor": "AHN"
+ },
+ {
+ "id": "4558075",
+ "name": "1015",
+ "instructor": "AHN"
+ }
+ ]
+ },
+ {
+ "name": "CS 1901",
+ "id": "CS1901",
+ "sections": [
+ {
+ "id": "4558076",
+ "name": "1300",
+ "instructor": "RAMIREZ"
+ }
+ ]
+ },
+ {
+ "name": "CS 1906",
+ "id": "CS1906",
+ "sections": [
+ {
+ "id": "4558077",
+ "name": "1050",
+ "instructor": "RAMIREZ"
+ }
+ ]
+ },
+ {
+ "name": "CS 1950",
+ "id": "CS1950",
+ "sections": [
+ {
+ "id": "4558078",
+ "name": "1010",
+ "instructor": "AHN"
+ },
+ {
+ "id": "4558079",
+ "name": "1015",
+ "instructor": "AHN"
+ }
+ ]
+ },
+ {
+ "name": "CS 1951",
+ "id": "CS1951",
+ "sections": [
+ {
+ "id": "4558080",
+ "name": "1300",
+ "instructor": "RAMIREZ"
+ }
+ ]
+ },
+ {
+ "name": "CS 1980",
+ "id": "CS1980",
+ "sections": [
+ {
+ "id": "4637603",
+ "name": "1200",
+ "instructor": "JORDAN"
+ }
+ ]
+ },
+ {
+ "name": "CS 2000",
+ "id": "CS2000",
+ "sections": [
+ {
+ "id": "4558081",
+ "name": "1010",
+ "instructor": "WALKER"
+ },
+ {
+ "id": "4558082",
+ "name": "1030",
+ "instructor": "CHILDERS"
+ },
+ {
+ "id": "4558083",
+ "name": "1040",
+ "instructor": "CHRYSANTHIS"
+ },
+ {
+ "id": "4558084",
+ "name": "1050",
+ "instructor": "HAUSKRECHT"
+ },
+ {
+ "id": "4558085",
+ "name": "1060",
+ "instructor": "HWA"
+ },
+ {
+ "id": "4558086",
+ "name": "1070",
+ "instructor": "KOVASHKA"
+ },
+ {
+ "id": "4558087",
+ "name": "1080",
+ "instructor": "LABRINIDIS"
+ },
+ {
+ "id": "4558088",
+ "name": "1090",
+ "instructor": "LANGE"
+ },
+ {
+ "id": "4558089",
+ "name": "1100",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558090",
+ "name": "1110",
+ "instructor": "LITMAN"
+ },
+ {
+ "id": "4558091",
+ "name": "1130",
+ "instructor": "MOSSE"
+ },
+ {
+ "id": "4558092",
+ "name": "1140",
+ "instructor": "PRUHS"
+ },
+ {
+ "id": "4558093",
+ "name": "1170",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4558094",
+ "name": "1200",
+ "instructor": "SHANGGUAN"
+ },
+ {
+ "id": "4558095",
+ "name": "1210",
+ "instructor": "BIEHL"
+ },
+ {
+ "id": "4599606",
+ "name": "1020",
+ "instructor": "LI"
+ },
+ {
+ "id": "4637604",
+ "name": "1220",
+ "instructor": "AHN"
+ },
+ {
+ "id": "4637605",
+ "name": "1230",
+ "instructor": "JIA"
+ }
+ ]
+ },
+ {
+ "name": "CS 2001",
+ "id": "CS2001",
+ "sections": [
+ {
+ "id": "4558096",
+ "name": "1040",
+ "instructor": "BIEHL"
+ }
+ ]
+ },
+ {
+ "name": "CS 2003",
+ "id": "CS2003",
+ "sections": [
+ {
+ "id": "4558097",
+ "name": "1060",
+ "instructor": "BIEHL"
+ }
+ ]
+ },
+ {
+ "name": "CS 2012",
+ "id": "CS2012",
+ "sections": [
+ {
+ "id": "4558098",
+ "name": "1040",
+ "instructor": "PRUHS"
+ },
+ {
+ "id": "4558099",
+ "name": "1050",
+ "instructor": "PRUHS"
+ }
+ ]
+ },
+ {
+ "name": "CS 2035",
+ "id": "CS2035",
+ "sections": [
+ {
+ "id": "4558100",
+ "name": "1200",
+ "instructor": "BIEHL"
+ }
+ ]
+ },
+ {
+ "name": "CS 2045",
+ "id": "CS2045",
+ "sections": [
+ {
+ "id": "4599607",
+ "name": "1010",
+ "instructor": "TANG"
+ }
+ ]
+ },
+ {
+ "name": "CS 2051",
+ "id": "CS2051",
+ "sections": [
+ {
+ "id": "4558101",
+ "name": "1200",
+ "instructor": "NUNES QUARESMA DE OL"
+ }
+ ]
+ },
+ {
+ "name": "CS 2053",
+ "id": "CS2053",
+ "sections": [
+ {
+ "id": "4558102",
+ "name": "1140",
+ "instructor": "GARRISON III"
+ }
+ ]
+ },
+ {
+ "name": "CS 2055",
+ "id": "CS2055",
+ "sections": [
+ {
+ "id": "4558103",
+ "name": "1100",
+ "instructor": "NIXON"
+ }
+ ]
+ },
+ {
+ "name": "CS 2056",
+ "id": "CS2056",
+ "sections": [
+ {
+ "id": "4558104",
+ "name": "1050",
+ "instructor": "JIA"
+ },
+ {
+ "id": "4558105",
+ "name": "1100",
+ "instructor": "JIA"
+ },
+ {
+ "id": "4558106",
+ "name": "1200",
+ "instructor": "YU"
+ }
+ ]
+ },
+ {
+ "name": "CS 2060",
+ "id": "CS2060",
+ "sections": [
+ {
+ "id": "4558107",
+ "name": "1300",
+ "instructor": "MAHONEY"
+ }
+ ]
+ },
+ {
+ "name": "CS 2074",
+ "id": "CS2074",
+ "sections": [
+ {
+ "id": "4649456",
+ "name": "1010",
+ "instructor": "MURRUGARRA LLERENA"
+ }
+ ]
+ },
+ {
+ "name": "CS 2075",
+ "id": "CS2075",
+ "sections": [
+ {
+ "id": "4558108",
+ "name": "1100",
+ "instructor": "SKEBA"
+ }
+ ]
+ },
+ {
+ "name": "CS 2078",
+ "id": "CS2078",
+ "sections": [
+ {
+ "id": "4639989",
+ "name": "1000",
+ "instructor": "JORDAN"
+ }
+ ]
+ },
+ {
+ "name": "CS 2084",
+ "id": "CS2084",
+ "sections": [
+ {
+ "id": "4647044",
+ "name": "1010",
+ "instructor": "LIU"
+ }
+ ]
+ },
+ {
+ "name": "CS 2210",
+ "id": "CS2210",
+ "sections": [
+ {
+ "id": "4558109",
+ "name": "1000",
+ "instructor": "ZHANG"
+ }
+ ]
+ },
+ {
+ "name": "CS 2520",
+ "id": "CS2520",
+ "sections": [
+ {
+ "id": "4558110",
+ "name": "1000",
+ "instructor": "SHANGGUAN"
+ }
+ ]
+ },
+ {
+ "name": "CS 2637",
+ "id": "CS2637",
+ "sections": [
+ {
+ "id": "4558111",
+ "name": "1010",
+ "instructor": "GAUTAM"
+ }
+ ]
+ },
+ {
+ "name": "CS 2710",
+ "id": "CS2710",
+ "sections": [
+ {
+ "id": "4637606",
+ "name": "1050",
+ "instructor": "SKEBA"
+ }
+ ]
+ },
+ {
+ "name": "CS 2731",
+ "id": "CS2731",
+ "sections": [
+ {
+ "id": "4558112",
+ "name": "1210",
+ "instructor": "YODER"
+ }
+ ]
+ },
+ {
+ "name": "CS 2756",
+ "id": "CS2756",
+ "sections": [
+ {
+ "id": "4637607",
+ "name": "1010",
+ "instructor": "JIA"
+ }
+ ]
+ },
+ {
+ "name": "CS 2900",
+ "id": "CS2900",
+ "sections": [
+ {
+ "id": "4558113",
+ "name": "1000",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4558114",
+ "name": "1020",
+ "instructor": "BABAY"
+ },
+ {
+ "id": "4558115",
+ "name": "1030",
+ "instructor": "BIEHL"
+ },
+ {
+ "id": "4558116",
+ "name": "1050",
+ "instructor": "CHILDERS"
+ },
+ {
+ "id": "4558117",
+ "name": "1060",
+ "instructor": "CHRYSANTHIS"
+ },
+ {
+ "id": "4558118",
+ "name": "1070",
+ "instructor": "COHEN"
+ },
+ {
+ "id": "4558119",
+ "name": "1080",
+ "instructor": "HAUSKRECHT"
+ },
+ {
+ "id": "4558120",
+ "name": "1100",
+ "instructor": "SHANGGUAN"
+ },
+ {
+ "id": "4558121",
+ "name": "1110",
+ "instructor": "JIA"
+ },
+ {
+ "id": "4558122",
+ "name": "1120",
+ "instructor": "KOVASHKA"
+ },
+ {
+ "id": "4558123",
+ "name": "1130",
+ "instructor": "LABRINIDIS"
+ },
+ {
+ "id": "4558124",
+ "name": "1170",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558125",
+ "name": "1180",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558126",
+ "name": "1200",
+ "instructor": "LITMAN"
+ },
+ {
+ "id": "4558127",
+ "name": "1210",
+ "instructor": "MOSSE"
+ },
+ {
+ "id": "4558128",
+ "name": "1220",
+ "instructor": "PRUHS"
+ },
+ {
+ "id": "4558129",
+ "name": "1230",
+ "instructor": "TANG"
+ },
+ {
+ "id": "4558130",
+ "name": "1240",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4599608",
+ "name": "1010",
+ "instructor": "LI"
+ },
+ {
+ "id": "4599609",
+ "name": "1040",
+ "instructor": "SHI"
+ },
+ {
+ "id": "4637608",
+ "name": "1090",
+ "instructor": "AHN"
+ },
+ {
+ "id": "4637609",
+ "name": "1140",
+ "instructor": "GAUTAM"
+ }
+ ]
+ },
+ {
+ "name": "CS 2905",
+ "id": "CS2905",
+ "sections": [
+ {
+ "id": "4558131",
+ "name": "1300",
+ "instructor": "BIEHL"
+ }
+ ]
+ },
+ {
+ "name": "CS 2910",
+ "id": "CS2910",
+ "sections": [
+ {
+ "id": "4558132",
+ "name": "1000",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4558133",
+ "name": "1020",
+ "instructor": "BABAY"
+ },
+ {
+ "id": "4558134",
+ "name": "1030",
+ "instructor": "BIEHL"
+ },
+ {
+ "id": "4558135",
+ "name": "1050",
+ "instructor": "CHILDERS"
+ },
+ {
+ "id": "4558136",
+ "name": "1060",
+ "instructor": "CHRYSANTHIS"
+ },
+ {
+ "id": "4558137",
+ "name": "1070",
+ "instructor": "COHEN"
+ },
+ {
+ "id": "4558138",
+ "name": "1080",
+ "instructor": "HAUSKRECHT"
+ },
+ {
+ "id": "4558139",
+ "name": "1100",
+ "instructor": "SHANGGUAN"
+ },
+ {
+ "id": "4558140",
+ "name": "1110",
+ "instructor": "JIA"
+ },
+ {
+ "id": "4558141",
+ "name": "1120",
+ "instructor": "KOVASHKA"
+ },
+ {
+ "id": "4558142",
+ "name": "1130",
+ "instructor": "LABRINIDIS"
+ },
+ {
+ "id": "4558143",
+ "name": "1170",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558144",
+ "name": "1180",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558145",
+ "name": "1200",
+ "instructor": "LITMAN"
+ },
+ {
+ "id": "4558146",
+ "name": "1210",
+ "instructor": "MOSSE"
+ },
+ {
+ "id": "4558147",
+ "name": "1220",
+ "instructor": "PRUHS"
+ },
+ {
+ "id": "4558148",
+ "name": "1230",
+ "instructor": "TANG"
+ },
+ {
+ "id": "4558149",
+ "name": "1240",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4599610",
+ "name": "1010",
+ "instructor": "LI"
+ },
+ {
+ "id": "4637610",
+ "name": "1040",
+ "instructor": "NUNES QUARESMA DE OL"
+ }
+ ]
+ },
+ {
+ "name": "CS 2990",
+ "id": "CS2990",
+ "sections": [
+ {
+ "id": "4558150",
+ "name": "1000",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4558151",
+ "name": "1010",
+ "instructor": "GAUTAM"
+ },
+ {
+ "id": "4558152",
+ "name": "1020",
+ "instructor": "BABAY"
+ },
+ {
+ "id": "4558153",
+ "name": "1030",
+ "instructor": "BIEHL"
+ },
+ {
+ "id": "4558154",
+ "name": "1040",
+ "instructor": "WALKER"
+ },
+ {
+ "id": "4558155",
+ "name": "1050",
+ "instructor": "CHILDERS"
+ },
+ {
+ "id": "4558156",
+ "name": "1060",
+ "instructor": "CHRYSANTHIS"
+ },
+ {
+ "id": "4558157",
+ "name": "1070",
+ "instructor": "COHEN"
+ },
+ {
+ "id": "4558158",
+ "name": "1080",
+ "instructor": "HAUSKRECHT"
+ },
+ {
+ "id": "4558159",
+ "name": "1100",
+ "instructor": "SHANGGUAN"
+ },
+ {
+ "id": "4558160",
+ "name": "1110",
+ "instructor": "JIA"
+ },
+ {
+ "id": "4558161",
+ "name": "1120",
+ "instructor": "KOVASHKA"
+ },
+ {
+ "id": "4558162",
+ "name": "1130",
+ "instructor": "LABRINIDIS"
+ },
+ {
+ "id": "4558163",
+ "name": "1170",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558164",
+ "name": "1180",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558165",
+ "name": "1200",
+ "instructor": "LITMAN"
+ },
+ {
+ "id": "4558166",
+ "name": "1210",
+ "instructor": "MOSSE"
+ },
+ {
+ "id": "4558167",
+ "name": "1220",
+ "instructor": "PRUHS"
+ },
+ {
+ "id": "4558168",
+ "name": "1230",
+ "instructor": "TANG"
+ },
+ {
+ "id": "4558169",
+ "name": "1240",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4599611",
+ "name": "1090",
+ "instructor": "LI"
+ },
+ {
+ "id": "4637611",
+ "name": "1140",
+ "instructor": "NUNES QUARESMA DE OL"
+ }
+ ]
+ },
+ {
+ "name": "CS 3000",
+ "id": "CS3000",
+ "sections": [
+ {
+ "id": "4558170",
+ "name": "1000",
+ "instructor": "WALKER"
+ },
+ {
+ "id": "4558171",
+ "name": "1020",
+ "instructor": "BABAY"
+ },
+ {
+ "id": "4558172",
+ "name": "1030",
+ "instructor": "BIEHL"
+ },
+ {
+ "id": "4558173",
+ "name": "1050",
+ "instructor": "CHILDERS"
+ },
+ {
+ "id": "4558174",
+ "name": "1060",
+ "instructor": "CHRYSANTHIS"
+ },
+ {
+ "id": "4558175",
+ "name": "1070",
+ "instructor": "COHEN"
+ },
+ {
+ "id": "4558176",
+ "name": "1080",
+ "instructor": "HAUSKRECHT"
+ },
+ {
+ "id": "4558177",
+ "name": "1100",
+ "instructor": "SHANGGUAN"
+ },
+ {
+ "id": "4558178",
+ "name": "1110",
+ "instructor": "JIA"
+ },
+ {
+ "id": "4558179",
+ "name": "1120",
+ "instructor": "KOVASHKA"
+ },
+ {
+ "id": "4558180",
+ "name": "1130",
+ "instructor": "LABRINIDIS"
+ },
+ {
+ "id": "4558181",
+ "name": "1170",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558182",
+ "name": "1180",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558183",
+ "name": "1200",
+ "instructor": "LITMAN"
+ },
+ {
+ "id": "4558184",
+ "name": "1210",
+ "instructor": "MOSSE"
+ },
+ {
+ "id": "4558185",
+ "name": "1220",
+ "instructor": "PRUHS"
+ },
+ {
+ "id": "4558186",
+ "name": "1230",
+ "instructor": "TANG"
+ },
+ {
+ "id": "4558187",
+ "name": "1240",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4599612",
+ "name": "1010",
+ "instructor": "LI"
+ },
+ {
+ "id": "4637612",
+ "name": "1140",
+ "instructor": "NUNES QUARESMA DE OL"
+ }
+ ]
+ },
+ {
+ "name": "CS 3550",
+ "id": "CS3550",
+ "sections": [
+ {
+ "id": "4558188",
+ "name": "1200",
+ "instructor": "LABRINIDIS"
+ }
+ ]
+ },
+ {
+ "name": "CS 3551",
+ "id": "CS3551",
+ "sections": [
+ {
+ "id": "4558189",
+ "name": "1100",
+ "instructor": "BABAY"
+ }
+ ]
+ },
+ {
+ "name": "CS 3750",
+ "id": "CS3750",
+ "sections": [
+ {
+ "id": "4558190",
+ "name": "1100",
+ "instructor": "JIA"
+ }
+ ]
+ },
+ {
+ "name": "CS 3800",
+ "id": "CS3800",
+ "sections": [
+ {
+ "id": "4639990",
+ "name": "1000",
+ "instructor": "LIU"
+ }
+ ]
+ },
+ {
+ "name": "CS 3900",
+ "id": "CS3900",
+ "sections": [
+ {
+ "id": "4558191",
+ "name": "1000",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4558192",
+ "name": "1020",
+ "instructor": "BABAY"
+ },
+ {
+ "id": "4558193",
+ "name": "1030",
+ "instructor": "BIEHL"
+ },
+ {
+ "id": "4558194",
+ "name": "1040",
+ "instructor": "WALKER"
+ },
+ {
+ "id": "4558195",
+ "name": "1050",
+ "instructor": "CHILDERS"
+ },
+ {
+ "id": "4558196",
+ "name": "1060",
+ "instructor": "CHRYSANTHIS"
+ },
+ {
+ "id": "4558197",
+ "name": "1070",
+ "instructor": "COHEN"
+ },
+ {
+ "id": "4558198",
+ "name": "1080",
+ "instructor": "HAUSKRECHT"
+ },
+ {
+ "id": "4558199",
+ "name": "1090",
+ "instructor": "NUNES QUARESMA DE OL"
+ },
+ {
+ "id": "4558200",
+ "name": "1100",
+ "instructor": "SHANGGUAN"
+ },
+ {
+ "id": "4558201",
+ "name": "1110",
+ "instructor": "JIA"
+ },
+ {
+ "id": "4558202",
+ "name": "1120",
+ "instructor": "KOVASHKA"
+ },
+ {
+ "id": "4558203",
+ "name": "1130",
+ "instructor": "LABRINIDIS"
+ },
+ {
+ "id": "4558204",
+ "name": "1140",
+ "instructor": "LANGE"
+ },
+ {
+ "id": "4558205",
+ "name": "1170",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558206",
+ "name": "1180",
+ "instructor": "LEE"
+ },
+ {
+ "id": "4558207",
+ "name": "1200",
+ "instructor": "LITMAN"
+ },
+ {
+ "id": "4558208",
+ "name": "1210",
+ "instructor": "MOSSE"
+ },
+ {
+ "id": "4558209",
+ "name": "1220",
+ "instructor": "PRUHS"
+ },
+ {
+ "id": "4558210",
+ "name": "1230",
+ "instructor": "TANG"
+ },
+ {
+ "id": "4558211",
+ "name": "1240",
+ "instructor": "ZHANG"
+ },
+ {
+ "id": "4599613",
+ "name": "1010",
+ "instructor": "LI"
+ }
+ ]
+ }
+]
diff --git a/tests/samples/textbook_courses_MATH.json b/tests/samples/textbook_courses_MATH.json
new file mode 100644
index 0000000..08cbacf
--- /dev/null
+++ b/tests/samples/textbook_courses_MATH.json
@@ -0,0 +1,870 @@
+[
+ {
+ "name": "MATH 0010",
+ "id": "MATH0010",
+ "sections": [
+ {
+ "id": "4650882",
+ "name": "1070",
+ "instructor": null
+ }
+ ]
+ },
+ {
+ "name": "MATH 0025",
+ "id": "MATH0025",
+ "sections": [
+ {
+ "id": "4561831",
+ "name": "7510",
+ "instructor": "DEMATEO"
+ },
+ {
+ "id": "4631046",
+ "name": "1100",
+ "instructor": "VARGSON"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0031",
+ "id": "MATH0031",
+ "sections": [
+ {
+ "id": "4561832",
+ "name": "7510",
+ "instructor": "O'MARA"
+ },
+ {
+ "id": "4631047",
+ "name": "1010",
+ "instructor": "VARGSON"
+ },
+ {
+ "id": "4631048",
+ "name": "1030",
+ "instructor": "ISLAM"
+ },
+ {
+ "id": "4631049",
+ "name": "1070",
+ "instructor": "ISLAM"
+ },
+ {
+ "id": "4650927",
+ "name": "1050",
+ "instructor": null
+ },
+ {
+ "id": "4650929",
+ "name": "1110",
+ "instructor": null
+ },
+ {
+ "id": "4650930",
+ "name": "1130",
+ "instructor": null
+ },
+ {
+ "id": "4650931",
+ "name": "1240",
+ "instructor": null
+ }
+ ]
+ },
+ {
+ "name": "MATH 0120",
+ "id": "MATH0120",
+ "sections": [
+ {
+ "id": "4631050",
+ "name": "1060",
+ "instructor": "GILTON"
+ },
+ {
+ "id": "4631051",
+ "name": "1096",
+ "instructor": "POPESCU"
+ },
+ {
+ "id": "4631052",
+ "name": "1110",
+ "instructor": "EVEREST"
+ },
+ {
+ "id": "4631053",
+ "name": "1250",
+ "instructor": "GILTON"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0125",
+ "id": "MATH0125",
+ "sections": [
+ {
+ "id": "4631054",
+ "name": "1040",
+ "instructor": "VARGSON"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0200",
+ "id": "MATH0200",
+ "sections": [
+ {
+ "id": "4631055",
+ "name": "1100",
+ "instructor": "HOCKENSMITH"
+ },
+ {
+ "id": "4631056",
+ "name": "1200",
+ "instructor": "DEBLOIS"
+ },
+ {
+ "id": "4631057",
+ "name": "1300",
+ "instructor": "BAO"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0220",
+ "id": "MATH0220",
+ "sections": [
+ {
+ "id": "4631058",
+ "name": "1025",
+ "instructor": "TROFIMOV"
+ },
+ {
+ "id": "4631059",
+ "name": "1060",
+ "instructor": "XIONG"
+ },
+ {
+ "id": "4631060",
+ "name": "1080",
+ "instructor": "YUST"
+ },
+ {
+ "id": "4631061",
+ "name": "1100",
+ "instructor": "HOCKENSMITH"
+ },
+ {
+ "id": "4631062",
+ "name": "1116",
+ "instructor": "WANG"
+ },
+ {
+ "id": "4631063",
+ "name": "1120",
+ "instructor": "XIONG"
+ },
+ {
+ "id": "4631064",
+ "name": "1191",
+ "instructor": "KAVEH"
+ },
+ {
+ "id": "4631065",
+ "name": "1195",
+ "instructor": "MORGAN"
+ },
+ {
+ "id": "4631066",
+ "name": "1210",
+ "instructor": "YUST"
+ },
+ {
+ "id": "4631067",
+ "name": "1230",
+ "instructor": "SPARLING"
+ },
+ {
+ "id": "4631068",
+ "name": "1239",
+ "instructor": "MORGAN"
+ },
+ {
+ "id": "4631069",
+ "name": "1250",
+ "instructor": "KAVEH"
+ },
+ {
+ "id": "4631070",
+ "name": "1300",
+ "instructor": "WANG"
+ },
+ {
+ "id": "4631071",
+ "name": "1350",
+ "instructor": "FEDOROV"
+ },
+ {
+ "id": "4631072",
+ "name": "1500",
+ "instructor": "YAO"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0230",
+ "id": "MATH0230",
+ "sections": [
+ {
+ "id": "4631073",
+ "name": "1010",
+ "instructor": "TROFIMOV"
+ },
+ {
+ "id": "4631074",
+ "name": "1015",
+ "instructor": "EVEREST"
+ },
+ {
+ "id": "4631075",
+ "name": "1036",
+ "instructor": "EVEREST"
+ },
+ {
+ "id": "4631076",
+ "name": "1040",
+ "instructor": "WHEELER"
+ },
+ {
+ "id": "4631077",
+ "name": "1050",
+ "instructor": "POPESCU"
+ },
+ {
+ "id": "4631078",
+ "name": "1060",
+ "instructor": "POPESCU"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0235",
+ "id": "MATH0235",
+ "sections": [
+ {
+ "id": "4634384",
+ "name": "1040",
+ "instructor": "WHEELER"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0240",
+ "id": "MATH0240",
+ "sections": [
+ {
+ "id": "4631079",
+ "name": "1015",
+ "instructor": "STREIPERT"
+ },
+ {
+ "id": "4631080",
+ "name": "1105",
+ "instructor": "HAJLASZ"
+ },
+ {
+ "id": "4631081",
+ "name": "1160",
+ "instructor": "HAJLASZ"
+ },
+ {
+ "id": "4631082",
+ "name": "1300",
+ "instructor": "SPARLING"
+ },
+ {
+ "id": "4631083",
+ "name": "1400",
+ "instructor": "POGGI CEVALLOS"
+ },
+ {
+ "id": "4631084",
+ "name": "1420",
+ "instructor": "PAN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0280",
+ "id": "MATH0280",
+ "sections": [
+ {
+ "id": "4631085",
+ "name": "1030",
+ "instructor": "CHEN"
+ },
+ {
+ "id": "4631086",
+ "name": "1041",
+ "instructor": "GARTSIDE"
+ },
+ {
+ "id": "4631087",
+ "name": "1045",
+ "instructor": "GARTSIDE"
+ },
+ {
+ "id": "4631088",
+ "name": "1200",
+ "instructor": "WOJTOWYTSCH"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0290",
+ "id": "MATH0290",
+ "sections": [
+ {
+ "id": "4631089",
+ "name": "1040",
+ "instructor": "BAO"
+ },
+ {
+ "id": "4631090",
+ "name": "1050",
+ "instructor": "STREIPERT"
+ },
+ {
+ "id": "4631091",
+ "name": "1090",
+ "instructor": "TRENCHEA"
+ },
+ {
+ "id": "4631092",
+ "name": "1100",
+ "instructor": "BAO"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0413",
+ "id": "MATH0413",
+ "sections": [
+ {
+ "id": "4631093",
+ "name": "1030",
+ "instructor": "LAYTON"
+ },
+ {
+ "id": "4631094",
+ "name": "1060",
+ "instructor": "HALES"
+ },
+ {
+ "id": "4631095",
+ "name": "1100",
+ "instructor": "KAVEH"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0420",
+ "id": "MATH0420",
+ "sections": [
+ {
+ "id": "4631096",
+ "name": "1020",
+ "instructor": "CHEN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0430",
+ "id": "MATH0430",
+ "sections": [
+ {
+ "id": "4631097",
+ "name": "1070",
+ "instructor": "PAN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0470",
+ "id": "MATH0470",
+ "sections": [
+ {
+ "id": "4631098",
+ "name": "1120",
+ "instructor": "XIONG"
+ }
+ ]
+ },
+ {
+ "name": "MATH 0480",
+ "id": "MATH0480",
+ "sections": [
+ {
+ "id": "4631099",
+ "name": "1000",
+ "instructor": "ION"
+ },
+ {
+ "id": "4631100",
+ "name": "1200",
+ "instructor": "MOAKHER"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1010",
+ "id": "MATH1010",
+ "sections": [
+ {
+ "id": "4631101",
+ "name": "1010",
+ "instructor": "LEWICKA"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1050",
+ "id": "MATH1050",
+ "sections": [
+ {
+ "id": "4631102",
+ "name": "1040",
+ "instructor": "CONSTANTINE"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1070",
+ "id": "MATH1070",
+ "sections": [
+ {
+ "id": "4631103",
+ "name": "1040",
+ "instructor": "LAYTON"
+ },
+ {
+ "id": "4631104",
+ "name": "1050",
+ "instructor": "YOTOV"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1101",
+ "id": "MATH1101",
+ "sections": [
+ {
+ "id": "4631105",
+ "name": "1030",
+ "instructor": "WHEELER"
+ },
+ {
+ "id": "4631106",
+ "name": "1300",
+ "instructor": "CHOU"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1119",
+ "id": "MATH1119",
+ "sections": [
+ {
+ "id": "4631107",
+ "name": "1010",
+ "instructor": "YUST"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1122",
+ "id": "MATH1122",
+ "sections": [
+ {
+ "id": "4631108",
+ "name": "1000",
+ "instructor": "CHADAM"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1126",
+ "id": "MATH1126",
+ "sections": [
+ {
+ "id": "4631109",
+ "name": "1010",
+ "instructor": "CHADAM"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1180",
+ "id": "MATH1180",
+ "sections": [
+ {
+ "id": "4631110",
+ "name": "1030",
+ "instructor": "SWIGON"
+ },
+ {
+ "id": "4631111",
+ "name": "1050",
+ "instructor": "CHEN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1185",
+ "id": "MATH1185",
+ "sections": [
+ {
+ "id": "4634385",
+ "name": "1040",
+ "instructor": "GILTON"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1270",
+ "id": "MATH1270",
+ "sections": [
+ {
+ "id": "4631112",
+ "name": "1040",
+ "instructor": "SWIGON"
+ },
+ {
+ "id": "4631113",
+ "name": "1050",
+ "instructor": "VAINCHTEIN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1290",
+ "id": "MATH1290",
+ "sections": [
+ {
+ "id": "4631114",
+ "name": "1100",
+ "instructor": "DEBLOIS"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1360",
+ "id": "MATH1360",
+ "sections": [
+ {
+ "id": "4631115",
+ "name": "1040",
+ "instructor": "ERMENTROUT"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1470",
+ "id": "MATH1470",
+ "sections": [
+ {
+ "id": "4631116",
+ "name": "1070",
+ "instructor": "CHEN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1530",
+ "id": "MATH1530",
+ "sections": [
+ {
+ "id": "4590464",
+ "name": "1060",
+ "instructor": "HAJLASZ"
+ },
+ {
+ "id": "4631117",
+ "name": "1230",
+ "instructor": "SCHIKORRA"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1550",
+ "id": "MATH1550",
+ "sections": [
+ {
+ "id": "4631118",
+ "name": "1040",
+ "instructor": "LENNARD"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1900",
+ "id": "MATH1900",
+ "sections": [
+ {
+ "id": "4561833",
+ "name": "1100",
+ "instructor": "DEBLOIS"
+ }
+ ]
+ },
+ {
+ "name": "MATH 1902",
+ "id": "MATH1902",
+ "sections": [
+ {
+ "id": "4561834",
+ "name": "1030",
+ "instructor": "HALES"
+ },
+ {
+ "id": "4561835",
+ "name": "1040",
+ "instructor": "SCHIKORRA"
+ },
+ {
+ "id": "4561836",
+ "name": "1050",
+ "instructor": "LAYTON"
+ },
+ {
+ "id": "4561837",
+ "name": "1060",
+ "instructor": "GILTON"
+ },
+ {
+ "id": "4561838",
+ "name": "1070",
+ "instructor": "DEBLOIS"
+ },
+ {
+ "id": "4561839",
+ "name": "1080",
+ "instructor": "CONSTANTINE"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2000",
+ "id": "MATH2000",
+ "sections": [
+ {
+ "id": "4561840",
+ "name": "1010",
+ "instructor": "NEILAN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2060",
+ "id": "MATH2060",
+ "sections": [
+ {
+ "id": "4590465",
+ "name": "1200",
+ "instructor": "CONSTANTINE"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2070",
+ "id": "MATH2070",
+ "sections": [
+ {
+ "id": "4590466",
+ "name": "1040",
+ "instructor": "NEILAN"
+ },
+ {
+ "id": "4631119",
+ "name": "1030",
+ "instructor": "NEILAN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2090",
+ "id": "MATH2090",
+ "sections": [
+ {
+ "id": "4590467",
+ "name": "1100",
+ "instructor": "TRENCHEA"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2301",
+ "id": "MATH2301",
+ "sections": [
+ {
+ "id": "4590468",
+ "name": "1040",
+ "instructor": "LENNARD"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2303",
+ "id": "MATH2303",
+ "sections": [
+ {
+ "id": "4590469",
+ "name": "1060",
+ "instructor": "LEWICKA"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2370",
+ "id": "MATH2370",
+ "sections": [
+ {
+ "id": "4590470",
+ "name": "1040",
+ "instructor": "WOJTOWYTSCH"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2500",
+ "id": "MATH2500",
+ "sections": [
+ {
+ "id": "4590471",
+ "name": "1100",
+ "instructor": "WANG ERICKSON"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2505",
+ "id": "MATH2505",
+ "sections": [
+ {
+ "id": "4590472",
+ "name": "1200",
+ "instructor": "ION"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2603",
+ "id": "MATH2603",
+ "sections": [
+ {
+ "id": "4590473",
+ "name": "1200",
+ "instructor": "YOTOV"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2800",
+ "id": "MATH2800",
+ "sections": [
+ {
+ "id": "4590474",
+ "name": "1100",
+ "instructor": "FEDOROV"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2900",
+ "id": "MATH2900",
+ "sections": [
+ {
+ "id": "4590475",
+ "name": "1000",
+ "instructor": "VAINCHTEIN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2920",
+ "id": "MATH2920",
+ "sections": [
+ {
+ "id": "4590476",
+ "name": "1070",
+ "instructor": "RUBIN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 2990",
+ "id": "MATH2990",
+ "sections": [
+ {
+ "id": "4561841",
+ "name": "1010",
+ "instructor": "NEILAN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 3000",
+ "id": "MATH3000",
+ "sections": [
+ {
+ "id": "4561842",
+ "name": "1010",
+ "instructor": "NEILAN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 3225",
+ "id": "MATH3225",
+ "sections": [
+ {
+ "id": "4590477",
+ "name": "1010",
+ "instructor": "YAO"
+ }
+ ]
+ },
+ {
+ "name": "MATH 3900",
+ "id": "MATH3900",
+ "sections": [
+ {
+ "id": "4561843",
+ "name": "1010",
+ "instructor": "NEILAN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 3902",
+ "id": "MATH3902",
+ "sections": [
+ {
+ "id": "4561844",
+ "name": "1010",
+ "instructor": "NEILAN"
+ }
+ ]
+ },
+ {
+ "name": "MATH 3923",
+ "id": "MATH3923",
+ "sections": [
+ {
+ "id": "4585743",
+ "name": "1200",
+ "instructor": "GALDI"
+ }
+ ]
+ }
+]
diff --git a/tests/samples/textbook_courses_STAT.json b/tests/samples/textbook_courses_STAT.json
deleted file mode 100644
index 1e5c3e4..0000000
--- a/tests/samples/textbook_courses_STAT.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"name":"STAT 0200","id":"STAT0200","sections":[{"id":"2105836","name":"1030","instructor":"QUESEN"},{"id":"2105837","name":"1035","instructor":null},{"id":"2105838","name":"1040","instructor":null},{"id":"2105839","name":"1045","instructor":null},{"id":"2105840","name":"1050","instructor":null},{"id":"2105841","name":"1055","instructor":"STRASSER"},{"id":"2105842","name":"1060","instructor":null},{"id":"2105843","name":"1065","instructor":null},{"id":"2105844","name":"1070","instructor":null},{"id":"2105845","name":"1075","instructor":null},{"id":"2105846","name":"1080","instructor":"STRASSER"},{"id":"2105847","name":"1085","instructor":null},{"id":"2105848","name":"1090","instructor":null},{"id":"2105849","name":"1095","instructor":null},{"id":"2105850","name":"1100","instructor":null},{"id":"2105851","name":"1105","instructor":"LUTZ"},{"id":"2105852","name":"1110","instructor":null},{"id":"2105853","name":"1115","instructor":null},{"id":"2105854","name":"1120","instructor":null},{"id":"2105855","name":"1125","instructor":null},{"id":"2105856","name":"1130","instructor":"PERRY"},{"id":"2105857","name":"1135","instructor":null},{"id":"2105858","name":"1140","instructor":null},{"id":"2105859","name":"1145","instructor":null},{"id":"2105860","name":"1150","instructor":null},{"id":"2105861","name":"7000","instructor":"HARMON"},{"id":"2105862","name":"7010","instructor":"KIM"},{"id":"2105863","name":"7020","instructor":"KIM"},{"id":"2105864","name":"7030","instructor":"KIM"},{"id":"2105865","name":"7510","instructor":"HARMON"}]},{"name":"STAT 0800","id":"STAT0800","sections":[{"id":"2105866","name":"1030","instructor":"NOROSKI"}]},{"name":"STAT 1000","id":"STAT1000","sections":[{"id":"2105867","name":"1040","instructor":"PFENNING"},{"id":"2105868","name":"1050","instructor":null},{"id":"2105869","name":"1060","instructor":null},{"id":"2105870","name":"1070","instructor":null},{"id":"2105871","name":"1080","instructor":null},{"id":"2105872","name":"1100","instructor":"WANG"},{"id":"2105873","name":"1110","instructor":null},{"id":"2105874","name":"1120","instructor":null},{"id":"2105875","name":"1130","instructor":null},{"id":"2105876","name":"1140","instructor":null},{"id":"2105877","name":"1200","instructor":"PFENNING"},{"id":"2105878","name":"1210","instructor":null},{"id":"2105879","name":"1260","instructor":"KENKEL"},{"id":"2105880","name":"1270","instructor":null},{"id":"2105881","name":"1280","instructor":null},{"id":"2105882","name":"1290","instructor":null},{"id":"2105883","name":"1300","instructor":null},{"id":"2105884","name":"1320","instructor":"YI"},{"id":"2105885","name":"1330","instructor":null},{"id":"2105886","name":"1340","instructor":null},{"id":"2105887","name":"1350","instructor":null},{"id":"2105888","name":"1360","instructor":null},{"id":"2105889","name":"1370","instructor":"WANG"},{"id":"2105890","name":"1380","instructor":null},{"id":"2105891","name":"1390","instructor":null},{"id":"2105892","name":"1400","instructor":null},{"id":"2105893","name":"1410","instructor":null},{"id":"2105894","name":"1420","instructor":"NELSON"},{"id":"2105895","name":"1425","instructor":null},{"id":"2105896","name":"1430","instructor":null},{"id":"2105897","name":"1435","instructor":null},{"id":"2105898","name":"1440","instructor":null}]},{"name":"STAT 1100","id":"STAT1100","sections":[{"id":"2105899","name":"1010","instructor":"NELSON"},{"id":"2105900","name":"1015","instructor":null},{"id":"2105901","name":"1020","instructor":null},{"id":"2105902","name":"1025","instructor":null},{"id":"2105903","name":"1030","instructor":null},{"id":"2105904","name":"1035","instructor":"NELSON"},{"id":"2105905","name":"1040","instructor":null},{"id":"2105906","name":"1045","instructor":null},{"id":"2105907","name":"1050","instructor":null},{"id":"2105908","name":"1055","instructor":null},{"id":"2105909","name":"1060","instructor":"NELSON"},{"id":"2105910","name":"1065","instructor":null},{"id":"2105911","name":"1070","instructor":null},{"id":"2105912","name":"1075","instructor":null},{"id":"2105913","name":"1080","instructor":null},{"id":"2105914","name":"1085","instructor":"BILONICK"},{"id":"2105915","name":"1090","instructor":null},{"id":"2105916","name":"1095","instructor":null},{"id":"2105917","name":"1100","instructor":null},{"id":"2105918","name":"1105","instructor":null},{"id":"2105919","name":"7510","instructor":"CHIAPPETTA"}]},{"name":"STAT 1151","id":"STAT1151","sections":[{"id":"2105920","name":"1055","instructor":"BODENSCHATZ"},{"id":"2105921","name":"1060","instructor":"BODENSCHATZ"}]},{"name":"STAT 1211","id":"STAT1211","sections":[{"id":"2105922","name":"1010","instructor":"BILONICK"}]},{"name":"STAT 1291","id":"STAT1291","sections":[{"id":"2105923","name":"1010","instructor":"JUNG"},{"id":"2105924","name":"1015","instructor":"JUNG"},{"id":"2105925","name":"1020","instructor":"JUNG"}]},{"name":"STAT 1321","id":"STAT1321","sections":[{"id":"2105926","name":"1060","instructor":"STOFFER"}]},{"name":"STAT 1631","id":"STAT1631","sections":[{"id":"2105927","name":"1030","instructor":"REN"}]},{"name":"STAT 1900","id":"STAT1900","sections":[{"id":"2105928","name":"1010","instructor":"IYENGAR"},{"id":"2105929","name":"1020","instructor":"CHENG"},{"id":"2105930","name":"1025","instructor":"SAMPSON"},{"id":"2105931","name":"1035","instructor":null},{"id":"2105932","name":"1040","instructor":"STOFFER"},{"id":"2105933","name":"1045","instructor":"JUNG"},{"id":"2105934","name":"1050","instructor":"CHEN"},{"id":"2105935","name":"1060","instructor":"REN"},{"id":"2105936","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 1902","id":"STAT1902","sections":[{"id":"2105937","name":"1015","instructor":"IYENGAR"},{"id":"2105938","name":"1030","instructor":"CHENG"},{"id":"2105939","name":"1035","instructor":"SAMPSON"},{"id":"2105940","name":"1045","instructor":"MENTCH"},{"id":"2105941","name":"1050","instructor":"STOFFER"},{"id":"2105942","name":"1055","instructor":"JUNG"},{"id":"2105943","name":"1060","instructor":"CHEN"},{"id":"2105944","name":"1070","instructor":"REN"},{"id":"2105945","name":"1080","instructor":"MENTCH"}]},{"name":"STAT 2001","id":"STAT2001","sections":[{"id":"2105946","name":"1010","instructor":"IYENGAR"},{"id":"2105947","name":"1025","instructor":"CHENG"},{"id":"2105948","name":"1030","instructor":"SAMPSON"},{"id":"2105949","name":"1040","instructor":null},{"id":"2105950","name":"1045","instructor":"STOFFER"},{"id":"2105951","name":"1050","instructor":"JUNG"},{"id":"2105952","name":"1055","instructor":"CHEN"},{"id":"2105953","name":"1060","instructor":"REN"},{"id":"2105954","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 2020","id":"STAT2020","sections":[{"id":"2105955","name":"1010","instructor":"PFENNING"}]},{"name":"STAT 2131","id":"STAT2131","sections":[{"id":"2105956","name":"1030","instructor":"CHEN"}]},{"name":"STAT 2210","id":"STAT2210","sections":[{"id":"2105957","name":"1010","instructor":"BILONICK"}]},{"name":"STAT 2320","id":"STAT2320","sections":[{"id":"2105958","name":"1060","instructor":"STOFFER"}]},{"name":"STAT 2321","id":"STAT2321","sections":[{"id":"2105959","name":"1210","instructor":"STOFFER"}]},{"name":"STAT 2381","id":"STAT2381","sections":[{"id":"2105960","name":"1030","instructor":"SAMPSON"}]},{"name":"STAT 2391","id":"STAT2391","sections":[{"id":"2105961","name":"1010","instructor":"IYENGAR"}]},{"name":"STAT 2392","id":"STAT2392","sections":[{"id":"2105962","name":"1010","instructor":null}]},{"name":"STAT 2630","id":"STAT2630","sections":[{"id":"2105963","name":"1030","instructor":"REN"}]},{"name":"STAT 2631","id":"STAT2631","sections":[{"id":"2105964","name":"1060","instructor":"JUNG"}]},{"name":"STAT 2661","id":"STAT2661","sections":[{"id":"2147518","name":"1010","instructor":"SAMPSON"}]},{"name":"STAT 2691","id":"STAT2691","sections":[{"id":"2105965","name":"1010","instructor":"REN"}]},{"name":"STAT 2711","id":"STAT2711","sections":[{"id":"2105966","name":"1020","instructor":"IYENGAR"}]},{"name":"STAT 2900","id":"STAT2900","sections":[{"id":"2105967","name":"1010","instructor":"IYENGAR"},{"id":"2105968","name":"1020","instructor":"CHENG"},{"id":"2105969","name":"1025","instructor":"SAMPSON"},{"id":"2105970","name":"1035","instructor":null},{"id":"2105971","name":"1040","instructor":"STOFFER"},{"id":"2105972","name":"1045","instructor":"JUNG"},{"id":"2105973","name":"1050","instructor":"CHEN"},{"id":"2105974","name":"1060","instructor":"REN"},{"id":"2105975","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 2991","id":"STAT2991","sections":[{"id":"2105976","name":"1010","instructor":"IYENGAR"},{"id":"2105977","name":"1025","instructor":"CHENG"},{"id":"2105978","name":"1030","instructor":"SAMPSON"},{"id":"2105979","name":"1040","instructor":null},{"id":"2105980","name":"1045","instructor":"STOFFER"},{"id":"2105981","name":"1050","instructor":"JUNG"},{"id":"2105982","name":"1055","instructor":"CHEN"},{"id":"2105983","name":"1060","instructor":"REN"},{"id":"2105984","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 3001","id":"STAT3001","sections":[{"id":"2105985","name":"1010","instructor":"IYENGAR"},{"id":"2105986","name":"1025","instructor":"CHENG"},{"id":"2105987","name":"1030","instructor":"SAMPSON"},{"id":"2105988","name":"1040","instructor":null},{"id":"2105989","name":"1045","instructor":"STOFFER"},{"id":"2105990","name":"1050","instructor":"JUNG"},{"id":"2105991","name":"1055","instructor":"CHEN"},{"id":"2105992","name":"1060","instructor":"REN"},{"id":"2105993","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 3901","id":"STAT3901","sections":[{"id":"2105994","name":"1030","instructor":"IYENGAR"},{"id":"2105995","name":"1060","instructor":"CHENG"},{"id":"2105996","name":"1070","instructor":"SAMPSON"},{"id":"2105997","name":"1090","instructor":null},{"id":"2105998","name":"1100","instructor":"STOFFER"},{"id":"2105999","name":"1105","instructor":"JUNG"},{"id":"2106000","name":"1110","instructor":"CHEN"},{"id":"2106001","name":"1120","instructor":"REN"},{"id":"2106002","name":"1130","instructor":"MENTCH"}]}]
diff --git a/tests/samples/textbook_subjects.json b/tests/samples/textbook_subjects.json
new file mode 100644
index 0000000..1a87a52
--- /dev/null
+++ b/tests/samples/textbook_subjects.json
@@ -0,0 +1,674 @@
+[
+ {
+ "id": "98224",
+ "name": "*GOGGLES"
+ },
+ {
+ "id": "98225",
+ "name": "*LABCOATS"
+ },
+ {
+ "id": "98226",
+ "name": "*NUR KITS"
+ },
+ {
+ "id": "22401",
+ "name": "AFRCNA"
+ },
+ {
+ "id": "22403",
+ "name": "ANTH"
+ },
+ {
+ "id": "22404",
+ "name": "ARABIC"
+ },
+ {
+ "id": "28594",
+ "name": "ARC"
+ },
+ {
+ "id": "22405",
+ "name": "ARTSC"
+ },
+ {
+ "id": "22406",
+ "name": "ASL"
+ },
+ {
+ "id": "22407",
+ "name": "ASTRON"
+ },
+ {
+ "id": "22408",
+ "name": "ATHLTR"
+ },
+ {
+ "id": "22409",
+ "name": "BACC"
+ },
+ {
+ "id": "22410",
+ "name": "BCHS"
+ },
+ {
+ "id": "80803",
+ "name": "BCMS"
+ },
+ {
+ "id": "22411",
+ "name": "BECN"
+ },
+ {
+ "id": "22412",
+ "name": "BFIN"
+ },
+ {
+ "id": "22414",
+ "name": "BIND"
+ },
+ {
+ "id": "22415",
+ "name": "BIOENG"
+ },
+ {
+ "id": "22416",
+ "name": "BIOETH"
+ },
+ {
+ "id": "22417",
+ "name": "BIOINF"
+ },
+ {
+ "id": "22418",
+ "name": "BIOSC"
+ },
+ {
+ "id": "22419",
+ "name": "BIOST"
+ },
+ {
+ "id": "22420",
+ "name": "BMIS"
+ },
+ {
+ "id": "22421",
+ "name": "BMKT"
+ },
+ {
+ "id": "22424",
+ "name": "BQOM"
+ },
+ {
+ "id": "22425",
+ "name": "BSEO"
+ },
+ {
+ "id": "22426",
+ "name": "BSPP"
+ },
+ {
+ "id": "22428",
+ "name": "BUSACC"
+ },
+ {
+ "id": "22430",
+ "name": "BUSBIS"
+ },
+ {
+ "id": "22431",
+ "name": "BUSECN"
+ },
+ {
+ "id": "22432",
+ "name": "BUSENV"
+ },
+ {
+ "id": "22433",
+ "name": "BUSERV"
+ },
+ {
+ "id": "22434",
+ "name": "BUSFIN"
+ },
+ {
+ "id": "22435",
+ "name": "BUSHRM"
+ },
+ {
+ "id": "22436",
+ "name": "BUSMKT"
+ },
+ {
+ "id": "22437",
+ "name": "BUSORG"
+ },
+ {
+ "id": "22438",
+ "name": "BUSQOM"
+ },
+ {
+ "id": "22439",
+ "name": "BUSSCM"
+ },
+ {
+ "id": "22440",
+ "name": "BUSSPP"
+ },
+ {
+ "id": "22441",
+ "name": "CDACCT"
+ },
+ {
+ "id": "22442",
+ "name": "CDENT"
+ },
+ {
+ "id": "22443",
+ "name": "CEE"
+ },
+ {
+ "id": "22444",
+ "name": "CGS"
+ },
+ {
+ "id": "22445",
+ "name": "CHE"
+ },
+ {
+ "id": "22446",
+ "name": "CHEM"
+ },
+ {
+ "id": "22447",
+ "name": "CHIN"
+ },
+ {
+ "id": "22448",
+ "name": "CLASS"
+ },
+ {
+ "id": "22449",
+ "name": "CLRES"
+ },
+ {
+ "id": "22452",
+ "name": "CMPBIO"
+ },
+ {
+ "id": "32207",
+ "name": "CMPINF"
+ },
+ {
+ "id": "22456",
+ "name": "COMMRC"
+ },
+ {
+ "id": "26403",
+ "name": "COUN"
+ },
+ {
+ "id": "22457",
+ "name": "CS"
+ },
+ {
+ "id": "22458",
+ "name": "CSD"
+ },
+ {
+ "id": "22459",
+ "name": "DENHYG"
+ },
+ {
+ "id": "22463",
+ "name": "DSPHL"
+ },
+ {
+ "id": "22466",
+ "name": "ECE"
+ },
+ {
+ "id": "22467",
+ "name": "ECON"
+ },
+ {
+ "id": "22468",
+ "name": "EDUC"
+ },
+ {
+ "id": "44670",
+ "name": "EFOP"
+ },
+ {
+ "id": "22469",
+ "name": "ELI"
+ },
+ {
+ "id": "22470",
+ "name": "EM"
+ },
+ {
+ "id": "22472",
+ "name": "ENGCMP"
+ },
+ {
+ "id": "22473",
+ "name": "ENGFLM"
+ },
+ {
+ "id": "22474",
+ "name": "ENGLIT"
+ },
+ {
+ "id": "22475",
+ "name": "ENGR"
+ },
+ {
+ "id": "22476",
+ "name": "ENGSCI"
+ },
+ {
+ "id": "22477",
+ "name": "ENGWRT"
+ },
+ {
+ "id": "22479",
+ "name": "EOH"
+ },
+ {
+ "id": "22480",
+ "name": "EPIDEM"
+ },
+ {
+ "id": "22481",
+ "name": "FACDEV"
+ },
+ {
+ "id": "26401",
+ "name": "FMST"
+ },
+ {
+ "id": "22484",
+ "name": "FP"
+ },
+ {
+ "id": "22485",
+ "name": "FR"
+ },
+ {
+ "id": "22491",
+ "name": "GEOL"
+ },
+ {
+ "id": "22492",
+ "name": "GER"
+ },
+ {
+ "id": "22493",
+ "name": "GERON"
+ },
+ {
+ "id": "22494",
+ "name": "GREEK"
+ },
+ {
+ "id": "22495",
+ "name": "GREEKM"
+ },
+ {
+ "id": "22496",
+ "name": "GSWS"
+ },
+ {
+ "id": "22497",
+ "name": "HAA"
+ },
+ {
+ "id": "23736",
+ "name": "HEBREW"
+ },
+ {
+ "id": "61591",
+ "name": "HHD"
+ },
+ {
+ "id": "26198",
+ "name": "HI"
+ },
+ {
+ "id": "22499",
+ "name": "HINDI"
+ },
+ {
+ "id": "22500",
+ "name": "HIST"
+ },
+ {
+ "id": "22501",
+ "name": "HONORS"
+ },
+ {
+ "id": "22503",
+ "name": "HPM"
+ },
+ {
+ "id": "22504",
+ "name": "HPS"
+ },
+ {
+ "id": "22505",
+ "name": "HRS"
+ },
+ {
+ "id": "22506",
+ "name": "HUGEN"
+ },
+ {
+ "id": "27731",
+ "name": "HUN"
+ },
+ {
+ "id": "22508",
+ "name": "IE"
+ },
+ {
+ "id": "22511",
+ "name": "INFSCI"
+ },
+ {
+ "id": "22512",
+ "name": "INTBP"
+ },
+ {
+ "id": "22513",
+ "name": "IRISH"
+ },
+ {
+ "id": "22516",
+ "name": "ITAL"
+ },
+ {
+ "id": "22517",
+ "name": "JPNSE"
+ },
+ {
+ "id": "22518",
+ "name": "JS"
+ },
+ {
+ "id": "22519",
+ "name": "KOREAN"
+ },
+ {
+ "id": "22520",
+ "name": "LATIN"
+ },
+ {
+ "id": "22521",
+ "name": "LAW"
+ },
+ {
+ "id": "61465",
+ "name": "LCJS"
+ },
+ {
+ "id": "22522",
+ "name": "LCTL"
+ },
+ {
+ "id": "22523",
+ "name": "LDRSHP"
+ },
+ {
+ "id": "22525",
+ "name": "LING"
+ },
+ {
+ "id": "22526",
+ "name": "LIS"
+ },
+ {
+ "id": "22528",
+ "name": "MATH"
+ },
+ {
+ "id": "22529",
+ "name": "ME"
+ },
+ {
+ "id": "22531",
+ "name": "MEDEDU"
+ },
+ {
+ "id": "22532",
+ "name": "MEMS"
+ },
+ {
+ "id": "22535",
+ "name": "MSCBIO"
+ },
+ {
+ "id": "22537",
+ "name": "MSCMP"
+ },
+ {
+ "id": "22538",
+ "name": "MSE"
+ },
+ {
+ "id": "22544",
+ "name": "MSNBIO"
+ },
+ {
+ "id": "22545",
+ "name": "MUSIC"
+ },
+ {
+ "id": "22548",
+ "name": "NROSCI"
+ },
+ {
+ "id": "64465",
+ "name": "NUCE"
+ },
+ {
+ "id": "22549",
+ "name": "NUR"
+ },
+ {
+ "id": "22550",
+ "name": "NURCNS"
+ },
+ {
+ "id": "22552",
+ "name": "NURNP"
+ },
+ {
+ "id": "22553",
+ "name": "NURSAN"
+ },
+ {
+ "id": "22554",
+ "name": "NURSP"
+ },
+ {
+ "id": "22555",
+ "name": "NUTR"
+ },
+ {
+ "id": "22560",
+ "name": "OT"
+ },
+ {
+ "id": "22561",
+ "name": "PAS"
+ },
+ {
+ "id": "22565",
+ "name": "PERS"
+ },
+ {
+ "id": "22566",
+ "name": "PETE"
+ },
+ {
+ "id": "22567",
+ "name": "PHARM"
+ },
+ {
+ "id": "22568",
+ "name": "PHIL"
+ },
+ {
+ "id": "22569",
+ "name": "PHYS"
+ },
+ {
+ "id": "22570",
+ "name": "PIA"
+ },
+ {
+ "id": "37740",
+ "name": "PITT"
+ },
+ {
+ "id": "22571",
+ "name": "POLISH"
+ },
+ {
+ "id": "22572",
+ "name": "PORT"
+ },
+ {
+ "id": "22574",
+ "name": "PS"
+ },
+ {
+ "id": "22575",
+ "name": "PSY"
+ },
+ {
+ "id": "22578",
+ "name": "PT"
+ },
+ {
+ "id": "22579",
+ "name": "PUBHLT"
+ },
+ {
+ "id": "22580",
+ "name": "PUBSRV"
+ },
+ {
+ "id": "22582",
+ "name": "REHSCI"
+ },
+ {
+ "id": "22583",
+ "name": "REL"
+ },
+ {
+ "id": "22584",
+ "name": "RELGST"
+ },
+ {
+ "id": "22585",
+ "name": "RESTD"
+ },
+ {
+ "id": "63601",
+ "name": "RT"
+ },
+ {
+ "id": "22586",
+ "name": "RUSS"
+ },
+ {
+ "id": "22587",
+ "name": "SA"
+ },
+ {
+ "id": "22589",
+ "name": "SLAV"
+ },
+ {
+ "id": "22590",
+ "name": "SLOVAK"
+ },
+ {
+ "id": "22591",
+ "name": "SOC"
+ },
+ {
+ "id": "22592",
+ "name": "SOCWRK"
+ },
+ {
+ "id": "22593",
+ "name": "SPAN"
+ },
+ {
+ "id": "22594",
+ "name": "STAT"
+ },
+ {
+ "id": "22595",
+ "name": "SWAHIL"
+ },
+ {
+ "id": "22596",
+ "name": "SWBEH"
+ },
+ {
+ "id": "22598",
+ "name": "SWCOSA"
+ },
+ {
+ "id": "22599",
+ "name": "SWE"
+ },
+ {
+ "id": "22600",
+ "name": "SWGEN"
+ },
+ {
+ "id": "22601",
+ "name": "SWINT"
+ },
+ {
+ "id": "22602",
+ "name": "SWRES"
+ },
+ {
+ "id": "22603",
+ "name": "SWWEL"
+ },
+ {
+ "id": "22604",
+ "name": "TELCOM"
+ },
+ {
+ "id": "22605",
+ "name": "THEA"
+ },
+ {
+ "id": "61590",
+ "name": "TLL"
+ },
+ {
+ "id": "22606",
+ "name": "TURKSH"
+ },
+ {
+ "id": "22607",
+ "name": "UKRAIN"
+ },
+ {
+ "id": "22608",
+ "name": "URBNST"
+ },
+ {
+ "id": "38025",
+ "name": "ZZZ"
+ }
+]
diff --git a/tests/samples/textbook_textbooks_CS_0441_garrison.json b/tests/samples/textbook_textbooks_CS_0441_garrison.json
new file mode 100644
index 0000000..ac59d62
--- /dev/null
+++ b/tests/samples/textbook_textbooks_CS_0441_garrison.json
@@ -0,0 +1,69 @@
+[
+ {
+ "id": "4558031_BSZWEWZWMZYJ_0",
+ "isbn": "BSZWEWZWMZYJ",
+ "pf_id": null,
+ "new_item_id": "2603309:N",
+ "used_item_id": null,
+ "required": "Inclusive Access",
+ "sort_order": [
+ "0",
+ "2",
+ "Ia Canvas Content"
+ ],
+ "cover_image_url": "//vst-coverimages.verbacompete.com/no_image.jpg",
+ "title": "Ia Canvas Content",
+ "author": "Redshelf Ia",
+ "notes": null,
+ "citation": "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).",
+ "metadata": {
+ "section_id": "4558031",
+ "section_code": "22840",
+ "term_name": "Fall 24"
+ },
+ "offers": [
+ {
+ "isbn": "BSZWEWZWMZYJ",
+ "retailer": "bookstore",
+ "item_id": "2603309:N",
+ "condition": "new",
+ "title": null,
+ "currency": null,
+ "merchant": "bookstore",
+ "rental_days": null,
+ "retailer_name": "University Store",
+ "metadata": null,
+ "in_cart": null,
+ "selected": null,
+ "total": "45.75",
+ "retailer_order": 0,
+ "lowest_price": null,
+ "highest_price": null,
+ "comments": "Books Arriving Every Day-Check Frequently!",
+ "special_comment": "Pick it up or ship it!",
+ "store_branded": true,
+ "data_source": "bookstore",
+ "description": "From University Store on Fifth",
+ "seller_rating": null,
+ "fcondition": "New",
+ "fprice": "$45.75",
+ "discounted_shipping": null,
+ "can_checkout": false,
+ "feedback_count": null,
+ "location": null,
+ "price": "45.75",
+ "shipping": 0,
+ "variant": "new",
+ "source": "default",
+ "id": "bookstore_BSZWEWZWMZYJ_new_2603309:N"
+ }
+ ],
+ "section_id": null,
+ "db_section_id": null,
+ "inclusive_access": false,
+ "catalog_id": null,
+ "edition": null,
+ "copyright_year": null,
+ "binding": null
+ }
+]
diff --git a/tests/samples/textbook_textbooks_MATH_0430_pan.json b/tests/samples/textbook_textbooks_MATH_0430_pan.json
new file mode 100644
index 0000000..38df6bf
--- /dev/null
+++ b/tests/samples/textbook_textbooks_MATH_0430_pan.json
@@ -0,0 +1,104 @@
+[
+ {
+ "id": "4631097_9780201763904_0",
+ "isbn": "9780201763904",
+ "pf_id": null,
+ "new_item_id": "1445331:N",
+ "used_item_id": "1445331:U",
+ "required": "Required",
+ "sort_order": [
+ "0",
+ "1",
+ "First Course In Abstract Algebra"
+ ],
+ "cover_image_url": "//vst-coverimages.verbacompete.com/33f017cc-33d1-5c4c-b8e0-122cb182fa8a.jpg",
+ "title": "First Course In Abstract Algebra",
+ "author": "Fraleigh",
+ "notes": null,
+ "citation": "\u003cem\u003eFirst Course In Abstract Algebra\u003c/em\u003e by Fraleigh. Pearson Education, 7th Edition, 2002. (ISBN: 9780201763904).",
+ "metadata": {
+ "section_id": "4631097",
+ "section_code": "11295",
+ "term_name": "Fall 24"
+ },
+ "offers": [
+ {
+ "isbn": "9780201763904",
+ "retailer": "bookstore",
+ "item_id": "1445331:N",
+ "condition": "new",
+ "title": null,
+ "currency": null,
+ "merchant": "bookstore",
+ "rental_days": null,
+ "retailer_name": "University Store",
+ "metadata": null,
+ "in_cart": null,
+ "selected": null,
+ "total": "206.65",
+ "retailer_order": 0,
+ "lowest_price": null,
+ "highest_price": null,
+ "comments": "Books Arriving Every Day-Check Frequently!",
+ "special_comment": "Pick it up or ship it!",
+ "store_branded": true,
+ "data_source": "bookstore",
+ "description": "From University Store on Fifth",
+ "seller_rating": null,
+ "fcondition": "New",
+ "fprice": "$206.65",
+ "discounted_shipping": null,
+ "can_checkout": false,
+ "feedback_count": null,
+ "location": null,
+ "price": "206.65",
+ "shipping": 0,
+ "variant": "new",
+ "source": "default",
+ "id": "bookstore_9780201763904_new_1445331:N"
+ },
+ {
+ "isbn": "9780201763904",
+ "retailer": "bookstore",
+ "item_id": "1445331:U",
+ "condition": "used",
+ "title": null,
+ "currency": null,
+ "merchant": "bookstore",
+ "rental_days": null,
+ "retailer_name": "University Store",
+ "metadata": null,
+ "in_cart": null,
+ "selected": null,
+ "total": "155.0",
+ "retailer_order": 0,
+ "lowest_price": null,
+ "highest_price": null,
+ "comments": "Pick it up or ship it!",
+ "special_comment": "Pick it up or ship it!",
+ "store_branded": true,
+ "data_source": "bookstore",
+ "description": "From University Store on Fifth",
+ "seller_rating": null,
+ "fcondition": "Used",
+ "fprice": "$155.00",
+ "discounted_shipping": null,
+ "can_checkout": true,
+ "feedback_count": null,
+ "location": null,
+ "price": "155.0",
+ "shipping": 0,
+ "variant": "used",
+ "source": "default",
+ "id": "bookstore_9780201763904_used_1445331:U"
+ }
+ ],
+ "section_id": null,
+ "db_section_id": null,
+ "inclusive_access": false,
+ "catalog_id": null,
+ "edition": "7",
+ "copyright_year": "2003",
+ "binding": null
+ }
+]
diff --git a/tests/textbook_test.py b/tests/textbook_test.py
index 5f8b2b7..88b95a7 100644
--- a/tests/textbook_test.py
+++ b/tests/textbook_test.py
@@ -17,192 +17,406 @@
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
+from pittapi import textbook
+
import responses
import json
import unittest
from pathlib import Path
+from pytest import mark
+from requests import ConnectionError
+from typing import Any
-from pittapi import textbook
-
-TERM = "1000"
SAMPLE_PATH = Path() / "tests" / "samples"
-
-print(SAMPLE_PATH.absolute())
+CSRF_TOKEN = "1MTtTVOcQCCXDjKNKTqkfiwp0lmLWz1RvFy2ed65XeyGO4on-8zWsQpEAt4cjiH0glx9CIyjhAOKpXhIqDK_vg"
+CS_SUBJECT_ID = "22457"
+MATH_SUBJECT_ID = "22528"
+CS_0441_GARRISON_SECTION_ID = "4558031"
+MATH_0430_PAN_SECTION_ID = "4631097"
class TextbookTest(unittest.TestCase):
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
- self.validate_term = textbook._validate_term
- self.validate_course = textbook._validate_course
+ with (SAMPLE_PATH / "textbook_base_page.html").open() as f:
+ self.html_text = f.read()
+ with (SAMPLE_PATH / "textbook_subjects.json").open() as f:
+ self.subjects_data = json.load(f)
with (SAMPLE_PATH / "textbook_courses_CS.json").open() as f:
self.cs_data = json.load(f)
- with (SAMPLE_PATH / "textbook_courses_STAT.json").open() as f:
- self.stat_data = json.load(f)
+ with (SAMPLE_PATH / "textbook_courses_MATH.json").open() as f:
+ self.math_data = json.load(f)
+ with (SAMPLE_PATH / "textbook_textbooks_CS_0441_garrison.json").open() as f:
+ self.cs_0441_textbook_data: list[dict[str, Any]] = json.load(f)
+ with (SAMPLE_PATH / "textbook_textbooks_MATH_0430_pan.json").open() as f:
+ self.math_0430_textbook_data: list[dict[str, Any]] = json.load(f)
- @responses.activate
- def test_textbook_get_textbook(self):
+ def setUp(self):
+ textbook.request_headers = None
+ textbook.subject_map = None
+ responses.start()
+
+ def tearDown(self):
+ responses.stop()
+ responses.reset()
+
+ def mock_base_site_success(self):
+ responses.add(responses.GET, "https://pitt.verbacompare.com/", body=self.html_text)
+
+ def mock_base_site_failure(self):
+ responses.add(responses.GET, "https://pitt.verbacompare.com/", status=400)
+
+ def mock_subject_map_success(self):
responses.add(
responses.GET,
- "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000",
- json=self.cs_data,
- status=200,
+ f"https://pitt.verbacompare.com/compare/departments/?term={textbook.CURRENT_TERM_ID}",
+ json=self.subjects_data,
)
- instructor_test = textbook.get_textbook(term=TERM, department="CS", course="445", instructor="GARRISON III")
- instructor_test = textbook.get_textbook(term=TERM, department="CS", course="445", instructor="GARRISON III")
- section_test = textbook.get_textbook(term=TERM, department="CS", course="445", section="1030")
- self.assertIsInstance(instructor_test, list)
- self.assertIsInstance(section_test, list)
+ def mock_subject_map_failure(self):
+ responses.add(
+ responses.GET, f"https://pitt.verbacompare.com/compare/departments/?term={textbook.CURRENT_TERM_ID}", status=400
+ )
- @responses.activate
- def test_textbook_get_textbooks(self):
+ def mock_cs_courses_success(self):
responses.add(
responses.GET,
- "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000",
+ f"https://pitt.verbacompare.com/compare/courses/?id={CS_SUBJECT_ID}&term_id={textbook.CURRENT_TERM_ID}",
json=self.cs_data,
- status=200,
)
+
+ def mock_cs_courses_failure(self):
responses.add(
responses.GET,
- "http://pitt.verbacompare.com/compare/courses/?id=22594&term_id=1000",
- json=self.stat_data,
- status=200,
+ f"https://pitt.verbacompare.com/compare/courses/?id={CS_SUBJECT_ID}&term_id={textbook.CURRENT_TERM_ID}",
+ status=400,
)
- multi_book_test = textbook.get_textbooks(
- term=TERM,
- courses=[
- {"department": "STAT", "course": "1000", "instructor": "WANG"},
- {"department": "CS", "course": "445", "section": "1030"},
- ],
- )
- self.assertIsInstance(multi_book_test, list)
- @responses.activate
- def test_get_textbook_invalid_term(self):
+ def mock_cs_0441_garrison_books_success(self):
responses.add(
responses.GET,
- "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000",
- json=self.cs_data,
- status=200,
+ f"https://pitt.verbacompare.com/compare/books?id={CS_0441_GARRISON_SECTION_ID}",
+ json=self.cs_0441_textbook_data,
)
- self.assertRaises(TypeError, textbook.get_textbook, "0000", "CS", "401")
- @responses.activate
- def test_get_textbook_invalid_subject(self):
- # TODO(@azharichenko): Added better subject verification in future update
+ def mock_cs_0441_garrison_books_none(self):
+ responses.add(responses.GET, f"https://pitt.verbacompare.com/compare/books?id={CS_0441_GARRISON_SECTION_ID}", json=[])
+
+ def mock_math_courses_success(self):
responses.add(
responses.GET,
- "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000",
- json=self.cs_data,
- status=200,
- )
- self.assertRaises(
- ValueError,
- textbook.get_textbook,
- TERM,
- "Computer Science",
- "000",
- "EXIST",
- None,
+ f"https://pitt.verbacompare.com/compare/courses/?id={MATH_SUBJECT_ID}&term_id={textbook.CURRENT_TERM_ID}",
+ json=self.math_data,
)
- @responses.activate
- def test_get_textbook_invalid_instructor(self):
+ def mock_math_courses_failure(self):
responses.add(
responses.GET,
- "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000",
- json=self.cs_data,
- status=200,
+ f"https://pitt.verbacompare.com/compare/courses/?id={MATH_SUBJECT_ID}&term_id={textbook.CURRENT_TERM_ID}",
+ status=400,
)
- self.assertRaises(LookupError, textbook.get_textbook, TERM, "CS", "447", "EXIST", None)
- @responses.activate
- def test_get_textbook_invalid_section(self):
+ def mock_math_0430_pan_books_success(self):
responses.add(
responses.GET,
- "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000",
- json=self.cs_data,
- status=200,
+ f"https://pitt.verbacompare.com/compare/books?id={MATH_0430_PAN_SECTION_ID}",
+ json=self.math_0430_textbook_data,
)
- self.assertRaises(LookupError, textbook.get_textbook, TERM, "CS", "401", None, "9999")
+ def test_course_info(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ subject, course_num, instructor, section_num = "CS", "0441", "GARRISON III", "1245"
+
+ course = textbook.CourseInfo(subject, course_num, instructor, section_num)
+
+ self.assertEqual(course.subject, subject)
+ self.assertEqual(course.course_num, course_num)
+ self.assertEqual(course.instructor, instructor)
+ self.assertEqual(course.section_num, section_num)
+
+ def test_course_info_convert_input(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ subject, course_num, instructor, section_num = "cs", "441", "garrison iii", "1245"
+
+ course = textbook.CourseInfo(subject, course_num, instructor, section_num)
+
+ self.assertEqual(course.subject, "CS")
+ self.assertEqual(course.course_num, "0441")
+ self.assertEqual(course.instructor, "GARRISON III")
+ self.assertEqual(course.section_num, section_num)
+
+ def test_course_info_missing_instructor_and_section_num(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ subject, course_num = "cs", "0441"
+
+ course = textbook.CourseInfo(subject, course_num)
+
+ self.assertEqual(course.subject, "CS")
+ self.assertEqual(course.course_num, "0441")
+ self.assertIsNone(course.instructor)
+ self.assertIsNone(course.section_num)
+
+ def test_course_info_invalid_subject(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ subject, course_num, instructor, section_num = "fake_subject", "0441", "GARRISON III", "1245"
+
+ self.assertRaises(LookupError, textbook.CourseInfo, subject, course_num, instructor, section_num)
+
+ def test_course_info_invalid_course_num(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ subject, course_num, instructor, section_num = "cs", "abc", "GARRISON III", "1245"
+
+ self.assertRaises(ValueError, textbook.CourseInfo, subject, course_num, instructor, section_num)
+
+ course_num = "44111"
+
+ self.assertRaises(ValueError, textbook.CourseInfo, subject, course_num, instructor, section_num)
+
+ def test_course_info_invalid_section_num(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ subject, course_num, instructor, section_num = "cs", "0441", "GARRISON III", "12456"
+
+ self.assertRaises(ValueError, textbook.CourseInfo, subject, course_num, instructor, section_num)
+
+ @mark.filterwarnings("ignore:Attempt")
@responses.activate
- def test_get_textbook_invalid_section_and_instructor(self):
- responses.add(
- responses.GET,
- "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000",
- json=self.cs_data,
- status=200,
+ def test_course_info_failing_header_requests(self):
+ self.mock_base_site_failure()
+
+ self.assertRaises(ConnectionError, textbook.CourseInfo, "CS", "0441", instructor="GARRISON III")
+
+ @responses.activate
+ def test_course_info_no_headers(self):
+ responses.add(responses.GET, "https://pitt.verbacompare.com/", body="")
+
+ self.assertRaises(ConnectionError, textbook.CourseInfo, "CS", "0441", instructor="GARRISON III")
+
+ @mark.filterwarnings("ignore:Attempt")
+ @responses.activate
+ def test_course_info_failing_subject_map_requests(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_failure()
+
+ self.assertRaises(ConnectionError, textbook.CourseInfo, "CS", "0441", instructor="GARRISON III")
+
+ def test_textbook_from_json(self):
+ self.assertEqual(len(self.cs_0441_textbook_data), 1)
+
+ textbook_info = textbook.Textbook.from_json(self.cs_0441_textbook_data[0])
+
+ self.assertIsNotNone(textbook_info)
+ self.assertEqual(textbook_info.title, "Ia Canvas Content")
+ self.assertEqual(textbook_info.author, "Redshelf Ia")
+ self.assertIsNone(textbook_info.edition)
+ self.assertEqual(textbook_info.isbn, "BSZWEWZWMZYJ")
+ self.assertEqual(textbook_info.citation, "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).")
+
+ def test_textbook_from_json_all_empty(self):
+ emptied_data: dict[str, Any] = self.cs_0441_textbook_data[0].copy()
+ emptied_data.pop("title")
+ emptied_data.pop("author")
+ emptied_data.pop("edition")
+ emptied_data.pop("isbn")
+ emptied_data.pop("citation")
+
+ textbook_info = textbook.Textbook.from_json(emptied_data)
+
+ self.assertIsNone(textbook_info)
+
+ @responses.activate
+ def test_get_textbooks_for_course_section_num(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_success()
+ self.mock_cs_0441_garrison_books_success()
+ course = textbook.CourseInfo("CS", "0441", section_num="1245")
+
+ textbooks = textbook.get_textbooks_for_course(course)
+
+ self.assertEqual(textbook.request_headers, {"X-CSRF-Token": CSRF_TOKEN})
+ self.assertEqual(len(textbook.subject_map), 168)
+ self.assertEqual(textbook.subject_map["CS"], CS_SUBJECT_ID)
+ self.assertEqual(len(textbooks), 1)
+ self.assertEqual(textbooks[0].title, "Ia Canvas Content")
+ self.assertEqual(textbooks[0].author, "Redshelf Ia")
+ self.assertIsNone(textbooks[0].edition)
+ self.assertEqual(textbooks[0].isbn, "BSZWEWZWMZYJ")
+ self.assertEqual(textbooks[0].citation, "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).")
+
+ @responses.activate
+ def test_get_textbooks_for_course_invalid_section_num(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_success()
+ self.mock_cs_0441_garrison_books_success()
+ course = textbook.CourseInfo("CS", "0441", section_num="0000")
+
+ self.assertRaises(LookupError, textbook.get_textbooks_for_course, course)
+
+ @responses.activate
+ def test_get_textbooks_for_course_instructor(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_success()
+ self.mock_cs_0441_garrison_books_success()
+ course = textbook.CourseInfo("CS", "0441", instructor="GARRISON III")
+
+ textbooks = textbook.get_textbooks_for_course(course)
+
+ self.assertEqual(textbook.request_headers, {"X-CSRF-Token": CSRF_TOKEN})
+ self.assertEqual(len(textbook.subject_map), 168)
+ self.assertEqual(textbook.subject_map["CS"], CS_SUBJECT_ID)
+ self.assertEqual(len(textbooks), 1)
+ self.assertEqual(textbooks[0].title, "Ia Canvas Content")
+ self.assertEqual(textbooks[0].author, "Redshelf Ia")
+ self.assertIsNone(textbooks[0].edition)
+ self.assertEqual(textbooks[0].isbn, "BSZWEWZWMZYJ")
+ self.assertEqual(textbooks[0].citation, "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).")
+
+ @responses.activate
+ def test_get_textbooks_for_course_invalid_instructor(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_success()
+ self.mock_cs_0441_garrison_books_success()
+ course = textbook.CourseInfo("CS", "0441", instructor="RAMIREZ")
+
+ self.assertRaises(LookupError, textbook.get_textbooks_for_course, course)
+
+ @responses.activate
+ def test_get_textbooks_for_course_invalid_course(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_success()
+ course = textbook.CourseInfo("CS", "0000")
+
+ self.assertRaises(LookupError, textbook.get_textbooks_for_course, course)
+
+ @responses.activate
+ def test_get_textbooks_for_course_deduce_section(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_math_courses_success()
+ self.mock_math_0430_pan_books_success()
+ course = textbook.CourseInfo("MATH", "0430")
+
+ textbooks = textbook.get_textbooks_for_course(course)
+
+ self.assertEqual(len(textbooks), 1)
+ self.assertEqual(textbooks[0].title, "First Course In Abstract Algebra")
+ self.assertEqual(textbooks[0].author, "Fraleigh")
+ self.assertEqual(textbooks[0].edition, "7")
+ self.assertEqual(textbooks[0].isbn, "9780201763904")
+ self.assertEqual(
+ textbooks[0].citation,
+ "\u003cem\u003eFirst Course In Abstract Algebra\u003c/em\u003e by Fraleigh. "
+ "Pearson Education, 7th Edition, 2002. (ISBN: 9780201763904).",
)
- self.assertRaises(TypeError, textbook.get_textbook, TERM, "CS", "401", None, None)
-
- def test_term_validation(self):
- self.assertEqual(self.validate_term(TERM), TERM)
-
- def test_term_validation_invalid(self):
- self.assertRaises(ValueError, self.validate_term, "1")
- self.assertRaises(ValueError, self.validate_term, "a")
- self.assertRaises(ValueError, self.validate_term, "100")
- self.assertRaises(ValueError, self.validate_term, "10000")
-
- def test_validate_course_correct_input(self):
- self.assertEqual(self.validate_course("0000"), "0000")
- self.assertEqual(self.validate_course("0001"), "0001")
- self.assertEqual(self.validate_course("0012"), "0012")
- self.assertEqual(self.validate_course("0123"), "0123")
- self.assertEqual(self.validate_course("1234"), "1234")
- self.assertEqual(self.validate_course("9999"), "9999")
-
- def test_validate_course_improper_input(self):
- self.assertEqual(self.validate_course("0"), "0000")
- self.assertEqual(self.validate_course("1"), "0001")
- self.assertEqual(self.validate_course("12"), "0012")
- self.assertEqual(self.validate_course("123"), "0123")
-
- def test_validate_course_incorrect_input(self):
- self.assertRaises(ValueError, self.validate_course, "")
- self.assertRaises(ValueError, self.validate_course, "00000")
- self.assertRaises(ValueError, self.validate_course, "11111")
- self.assertRaises(ValueError, self.validate_course, "hi")
-
- def test_construct_query(self):
- construct = textbook._construct_query
- course_query = "compare/courses/?id=9999&term_id=1111"
- book_query = "compare/books?id=9999"
-
- self.assertEqual(construct("courses", "9999", "1111"), course_query)
- self.assertEqual(construct("books", "9999"), book_query)
-
- def test_find_item(self):
- find = textbook._find_item("id", "key", "test")
- test_data = [
- {"id": 1, "key": 1},
- {"id": 2, "key": 4},
- {"id": 3, "key": 9},
- {"id": 4, "key": 16},
- {"id": 5, "key": 25},
- ]
- for i in range(1, 6):
- self.assertEqual(find(test_data, i), i**2)
+ @responses.activate
+ def test_get_textbooks_for_course_not_enough_info(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_success()
+ course = textbook.CourseInfo("CS", "0441")
+
+ self.assertRaises(LookupError, textbook.get_textbooks_for_course, course)
+
+ @mark.filterwarnings("ignore:Attempt")
+ @responses.activate
+ def test_get_textbooks_for_course_failing_courses_requests(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_failure()
+ course = textbook.CourseInfo("CS", "0441", instructor="GARRISON III")
- self.assertRaises(LookupError, find, test_data, 6)
+ self.assertRaises(ConnectionError, textbook.get_textbooks_for_course, course)
@responses.activate
- def test_extract_id(self):
+ def test_get_textbooks_for_course_no_textbook(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_success()
+ self.mock_cs_0441_garrison_books_none()
+ course = textbook.CourseInfo("CS", "0441", instructor="GARRISON III")
+
+ textbooks = textbook.get_textbooks_for_course(course)
+
+ self.assertEqual(len(textbooks), 0)
+
+ @mark.filterwarnings("ignore:No textbook info found")
+ @responses.activate
+ def test_get_textbooks_for_course_textbook_no_info(self):
+ emptied_data: dict[str, Any] = self.cs_0441_textbook_data[0].copy()
+ emptied_data.pop("title")
+ emptied_data.pop("author")
+ emptied_data.pop("edition")
+ emptied_data.pop("isbn")
+ emptied_data.pop("citation")
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_success()
responses.add(
- responses.GET,
- "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000",
- json=self.cs_data,
- status=201,
+ responses.GET, f"https://pitt.verbacompare.com/compare/books?id={CS_0441_GARRISON_SECTION_ID}", json=[emptied_data]
)
+ course = textbook.CourseInfo("CS", "0441", instructor="GARRISON III")
- def test_filter_dictionary(self):
- test_dict = {"a": 1, "b": 2, "c": 3}
- test_key = ["a", "c"]
- self.assertEqual(textbook._filter_dictionary(test_dict, test_key), {"a": 1, "c": 3})
+ textbook_info = textbook.get_textbooks_for_course(course)
+
+ self.assertEqual(len(textbook_info), 0)
+
+ @responses.activate
+ def test_get_textbooks_for_courses(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_success()
+ self.mock_math_courses_success()
+ self.mock_cs_0441_garrison_books_success()
+ self.mock_math_0430_pan_books_success()
+ courses = [
+ textbook.CourseInfo("CS", "0441", instructor="GARRISON III"),
+ textbook.CourseInfo("MATH", "0430", instructor="PAN"),
+ ]
+
+ textbooks = textbook.get_textbooks_for_courses(courses)
+
+ self.assertEqual(len(textbooks), 2)
+ # Sort to guarantee output order, since the textbook requests are async
+ textbooks.sort(key=lambda x: x.author if x.author else "")
+
+ self.assertEqual(textbooks[0].title, "First Course In Abstract Algebra")
+ self.assertEqual(textbooks[0].author, "Fraleigh")
+ self.assertEqual(textbooks[0].edition, "7")
+ self.assertEqual(textbooks[0].isbn, "9780201763904")
+ self.assertEqual(
+ textbooks[0].citation,
+ "\u003cem\u003eFirst Course In Abstract Algebra\u003c/em\u003e by Fraleigh. "
+ "Pearson Education, 7th Edition, 2002. (ISBN: 9780201763904).",
+ )
+
+ self.assertEqual(textbooks[1].title, "Ia Canvas Content")
+ self.assertEqual(textbooks[1].author, "Redshelf Ia")
+ self.assertIsNone(textbooks[1].edition)
+ self.assertEqual(textbooks[1].isbn, "BSZWEWZWMZYJ")
+ self.assertEqual(textbooks[1].citation, "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).")
+
+ @mark.filterwarnings("ignore:Attempt")
+ @responses.activate
+ def test_get_textbooks_for_courses_failing_courses_requests(self):
+ self.mock_base_site_success()
+ self.mock_subject_map_success()
+ self.mock_cs_courses_failure()
+ self.mock_math_courses_failure()
+ courses = [
+ textbook.CourseInfo("CS", "0441", instructor="GARRISON III"),
+ textbook.CourseInfo("MATH", "0430", instructor="PAN"),
+ ]
- def test_invalid_department_code(self):
- self.assertRaises(ValueError, textbook.get_textbook, TERM, "TEST", "000", "EXIST", None)
+ self.assertRaises(ConnectionError, textbook.get_textbooks_for_courses, courses)