Perfect Home

Download as pdf or txt
Download as pdf or txt
You are on page 1of 12

Task 3 - Process Amenities

In this task, you MUST use the csv and the json modules to read and manipulate other files being
provided to you. We are going to create some more dictionaries for each of the following files:

1. melbourne_schools.csv contains a CSV formatted list of schools in the city of Melbourne. This
file contains many fields but we are only concerned about the following:
school_no , which contains the school number,

school_name , which contains the school name,


school_type , which contains the type of the school (Primary, Secondary, or Pri/Sec
which means both),
school_lat , which contains the latitude value of the school,

school_lon , which contains the longitude value of the school.


2. melbourne_medical.csv contains a CSV formatted list of GPs in Melbourne. This file contains a
few fields but we are only concerned about the following:
gp_code ; which contains the code of the GP service,
gp_name ; which contains the name of the GP service,

location ; which contains the latitude and longitude of the service in a particular format.
You should read the file (either manually or through code) to figure out how to read and
process the latitude and longitude.
3. sport_facilities.csv contains a CSV formatted list of sport facilities in Melbourne. This file
contains a few fields but we are only concerned about the following:
facility_id ; which contains the ID of the sports facility,

facility_name ; which contains the name of the sports facility,


sport_lat ; which contains the latitude value of the sports facility,
sport_lon ; which contains the longitude value of the sports facility,
sport_played ; which contains the sport played at the facility.

Your task is to complete the functionality of the two functions given to you:

1. process_schools(file_name: str) -> dict : this function should read the file passed as
file_name and return a dictionary that contains all the schools read from the file where the key
is a string version of the school_no and the value is a dictionary containing five keys (and their
appropriate value data types) ( school_no <str> , school_name <str> , school_type <str> ,
school_lat <float> and school_lon <float> ).
2. process_medicals(file_name: str) -> dict : this function should read the file passed as
file_name and return a dictionary that contains all the GPs read from the file where the key is
the gp_code and the value is a dictionary containing four keys (and their appropriate value data
types) ( gp_code <str> , gp_name <str> , gp_lat <float> , gp_lon <float> ).
3. process_sport(file_name: str) -> dict : this function should read the file passed as
file_name and return a dictionary that contains all the sport facilities read from the file where
the key is the facility_id and the value is a dictionary containing five keys (and their
appropriate value data types) ( facility_id <str> , facility_name <str> , sport_lat
<float> , sport_lon <float> , sport_played <str> ).

Sample files have been provided to you in this task and can be used to test your code.

You will notice that there are some rows where the location or latitude or longitude is empty, or
marked 'NA'. In these cases, you are supposed to ignore those rows and move on to the next row.

Sometimes you will see some junk characters being read such as '\ufeff' in your file. If this is the case, when
you use the appropriate function from the csv module, make sure to mention the encoding as 'utf-8-sig'
and that should get rid of the junk characters.

You have also been given some code in the main block which should run if you have completed the three
functions properly.
Task 4 - Making Classes Using OOP
We have had our fun with dictionaries, but we must bid them adieu. Now that you know about a
much more robust form of data storage, we will convert all that we have worked with so far into
classes and objects.

Some boilerplate code has been provided for you in various files, so open up the file explorer to see
what you have. You have to read the instructions in the docstrings of the methods, create the classes,
and define the methods.

Part 1 - The Property Class and its Children


The Property class will be the overarching abstract base class for all properties. This class will have
two child classes; House and Apartment class. These classes should have one or many variables
each to hold the following information:

1. the property's ID,


2. the property's address,
3. the property's suburb,
4. the number of bedrooms the property has,
5. the number of bathrooms the property has,
6. the type of property,
7. the number of parking spaces the property has,
8. a tuple containing the (latitude, longitude) of the property,
9. the floor number of the property,
10. the land area of the property,
11. the floor area of the property,
12. the price of the property,
13. a list of the features of the property.

