The Necessary Art — On the Construction of a Content Management System using Ruby on Rails
The human condition, particularly mortality, has been the focus of mankind for as long as abstract human thought has existed, likely with the dawn of language some time between 50,000 and 100,000 years ago, distinguishing us from all other beasts. That our friends and family will surely die as their ancestors before them did invariably prompted a search for either a reason or a solution, for why we should become diseased, or die, or even why we are born in the first place with minds and wills of our own. Thus, many professions and talents approached this problem angled towards their own interests.
Perhaps the ambition of the architect is to make the condition of his fellow men more beautiful, designing and placing masterpieces amongst them with evident care and craftsmanship. The ambition of the cleric is to look to the teachings of mysterious prophecy and to interpret and dispense this information among the laymen who seek meaning in their lives and to act in accordance with their God, or gods. But the ambition of the medic is more literal in this respect; it is not to improve an intangible condition of their fellow man, but to aid the most real aspect of all: the flesh and bones that make us human.
In early civilisations in particular, plagues and famines were prevalent with the advent of irrigation, agriculture and ultimately the invention of the concept of a city. Mankind until this point had lived in small tribes of usually no greater than 200 men, women and children who were hunter-gatherers. But the new city, bustling with life and sustainable food and protection from foreign invasions was too tempting to resist. High concentrations of humans in a relatively unsterile environment invariably led to disease, however, and many cities either lost significant amounts of population or were simply abandoned. Naturally, the people of ancient civilisations were drawn to fighting the unseen adversary, pestilence. In Egypt, hopeful early medics turned their attention to their gods and goddesses for guidance and advice; one such goddess which received particular attention for medicine (“the necessary art” as known by the ancients) was Sekhmet. Sensibly, she was also the goddess of natural disasters, plagues, fires, and destruction, and had a suitably fiery personality: constantly enraged and needing to be appeased daily against death and war.
So, without further ado, I would like to present my most recent project with Flatiron School: a Content Management System built with Ruby on Rails, SecMed, a nod to Sekhmet, a short period of time (Second) and the necessary art (Medicine). I hope the pun was worth it.
SecMed was designed with two functions in mind: as a hub for a patient to log in to their own accounts and see their own medical records and appointment history, and for medical practitioners to log such appointments, detailing conditions and prescriptions that a patient should take. The idea, primarily, is that a database would be local to a particular medical practice rather than being universal, such that a doctor may only look at the medical records and enter appointments in for existing, consenting patients. It is impossible for a doctor to sign up, for a doctor is able to see all patient records, and so the signup process is only for patients; the accounts for doctors are created in an entirely different fashion internally and there is no way for a user to create a doctor’s account.
A doctor is able to add appointments to a patient’s record, including jotting down a diagnosis and, if applicable, a prescription. The number of drugs available for selection number 1,969 separate drugs pulled directly from the British National Formulary (BNF) website and stored into a database. While it is a throwback to Phase 1 with Flatiron School, Nokogiri was used for this process!
But now for the killer quirk of SecMed which sets it above being simply a place to select drugs, give a prescription expiry, and let that happily sit in a patient’s record.
Suppose you are a medical practitioner and your patient is prone to seizures. They are actively taking phenytoin for this ailment. Unfortunately, the patient now complains of dizziness and a high heart rate and, upon investigation, they were diagnosed with atrial fibrillation. ‘No problem,’ you may think, ‘I shall prescribe warfarin’.
Bad move. This is a more obvious example from a medical standpoint, and is a textbook example of a drug interaction, in other words where two drugs do not act well together. In fact, among the 1,969 drugs from my selection, there are 59,806 separate drug combinations which should be either regarded with caution or avoided at all costs. Good doctors are expected to know around 100 of these drugs and recall, within reason, the interactions among the worst of them, but learning all 60,000 would be a near impossibility.
I designed a simple algorithm which would search through each drug being actively taken, if the number of drugs is greater than 1, which works as follows: suppose a patient is a polypharmacy taking five drugs, D, B, E, C, and A. The algorithm works as follows:
- The drugs are sorted alphabetically into: A, B, C, D, E.
- The drug interaction table in the database (called drug_interactions) is queried for the pair AB.
- If AB is a pair, this row of the table is stored into a blank array as in instance of the Ruby class, DrugInteraction.
- Steps 2–3 are repeated for pairs AC, AD, and AE.
- The algorithm advances to looking at pairs of drugs including B. B does not need to search the pair BA (because AB has already been searched) and thus the search begins at BC. Steps 2–3 are hence repeated for BC, BD, and BE.
- Steps 2–3 are then repeated for CD, CE, and finally DE. The final drug, E, does not need to be searched since all pairings to E have already been found, so the final drug is skipped.
It is worth noting that the drug_interactions table contains two columns for drug names, drug_1 and drug_2, which is always in alphabetical order, hence the importance of Step 1 of the algorithm. Initially, the table did not have a particular order to drug_1 nor drug_2, although sorting them in the table means that the number of SQL queries made during the searches in Step 2 are minimised, hence making the program work more efficiently.
So what is the result of entering these two different conditions and drugs into our dummy patient, Mr. A. B. C.?
To demonstrate a little further, we shall add something else. Suppose Arnold is diagnosed with pneumonia and is prescribed amoxicillin.
Amoxicillin would also be a poor antibiotic to choose due to how it alters the anticoagulant effect of warfarin. Well, aminophylline is used in patients with respiratory problems, how about that?
Now aminophylline conflicts with phenytoin and, while using the two drugs in conjunction is warned against, the manufacturer only suggests adjusting the dose so that the phenytoin does not decrease the exposure to the drug. Helpful to know!
So that a medical practitioner is always on top of these interactions, these show up in appointment overviews as well as the patient’s medical record so that they are always at the forefront of the mind and not overlooked at any stage. Whether the drug is prescribed or not is at always at the practitioner’s professional discretion.
One of the biggest challenges of this project was the seed data itself. While I had contented myself at the beginning with using perhaps five or six drugs to demonstrate the idea, I felt that the added functionality of detecting drug interactions was useless without as much data as possible. I wanted to create something which not only fulfilled the requirements of the project at hand, but for something which could have a real use in the world of medicine. Much like Monarch Explorer, my first project with Flatiron School, and as mentioned before, I used Nokogiri to scrape the names of all drugs listed on the BNF and went another level deeper to find out all of the interactions (website sadly unavailable outside the UK — use a VPN if you can!) which are sorted alphabetically into a list.
With almost 2,000 drugs and almost 60,000 interactions, writing this information to the database takes considerable time which is wasted if any aspect of the data is incorrect.
We want to build up a table called “drugs” in our database, which contains the name of the drug and the URL which takes us from the index to the drug’s interaction page. So far, so good: using Nokogiri can obtain us that information swiftly and easily, and we will shortly have a table with our 1,969 drugs. The other 59,806 records will go into another table called “drug_interactions”.
(Note: there is a step I have not discussed concerning contra-indications, i.e. whether a drug conflicts with an existing condition that a patient has. This has been implemented into SecMed and the process was very similar to the drug interactions; much of the code was either identical or not as complex. However, the list of drugs was actually pulled from a similar index which was more comprehensive than the index for interactions, but the index is identical for the purpose of this discussion.)
Though the last image is small, one can get the general idea of how the list continues to populate: in small grid-like structures with the name of the interactant on the left and a short narrative on the right. The HTML classes of each element is reassuringly straightforward: “.interaction”, “.scroll-target”, and “.row”. Plugging all of these into Nokogiri spits out only the HTML of those rows, and now we want to assess each row.
For each row, we already know the names of one of the interactants: obviously, the name of the drug whose webpage we are already looking at. The other interactant sits comfortable in these classes: “.interactant” and “.h5” (the last class is just to be sure that I am grabbing the right thing. You can never be too sure in Nokogiri!). So, we have the names of drug_1 and drug_2, basically. But what if there is already a row in this table where drug_2 is our new drug_1 and drug_1 is our new drug_2? Skip it! We don’t want to duplicate the work, it wastes space, doubles our number of records, and makes querying this database a little bit longer. If it doesn’t already exist, we do the obvious and create this new record.
The way that this table is constructed should mean that drug_1 always precedes drug_2 in terms of alphabetical sorting. I did not want to risk them being out of order and I also did not want to check over 59,806 records myself, so I wrote a small script to switch the two drugs on the condition that drug_1 comes after drug_2 in the alphabet. For good measure, I checked for any duplicates. Thankfully, there weren’t any, so my table was efficient as possible.
This whole process of writing drugs to the database was not a process of jumping into the deep end and throwing values into Active Record to compute, but a lot of experimental work in outputting text to the console, seeing the iterations without any transactions committing themselves to the table just yet, and making sure everything was to spec. When it finally came to committing the transactions and watching my database populate row by row, it didn’t matter that I had to wait for 20 minutes for everything to populate — it was highly rewarding!