lift out flat BG check from determinebasal to BgQualityCheckPlugin

This commit is contained in:
Milos Kozak 2022-12-04 23:46:06 +01:00
parent 1f1a2eae48
commit d5af299692
21 changed files with 197 additions and 94 deletions

View file

@ -13,6 +13,7 @@ import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.database.impl.AppRepository
import info.nightscout.implementation.constraints.ConstraintsImpl
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.constraints.Objectives
@ -71,6 +72,7 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
@Mock lateinit var profileInstantiator: ProfileInstantiator
@Mock lateinit var danaHistoryDatabase: DanaHistoryDatabase
@Mock lateinit var insightDatabase: InsightDatabase
@Mock lateinit var bgQualityCheck: BgQualityCheck
private lateinit var hardLimits: HardLimits
private lateinit var danaPump: DanaPump
@ -182,7 +184,8 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
sp,
dateUtil,
repository,
glucoseStatusProvider
glucoseStatusProvider,
bgQualityCheck
)
openAPSSMBDynamicISFPlugin =
OpenAPSSMBDynamicISFPlugin(
@ -201,7 +204,8 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
dateUtil,
repository,
glucoseStatusProvider,
config
config,
bgQualityCheck
)
openAPSAMAPlugin =
OpenAPSAMAPlugin(

View file

@ -7,6 +7,7 @@ import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.plugin.ActivePlugin
@ -37,6 +38,7 @@ class SafetyPluginTest : TestBaseWithProfile() {
@Mock lateinit var profiler: Profiler
@Mock lateinit var repository: AppRepository
@Mock lateinit var glucoseStatusProvider: GlucoseStatusProvider
@Mock lateinit var bgQualityCheck: BgQualityCheck
private lateinit var hardLimits: HardLimits
private lateinit var safetyPlugin: SafetyPlugin
@ -80,7 +82,7 @@ class SafetyPluginTest : TestBaseWithProfile() {
)
openAPSSMBPlugin = OpenAPSSMBPlugin(
injector, aapsLogger, rxBus, constraintChecker, rh, profileFunction, context, activePlugin, iobCobCalculator, hardLimits, profiler, sp,
dateUtil, repository, glucoseStatusProvider
dateUtil, repository, glucoseStatusProvider, bgQualityCheck
)
}

View file

@ -30,7 +30,7 @@ interface DetermineBasalAdapter {
microBolusAllowed: Boolean = false,
uamAllowed: Boolean = false,
advancedFiltering: Boolean = false,
isSaveCgmSource: Boolean = false
flatBGsDetected: Boolean = false
)
operator fun invoke(): APSResult?

View file

@ -3,6 +3,15 @@ package info.nightscout.interfaces.bgQualityCheck
import androidx.annotation.DrawableRes
interface BgQualityCheck {
enum class State {
UNKNOWN,
FIVE_MIN_DATA,
RECALCULATED,
DOUBLED,
FLAT // stale data for 45 min
}
var state: State
var message: String
@DrawableRes fun icon(): Int
fun stateDescription(): String

View file

@ -0,0 +1,3 @@
package info.nightscout.interfaces.source
interface DexcomBoyda

View file

@ -110,7 +110,7 @@ function enable_smb(
return false;
}
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, isSaveCgmSource) {
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, flatBGsDetected) {
var rT = {}; //short for requestedTemp
var deliverAt = new Date();
@ -142,16 +142,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
if (minAgo > 12 || minAgo < -5) { // Dexcom data is too old, or way in the future
rT.reason = "If current system time "+systemTime+" is correct, then BG data is too old. The last BG data was read "+minAgo+"m ago at "+bgTime;
// if BG is too old/noisy, or is changing less than 1 mg/dL/5m for 45m, cancel any high temps and shorten any long zero temps
//cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc
} else if ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 && !isSaveCgmSource) {
} else if ( bg > 60 && flatBGsDetected) {
if ( glucose_status.last_cal && glucose_status.last_cal < 3 ) {
rT.reason = "CGM was just calibrated";
} else {
rT.reason = "Error: CGM data is unchanged for the past ~45m";
}
}
//cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc
if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 ) && !isSaveCgmSource ) {
if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && flatBGsDetected )) {
if (currenttemp.rate > basal) { // high temp is running
rT.reason += ". Replacing high temp basal of "+currenttemp.rate+" with neutral temp of "+basal;
rT.deliverAt = deliverAt;

View file

@ -110,7 +110,7 @@ function enable_smb(
return false;
}
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, isSaveCgmSource) {
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, flatBGsDetected) {
var rT = {}; //short for requestedTemp
var deliverAt = new Date();
@ -142,16 +142,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
if (minAgo > 12 || minAgo < -5) { // Dexcom data is too old, or way in the future
rT.reason = "If current system time "+systemTime+" is correct, then BG data is too old. The last BG data was read "+minAgo+"m ago at "+bgTime;
// if BG is too old/noisy, or is changing less than 1 mg/dL/5m for 45m, cancel any high temps and shorten any long zero temps
//cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc
} else if ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 && !isSaveCgmSource) {
} else if ( bg > 60 && flatBGsDetected) {
if ( glucose_status.last_cal && glucose_status.last_cal < 3 ) {
rT.reason = "CGM was just calibrated";
} else {
rT.reason = "Error: CGM data is unchanged for the past ~45m";
}
}
//cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc
if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 ) && !isSaveCgmSource ) {
if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && flatBGsDetected )) {
if (currenttemp.rate > basal) { // high temp is running
rT.reason += ". Replacing high temp basal of "+currenttemp.rate+" with neutral temp of "+basal;
rT.deliverAt = deliverAt;

View file

@ -162,7 +162,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
flatBGsDetected: Boolean
) {
this.profile = JSONObject()
this.profile.put("max_iob", maxIob)

View file

@ -57,7 +57,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
private var microBolusAllowed = false
private var smbAlwaysAllowed = false
private var currentTime: Long = 0
private var saveCgmSource = false
private var flatBGsDetected = false
override var currentTempParam: String? = null
override var iobDataParam: String? = null
@ -79,7 +79,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
aapsLogger.debug(LTag.APS, "MicroBolusAllowed: $microBolusAllowed")
aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: $smbAlwaysAllowed")
aapsLogger.debug(LTag.APS, "CurrentTime: $currentTime")
aapsLogger.debug(LTag.APS, "isSaveCgmSource: $saveCgmSource")
aapsLogger.debug(LTag.APS, "flatBGsDetected: $flatBGsDetected")
var determineBasalResultSMB: DetermineBasalResultSMB? = null
val rhino = Context.enter()
val scope: Scriptable = rhino.initStandardObjects()
@ -119,7 +119,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
java.lang.Boolean.valueOf(microBolusAllowed),
makeParam(null, rhino, scope), // reservoir data as undefined
java.lang.Long.valueOf(currentTime),
java.lang.Boolean.valueOf(saveCgmSource)
java.lang.Boolean.valueOf(flatBGsDetected)
)
val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject
scriptDebug = LoggerCallback.scriptDebug
@ -174,7 +174,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
flatBGsDetected: Boolean
) {
val pump = activePlugin.activePump
val pumpBolusStep = pump.pumpDescription.bolusStep
@ -262,7 +262,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
this.microBolusAllowed = microBolusAllowed
smbAlwaysAllowed = advancedFiltering
currentTime = now
saveCgmSource = isSaveCgmSource
this.flatBGsDetected = flatBGsDetected
}
private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any {

View file

@ -14,6 +14,7 @@ import info.nightscout.interfaces.aps.APS
import info.nightscout.interfaces.aps.AutosensResult
import info.nightscout.interfaces.aps.DetermineBasalAdapter
import info.nightscout.interfaces.aps.SMBDefaults
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.IobCobCalculator
@ -55,7 +56,8 @@ class OpenAPSSMBPlugin @Inject constructor(
private val sp: SP,
private val dateUtil: DateUtil,
private val repository: AppRepository,
private val glucoseStatusProvider: GlucoseStatusProvider
private val glucoseStatusProvider: GlucoseStatusProvider,
private val bgQualityCheck: BgQualityCheck
) : PluginBase(
PluginDescription()
.mainType(PluginType.APS)
@ -191,6 +193,7 @@ class OpenAPSSMBPlugin @Inject constructor(
constraintChecker.isUAMEnabled(it)
inputConstraints.copyReasons(it)
}
val flatBGsDetected = bgQualityCheck.state == BgQualityCheck.State.FLAT
profiler.log(LTag.APS, "detectSensitivityAndCarbAbsorption()", startPart)
profiler.log(LTag.APS, "SMB data gathering", start)
start = System.currentTimeMillis()
@ -207,7 +210,7 @@ class OpenAPSSMBPlugin @Inject constructor(
smbAllowed.value(),
uam.value(),
advancedFiltering.value(),
activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin"
flatBGsDetected
)
val now = System.currentTimeMillis()
val determineBasalResultSMB = determineBasalAdapterSMBJS.invoke()

View file

@ -59,7 +59,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
private var microBolusAllowed = false
private var smbAlwaysAllowed = false
private var currentTime: Long = 0
private var saveCgmSource = false
private var flatBGsDetected = false
override var currentTempParam: String? = null
override var iobDataParam: String? = null
@ -81,7 +81,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
aapsLogger.debug(LTag.APS, "MicroBolusAllowed: $microBolusAllowed")
aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: $smbAlwaysAllowed")
aapsLogger.debug(LTag.APS, "CurrentTime: $currentTime")
aapsLogger.debug(LTag.APS, "isSaveCgmSource: $saveCgmSource")
aapsLogger.debug(LTag.APS, "flatBGsDetected: $flatBGsDetected")
var determineBasalResultSMB: DetermineBasalResultSMB? = null
val rhino = Context.enter()
val scope: Scriptable = rhino.initStandardObjects()
@ -121,7 +121,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
java.lang.Boolean.valueOf(microBolusAllowed),
makeParam(null, rhino, scope), // reservoir data as undefined
java.lang.Long.valueOf(currentTime),
java.lang.Boolean.valueOf(saveCgmSource)
java.lang.Boolean.valueOf(flatBGsDetected)
)
val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject
scriptDebug = LoggerCallback.scriptDebug
@ -176,7 +176,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
flatBGsDetected: Boolean
) {
val pump = activePlugin.activePump
val pumpBolusStep = pump.pumpDescription.bolusStep
@ -312,7 +312,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
this.microBolusAllowed = microBolusAllowed
smbAlwaysAllowed = advancedFiltering
currentTime = now
saveCgmSource = isSaveCgmSource
this.flatBGsDetected = flatBGsDetected
}
private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any {

View file

@ -7,6 +7,7 @@ import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.aps.DetermineBasalAdapter
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
@ -42,7 +43,8 @@ class OpenAPSSMBDynamicISFPlugin @Inject constructor(
dateUtil: DateUtil,
repository: AppRepository,
glucoseStatusProvider: GlucoseStatusProvider,
private val config: Config
private val config: Config,
private val bgQualityCheck: BgQualityCheck
) : OpenAPSSMBPlugin(
injector,
aapsLogger,
@ -58,7 +60,8 @@ class OpenAPSSMBDynamicISFPlugin @Inject constructor(
sp,
dateUtil,
repository,
glucoseStatusProvider
glucoseStatusProvider,
bgQualityCheck
) {
init {

View file

@ -41,17 +41,11 @@ class AidexPlugin @Inject constructor(
aapsLogger, rh, injector
), BgSource {
private var advancedFiltering = false
/**
* Aidex App doesn't have upload to NS
*/
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = true
override fun advancedFilteringSupported(): Boolean {
return advancedFiltering
}
// Allow only for pumpcontrol or dev & engineering_mode
override fun specialEnableCondition(): Boolean {
return config.APS.not() || config.isDev() && config.isEngineeringMode()

View file

@ -27,6 +27,7 @@ import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.source.BgSource
import info.nightscout.interfaces.source.DexcomBoyda
import info.nightscout.plugins.R
import info.nightscout.plugins.source.activities.RequestDexcomPermissionActivity
import info.nightscout.rx.logging.AAPSLogger
@ -58,7 +59,7 @@ class DexcomPlugin @Inject constructor(
.preferencesId(R.xml.pref_dexcom)
.description(R.string.description_source_dexcom),
aapsLogger, rh, injector
), BgSource {
), BgSource, DexcomBoyda {
init {
if (!config.NSCLIENT) {
@ -66,9 +67,7 @@ class DexcomPlugin @Inject constructor(
}
}
override fun advancedFilteringSupported(): Boolean {
return true
}
override fun advancedFilteringSupported(): Boolean = true
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean =
(glucoseValue.sourceSensor == GlucoseValue.SourceSensor.DEXCOM_G6_NATIVE ||

View file

@ -64,9 +64,7 @@ class NSClientSourcePlugin @Inject constructor(
}
}
override fun advancedFilteringSupported(): Boolean {
return isAdvancedFilteringEnabled
}
override fun advancedFilteringSupported(): Boolean = isAdvancedFilteringEnabled
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = false

View file

@ -73,9 +73,7 @@ class RandomBgPlugin @Inject constructor(
private val disposable = CompositeDisposable()
override fun advancedFilteringSupported(): Boolean {
return true
}
override fun advancedFilteringSupported(): Boolean = true
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean =
glucoseValue.sourceSensor == GlucoseValue.SourceSensor.RANDOM && sp.getBoolean(info.nightscout.core.utils.R.string.key_do_ns_upload, false)

View file

@ -43,9 +43,7 @@ class XdripPlugin @Inject constructor(
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = false
override fun advancedFilteringSupported(): Boolean {
return advancedFiltering
}
override fun advancedFilteringSupported(): Boolean = advancedFiltering
private fun detectSource(glucoseValue: GlucoseValue) {
advancedFiltering = arrayOf(

View file

@ -7,9 +7,11 @@ import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.source.DexcomBoyda
import info.nightscout.plugins.support.R
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
@ -36,7 +38,8 @@ class BgQualityCheckPlugin @Inject constructor(
private val aapsSchedulers:
AapsSchedulers,
private val fabricPrivacy: FabricPrivacy,
private val dateUtil: DateUtil
private val dateUtil: DateUtil,
private val activePlugin: ActivePlugin
) : PluginBase(
PluginDescription()
.mainType(PluginType.CONSTRAINTS)
@ -49,13 +52,6 @@ class BgQualityCheckPlugin @Inject constructor(
private var disposable: CompositeDisposable = CompositeDisposable()
enum class State {
UNKNOWN,
FIVE_MIN_DATA,
RECALCULATED,
DOUBLED
}
override fun onStart() {
super.onStart()
disposable += rxBus
@ -69,51 +65,83 @@ class BgQualityCheckPlugin @Inject constructor(
disposable.clear()
}
var state: State = State.UNKNOWN
override var state: BgQualityCheck.State = BgQualityCheck.State.UNKNOWN
override var message: String = ""
// Fallback to LGS if BG values are doubled
override fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> =
if (state == State.DOUBLED)
if (state == BgQualityCheck.State.DOUBLED)
maxIob.set(aapsLogger, 0.0, "Doubled values in BGSource", this)
else
maxIob
@Suppress("CascadeIf") fun processBgData() {
fun processBgData() {
val readings = iobCobCalculator.ads.getBgReadingsDataTableCopy()
for (i in readings.indices)
// Deltas are calculated from last ~50 min. Detect RED state only on this interval
if (i < min(readings.size - 2, 10))
if (abs(readings[i].timestamp - readings[i + 1].timestamp) <= T.secs(20).msecs()) {
state = State.DOUBLED
state = BgQualityCheck.State.DOUBLED
aapsLogger.debug(LTag.CORE, "BG similar. Turning on red state.\n${readings[i]}\n${readings[i + 1]}")
message = rh.gs(R.string.bg_too_close, dateUtil.dateAndTimeAndSecondsString(readings[i].timestamp), dateUtil.dateAndTimeAndSecondsString(readings[i + 1].timestamp))
return
}
if (iobCobCalculator.ads.lastUsed5minCalculation == true) {
state = State.FIVE_MIN_DATA
if (activePlugin.activeBgSource !is DexcomBoyda && isBgFlatForInterval(staleBgCheckPeriodMinutes, staleBgMaxDeltaMgdl) == true) {
state = BgQualityCheck.State.FLAT
message = rh.gs(R.string.a11y_bg_quality_flat)
} else if (iobCobCalculator.ads.lastUsed5minCalculation == true) {
state = BgQualityCheck.State.FIVE_MIN_DATA
message = "Data is clean"
} else if (iobCobCalculator.ads.lastUsed5minCalculation == false) {
state = State.RECALCULATED
state = BgQualityCheck.State.RECALCULATED
message = rh.gs(R.string.recalculated_data_used)
} else {
state = State.UNKNOWN
state = BgQualityCheck.State.UNKNOWN
message = ""
}
}
// inspired by @justmara
@Suppress("SpellCheckingInspection", "SameParameterValue")
private fun isBgFlatForInterval(minutes: Long, maxDelta: Double): Boolean? {
val data = iobCobCalculator.ads.getBgReadingsDataTableCopy()
val lastBg = iobCobCalculator.ads.lastBg()?.value
val now = dateUtil.now()
val offset = now - T.mins(minutes).msecs()
val sizeRecords = data.size
lastBg ?: return null
if (sizeRecords < 5) return null // not enough data
if (data[data.size - 1].timestamp > now - 45 * 60 * 1000L) return null // data too fresh to detect
if (data[0].timestamp < now - 7 * 60 * 1000L) return null // data is old
for (bg in data) {
if (bg.timestamp < offset) break
if (abs(lastBg - bg.value) > maxDelta) return false
}
return true
}
@DrawableRes override fun icon(): Int =
when (state) {
State.UNKNOWN -> 0
State.FIVE_MIN_DATA -> 0
State.RECALCULATED -> R.drawable.ic_baseline_warning_24_yellow
State.DOUBLED -> R.drawable.ic_baseline_warning_24_red
BgQualityCheck.State.UNKNOWN -> 0
BgQualityCheck.State.FIVE_MIN_DATA -> 0
BgQualityCheck.State.RECALCULATED -> R.drawable.ic_baseline_warning_24_yellow
BgQualityCheck.State.DOUBLED -> R.drawable.ic_baseline_warning_24_red
BgQualityCheck.State.FLAT -> R.drawable.ic_baseline_trending_flat_24
}
override fun stateDescription(): String =
when (state) {
State.RECALCULATED -> rh.gs(R.string.a11y_bg_quality_recalculated)
State.DOUBLED -> rh.gs(R.string.a11y_bg_quality_doubles)
else -> ""
BgQualityCheck.State.RECALCULATED -> rh.gs(R.string.a11y_bg_quality_recalculated)
BgQualityCheck.State.DOUBLED -> rh.gs(R.string.a11y_bg_quality_doubles)
BgQualityCheck.State.FLAT -> rh.gs(R.string.a11y_bg_quality_flat)
else -> ""
}
companion object {
const val staleBgCheckPeriodMinutes = 45L
const val staleBgMaxDeltaMgdl = 1.0
}
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FF0000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M22,12l-4,-4v3H3v2h15v3z"/>
</vector>

View file

@ -39,5 +39,6 @@
<string name="bg_too_close">BG too close:\n%1$s\n%2$s</string>
<string name="a11y_bg_quality_recalculated">recalculated</string>
<string name="a11y_bg_quality_doubles">double entries</string>
<string name="a11y_bg_quality_flat">Flat data. Considered to be wrong</string>
</resources>

View file

@ -6,14 +6,18 @@ import info.nightscout.androidaps.TestBase
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.aps.AutosensDataStore
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.source.BgSource
import info.nightscout.interfaces.source.DexcomBoyda
import info.nightscout.plugins.support.R
import info.nightscout.rx.bus.RxBus
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import org.junit.Assert
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.any
@ -28,10 +32,12 @@ class BgQualityCheckPluginTest : TestBase() {
@Mock lateinit var fabricPrivacy: FabricPrivacy
@Mock lateinit var dateUtil: DateUtil
@Mock lateinit var autosensDataStore: AutosensDataStore
@Mock lateinit var activePlugin: ActivePlugin
private lateinit var plugin: BgQualityCheckPlugin
val injector = HasAndroidInjector { AndroidInjector { } }
private val injector = HasAndroidInjector { AndroidInjector { } }
val now = 100000000L
//private val autosensDataStore = AutosensDataStoreObject()
@BeforeEach
@ -45,27 +51,29 @@ class BgQualityCheckPluginTest : TestBase() {
iobCobCalculator,
aapsSchedulers,
fabricPrivacy,
dateUtil
dateUtil,
activePlugin
)
`when`(iobCobCalculator.ads).thenReturn(autosensDataStore)
`when`(rh.gs(anyInt())).thenReturn("")
`when`(rh.gs(anyInt(), any(), any())).thenReturn("")
`when`(dateUtil.now()).thenReturn(now)
}
@Test
fun runTest() {
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(null)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.UNKNOWN, plugin.state)
Assert.assertEquals(0, plugin.icon())
Assertions.assertEquals(BgQualityCheck.State.UNKNOWN, plugin.state)
Assertions.assertEquals(0, plugin.icon())
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(true)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.FIVE_MIN_DATA, plugin.state)
Assert.assertEquals(0, plugin.icon())
Assertions.assertEquals(BgQualityCheck.State.FIVE_MIN_DATA, plugin.state)
Assertions.assertEquals(0, plugin.icon())
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(false)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.RECALCULATED, plugin.state)
Assert.assertEquals(R.drawable.ic_baseline_warning_24_yellow, plugin.icon())
Assertions.assertEquals(BgQualityCheck.State.RECALCULATED, plugin.state)
Assertions.assertEquals(R.drawable.ic_baseline_warning_24_yellow, plugin.icon())
val superData: MutableList<GlucoseValue> = ArrayList()
superData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
@ -76,10 +84,10 @@ class BgQualityCheckPluginTest : TestBase() {
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(true)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.FIVE_MIN_DATA, plugin.state)
Assertions.assertEquals(BgQualityCheck.State.FIVE_MIN_DATA, plugin.state)
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(false)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.RECALCULATED, plugin.state)
Assertions.assertEquals(BgQualityCheck.State.RECALCULATED, plugin.state)
val duplicatedData: MutableList<GlucoseValue> = ArrayList()
duplicatedData.add(
@ -136,8 +144,8 @@ class BgQualityCheckPluginTest : TestBase() {
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(true)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.DOUBLED, plugin.state)
Assert.assertEquals(R.drawable.ic_baseline_warning_24_red, plugin.icon())
Assertions.assertEquals(BgQualityCheck.State.DOUBLED, plugin.state)
Assertions.assertEquals(R.drawable.ic_baseline_warning_24_red, plugin.icon())
val identicalData: MutableList<GlucoseValue> = ArrayList()
identicalData.add(
@ -194,19 +202,73 @@ class BgQualityCheckPluginTest : TestBase() {
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(false)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.DOUBLED, plugin.state)
Assertions.assertEquals(BgQualityCheck.State.DOUBLED, plugin.state)
// Flat data
val flatData: MutableList<GlucoseValue> = ArrayList()
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(0).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow
.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 101.0, timestamp = now + T.mins(-10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-25).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 99.0, timestamp = now + T.mins(-30).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-35).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-40).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-45).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(flatData)
`when`(iobCobCalculator.ads.lastBg()).thenReturn(flatData[0])
// Test non-dexcom plugin on flat data
class OtherPlugin : BgSource {
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = true
}
`when`(activePlugin.activeBgSource).thenReturn(OtherPlugin())
plugin.processBgData()
Assertions.assertEquals(BgQualityCheck.State.FLAT, plugin.state)
Assertions.assertEquals(R.drawable.ic_baseline_trending_flat_24, plugin.icon())
// Test dexcom plugin on flat data
class DexcomPlugin : BgSource, DexcomBoyda {
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = true
}
`when`(activePlugin.activeBgSource).thenReturn(DexcomPlugin())
plugin.processBgData()
Assertions.assertNotEquals(BgQualityCheck.State.FLAT, plugin.state)
// not enough data
val incompleteData: MutableList<GlucoseValue> = ArrayList()
incompleteData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(0).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
incompleteData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(incompleteData)
`when`(iobCobCalculator.ads.lastBg()).thenReturn(incompleteData[0])
`when`(activePlugin.activeBgSource).thenReturn(OtherPlugin())
plugin.processBgData()// must be more than 5 values
Assertions.assertNotEquals(BgQualityCheck.State.FLAT, plugin.state)
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 101.0, timestamp = now + T.mins(-10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-25).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 99.0, timestamp = now + T.mins(-30).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-35).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-40).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
plugin.processBgData() // must be at least 45 min old
Assertions.assertNotEquals(BgQualityCheck.State.FLAT, plugin.state)
}
@Test
fun applyMaxIOBConstraintsTest() {
plugin.state = BgQualityCheckPlugin.State.UNKNOWN
Assert.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheckPlugin.State.FIVE_MIN_DATA
Assert.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheckPlugin.State.RECALCULATED
Assert.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheckPlugin.State.DOUBLED
Assert.assertEquals(0.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheck.State.UNKNOWN
Assertions.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheck.State.FIVE_MIN_DATA
Assertions.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheck.State.RECALCULATED
Assertions.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheck.State.DOUBLED
Assertions.assertEquals(0.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
}
}