You have to choose if these should be class or instance variables, and which class they should be
defined in. Make sure that the data type of these variables also match what you did in Task 1. The
only exception is the coordinates, which is now a tuple of floats (latitude, longitude).

You should define getters and setters for these variables. There are exceptions for these, namely:
prop_id , full_address , prop_type , coordinates and suburb are static fields and hence do not
need setters, only getters. These values for a property cannot change as that would change the
property itself. The rest of the attributes can change (think renovation)

Getters and setters help us implement an important aspect of OOP called Encapsulation. You can read more
about it here.

For example for the variable bedrooms , one of your classes should have the method:
get_bedrooms(self) -> int that returns the number of bedrooms that exist in the property and
also a method set_bedrooms(self, num_bedrooms: int) -> None that sets the value
num_bedrooms to the bedrooms variable in the class.

You will have some methods as abstract methods in the Property class. When overriding the method, if that
particular method should not have an implementation in one of the child classes, make it return None .

Completing this section should pass the tests from 4.1 to 4.20.

Part 2 - The Amenity Class


The Amenity class will help hold all the information about the various amenities we learned about in
the previous tasks (Train stations, Medical Centres, Sport Facilities, Schools). This class will not have
any children classes, but would have the following variables and their appropriate getters and
setters :

1. a variable to store the gp_code , school_no , stop_id or facility_id ,


2. a variable to store the gp_name , school_name , stop_name or facility_name ,
3. a variable to store the coordinates (latitude, longitude) of the amenity in a tuple of floats,
4. a variable to store the type of amenity ('school', 'sport_facility', 'train_station' or
'medical_centre'),
5. a variable to store the subtype of the amenity ('primary', 'secondary', 'pri/sec' for schools, or
whatever sport is played at the sport_facility). If the amenity does not have a subtype, then just
set this to None .

Please DO NOT add any additional parameters to ANY of the given functions.

Again, there are exceptions for these getters and setters , namely: amenity code , amenity type
and amenity coords are static fields and hence do not need setters, only getters.

Completing this part should pass the tests from 4.21 to 4.22.

Additional Important Information About Task 4


You have been given some code in the file ingestion.py including a function called ingest_files .
The purpose of the ingest_files function here is to read the five files given to you
( sample_properties.csv , train_stations.csv , sample_melbourne_schools.csv ,
sample_melbourne_medical.csv , sample_melbourne_sport_facilities.csv ) and create two lists:

1. properties which is a list of objects from the House/Apartment/Property family,


2. amenities which is a list of objects from the Amenity class.

You do not need to change this file or this function. As long as you have created the four classes
properly (Property, House, Apartment, Amenity) then this function should run properly and produce
some output for you.

Please remember to always use the getter and setter methods to access the class and instance
variables where appropriate. Using the instance variables directly where a getter or a setter should have
been used will result in a loss of marks.

Some code has already been given to you in the file at the end, and if your classes and the main() method
have been successfully set up, it should print out some legible output.

� YOU MUST NOT CHANGE any of the following elements of the skeleton.

Class names.
Class method names, parameters, or return types.

� YOU MUST CHANGE

all other aspects of the skeleton required to produce a functional well-structured solution to the
assignment.

� YOU CAN CHANGE

everything that is in the if __name__ == '__main__' block of code.


instance and class variables for any of the classes. You have to decide how many class and
instance variables to make and what their purpose should be.
Task 5 - Adding More Methods Using More OOP
Now that you have working classes, we will refine them slightly. Read the instructions carefully and
then edit the classes from the previous tasks.

Part 1 - Editing Property Features


In Task 1, we created two functions to add_feature(self, feature: str) -> None and
remove_feature(self, feature: str) -> None . We are now going to implement those as methods
in the property classes. You may or may not need to implement in one or all of the three property
classes, you need to figure out where it would suit best.

You need to copy and paste the code for these classes from Task 4 into the appropriate files given in
this slide's scaffold and then add these methods.

