Add Tune insulin Curve option

This commit is contained in:
Philoul 2022-05-09 08:59:57 +02:00
parent bf46d8a605
commit e7da675e1e
10 changed files with 323 additions and 352 deletions

View file

@ -1,6 +1,7 @@
package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
@ -28,16 +29,14 @@ class AutotuneCore @Inject constructor(
var carbRatio = previousAutotune.ic
//console.error(carbRatio);
var csf = isf / carbRatio
//val dia = previousAutotune.dia
//val insulinInterface = activePlugin.activeInsulin
//var peak = 75
//if (insulinInterface.id == InsulinInterface.InsulinType.OREF_ULTRA_RAPID_ACTING) peak = 55 else if (insulinInterface.id == InsulinInterface.InsulinType.OREF_FREE_PEAK) peak = sp.getInt(R.string.key_insulin_oref_peak, 75)
var dia = previousAutotune.dia
var peak = previousAutotune.peak
val csfGlucose = preppedGlucose.csfGlucoseData
val isfGlucose = preppedGlucose.isfGlucoseData
val basalGlucose = preppedGlucose.basalGlucoseData
val crData = preppedGlucose.crData
//List<DiaDatum> diaDeviations = preppedGlucose.diaDeviations;
//List<PeakDatum> peakDeviations = preppedGlucose.peakDeviations;
val diaDeviations = preppedGlucose.diaDeviations
val peakDeviations = preppedGlucose.peakDeviations
val pumpISF = pumpProfile.isf
val pumpCarbRatio = pumpProfile.ic
val pumpCSF = pumpISF / pumpCarbRatio
@ -46,92 +45,98 @@ class AutotuneCore @Inject constructor(
val autotuneMin = sp.getDouble(R.string.key_openapsama_autosens_min, 0.7)
val min5minCarbImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0)
/*******Tune DIA (#57-#99) and Peak (#101-#139) disabled for the first version code below in js********************************************************************************************************
* // tune DIA
* var newDIA = DIA;
* if (diaDeviations) {
* var currentDIAMeanDev = diaDeviations[2].meanDeviation;
* var currentDIARMSDev = diaDeviations[2].RMSDeviation;
* //console.error(DIA,currentDIAMeanDev,currentDIARMSDev);
* var minMeanDeviations = 1000000;
* var minRMSDeviations = 1000000;
* var meanBest = 2;
* var RMSBest = 2;
* for (var i=0; i < diaDeviations.length; i++) {
* var meanDeviations = diaDeviations[i].meanDeviation;
* var RMSDeviations = diaDeviations[i].RMSDeviation;
* if (meanDeviations < minMeanDeviations) {
* minMeanDeviations = Math.round(meanDeviations*1000)/1000;
* meanBest = i;
* }
* if (RMSDeviations < minRMSDeviations) {
* minRMSDeviations = Math.round(RMSDeviations*1000)/1000;
* RMSBest = i;
* }
* }
* console.error("Best insulinEndTime for meanDeviations:",diaDeviations[meanBest].dia,"hours");
* console.error("Best insulinEndTime for RMSDeviations:",diaDeviations[RMSBest].dia,"hours");
* if ( meanBest < 2 && RMSBest < 2 ) {
* if ( diaDeviations[1].meanDeviation < currentDIAMeanDev * 0.99 && diaDeviations[1].RMSDeviation < currentDIARMSDev * 0.99 ) {
* newDIA = diaDeviations[1].dia;
* }
* } else if ( meanBest > 2 && RMSBest > 2 ) {
* if ( diaDeviations[3].meanDeviation < currentDIAMeanDev * 0.99 && diaDeviations[3].RMSDeviation < currentDIARMSDev * 0.99 ) {
* newDIA = diaDeviations[3].dia;
* }
* }
* if ( newDIA > 12 ) {
* console.error("insulinEndTime maximum is 12h: not raising further");
* newDIA=12;
* }
* if ( newDIA !== DIA ) {
* console.error("Adjusting insulinEndTime from",DIA,"to",newDIA,"hours");
* } else {
* console.error("Leaving insulinEndTime unchanged at",DIA,"hours");
* }
* }
*
* // tune insulinPeakTime
* var newPeak = peak;
* if (peakDeviations && peakDeviations[2]) {
* var currentPeakMeanDev = peakDeviations[2].meanDeviation;
* var currentPeakRMSDev = peakDeviations[2].RMSDeviation;
* //console.error(currentPeakMeanDev);
* minMeanDeviations = 1000000;
* minRMSDeviations = 1000000;
* meanBest = 2;
* RMSBest = 2;
* for (i=0; i < peakDeviations.length; i++) {
* meanDeviations = peakDeviations[i].meanDeviation;
* RMSDeviations = peakDeviations[i].RMSDeviation;
* if (meanDeviations < minMeanDeviations) {
* minMeanDeviations = Math.round(meanDeviations*1000)/1000;
* meanBest = i;
* }
* if (RMSDeviations < minRMSDeviations) {
* minRMSDeviations = Math.round(RMSDeviations*1000)/1000;
* RMSBest = i;
* }
* }
* console.error("Best insulinPeakTime for meanDeviations:",peakDeviations[meanBest].peak,"minutes");
* console.error("Best insulinPeakTime for RMSDeviations:",peakDeviations[RMSBest].peak,"minutes");
* if ( meanBest < 2 && RMSBest < 2 ) {
* if ( peakDeviations[1].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[1].RMSDeviation < currentPeakRMSDev * 0.99 ) {
* newPeak = peakDeviations[1].peak;
* }
* } else if ( meanBest > 2 && RMSBest > 2 ) {
* if ( peakDeviations[3].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[3].RMSDeviation < currentPeakRMSDev * 0.99 ) {
* newPeak = peakDeviations[3].peak;
* }
* }
* if ( newPeak !== peak ) {
* console.error("Adjusting insulinPeakTime from",peak,"to",newPeak,"minutes");
* } else {
* console.error("Leaving insulinPeakTime unchanged at",peak);
* }
* }
*
*/
// tune DIA
var newDia = dia
if (diaDeviations.size > 0)
{
val currentDiaMeanDev = diaDeviations[2].meanDeviation
val currentDiaRMSDev = diaDeviations[2].rmsDeviation
//Console.WriteLine(DIA,currentDIAMeanDev,currentDIARMSDev);
var minMeanDeviations = 1000000.0
var minRmsDeviations = 1000000.0
var meanBest = 2
var rmsBest = 2
for (i in 0..diaDeviations.size-1)
{
val meanDeviations = diaDeviations[i].meanDeviation
val rmsDeviations = diaDeviations[i].rmsDeviation
if (meanDeviations < minMeanDeviations)
{
minMeanDeviations = Round.roundTo(meanDeviations, 0.001)
meanBest = i
}
if (rmsDeviations < minRmsDeviations)
{
minRmsDeviations = Round.roundTo(rmsDeviations, 0.001)
rmsBest = i
}
}
log("Best insulinEndTime for meanDeviations: ${diaDeviations[meanBest].dia} hours")
log("Best insulinEndTime for RMSDeviations: ${diaDeviations[rmsBest].dia} hours")
if (meanBest < 2 && rmsBest < 2)
{
if (diaDeviations[1].meanDeviation < currentDiaMeanDev * 0.99 && diaDeviations[1].rmsDeviation < currentDiaRMSDev * 0.99)
newDia = diaDeviations[1].dia
}
else if (meanBest > 2 && rmsBest > 2)
{
if (diaDeviations[3].meanDeviation < currentDiaMeanDev * 0.99 && diaDeviations[3].rmsDeviation < currentDiaRMSDev * 0.99)
newDia = diaDeviations[3].dia
}
if (newDia > 12.0)
{
log("insulinEndTime maximum is 12h: not raising further")
newDia = 12.0
}
if (newDia != dia)
log("Adjusting insulinEndTime from $dia to $newDia hours")
else
log("Leaving insulinEndTime unchanged at $dia hours")
}
// tune insulinPeakTime
var newPeak = peak
if (peakDeviations.size > 2)
{
val currentPeakMeanDev = peakDeviations[2].meanDeviation
val currentPeakRMSDev = peakDeviations[2].rmsDeviation
//Console.WriteLine(currentPeakMeanDev);
var minMeanDeviations = 1000000.0
var minRmsDeviations = 1000000.0
var meanBest = 2
var rmsBest = 2
for (i in 0..peakDeviations.size-1)
{
val meanDeviations = peakDeviations[i].meanDeviation;
val rmsDeviations = peakDeviations[i].rmsDeviation;
if (meanDeviations < minMeanDeviations)
{
minMeanDeviations = Round.roundTo(meanDeviations, 0.001)
meanBest = i
}
if (rmsDeviations < minRmsDeviations)
{
minRmsDeviations = Round.roundTo(rmsDeviations, 0.001)
rmsBest = i
}
}
log("Best insulinPeakTime for meanDeviations: ${peakDeviations[meanBest].peak} minutes")
log("Best insulinPeakTime for RMSDeviations: ${peakDeviations[rmsBest].peak} minutes")
if (meanBest < 2 && rmsBest < 2)
{
if (peakDeviations[1].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[1].rmsDeviation < currentPeakRMSDev * 0.99)
newPeak = peakDeviations[1].peak
}
else if (meanBest > 2 && rmsBest > 2)
{
if (peakDeviations[3].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[3].rmsDeviation < currentPeakRMSDev * 0.99)
newPeak = peakDeviations[3].peak
}
if (newPeak != peak)
log("Adjusting insulinPeakTime from " + peak + " to " + newPeak + " minutes")
else
log("Leaving insulinPeakTime unchanged at " + peak)
}
// Calculate carb ratio (CR) independently of csf and isf
// Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
@ -142,7 +147,7 @@ class AutotuneCore @Inject constructor(
//autotune-core (lib/autotune/index.js) #149-#165
var crTotalCarbs = 0.0
var crTotalInsulin = 0.0
for (i in crData!!.indices) {
for (i in crData.indices) {
val crDatum = crData[i]
val crBGChange = crDatum.crEndBG - crDatum.crInitialBG
val crInsulinReq = crBGChange / isf
@ -181,7 +186,7 @@ class AutotuneCore @Inject constructor(
// look at net deviations for each hour
for (hour in 0..23) {
var deviations = 0.0
for (i in basalGlucose!!.indices) {
for (i in basalGlucose.indices) {
val BGTime = Calendar.getInstance()
//var BGTime: Date? = null
if (basalGlucose[i].date != 0L) {
@ -300,7 +305,7 @@ class AutotuneCore @Inject constructor(
//log.debug(CSFGlucose[0].mealAbsorption);
//log.debug(CSFGlucose[0]);
//autotune-core (lib/autotune/index.js) #346-#365
for (i in csfGlucose!!.indices) {
for (i in csfGlucose.indices) {
//log.debug(CSFGlucose[i].mealAbsorption, i);
if (csfGlucose[i].mealAbsorption === "start") {
deviations = 0.0
@ -412,7 +417,7 @@ class AutotuneCore @Inject constructor(
val avgDeltas: MutableList<Double> = ArrayList()
val ratios: MutableList<Double> = ArrayList()
var count = 0
for (i in isfGlucose!!.indices) {
for (i in isfGlucose.indices) {
val deviation = isfGlucose[i].deviation
isfDeviations.add(deviation)
val BGI = isfGlucose[i].bgi
@ -497,13 +502,10 @@ class AutotuneCore @Inject constructor(
previousAutotune.isf = isf
previousAutotune.ic = Round.roundTo(carbRatio, 0.001)
previousAutotune.basalUntuned = basalUntuned
/* code prepared for future dia/peak integration
previousAutotune.dia=newDia;
previousAutotune.peak = newPeak ;
if (diaDeviations || peakDeviations) {
autotuneOutput.useCustomPeakTime = true;
}
*/
previousAutotune.dia = newDia
previousAutotune.peak = newPeak
val localInsulin = LocalInsulin("Ins_$newPeak-$newDia", newPeak, newDia)
previousAutotune.localInsulin = localInsulin
previousAutotune.updateProfile()
return previousAutotune
}

View file

@ -384,6 +384,7 @@ class AutotuneFragment : DaggerFragment() {
if (autotunePlugin.result.isNotBlank()) {
var toMgDl = 1.0
if (profileFunction.getUnits() == GlucoseUnit.MMOL) toMgDl = Constants.MMOLL_TO_MGDL
var isf_Format = if (profileFunction.getUnits() == GlucoseUnit.MMOL) "%.2f" else "%.1f"
binding.autotuneResults.addView(
TableLayout(context).also { layout ->
layout.addView(
@ -395,8 +396,13 @@ class AutotuneFragment : DaggerFragment() {
})
autotunePlugin.tunedProfile?.let { tuned ->
layout.addView(toTableRowHeader())
layout.addView(toTableRowValue(rh.gs(R.string.isf_short), Round.roundTo(autotunePlugin.pumpProfile.isf / toMgDl, 0.001), Round.roundTo(tuned.isf / toMgDl, 0.001)))
layout.addView(toTableRowValue(rh.gs(R.string.ic_short), Round.roundTo(autotunePlugin.pumpProfile.ic, 0.001), Round.roundTo(tuned.ic, 0.001)))
val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
if (tuneInsulin) {
layout.addView(toTableRowValue(rh.gs(R.string.insulin_peak), autotunePlugin.pumpProfile.localInsulin.peak.toDouble(), tuned.localInsulin.peak.toDouble(), "%.0f"))
layout.addView(toTableRowValue(rh.gs(R.string.dia), Round.roundTo(autotunePlugin.pumpProfile.localInsulin.dia, 0.1), Round.roundTo(tuned.localInsulin.dia, 0.1),"%.1f"))
}
layout.addView(toTableRowValue(rh.gs(R.string.isf_short), Round.roundTo(autotunePlugin.pumpProfile.isf / toMgDl, 0.001), Round.roundTo(tuned.isf / toMgDl, 0.001), isf_Format))
layout.addView(toTableRowValue(rh.gs(R.string.ic_short), Round.roundTo(autotunePlugin.pumpProfile.ic, 0.001), Round.roundTo(tuned.ic, 0.001), "%.2f"))
layout.addView(
TextView(context).apply {
text = rh.gs(R.string.basal)
@ -413,7 +419,7 @@ class AutotuneFragment : DaggerFragment() {
val time = df.format(h.toLong()) + ":00"
totalPump += autotunePlugin.pumpProfile.basal[h]
totalTuned += tuned.basal[h]
layout.addView(toTableRowValue(time, autotunePlugin.pumpProfile.basal[h], tuned.basal[h], tuned.basalUntuned[h].toString()))
layout.addView(toTableRowValue(time, autotunePlugin.pumpProfile.basal[h], tuned.basal[h], "%.3f", tuned.basalUntuned[h].toString()))
}
layout.addView(toTableRowValue("", totalPump, totalTuned, " "))
}
@ -456,7 +462,7 @@ class AutotuneFragment : DaggerFragment() {
})
}
private fun toTableRowValue(hour: String, inputValue: Double, tunedValue: Double, missing: String = ""): TableRow =
private fun toTableRowValue(hour: String, inputValue: Double, tunedValue: Double, format:String = "%.3f", missing: String = ""): TableRow =
TableRow(context).also { row ->
val percentValue = Round.roundTo(tunedValue / inputValue * 100 - 100, 1.0).toInt().toString() + "%"
val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f }
@ -469,12 +475,12 @@ class AutotuneFragment : DaggerFragment() {
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 1 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = String.format("%.3f", inputValue)
text = String.format(format, inputValue)
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 2 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = String.format("%.3f", tunedValue)
text = String.format(format, tunedValue)
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 3 }

View file

@ -31,7 +31,6 @@ import javax.inject.Singleton
* adaptation from oref0 autotune started by philoul on 2020 (complete refactoring of AutotunePlugin initialised by Rumen Georgiev on 1/29/2018.)
*
* TODO: replace Thread by Worker
* TODO: future version (once first version validated): add DIA and Peak tune for insulin
* TODO: future version: Allow day of the week selection to tune specifics days (training days, working days, WE days)
*/
@ -94,7 +93,7 @@ class AutotunePlugin @Inject constructor(
profileFunction.getProfile()?.let { currentProfile ->
profile = profileStore.getSpecificProfile(profileToTune)?.let { ProfileSealed.Pure(it) } ?: currentProfile
}
var localInsulin = LocalInsulin("PumpInsulin", activePlugin.activeInsulin.peak, profile.dia) // var because localInsulin could be updated later with Tune Insulin peak/dia
val localInsulin = LocalInsulin("PumpInsulin", activePlugin.activeInsulin.peak, profile.dia) // var because localInsulin could be updated later with Tune Insulin peak/dia
log("Start Autotune with $daysBack days back")
autotuneFS.createAutotuneFolder() //create autotune subfolder for autotune files if not exists
@ -120,7 +119,7 @@ class AutotunePlugin @Inject constructor(
autotuneIob.initializeData(from, to, tunedProfile) //autotuneIob contains BG and Treatments data from history (<=> query for ns-treatments and ns-entries)
autotuneFS.exportEntries(autotuneIob) //<=> ns-entries.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
autotuneFS.exportTreatments(autotuneIob) //<=> ns-treatments.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine (include treatments ,tempBasal and extended
preppedGlucose = autotunePrep.categorizeBGDatums(tunedProfile, localInsulin) //<=> autotune.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
preppedGlucose = autotunePrep.categorize(tunedProfile) //<=> autotune.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
}
if (preppedGlucose == null || tunedProfile == null) {
@ -204,9 +203,14 @@ class AutotunePlugin @Inject constructor(
var strResult = line
strResult += rh.gs(R.string.autotune_log_title)
strResult += line
val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
if (tuneInsulin) {
strResult += rh.gs(R.string.autotune_log_peak, rh.gs(R.string.insulin_peak), pumpProfile.localInsulin.peak, tunedProfile.localInsulin.peak)
strResult += rh.gs(R.string.autotune_log_dia, rh.gs(R.string.ic_short), pumpProfile.localInsulin.dia, tunedProfile.localInsulin.dia)
}
// show ISF and CR
strResult += rh.gs(R.string.autotune_log_isf, rh.gs(R.string.isf_short), pumpProfile.isf, tunedProfile.isf)
strResult += rh.gs(R.string.autotune_log_ic, rh.gs(R.string.ic_short), pumpProfile.ic, tunedProfile.ic)
strResult += rh.gs(R.string.autotune_log_ic_isf, rh.gs(R.string.isf_short), pumpProfile.isf, tunedProfile.isf)
strResult += rh.gs(R.string.autotune_log_ic_isf, rh.gs(R.string.ic_short), pumpProfile.ic, tunedProfile.ic)
strResult += line
var totalBasal = 0.0
var totalTuned = 0.0
@ -232,7 +236,7 @@ class AutotunePlugin @Inject constructor(
val endDateString = dateUtil.toISOString(lastloopend - 24 * 60 * 60 * 1000L).substring(0,10)
val nsUrl = sp.getString(R.string.key_nsclientinternal_url, "")
val optCategorizeUam = if (sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false)) "-c=true" else ""
val optInsulinCurve = ""
val optInsulinCurve = if (sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)) "-i=true" else ""
try {
jsonSettings.put("datestring", dateUtil.toISOString(runDate))
jsonSettings.put("dateutc", dateUtil.toISOAsUTC(runDate))

View file

@ -3,14 +3,13 @@ package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.data.BGDatum
import info.nightscout.androidaps.plugins.general.autotune.data.CRDatum
import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
import info.nightscout.androidaps.plugins.general.autotune.data.*
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.Carbs
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.MidnightTime
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
@ -23,11 +22,121 @@ class AutotunePrep @Inject constructor(
private val autotuneFS: AutotuneFS,
private val autotuneIob: AutotuneIob
) {
fun categorize(tunedprofile: ATProfile): PreppedGlucose? {
val preppedGlucose = categorizeBGDatums(tunedprofile, tunedprofile.localInsulin)
val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
if (tuneInsulin) {
var minDeviations = 1000000.0
val diaDeviations: MutableList<DiaDeviation> = ArrayList()
val peakDeviations: MutableList<PeakDeviation> = ArrayList()
val currentDIA = tunedprofile.localInsulin.dia
val currentPeak = tunedprofile.localInsulin.peak
var dia = currentDIA - 2
val endDIA = currentDIA + 2
while (dia <= endDIA)
{
var sqrtDeviations = 0.0
var deviations = 0.0
var deviationsSq = 0.0
val localInsulin = LocalInsulin("Ins_$currentPeak-$dia", currentPeak, dia)
val curve_output = categorizeBGDatums(tunedprofile, localInsulin, false)
val basalGlucose = curve_output?.basalGlucoseData
basalGlucose?.let {
for (hour in 0..23) {
for (i in 0..(basalGlucose.size-1)) {
val myHour = ((basalGlucose[i].date - MidnightTime.calc(basalGlucose[i].date)) / T.hours(1).msecs()).toInt()
if (hour == myHour) {
sqrtDeviations += Math.pow(Math.abs(basalGlucose[i].deviation), 0.5)
deviations += Math.abs(basalGlucose[i].deviation)
deviationsSq += Math.pow(basalGlucose[i].deviation, 2.0)
}
}
}
val meanDeviation = Round.roundTo(Math.abs(deviations / basalGlucose.size), 0.001)
val smrDeviation = Round.roundTo(Math.pow(sqrtDeviations / basalGlucose.size, 2.0), 0.001)
val rmsDeviation = Round.roundTo(Math.pow(deviationsSq / basalGlucose.size, 0.5), 0.001)
log("insulinEndTime $dia meanDeviation: $meanDeviation SMRDeviation: $smrDeviation RMSDeviation: $rmsDeviation (mg/dL)")
diaDeviations.add(
DiaDeviation(
dia = dia,
meanDeviation = meanDeviation,
smrDeviation = smrDeviation,
rmsDeviation = rmsDeviation
)
)
}
preppedGlucose?.diaDeviations = diaDeviations
deviations = Round.roundTo(deviations, 0.001)
if (deviations < minDeviations)
minDeviations = Round.roundTo(deviations, 0.001)
dia += 1.0
}
// consoleError('Optimum insulinEndTime', newDIA, 'mean deviation:', JSMath.Round(minDeviations/basalGlucose.length*1000)/1000, '(mg/dL)');
//consoleError(diaDeviations);
minDeviations = 1000000.0
var peak = currentPeak - 10
val endPeak = currentPeak + 10
while (peak <= endPeak)
{
var sqrtDeviations = 0.0
var deviations = 0.0
var deviationsSq = 0.0
val localInsulin = LocalInsulin("Ins_$peak-$currentDIA", peak, currentDIA)
val curve_output = categorizeBGDatums(tunedprofile, localInsulin, false)
val basalGlucose = curve_output?.basalGlucoseData
basalGlucose?.let {
for (hour in 0..23) {
for (i in 0..(basalGlucose.size - 1)) {
val myHour = ((basalGlucose[i].date - MidnightTime.calc(basalGlucose[i].date)) / T.hours(1).msecs()).toInt()
if (hour == myHour) {
//console.error(basalGlucose[i].deviation);
sqrtDeviations += Math.pow(Math.abs(basalGlucose[i].deviation), 0.5)
deviations += Math.abs(basalGlucose[i].deviation)
deviationsSq += Math.pow(basalGlucose[i].deviation, 2.0)
}
}
}
val meanDeviation = Round.roundTo(deviations / basalGlucose.size, 0.001)
val smrDeviation = Round.roundTo(Math.pow(sqrtDeviations / basalGlucose.size, 2.0), 0.001)
val rmsDeviation = Round.roundTo(Math.pow(deviationsSq / basalGlucose.size, 0.5), 0.001)
log("insulinPeakTime $peak meanDeviation: $meanDeviation SMRDeviation: $smrDeviation RMSDeviation: $rmsDeviation (mg/dL)")
peakDeviations.add(
PeakDeviation
(
peak = peak,
meanDeviation = meanDeviation,
smrDeviation = smrDeviation,
rmsDeviation = rmsDeviation,
)
)
}
deviations = Round.roundTo(deviations, 0.001);
if (deviations < minDeviations)
minDeviations = Round.roundTo(deviations, 0.001)
peak += 5
}
//consoleError($"Optimum insulinPeakTime {newPeak} mean deviation: {JSMath.Round(minDeviations/basalGlucose.Count, 3)} (mg/dL)");
//consoleError(peakDeviations);
preppedGlucose?.peakDeviations = peakDeviations
}
return preppedGlucose
}
// private static Logger log = LoggerFactory.getLogger(AutotunePlugin.class);
fun categorizeBGDatums(tunedprofile: ATProfile, localInsulin: LocalInsulin): PreppedGlucose? {
fun categorizeBGDatums(tunedprofile: ATProfile, localInsulin: LocalInsulin, verbose: Boolean = true): PreppedGlucose? {
//lib/meals is called before to get only meals data (in AAPS it's done in AutotuneIob)
var treatments: MutableList<Carbs> = autotuneIob.meals
var boluses: MutableList<Bolus> = autotuneIob.boluses
val treatments: MutableList<Carbs> = autotuneIob.meals
val boluses: MutableList<Bolus> = autotuneIob.boluses
// Bloc between #21 and # 54 replaced by bloc below (just remove BG value below 39, Collections.sort probably not necessary because BG values already sorted...)
val glucose = autotuneIob.glucose
val glucoseData: MutableList<GlucoseValue> = ArrayList()
@ -37,6 +146,7 @@ class AutotunePrep @Inject constructor(
}
}
if (glucose.size == 0 || glucoseData.size == 0 ) {
if (verbose)
log("No BG value received")
return null
}
@ -49,10 +159,12 @@ class AutotunePrep @Inject constructor(
//val boluses = 0
//val maxCarbs = 0
if (treatments.size == 0) {
if (verbose)
log("No Carbs entries")
//return null
}
if (autotuneIob.boluses.size == 0) {
if (verbose)
log("No treatment received")
return null
}
@ -141,6 +253,7 @@ class AutotunePrep @Inject constructor(
}
avgDelta = (bg - bucketedData[i + 4].value) / 4
} else {
if (verbose)
log("Could not find glucose data")
}
avgDelta = Round.roundTo(avgDelta, 0.01)
@ -207,6 +320,7 @@ class AutotunePrep @Inject constructor(
crInitialIOB = iob.iob
crInitialBG = glucoseDatum.value
crInitialCarbTime = glucoseDatum.date
if (verbose)
log("CRInitialIOB: " + crInitialIOB + " CRInitialBG: " + crInitialBG + " CRInitialCarbTime: " + dateUtil.toISOString(crInitialCarbTime))
}
// keep calculatingCR as long as we have COB or enough IOB
@ -219,6 +333,7 @@ class AutotunePrep @Inject constructor(
val crEndIOB = iob.iob
val crEndBG = glucoseDatum.value
val crEndTime = glucoseDatum.date
if (verbose)
log("CREndIOB: " + crEndIOB + " CREndBG: " + crEndBG + " CREndTime: " + dateUtil.toISOString(crEndTime))
val crDatum = CRDatum(dateUtil)
crDatum.crInitialBG = crInitialBG
@ -234,6 +349,7 @@ class AutotunePrep @Inject constructor(
//log.debug(CREndTime - CRInitialCarbTime, CRElapsedMinutes);
if (CRElapsedMinutes < 60 || i == 1 && mealCOB > 0) {
if (verbose)
log("Ignoring $CRElapsedMinutes m CR period.")
} else {
crData.add(crDatum)
@ -262,6 +378,7 @@ class AutotunePrep @Inject constructor(
//log.debug(type);
if (type != "csf") {
glucoseDatum.mealAbsorption = "start"
if (verbose)
log(glucoseDatum.mealAbsorption + " carb absorption")
}
type = "csf"
@ -272,6 +389,7 @@ class AutotunePrep @Inject constructor(
// check previous "type" value, and if it was csf, set a mealAbsorption end flag
if (type == "csf") {
csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption = "end"
if (verbose)
log(csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption + " carb absorption")
}
if (iob.iob > 2 * currentBasal || deviation > 6 || uam) {
@ -282,12 +400,14 @@ class AutotunePrep @Inject constructor(
}
if (type != "uam") {
glucoseDatum.uamAbsorption = "start"
if (verbose)
log(glucoseDatum.uamAbsorption + " unannnounced meal absorption")
}
type = "uam"
uamGlucoseData.add(glucoseDatum)
} else {
if (type == "uam") {
if (verbose)
log("end unannounced meal absorption")
}
@ -313,6 +433,7 @@ class AutotunePrep @Inject constructor(
}
}
// debug line to print out all the things
if (verbose)
log((if (absorbing) 1 else 0).toString() + " mealCOB: " + Round.roundTo(mealCOB, 0.1) + " mealCarbs: " + Math.round(mealCarbs) + " basalBGI: " + Round.roundTo(basalBGI, 0.1) + " BGI: " + Round.roundTo(BGI, 0.1) + " IOB: " + iob.iob+ " Activity: " + iob.activity + " at " + dateUtil.timeStringWithSeconds(BGTime) + " dev: " + deviation + " avgDelta: " + avgDelta + " " + type)
}
@ -328,16 +449,20 @@ class AutotunePrep @Inject constructor(
val UAMLength = uamGlucoseData.size
var basalLength = basalGlucoseData.size
if (sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false)) {
if (verbose)
log("Categorizing all UAM data as basal.")
basalGlucoseData.addAll(uamGlucoseData)
} else if (CSFLength > 12) {
if (verbose)
log("Found at least 1h of carb: assuming meals were announced, and categorizing UAM data as basal.")
basalGlucoseData.addAll(uamGlucoseData)
} else {
if (2 * basalLength < UAMLength) {
//log.debug(basalGlucoseData, UAMGlucoseData);
if (verbose) {
log("Warning: too many deviations categorized as UnAnnounced Meals")
log("Adding $UAMLength UAM deviations to $basalLength basal ones")
}
basalGlucoseData.addAll(uamGlucoseData)
//log.debug(basalGlucoseData);
// if too much data is excluded as UAM, add in the UAM deviations, but then discard the highest 50%
@ -348,9 +473,11 @@ class AutotunePrep @Inject constructor(
}
//log.debug(newBasalGlucose);
basalGlucoseData = newBasalGlucose
if (verbose)
log("and selecting the lowest 50%, leaving " + basalGlucoseData.size + " basal+UAM ones")
}
if (2 * ISFLength < UAMLength) {
if (verbose)
log("Adding $UAMLength UAM deviations to $ISFLength ISF ones")
isfGlucoseData.addAll(uamGlucoseData)
// if too much data is excluded as UAM, add in the UAM deviations to ISF, but then discard the highest 50%
@ -361,6 +488,7 @@ class AutotunePrep @Inject constructor(
}
//console.error(newISFGlucose);
isfGlucoseData = newISFGlucose
if (verbose)
log("and selecting the lowest 50%, leaving " + isfGlucoseData.size + " ISF+UAM ones")
//log.error(ISFGlucoseData.length, UAMLength);
}
@ -368,171 +496,21 @@ class AutotunePrep @Inject constructor(
basalLength = basalGlucoseData.size
ISFLength = isfGlucoseData.size
if (4 * basalLength + ISFLength < CSFLength && ISFLength < 10) {
if (verbose) {
log("Warning: too many deviations categorized as meals")
//log.debug("Adding",CSFLength,"CSF deviations to",basalLength,"basal ones");
//var basalGlucoseData = basalGlucoseData.concat(CSFGlucoseData);
log("Adding $CSFLength CSF deviations to $ISFLength ISF ones")
}
isfGlucoseData.addAll(csfGlucoseData)
csfGlucoseData = ArrayList()
}
// categorize.js Lines 437-444
if (verbose)
log("CRData: " + crData.size + " CSFGlucoseData: " + csfGlucoseData.size + " ISFGlucoseData: " + isfGlucoseData.size + " BasalGlucoseData: " + basalGlucoseData.size)
// Here is the end of categorize.js file
/* bloc below is for --tune-insulin-curve not developed for the moment
// these lines are in index.js file (autotune-prep folder)
if (inputs.tune_insulin_curve) {
if (opts.profile.curve === 'bilinear') {
console.error('--tune-insulin-curve is set but only valid for exponential curves');
} else {
var minDeviations = 1000000;
var newDIA = 0;
var diaDeviations = [];
var peakDeviations = [];
var currentDIA = opts.profile.dia;
var currentPeak = opts.profile.insulinPeakTime;
var consoleError = console.error;
console.error = function() {};
var startDIA=currentDIA - 2;
var endDIA=currentDIA + 2;
for (var dia=startDIA; dia <= endDIA; ++dia) {
var sqrtDeviations = 0;
var deviations = 0;
var deviationsSq = 0;
opts.profile.dia = dia;
var curve_output = categorize(opts);
var basalGlucose = curve_output.basalGlucoseData;
for (var hour=0; hour < 24; ++hour) {
for (var i=0; i < basalGlucose.length; ++i) {
var BGTime;
if (basalGlucose[i].date) {
BGTime = new Date(basalGlucose[i].date);
} else if (basalGlucose[i].displayTime) {
BGTime = new Date(basalGlucose[i].displayTime.replace('T', ' '));
} else if (basalGlucose[i].dateString) {
BGTime = new Date(basalGlucose[i].dateString);
} else {
consoleError("Could not determine last BG time");
}
var myHour = BGTime.getHours();
if (hour === myHour) {
//console.error(basalGlucose[i].deviation);
sqrtDeviations += Math.pow(parseFloat(Math.abs(basalGlucose[i].deviation)), 0.5);
deviations += Math.abs(parseFloat(basalGlucose[i].deviation));
deviationsSq += Math.pow(parseFloat(basalGlucose[i].deviation), 2);
}
}
}
var meanDeviation = Math.round(Math.abs(deviations/basalGlucose.length)*1000)/1000;
var SMRDeviation = Math.round(Math.pow(sqrtDeviations/basalGlucose.length,2)*1000)/1000;
var RMSDeviation = Math.round(Math.pow(deviationsSq/basalGlucose.length,0.5)*1000)/1000;
consoleError('insulinEndTime', dia, 'meanDeviation:', meanDeviation, 'SMRDeviation:', SMRDeviation, 'RMSDeviation:',RMSDeviation, '(mg/dL)');
diaDeviations.push({
dia: dia,
meanDeviation: meanDeviation,
SMRDeviation: SMRDeviation,
RMSDeviation: RMSDeviation,
});
autotune_prep_output.diaDeviations = diaDeviations;
deviations = Math.round(deviations*1000)/1000;
if (deviations < minDeviations) {
minDeviations = Math.round(deviations*1000)/1000;
newDIA = dia;
}
}
// consoleError('Optimum insulinEndTime', newDIA, 'mean deviation:', Math.round(minDeviations/basalGlucose.length*1000)/1000, '(mg/dL)');
//consoleError(diaDeviations);
minDeviations = 1000000;
var newPeak = 0;
opts.profile.dia = currentDIA;
//consoleError(opts.profile.useCustomPeakTime, opts.profile.insulinPeakTime);
if ( ! opts.profile.useCustomPeakTime === true && opts.profile.curve === "ultra-rapid" ) {
opts.profile.insulinPeakTime = 55;
} else if ( ! opts.profile.useCustomPeakTime === true ) {
opts.profile.insulinPeakTime = 75;
}
opts.profile.useCustomPeakTime = true;
var startPeak=opts.profile.insulinPeakTime - 10;
var endPeak=opts.profile.insulinPeakTime + 10;
for (var peak=startPeak; peak <= endPeak; peak=(peak+5)) {
sqrtDeviations = 0;
deviations = 0;
deviationsSq = 0;
opts.profile.insulinPeakTime = peak;
curve_output = categorize(opts);
basalGlucose = curve_output.basalGlucoseData;
for (hour=0; hour < 24; ++hour) {
for (i=0; i < basalGlucose.length; ++i) {
if (basalGlucose[i].date) {
BGTime = new Date(basalGlucose[i].date);
} else if (basalGlucose[i].displayTime) {
BGTime = new Date(basalGlucose[i].displayTime.replace('T', ' '));
} else if (basalGlucose[i].dateString) {
BGTime = new Date(basalGlucose[i].dateString);
} else {
consoleError("Could not determine last BG time");
}
myHour = BGTime.getHours();
if (hour === myHour) {
//console.error(basalGlucose[i].deviation);
sqrtDeviations += Math.pow(parseFloat(Math.abs(basalGlucose[i].deviation)), 0.5);
deviations += Math.abs(parseFloat(basalGlucose[i].deviation));
deviationsSq += Math.pow(parseFloat(basalGlucose[i].deviation), 2);
}
}
}
console.error(deviationsSq);
meanDeviation = Math.round(deviations/basalGlucose.length*1000)/1000;
SMRDeviation = Math.round(Math.pow(sqrtDeviations/basalGlucose.length,2)*1000)/1000;
RMSDeviation = Math.round(Math.pow(deviationsSq/basalGlucose.length,0.5)*1000)/1000;
consoleError('insulinPeakTime', peak, 'meanDeviation:', meanDeviation, 'SMRDeviation:', SMRDeviation, 'RMSDeviation:',RMSDeviation, '(mg/dL)');
peakDeviations.push({
peak: peak,
meanDeviation: meanDeviation,
SMRDeviation: SMRDeviation,
RMSDeviation: RMSDeviation,
});
autotune_prep_output.diaDeviations = diaDeviations;
deviations = Math.round(deviations*1000)/1000;
if (deviations < minDeviations) {
minDeviations = Math.round(deviations*1000)/1000;
newPeak = peak;
}
}
//consoleError('Optimum insulinPeakTime', newPeak, 'mean deviation:', Math.round(minDeviations/basalGlucose.length*1000)/1000, '(mg/dL)');
//consoleError(peakDeviations);
autotune_prep_output.peakDeviations = peakDeviations;
console.error = consoleError;
}
}
*/
return PreppedGlucose(autotuneIob.startBG, crData, csfGlucoseData, isfGlucoseData, basalGlucoseData, dateUtil)
// and may be later
// return new PreppedGlucose(crData, csfGlucoseData, isfGlucoseData, basalGlucoseData, diaDeviations, peakDeviations);
}
//dosed.js full

View file

@ -3,15 +3,9 @@ package info.nightscout.androidaps.plugins.general.autotune.data
import org.json.JSONException
import org.json.JSONObject
class DiaDatum {
class DiaDeviation(var dia: Double = 0.0, var meanDeviation: Double = 0.0, var smrDeviation: Double = 0.0, var rmsDeviation: Double = 0.0) {
var dia = 0.0
var meanDeviation = 0.0
var smrDeviation = 0.0
var rmsDeviation = 0.0
constructor() {}
constructor(json: JSONObject) {
constructor(json: JSONObject) : this() {
try {
if (json.has("dia")) dia = json.getDouble("dia")
if (json.has("meanDeviation")) meanDeviation = json.getDouble("meanDeviation")
@ -32,13 +26,4 @@ class DiaDatum {
}
return crjson
}
fun equals(obj: DiaDatum): Boolean {
var isEqual = true
if (dia != obj.dia) isEqual = false
if (meanDeviation != obj.meanDeviation) isEqual = false
if (smrDeviation != obj.smrDeviation) isEqual = false
if (rmsDeviation != obj.rmsDeviation) isEqual = false
return isEqual
}
}

View file

@ -3,17 +3,11 @@ package info.nightscout.androidaps.plugins.general.autotune.data
import org.json.JSONException
import org.json.JSONObject
class PeakDatum {
class PeakDeviation(var peak: Int = 0, var meanDeviation: Double = 0.0, var smrDeviation: Double = 0.0, var rmsDeviation: Double = 0.0) {
var peak = 0.0
var meanDeviation = 0.0
var smrDeviation = 0.0
var rmsDeviation = 0.0
constructor() {}
constructor(json: JSONObject) {
constructor(json: JSONObject) : this() {
try {
if (json.has("peak")) peak = json.getDouble("peak")
if (json.has("peak")) peak = json.getInt("peak")
if (json.has("meanDeviation")) meanDeviation = json.getDouble("meanDeviation")
if (json.has("SMRDeviation")) smrDeviation = json.getDouble("SMRDeviation")
if (json.has("RMSDeviation")) rmsDeviation = json.getDouble("RMSDeviation")
@ -32,13 +26,4 @@ class PeakDatum {
}
return crjson
}
fun equals(obj: PeakDatum): Boolean {
var isEqual = true
if (peak != obj.peak) isEqual = false
if (meanDeviation != obj.meanDeviation) isEqual = false
if (smrDeviation != obj.smrDeviation) isEqual = false
if (rmsDeviation != obj.rmsDeviation) isEqual = false
return isEqual
}
}

View file

@ -8,12 +8,12 @@ import java.util.*
class PreppedGlucose {
var crData: List<CRDatum>? = ArrayList()
var csfGlucoseData: List<BGDatum>? = ArrayList()
var isfGlucoseData: List<BGDatum>? = ArrayList()
var basalGlucoseData: List<BGDatum>? = ArrayList()
var diaDeviations: List<DiaDatum> = ArrayList()
var peakDeviations: List<PeakDatum> = ArrayList()
var crData: List<CRDatum> = ArrayList()
var csfGlucoseData: List<BGDatum> = ArrayList()
var isfGlucoseData: List<BGDatum> = ArrayList()
var basalGlucoseData: List<BGDatum> = ArrayList()
var diaDeviations: List<DiaDeviation> = ArrayList()
var peakDeviations: List<PeakDeviation> = ArrayList()
var from: Long = 0
lateinit var dateUtil: DateUtil
@ -22,7 +22,7 @@ class PreppedGlucose {
return toString(0)
}
constructor(from: Long, crData: List<CRDatum>?, csfGlucoseData: List<BGDatum>?, isfGlucoseData: List<BGDatum>?, basalGlucoseData: List<BGDatum>?, dateUtil: DateUtil) {
constructor(from: Long, crData: List<CRDatum>, csfGlucoseData: List<BGDatum>, isfGlucoseData: List<BGDatum>, basalGlucoseData: List<BGDatum>, dateUtil: DateUtil) {
this.from = from
this.crData = crData
this.csfGlucoseData = csfGlucoseData
@ -34,10 +34,10 @@ class PreppedGlucose {
constructor(json: JSONObject?, dateUtil: DateUtil) {
if (json == null) return
this.dateUtil = dateUtil
crData = null
csfGlucoseData = null
isfGlucoseData = null
basalGlucoseData = null
crData = ArrayList()
csfGlucoseData = ArrayList()
isfGlucoseData = ArrayList()
basalGlucoseData = ArrayList()
try {
crData = JsonCRDataToList(json.getJSONArray("CRData"))
csfGlucoseData = JsonGlucoseDataToList(json.getJSONArray("CSFGlucoseData"))
@ -76,19 +76,19 @@ class PreppedGlucose {
val json = JSONObject()
try {
val crjson = JSONArray()
for (crd in crData!!) {
for (crd in crData) {
crjson.put(crd.toJSON())
}
val csfjson = JSONArray()
for (bgd in csfGlucoseData!!) {
for (bgd in csfGlucoseData) {
csfjson.put(bgd.toJSON(true))
}
val isfjson = JSONArray()
for (bgd in isfGlucoseData!!) {
for (bgd in isfGlucoseData) {
isfjson.put(bgd.toJSON(false))
}
val basaljson = JSONArray()
for (bgd in basalGlucoseData!!) {
for (bgd in basalGlucoseData) {
basaljson.put(bgd.toJSON(false))
}
val diajson = JSONArray()

View file

@ -533,6 +533,7 @@
<string name="key_insulin_oref_peak" translatable="false">insulin_oref_peak</string>
<string name="insulin_oref_peak">IOB Curve Peak Time</string>
<string name="insulin_peak_time">Peak Time [min]</string>
<string name="insulin_peak">Peak</string>
<string name="free_peak_oref">Free-Peak Oref</string>
<string name="rapid_acting_oref">Rapid-Acting Oref</string>
<string name="ultrarapid_oref">Ultra-Rapid Oref</string>

View file

@ -4,7 +4,7 @@
<PreferenceCategory
android:key="@string/key_autotune_plugin"
android:title="@string/autotune_settings"
app:initialExpandedChildrenCount="0">
app:initialExpandedChildrenCount="10">
<SwitchPreference
android:defaultValue="false"
@ -18,6 +18,12 @@
android:summary="@string/autotune_categorize_uam_as_basal_summary"
android:title="@string/autotune_categorize_uam_as_basal_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_autotune_tune_insulin_curve"
android:summary="@string/autotune_tune_insulin_curve_summary"
android:title="@string/autotune_tune_insulin_curve_title" />
<EditTextPreference
android:defaultValue="5"
android:inputType="number"

View file

@ -68,6 +68,7 @@
<string name="key_insulin_oref_peak" translatable="false">insulin_oref_peak</string>
<string name="key_autotune_auto" translatable="false">autotune_auto</string>
<string name="key_autotune_categorize_uam_as_basal" translatable="false">categorize_uam_as_basal</string>
<string name="key_autotune_tune_insulin_curve" translatable="false">autotune_tune_insulin_curve</string>
<string name="key_autotune_default_tune_days" translatable="false">autotune_default_tune_days</string>
<string name="key_autotune_circadian_ic_isf" translatable="false">autotune_circadian_ic_isf</string>
<string name="key_autotune_additional_log" translatable="false">autotune_additional_log</string>
@ -554,6 +555,8 @@
<string name="autotune_auto_summary">If enabled, Autotune will automatically update and switch to input profile after calculation from an automation rule.</string>
<string name="autotune_categorize_uam_as_basal_title">Categorize UAM as basal</string>
<string name="autotune_categorize_uam_as_basal_summary">Enable only if you have reliably entered all carbs eaten, with this option sudden rises seen by Autotune will be used to recommend changes to the basal rate.</string>
<string name="autotune_tune_insulin_curve_title">Tune insulin curve</string>
<string name="autotune_tune_insulin_curve_summary">Enable only if you use free peak. This option will tune peak and DIA durations</string>
<string name="autotune_default_tune_days_title">Number of days of data</string>
<string name="autotune_circadian_ic_isf_title">Apply average result in circadian IC/ISF</string>
<string name="autotune_circadian_ic_isf_summary">Autotune will not tune circadian variations, this option only apply the average tuning of IC and ISF to your circadian input profile</string>
@ -589,8 +592,9 @@
<string name="autotune_profile_invalid">Profile invalid</string>
<string name="autotune_log_title" translatable="false">|Param|Profile|Tuned|%/Miss.\n</string>
<string name="autotune_log_separator" translatable="false">+------------------------------------------\n</string>
<string name="autotune_log_isf" translatable="false">| %1$4.4s |\t%2$3.3f |\t%3$3.3f |\n</string>
<string name="autotune_log_ic" translatable="false">| %1$4.4s |\t%2$3.3f |\t%3$3.3f |\n</string>
<string name="autotune_log_peak" translatable="false">| %1$4.4s |\t%2$d |\t%3$d |\n</string>
<string name="autotune_log_dia" translatable="false">| %1$4.4s |\t%2$3.1f |\t%3$3.1f |\n</string>
<string name="autotune_log_ic_isf" translatable="false">| %1$4.4s | %2$3.3f |\t%3$3.3f |\n</string>
<string name="autotune_log_basal" translatable="false">|\t%1$02.0f\t| %2$3.3f |%3$3.3f\t| %5$.0f%% / %4$d\n</string>
<string name="autotune_log_sum_basal" translatable="false">|\t∑\t|\t%1$3.1f |\t%2$3.1f |\n</string>
<string name="autotune_run_without_autoswitch">Autotune runned without profile switch</string>