-
Notifications
You must be signed in to change notification settings - Fork 396
/
calculate.js
146 lines (115 loc) · 6.2 KB
/
calculate.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
'use strict';
function iobCalc(treatment, time, curve, dia, peak, profile) {
// iobCalc returns two variables:
// activityContrib = units of treatment.insulin used in previous minute
// iobContrib = units of treatment.insulin still remaining at a given point in time
// ("Contrib" is used because these are the amounts contributed from pontentially multiple treatment.insulin dosages -- totals are calculated in total.js)
//
// Variables can be calculated using either:
// A bilinear insulin action curve (which only takes duration of insulin activity (dia) as an input parameter) or
// An exponential insulin action curve (which takes both a dia and a peak parameter)
// (which functional form to use is specified in the user's profile)
if (treatment.insulin) {
// Calc minutes since bolus (minsAgo)
if (typeof time === 'undefined') {
time = new Date();
}
var bolusTime = new Date(treatment.date);
var minsAgo = Math.round((time - bolusTime) / 1000 / 60);
if (curve === 'bilinear') {
return iobCalcBilinear(treatment, minsAgo, dia); // no user-specified peak with this model
} else {
return iobCalcExponential(treatment, minsAgo, dia, peak, profile);
}
} else { // empty return if (treatment.insulin) == False
return {};
}
}
function iobCalcBilinear(treatment, minsAgo, dia) {
var default_dia = 3.0 // assumed duration of insulin activity, in hours
var peak = 75; // assumed peak insulin activity, in minutes
var end = 180; // assumed end of insulin activity, in minutes
// Scale minsAgo by the ratio of the default dia / the user's dia
// so the calculations for activityContrib and iobContrib work for
// other dia values (while using the constants specified above)
var timeScalar = default_dia / dia;
var scaled_minsAgo = timeScalar * minsAgo;
var activityContrib = 0;
var iobContrib = 0;
// Calc percent of insulin activity at peak, and slopes up to and down from peak
// Based on area of triangle, because area under the insulin action "curve" must sum to 1
// (length * height) / 2 = area of triangle (1), therefore height (activityPeak) = 2 / length (which in this case is dia, in minutes)
// activityPeak scales based on user's dia even though peak and end remain fixed
var activityPeak = 2 / (dia * 60)
var slopeUp = activityPeak / peak
var slopeDown = -1 * (activityPeak / (end - peak))
if (scaled_minsAgo < peak) {
activityContrib = treatment.insulin * (slopeUp * scaled_minsAgo);
var x1 = (scaled_minsAgo / 5) + 1; // scaled minutes since bolus, pre-peak; divided by 5 to work with coefficients estimated based on 5 minute increments
iobContrib = treatment.insulin * ( (-0.001852*x1*x1) + (0.001852*x1) + 1.000000 );
} else if (scaled_minsAgo < end) {
var minsPastPeak = scaled_minsAgo - peak
activityContrib = treatment.insulin * (activityPeak + (slopeDown * minsPastPeak));
var x2 = ((scaled_minsAgo - peak) / 5); // scaled minutes past peak; divided by 5 to work with coefficients estimated based on 5 minute increments
iobContrib = treatment.insulin * ( (0.001323*x2*x2) + (-0.054233*x2) + 0.555560 );
}
return {
activityContrib: activityContrib,
iobContrib: iobContrib
};
}
function iobCalcExponential(treatment, minsAgo, dia, peak, profile) {
// Use custom peak time (in minutes) if value is valid
if ( profile.curve === "rapid-acting" ) {
if (profile.useCustomPeakTime === true && profile.insulinPeakTime !== undefined) {
if ( profile.insulinPeakTime > 120 ) {
console.error('Setting maximum Insulin Peak Time of 120m for',profile.curve,'insulin');
peak = 120;
} else if ( profile.insulinPeakTime < 50 ) {
console.error('Setting minimum Insulin Peak Time of 50m for',profile.curve,'insulin');
peak = 50;
} else {
peak = profile.insulinPeakTime;
}
} else {
peak = 75;
}
} else if ( profile.curve === "ultra-rapid" ) {
if (profile.useCustomPeakTime === true && profile.insulinPeakTime !== undefined) {
if ( profile.insulinPeakTime > 100 ) {
console.error('Setting maximum Insulin Peak Time of 100m for',profile.curve,'insulin');
peak = 100;
} else if ( profile.insulinPeakTime < 35 ) {
console.error('Setting minimum Insulin Peak Time of 35m for',profile.curve,'insulin');
peak = 35;
} else {
peak = profile.insulinPeakTime;
}
} else {
peak = 55;
}
} else {
console.error('Curve of',profile.curve,'is not supported.');
}
var end = dia * 60; // end of insulin activity, in minutes
var activityContrib = 0;
var iobContrib = 0;
if (minsAgo < end) {
// Formula source: https://github.com/LoopKit/Loop/issues/388#issuecomment-317938473
// Mapping of original source variable names to those used here:
// td = end
// tp = peak
// t = minsAgo
var tau = peak * (1 - peak / end) / (1 - 2 * peak / end); // time constant of exponential decay
var a = 2 * tau / end; // rise time factor
var S = 1 / (1 - a + (1 + a) * Math.exp(-end / tau)); // auxiliary scale factor
activityContrib = treatment.insulin * (S / Math.pow(tau, 2)) * minsAgo * (1 - minsAgo / end) * Math.exp(-minsAgo / tau);
iobContrib = treatment.insulin * (1 - S * (1 - a) * ((Math.pow(minsAgo, 2) / (tau * end * (1 - a)) - minsAgo / tau - 1) * Math.exp(-minsAgo / tau) + 1));
//console.error('DIA: ' + dia + ' minsAgo: ' + minsAgo + ' end: ' + end + ' peak: ' + peak + ' tau: ' + tau + ' a: ' + a + ' S: ' + S + ' activityContrib: ' + activityContrib + ' iobContrib: ' + iobContrib);
}
return {
activityContrib: activityContrib,
iobContrib: iobContrib
};
}
exports = module.exports = iobCalc;