You should try and use the methods that have been defined in the classes rather than calling the variables
themselves. Doing so will result in a loss of marks.

Completing this part should pass the tests from 5.1 to 5.4.

Part 2 - Finding the Nearest Amenity


To the Property class, you need to add a method called nearest_amenity(self, amenities:
List[Amenity], amenity_type: str, amenity_subtype: str = None) -> Tuple[Amenity,
float] which will take as an input a list of amenities, the type of the amenity and the subtype (if
applicable), and return a Tuple containing an object of the Amenity class that is the closest amenity of
this type and subtype as well as the distance to this particular Amenity.

Please note that if the subtype is Pri/Sec for a school, then that school should appear in both Primary and
Secondary searches.

If a amenity_subtype is not provided, then it should consider all amenities of that type and return the closest
one.

You may want to copy the haversine function to this class but ensure to make it a private function of this
class rather than a global function in the file as this will fail test cases.

Completing this part should pass the tests 5.5 and 5.6

Additional Important Information About Task 5


As a reminder, the file ingestion.py that reads the properties and amenities has been provided to
you and organizes them into lists. You may read this file if you require but you DO NOT NEED TO OR
SHOULD CHANGE THIS FILE AT ALL.

Once you add the three new methods, and if all works well then you should see an output similar to
the following:

