Merge pull request #1851 from Philoul/Autotune/UnitTests

Add Unit test for AutotunePrep
This commit is contained in:
Milos Kozak 2022-06-25 15:56:53 +02:00 committed by GitHub
commit 5ea5e80393
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 546 additions and 12 deletions

View file

@ -29,7 +29,7 @@ import kotlin.math.ceil
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Singleton @Singleton
class AutotuneIob @Inject constructor( open class AutotuneIob @Inject constructor(
private val aapsLogger: AAPSLogger, private val aapsLogger: AAPSLogger,
private val repository: AppRepository, private val repository: AppRepository,
private val profileFunction: ProfileFunction, private val profileFunction: ProfileFunction,
@ -41,10 +41,10 @@ class AutotuneIob @Inject constructor(
private val nsTreatments = ArrayList<NsTreatment>() private val nsTreatments = ArrayList<NsTreatment>()
private var dia: Double = Constants.defaultDIA private var dia: Double = Constants.defaultDIA
var boluses: MutableList<Bolus> = ArrayList() var boluses: ArrayList<Bolus> = ArrayList()
var meals = ArrayList<Carbs>() var meals = ArrayList<Carbs>()
lateinit var glucose: List<GlucoseValue> // newest at index 0 lateinit var glucose: List<GlucoseValue> // newest at index 0
private lateinit var tempBasals: MutableList<TemporaryBasal> private lateinit var tempBasals: ArrayList<TemporaryBasal>
var startBG: Long = 0 var startBG: Long = 0
var endBG: Long = 0 var endBG: Long = 0
private fun range(): Long = (60 * 60 * 1000L * dia + T.hours(2).msecs()).toLong() private fun range(): Long = (60 * 60 * 1000L * dia + T.hours(2).msecs()).toLong()
@ -225,7 +225,7 @@ class AutotuneIob @Inject constructor(
} }
} }
fun getIOB(time: Long, localInsulin: LocalInsulin): IobTotal { open fun getIOB(time: Long, localInsulin: LocalInsulin): IobTotal {
val bolusIob = getCalculationToTimeTreatments(time, localInsulin).round() val bolusIob = getCalculationToTimeTreatments(time, localInsulin).round()
return bolusIob return bolusIob
} }

View file

