lift out flat BG check from determinebasal to BgQualityCheckPlugin
This commit is contained in:
parent
1f1a2eae48
commit
d5af299692
21 changed files with 197 additions and 94 deletions
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package info.nightscout.interfaces.source
|
||||
|
||||
interface DexcomBoyda
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 ||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue