cob.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. var basal = require('../profile/basal');
  2. var get_iob = require('../iob');
  3. var find_insulin = require('../iob/history');
  4. var isf = require('../profile/isf');
  5. function detectCarbAbsorption(inputs) {
  6. var glucose_data = inputs.glucose_data.map(function prepGlucose (obj) {
  7. //Support the NS sgv field to avoid having to convert in a custom way
  8. obj.glucose = obj.glucose || obj.sgv;
  9. return obj;
  10. });
  11. var iob_inputs = inputs.iob_inputs;
  12. var basalprofile = inputs.basalprofile;
  13. /* TODO why does declaring profile break tests-command-behavior.tests.sh? */
  14. profile = inputs.iob_inputs.profile;
  15. var mealTime = new Date(inputs.mealTime);
  16. var ciTime = new Date(inputs.ciTime);
  17. //console.error(mealTime, ciTime);
  18. // get treatments from pumphistory once, not every time we get_iob()
  19. var treatments = find_insulin(inputs.iob_inputs);
  20. var avgDeltas = [];
  21. var bgis = [];
  22. var deviations = [];
  23. var deviationSum = 0;
  24. var carbsAbsorbed = 0;
  25. var bucketed_data = [];
  26. bucketed_data[0] = glucose_data[0];
  27. var j=0;
  28. var foundPreMealBG = false;
  29. var lastbgi = 0;
  30. if (! glucose_data[0].glucose || glucose_data[0].glucose < 39) {
  31. lastbgi = -1;
  32. }
  33. for (var i=1; i < glucose_data.length; ++i) {
  34. var bgTime;
  35. var lastbgTime;
  36. if (glucose_data[i].display_time) {
  37. bgTime = new Date(glucose_data[i].display_time.replace('T', ' '));
  38. } else if (glucose_data[i].dateString) {
  39. bgTime = new Date(glucose_data[i].dateString);
  40. } else { console.error("Could not determine BG time"); }
  41. if (! glucose_data[i].glucose || glucose_data[i].glucose < 39) {
  42. //console.error("skipping:",glucose_data[i].glucose);
  43. continue;
  44. }
  45. // only consider BGs for 6h after a meal for calculating COB
  46. var hoursAfterMeal = (bgTime-mealTime)/(60*60*1000);
  47. if (hoursAfterMeal > profile.maxMealAbsorptionTime || foundPreMealBG) {
  48. continue;
  49. } else if (hoursAfterMeal < 0) {
  50. //console.error("Found pre-meal BG:",glucose_data[i].glucose, bgTime, Math.round(hoursAfterMeal*100)/100);
  51. foundPreMealBG = true;
  52. }
  53. //console.error(glucose_data[i].glucose, bgTime, Math.round(hoursAfterMeal*100)/100, bucketed_data[bucketed_data.length-1].display_time);
  54. // only consider last ~45m of data in CI mode
  55. // this allows us to calculate deviations for the last ~30m
  56. if (typeof ciTime !== 'undefined') {
  57. var hoursAgo = (ciTime-bgTime)/(45*60*1000);
  58. if (hoursAgo > 1 || hoursAgo < 0) {
  59. continue;
  60. }
  61. }
  62. if (bucketed_data[bucketed_data.length-1].display_time) {
  63. lastbgTime = new Date(bucketed_data[bucketed_data.length-1].display_time.replace('T', ' '));
  64. } else if ((lastbgi >= 0) && glucose_data[lastbgi].display_time) {
  65. lastbgTime = new Date(glucose_data[lastbgi].display_time.replace('T', ' '));
  66. } else if ((lastbgi >= 0) && glucose_data[lastbgi].dateString) {
  67. lastbgTime = new Date(glucose_data[lastbgi].dateString);
  68. } else { console.error("Could not determine last BG time"); }
  69. var elapsed_minutes = (bgTime - lastbgTime)/(60*1000);
  70. //console.error(bgTime, lastbgTime, elapsed_minutes);
  71. if(Math.abs(elapsed_minutes) > 8) {
  72. // interpolate missing data points
  73. var lastbg = glucose_data[lastbgi].glucose;
  74. // cap interpolation at a maximum of 4h
  75. elapsed_minutes = Math.min(240,Math.abs(elapsed_minutes));
  76. //console.error(elapsed_minutes);
  77. while(elapsed_minutes > 5) {
  78. var previousbgTime = new Date(lastbgTime.getTime() - 5 * 60*1000);
  79. j++;
  80. bucketed_data[j] = [];
  81. bucketed_data[j].date = previousbgTime.getTime();
  82. var gapDelta = glucose_data[i].glucose - lastbg;
  83. //console.error(gapDelta, lastbg, elapsed_minutes);
  84. var previousbg = lastbg + (5/elapsed_minutes * gapDelta);
  85. bucketed_data[j].glucose = Math.round(previousbg);
  86. //console.error("Interpolated", bucketed_data[j]);
  87. elapsed_minutes = elapsed_minutes - 5;
  88. lastbg = previousbg;
  89. lastbgTime = new Date(previousbgTime);
  90. }
  91. } else if(Math.abs(elapsed_minutes) > 2) {
  92. j++;
  93. bucketed_data[j]=glucose_data[i];
  94. bucketed_data[j].date = bgTime.getTime();
  95. } else {
  96. bucketed_data[j].glucose = (bucketed_data[j].glucose + glucose_data[i].glucose)/2;
  97. }
  98. lastbgi = i;
  99. //console.error(bucketed_data[j].date)
  100. }
  101. var currentDeviation;
  102. var slopeFromMaxDeviation = 0;
  103. var slopeFromMinDeviation = 999;
  104. var maxDeviation = 0;
  105. var minDeviation = 999;
  106. var allDeviations = [];
  107. //console.error(bucketed_data);
  108. for (i=0; i < bucketed_data.length-3; ++i) {
  109. bgTime = new Date(bucketed_data[i].date);
  110. var sens = isf.isfLookup(profile.isfProfile,bgTime);
  111. //console.error(bgTime , bucketed_data[i].glucose, bucketed_data[i].date);
  112. var bg;
  113. var avgDelta;
  114. var delta;
  115. if (typeof(bucketed_data[i].glucose) !== 'undefined') {
  116. bg = bucketed_data[i].glucose;
  117. if ( bg < 39 || bucketed_data[i+3].glucose < 39) {
  118. process.stderr.write("!");
  119. continue;
  120. }
  121. avgDelta = (bg - bucketed_data[i+3].glucose)/3;
  122. delta = (bg - bucketed_data[i+1].glucose);
  123. } else { console.error("Could not find glucose data"); }
  124. avgDelta = avgDelta.toFixed(2);
  125. iob_inputs.clock=bgTime;
  126. iob_inputs.profile.current_basal = basal.basalLookup(basalprofile, bgTime);
  127. //console.log(JSON.stringify(iob_inputs.profile));
  128. //console.error("Before: ", new Date().getTime());
  129. var iob = get_iob(iob_inputs, true, treatments)[0];
  130. //console.error("After: ", new Date().getTime());
  131. //console.error(JSON.stringify(iob));
  132. var bgi = Math.round(( -iob.activity * sens * 5 )*100)/100;
  133. bgi = bgi.toFixed(2);
  134. //console.error(delta);
  135. var deviation = delta-bgi;
  136. deviation = deviation.toFixed(2);
  137. //if (deviation < 0 && deviation > -2) { console.error("BG: "+bg+", avgDelta: "+avgDelta+", BGI: "+bgi+", deviation: "+deviation); }
  138. // calculate the deviation right now, for use in min_5m
  139. if (i===0) {
  140. currentDeviation = Math.round((avgDelta-bgi)*1000)/1000;
  141. if (ciTime > bgTime) {
  142. //console.error("currentDeviation:",currentDeviation,avgDelta,bgi);
  143. allDeviations.push(Math.round(currentDeviation));
  144. }
  145. if (currentDeviation/2 > profile.min_5m_carbimpact) {
  146. //console.error("currentDeviation",currentDeviation,"/2 > min_5m_carbimpact",profile.min_5m_carbimpact);
  147. }
  148. } else if (ciTime > bgTime) {
  149. var avgDeviation = Math.round((avgDelta-bgi)*1000)/1000;
  150. var deviationSlope = (avgDeviation-currentDeviation)/(bgTime-ciTime)*1000*60*5;
  151. //console.error(avgDeviation,currentDeviation,bgTime,ciTime)
  152. if (avgDeviation > maxDeviation) {
  153. slopeFromMaxDeviation = Math.min(0, deviationSlope);
  154. maxDeviation = avgDeviation;
  155. }
  156. if (avgDeviation < minDeviation) {
  157. slopeFromMinDeviation = Math.max(0, deviationSlope);
  158. minDeviation = avgDeviation;
  159. }
  160. //console.error("Deviations:",avgDeviation, avgDelta,bgi,bgTime);
  161. allDeviations.push(Math.round(avgDeviation));
  162. //console.error(allDeviations);
  163. }
  164. // if bgTime is more recent than mealTime
  165. if(bgTime > mealTime) {
  166. // figure out how many carbs that represents
  167. // if currentDeviation is > 2 * min_5m_carbimpact, assume currentDeviation/2 worth of carbs were absorbed
  168. // but always assume at least profile.min_5m_carbimpact (3mg/dL/5m by default) absorption
  169. var ci = Math.max(deviation, currentDeviation/2, profile.min_5m_carbimpact);
  170. var absorbed = ci * profile.carb_ratio / sens;
  171. // and add that to the running total carbsAbsorbed
  172. //console.error("carbsAbsorbed:",carbsAbsorbed,"absorbed:",absorbed,"bgTime:",bgTime,"BG:",bucketed_data[i].glucose)
  173. carbsAbsorbed += absorbed;
  174. }
  175. }
  176. if(maxDeviation>0) {
  177. //console.error("currentDeviation:",currentDeviation,"maxDeviation:",maxDeviation,"slopeFromMaxDeviation:",slopeFromMaxDeviation);
  178. }
  179. return {
  180. "carbsAbsorbed": carbsAbsorbed
  181. , "currentDeviation": currentDeviation
  182. , "maxDeviation": maxDeviation
  183. , "minDeviation": minDeviation
  184. , "slopeFromMaxDeviation": slopeFromMaxDeviation
  185. , "slopeFromMinDeviation": slopeFromMinDeviation
  186. , "allDeviations": allDeviations
  187. }
  188. }
  189. module.exports = detectCarbAbsorption;