@ -302,7 +302,7 @@ class AutotunePrep @Inject constructor(
// Then, calculate carb absorption for that 5m interval using the deviation. // Then, calculate carb absorption for that 5m interval using the deviation.
if (mealCOB > 0) { if (mealCOB > 0) {
val ci = Math.max(deviation, sp.getDouble("openapsama_min_5m_carbimpact", 3.0)) val ci = Math.max(deviation, sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0))
val absorbed = ci * tunedprofile.ic / sens val absorbed = ci * tunedprofile.ic / sens
// Store the COB, and use it as the starting point for the next data point. // Store the COB, and use it as the starting point for the next data point.
mealCOB = Math.max(0.0, mealCOB - absorbed) mealCOB = Math.max(0.0, mealCOB - absorbed)

View file

@ -30,9 +30,6 @@ class AutotuneCoreTest : TestBaseWithProfile() {
@Mock lateinit var injector: HasAndroidInjector @Mock lateinit var injector: HasAndroidInjector
@Mock lateinit var activePlugin: ActivePlugin @Mock lateinit var activePlugin: ActivePlugin
lateinit var autotuneCore: AutotuneCore lateinit var autotuneCore: AutotuneCore
lateinit var prep: PreppedGlucose
lateinit var prepjson: String
lateinit var inputProfile: ATProfile
var min5mCarbImpact = 0.0 var min5mCarbImpact = 0.0
var autotuneMin = 0.0 var autotuneMin = 0.0
var autotuneMax = 0.0 var autotuneMax = 0.0
@ -41,14 +38,15 @@ class AutotuneCoreTest : TestBaseWithProfile() {
fun initData() { fun initData() {
autotuneCore = AutotuneCore(sp,autotuneFS) autotuneCore = AutotuneCore(sp,autotuneFS)
TimeZone.setDefault(TimeZone.getTimeZone("GMT+2")) TimeZone.setDefault(TimeZone.getTimeZone("GMT+2"))
prepjson = File("src/test/res/autotune/test1/autotune.2022-05-21.json").readText()
val inputProfileJson = File("src/test/res/autotune/test1/profile.pump.json").readText()
inputProfile = atProfileFromOapsJson(JSONObject(inputProfileJson), dateUtil)!!
prep = PreppedGlucose(JSONObject(prepjson), dateUtil)
} }
@Test @Test
fun autotuneCoreTest() { // Test if load from file of OpenAPS categorisation is Ok fun autotuneCoreTest() { // Test if load from file of OpenAPS categorisation is Ok
val prepjson = File("src/test/res/autotune/test1/autotune.2022-05-21.json").readText()
val inputProfileJson = File("src/test/res/autotune/test1/profile.pump.json").readText()
val inputProfile = atProfileFromOapsJson(JSONObject(inputProfileJson), dateUtil)!!
val prep = PreppedGlucose(JSONObject(prepjson), dateUtil)
`when`(sp.getDouble(R.string.key_openapsama_autosens_max, 1.2)).thenReturn(autotuneMax) `when`(sp.getDouble(R.string.key_openapsama_autosens_max, 1.2)).thenReturn(autotuneMax)
`when`(sp.getDouble(R.string.key_openapsama_autosens_min, 0.7)).thenReturn(autotuneMin) `when`(sp.getDouble(R.string.key_openapsama_autosens_min, 0.7)).thenReturn(autotuneMin)
`when`(sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0)).thenReturn(min5mCarbImpact) `when`(sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0)).thenReturn(min5mCarbImpact)

View file

@ -0,0 +1,250 @@
package info.nightscout.androidaps.plugins.general.autotune
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.data.PureProfile
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.data.Block
import info.nightscout.androidaps.database.data.TargetBlock
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.Carbs
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.plugins.general.autotune.data.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.T
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.`when`
import java.io.File
import java.util.*
import kotlin.collections.ArrayList
class AutotunePrepTest : TestBaseWithProfile() {
@Mock lateinit var sp: SP
@Mock lateinit var autotuneFS: AutotuneFS
@Mock lateinit var injector: HasAndroidInjector
@Mock lateinit var activePlugin: ActivePlugin
@Mock lateinit var repository: AppRepository
lateinit var autotunePrep: AutotunePrep
lateinit var autotuneIob: TestAutotuneIob
lateinit var inputProfile: ATProfile
var min5mCarbImpact = 0.0
var autotuneMin = 0.0
var autotuneMax = 0.0
var startDayTime = 0L
@Before
fun initData() {
TimeZone.setDefault(TimeZone.getTimeZone("GMT+2"))
val inputProfileJson = File("src/test/res/autotune/test1/profile.pump.json").readText()
inputProfile = atProfileFromOapsJson(JSONObject(inputProfileJson), dateUtil)!!
val inputIobJson = File("src/test/res/autotune/test1/oaps-iobCalc.2022-05-21.json").readText() //json files build with iob/activity calculated by OAPS
val iobOapsCalcul = buildIobOaps(JSONArray(inputIobJson))
autotuneIob = TestAutotuneIob(aapsLogger, repository, profileFunction, sp, dateUtil, activePlugin, autotuneFS, iobOapsCalcul)
autotunePrep = AutotunePrep(sp, dateUtil, autotuneFS, autotuneIob)
}
@Test
fun autotunePrepTest() { // Test if load from file of OpenAPS categorisation is Ok
val prepjson = File("src/test/res/autotune/test1/autotune.2022-05-21.json").readText()
val oapsPreppedGlucose = PreppedGlucose(JSONObject(prepjson), dateUtil) //prep data calculated by OpenAPS autotune
val oapsEntriesJson = File("src/test/res/autotune/test1/aaps-entries.2022-05-21.json").readText()
autotuneIob.glucose = buildGlucose(JSONArray(oapsEntriesJson))
val oapsTreatmentsJson = File("src/test/res/autotune/test1/aaps-treatments.2022-05-21.json").readText()
autotuneIob.meals = buildMeals(JSONArray(oapsTreatmentsJson)) //Only meals is used in unit test, Insulin only used for iob calculation
autotuneIob.boluses = buildBoluses(oapsPreppedGlucose) //Values from oapsPrepData because linked to iob calculation method for TBR
`when`(sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0)).thenReturn(min5mCarbImpact)
val aapsPreppedGlucose = autotunePrep.categorizeBGDatums(inputProfile, inputProfile.localInsulin, false)
try {
aapsPreppedGlucose?.let { // compare all categorization calculated by aaps plugin (aapsPreppedGlucose) with categorization calculated by OpenAPS (oapsPreppedGlucose)
for (i in aapsPreppedGlucose.crData.indices)
Assert.assertTrue(oapsPreppedGlucose.crData[i].equals(aapsPreppedGlucose.crData[i]))
for (i in aapsPreppedGlucose.csfGlucoseData.indices)
Assert.assertTrue(oapsPreppedGlucose.csfGlucoseData[i].equals(aapsPreppedGlucose.csfGlucoseData[i]))
for (i in aapsPreppedGlucose.isfGlucoseData.indices)
Assert.assertTrue(oapsPreppedGlucose.isfGlucoseData[i].equals(aapsPreppedGlucose.isfGlucoseData[i]))
for (i in aapsPreppedGlucose.basalGlucoseData.indices)
Assert.assertTrue(oapsPreppedGlucose.basalGlucoseData[i].equals(aapsPreppedGlucose.basalGlucoseData[i]))
}
?: Assert.fail()
} catch (e: Exception) {
Assert.fail()
}
}
/**
* OpenAPS profile for Autotune only have one ISF value and one IC value
*/
fun atProfileFromOapsJson(jsonObject: JSONObject, dateUtil: DateUtil, defaultUnits: String? = null): ATProfile? {
try {
min5mCarbImpact = JsonHelper.safeGetDoubleAllowNull(jsonObject, "min_5m_carbimpact") ?: return null
autotuneMin = JsonHelper.safeGetDoubleAllowNull(jsonObject, "autosens_min") ?: return null
autotuneMax = JsonHelper.safeGetDoubleAllowNull(jsonObject, "autosens_max") ?: return null
val txtUnits = JsonHelper.safeGetStringAllowNull(jsonObject, "units", defaultUnits) ?: return null
val units = GlucoseUnit.fromText(txtUnits)
val dia = JsonHelper.safeGetDoubleAllowNull(jsonObject, "dia") ?: return null
val peak = JsonHelper.safeGetIntAllowNull(jsonObject, "insulinPeakTime") ?: return null
val localInsulin = LocalInsulin("insulin", peak, dia)
val timezone = TimeZone.getTimeZone(JsonHelper.safeGetString(jsonObject, "timezone", "UTC"))
val isfJson = jsonObject.getJSONObject("isfProfile")
val isfBlocks = ArrayList<Block>(1).also {
val isfJsonArray = isfJson.getJSONArray("sensitivities")
val value = isfJsonArray.getJSONObject(0).getDouble("sensitivity")
it.add(0,Block((T.hours(24).secs()) * 1000L, value))
}
val icBlocks = ArrayList<Block>(1).also {
val value = jsonObject.getDouble("carb_ratio")
it.add(0,Block((T.hours(24).secs()) * 1000L, value))
}
val basalBlocks = blockFromJsonArray(jsonObject.getJSONArray("basalprofile"), dateUtil)
?: return null
val targetBlocks = ArrayList<TargetBlock>(1).also {
it.add(0, TargetBlock((T.hours(24).secs()) * 1000L, 100.0, 100.0))
}
val pure = PureProfile(
jsonObject = jsonObject,
basalBlocks = basalBlocks,
isfBlocks = isfBlocks,
icBlocks = icBlocks,
targetBlocks = targetBlocks,
glucoseUnit = units,
timeZone = timezone,
dia = dia
)
return ATProfile(ProfileSealed.Pure(pure), localInsulin, profileInjector).also { it.dateUtil = dateUtil}
} catch (ignored: Exception) {
return null
}
}
fun blockFromJsonArray(jsonArray: JSONArray?, dateUtil: DateUtil): List<Block>? {
val size = jsonArray?.length() ?: return null
val ret = ArrayList<Block>(size)
try {
for (index in 0 until jsonArray.length() - 1) {
val o = jsonArray.getJSONObject(index)
val tas = o.getInt("minutes") * 60
val next = jsonArray.getJSONObject(index + 1)
val nextTas = next.getInt("minutes") * 60
val value = o.getDouble("rate")
if (tas % 3600 != 0) return null
if (nextTas % 3600 != 0) return null
ret.add(index, Block((nextTas - tas) * 1000L, value))
}
val last: JSONObject = jsonArray.getJSONObject(jsonArray.length() - 1)
val lastTas = last.getInt("minutes") * 60
val value = last.getDouble("rate")
ret.add(jsonArray.length() - 1, Block((T.hours(24).secs() - lastTas) * 1000L, value))
} catch (e: Exception) {
return null
}
return ret
}
fun buildBoluses(preppedGlucose: PreppedGlucose): ArrayList<Bolus> { //if categorization is correct then I return for dose function the crInsulin calculated in Oaps
val boluses: ArrayList<Bolus> = ArrayList()
try {
for (i in preppedGlucose.crData.indices) {
boluses.add(
Bolus(
timestamp = preppedGlucose.crData[i].crEndTime,
amount = preppedGlucose.crData[i].crInsulin,
type = Bolus.Type.NORMAL
)
)
}
} catch (e: Exception) { }
return boluses
}
fun buildMeals(jsonArray: JSONArray): ArrayList<Carbs> {
val list: ArrayList<Carbs> = ArrayList()
try {
for (index in 0 until jsonArray.length()) {
val json = jsonArray.getJSONObject(index)
val value = JsonHelper.safeGetDouble(json, "carbs", 0.0)
val timestamp = JsonHelper.safeGetLong(json, "date")
if (value > 0.0 && timestamp > startDayTime) {
list.add(Carbs(timestamp=timestamp, amount = value, duration = 0))
}
}
} catch (e: Exception) { }
return list
}
fun buildGlucose(jsonArray: JSONArray): List<GlucoseValue> {
val list: ArrayList<GlucoseValue> = ArrayList()
try {
for (index in 0 until jsonArray.length()) {
val json = jsonArray.getJSONObject(index)
val value = JsonHelper.safeGetDouble(json, "sgv")
val timestamp = JsonHelper.safeGetLong(json, "date")
list.add(GlucoseValue(raw = value, noise = 0.0, value = value, timestamp = timestamp, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
}
} catch (e: Exception) { }
if (list.size > 0)
startDayTime = list[list.size-1].timestamp
return list
}
fun buildIobOaps(jsonArray: JSONArray): ArrayList<IobTotal> { //if categorization is correct then I return for dose function the crInsulin calculated in Oaps
val list: ArrayList<IobTotal> = ArrayList()
for (index in 0 until jsonArray.length()) {
val json = jsonArray.getJSONObject(index)
val time = JsonHelper.safeGetLong(json,"date")
val iob = JsonHelper.safeGetDouble(json, "iob")
val activity = JsonHelper.safeGetDouble(json, "activity")
val iobTotal = IobTotal(time)
iobTotal.iob = iob
iobTotal.activity = activity
list.add(iobTotal)
}
return list
}
class TestAutotuneIob(
val aapsLogger: AAPSLogger,
repository: AppRepository,
val profileFunction: ProfileFunction,
val sp: SP,
val dateUtil: DateUtil,
val activePlugin: ActivePlugin,
autotuneFS: AutotuneFS,
val iobOapsCalcul: ArrayList<IobTotal>
) : AutotuneIob(
aapsLogger,
repository,
profileFunction,
sp,
dateUtil,
activePlugin,
autotuneFS
) {
override fun getIOB(time: Long, localInsulin: LocalInsulin): IobTotal {
var bolusIob = IobTotal(time)
iobOapsCalcul.forEach {
if (it.time == time)
return it
}
return bolusIob
}
}
}

Binary file not shown.

View file

@ -0,0 +1,286 @@
[
{ "iob": 5.249, "activity": 0.0391, "date": 1653099848000},
{ "iob": 5.292, "activity": 0.0438, "date": 1653100148000},
{ "iob": 4.964, "activity": 0.047, "date": 1653100449000},
{ "iob": 4.674, "activity": 0.0489, "date": 1653100748000},
{ "iob": 4.377, "activity": 0.0498, "date": 1653101048000},
{ "iob": 4.077, "activity": 0.0498, "date": 1653101348000},
{ "iob": 3.73, "activity": 0.0491, "date": 1653101648000},
{ "iob": 3.389, "activity": 0.0476, "date": 1653101948000},
{ "iob": 3.055, "activity": 0.0457, "date": 1653102249000},
{ "iob": 2.782, "activity": 0.0436, "date": 1653102548000},
{ "iob": 2.47, "activity": 0.0411, "date": 1653102848000},
{ "iob": 2.22, "activity": 0.0386, "date": 1653103149000},
{ "iob": 1.935, "activity": 0.0358, "date": 1653103448000},
{ "iob": 1.711, "activity": 0.0332, "date": 1653103748000},
{ "iob": 1.452, "activity": 0.0304, "date": 1653104049000},
{ "iob": 1.927, "activity": 0.028, "date": 1653104349000},
{ "iob": 1.84, "activity": 0.027, "date": 1653104648000},
{ "iob": 1.658, "activity": 0.0258, "date": 1653104949000},
{ "iob": 1.482, "activity": 0.0245, "date": 1653105248000},
{ "iob": 1.264, "activity": 0.0229, "date": 1653105549000},
{ "iob": 1.104, "activity": 0.0212, "date": 1653105848000},
{ "iob": 0.952, "activity": 0.0194, "date": 1653106149000},
{ "iob": 1.619, "activity": 0.0181, "date": 1653106449000},
{ "iob": 1.678, "activity": 0.0184, "date": 1653106749000},
{ "iob": 1.775, "activity": 0.0187, "date": 1653107048000},
{ "iob": 1.581, "activity": 0.019, "date": 1653107348000},
{ "iob": 1.437, "activity": 0.0188, "date": 1653107648000},
{ "iob": 1.294, "activity": 0.0183, "date": 1653107948000},
{ "iob": 1.153, "activity": 0.0176, "date": 1653108249000},
{ "iob": 1.017, "activity": 0.0167, "date": 1653108548000},
{ "iob": 0.887, "activity": 0.0156, "date": 1653108848000},
{ "iob": 1.011, "activity": 0.0147, "date": 1653109148000},
{ "iob": 0.889, "activity": 0.0141, "date": 1653109448000},
{ "iob": 0.771, "activity": 0.0133, "date": 1653109748000},
{ "iob": 0.656, "activity": 0.0124, "date": 1653110049000},
{ "iob": 0.547, "activity": 0.0114, "date": 1653110348000},
{ "iob": 0.392, "activity": 0.0103, "date": 1653110649000},
{ "iob": 0.294, "activity": 0.0091, "date": 1653110949000},
{ "iob": 0.2, "activity": 0.008, "date": 1653111249000},
{ "iob": 0.065, "activity": 0.0066, "date": 1653111549000},
{ "iob": 0.034, "activity": 0.0055, "date": 1653111849000},
{ "iob": 0.549, "activity": 0.0048, "date": 1653112148000},
{ "iob": 0.674, "activity": 0.0053, "date": 1653112449000},
{ "iob": 0.597, "activity": 0.0057, "date": 1653112749000},
{ "iob": 0.468, "activity": 0.0058, "date": 1653113049000},
{ "iob": 0.389, "activity": 0.0057, "date": 1653113349000},
{ "iob": 0.262, "activity": 0.0053, "date": 1653113649000},
{ "iob": 0.186, "activity": 0.0048, "date": 1653113949000},
{ "iob": 0.064, "activity": 0.0041, "date": 1653114249000},
{ "iob": -0.005, "activity": 0.0034, "date": 1653114549000},
{ "iob": -0.12, "activity": 0.0026, "date": 1653114849000},
{ "iob": -0.181, "activity": 0.0018, "date": 1653115149000},
{ "iob": -0.287, "activity": 0.0008, "date": 1653115449000},
{ "iob": -0.34, "activity": 0, "date": 1653115749000},
{ "iob": -0.436, "activity": -0.001, "date": 1653116049000},
{ "iob": -0.48, "activity": -0.0018, "date": 1653116349000},
{ "iob": -0.519, "activity": -0.0026, "date": 1653116649000},
{ "iob": -0.554, "activity": -0.0034, "date": 1653116949000},
{ "iob": -0.585, "activity": -0.0041, "date": 1653117249000},
{ "iob": -0.662, "activity": -0.0049, "date": 1653117549000},
{ "iob": -0.687, "activity": -0.0055, "date": 1653117848000},
{ "iob": -0.708, "activity": -0.0061, "date": 1653118149000},
{ "iob": -0.726, "activity": -0.0066, "date": 1653118449000},
{ "iob": -0.351, "activity": -0.0071, "date": 1653118748000},
{ "iob": -0.218, "activity": -0.0064, "date": 1653119049000},
{ "iob": -0.088, "activity": -0.0055, "date": 1653119349000},
{ "iob": 13.594, "activity": 0.0023, "date": 1653119649000},
{ "iob": 13.552, "activity": 0.0331, "date": 1653119949000},
{ "iob": 13.224, "activity": 0.0573, "date": 1653120249000},
{ "iob": 12.838, "activity": 0.076, "date": 1653120548000},
{ "iob": 12.322, "activity": 0.09, "date": 1653120849000},
{ "iob": 11.794, "activity": 0.1001, "date": 1653121149000},
{ "iob": 11.177, "activity": 0.1068, "date": 1653121449000},
{ "iob": 10.58, "activity": 0.111, "date": 1653121749000},
{ "iob": 9.919, "activity": 0.1129, "date": 1653122049000},
{ "iob": 9.254, "activity": 0.1129, "date": 1653122349000},
{ "iob": 8.643, "activity": 0.1114, "date": 1653122649000},
{ "iob": 7.992, "activity": 0.1088, "date": 1653122949000},
{ "iob": 7.406, "activity": 0.1053, "date": 1653123248000},
{ "iob": 7.111, "activity": 0.1012, "date": 1653123549000},
{ "iob": 6.515, "activity": 0.0971, "date": 1653123849000},
{ "iob": 5.99, "activity": 0.0927, "date": 1653124149000},
{ "iob": 5.439, "activity": 0.0878, "date": 1653124448000},
{ "iob": 4.962, "activity": 0.0828, "date": 1653124749000},
{ "iob": 4.462, "activity": 0.0775, "date": 1653125048000},
{ "iob": 4.036, "activity": 0.0724, "date": 1653125349000},
{ "iob": 3.638, "activity": 0.0672, "date": 1653125649000},
{ "iob": 3.214, "activity": 0.0621, "date": 1653125948000},
{ "iob": 2.866, "activity": 0.0571, "date": 1653126249000},
{ "iob": 2.494, "activity": 0.0522, "date": 1653126549000},
{ "iob": 2.193, "activity": 0.0476, "date": 1653126849000},
{ "iob": 1.867, "activity": 0.0431, "date": 1653127149000},
{ "iob": 1.563, "activity": 0.0388, "date": 1653127449000},
{ "iob": 1.329, "activity": 0.0347, "date": 1653127748000},
{ "iob": 1.115, "activity": 0.0309, "date": 1653128049000},
{ "iob": 0.87, "activity": 0.0272, "date": 1653128349000},
{ "iob": 0.692, "activity": 0.0239, "date": 1653128649000},
{ "iob": 0.482, "activity": 0.0206, "date": 1653128948000},
{ "iob": 0.287, "activity": 0.0176, "date": 1653129249000},
{ "iob": 0.156, "activity": 0.0148, "date": 1653129548000},
{ "iob": 0.038, "activity": 0.0122, "date": 1653129848000},
{ "iob": -0.067, "activity": 0.0099, "date": 1653130149000},
{ "iob": -0.161, "activity": 0.0078, "date": 1653130448000},
{ "iob": -0.195, "activity": 0.006, "date": 1653130748000},
{ "iob": -0.271, "activity": 0.0044, "date": 1653131049000},
{ "iob": -0.339, "activity": 0.0029, "date": 1653131349000},
{ "iob": -0.4, "activity": 0.0015, "date": 1653131649000},
{ "iob": -0.155, "activity": 0.0007, "date": 1653131948000},
{ "iob": 8.11, "activity": 0.0045, "date": 1653132249000},
{ "iob": 7.992, "activity": 0.0222, "date": 1653132549000},
{ "iob": 7.745, "activity": 0.0359, "date": 1653132849000},
{ "iob": 7.488, "activity": 0.0465, "date": 1653133149000},
{ "iob": 7.136, "activity": 0.0541, "date": 1653133449000},
{ "iob": 6.8, "activity": 0.0596, "date": 1653133749000},
{ "iob": 6.441, "activity": 0.0633, "date": 1653134049000},
{ "iob": 6.022, "activity": 0.0651, "date": 1653134349000},
{ "iob": 5.644, "activity": 0.0657, "date": 1653134649000},
{ "iob": 5.216, "activity": 0.0653, "date": 1653134949000},
{ "iob": 4.962, "activity": 0.0644, "date": 1653135249000},
{ "iob": 4.544, "activity": 0.0627, "date": 1653135549000},
{ "iob": 4.185, "activity": 0.0606, "date": 1653135849000},
{ "iob": 3.839, "activity": 0.058, "date": 1653136149000},
{ "iob": 3.456, "activity": 0.055, "date": 1653136449000},
{ "iob": 3.139, "activity": 0.0519, "date": 1653136749000},
{ "iob": 2.788, "activity": 0.0486, "date": 1653137049000},
{ "iob": 2.503, "activity": 0.0453, "date": 1653137350000},
{ "iob": 2.186, "activity": 0.0418, "date": 1653137649000},
{ "iob": 3.17, "activity": 0.0407, "date": 1653137949000},
{ "iob": 3.956, "activity": 0.0413, "date": 1653138249000},
{ "iob": 3.648, "activity": 0.042, "date": 1653138549000},
{ "iob": 4.126, "activity": 0.043, "date": 1653138849000},
{ "iob": 3.81, "activity": 0.0437, "date": 1653139150000},
{ "iob": 3.541, "activity": 0.0437, "date": 1653139450000},
{ "iob": 3.272, "activity": 0.0431, "date": 1653139749000},
{ "iob": 3.588, "activity": 0.0428, "date": 1653140049000},
{ "iob": 3.875, "activity": 0.0431, "date": 1653140349000},
{ "iob": 3.608, "activity": 0.0434, "date": 1653140648000},
{ "iob": 4.271, "activity": 0.0442, "date": 1653140949000},
{ "iob": 3.996, "activity": 0.0452, "date": 1653141249000},
{ "iob": 4.298, "activity": 0.0462, "date": 1653141548000},
{ "iob": 4.015, "activity": 0.0468, "date": 1653141849000},
{ "iob": 4.538, "activity": 0.0481, "date": 1653142149000},
{ "iob": 4.245, "activity": 0.0489, "date": 1653142449000},
{ "iob": 4.729, "activity": 0.05, "date": 1653142749000},
{ "iob": 4.426, "activity": 0.0508, "date": 1653143049000},
{ "iob": 4.353, "activity": 0.051, "date": 1653143349000},
{ "iob": 4.049, "activity": 0.0507, "date": 1653143650000},
{ "iob": 3.747, "activity": 0.0498, "date": 1653143949000},
{ "iob": 3.402, "activity": 0.0483, "date": 1653144249000},
{ "iob": 3.115, "activity": 0.0464, "date": 1653144550000},
{ "iob": 2.788, "activity": 0.0441, "date": 1653144849000},
{ "iob": 2.524, "activity": 0.0417, "date": 1653145149000},
{ "iob": 2.271, "activity": 0.0391, "date": 1653145449000},
{ "iob": 2.033, "activity": 0.0365, "date": 1653145750000},
{ "iob": 1.807, "activity": 0.0338, "date": 1653146050000},
{ "iob": 1.594, "activity": 0.0312, "date": 1653146349000},
{ "iob": 1.395, "activity": 0.0286, "date": 1653146650000},
{ "iob": 1.258, "activity": 0.0262, "date": 1653146949000},
{ "iob": 1.034, "activity": 0.0236, "date": 1653147249000},
{ "iob": 0.971, "activity": 0.0215, "date": 1653147549000},
{ "iob": 1.008, "activity": 0.0197, "date": 1653147849000},
{ "iob": 0.963, "activity": 0.0183, "date": 1653148149000},
{ "iob": 1.046, "activity": 0.0171, "date": 1653148450000},
{ "iob": 0.912, "activity": 0.0161, "date": 1653148749000},
{ "iob": 1.254, "activity": 0.0156, "date": 1653149049000},
{ "iob": 1.176, "activity": 0.0154, "date": 1653149349000},
{ "iob": 1.051, "activity": 0.0149, "date": 1653149650000},
{ "iob": 0.928, "activity": 0.0142, "date": 1653149949000},
{ "iob": 1.008, "activity": 0.0138, "date": 1653150249000},
{ "iob": 0.89, "activity": 0.0133, "date": 1653150549000},
{ "iob": 0.775, "activity": 0.0127, "date": 1653150849000},
{ "iob": 0.664, "activity": 0.0118, "date": 1653151149000},
{ "iob": 0.606, "activity": 0.011, "date": 1653151449000},
{ "iob": 0.703, "activity": 0.0105, "date": 1653151749000},
{ "iob": 0.702, "activity": 0.0101, "date": 1653152049000},
{ "iob": 0.902, "activity": 0.0101, "date": 1653152349000},
{ "iob": 0.801, "activity": 0.0101, "date": 1653152649000},
{ "iob": 0.701, "activity": 0.0098, "date": 1653152949000},
{ "iob": 0.603, "activity": 0.0094, "date": 1653153249000},
{ "iob": 0.508, "activity": 0.0088, "date": 1653153549000},
{ "iob": 0.415, "activity": 0.0081, "date": 1653153849000},
{ "iob": 0.327, "activity": 0.0074, "date": 1653154149000},
{ "iob": 0.242, "activity": 0.0066, "date": 1653154449000},
{ "iob": 0.161, "activity": 0.0058, "date": 1653154749000},
{ "iob": 0.134, "activity": 0.005, "date": 1653155049000},
{ "iob": 0.4, "activity": 0.0048, "date": 1653155349000},
{ "iob": 0.475, "activity": 0.0049, "date": 1653155649000},
{ "iob": 0.401, "activity": 0.005, "date": 1653155950000},
{ "iob": 0.327, "activity": 0.0048, "date": 1653156250000},
{ "iob": 0.203, "activity": 0.0044, "date": 1653156549000},
{ "iob": 0.132, "activity": 0.004, "date": 1653156849000},
{ "iob": 0.064, "activity": 0.0034, "date": 1653157149000},
{ "iob": -0.002, "activity": 0.0028, "date": 1653157449000},
{ "iob": -0.064, "activity": 0.0022, "date": 1653157750000},
{ "iob": -0.073, "activity": 0.0016, "date": 1653158049000},
{ "iob": -0.08, "activity": 0.0011, "date": 1653158349000},
{ "iob": -0.085, "activity": 0.0007, "date": 1653158649000},
{ "iob": -0.087, "activity": 0.0004, "date": 1653158949000},
{ "iob": -0.088, "activity": 0.0001, "date": 1653159249000},
{ "iob": -0.088, "activity": -0.0001, "date": 1653159549000},
{ "iob": -0.137, "activity": -0.0004, "date": 1653159849000},
{ "iob": -0.184, "activity": -0.0007, "date": 1653160150000},
{ "iob": -0.18, "activity": -0.001, "date": 1653160449000},
{ "iob": -0.175, "activity": -0.0012, "date": 1653160749000},
{ "iob": -0.168, "activity": -0.0014, "date": 1653161049000},
{ "iob": -0.211, "activity": -0.0015, "date": 1653161350000},
{ "iob": -0.203, "activity": -0.0017, "date": 1653161649000},
{ "iob": -0.243, "activity": -0.002, "date": 1653161950000},
{ "iob": -0.282, "activity": -0.0023, "date": 1653162250000},
{ "iob": -0.32, "activity": -0.0026, "date": 1653162549000},
{ "iob": -0.357, "activity": -0.0029, "date": 1653162850000},
{ "iob": -0.391, "activity": -0.0032, "date": 1653163150000},
{ "iob": -0.375, "activity": -0.0035, "date": 1653163449000},
{ "iob": -0.357, "activity": -0.0036, "date": 1653163749000},
{ "iob": -0.149, "activity": -0.0035, "date": 1653164049000},
{ "iob": -0.132, "activity": -0.0031, "date": 1653164350000},
{ "iob": 9.296, "activity": 0.015, "date": 1653164649000},
{ "iob": 9.123, "activity": 0.0332, "date": 1653164950000},
{ "iob": 8.96, "activity": 0.0474, "date": 1653165249000},
{ "iob": 8.644, "activity": 0.0584, "date": 1653165550000},
{ "iob": 8.282, "activity": 0.0664, "date": 1653165850000},
{ "iob": 7.835, "activity": 0.0718, "date": 1653166149000},
{ "iob": 7.416, "activity": 0.0753, "date": 1653166450000},
{ "iob": 6.984, "activity": 0.0772, "date": 1653166749000},
{ "iob": 6.547, "activity": 0.0776, "date": 1653167049000},
{ "iob": 6.061, "activity": 0.0769, "date": 1653167350000},
{ "iob": 5.68, "activity": 0.0754, "date": 1653167650000},
{ "iob": 5.458, "activity": 0.0734, "date": 1653167949000},
{ "iob": 5.046, "activity": 0.0712, "date": 1653168250000},
{ "iob": 4.925, "activity": 0.069, "date": 1653168549000},
{ "iob": 4.487, "activity": 0.0663, "date": 1653168849000},
{ "iob": 4.113, "activity": 0.0633, "date": 1653169150000},
{ "iob": 3.706, "activity": 0.0599, "date": 1653169449000},
{ "iob": 3.363, "activity": 0.0566, "date": 1653169749000},
{ "iob": 3.039, "activity": 0.053, "date": 1653170049000},
{ "iob": 2.733, "activity": 0.0494, "date": 1653170350000},
{ "iob": 2.395, "activity": 0.0457, "date": 1653170650000},
{ "iob": 2.126, "activity": 0.0421, "date": 1653170950000},
{ "iob": 1.824, "activity": 0.0385, "date": 1653171250000},
{ "iob": 1.64, "activity": 0.0351, "date": 1653171549000},
{ "iob": 1.473, "activity": 0.0319, "date": 1653171849000},
{ "iob": 1.273, "activity": 0.0288, "date": 1653172150000},
{ "iob": 1.136, "activity": 0.026, "date": 1653172450000},
{ "iob": 1.011, "activity": 0.0235, "date": 1653172749000},
{ "iob": 0.852, "activity": 0.021, "date": 1653173050000},
{ "iob": 0.752, "activity": 0.0189, "date": 1653173350000},
{ "iob": 0.662, "activity": 0.0169, "date": 1653173650000},
{ "iob": 0.534, "activity": 0.0149, "date": 1653173949000},
{ "iob": 0.463, "activity": 0.0132, "date": 1653174249000},
{ "iob": 0.352, "activity": 0.0115, "date": 1653174550000},
{ "iob": 0.298, "activity": 0.0101, "date": 1653174850000},
{ "iob": 0.25, "activity": 0.0089, "date": 1653175149000},
{ "iob": 0.329, "activity": 0.0079, "date": 1653175450000},
{ "iob": 0.34, "activity": 0.0074, "date": 1653175750000},
{ "iob": 0.304, "activity": 0.0068, "date": 1653176050000},
{ "iob": 0.272, "activity": 0.0062, "date": 1653176350000},
{ "iob": 0.512, "activity": 0.006, "date": 1653176650000},
{ "iob": 0.581, "activity": 0.0061, "date": 1653176950000},
{ "iob": 1.238, "activity": 0.0075, "date": 1653177250000},
{ "iob": 1.147, "activity": 0.0088, "date": 1653177550000},
{ "iob": 1.151, "activity": 0.0098, "date": 1653177850000},
{ "iob": 1.05, "activity": 0.0104, "date": 1653178149000},
{ "iob": 0.947, "activity": 0.0107, "date": 1653178449000},
{ "iob": 0.844, "activity": 0.0106, "date": 1653178750000},
{ "iob": 0.741, "activity": 0.0104, "date": 1653179050000},
{ "iob": 0.59, "activity": 0.0099, "date": 1653179350000},
{ "iob": 0.542, "activity": 0.0093, "date": 1653179649000},
{ "iob": 0.497, "activity": 0.0087, "date": 1653179949000},
{ "iob": 0.456, "activity": 0.0081, "date": 1653180250000},
{ "iob": 0.516, "activity": 0.0077, "date": 1653180549000},
{ "iob": 0.429, "activity": 0.0072, "date": 1653180849000},
{ "iob": 0.294, "activity": 0.0066, "date": 1653181150000},
{ "iob": 0.163, "activity": 0.0058, "date": 1653181450000},
{ "iob": 0.037, "activity": 0.0049, "date": 1653181750000},
{ "iob": -0.085, "activity": 0.0039, "date": 1653182050000},
{ "iob": -0.153, "activity": 0.0029, "date": 1653182349000},
{ "iob": -0.265, "activity": 0.0018, "date": 1653182650000},
{ "iob": -0.371, "activity": 0.0007, "date": 1653182949000},
{ "iob": -0.471, "activity": -0.0005, "date": 1653183249000},
{ "iob": -0.566, "activity": -0.0016, "date": 1653183550000},
{ "iob": -0.606, "activity": -0.0026, "date": 1653183850000},
{ "iob": -0.64, "activity": -0.0036, "date": 1653184150000},
{ "iob": -0.67, "activity": -0.0044, "date": 1653184450000}
]