cob.js 8.9 KB

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