Calculating insulin bolus dose and infusion rate

I wrote a hobby program to ease the job of nurses to calculate insulin bolus dose and infusion rate according to measured blood glucose.

There are several protocols around. Almost all hospitals create their own protocols or modify existing ones to accommodate their specific concerns about the patient population at hand.

This is an example from a scientific article:

<table cellpadding="2">
<tr><td>Blood Glucose</td><td>Regular Insulin, Bolus</td><td>Regular lnsulin, Infusion</td></tr>
<tr><td>151-200</td><td>No bolus</td><td>2 units/h intravenously</td></tr>
<tr><td>201-250</td><td>3 units intravenously</td><td>2 units/h intravenously</td></tr>
<tr><td>251-300</td><td>6 units intravenously</td><td>3 units/h intravenously</td></tr>
<tr><td>301-350</td><td>9 units intravenously</td><td>3 units/h intravenously</td></tr>
<tr><td>350</td><td>10 units intravenously</td><td>4 units/h intravenously</td></tr>
</table>

This is one of the original functions I wrote:

(defun calculate-insulin-dose (blood-glucose)
"Returns a cons as: ("bolus dose" . "infusion rate")
(cond ((and (> kan-sekeri 116) (<= kan-sekeri 143)) (cons 0 1))
((and (> kan-sekeri 143) (<= kan-sekeri 179)) (cons 5 1))
((and (> kan-sekeri 179) (<= kan-sekeri 233)) (cons 7 2))
((and (> kan-sekeri 233) (<= kan-sekeri 287)) (cons 10 3))
((and (> kan-sekeri 287) (<= kan-sekeri 360)) (cons 13 4))
((> kan-sekeri 360) (cons 15 5))))


While reading the book Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp by Peter Norvig (Chapter 2: A Grammar for a subset of English), I decided to try a rule-based, data-driven approach and transformed the above code into this:

(defparameter *INFUSION-TABLE*
'((117 - 143            0           1              )
(144 - 179            5           1              )
(180 - 233            7           2              )
(234 - 287            10          3              )
(288 - 359            13          4              )
(360 - 999            15          5              ))
"Blood glucose range  Bolus dose  Infusion rate"  )

(defun check-protocol (blood-glucose row)
(when (and (> blood-glucose (first row))
(<= blood-glucose (third row)))
(list* (fourth row) (fifth row))))

