From 0e1caa3c9c9fad5fd6adb03da620cdbcfe981d09 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 13 Dec 2022 12:51:32 +0100 Subject: [PATCH] DynISF: resolve situation where TDD is not available --- .../interfaces/stats/TddCalculator.kt | 10 ++-- .../implementation/stats/TddCalculatorImpl.kt | 49 ++++++++++------- .../OpenAPSSMBDynamicISF/determine-basal.js | 55 +++++++++---------- .../DetermineBasalAdapterSMBDynamicISFJS.kt | 52 +++++++++--------- .../plugins/ui/StatusLightHandler.kt | 9 ++- 5 files changed, 90 insertions(+), 85 deletions(-) diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/stats/TddCalculator.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/stats/TddCalculator.kt index f558d24dd6..0b9c56ab39 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/stats/TddCalculator.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/stats/TddCalculator.kt @@ -7,10 +7,10 @@ import info.nightscout.database.entities.TotalDailyDose interface TddCalculator { - fun calculate(days: Long): LongSparseArray - fun calculateToday(): TotalDailyDose - fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose - fun calculate(startTime: Long, endTime: Long): TotalDailyDose - fun averageTDD(tdds: LongSparseArray): TotalDailyDose? + fun calculate(days: Long, allowMissingDays: Boolean): LongSparseArray? + fun calculateToday(): TotalDailyDose? + fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose? + fun calculate(startTime: Long, endTime: Long, allowMissingData: Boolean): TotalDailyDose? + fun averageTDD(tdds: LongSparseArray?): TotalDailyDose? fun stats(context: Context): TableLayout } diff --git a/implementation/src/main/java/info/nightscout/implementation/stats/TddCalculatorImpl.kt b/implementation/src/main/java/info/nightscout/implementation/stats/TddCalculatorImpl.kt index 188e3e02aa..c05bc98348 100644 --- a/implementation/src/main/java/info/nightscout/implementation/stats/TddCalculatorImpl.kt +++ b/implementation/src/main/java/info/nightscout/implementation/stats/TddCalculatorImpl.kt @@ -8,6 +8,7 @@ import android.view.ViewGroup import android.widget.TableLayout import android.widget.TableRow import android.widget.TextView +import androidx.core.util.size import info.nightscout.database.ValueWrapper import info.nightscout.database.entities.Bolus import info.nightscout.database.entities.TotalDailyDose @@ -37,7 +38,7 @@ class TddCalculatorImpl @Inject constructor( private val repository: AppRepository ) : TddCalculator { - override fun calculate(days: Long): LongSparseArray { + override fun calculate(days: Long, allowMissingDays: Boolean): LongSparseArray? { var startTime = MidnightTime.calc(dateUtil.now() - T.days(days).msecs()) val endTime = MidnightTime.calc(dateUtil.now()) //val stepSize = T.hours(24).msecs() // this is not true on DST change @@ -55,8 +56,8 @@ class TddCalculatorImpl @Inject constructor( if (endTime > startTime) { var midnight = startTime while (midnight < endTime) { - val tdd = calculate(midnight, midnight + T.hours(24).msecs()) - result.put(midnight, tdd) + val tdd = calculate(midnight, midnight + T.hours(24).msecs(), allowMissingData = false) + if (tdd != null) result.put(midnight, tdd) midnight = MidnightTime.calc(midnight + T.hours(27).msecs()) // be sure we find correct midnight } } @@ -68,25 +69,28 @@ class TddCalculatorImpl @Inject constructor( repository.insertTotalDailyDose(tdd) } } - return result + if (result.size.toLong() == days || allowMissingDays) return result + return null } - override fun calculateToday(): TotalDailyDose { + override fun calculateToday(): TotalDailyDose? { val startTime = MidnightTime.calc(dateUtil.now()) val endTime = dateUtil.now() - return calculate(startTime, endTime) + return calculate(startTime, endTime, allowMissingData = true) } - override fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose { + override fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose? { val startTime = dateUtil.now() + T.hours(hour = startHours).msecs() val endTime = dateUtil.now() + T.hours(hour = endHours).msecs() - return calculate(startTime, endTime) + return calculate(startTime, endTime, allowMissingData = false) } - override fun calculate(startTime: Long, endTime: Long): TotalDailyDose { + override fun calculate(startTime: Long, endTime: Long, allowMissingData: Boolean): TotalDailyDose? { + val isWholeDay = (endTime - startTime) >= T.days(1).msecs() val startTimeAligned = startTime - startTime % (5 * 60 * 1000) val endTimeAligned = endTime - endTime % (5 * 60 * 1000) val tdd = TotalDailyDose(timestamp = startTimeAligned) + var tbrFound = false repository.getBolusesDataFromTimeToTime(startTime, endTime, true).blockingGet() .filter { it.type != Bolus.Type.PRIMING } .forEach { t -> @@ -98,8 +102,9 @@ class TddCalculatorImpl @Inject constructor( val calculationStep = T.mins(5).msecs() for (t in startTimeAligned until endTimeAligned step calculationStep) { - val profile = profileFunction.getProfile(t) ?: continue + val profile = profileFunction.getProfile(t) ?: if (allowMissingData) continue else return null val tbr = iobCobCalculator.getBasalData(profile, t) + if (tbr.isTempBasalRunning) tbrFound = true val absoluteRate = tbr.tempBasalAbsolute tdd.basalAmount += absoluteRate / 60.0 * 5.0 @@ -111,11 +116,13 @@ class TddCalculatorImpl @Inject constructor( } tdd.totalAmount = tdd.bolusAmount + tdd.basalAmount aapsLogger.debug(LTag.CORE, tdd.toString()) - return tdd + if (tdd.bolusAmount > 0 || tdd.basalAmount > 0 || tbrFound) return tdd + return null } - override fun averageTDD(tdds: LongSparseArray): TotalDailyDose? { + override fun averageTDD(tdds: LongSparseArray?): TotalDailyDose? { val totalTdd = TotalDailyDose(timestamp = dateUtil.now()) + tdds ?: return null if (tdds.size() == 0) return null for (i in 0 until tdds.size()) { val tdd = tdds.valueAt(i) @@ -132,7 +139,7 @@ class TddCalculatorImpl @Inject constructor( } override fun stats(context: Context): TableLayout { - val tdds = calculate(7) + val tdds = calculate(7, allowMissingDays = true) ?: return TableLayout(context) val averageTdd = averageTDD(tdds) val todayTdd = calculateToday() val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT) @@ -156,13 +163,15 @@ class TddCalculatorImpl @Inject constructor( }) layout.addView(averageTdd.toTableRow(context, rh, tdds.size(), includeCarbs = true)) } - layout.addView(TextView(context).apply { - text = rh.gs(info.nightscout.shared.R.string.today) - setTypeface(typeface, Typeface.BOLD) - gravity = Gravity.CENTER_HORIZONTAL - setTextAppearance(android.R.style.TextAppearance_Material_Medium) - }) - layout.addView(todayTdd.toTableRow(context, rh, dateUtil, includeCarbs = true)) + todayTdd?.let { + layout.addView(TextView(context).apply { + text = rh.gs(info.nightscout.shared.R.string.today) + setTypeface(typeface, Typeface.BOLD) + gravity = Gravity.CENTER_HORIZONTAL + setTextAppearance(android.R.style.TextAppearance_Material_Medium) + }) + layout.addView(todayTdd.toTableRow(context, rh, dateUtil, includeCarbs = true)) + } } } } diff --git a/plugins/aps/src/main/assets/OpenAPSSMBDynamicISF/determine-basal.js b/plugins/aps/src/main/assets/OpenAPSSMBDynamicISF/determine-basal.js index 34a3233647..df35771794 100644 --- a/plugins/aps/src/main/assets/OpenAPSSMBDynamicISF/determine-basal.js +++ b/plugins/aps/src/main/assets/OpenAPSSMBDynamicISF/determine-basal.js @@ -577,14 +577,12 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // for IOBpredBGs, predicted deviation impact drops linearly from current deviation down to zero // over 60 minutes (data points every 5m) var predDev = ci * ( 1 - Math.min(1,IOBpredBGs.length/(60/5)) ); - //IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI + predDev; - IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD * (Math.log((Math.max( IOBpredBGs[IOBpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) - * 5 ),2)) + predDev; + if (!TDD) IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI + predDev; + else IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD * (Math.log((Math.max( IOBpredBGs[IOBpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) * 5 ),2)) + predDev; // calculate predBGs with long zero temp without deviations - //var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + predZTBGI; - var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + (round(( -iobTick.iobWithZeroTemp.activity * (1800 / ( TDD * (Math.log(( Math.max(ZTpredBGs[ZTpredBGs.length-1],39) / - insulinDivisor ) + 1 ) ) )) * 5 ), 2)); + if (!TDD) var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + predZTBGI; + else var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + (round(( -iobTick.iobWithZeroTemp.activity * (1800 / ( TDD * (Math.log(( Math.max(ZTpredBGs[ZTpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) * 5 ), 2)); // for COBpredBGs, predicted carb impact drops linearly from current carb impact down to zero // eventually accounting for all carbs (if they can be absorbed over DIA) var predCI = Math.max(0, Math.max(0,ci) * ( 1 - COBpredBGs.length/Math.max(cid*2,1) ) ); @@ -613,9 +611,8 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ //console.error(UAMpredBGs.length,slopeFromDeviations, predUCI); UAMduration=round((UAMpredBGs.length+1)*5/60,1); } - //UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + predBGI + Math.min(0, predDev) + predUCI; - UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD - * (Math.log(( Math.max(UAMpredBGs[UAMpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) * 5 ),2)) + Math.min(0, predDev) + predUCI; + if (!TDD) UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + predBGI + Math.min(0, predDev) + predUCI; + else UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD * (Math.log(( Math.max(UAMpredBGs[UAMpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) * 5 ),2)) + Math.min(0, predDev) + predUCI; //console.error(predBGI, predCI, predUCI); // truncate all BG predictions at 4 hours if ( IOBpredBGs.length < 48) { IOBpredBGs.push(IOBpredBG); } @@ -727,27 +724,29 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var fSensBG = Math.min(minPredBG,bg); - if (bg > target_bg && glucose_status.delta < 3 && glucose_status.delta > -3 && glucose_status.short_avgdelta > -3 && glucose_status.short_avgdelta < 3 && eventualBG > target_bg && eventualBG < bg ) { - var future_sens = ( 1800 / (Math.log((((fSensBG * 0.5) + (bg * 0.5))/insulinDivisor)+1)*TDD)); - //var future_sens_old = ( 277700 / (TDD * ((bg * 0.5) + (eventualBG * 0.5 )))); - console.log("Future state sensitivity is " +future_sens+" based on eventual and current bg due to flat glucose level above target"); - rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;"; - } - - else if( glucose_status.delta > 0 && eventualBG > target_bg || eventualBG > bg) { - var future_sens = ( 1800 / (Math.log((bg/insulinDivisor)+1)*TDD)); - //var future_sens_old = ( 277700 / (TDD * bg)); - console.log("Future state sensitivity is " +future_sens+" using current bg due to small delta or variation"); - rT.reason += "Dosing sensitivity: " +future_sens+" using current BG;"; + if (TDD) { + if (bg > target_bg && glucose_status.delta < 3 && glucose_status.delta > -3 && glucose_status.short_avgdelta > -3 && glucose_status.short_avgdelta < 3 && eventualBG > target_bg && eventualBG < bg ) { + var future_sens = ( 1800 / (Math.log((((fSensBG * 0.5) + (bg * 0.5))/insulinDivisor)+1)*TDD)); + //var future_sens_old = ( 277700 / (TDD * ((bg * 0.5) + (eventualBG * 0.5 )))); + console.log("Future state sensitivity is " +future_sens+" based on eventual and current bg due to flat glucose level above target"); + rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;"; } - else { - var future_sens = ( 1800 / (Math.log((fSensBG/insulinDivisor)+1)*TDD)); - //var future_sens_old = ( 277700 / (TDD * eventualBG)); - console.log("Future state sensitivity is " +future_sens+" based on eventual bg due to -ve delta"); - rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;"; - } - future_sens = round(future_sens,1); + else if( glucose_status.delta > 0 && eventualBG > target_bg || eventualBG > bg) { + var future_sens = ( 1800 / (Math.log((bg/insulinDivisor)+1)*TDD)); + //var future_sens_old = ( 277700 / (TDD * bg)); + console.log("Future state sensitivity is " +future_sens+" using current bg due to small delta or variation"); + rT.reason += "Dosing sensitivity: " +future_sens+" using current BG;"; + } + + else { + var future_sens = ( 1800 / (Math.log((fSensBG/insulinDivisor)+1)*TDD)); + //var future_sens_old = ( 277700 / (TDD * eventualBG)); + console.log("Future state sensitivity is " +future_sens+" based on eventual bg due to -ve delta"); + rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;"; + } + future_sens = round(future_sens,1); + } else future_sens = variable_sens var fractionCarbsLeft = meal_data.mealCOB/meal_data.carbs; diff --git a/plugins/aps/src/main/java/info/nightscout/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt b/plugins/aps/src/main/java/info/nightscout/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt index 19ee62abfa..c5e19f0061 100644 --- a/plugins/aps/src/main/java/info/nightscout/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt +++ b/plugins/aps/src/main/java/info/nightscout/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt @@ -262,23 +262,11 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri this.mealData.put("lastBolusTime", mealData.lastBolusTime) this.mealData.put("lastCarbTime", mealData.lastCarbTime) - val tdd1D = tddCalculator.averageTDD(tddCalculator.calculate(1))?.totalAmount - val tdd7D = tddCalculator.averageTDD(tddCalculator.calculate(7))?.totalAmount - val tddLast24H = tddCalculator.calculateDaily(-24, 0).totalAmount - val tddLast4H = tddCalculator.calculateDaily(-4, 0).totalAmount - val tddLast8to4H = tddCalculator.calculateDaily(-8, -4).totalAmount - - val tddWeightedFromLast8H = ((1.4 * tddLast4H) + (0.6 * tddLast8to4H)) * 3 -// console.error("Rolling 8 hours weight average: " + tdd_last8_wt + "; "); -// console.error("1-day average TDD is: " + tdd1 + "; "); -// console.error("7-day average TDD is: " + tdd7 + "; "); - - var tdd = - if (tdd1D != null && tdd7D != null) (tddWeightedFromLast8H * 0.33) + (tdd7D * 0.34) + (tdd1D * 0.33) - else tddWeightedFromLast8H -// console.log("TDD = " + TDD + " using average of 7-day, 1-day and weighted 8hr average"); - -// console.log("Insulin Peak = " + insulin.peak + "; "); + val tdd1D = tddCalculator.averageTDD(tddCalculator.calculate(1, allowMissingDays = false))?.totalAmount + val tdd7D = tddCalculator.averageTDD(tddCalculator.calculate(7, allowMissingDays = false))?.totalAmount + val tddLast24H = tddCalculator.calculateDaily(-24, 0)?.totalAmount + val tddLast4H = tddCalculator.calculateDaily(-4, 0)?.totalAmount + val tddLast8to4H = tddCalculator.calculateDaily(-8, -4)?.totalAmount val insulin = activePlugin.activeInsulin val insulinDivisor = when { @@ -286,25 +274,35 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri insulin.peak > 50 -> 65 // ultra rapid peak: 55 else -> 75 // rapid peak: 75 } -// console.log("For " + insulin.friendlyName + " (insulin peak: " + insulin.peak + ") insulin divisor is: " + ins_val + "; "); - val dynISFadjust = SafeParse.stringToDouble(sp.getString(R.string.key_DynISFAdjust, "100")) / 100.0 - tdd *= dynISFadjust + var tdd: Double? = null + var variableSensitivity: Double + if (tddLast24H != null && tddLast4H != null && tddLast8to4H != null) { + val tddWeightedFromLast8H = ((1.4 * tddLast4H) + (0.6 * tddLast8to4H)) * 3 +// console.error("Rolling 8 hours weight average: " + tdd_last8_wt + "; "); +// console.error("1-day average TDD is: " + tdd1 + "; "); +// console.error("7-day average TDD is: " + tdd7 + "; "); - var variableSensitivity = 1800 / (tdd * (ln((glucoseStatus.glucose / insulinDivisor) + 1))) - variableSensitivity = Round.roundTo(variableSensitivity, 0.1) + tdd = + if (tdd1D != null && tdd7D != null) (tddWeightedFromLast8H * 0.33) + (tdd7D * 0.34) + (tdd1D * 0.33) + else tddWeightedFromLast8H +// console.log("TDD = " + TDD + " using average of 7-day, 1-day and weighted 8hr average"); - if (dynISFadjust != 1.0) { -// console.log("TDD adjusted to " + TDD + " using adjustment factor of " + dynISFadjust + "; "); + val dynISFadjust = SafeParse.stringToDouble(sp.getString(R.string.key_DynISFAdjust, "100")) / 100.0 + tdd *= dynISFadjust + + variableSensitivity = 1800 / (tdd * (ln((glucoseStatus.glucose / insulinDivisor) + 1))) + variableSensitivity = Round.roundTo(variableSensitivity, 0.1) + } else { + variableSensitivity = profile.getIsfMgdl() } -// console.log("Current sensitivity for predictions is " + variable_sens + " based on current bg"); this.profile.put("variable_sens", variableSensitivity) this.profile.put("insulinDivisor", insulinDivisor) - this.profile.put("TDD", tdd) + tdd?.let { this.profile.put("TDD", tdd) } - if (sp.getBoolean(R.string.key_adjust_sensitivity, false) && tdd7D != null && tdd7D != 0.0) + if (sp.getBoolean(R.string.key_adjust_sensitivity, false) && tdd7D != null && tddLast24H != null) autosensData.put("ratio", tddLast24H / tdd7D) else autosensData.put("ratio", 1.0) diff --git a/plugins/main/src/main/java/info/nightscout/plugins/ui/StatusLightHandler.kt b/plugins/main/src/main/java/info/nightscout/plugins/ui/StatusLightHandler.kt index 5becfcf64f..f2713c5704 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/ui/StatusLightHandler.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/ui/StatusLightHandler.kt @@ -133,11 +133,10 @@ class StatusLightHandler @Inject constructor( private fun handleUsage(view: TextView?, units: String) { handler.post { val therapyEvent = repository.getLastTherapyRecordUpToNow(TherapyEvent.Type.CANNULA_CHANGE).blockingGet() - var usage = 0.0 - if (therapyEvent is ValueWrapper.Existing) { - val tdd = tddCalculator.calculate(therapyEvent.value.timestamp, dateUtil.now()) - usage = tdd.totalAmount - } + var usage = + if (therapyEvent is ValueWrapper.Existing) { + tddCalculator.calculate(therapyEvent.value.timestamp, dateUtil.now(), allowMissingData = false)?.totalAmount ?: 0.0 + } else 0.0 runOnUiThread { view?.text = DecimalFormatter.to0Decimal(usage, units) }