Skip to content

Project 3: Advising Buddy

JMU Advising Buddy

Introduction

During the last few weeks, the JMU Computer Science Advisor has been overwhelmed with questions about CS course selection. She has determined that a technological solution is required and approached the CS 149 instructors for help. The plan is to create an application that will help students experiment with course sequencing while taking prerequisites into account. The CS faculty have promised to deliver the perfect application for the job: Advising Buddy™. Unfortunately, after completing the basic design and developing a stylish graphical interface, the CS faculty lost interest and decided to spend the rest of the semester attempting to resolve the Collatz conjecture. Finishing the application is up to you.

Provided Code and data

Stubs and docstrings for all modules are provided.

  • Review for Part A
    • buddy.py – Graphical interface to the Advising Buddy™ application (FINISHED).
    • cs_catalog.json – A JSON file containing information about all JMU CS courses.
    • japn_catalog.json – A much smaller JSON file containing information about a subset of JMU Japanese courses.
  • Submit for Part B
  • Submit for Part C

Note

As a reminder, docstrings are not required for test functions. But test modules should include a docstring at the top. Don't forget to write your name and date in each module.

Part A – Readiness Quiz

Before the deadline for Part A you should read this document carefully, then look at the starter code provided above. Once you have a clear understanding of the specification, complete the readiness quiz in Canvas. The grading for this quiz will be all or nothing: your score on the quiz will be 0 if you miss any questions. You have unlimited attempts.

Warning

If you do not successfully complete the readiness quiz on time, you cannot receive any credit for the entire project (100 points total).

Part B – Unit Testing

For this assignment, you will write the test code first. Writing test code before software is fully developed is a common practice in industry known as Test-Driven Development (TDD). While TDD may seem counterintuitive initially, TDD can lead to faster development in the long run. Writing tests will help you to understand the requirements and reduce debugging time later.

Write at least two assert statements for each function defined in test_schedule.py and test_catalog.py. Run pytest to make sure your tests modules load correctly. At this point, all of your tests should fail, given that none of the Part C code has been written. But after completing Part C, all of your tests should pass.

When you submit to Gradescope, your tests will be run against the sample solution for Part C. That way, you will know if your tests are correct. You will also see how many statements and branches in the sample solution that your tests cover. If you don't cover 100% of the sample solution, you are not testing every requirement.

You may assume that japn_catalog.json and cs_catalog.json will be available in the same folder as your testing files. Your tests must not rely on any other data files (except for new files that your tests create).

You have unlimited submissions for Part B. Your score will be based on both correctness and code coverage, as described in the previous paragraph.

Part C – Implementation

Part C will involve writing Python functions to work with student schedules and course catalogs.

Student Schedules

The schedule_utils.py file contains the docstrings for the required functions.

Internally, a course schedule is represented as a list of sets where each entry in the list represents an academic term, and the entries in the sets are strings representing the course ids of courses taken during that term. For example, the following schedule could represent a student's CS courses during the first three semesters at JMU:

[
   {'ALGEBRA'},            # First semester
   {'CS 149'},             # Second semester
   {'CS 227', 'CS 159'}    # Third semester
]

Since JSON files don't provide a built-in mechanism for saving and loading sets, we'll need to create a helper function to convert this format to a "list of lists" before the data can be saved.

The following example illustrates the expected format for the resulting JSON files. Notice that the course ids within each semester are stored in alphabetical order. Hint: Use sorted().

[
    [
        "ALGEBRA"
    ],
    [
        "CS 149"
    ],
    [
        "CS 159",
        "CS 227"
    ]
]

The schedule_utils.py file includes the following five unfinished functions:

  • schedule_to_json(schedule) – Convert a course schedule to a format suitable for saving as JSON.
  • save_schedule(schedule, filename) – Save the provided schedule as a JSON file with the indicated file name. When saving the file you must provide the optional indent argument so that entries will be indented four spaces as illustrated in the example above.
  • json_to_schedule(schedule_list) – The Python json module will load the schedule as a lists of lists. This function will convert that list of lists to a list of sets.
  • load_schedule(filename) – Read the indicated JSON file and return the corresponding schedule as a list of sets.
  • get_duplicates(schedule) – Return a set containing any duplicate course ids from the provided schedule. For example:

    >>> get_duplicates([{'CS 149'}, {'CS 227', 'CS 149'}])
    {'CS 149'}
    

    Implementation Advice

    Create a nested for loop where the outer loop iterates over the semesters and the inner loop iterates over the courses within each semester. During each iteration, maintain a set of all course ids that have been observed so far. Each time a course is processed, check if it appears in the set of observed courses. If it does, add it to the set of duplicates; if it doesn't, add it to the set of observed courses.

Course Catalogs

The full Advising Buddy™ application is designed to help students explore course options and sequence their courses so that all prerequisites are met. This requires working with information stored the course catalog. Internally, information about courses will be stored in a nested dictionary structure, where each entry in the dictionary represents a single course. The following example illustrates this internal representation:

Catalog Data

{'JAPN 101': {'credits': (4,),
              'description': 'The fundamentals of Japanese through listening, speaking, reading, and writing.',
              'name': 'Elementary Japanese I',
              'prerequisites': set()},
 'JAPN 102': {'credits': (4,),
              'description': 'The fundamentals of Japanese through listening, speaking, reading, and writing. Practice in pronunciation and development of comprehension.',
              'name': 'Elementary Japanese II',
              'prerequisites': {'JAPN 101'}},
 'JAPN 231': {'credits': (3, 4),
              'description': 'A thorough review of grammar, vocabulary building, conversation, composition, and reading.',
              'name': 'Intermediate Japanese I',
              'prerequisites': {'JAPN 102'}}}