(defun calculate-insulin-dose (blood-glucose)
(mapcan #'(lambda (x)
(check blood-glucose (nth x *INFUSION-TABLE*)))
'(0 1 2 3 4 5)))


I don't claim that this is what is meant by Peter Norvig, but it is certainly easier to check for simple range errors like checking for blood glucose ranges between both 144-179 and 179-233.

Once initiated, insulin infusion is regulated according to serial measurements. An example from the same study is below:

<table cellpadding="2">
<tr><td></td>
<td colspan="3" align="center">Current Insulin Infusion Rates</td></tr>
<tr><td>Current Blood Glucose</td>
<td>1-5 units/h</td>
<td>6-10 units/h</td>
<td>11-15 units/h</td></tr>
<tr><td valign="top">151-200</td><td>No bolus<p>↑ by 1 unit/h</td>
<td>No bolus<p>↑ by 2 units/h</td>
<td>No bolus<p>↑ by 2 units/h</td></tr>
<tr><td valign="top">201-250</td><td>3 units<p>↑ by 1 unit/h</td>
<td>5 units<p>↑ by 2 units/h</td>
<td>5 units<p>↑ by 2 units/h</td></tr>
<tr><td valign="top">201-250</td><td>3 units<p>↑ by 1 unit/h</td>
<td>5 units<p>↑ by 2 units/h</td>
<td>5 units<p>↑ by 2 units/h</td></tr>
<tr><td valign="top">251-300</td><td>8 units<p>↑ by 1 unit/h</td>
<td>8 units<p>↑ by 2 units/h</td>
<td>8 units<p>↑ by 2 units/h</td></tr>
<tr><td valign="top">301-350</td><td>10 units<p>↑ by 1 unit/h</td>
<td>10 units<p>↑ by 2 units/h</td>
<td>10 units<p>↑ by 2 units/h</td></tr>
<tr><td valign="top">>350</td><td>10 units<p>↑ by 2 unit/h</td>
<td>10 units<p>↑ by 3 units/h</td>
<td>10 units<p>↑ by 3 units/h</td></tr>
</table>

Currently the measurements, insulin doses and infusion rates are stored in a postgresql database, along with several demographic info about the corresponding patient; and I occasionally run some statistical analysis to see if a certain percent of patients do well enough. To do this I check for adverse events like hypoglycemia or persistent hyperglycemia. I also check if certain patient groups (e.g. trauma patients, etc) do benefit or not from the current infusion protocol.

I routinely connect to the database through R and run some tests. However, most often, I run bizarre functions to calculate the variance of differences between two blood glucose measurements, since a statistical programming language is not required just to calculate a variance.

I don't include these functions in this question since they are highly complicated and of poor quality. More importantly, I often change them according to what result I get from the statistical analysis.

However, after each analysis, I often modify one of the blood glucose ranges or corresponding doses by hand. Therefore I would like to extend the data structure above to accomodate some variables like patient age, or history of diabetes.

To date, I came up with a matrix-like structure such as:

'(old-person fragile undernourished diabetic) x '(144 - 179 3 1)


or

'(young-person fit obese non-diabetic) x '(144 - 179 6 2)


Of course, the real representation was sth like:

#2A((YOUNG-PERSON FIT OBESE NON-DIABETIC) (144 179 6 2))


I was inspired by heavy use of matrix manipulation in machine learning like lush, while trying to understood this subject. Although matrices were well suited for R and several other software, they look like another representation of sql tables to me. I look for a more natural representation of the data.

Questions:

1. I can't imagine how a data-driven approach can ease my job (modifying the ranges and doses), since I store everything in an sql database. (I am not interested how a, say, object-oriented or XXX approach, would serve.)

Efficiency is not my primary concern, since the system receives ~20 inputs per hour (I use hunchentoot as user interface). However, I intend to automate the process of analysis -> modify ranges/doses by constructing a closed loop in the future.

Due to concerns about patient safety, this will inevitably involve massive realtime checks for all kinds of possible errors.

2. Are there any tips for modifying the data structure above to start off with an extensible data structure?**

Before suggesting the use of any other approach, please keep in mind that I also thought about using real-time optimizations through continuous analysis of data with the help of object persistence with CL-PREVALENCE or AllegroGraph triples. I haven't tried any of them yet, because I am not ready for real-time monitoring and possible risks associated with it. I just need a simple, extensible primer, which obviously is one of the most important parts of the design process.

• Why not use struct rather than a list of lists? The advantage is that it names fields and creates trivial functions for accessing them. The usual drawback of creating container-like objects is the serialization and the associated rigidity that doesn't allow update to the schema seem not to be an issue in your case. However, if what you are after is modifying the rules for administering drugs, then, probably you should look into storing rules as data rather than separating data from code that operates on it. (contd) Nov 29, 2017 at 13:04
• So, you would have some very general match function which would try to apply to things like "patients history" and "protocol values". Where both history and values could be updated as more data become available. Nov 29, 2017 at 13:06
• @wvxvw I'm after modifying the rules for administering drugs. But doesn't *INFUSION-TABLE* count as "storing rules as data"? On the second look, I agree that it counts as "separating data from code that operates on it", though. This program already does what I want with CRUD operations. I query data like "patient history" with sql queries. (contd) Nov 29, 2017 at 20:32
• @wvxvw However it is a pain to modify the logic (infusin protocol in this case). I don't know if this program running on a raspberry pi (with an uninterrupted power supply) is ever suitable to make use of object prevalence. I am really confused about the advantages of using a sql database as a robust data store vs endless possibilities which may come up with realtime objects. So, as a start, I am looking for a data type other than a sql database. Nov 29, 2017 at 20:32
• Well, in a sense, *infusion-table* stores rules, but a list can encode only some very simple kinds of relations. You can of course interpret some elements of the list or their combinations to have special meanings, but on its own, a list is very limiting, if you need to describe something like rules. On the other hand, functions that operate on this data contain a lot more information about these relations. I was thinking that en.wikipedia.org/wiki/Reification_(computer_science) of your functions that operate on the table can later make it easier to adjust the program overall. Nov 30, 2017 at 6:28

Some coding style feedback:

Function

• <= allows as to check if a variable is between bounds
• values returns multiple objects

Example:

(defun calculate-insulin-dose (blood-glucose)
"Returns bolus dose and infusion rate"
(cond ((<= 117 blood-glucose 143)  (values  0 1))
((<= 144 blood-glucose 179)  (values  5 1))
((<= 180 blood-glucose 233)  (values  7 2))
((<= 234 blood-glucose 287)  (values 10 3))
((<= 288 blood-glucose 360)  (values 13 4))
((<= 361 blood-glucose    )  (values 15 5))))


Table

(defparameter *INFUSION-TABLE*
'((117 - 143            0           1              )
(144 - 179            5           1              )
(180 - 233            7           2              )
(234 - 287            10          3              )
(288 - 359            13          4              )
(360 - 999            15          5              ))
"Blood glucose range  Bolus dose  Infusion rate"  )

• Iterate over a list directly, don't use nth
• LOOP can destructure lists

Example:

(defun calculate-insulin-dose (blood-glucose)
(loop for (a nil b dose rate) in *INFUSION-TABLE*
when (<= a blood-glucose b)
do (return (values dose rate))))