The Long Arm of the Law — On the Construction of a Single Page Application using JavaScript & Rails

The gavel is often used as a symbol of the law in the UK. However, you will never find such an object in the hands of a judge in a courtroom.

All of my projects up to and including this point seem to have taken an intensely mathematical, algorithmic perspective of things, even if the project would appear the furthest thing from mathematical at first sight.

For this project, and for my previous project, I did not simply set out to solve a problem which I had invented or provide a service which nobody was asking for, but instead asked people close to me in my life what they would find useful in their own lives and create something which helps them. My previous project was inspired by my girlfriend, a medical student, who said that the determination of which drugs do not mix well together can be a time-consuming process, particularly in polypharmacies. As I detailed in the corresponding blog post, it also is not possible to memorise 60,000 or so combinations of drugs which do not mix together.

This project, however, seemed less intensely mathematical. The problem was brought to me by a friend of mine, a barrister, who said something along the lines of:

You should build a calculator for determining how long a criminal sentence would be. A lot of barristers use the calculators on their phones which wastes a lot of time and can be inaccurate in more complex cases.

In truth, the project seemed slightly absurd to me, because I did not particularly understand what was so difficult about summing together multiple dates. It seemed like something you could just do on Excel. But there was a catch to it: the concepts of concurrent sentences and consecutive sentences. When my friend told me about concurrent and consecutive sentences, my interest was piqued.

When one criminal count is concurrent with another, it means that the sentences are being served simultaneously. For example, if the defendant has two charges, one of theft and another of battery, each punishable by one year but treated as though they are being served at the same time (concurrent), the defendant serves one year in prison, not two. If, however, it is determined that the defendant should serve time in prison for each respective count (consecutive), then the defendant would serve two years in prison.

This is easy in cases where the defendant has two or three counts of crime, but what about four, five, or twenty? This is where the motivation for the app comes in.

The “New Calculation” view.

The Sentencing Calculator is a ready-to-use professional assistant for legal professionals to estimate the length of a sentence for, say, a defendant. In the example provided above, the Test Defendant has three crimes, respectively for one year, two months, and four months. The Concurrency Table is made such that the user can select which counts of crime will be served at the same time and, if a box is left unselected, the two crimes are considered to be consecutive. Therefore, out of counts 2 and 3, count 3 is the greater number of the two and therefore “wins out” between the two. Count 1 is consecutive, so the result is (1 year) + (the greater of 2 and 4 months) = 16 months.

Saving a defendant allows you to refer back to the defendant to see their crimes and lengths. These are saved with the session ID and cannot be accessed without it.

As I said at the beginning, my projects seem to assume a somewhat mathematical or algorithmic theme, no matter what my intended end result is. So, what was the difficulty that I needed to overcome this time? Something I called concurrency chaining.

Early on in my application, concurrency chaining was so much of an issue that I built a detector for whenever it happened and, instead of calculating anything, I would say that the sentence is incalculable until the user adjusted some of the checkboxes in the concurrency table.

Here is the issue: the concurrency table adds one row and column for every count that is added, let’s call them C[1] to C[n]. When constructing the table and considering every pair of counts C[x] and C[y], we want to ignore all cases where x=y and, where a C[x]C[y] pair has already been defined, ensure that a C[y]C[x] pair does not arise; this is a duplicate. The table we end up with looks like this:

You can imagine this extending as far down and across as you like — there’s no limit!

As the checkboxes are created, I created HTML classes for them. The first class would be count-${row} and the second class would be count-${column}. Since we have been careful not to include any redundant or duplicate pairings, the classes of each checkbox are unique. For example, the checkbox with class count-2 count-4 would be in Row 2, Column 4. Simple. Now, when we check the boxes count-1 count-2 and count-1 count-3, for example, we know that C[1] is concurrent with C[2] and that C[1] is concurrent with C[3] as well. So, if the first, second, and third are equal to 1 year, 2 years, and 3 years, then concurrency is respected and the total sentence is 3 years. The way this was initially calculated was as follows:

  1. A checked checkbox has its HTML classes read.
  2. For each class, a value for the number of days is extracted from the actual sentence length.
  3. The greater of the two sentences “wins”, all the sentences are summed thereafter as if consecutive, and the loser’s value is subtracted from the total.
  4. Repeat for all checked checkboxes.

But here is where concurrency chaining threw a spanner in the works: what if C[1] is concurrent with C[2] and C[2] is concurrent with C[3]? For the sake of ease, let’s replace the term “is concurrent with” with the symbol ^.

We know that if a=b and b=c then a=c — this is the Transitive Axiom of algebra. Similarly, if C[1]^C[2] and C[2]^C[3] it follows that C[1]^C[3]. However, with the examples of 2, 1, and 3 years, let’s follow this through our algorithm at the moment.

This is the scenario we are dealing with.
  1. The first checkbox’s classes are read: count-1 count-2.
  2. The number of days for count-1 and count-2 are read. This amount of precision is unnecessary for this, so we’ll leave the pair as being 2 and 1 years.
  3. Count 1 wins, and the loser (1 year) is subtracted from the total (currently (2+1+3)-1
  4. Repeat for the second box. count-2 and count-3 are read, Count 3 is the winner, the lower (2 year) is subtracted from the total (finally (2+1+3)-1-1 = 4.

This is not correct, yet it isn’t clear what needs to be done in order for the correct answer, 3, to appear. There are many ways to solve this problem when chaining is not an issue, such as converting the “losers” to a length of 0. This makes sense intuitively, but still consistently arrives at the wrong answer.

So, initial solution borne of frustration was to simply inform the user that, no, they could not simply chain concurrencies like that. My friend, however, begged to differ, saying that there is certainly a way to make it possible.

So, after many hours of toiling, I created a JavaScript function named the Gordon Evaluator — named in honour of the friend who suggested this project — whose line of reasoning goes as follows:

Firstly: Check if any boxes have been checked whatsoever. If not, then return the maximum sentence among all counts. If so, proceed.

Secondly: Extract all checkboxes which have been ticked, and pass each of their class names as a two-element array of strings into an array named pairs.

To refer back to our example, we will have an array that looks like this: [["count-1", "count-2"], ["count-2", "count-3"]]. The order is important.

Now, for something to be “chained”, remember that we rely on the structure C[1]^C[2] and C[2]^C[3]. What we want to do now, bearing that in mind, is that if the second element of a pair equals the first element of another pair, then all four elements (i.e. three unique elements) are chained.

So, we want to iterate over each pair’s second element and check that element against every single other pair’s first element. In this case, our algorithm immediately finds that the second element of Pair 1 equals the first element of Pair 2. We can then collate the elements of both pairs — count-1, count-2, count-3 — and return the largest of all of those. Then, shovel those counts into an array and make sure to subtract the losers at the end. As for the other stragglers, add them on like normal consecutive counts.

Easy, right?

Physics student turned accountant turned software engineering student.