The dictionary maps each course id to a nested dictionary that contains information about the course. Each nested dictionary contains four keys:

  • 'name' – Maps to a string representing the course name.
  • 'description' – Maps to a string representing the course description.
  • 'credits' – Maps to a tuple containing the options for the number of credits associated with the course. Most courses have a fixed number of credits, but some may have a variable number of credits. In this case, the credits element in the JSON file will contain two numbers separated by a dash, "1-3", representing the range of possible credits. The stored tuple would then be (1, 2, 3). See the description of the parse_credits method for more information.
  • 'prerequisites' – Maps to a set containing the ids of all prerequisite courses.

This catalog data will be loaded from provided JSON files. Here is an example illustrating the JSON format corresponding the the catalog information shown above:

JSON Version

{
    "JAPN 101": {
        "name": "Elementary Japanese I",
        "credits": "4",
        "description": "The fundamentals of Japanese through listening, speaking, reading, and writing.",
        "prerequisites": []
    },
    "JAPN 102": {
        "name": "Elementary Japanese II",
        "credits": "4",
        "description": "The fundamentals of Japanese through listening, speaking, reading, and writing. Practice in pronunciation and development of comprehension.",
        "prerequisites": [
            "JAPN 101"
        ]
    },
    "JAPN 231": {
        "name": "Intermediate Japanese I",
        "credits": "3-4",
        "description": "A thorough review of grammar, vocabulary building, conversation, composition, and reading.",
        "prerequisites": [
            "JAPN 102"
        ]
    }
}

This looks very similar to the dictionary shown above, but once again, we need to address the fact that the JSON format has no mechanism for storing Python data types like sets and tuples. Once the catalog data is read using the json module, you'll need to modify the resulting structure to convert the prerequisites for each course to a set, and the possible credits to a tuple.

The catalog_utils.py file includes the following unfinished functions:

  • parse_credits(credits) – Takes a string representing the range of credits associated with a particular course, and returns a tuple containing an ordered sequence of credit options. Ex:

    >>> parse_credits("3")
    (3,)
    >>> parse_credits("1-4")
    (1, 2, 3, 4)
    
  • json_to_catalog(json_dict) – Convert from the JSON catalog format to the correct internal format.

  • load_catalog(filename) – Reads a JSON catalog file and returns a properly structured dictionary containing course information. This means that load_catalog must return a dictionary that is formatted like the catalog data example above.

  • get_dependencies(course_id, catalog) – Return all courses that must be completed before a student is eligible to take the indicated course. This includes the course's prerequisites, the prerequisites for the prerequisites, and so on.

    Important

    The get_dependencies() function must be recursive.

  • format_course_info(course_id, catalog, width) – Return a formatted string including information about the indicated course. The resulting string will have five fields: Name, Description, Credits, Prerequisites, and Dependencies.

    Each field will be separated by a blank line and each will be wrapped to the maximum allowable number of characters as indicated by the width argument. The width must have a default value of 40.

    For example:

    >>> format_course_info('JAPN 231', catalog)
    Name: Intermediate Japanese I
    
    Description: A thorough review of
    grammar, vocabulary building,
    conversation, composition, and reading.
    
    Credits: 3
    
    Prerequisites: JAPN 102
    
    Dependencies: JAPN 101, JAPN 102
    

    For courses with a variable number of credits, the credit field must be formatted with a dash separating the minimum and maximum credits:

    Credits: 1-3
    

    The prerequisites and dependencies must be listed in alphabetical order.

    Implementation Advice

    Create one string for each of the five fields and use the textwrap module to wrap each of those strings to the desired width. You can then combine those five strings into a single string, possibly by placing them into a list and using the join method.

  • total_credits(schedule, catalog) – Return a tuple where the first entry is the minimum possible credits associated with the provided schedule and the second is the maximum possible credits. These will not be equal in the case where some courses in the schedule have variable credit amounts.

  • available_classes(schedule, semester, catalog) – Return a set containing course id's of available courses that could be added to the schedule for the indicated semester. A course is available if it satisfies the following conditions:

    • It must not already be in the schedule.
    • All of the course's prerequisites must be present in some earlier semester.

    For example:

    >>> available_classes([{'JAPN 101'}, set(), set()], 1, catalog)
    {'JAPN 102'}
    
    >>> available_classes([{'JAPN 101'}, set(), set()], 0, catalog)
    set()  # No other classes can be taken in semester 0
    

    Implementation Advice

    First, iterate over all semesters to construct a complete set of all courses in the schedule. Next, iterate over all semesters prior to the indicated semester to create a set of completed prerequisites. Then loop over all courses in the catalog and check whether each is valid. The set difference operator can be used to check whether a course's prerequisites are satisfied. Assuming that prereqs is a set containing the course prerequisites and completed is a set containing completed courses, prereqs - completed will evaluate to a set containing all of the elements in prereqs that are not in completed. If this set is empty, then all of the prerequisites have been met.

  • check_prerequisites(schedule, catalog) – Checks all courses in the schedule to determine if their prerequisites have been met at the time the course is taken. Returns a set containing all courses with unmet prerequisites.

    For example:

    >>> check_prerequisites([{'JAPN 101'}, set(), {'JAPN 231'}], catalog)
    {'JAPN 231'}
    
    >>> check_prerequisites([set(), {'JAPN 102'}, {'JAPN 231'}], catalog)
    {'JAPN 102'}
    

You will be limited to 10 submissions for Part C code.

PA3 Attribution

Provide a short statement describing any assistance that you received on this assignment, either from another student, an online source, an AI-enabled tool, or any other sources.