The aim of this project was to create a tax calculator for IT contractors to compare take home pay across different jurisdictions.
Tax law is complex and time consuming to research. This project aims to democratise tax knowledge so that the average IT contractor can see with a quick calculation how they will be affected by taxes in different jurisdictions.
This calculator is of use to any IT contractor considering moving countries. It is also useful to those in the UK, since certain tax matters are devolved which means that tax rates can be different in England, Wales, Scotland and Northern Ireland. As a result, for some residents who live near the borders, a simple move to the next village can result in a drastically different tax bill.
The goal of this calculator is to allow them to see an indicative calculation of how a move would affect their take home pay, without them having to spend weeks investigating complex legal issues or spending thousands on tax advisers. In the event that they are seriously considering a move to another jurisdiction after seeing the indicative calculation, then they will be advised to seek personalised, specialist advice.
The tax calculator could be expanded in the future to be of use to a wider demographic than just IT contractors. The software architecture of this project has been designed with scalability/xxxx? in mind, to ensure that additional jurisdictions and demographics can be added easily.
The admin user site has been designed for a sophisticated professional tax adviser who has commissioned the project. As a result, and to keep the user experience streamlined, it does not explain tax concepts, rules, or basic instructions as the user would have in-depth knowledge of these.
The users of this site will be any IT contractor working outside IR35 and considering moving to another tax jurisdiction. The site may also be suitable for any other type of contractor working through a limited company in the same way.
Finding accurate demographics for this group was not easy, and as this is a new application I have no existing business data to draw on. The most accurate data I found related to the IT contractor market in the USA, so it was not ideal, but it does give a clear view of the typical demographic:
Since most IT contractors work through limited companies and submit tax returns each year I would expect the users to be reasonably sophisticated, possibly with multiple sources of income, and a reasonable understanding of tax language. I therefore did not define terms like ‘corporation tax’ as the average user of this site would more likely than not already be familiar with them. I do not have any data to test this assumption, as this is a new site. As it is used, data will be collected and if my assumptions are incorrect the user experience can be modified. For example the terms could be clarified in some additional notes. For the sake of a clean user experience those notes have not been included at this stage.
Additionally, there will also be one admin user who will be able to log in, add new jurisdictions and update tax rates for existing jurisdictions. It is critical to note that the admin user is a sophisticated professional tax adviser who has commissioned the project. As a result, and to keep the user experience streamlined, the admin user section does not explain tax concepts, rules, or basic instructions as the user would have in-depth knowledge of these.
All user stories were documented, and progress towards delivering them, was tracked in Github.
Tax law is highly complex. Different systems have different types of rules and caluclations. In order to limit the scope of the project, personal allowances have not been included yet. The project is dependent upon the calculations being accurate. Therefore research was a critical aspect. The sophisticated admin user will need to keep the information up to date for the calculator to continue to be accurate over time.
Tax ranges and limits are specified in the currency of the jurisdiction. This site is currently aimed at UK residents so will only take income in GBP for the time being.
The architecture of the project was critical to ensuring that the project is scalable and can be amended and kept up to date.
The aim of this site was to appear professional, accurate and trustworthy with good levels of contrast to satisfy optimal UX design.
As this is an entirely new site, I do not have any demographic data about the expected users. Once the site is in use data will be collected and demographic assumptions will be revised. The colour scheme can then be changed if it is felt necessary.
Since the site is conveing complex informaiton, a simple, clear text was desired.
The site is free from images to ensure a clean, simple interface.
Font awesome was used to provide simple, clear graphics.
IT Contractor Wireframes
As an IT contractor, I am first asked to choose whether I need to register or login:
If I choose to register, I am presented with a registration form:
If I choose to login, I am presented with a login form to access the Tax Calculator using my existing username and password:
When I login, I can see a summary of my past tax calculations, and my subscription status:
If my subscription has expired, or I have not subscribed yet, then I can choose to extend my subscription:
Once I have chosen my new subscription plan, I see a checkout page for my subscription:
When I have entered my payment details, payment is taken and I am shown a page confirming the status of the payment:
Once my payment has been completed, and my subscription updated, then I can start creating a tax calculation. The first step is to choose the countries that I want to compare:
Next, I enter my financial information following the on-screen instructions. Firstly, I select my income sources:
Then I enter my income details:
Finally, once I have provided all required data, my tax calculation is displayed:
Admin Wireframes
As an admin, I can see a list of jurisdictions. I can choose to delete a jurisdiction, or add a new one:
If I choose to add a jurisdiction, then I am shown a form to enter jurisdiction details:
As an admin, I can see a list of tax categories. I can choose to add a new tax category, or delete one:
If I choose to add a tax category, then I am shown a form to enter tax category details:
As an admin, I can see a list of questions for a selected jurisdiction. I can choose to edit or delete existing questions, or add a new one:
When I choose to add or edit a question, I am shown a form to enter the details for the question. If I am editing an existing question, then the details of the current question are displayed:
As an admin, I can see a list of tax rates for a selected jurisdiction. I can choose to edit or delete existing tax rates, or add a new one:
When I choose to add or edit a tax rate, I am shown a form to enter the details for the tax rate. If I am editing an existing tax rate, then the details of the current tax rate are displayed:
A single landing page for Contractors and Admins
An instructions page for Contractors
A login form
A registration form
Toasts to display important Djanog messages to the user
A dashboard for Contractors
Responsive navbar with relevant Contractor, Admin, and Account links clearly displayed
The Jurisdictions Selection form
Next
button is disabled until at least one jurisdiction has been selected.
The Financial Information form
The Calculation Results page
The Calculation process breadcrumb
The Subscription Options form
The Checkout form
The Admin landing page
The Basic admin app
Managing Jurisdictions
Managing Tax Categories
Managing Subscriptions
Managing Payments
Selecting Jurisdictions in the Config App
The Question Display
The Create Question journey
The Edit Question journey
Adding Multiple Choice Options
Multiple Choice Question
form.
Edit Question
page.
Reordering Questions
The Rulesets display
The Create Ruleset journey
Reordering Rulesets
The Add Rule journey
Create Rule
form that is displayed and then clicks Save Changes.
Editing Rules
Adding Rule Tiers
For this project I had to be very careful to keep the scope as tight as possible since there was a large amount of legal research, algorithms and architecture to carry out. With the limited time available, I had to prioritise. I architected the project in an agile way, to ensure that I could come back to it at a later date and add functionality as easily as possible. With more time, I would consider adding the following functionality:
The fundamental problem for the Tax Calculator is to gather financial information from the user and then apply a series of calculations to work out, based on that info, how the user will be taxed. The problem is that the structure of the calculations, and the calculations themselves, and the info on which they are based, varies from country to country.
The forms will be different for each country - different questions for each country, and the calculations are going to be different for each country. Given this, I needed to come up with a piece of software that can gather the right info from the user for all of the countries, and then apply the right calculation for all of the countries.
The problem is this: how do I get info from users when the info needed is dependent on the particular country and the calculation needed is also dependent on the particular country?
I wanted to architect this in a way that means other countries can be added in the future without needing to change the software itself. This will make the project more extensible and future-proof, and is in line with the principles of Uncle Bob’s ‘Clean Code’ - reducing the time and expense needed to update the project in the future.
There were three options:
To use lots of if statement, with hard coded calculations for each country, and then the algorithm selects the calculations based on the country. But with this design, software engineers would have to change it every time a new country is added, requiring a release of code every time. This is because all of the logic is hard coded. Additionally, Uncle Bob doesn’t like if statements!
Enable additional countries to be added by extension. Create a software module for each country and each will have hard coded questions and answers. A plug-in would effectively be created for each country. This doesn’t require a redeployment of the whole application code if changes are made, but you need a software engineer to create a new plugin and maintain the logic for each country.
Enable administrators to configure each country. This approach would come up with a generic algorithm that is data driven and store all of the knowledge in a database. The algorithm uses the knowledge in the database to work out what questions to ask and what calculations to apply. This would enable an admin user to update the database with a new country, and no software engineering is needed. This makes the project much more accessible, usable and updateable.
I selected the third option to make it as easy as possible for tax experts to rapidly expand the range of countries supported by the Tax Calculator. The issue with this option is that if a new country has totally different tax rules than those in the existing database, then a software engineer would need to update the system to provide new types of tax rate but this is much less common than having to change the tax rates themselves. This option can lead a developer down a rabbit hole, trying to anticipate every single tax rule set that might possibly come up. But all tax systems I have studied so far have had similar rules, so this still remains the best option. In the event that a new country was added with drastically different rules, software engineering would be required to ensure the overall logic still worked.
This project is about striking a balance between ensuring the application is useful to the end user (IT contractors) whilst also being easily updatable by an admin user as far as that is reasonably practicable. There may be outlying cases where a software engineer would be required to add a new country, but I am limiting the scope where possible to minimise this risk.
Given the user stories and analysis of the Tax Calculator problem above, it is clear that the backend of the Tax Calculator will be very complicated with a need to support several different areas of functionality:
Given these different types of functionality that the backend of the Tax Calculator will need to support, I had to decide how best to architect the solution. One option would be to adopt a monolithic style similar to that used on the Boutique ADO project. In this approach, the code for the user interface (templates and views) lives in the same module (in our case, Django app) as the models and data access logic which that user interface relates to. This works well for small scale applications. However, as the application grows it becomes harder to maintain clean separation of concerns between modules.
The Tax Calculator has already reached this point. For example, there will be need to be two different applications at least - one for admins and one for IT contractors. Both applications, however, will need access to both the question and rules data. The admin will need to be able to view lists of questions and rules so they can edit that data. The IT contractor will need to access the same data to generate the financial information form and their tax calculation. Similarly, administrators need to be able to view subscriptions and create subscription options. Users need to choose subscription options and update subscriptions.
This raises the problem as to which Django app should own each piece of data. Using the conventional monolithic approach, there are many scenarios where one app would need to access the data provided by another app. Furthermore, this is likely to lead to duplicated code in many places.
To avoid this problem, an alternative architectural style is the microservices architecture. Using this style involves hiding data and functionality microservices which are accessed via RESTful APIs. Each API manages the data and provides the functionality for a specific, closely-related set of data models. This allows the functions that process data to be decoupled from the user interfaces which allow users to view and input data. Because the functionality provided by each microservice is accessed via a RESTful API, this means that many Django apps could access the same functionality and data without replicating too much code. This also allows for consistent validation of data where multiple apps can edit the same data.
I therefore decided to opt for the microservices style.
Having decided to adopt a microservice approach, the next question I had to answer is what are the microservices that I need?
Microservices are closely related to the concept of domain-driven design. Each microservice should manage the data for exactly one domain. Domain-driven design involves designing data models in the software that closely resembles the terminology used by real users of the application. An important aspect of domain design is:
How to organize large domains into a network of Bounded Contexts.
This involves separating complex data models into separate bounded contexts. The guidance is that the context changes when the language changes, and that different contexts have unrelated concepts - but also some overlapping concepts.
Using this approach I was able to identify five bounded contexts. To do this, I worked out from the list of all of the data that the Tax Calculator would need, which bits of data belonged together.
The five contexts are:
Originally, I had intended to separate out a sixth context - the Calculations context - from the Rules context. However, it became clear that this was not sensible or feasible since the Calculations context would need all of the data contained within the Rules context to do its job. The calculation algorithms are in fact just the functionality associated with the Rules context.
I also considered holding Forms and Rules data as part of the Jurisdiction context. However, Forms and Rules data are not very closely related and this would have felt like too much data and functionality in a single microservice.
Having identified the bounded contexts for the Tax Calculator, I was able to design my high-level architecture as shown below:
In this diagram, there is an API for each bounded context identified above. Each API is intended to fully own and manage the data and functionality associated with that bounded context.
Additionally, there are a number of Django applications. Each application is intended to contain a complete set of end-to-end user journeys as follows:
An API needs to do a number of things:
Following the single-responsibility principle, and loosely based on the Model-View-Controller pattern, I decided to separate out my API code into several layers as shown in the diagram below:
The layers are:
The following diagram provides an early high-level design for how these different components will collaborate to provide the end-to-end calculation journey:
The Jurisdictions Domain The data model for the Jurisdiction domain is shown below:
Arguably, this data model is too small to be considered a context in its own right. However, as noted above this data model is referenced by both the Forms and Rules context. Given that the Jurisdiction data model is shared in this way, it was not logical to place the Jurisdiction data model in either the Forms or Rules.
The alternative would have been to place Jurisdiction, Forms and Rules data in a single bounded context but I felt the resulting domain would have been too large with lots of unrelated functionality living behind a single API. This would have broken the single responsibility principle. I therefore felt splitting the Jurisdictions data out was the best option.
The Forms Domain The data model for the Forms domain is shown below:
In this data model, Questions for a single jurisdiction are grouped together into a Form. Given that the Form lives in a separate API, it holds the integer ID of the corresponding Jurisdiction rather than a Django foreign key. Holding the Foreign Key would have broken the microservices principle by allowing a model in one service to access the data inside another without going via the API.
The model allows the admin to configure three types of question:
The Rules Domain The data model for the Rules domain is shown below:
The Rules data model follows a similar pattern to the Questions data model. This is an implementation of the Strategy Pattern which allows multiple implementations of the same function to be used interchangeably. In this case, the functions are the Validate and Calculate functions. This allows multiple rules of varying types to be defined for a jurisdiction and processed without complex conditional logic. Uncle Bob would like this!
TaxCategory objects correspond to different types of tax (e.g., income, corporation, VAT, dividend, inheritance).
RuleSets are used to group all of the tax Rules for a specific Jurisdiction and TaxCategory combination. For example, one RuleSet instance would be defined to hold Corporation Tax rules for France. Another would hold Corporation Tax rules for Germany, and so on.
Three types of rule are supported:
The Rules context also contains three models that hold the results of a tax calculation. Rather than just displaying the final amount of tax payable for each jurisdiction, I wanted to be able to display each step of the calculation to the IT Contractors so they can understand how the calculation was reached if they wish to. This data model allows the steps to be grouped and ordered.
It's worth noting that these models do not hold foreign keys to any of the RuleSet or Rule data models but instead hold integer IDs referencing the RuleSet or Rule models, as well as the summary data for the associated rules and rulesets. The reason for this is to minimise the complexity of SQL queries when retrieving TaxCalculationResults. Instead of having to join the TaxRuleTierResults to the associated Rules in order to get the details of the step, all of the data needed to explain the step to the user is held within the TaxRuleTierResults.
The Subscriptions Domain The data model for the Subscriptions domain is shown below:
The Payments Domain The data model for the Payments domain is shown below:
One important factor to note is that the Payment model holds the payment ID that is returned by Stripe for the payment. This is required to properly handle webhooks that are returned by Stripe. By storing the stripe_pid on the Payment model, the API can correctly identify the Payment entity to which a given webhook coming in from Stripe corresponds. This is needed since Stripe cannot hold the API's Payment.id value and a common identifier is needed between the two systems to allow the matching to take place.
It is debatable whether the Payments context should really have been separated out from the Subscriptions context. Initially this decision was taken since the Payment functionality is very different to, and separate from, the functionality required to update Subscriptions. However, an alternative point of view is that the Pamyent process is simply part of the subscription user journey.
One consequence of separating out the Payment and Subscription contexts was that it became harder to update a Subscription once Payment was completed. In a future version of the Tax Calculator, therefore, I would merge these two contexts together.
For all testing, please refer to the TESTING.md file.
The live deployed application can be found at Tax Calculator.
This project uses Heroku, a platform as a service (PaaS) that enables developers to build, run, and operate applications entirely in the cloud.
Deployment steps are as follows, after account setup:
AWS_ACCESS_KEY_ID
Your AWS access keyAWS_SECRET_ACCESS_KEY
Your secret AWS access keyDATABASE_URL
Your database URLDEVELOPMENT
FalseSECRET_KEY
A randomly generated secret keySTRIPE_PUBLIC_KEY
Your Stripe public keySTRIPE_SECRET_KEY
Your Stripe secret keySTRIPE_WH_SECRET
Your Stripe webhook keyUSE_AWS
TrueUSE_TEST_DB
FalseHeroku needs two additional files in order to deploy properly.
You can install this project's requirements (where applicable) using: pip3 install -r requirements.txt
. If you have your own packages that have been installed, then the requirements file needs updated using: pip3 freeze --local > requirements.txt
The Procfile can be created with the following command: echo -e web: python app.py\nworker: celery -A worker.celery worker > Procfile
For Heroku deployment, follow these steps to connect your GitHub repository to the newly created app:
Either:
Or:
heroku login -i
heroku git:remote -a contractor-tax-calculator
add
, commit
, and push
to GitHub, you can now type: git push heroku main
The frontend terminal should now be connected and deployed to Heroku.
This project uses ElephantSQL DB as the relational database for the application's data warehouse.
Deployment steps to create the database in ElephantSQL, after account setup, are as follows:
Instances
dashboard, select the Create New Instance
button.Name
field, enter contractor-tax-calculator
.Plan
field to Tiny Turtle (Free)
.Select Region
button.Data Center
field, choose EU-West-1 (Ireland)
and click Review
.Create Instance
.Instances
dashboard. Now choose the contractor-tax-calculator
instance.Details
page, select the eye icon next to the URL
field. Choose the Copy
icon next to the URL field.DATABASE_URL
var.This project uses Stripe as the payment provider to receive payments for subscriptions from the user.
Deployment steps to set up Stripe integration, after account setup, are as follows:
Home
page after signing in to Stripe, click the Developers link in the top right hand corner.Publishable Key
and paste this into the Value
field for the STRIPE_PUBLIC_KEY
var in the Heroku Settings
page.Secret key
field in the Stripe API keys tab. Copy the revealed value.Secret Key
and paste this into the Value
field for the STRIPE_SECRET_KEY
var in the Heroku Settings
page.Developers
page. Choose Add EndpointEndpoint URL
field, enter the URL of your deployed Heroku app, followed by: /api/payments/webhooks/
payment_intent
.payment_intent.canceled
, payment_intent.payment_failed
, payment_intent.requires_action
and payment_intent.succeeded
events. Click Add events.Developers
page, choose the newly-created endpoint.Signing Secret
, click Reveal. Copy the revealed webhook key.STRIPE_WH_SECRET
var in the Heroku Settings
page.Gitpod IDE was used to write the code for this project.
To make a local copy of this repository, you can clone the project by typing the follow into your IDE terminal:
git clone https://github.com/Laura10101/cat-identifier.git
You can install this project's requirements (where applicable) using: pip3 install -r requirements.txt
.
Create an env.py
file, and add the following environment variables:
import os
os.environ['SECRET_KEY'] = "11)7m!ubta%^*^e68=^03k)ht^yyh@724#&%eyn6ihng9i+uim"
os.environ['STRIPE_PUBLIC_KEY'] = 'pk_test_51NDoGHFkVBiDxSnkTY8frrULeHhmIUMQUtoAJPyqnRCV3xM7kgd1PNX4AkWOx7lWDuRdzXTXQCcvIqMvpGLJvUZR00gMGZRSEG'
os.environ['STRIPE_SECRET_KEY'] = 'sk_test_51NDoGHFkVBiDxSnkgbUnGaJO53YUKEj8snA7eqrRYmgRzmzjV0JyOUv1dHF9VUAmgg9lfGOoORrnYT126SpgFk7m00HsjjFcmm'
os.environ['STRIPE_WH_SECRET'] = 'whsec_PBvl6xDiaj1yrqG8dQ3mQLLyslnheCj0'
os.environ['DATABASE_URL'] = 'postgres://mqrlkcwy:aAuiwNbWU-9KjIL1Vsrrxn8ju8ZZuSXl@tyke.db.elephantsql.com/mqrlkcwy'
os.environ['DEVELOPMENT'] = 'True'
os.environ['USE_TEST_DB'] = 'False'
Alternatively, if using Gitpod, you can click below to create your own workspace using this repository.
I would like to thank the Code Institute for all of the support through all four of my projects. Special thanks goes to Tim Nelson who was my personal tutor. He is a remarkable software professional and has a natural ability to teach and inspire.