The nearest station to 3 Antrim Place Langwarrin VIC 3910 is Baxter Railway Station (Baxter) and it is
The property at G01/7 Rugby Road Hughesdale VIC 3166 has the following features: ['dishwasher', 'air co
We are going to add 'tennis court' as a feature to this property
Now the property at G01/7 Rugby Road Hughesdale VIC 3166 has the following features: ['dishwasher', 'ai
Task 6 - Producing Suitability Score
You've done extremely well to get to this stage! Pat yourself on the back! �

We will now use the information that you have ingested and apply that to a real-world scenario. We
will import a request from the user to provide a suitability score for each property and then provide a
list of properties that fit the user's criteria.

Part 1 - Read the User's Request in a JSON Format


To do this, you are going to need the json module as the requests are going to be handled using
json files. The user's request is provided to you in the file request.json and has the following
format:

{
"request": {
"house_importance": {
"suburb": "Clayton",
"prop_type": "Apartment",
"bedrooms": 3,
"bathrooms": 2,
"parking_spaces": 1,
"price": 900000,
"property_features":"air conditioning"
},
"amenities_accessibility": {
"train_station": "walk",
"school": {
"school_type": "Primary",
"accessibility": "cycle"
},
"medical_centre": "drive",
"sport_facility": {
"sport_played": "Cricket",
"accessibility": "optional"
}
}
}
}

Please note that some or all fields in the house_importance may or may not exist in the request. The valid
fields are the variables that you created in the Property family. Similarly, in the amenities school and
sport_facility, the school_type and sport_played may or may not exist. If they don't exist then you should
consider all amenities in that category. Similar to the previous task, if a user asks for a Primary or a Secondary
school then the Pri/Sec schools should be considered in both categories.
Your task is to write the appropriate code in the function read_request which returns two
dictionaries; house_importance and amenity_accessibility based on the JSON request being
read.

Completing this part should pass test 6.1.

Part 2 - Collect Properties that Match the Given Criteria in the JSON
Request
You will now use the dictionary house_importance obtained from the previous part as well as the list
of all properties that has been ingested for you in the variable props and return a list of matching
properties based on the user's request. The return value should be a list that contains objects of the
Property Family (i.e., House/Apartment/Property). This will be done in the
find_matching_properties function.

There are certain constraints for selecting a property based on the request:

1. For property type and suburb, consider only a full match; i.e. if the request is for a house, filter
out apartments.
2. For property features; consider a full match in the list. If the feature is mentioned in the request
and is not present in the property, that property should be filtered out.
3. For floor area, land area, bedrooms, bathrooms and parking_spaces, you should treat the given
values as the baseline. Anything higher than or equal to the value should be accepted.
4. For the floor number and price, treat the given value as the ceiling. Anything less than or equal
to the value should be accepted.

For item 2 above, please assume that the request will only contain one property feature.

While filtering properties, any field that is missing in the request should not be considered as a filtering criteria.
For example, if the request does not contain a value for bedrooms, you should keep all possible values of
bedrooms. This applies to all fields.

Completing this part should pass the test cases 6.2 and 6.3.

Additional Information About Scoring the of Properties


If you notice the function produce_star_scores , once you have completed parts 1 and 2, it should
now produce some star scores for the properties using some complex math that is contained in the
score.py file. You need not worry about the math behind this step.

After this step is complete, you will have a dictionary in the variable called prop_scored that
contains the star_score as the key and the property object as the value. An example of the format of
the prop_scored dictionary is as below:
prop_scored = {
"3.5" : <Property_Object_1>,
"2.3" : <Property_Object_2>,
"1.3" : <Property_Object_3>
}

Part 3 - Create a Response Dictionary and Return


In this part, you will need to define the function create_response_dict that takes in the dictionary
prop_scored and creates the dictionary response_dict . The response dictionary should be in a
JSON valid format and an example is given below:

{
"properties": [
{
"property_id": "P1001",
"star_score": 4.2
},
{
"property_id": "P1002",
"star_score": 3.8
},
{
"property_id": "P1003",
"star_score": 3.5
}
]
}

Once you create this dictionary, you should return it from the function.

Completing this part should pass the test cases 6.4, 6.5 and 6.6

Part 4 - Responding in a JSON file


You will now write the code for the respond function.

respond(response_dict: dict) -> None : This function reads the return value from the function
produce_star_scores and writes the file called response.json . While writing the response files,
sort the scores in descending order by star_score and if there is a tie then ascending order of
prop_id. Unfortunately due to the limitations of Ed, simply pressing the 'Run' button will not create
your response file, you need to open the terminal and manually execute the task6.py file. Then, you
should be able to see the response.json file in your scaffold.

It is YOUR RESPONSIBILITY to validate your JSON file. You can do so by copying the content of your
response.json file and checking its validity on a website such as JSONFormatter.

There are no tests for this task, your file will be manually checked by a tutor.
Additional Important Information About Task 6
You must copy and paste the code for all four classes from Task 5 into the appropriate files
given in the scaffold.
You have been given a couple of functions and their arguments in the scaffold that you need to
use, however, you are STRONGLY RECOMMENDED to define other functions that are called
within the given functions. Please do not edit the arguments or the return value types that our
functions require.
Once you have completed your code in task6.py , you should be able to run the code given in
the if __name__ == '__main__' block and produce the response.json file while printing the
success or the failure of your code.
# returned dictionary should look like:
returned_dict = {
"unit_no" : 'G101',
"street_no" : "7",
"street_name": "Rugby",
"street_type": "Rd",
"suburb": "Hughesdale",
"state": "VIC",
"postcode": "3166"
}

Example 2

address_str = "85 Point Cook Rd Point Cook VIC 3030"

# returned dictionary should look like:


returned_dict = {
"unit_no" : None,
"street_no" : "85",
"street_name": "Point Cook",
"street_type": "Rd",
"suburb": "Point Cook",
"state": "VIC",
"postcode": "3030"
}

Example 3
address_str = "Floor 10/45 The Avenue St Toorak VIC-3142"

# returned dictionary should look like:


returned_dict = {
"unit_no" : 'Floor 10',
"street_no" : "45",
"street_name": "The Avenue",
"street_type": "St",
"suburb": "Toorak",
"state": "VIC",
"postcode": "3142"
}

Example 4 (Redundant Spaces)


address_str = "FL10/45 The Avenue St Toorak VIC-3142"

# returned dictionary should look like:


returned_dict = {
"unit_no" : 'FL10',
"street_no" : "45",
"street_name": "The Avenue",
"street_type": "St",

You might also like