After completing this assignment, you will be able to:
- read code you didn't write and understand both its design and implementation; specifically:
- read the class and method docstrings carefully (this includes, but is not limited to: attributes, representation invariants, and preconditions)
- determine both "has a" and "is a" relationships between classes, by which you can apply your knowledge of composition and inheritance
- ensure that your code follows the OOP design principles (e.g., encapsulation) covered in this course
- complete partial implementation of a class, including:
- reading the representation invariants to enforce important facts about implementation decisions
- reading the preconditions to factor in assumptions that they permit
- writing the required methods
- perform test-driven development via unit-testing with pytest, specifically:
- considering both generic and edge cases when developing the application
- writing good test cases to ensure robust code
Python Air (PA) is a popular budget airline that has been up and coming in the international computer science community. Being a member of that community, they are reaching out to you to help with the development of their data management software.
PA wants you to read in a dataset of airports, customers, flight segments, and past trips which have already taken place in some interval of time in 2019. Further, they want to produce a visualization of these datasets. This visualization should allow the Python Air's operators to search for various bits of information based on their needs, in a visual manner. The operators will get all flight segments taken by all customers in the dataset interval and narrow down their search by filtering the data on various criteria. We use the term "to filter" to indicate that we want to display only those flight segments which match the search criterion.
PA is still in the early development stages; this means that this is not a "real-time" system where flight segments may change, be canceled, or newly created based on real-world events. Additionally, we do not have to worry about a passenger missing a trip or any other real-time events. Rather, this software is here to visualize and compute historic events for use of PA's Data Analytics team.
That being said, PA's Data Analytics team does have the option to make some changes in the system, to the objects storing information. For instance, you will implement the ability to cancel trips for a given customer, even if this feature is not connected with the visual interface of the application.
Furthermore, the classes you will implement may include some methods which are never used in the present application, and these decisions will be left in your capable hands. Remember, though you will not be penalized for inefficient code, the more efficient your code the more usable the application will be for future versions.
Download the starter code.
Python Air (PA) has designed its software to be broken into the following components:
- Airport ( )
- Customer ( )
- Filter ( )
- FlightSegment ( )
- Trip ( )
- Visualizer ( )
- The main application ( )
A Python Air (PA) Customer has several attributes associated to them, the most important is their customer identifier (a 6-digit integer) that provides us with a means to uniquely identify them. Among other attributes, customers all have an(frequent flyer status), (PA's qualifying miles), and .
is a customer's frequent flyer status, which is an achievement of loyalty through taking trips with PA. The statuses are identified in a static variable coined in . Once a status is achieved, it is never lost.
is a customer's qualifying miles on PA, which is the sum of all flight segments' length multiplied by their respective multiplier (i.e. see in for reference) that a particular customer has taken over the course of time. The number of miles will determine what a customer is given (you may visit in for reference to this association). are accumulated over the course of one's life, which means they are never lost. Note: even though the variable is called , the variable's unit of measure is actually kilometres (so there is no need to convert between miles and kilomteres).
is the total cost of all flight segments that a customer has taken. This is computed by multiplying the flight segment's base fare cost with their seat class (i.e. utilizing the ), as well as, finding and applying their discount (see in ). The values of the tuples are (kilometres, percent), where a negative percent indicates a discount (e.g. a tuple (15000, -10) would be interpreted as 15,000KMs and -10% discount).
An Airport is a data structure which represents international airports, specifically their, a unique IATA (International Air Transport Association) location identifier , and a (longitude, latitude) tuple which stores that airport's geographical coordinate.
A FlightSegment is a PA flight that tracks everything from its departure airport location to its arrival airport location, the manifest, seat availability and capacity, base fare cost, etc. Some of this data requires computation through the use of private (helper) methods. Unlike airport and customer, the unique identifier of a FlightSegment is the combination of its flight id and departure time.
The flight id represents an alpha-numeric string uniquely distinguishing all flights that depart from the same location and arrive at the same location (i.e. IATA location identifiers).
Thus, the combination of both the flight identifier and time of departure will allow us to uniquely identify that flight. No two flights will have the same flight identifier and departure time.
A Trip is composed of several, or one, flight segments which makes up a customer's itinerary (i.e. the series of flights getting the customer from their original departure airport to their end arrival airport). Every trip has a unique identifierwhich allows us to identify the customer who made the booking, but it also stores an alias of every flight segment that is part of this customer's itinerary. It should be noted that for a trip to be created, it must be composed of all the flight segments of that customer's itinerary. If one of the flight segments cannot be booked in any day within the timeframe of the dataset, then the whole trip does not get stored, as the trip cannot be booked.
A Filter represents a specific criterion to filter the flight segments so that the user will be able to visually see their final refined result. There are several types of filters, as described in the starter code.
The Visualizer is a class that does all the graphical work for this assignment. You do not need to code anything here, just know that it exists and is handling this for you.
Finally, we have the Application which does the data processing (reading, parsing, and cleaning), instantiation and storage of all PA's objects, and launches the graphical user interface.
To aid in the rapid progression of software development, Python Air has provided you with bothand normal data. The following data files have been given:
Thedatasets include data from January 1st, 2019 through January 31st, 2019, while the regular sized versions include data from January 1st, 2019 through June 30th, 2019. Further, both and are files which are used when loading either the small or regular sized datasets.
NOTE: data is assumed to be correct and complete. This means that there will never be errors in the CSV files. CSV files will have exactly one blank line at the end of them.
This CSV-file contains the data of all international airports that Python Air has deals with. The data is provided in the following order: unique airport IATA location identifier, airport name, longitude, and latitude.
For example, let's take this airport:
the data can be parsed as follows:
This CSV-file contains the data of all customers that Python Air currently has stored in their company database. The data is provided in the following order: unique customer identifier, customer name, customer age, and customer nationality.
For example, let's take this customer:
the data can be parsed as follows:
This CSV-file contains the data of all flight segments that Python Air operated in 2019. The data is provided in the following order: flight identifier, departure IATA location identifier, arrival IATA location identifier, date of flight segment departure, time of flight segment departure, time of flight segment arrival, and flight segment length (in KMs).
For example, let's take this trip:
the data can be parsed as follows:
This CSV-file contains the data of all customers' trips that Python Air operated in 2019. The data is provided in the following order: unique reservation identifier, unique customer identifier, date of departure, itinerary list.
For example, let's take this trip:
the data can be parsed as follows:
and, for clarity, the itinerary can be read as follows flight segments:
- (Moscow) --> (Rome) in class
- (Rome) --> (Toronto) in class
- (Toronto) --> (Cairo) in class
- (Cairo) --> (Hong Kong) in class
- (Hong Kong) --> (Dubai) in class
Trips will always be well formed. This means a trip in the dataset will always have a departure and arrival location. You will never have partial/missing data.
NOTE: any sample parsing provided in the examples above, does not show the conversion into the correct types from the type contracts. It is solely showing you how the string maps to the variable of the associated class. The variable may need to be private!
You are now ready to begin writing code! A note about doctests: we have omitted doctests from most of the starter code, and you are not required to write doctests for this assignment. Instead, you will write your tests in pytest because, in this assignment, initializing objects for testing is more involved than usual, resulting in messy docstrings. However, you will be required to follow all other aspects of the class design recipe.
Before starting to code, review your four datasets. I would suggest taking a piece of paper and recording the 'headers' of each of the data files. They are shown above. Additionally, if you are not familiar with themodule yet, read the .
Do not duplicate code! If you are given a method, use it! If you need to write a helper function because a piece of code is utilized over and over, do it. But only if it is necessary. Remember to make helpers private, and to follow data encapsulation principles.
Task 1: Complete the Initializers and Getters
Your first code-writing task is to complete the initializers (this includes initializing the specified default parameters) and getter methods only of the following classes (according to the docstring and type contract) in the order stated:
Airport: this means you will complete the entire class.
FlightSegment: ensure that you set any necessary attributes appropriately.
Note: The base fare cost of the flight segment is calculated at aof $0.1225/km. The actual cost of a flight will change depending on the booking class (i.e. utilizing the ).
Hint: helper functions may come in handy here!
Customer: do not completeat this stage, you will do this in 3.1.
Trip: do not completeand at this stage, you will do it in 3.3.
Task 2: Reading, Parsing, and Loading Data
In the main application, implement the following four functions (in the order stated):
To test this, when you launchyou should be able to match the statistics printed to the terminal with the number of lines in your dataset. In addition, you may consider verifying that your intended class objects have been properly created by leveraging the getter methods created in Task 1.
Foryou may start thinking about implementation here, move on to Task 3, then circle back. There will be a bit of a refinement process.
Task 3: Complete the Methods
Task 3.1: Customer Class
Customer objects not only store data about themselves, but also contain features such asand . By booking a given trip a customer instance will store a list of Trip objects as well as book all the flight segments for this particular reservation.
Booking a trip entails a great many things, the primary of which is to actually book that customer's seat type on each specific FlightSegment in their itinerary. Further, booking each flight segment means you must:
verify availability and ensure that the customer is never occupying more than one seat on the same flight segment
update that customer's frequent flyer status, their airline qualifying miles, as well as, update their total flight costs
Note: a Trip is booked given the customer's current frequent flyer status at the time immediately before the booking. In other words, their frequent flyer status does not change until all flight segments of that trip have been booked.
The methodis only invoked by Python Air's staff through the use of the Customer class. You may assume that a Trip will only ever be canceled by the airline before it is taken. Canceling a trip will not reverse any benefits that a customer would have received, however, it does provide them with a full refund and a $100 voucher (applied to reduce ). If the customer had not spent more than $100 on trips at that point in time, their balance could be negative to indicate a credit (usable on future Python Air Trips).
Note: a canceled trip will continue to be displayed until a reset filter is applied (further details on filters in Task 4).
Complete the methods in the following order:
Task 3.2. FlightSegment Class
A flight segment is a flight that is taken from one departure airport to one
arrival airport (this is different from a Trip, see 3.3!). Each flight segment will store details about itself. One of its most important details is its , which stores all customer identifiers and their seat types. In addition, it has several functionalities, one of which is its ability to for a specific customer on that flight. It should be noted that by booking a seat on a specific flight we must ensure that we check the manifest and verify that there is availability for that specific seat type. We cannot overfill a specific cabin type (i.e. "Economy" or "Business").
Note (updated sentence, for reducing ambiguity): this method should be called before booking the trip (you will have to figure out where it's most appropriate, so make sure to revisit the previous tasks at this point), to ensure that the customer already has a seat in each flight segment, before the trip can be booked. You must make sure that a customer is never double-booked (see the docstring).
A flight segment will also have the ability to, for a Customer whose Trip involving this FlightSegment was canceled by the airline.
Complete the methods in the following order:
Task 3.3: Trip Class
Trips are composed of one or more flight segments, making up a customer's entire travel itinerary. Trips have several attributes, one of which stores a list of all FlightSegment objects that are associated to this specific trip.
At this point, you can complete the following two methods in this order:
Task 4: Filtering Flight Segments and Displaying Summary
Now that you have the data loaded and recorded in the system, as well as, bookings completed, we are ready to add the filtering feature. This feature will allow the user to filter the flights that are displayed, for example so that they see only flights taken by a certain customer or all flights which depart/arrive from a specific airport.
First, let's understand the various filters and what they will do. If you run the application, the menu bar in the visualization window informs the application user of the supported types of actions. The filtering actions are invoked by pressing the following keys (case insensitive):
- c: show only flight segments belonging to a given customer id
- d: show only flight segments with at least (or at most) a given duration (which is specified in minutes)
- l: show only flights (departing from or arriving at) a given airport location (which is specified by their IATA location identifier)
- t: show only flight segments belonging to a given trip (aka reservation) id; this will always uniquely find a trip even if it has previously been filtered out
- y: show only flight segments that have departed airports and arrived between two dates (because why not :))
- s: pretty prints to the terminal a summary belonging to a specific trip (aka reservation) id
- r: reset all filters applied so far
- q: quit the application gracefully
Once the corresponding key is pressed, filters that require additional information ask the user to enter it. For example, the 'c' filter asks the user to enter the desired customer id.
You must follow the required input guide, violating this input will be followed (and caught) by an error. For example, the "d" filter requires either a "G" (for strictly Greater than) or "L" (for strictly Less than) #### integers; which are either leading zero filled (e.g. 50-minutes would be 0050). Or, another example, the "l" filter requires a "D" (for Departure) or "A" (for Arrival) XXX string; which is an attempt at a 3-character IATA location identifier. Further, there are some events which may result in the application crashing (see Known Application Quirks for reference).
Note: The first time you press a key to apply a filter, you will see a window pop-up with a welcome message from Python Air's Frequent Flyer System. This window will appear on top of your filter pop-up, so you must close the welcome pop-up before you can proceed to applying a filter. Often it is asked, "Why does this happen?", as you would expect that window to pop-up when you first launch the application, rather than when you press a filter key. However, Python Air decided to meddle with the application's functionality, and we are now stuck with this behaviour, which you can safely ignore! :)
The user can apply one filter after another. The visualization starts by showing all the flight segments in the system that have a manifest of at least one Python Air customer. Then, as filters are applied it will gradually narrow down the search by only querying the remaining records left after applying the previous filter.
For example, you can apply a 'c' filter, followed by a 'd' filter, and then a 'y' filter, in order to determine all the flights taken by a certain customer, with a duration over or under some number of minutes, followed by a range of dates which that customer flew between. The 'r' switch resets all the filters applied so far and restarts the search on the entire dataset.
This is what the code will do. You need to implement all of the incomplete filter classes.
Note: If you are curious to know how the actual visualization process works, you are encouraged to look into, but you do not need to understand the visualizer module in order to solve this assignment. However, if you are curious to explore the module, we recommend that you read the docstring of the method of the class. The main block in the module sends it a list of s, which it then draws them on the pygame window. You might wish to also look at the method, which takes input from the application user and acts accordingly. You will notice a whole bunch of filter objects, of various types, for example, , , , etc. Additionally, there is a method which will a summary of a given Trip to the terminal window.
Task 4.1: Implement the filters
In module, review the class and its documentation carefully. The actual filtering work is done in the method, which acts accordingly for each type of filter. Your task is to implement all the apply() methods in the filter classes which subclass Filter.
Each of the filters has its own definition of what is a valid user input. This is displayed to the user in the application as a visual prompt (themethod returns the visual prompt message). Nevertheless, a user might still enter incorrectly formatted input, or may enter invalid input (e.g., a customer id which does not exist). For each filter, you must enforce "sanity" checks in the method to ensure that your code will not crash if given an incorrect or invalid input. We want you to consider how malformed or invalid inputs (as given in the argument) can impact your implementation of the apply() method, and to make your code robust. We are not explicitly telling you what cases to consider because we want to encourage you to consider robustness during your implementation. Ultimately, if during the execution of your program a cat walks across your keyboard (don't they love to? :D) your code should not crash and it should do the right thing.
There are two ways to check for incorrect inputs: - ensure that an incorrect input cannot crash your code by using explicit validity checks (e.g. using if statements); or - use try/except blocks to catch whichever Exception may be raised by your code due to an incorrect or invalid input.
For the latter, revisit the lecture prep on Exceptions for an example of how to handle exceptions.
Visualize your work: run theand try applying each filter. As you apply more and more filters, some flight segments will disappear from the map. Keep in mind that although visualizat