Having two kids in the NYC Public school system, we are getting to the time of year, when the kids start asking me how many days left until the end of the school year. Normally, if I have the time, I'll grab the calendar, and count the # of days from the last school day back until today, of course omitting weekends and "holidays" (which I arbitrarily use to describe any day when there is no school).
Since we are a family that has an Amazon Echo which we use frequently, I started to think this would be a decent task to use Alexa for, so we could just ask Alexa "How many school days left?" This is exactly what I have done.
See v1 tag of codebase. https://github.com/jefftapper/SchoolDays/releases/tag/v1
So, for the initial release, I followed exactly the same logic. I created AlexaSchoolDays.py which is a python file combining the Alexa boiler plate with my own specific logic. Starting on line 206 you can see my specific methods. Starting from the bottom of the file, you will find makeVacationList() in which I list out all of the school years vacation days, create a date object for each, and pass them into the makeVacationDay method, to build the dictionary of vacation days. Above that I created a convenience method to return the last day of school. The one before that is where most of the magic actually happens. The function howManyLeft() is the entry point into my logic, which first calls the makeVacationList() method (described above), then it gets the first and last days of the school year, and passes them, as well as the vacationDays dictionary into the makeDateList method, and sets the return value to the local variable dateList. Lastly, the method computes todays date, and passes today and the dateList to howManySchoolDaysRemain(). The return value from this method is the answer to the question, so this is returned to the calling function.
Let's dig in a bit deeper, and understand makeDateList(). As you can see (at line 214), I start by declaring a daysRemaining variable and setting it to 0, and creating a misnamed variable called returnArray (returnDict would have been a better name) as an empty dictionary. I create one more variable, currentDate, which will indicate the date I am actively checking. With these 3 in place, I start a loop backwards from the end date back to the startDate. On each iteration, I set currentDate key of the returnArray to be equal to the number of days remaining. On the last day of school, that number is 0. After this, I compute the daysRemaining for the next day that I will iterate over, by passing the currentDate, daysRemaining and vacationDays to the getDaysRemaining method. When the loop is completed, I return the returnArray dictionary.
The getDaysRemaining method is relatively simple, in that it checks if the currentDate is a day off. If so, it returns true, indicating there is no school on this day, so no need to increment the daysRemaining value. If the currentDate is not a day off, it returns false. Computing if a date is a day off is really an amalgamation of two checks, first is the date a weekend day (which can be determined by date.weekday()>4, as the weekday method returns a 0 indexed value for day of the week with Monday as 0, and Sunday as 6), and if not a weekend, it checks if the currentDate is in the vacationDays dictionary.
As this iterates from end to beginning, it creates a dictionary with each date as a key, and the number of days remaining as the value.
The Alexa boilerplate methods were lifted wholesale from the sample apps (in fact there are still a few unused methods in this release from that sample). What I did add/modify were 2 new methods and one modified one. Copying an existing method, I created howManyDaysLeft() and whatIsLastDay(). In each case, I called my existing methods (described above) to compute the number of days remaining, or the last day of school, and format the answer into Alexa's speech output.
I also modified on_intent, which maps Alexa intents (more on this below) to these methods.
Take a look at SchoolDaysIntent.json, and you can see a simple JSON file (again, copied and modified from Alexa boilerplate), which lists out the core intents HowManyDaysLeft and WhatIsLastDay. You can add as many intents as you need, as each intent will be matched to one or more utterances (what the user says to Alexa). You can see this mapping the SchoolDaysUtterances file.
Now to refactor
See v1.0.1 in github https://github.com/jefftapper/SchoolDays/releases/tag/v1.0.1
Of course, there were many things I did not like about the initial version, including the mixed responsibilities of the AlexaSchoolDays.py file, which contained my application specific logic as well as all of the Alexa specific logic. Also, I didn't like that I was recreating the entire school day dictionary each time the application was run. So the first refactor was to break AlexaSchoolDays.py up into two files.
Next refactor, stop hardcoding data in python, move to json
See v1.0.2 in github https://github.com/jefftapper/SchoolDays/releases/tag/v1.0.2
The next thing that bothered me in the code, were the hard coded values for start date, end date and vacation days. This release updates the code to read all of these values from the json file. It also splits out the logic for creating the schooldaydata.json file into a separate python file (makeSchoolDateJSON.py) from the core runtime logic. With this change, the json file can get crated once for each school year (or re-created if the dates change, like if there is a snow day), and that file can be re-read each time it is needed. This is far more efficient than re-creating the data on each run.
Last refactor (for now).
-See v.1.0.3 in github* https://github.com/jefftapper/SchoolDays/releases/tag/v1.0.3
The next series of refactors were aimed at making it easier to reuse this application for other years or other school districts. First the names of the JSON files were renamed to include the school district and year. This is true for both the created json files (schooldaydata.json becomes SchoolDataNYC20162017.json), but also the file containing start and end dates and vacation days (SchoolDates.json becomes NYC20162017SchoolDates.json). I also realized I still had hard coded start and end dates in howManyDays.py, so these were removed. I ultimately realized that I wasnt using the start date at all, and that I could derive the end date from the list of school days passed in.
So, this is a long and drawn out entry, walking you through my development process in creating and refactoring this Alex skill. I hope you found it interesting. If you are interested in using it yourself, feel free to grab the code and run, I am releasing it under the Apache 2.0 license.
After a long and painful process, I amazon has finally approved SchoolDays. You can find it in the Alexa app if you want to know how many school days remain in the school year (16 as of today!). You will also notice, I already have the data files for the 2017-2018 school years up in github, so as soon as this year is done, I'll update my lambda service with next years data.