DynISF: resolve situation where TDD is not available

This commit is contained in:
Milos Kozak 2022-12-13 12:51:32 +01:00
parent 85f170f17d
commit 0e1caa3c9c
5 changed files with 90 additions and 85 deletions

View file

@ -7,10 +7,10 @@ import info.nightscout.database.entities.TotalDailyDose
interface TddCalculator {
fun calculate(days: Long): LongSparseArray<TotalDailyDose>
fun calculateToday(): TotalDailyDose
fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose
fun calculate(startTime: Long, endTime: Long): TotalDailyDose
fun averageTDD(tdds: LongSparseArray<TotalDailyDose>): TotalDailyDose?
fun calculate(days: Long, allowMissingDays: Boolean): LongSparseArray<TotalDailyDose>?
fun calculateToday(): TotalDailyDose?
fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose?
fun calculate(startTime: Long, endTime: Long, allowMissingData: Boolean): TotalDailyDose?
fun averageTDD(tdds: LongSparseArray<TotalDailyDose>?): TotalDailyDose?
fun stats(context: Context): TableLayout
}

View file

@ -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<TotalDailyDose> {
override fun calculate(days: Long, allowMissingDays: Boolean): LongSparseArray<TotalDailyDose>? {
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>): TotalDailyDose? {
override fun averageTDD(tdds: LongSparseArray<TotalDailyDose>?): 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))
}
}
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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)
}