A CRUD backend API built in Python with the FastAPI framework.
The application handles patient demographic data, addresses, and appointments.
Install the dependancies:
pip install -r requirements.txt
Run via uvicorn:
uvicorn panda.main:app
Build the Docker image:
docker build -t panda .
Run Docker image:
docker run -d -p 80:80 panda
https://127.0.0.1 (Depending on local setup)
https://pandacrud-1-r3693083.deta.app (Only allows GET actions)
Access OpenAPI docs via [URL]/docs, or Redoc via /redoc.
Example payloads are included for patients, addresses and appointments in the OpenAPI docs.
FastAPI/Pydantic take care of a lot of the validation and error reporting for missing/invalid requests.
They also take care of serialising/deserialising objects in requests and responses, and automatic type conversion - helping to avoid errors/mistakes in the code.
We can also add our own validator methods with decorators. For example, using RegEx to check a valid postcode, to check a datetime has timezone data, or that the appointment end_at field is after the start_at field.
Validation is done via is_valid_nhs_number. The NHS Number is checked via Pydantic's validator decorator. ValueError is raised and returned to the user if the payload number fails this check.
Basic examples of unit tests are included in /tests. These are performed with pytest. Fixtures are included which provide valid/invalid/badly formatted NHS Numbers, valid and invalid PatientCreate objects, etc.
The database currently uses SQLite for the MVP but FastAPI can easily integrate with any database supported by SQLAlchemy, e.g., PostgreSQL, MySQL. Migrations need to be enacted with a third-party tool, like Alembic.
The database can be filled with fake data with:
python -m panda.populate_db
This will drop data from an existing db, create and populate the panda_app.db file with fake patients, addresses and appointments.
Addresses have an owner_type and owner_id field. This allows us to use the Address table to add patient addresses, locations, departments, etc. We can then get the corresponding address record based on these two fields. This avoids having different tables for different address types.
Once appointments have Clinician, Department, etc data (which are currently commented out in the model), we can obtain stats for per-clinicia/department missed appointments. 'Missed' is not currently a field as it can be inferred from the data (is_cancelled = false and attended_at = None).
Foreign markets would require different validation than NHS Numbers. Instead of an NHS Number field, a 'Reference Number' field could be used and a specific validator injected depending on the market.
A more RESTful design -
Given more time, responses would include additional metadata, links, and requests accept an 'include' parameter.
An example response could look like:
{
"data": [
{
"id": 1,
"name": "David Winch",
...
"links": [
{
"rel": "self",
"uri": "/patients/1"
},
{
"rel": "patient.address",
"uri": "/patients/1/address"
},
{
"rel": "patient.appointments",
"uri": "/patients/1/appointments"
}
},
...
]
"pagination": {
"total": 1000,
"count": 12,
"per_page": 12,
"current_offset": 12,
"total_pages": 84,
"next_url": "/patients?offset=12&limit=12"
}
}
Testing. Testing. Testing. The tests are there as examples, much more was required before writing the code. In the interest of time, not everything has been tested.
Add fields to models. Patient is missing phone number, email, etc. Pretty important data!
Add Clinician and Department models.
Add functions to get missed appointments, cancelled appointments, etc.
Behaviour testing to test functionality with scenarios, rather than unit testing which may not work as expected.
Remove Address router file - Addresses are attached to a resource. We can get them via the resource path, e.g. /patient/1/address.
Allow queries for requests, beyond just limit and offset.
Index database columns.
Better documentation explanations/examples. E.g., AddressOwnerType in Schema - What is it? What are potential values?
GET/addresses/ Get Addresses
GET/addresses/{address_id} Get Address
GET/patients/ Get Patients
POST/patients/ Create Patient
GET/patients/getbynhsnumber Get Patient By Nhs Number
GET/patients/{patient_id} Get Patient
PUT/patients/{patient_id} Update Patient
DELETE/patients/{patient_id} Delete Patient
GET/patients/{patient_id}/address Get Patient Address
POST/patients/{patient_id}/address Create Patient Address
GET/appointments/ Get Appointments
POST/appointments/ Create Appointment
GET/appointments/{appointment_id} Get Appointment By Id
PUT/appointments/{appointment_id} Update Appointment
POST/appointments/{appointment_id}/cancel Cancel Appointment
POST/appointments/{appointment_id}/attended Mark Appointment Attended