Rail UK is a Skill for Amazon's Alexa, which aims to provide reliable and accurate train times within the UK by way of an intuitive voice interface. It currently offers the following features:
-
Set Home Station - Allows the user to specify a "home station", as well as how long it takes the user to travel to the station. The skill will then remember this information and factor it into future requests when no origin station is specified. The travel time the user provides will be used to ensure that the skill doesn't tell the user about a train that they won't be able to get to in time.
-
Next Train - Retrieves the next train to a specified destination, departing from either the user's home station (if set) or a specified origin, along with live departure information.
-
Fastest Train - Retrieves the fastest train to a specified destination, departing from either the user's home station (if set) or a specified origin, along with live departure information.
-
Last Train - Retrieves the last train of the current day to a specified destination, departing from either the user's home station (if set) or a specified origin, along with live departure information if the train is timetabled to depart within the next two hours.
The Rail UK skill is comprised of three components:
- Alexa Voice Interaction Model - High-level definition of the ways in which users can interact with the skill, implemented using the Alexa Development Console.
- Python Codebase - Implementation of the Skill's underlying logic, hosted on AWS Lambda.
- NoSQL Database - Storage of users' home station data, using Amazon DynamoDB
This repository only contains one of these components - the Python Codebase.
Rail UK also uses two APIs for fetching departure information:
-
OpenLDBWS - SOAP API for live departure information.
-
TransportAPI - REST API for future timetable information.
This project does not follow a conventional Python project structure. This is because AWS Lambda requires the function that handles incoming lambda triggers (lambda_entry
in this case) to be in a file in the root directory of the deployment package.
The structure is as follows:
rail-uk/
├── .circleci
│ └── config.yml # Configuration for CI/CD.
│
├── rail_uk/ # Rail UK's underlying logic.
│ ├── __init__.py
│ ├── data.py # Creates, sends and parses SOAP and HTTP requests
│ ├── dtos.py # Houses Data Transfer Object definitions
│ ├── dynamodb.py # Communicates with Amazon DynamoDB
│ ├── events.py # Handles various Alexa Skill events and wraps intent handlers
│ ├── exceptions.py # Custom exceptions used by the Skill
│ ├── intents.py # Handles all skill intents
│ └── lambda_handler.py # Handles incoming function triggers
│
├── scripts/ # Scripts for deploying Python packages to AWS Lambda
├── res/ # Static resources used by Rail UK
│ ├── templates/ # SOAP templates for OpenLDBWS requests
│ └── stations.csv # Values used by Alexa to match station names
│
├── tests/
│ ├── helpers/ # Package containing helpers for tests
│ ├── mock_responses/ # Example responses for testing
│ ├── <module>_test.py # Unit tests for a specific module
│ └── end_to_end_tests.py # End-to-end tests for skill's happy paths
│
├── lamdba_entry.py # Provides a simple entry point for Lambda trigger
├── README.md # This file
├── requirements.txt # Runtime dependencies
├── requirements-dev.txt # Development/Testing dependencies
├── setup.cfg # Py.test configuration
└── template.env # Template environment variable file
Rail UK, just like all Alexa Skills, is based on the idea of intents. At a high level, intents are essentially features. Currently, four intents have been implemented:
This intent allows users to set or update their home station, which is persisted and can be used as a default value for the origin
slot in service-related queries.
When a user's home station is set and used when using the FastestTrain
or NextTrain
intents, the distance
value is used as a time offset in the query so that the user will only be given information on departures that they'll be realistically able to get to the station in time for.
The slots this intent uses are:
home
- The name/colloquial name of the desired home stationdistance
- The time it typically takes the user to travel to the desired home station
This intent retrieves live departure information about the next direct train to destination
from origin
.
The slots this intent uses are:
-
destination
- The name/colloquial name of the station the user would like to travel to. -
origin
(Optional) - The name/colloquial name of the station the user would like to travel from.This slot may be omitted when a Home Station has been set. In this case, the intent will instead find the user's home station details, use the station as the value of the
origin
slot, and use thedistance
value as an offset in the OpenLDBWS query.
This intent retrieves live departure information about the fastest direct train to destination
from origin
, where "fastest train" is defined as the train that reaches destination
soonest.
For slots, see NextTrain
.
This intent finds the last direct service from origin
to destination
on that day.
Unlike NextTrain
and FastestTrain
, this intent does not factor the home station's distance
into the TransportAPI query when origin
is omitted. This is because the intent will always return the last service on any particular day, regardless of the relative time of the user's request (i.e. before or after).
For slots, see NextTrain
.
Intent delegation is handled by the Alexa service.
Setting up Rail UK for testing purposes is extremely simple.
-
Ensure
python3
(>=3.6),venv
andpip
/pip3
are installed. -
Create and activate a virtual environment:
python3 -m venv venv
source venv/bin/activate
-
Install dependencies:
pip install -r dev-requirements.txt
Setup for runtime is significantly more involved and is discussed in a later section of this README.
This project uses pytest
and pytest-cov
for unit and end-to-end testing. These packages will already be pre-installed the setup section has been followed.
To run the full suite of unit tests with coverage reporting, navigate to the project's root directory and run:
python3 -m pytest --cov-report term-missing --cov=rail_uk
For simplicity's sake - and permitted by the high coverage acheived by the unit tests - end-to-end tests currently only cover happy paths for each of the "Get Train"-style intents:
NextTrain
withorigin
anddestination
slots suppliedFastestTrain
withorigin
anddestination
slots suppliedLastTrain
withorigin
anddestination
slots supplied
The end-to-end tests use mocked responses for OpenLDBWS and TransportAPI requests, and have no need to communicate with DynamoDB. This is to remove dependency on full setup and the third-party services themselves during testing. The tests can be run with:
python3 -m pytest tests/end_to_end_tests.py
Note: While running Rail UK locally is technically possible, there's currently no script for actually supplying the right data to the lambda_entry
module and providing a response. An Alexa Simulator is currently in development, which will draw in heavily from the implementation of the end-to-end tests and allow local users to call upon intents with their desired slot values in order to get real, live departure information. /Note
This project was specifically designed for execution within an AWS Lambda function as part of an Alexa skill. As a consequence, running Rail UK locally will require users to set up some AWS resources and gain access to two third-party APIs.
- OpenLDBWS - Register here.
- TransportAPI - Register here.
- Amazon DynamoDB
Once registered for OpenLDBWS and TransportAPI, API configuration is as simple as populating some environment variables. A template .env
file has been provided to give the naming scheme of these variables. A suitable copy should be made and populated with the appropriate values.
Configuring DynamoDB is more involved. You'll need to follow Amazon's documentation to setup a table with the name 'RailUK' and partition key 'UserID'. You'll also need setup the appropriate IAM permissions, and install and configure awscli
so that boto3
can work it's magic and communicate with your table.
Side Note: I'll eventually abstract the table name and partition key into appropriate environment variables.
Once you've done all that, you should be able to run this project! To actually make use of the project, a script will need to be written that provides a valid Alexa request object to the lambda_entry
module.