diff --git a/app/src/main/java/info/nightscout/androidaps/data/MealData.java b/app/src/main/java/info/nightscout/androidaps/data/MealData.java index 160d47813e..292d2272d5 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/MealData.java +++ b/app/src/main/java/info/nightscout/androidaps/data/MealData.java @@ -1,17 +1,5 @@ package info.nightscout.androidaps.data; -import java.util.Date; -import java.util.List; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.OpenAPSAMA.Autosens; -import info.nightscout.androidaps.plugins.OpenAPSAMA.AutosensResult; -import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin; -import info.nightscout.androidaps.plugins.NSClientInternal.data.NSProfile; - /** * Created by mike on 04.01.2017. */ @@ -19,28 +7,4 @@ public class MealData { public double boluses = 0d; public double carbs = 0d; public double mealCOB = 0.0d; - - - public void addTreatment(Treatment treatment) { - NSProfile profile = MainApp.getConfigBuilder().getActiveProfile().getProfile(); - if (profile == null) return; - - long now = new Date().getTime(); - long dia_ago = now - (new Double(1.5d * profile.getDia() * 60 * 60 * 1000l)).longValue(); - long t = treatment.created_at.getTime(); - if (t > dia_ago && t <= now) { - if (treatment.carbs >= 1) { - carbs += treatment.carbs; - if (MainApp.getSpecificPlugin(OpenAPSAMAPlugin.class).isEnabled(PluginBase.APS)) { - AutosensResult result = Autosens.detectSensitivityandCarbAbsorption((long) (new Date().getTime() - 60 * 60 * 1000L * profile.getDia() * 2), t); - double myCarbsAbsorbed = result.carbsAbsorbed; - double myMealCOB = Math.max(0, carbs - myCarbsAbsorbed); - mealCOB = Math.max(mealCOB, myMealCOB); - } - } - if (treatment.insulin > 0 && treatment.mealBolus) { - boluses += treatment.insulin; - } - } - } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java index 75ae83c440..7210a171b6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java @@ -7,8 +7,10 @@ import java.util.Date; */ public class AutosensData { + long time = 0L; String pastSensitivity = ""; double deviation = 0d; + boolean calculateWithDeviation = false; double absorbed = 0d; double carbsFromBolus = 0d; public double cob = 0; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/AutosensResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensResult.java similarity index 93% rename from app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/AutosensResult.java rename to app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensResult.java index 811fee0f39..1e40005b36 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/AutosensResult.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensResult.java @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.OpenAPSAMA; +package info.nightscout.androidaps.plugins.IobCobCalculator; import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java index 568c9b54eb..af54ab8ced 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java @@ -12,15 +12,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; -import java.util.HashMap; import java.util.List; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.IobTotal; -import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventNewBG; @@ -30,9 +29,9 @@ import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventNewHistoryData; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSProfile; -import info.nightscout.androidaps.plugins.OpenAPSAMA.Autosens; -import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.Round; import info.nightscout.utils.SP; +import info.nightscout.utils.SafeParse; /** * Created by mike on 24.04.2017. @@ -41,17 +40,19 @@ import info.nightscout.utils.SP; public class IobCobCalculatorPlugin implements PluginBase { private static Logger log = LoggerFactory.getLogger(IobCobCalculatorPlugin.class); - private static LongSparseArray iobTable = new LongSparseArray<>(); - private static LongSparseArray autosensDataTable = new LongSparseArray<>(); + private static LongSparseArray iobTable = new LongSparseArray<>(); // oldest at index 0 + private static LongSparseArray autosensDataTable = new LongSparseArray<>(); // oldest at index 0 - private static List bgReadings = null; // newest at index 0 - private static List bucketed_data = null; + private static volatile List bgReadings = null; // newest at index 0 + private static volatile List bucketed_data = null; private static double dia = Constants.defaultDIA; private static Handler sHandler = null; private static HandlerThread sHandlerThread = null; + private static Object dataLock = new Object(); + @Override public int getType() { return GENERAL; @@ -109,15 +110,20 @@ public class IobCobCalculatorPlugin implements PluginBase { @Nullable public static List getBucketedData(long fromTime) { - if (bucketed_data == null) { - log.debug("No bucketed data available"); - return null; - } - int index = indexNewerThan(fromTime); - if (index > -1) { - log.debug("Bucketed data striped off: " + index + "/" + bucketed_data.size()); - return bucketed_data.subList(0, index); + //log.debug("Locking getBucketedData"); + synchronized (dataLock) { + if (bucketed_data == null) { + log.debug("No bucketed data available"); + return null; + } + int index = indexNewerThan(fromTime); + if (index > -1) { + List part = bucketed_data.subList(0, index); + log.debug("Bucketed data striped off: " + part.size() + "/" + bucketed_data.size()); + return part; + } } + //log.debug("Releasing getBucketedData"); return null; } @@ -137,168 +143,178 @@ public class IobCobCalculatorPlugin implements PluginBase { } private void loadBgData() { - onNewProfile(new EventNewBasalProfile(null)); - bgReadings = MainApp.getDbHelper().getBgreadingsDataFromTime((long) (new Date().getTime() - 60 * 60 * 1000L * (24 + dia)), false); - log.debug("BG data loaded. Size: " + bgReadings.size()); + //log.debug("Locking loadBgData"); + synchronized (dataLock) { + onNewProfile(new EventNewBasalProfile(null)); + bgReadings = MainApp.getDbHelper().getBgreadingsDataFromTime((long) (new Date().getTime() - 60 * 60 * 1000L * (24 + dia)), false); + log.debug("BG data loaded. Size: " + bgReadings.size()); + } + //log.debug("Releasing loadBgData"); } public void createBucketedData() { - if (bgReadings == null || bgReadings.size() < 3) { - bucketed_data = null; - return; - } - - bucketed_data = new ArrayList<>(); - bucketed_data.add(bgReadings.get(0)); - int j = 0; - for (int i = 1; i < bgReadings.size(); ++i) { - long bgTime = bgReadings.get(i).getTimeIndex(); - long lastbgTime = bgReadings.get(i - 1).getTimeIndex(); - //log.error("Processing " + i + ": " + new Date(bgTime).toString() + " " + bgReadings.get(i).value + " Previous: " + new Date(lastbgTime).toString() + " " + bgReadings.get(i - 1).value); - if (bgReadings.get(i).value < 39 || bgReadings.get(i - 1).value < 39) { - continue; + //log.debug("Locking createBucketedData"); + synchronized (dataLock) { + if (bgReadings == null || bgReadings.size() < 3) { + bucketed_data = null; + return; } - long elapsed_minutes = (bgTime - lastbgTime) / (60 * 1000); - if (Math.abs(elapsed_minutes) > 8) { - // interpolate missing data points - double lastbg = bgReadings.get(i - 1).value; - elapsed_minutes = Math.abs(elapsed_minutes); - //console.error(elapsed_minutes); - long nextbgTime; - while (elapsed_minutes > 5) { - nextbgTime = lastbgTime - 5 * 60 * 1000; + bucketed_data = new ArrayList<>(); + bucketed_data.add(bgReadings.get(0)); + int j = 0; + for (int i = 1; i < bgReadings.size(); ++i) { + long bgTime = bgReadings.get(i).getTimeIndex(); + long lastbgTime = bgReadings.get(i - 1).getTimeIndex(); + //log.error("Processing " + i + ": " + new Date(bgTime).toString() + " " + bgReadings.get(i).value + " Previous: " + new Date(lastbgTime).toString() + " " + bgReadings.get(i - 1).value); + if (bgReadings.get(i).value < 39 || bgReadings.get(i - 1).value < 39) { + continue; + } + + long elapsed_minutes = (bgTime - lastbgTime) / (60 * 1000); + if (Math.abs(elapsed_minutes) > 8) { + // interpolate missing data points + double lastbg = bgReadings.get(i - 1).value; + elapsed_minutes = Math.abs(elapsed_minutes); + //console.error(elapsed_minutes); + long nextbgTime; + while (elapsed_minutes > 5) { + nextbgTime = lastbgTime - 5 * 60 * 1000; + j++; + BgReading newBgreading = new BgReading(); + newBgreading.timeIndex = nextbgTime; + double gapDelta = bgReadings.get(i).value - lastbg; + //console.error(gapDelta, lastbg, elapsed_minutes); + double nextbg = lastbg + (5d / elapsed_minutes * gapDelta); + newBgreading.value = Math.round(nextbg); + //console.error("Interpolated", bucketed_data[j]); + bucketed_data.add(newBgreading); + //log.error("******************************************************************************************************* Adding:" + new Date(newBgreading.timeIndex).toString() + " " + newBgreading.value); + + elapsed_minutes = elapsed_minutes - 5; + lastbg = nextbg; + lastbgTime = nextbgTime; + } j++; BgReading newBgreading = new BgReading(); - newBgreading.timeIndex = nextbgTime; - double gapDelta = bgReadings.get(i).value - lastbg; - //console.error(gapDelta, lastbg, elapsed_minutes); - double nextbg = lastbg + (5d / elapsed_minutes * gapDelta); - newBgreading.value = Math.round(nextbg); - //console.error("Interpolated", bucketed_data[j]); + newBgreading.value = bgReadings.get(i).value; + newBgreading.timeIndex = bgTime; bucketed_data.add(newBgreading); - //log.error("******************************************************************************************************* Adding:" + new Date(newBgreading.timeIndex).toString() + " " + newBgreading.value); - - elapsed_minutes = elapsed_minutes - 5; - lastbg = nextbg; - lastbgTime = nextbgTime; + //log.error("******************************************************************************************************* Copying:" + new Date(newBgreading.timeIndex).toString() + " " + newBgreading.value); + } else if (Math.abs(elapsed_minutes) > 2) { + j++; + BgReading newBgreading = new BgReading(); + newBgreading.value = bgReadings.get(i).value; + newBgreading.timeIndex = bgTime; + bucketed_data.add(newBgreading); + //log.error("******************************************************************************************************* Copying:" + new Date(newBgreading.timeIndex).toString() + " " + newBgreading.value); + } else { + bucketed_data.get(j).value = (bucketed_data.get(j).value + bgReadings.get(i).value) / 2; + //log.error("***** Average"); } - j++; - BgReading newBgreading = new BgReading(); - newBgreading.value = bgReadings.get(i).value; - newBgreading.timeIndex = bgTime; - bucketed_data.add(newBgreading); - //log.error("******************************************************************************************************* Copying:" + new Date(newBgreading.timeIndex).toString() + " " + newBgreading.value); - } else if (Math.abs(elapsed_minutes) > 2) { - j++; - BgReading newBgreading = new BgReading(); - newBgreading.value = bgReadings.get(i).value; - newBgreading.timeIndex = bgTime; - bucketed_data.add(newBgreading); - //log.error("******************************************************************************************************* Copying:" + new Date(newBgreading.timeIndex).toString() + " " + newBgreading.value); - } else { - bucketed_data.get(j).value = (bucketed_data.get(j).value + bgReadings.get(i).value) / 2; - //log.error("***** Average"); } + log.debug("Bucketed data created. Size: " + bucketed_data.size()); } - log.debug("Bucketed data created. Size: " + bucketed_data.size()); + //log.debug("Releasing createBucketedData"); } public void calculateSensitivityData() { - NSProfile profile = ConfigBuilderPlugin.getActiveProfile() != null ? ConfigBuilderPlugin.getActiveProfile().getProfile() : null; + //log.debug("Locking calculateSensitivityData"); + synchronized (dataLock) { + NSProfile profile = ConfigBuilderPlugin.getActiveProfile() != null ? ConfigBuilderPlugin.getActiveProfile().getProfile() : null; - if (profile == null) { - log.debug("calculateSensitivityData: No profile available"); - return; - } - - if (ConfigBuilderPlugin.getActiveTreatments() == null) { - log.debug("calculateSensitivityData: No treatments plugin"); - return; - } - - TreatmentsInterface treatmentsInterface = ConfigBuilderPlugin.getActiveTreatments(); - if (bucketed_data == null || bucketed_data.size() < 3) { - log.debug("calculateSensitivityData: No bucketed data available"); - return; - } - - long prevDataTime = roundUpTime(bucketed_data.get(bucketed_data.size() - 3).timeIndex); - log.debug("Prev data time: " + new Date(prevDataTime).toLocaleString()); - AutosensData previous = autosensDataTable.get(prevDataTime); - // start from oldest to be able sub cob - for (int i = bucketed_data.size() - 4; i >= 0; i--) { - // check if data already exists - long bgTime = bucketed_data.get(i).timeIndex; - bgTime = roundUpTime(bgTime); - - AutosensData existing; - if ((existing = autosensDataTable.get(bgTime)) != null) { - previous = existing; - continue; + if (profile == null) { + log.debug("calculateSensitivityData: No profile available"); + return; } - int secondsFromMidnight = NSProfile.secondsFromMidnight(bgTime); - double sens = NSProfile.toMgdl(profile.getIsf(secondsFromMidnight), profile.getUnits()); - - AutosensData autosensData = new AutosensData(); - - //console.error(bgTime , bucketed_data[i].glucose); - double bg; - double avgDelta; - double delta; - bg = bucketed_data.get(i).value; - if (bg < 39 || bucketed_data.get(i + 3).value < 39) { - log.error("! value < 39"); - continue; - } - avgDelta = (bg - bucketed_data.get(i + 3).value) / 3; - delta = (bg - bucketed_data.get(i + 1).value); - - IobTotal iob = calulateFromTreatmentsAndTemps(bgTime); - - double bgi = Math.round((-iob.activity * sens * 5) * 100) / 100d; - double deviation = delta - bgi; - - List recentTreatments = treatmentsInterface.getTreatments5MinBack(bgTime); - for (int ir = 0; ir < recentTreatments.size(); ir++) { - autosensData.carbsFromBolus += recentTreatments.get(ir).carbs; + if (ConfigBuilderPlugin.getActiveTreatments() == null) { + log.debug("calculateSensitivityData: No treatments plugin"); + return; } - // if we absorbing carbs - if (previous != null && previous.cob > 0) { - // figure out how many carbs that represents - // but always assume at least 3mg/dL/5m (default) absorption - double ci = Math.max(deviation, SP.getDouble("openapsama_min_5m_carbimpact", 3.0)); - autosensData.absorbed = ci * profile.getIc(secondsFromMidnight) / sens; - // and add that to the running total carbsAbsorbed - autosensData.cob = Math.max(previous.cob - autosensData.absorbed, 0d); + TreatmentsInterface treatmentsInterface = ConfigBuilderPlugin.getActiveTreatments(); + if (bucketed_data == null || bucketed_data.size() < 3) { + log.debug("calculateSensitivityData: No bucketed data available"); + return; } - autosensData.cob += autosensData.carbsFromBolus; - // calculate autosens only without COB - if (autosensData.cob <= 0) { - if (deviation > 0) { - autosensData.pastSensitivity += "+"; - } else if (deviation == 0) { - autosensData.pastSensitivity += "="; - } else { - autosensData.pastSensitivity += "-"; + long prevDataTime = roundUpTime(bucketed_data.get(bucketed_data.size() - 3).timeIndex); + log.debug("Prev data time: " + new Date(prevDataTime).toLocaleString()); + AutosensData previous = autosensDataTable.get(prevDataTime); + // start from oldest to be able sub cob + for (int i = bucketed_data.size() - 4; i >= 0; i--) { + // check if data already exists + long bgTime = bucketed_data.get(i).timeIndex; + bgTime = roundUpTime(bgTime); + + AutosensData existing; + if ((existing = autosensDataTable.get(bgTime)) != null) { + previous = existing; + continue; } - //avgDeltas[i] = avgDelta; - //bgis[i] = bgi; + + int secondsFromMidnight = NSProfile.secondsFromMidnight(bgTime); + double sens = NSProfile.toMgdl(profile.getIsf(secondsFromMidnight), profile.getUnits()); + + AutosensData autosensData = new AutosensData(); + autosensData.time = bgTime; + + //console.error(bgTime , bucketed_data[i].glucose); + double bg; + double avgDelta; + double delta; + bg = bucketed_data.get(i).value; + if (bg < 39 || bucketed_data.get(i + 3).value < 39) { + log.error("! value < 39"); + continue; + } + avgDelta = (bg - bucketed_data.get(i + 3).value) / 3; + delta = (bg - bucketed_data.get(i + 1).value); + + IobTotal iob = calulateFromTreatmentsAndTemps(bgTime); + + double bgi = Math.round((-iob.activity * sens * 5) * 100) / 100d; + double deviation = delta - bgi; + + List recentTreatments = treatmentsInterface.getTreatments5MinBack(bgTime); + for (int ir = 0; ir < recentTreatments.size(); ir++) { + autosensData.carbsFromBolus += recentTreatments.get(ir).carbs; + } + + // if we are absorbing carbs + if (previous != null && previous.cob > 0) { + // figure out how many carbs that represents + // but always assume at least 3mg/dL/5m (default) absorption + double ci = Math.max(deviation, SP.getDouble("openapsama_min_5m_carbimpact", 3.0)); + autosensData.absorbed = ci * profile.getIc(secondsFromMidnight) / sens; + // and add that to the running total carbsAbsorbed + autosensData.cob = Math.max(previous.cob - autosensData.absorbed, 0d); + } + autosensData.cob += autosensData.carbsFromBolus; autosensData.deviation = deviation; - } else { - autosensData.pastSensitivity += "C"; - //console.error(bgTime); + + // calculate autosens only without COB + if (autosensData.cob <= 0) { + if (deviation > 0) { + autosensData.pastSensitivity += "+"; + } else if (deviation == 0) { + autosensData.pastSensitivity += "="; + } else { + autosensData.pastSensitivity += "-"; + } + autosensData.calculateWithDeviation = true; + } else { + autosensData.pastSensitivity += "C"; + } + //log.debug("TIME: " + new Date(bgTime).toString() + " BG: " + bg + " SENS: " + sens + " DELTA: " + delta + " AVGDELTA: " + avgDelta + " IOB: " + iob.iob + " ACTIVITY: " + iob.activity + " BGI: " + bgi + " DEVIATION: " + deviation); + + previous = autosensData; + autosensDataTable.put(bgTime, autosensData); + log.debug(autosensData.log(bgTime)); } - //log.debug("TIME: " + new Date(bgTime).toString() + " BG: " + bg + " SENS: " + sens + " DELTA: " + delta + " AVGDELTA: " + avgDelta + " IOB: " + iob.iob + " ACTIVITY: " + iob.activity + " BGI: " + bgi + " DEVIATION: " + deviation); - - previous = autosensData; - autosensDataTable.put(bgTime, autosensData); - log.debug(autosensData.log(bgTime)); } - + //log.debug("Releasing calculateSensitivityData"); } public static IobTotal calulateFromTreatmentsAndTemps(long time) { @@ -326,7 +342,7 @@ public class IobCobCalculatorPlugin implements PluginBase { public static AutosensData getAutosensData(long time) { long now = new Date().getTime(); - if (time > now ) + if (time > now) return null; time = roundUpTime(time); AutosensData data = autosensDataTable.get(time); @@ -339,6 +355,15 @@ public class IobCobCalculatorPlugin implements PluginBase { } } + public static AutosensData getLastAutosensData() { + AutosensData data = autosensDataTable.valueAt(autosensDataTable.size() - 1); + if (data.time < new Date().getTime() - 5 * 60 * 1000) { + return null; + } else { + return data; + } + } + public static IobTotal[] calculateIobArrayInDia() { NSProfile profile = ConfigBuilderPlugin.getActiveProfile().getProfile(); // predict IOB out to DIA plus 30m @@ -355,6 +380,103 @@ public class IobCobCalculatorPlugin implements PluginBase { return array; } + public static AutosensResult detectSensitivity(long fromTime) { + //log.debug("Locking detectSensitivity"); + synchronized (dataLock) { + if (autosensDataTable == null || autosensDataTable.size() < 4) { + log.debug("No bucketed data available"); + return new AutosensResult(); + } + + AutosensData current = getLastAutosensData(); + if (current == null) { + log.debug("No current autosens data available"); + return new AutosensResult(); + } + + + List deviationsArray = new ArrayList<>(); + String pastSensitivity = ""; + int index = 0; + while (index < autosensDataTable.size()) { + AutosensData autosensData = autosensDataTable.valueAt(index); + + if (autosensData.time < fromTime) { + index++; + continue; + } + + if (autosensData.calculateWithDeviation) + deviationsArray.add(autosensData.deviation); + + pastSensitivity += autosensData.pastSensitivity; + int secondsFromMidnight = NSProfile.secondsFromMidnight(autosensData.time); + if (secondsFromMidnight % 3600 < 2.5 * 60 || secondsFromMidnight % 3600 > 57.5 * 60) { + pastSensitivity += "(" + Math.round(secondsFromMidnight / 3600d) + ")"; + } + index++; + } + + Double[] deviations = new Double[deviationsArray.size()]; + deviations = deviationsArray.toArray(deviations); + + NSProfile profile = ConfigBuilderPlugin.getActiveProfile().getProfile(); + + double ratio = 1; + String ratioLimit = ""; + String sensResult = ""; + + log.debug("Records: " + index + " " + pastSensitivity); + Arrays.sort(deviations); + + for (double i = 0.9; i > 0.1; i = i - 0.02) { + if (percentile(deviations, (i + 0.02)) >= 0 && percentile(deviations, i) < 0) { + log.debug(Math.round(100 * i) + "% of non-meal deviations negative (target 45%-50%)"); + } + } + double pSensitive = percentile(deviations, 0.50); + double pResistant = percentile(deviations, 0.45); + + double basalOff = 0; + + if (pSensitive < 0) { // sensitive + basalOff = pSensitive * (60 / 5) / NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()), profile.getUnits()); + sensResult = "Excess insulin sensitivity detected"; + } else if (pResistant > 0) { // resistant + basalOff = pResistant * (60 / 5) / NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()), profile.getUnits()); + sensResult = "Excess insulin resistance detected"; + } else { + sensResult = "Sensitivity normal"; + } + log.debug(sensResult); + ratio = 1 + (basalOff / profile.getMaxDailyBasal()); + + double rawRatio = ratio; + ratio = Math.max(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_min", "0.7"))); + ratio = Math.min(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_max", "1.2"))); + + if (ratio != rawRatio) { + ratioLimit = "Ratio limited from " + rawRatio + " to " + ratio; + log.debug(ratioLimit); + } + + double newisf = Math.round(NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()), profile.getUnits()) / ratio); + if (ratio != 1) { + log.debug("ISF adjusted from " + NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()), profile.getUnits()) + " to " + newisf); + } + + AutosensResult output = new AutosensResult(); + output.ratio = Round.roundTo(ratio, 0.01); + output.carbsAbsorbed = Round.roundTo(current.cob, 0.01); + output.pastSensitivity = pastSensitivity; + output.ratioLimit = ratioLimit; + output.sensResult = sensResult; + return output; + } + //log.debug("Releasing detectSensitivity"); + } + + public static JSONArray convertToJSONArray(IobTotal[] iobArray) { JSONArray array = new JSONArray(); for (int i = 0; i < iobArray.length; i++) { @@ -388,24 +510,44 @@ public class IobCobCalculatorPlugin implements PluginBase { // When historical data is changed (comming from NS etc) finished calculations after this date must be invalidated @Subscribe public void onNewHistoryData(EventNewHistoryData ev) { - long time = ev.time; - log.debug("Invalidating cached data to: " + new Date(time).toLocaleString()); - for (int index = iobTable.size() - 1; index >= 0; index--) { - if (iobTable.keyAt(index) > time) { - log.debug("Removing from iobTable: " + new Date(iobTable.keyAt(index)).toLocaleString()); - iobTable.removeAt(index); - } else { - break; - } - } - for (int index = autosensDataTable.size() - 1; index >= 0; index--) { - if (autosensDataTable.keyAt(index) > time) { - log.debug("Removing from autosensDataTable: " + new Date(autosensDataTable.keyAt(index)).toLocaleString()); - autosensDataTable.removeAt(index); - } else { - break; + //log.debug("Locking onNewHistoryData"); + synchronized (dataLock) { + long time = ev.time; + log.debug("Invalidating cached data to: " + new Date(time).toLocaleString()); + for (int index = iobTable.size() - 1; index >= 0; index--) { + if (iobTable.keyAt(index) > time) { + log.debug("Removing from iobTable: " + new Date(iobTable.keyAt(index)).toLocaleString()); + iobTable.removeAt(index); + } else { + break; + } + } + for (int index = autosensDataTable.size() - 1; index >= 0; index--) { + if (autosensDataTable.keyAt(index) > time) { + log.debug("Removing from autosensDataTable: " + new Date(autosensDataTable.keyAt(index)).toLocaleString()); + autosensDataTable.removeAt(index); + } else { + break; + } } } + //log.debug("Releasing onNewHistoryData"); } + // From https://gist.github.com/IceCreamYou/6ffa1b18c4c8f6aeaad2 + // Returns the value at a given percentile in a sorted numeric array. + // "Linear interpolation between closest ranks" method + public static double percentile(Double[] arr, double p) { + if (arr.length == 0) return 0; + if (p <= 0) return arr[0]; + if (p >= 1) return arr[arr.length - 1]; + + double index = arr.length * p, + lower = Math.floor(index), + upper = lower + 1, + weight = index % 1; + + if (upper >= arr.length) return arr[(int) lower]; + return arr[(int) lower] * (1 - weight) + arr[(int) upper] * weight; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/Autosens.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/Autosens.java deleted file mode 100644 index 316dc37259..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/Autosens.java +++ /dev/null @@ -1,204 +0,0 @@ -package info.nightscout.androidaps.plugins.OpenAPSAMA; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.Date; -import java.util.List; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.data.IobTotal; -import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.plugins.NSClientInternal.data.NSProfile; -import info.nightscout.utils.Round; -import info.nightscout.utils.SP; -import info.nightscout.utils.SafeParse; - - -public class Autosens { - private static Logger log = LoggerFactory.getLogger(Autosens.class); - - public static AutosensResult detectSensitivityandCarbAbsorption(long dataFromTime, Long mealTime) { - NSProfile profile = MainApp.getConfigBuilder().getActiveProfile().getProfile(); - - //console.error(mealTime); - - double carbsAbsorbed = 0; - - List bucketed_data = IobCobCalculatorPlugin.getBucketedData(dataFromTime); - if (bucketed_data == null) - return new AutosensResult(); - - //console.error(bucketed_data); - //double[] avgDeltas = new double[bucketed_data.size() - 2]; - //double[] bgis = new double[bucketed_data.size() - 2]; - double[] deviations = new double[bucketed_data.size() - 2]; - - String pastSensitivity = ""; - for (int i = 0; i < bucketed_data.size() - 3; ++i) { - long bgTime = bucketed_data.get(i).timeIndex; - int secondsFromMidnight = NSProfile.secondsFromMidnight(new Date(bgTime)); - - String hour = ""; - //log.debug(new Date(bgTime).toString()); - if (secondsFromMidnight % 3600 < 2.5 * 60 || secondsFromMidnight % 3600 > 57.5 * 60) { - hour += "(" + Math.round(secondsFromMidnight / 3600d) + ")"; - } - - double sens = NSProfile.toMgdl(profile.getIsf(secondsFromMidnight), profile.getUnits()); - - //console.error(bgTime , bucketed_data[i].glucose); - double bg; - double avgDelta; - double delta; - bg = bucketed_data.get(i).value; - if (bg < 39 || bucketed_data.get(i + 3).value < 39) { - log.error("! value < 39"); - continue; - } - avgDelta = (bg - bucketed_data.get(i + 3).value) / 3; - delta = (bg - bucketed_data.get(i + 1).value); - -// avgDelta = avgDelta.toFixed(2); - IobTotal iob = IobCobCalculatorPlugin.calulateFromTreatmentsAndTemps(bgTime); - - double bgi = Math.round((-iob.activity * sens * 5) * 100) / 100d; -// bgi = bgi.toFixed(2); - //console.error(delta); - double deviation = delta - bgi; -// deviation = deviation.toFixed(2); - //if (deviation < 0 && deviation > -2) { console.error("BG: "+bg+", avgDelta: "+avgDelta+", BGI: "+bgi+", deviation: "+deviation); } - - // Exclude large positive deviations (carb absorption) from autosens - if (avgDelta - bgi < 6) { - if (deviation > 0) { - pastSensitivity += "+"; - } else if (deviation == 0) { - pastSensitivity += "="; - } else { - pastSensitivity += "-"; - } - //avgDeltas[i] = avgDelta; - //bgis[i] = bgi; - deviations[i] = deviation; - } else { - pastSensitivity += ">"; - //console.error(bgTime); - } - pastSensitivity += hour; - //log.debug("TIME: " + new Date(bgTime).toString() + " BG: " + bg + " SENS: " + sens + " DELTA: " + delta + " AVGDELTA: " + avgDelta + " IOB: " + iob.iob + " ACTIVITY: " + iob.activity + " BGI: " + bgi + " DEVIATION: " + deviation); - - // if bgTime is more recent than mealTime - if (mealTime != null && bgTime > mealTime) { - // figure out how many carbs that represents - // but always assume at least 3mg/dL/5m (default) absorption - double ci = Math.max(deviation, SP.getDouble("openapsama_min_5m_carbimpact", 3.0)); - double absorbed = ci * profile.getIc(secondsFromMidnight) / sens; - // and add that to the running total carbsAbsorbed - carbsAbsorbed += absorbed; - } - } - - double ratio = 1; - String ratioLimit = ""; - String sensResult = ""; - - if (mealTime == null) { - //console.error(""); - log.debug(pastSensitivity); - //console.log(JSON.stringify(avgDeltas)); - //console.log(JSON.stringify(bgis)); - //Arrays.sort(avgDeltas); - //Arrays.sort(bgis); - Arrays.sort(deviations); - - for (double i = 0.9; i > 0.1; i = i - 0.02) { - //console.error("p="+i.toFixed(2)+": "+percentile(avgDeltas, i).toFixed(2)+", "+percentile(bgis, i).toFixed(2)+", "+percentile(deviations, i).toFixed(2)); - if (percentile(deviations, (i + 0.02)) >= 0 && percentile(deviations, i) < 0) { - //console.error("p="+i.toFixed(2)+": "+percentile(avgDeltas, i).toFixed(2)+", "+percentile(bgis, i).toFixed(2)+", "+percentile(deviations, i).toFixed(2)); - log.debug(Math.round(100 * i) + "% of non-meal deviations negative (target 45%-50%)"); - } - } - double pSensitive = percentile(deviations, 0.50); - double pResistant = percentile(deviations, 0.45); - //p30 = percentile(deviations, 0.3); - -// average = deviationSum / deviations.length; - - //console.error("Mean deviation: "+average.toFixed(2)); - double basalOff = 0; - - if (pSensitive < 0) { // sensitive - basalOff = pSensitive * (60 / 5) / NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()), profile.getUnits()); - sensResult = "Excess insulin sensitivity detected"; - } else if (pResistant > 0) { // resistant - basalOff = pResistant * (60 / 5) / NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()), profile.getUnits()); - sensResult = "Excess insulin resistance detected"; - } else { - sensResult = "Sensitivity normal"; - } - log.debug(sensResult); - ratio = 1 + (basalOff / profile.getMaxDailyBasal()); - - // don't adjust more than 1.5x - double rawRatio = ratio; - ratio = Math.max(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_min", "0.7"))); - ratio = Math.min(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_max", "1.2"))); - - if (ratio != rawRatio) { - ratioLimit = "Ratio limited from " + rawRatio + " to " + ratio; - log.debug(ratioLimit); - } - - double newisf = Math.round(NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()), profile.getUnits()) / ratio); - if (ratio != 1) { - log.debug("ISF adjusted from " + NSProfile.toMgdl(profile.getIsf(NSProfile.secondsFromMidnight()), profile.getUnits()) + " to " + newisf); - } - //console.error("Basal adjustment "+basalOff.toFixed(2)+"U/hr"); - //console.error("Ratio: "+ratio*100+"%: new ISF: "+newisf.toFixed(1)+"mg/dL/U"); - } - - AutosensResult output = new AutosensResult(); - output.ratio = Round.roundTo(ratio, 0.01); - output.carbsAbsorbed = Round.roundTo(carbsAbsorbed, 0.01); - output.pastSensitivity = pastSensitivity; - output.ratioLimit = ratioLimit; - output.sensResult = sensResult; - return output; - } - - // From https://gist.github.com/IceCreamYou/6ffa1b18c4c8f6aeaad2 - // Returns the value at a given percentile in a sorted numeric array. - // "Linear interpolation between closest ranks" method - public static double percentile(double[] arr, double p) { - if (arr.length == 0) return 0; - if (p <= 0) return arr[0]; - if (p >= 1) return arr[arr.length - 1]; - - double index = arr.length * p, - lower = Math.floor(index), - upper = lower + 1, - weight = index % 1; - - if (upper >= arr.length) return arr[(int) lower]; - return arr[(int) lower] * (1 - weight) + arr[(int) upper] * weight; - } - - // Returns the percentile of the given value in a sorted numeric array. - public static double percentRank(double[] arr, double v) { - for (int i = 0, l = arr.length; i < l; i++) { - if (v <= arr[i]) { - while (i < l && v == arr[i]) i++; - if (i == 0) return 0; - if (v != arr[i - 1]) { - i += (v - arr[i - 1]) / (arr[i] - arr[i - 1]); - } - return i / l; - } - } - return 1; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java index 2d530f7b57..be4257dd47 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java @@ -17,6 +17,7 @@ import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.interfaces.APSInterface; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensResult; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.APSResult; import info.nightscout.androidaps.plugins.Loop.ScriptReader; @@ -217,7 +218,8 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { startPart = new Date(); if(MainApp.getConfigBuilder().isAMAModeEnabled()){ - lastAutosensResult = Autosens.detectSensitivityandCarbAbsorption(getBGDataFrom, null); + //lastAutosensResult = Autosens.detectSensitivityandCarbAbsorption(getBGDataFrom, null); + lastAutosensResult = IobCobCalculatorPlugin.detectSensitivity(getBGDataFrom); } else { lastAutosensResult = new AutosensResult(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java index aae89d26f4..78f20b6d24 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java @@ -1084,7 +1084,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, Double maxCobValueFound = 0d; if (showIobView.isChecked() || showCobView.isChecked()) { - Date start = new Date(); + //Date start = new Date(); List iobArray = new ArrayList<>(); List cobArray = new ArrayList<>(); for (long time = fromTime; time <= now; time += 5 * 60 * 1000L) { @@ -1101,7 +1101,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, } } } - Profiler.log(log, "IOB processed", start); + //Profiler.log(log, "IOB processed", start); DataPoint[] iobData = new DataPoint[iobArray.size()]; iobData = iobArray.toArray(iobData); iobSeries = new FixedLineGraphSeries<>(iobData); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java index 90ed870574..189b8d3bc6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java @@ -21,6 +21,8 @@ import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; +import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSProfile; import info.nightscout.utils.SP; @@ -51,7 +53,7 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { @Override public String getNameShort() { String name = MainApp.sResources.getString(R.string.treatments_shortname); - if (!name.trim().isEmpty()){ + if (!name.trim().isEmpty()) { //only if translation exists return name; } @@ -153,8 +155,27 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { public MealData getMealData() { MealData result = new MealData(); + NSProfile profile = MainApp.getConfigBuilder().getActiveProfile().getProfile(); + if (profile == null) return result; + + long now = new Date().getTime(); + long dia_ago = now - (new Double(1.5d * profile.getDia() * 60 * 60 * 1000l)).longValue(); + for (Treatment treatment : treatments) { - result.addTreatment(treatment); + long t = treatment.created_at.getTime(); + if (t > dia_ago && t <= now) { + if (treatment.carbs >= 1) { + result.carbs += treatment.carbs; + } + if (treatment.insulin > 0 && treatment.mealBolus) { + result.boluses += treatment.insulin; + } + } + } + + AutosensData autosensData = IobCobCalculatorPlugin.getLastAutosensData(); + if (autosensData != null) { + result.mealCOB = autosensData.cob; } return result; }