mirror of
https://github.com/bckelley/tconnectsync.git
synced 2026-08-24 03:34:12 -05:00
sync/basal: process manual suspension events as manual 0u/hr basal, fixes #17
This commit is contained in:
@@ -40,6 +40,16 @@ class TConnectEntry:
|
||||
"duration_mins": duration_mins,
|
||||
"basal_rate": basal_rate,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def manual_suspension_to_basal_entry(parsedSuspension, seconds):
|
||||
duration_mins = seconds / 60
|
||||
return {
|
||||
"time": parsedSuspension["time"],
|
||||
"delivery_type": "%s suspension" % parsedSuspension["suspendReason"],
|
||||
"duration_mins": duration_mins,
|
||||
"basal_rate": 0.0
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def parse_suspension_entry(data):
|
||||
|
||||
@@ -32,11 +32,53 @@ def process_ciq_basal_events(data):
|
||||
for b in data["basal"]["profileDeliveryEvents"]:
|
||||
basalEvents.append(TConnectEntry.parse_ciq_basal_entry(b, delivery_type="profileDelivery"))
|
||||
|
||||
basalEvents.sort(key=lambda x: arrow.get(x["time"]))
|
||||
|
||||
# Suspensions with suspendReason 'control-iq' will match a basal event found above.
|
||||
for i in basalEvents:
|
||||
if i["time"] in suspensionEvents:
|
||||
i["suspendReason"] = suspensionEvents[i["time"]]["suspendReason"]
|
||||
i["delivery_type"] += " (" + suspensionEvents[i["time"]]["suspendReason"] + " suspension)"
|
||||
|
||||
del suspensionEvents[i["time"]]
|
||||
|
||||
# Suspensions with suspendReason 'manual' do not have an associated basal event,
|
||||
# and require extra processing.
|
||||
|
||||
basalEvents.sort(key=lambda x: arrow.get(x["time"]))
|
||||
|
||||
unprocessedSuspensions = list(suspensionEvents.values())
|
||||
unprocessedSuspensions.sort(key=lambda x: arrow.get(x["time"]))
|
||||
|
||||
# For the remaining suspensions which did not match with an existing basal event,
|
||||
# add a new event manually. This means we need to calculate the duration of the
|
||||
# suspension.
|
||||
newEvents = []
|
||||
for i in range(len(basalEvents)):
|
||||
if len(unprocessedSuspensions) == 0:
|
||||
break
|
||||
|
||||
existingTime = arrow.get(basalEvents[i]["time"])
|
||||
unprocessedTime = arrow.get(unprocessedSuspensions[0]["time"])
|
||||
|
||||
# If we've found an event which occurs after the suspension, then the
|
||||
# difference in their timestamps is the duration of the suspension.
|
||||
if i > 0 and existingTime > unprocessedTime:
|
||||
suspension = unprocessedSuspensions.pop(0)
|
||||
|
||||
# TConnect's internal duration object tracks the duration in seconds
|
||||
seconds = (existingTime - unprocessedTime).seconds
|
||||
|
||||
newEvent = TConnectEntry.manual_suspension_to_basal_entry(suspension, seconds)
|
||||
logger.debug("Creating basal event for unprocessed suspension: %s" % newEvent)
|
||||
newEvents.append(newEvent)
|
||||
|
||||
# Any remaining suspensions which have not been processed have not ended,
|
||||
# which means we do not know their duration; so we will skip them (for now)
|
||||
|
||||
# Add any new events and re-sort
|
||||
if newEvents:
|
||||
basalEvents += newEvents
|
||||
basalEvents.sort(key=lambda x: arrow.get(x["time"]))
|
||||
|
||||
|
||||
return basalEvents
|
||||
|
||||
|
||||
@@ -60,6 +60,26 @@ class TestTConnectEntrySuspension(unittest.TestCase):
|
||||
}
|
||||
)
|
||||
|
||||
class TestTConnectEntrySuspensionToBasal(unittest.TestCase):
|
||||
def test_manual_suspension_to_basal_entry(self):
|
||||
suspension = {
|
||||
"time": "2021-03-16 00:30:21-04:00",
|
||||
"continuation": None,
|
||||
"suspendReason": "manual"
|
||||
}
|
||||
|
||||
self.assertEqual(
|
||||
TConnectEntry.manual_suspension_to_basal_entry(
|
||||
suspension,
|
||||
seconds=300
|
||||
), {
|
||||
"time": "2021-03-16 00:30:21-04:00",
|
||||
"delivery_type": "manual suspension",
|
||||
"duration_mins": 5.0,
|
||||
"basal_rate": 0.0
|
||||
}
|
||||
)
|
||||
|
||||
class TestTConnectEntryCGM(unittest.TestCase):
|
||||
def test_parse_cgm_entry(self):
|
||||
self.assertEqual(
|
||||
|
||||
+68
-10
@@ -5,6 +5,8 @@ from tconnectsync.sync.basal import process_ciq_basal_events
|
||||
from tconnectsync.parser.tconnect import TConnectEntry
|
||||
|
||||
class TestBasalSync(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
base = {
|
||||
"basal": {
|
||||
"profileRates": [],
|
||||
@@ -25,26 +27,26 @@ class TestBasalSync(unittest.TestCase):
|
||||
{
|
||||
"y": 0.8,
|
||||
"duration": 1221,
|
||||
"x": 1615878000
|
||||
"x": 1615878000 # 12:00:00
|
||||
}
|
||||
]
|
||||
data["basal"]["algorithmDeliveryEvents"] = [
|
||||
{
|
||||
"y": 0.797,
|
||||
"duration": 300,
|
||||
"x": 1615879521
|
||||
"x": 1615879521 # 12:25:21
|
||||
},
|
||||
{
|
||||
"y": 0,
|
||||
"duration": 2693,
|
||||
"x": 1615879821
|
||||
"x": 1615879821 # 12:30:21
|
||||
},
|
||||
]
|
||||
data["basal"]["profileDeliveryEvents"] = [
|
||||
{
|
||||
"y": 0.799,
|
||||
"duration": 300,
|
||||
"x": 1615879221
|
||||
"x": 1615879221 # 12:20:21
|
||||
}
|
||||
]
|
||||
|
||||
@@ -52,7 +54,7 @@ class TestBasalSync(unittest.TestCase):
|
||||
{
|
||||
"suspendReason": "control-iq",
|
||||
"continuation": None,
|
||||
"x": 1615879821
|
||||
"x": 1615879821 # 12:30:21
|
||||
},
|
||||
]
|
||||
|
||||
@@ -73,12 +75,68 @@ class TestBasalSync(unittest.TestCase):
|
||||
self.assertEqual(basalEvents[2], TConnectEntry.parse_ciq_basal_entry(
|
||||
data["basal"]["algorithmDeliveryEvents"][0], delivery_type="algorithmDelivery"))
|
||||
|
||||
self.assertEqual(basalEvents[3], {
|
||||
"suspendReason": "control-iq",
|
||||
**TConnectEntry.parse_ciq_basal_entry(
|
||||
self.assertEqual(basalEvents[3], TConnectEntry.parse_ciq_basal_entry(
|
||||
data["basal"]["algorithmDeliveryEvents"][1],
|
||||
delivery_type="algorithmDelivery")
|
||||
})
|
||||
delivery_type="algorithmDelivery (control-iq suspension)")
|
||||
)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_example_ciq_basal_events_with_manual_suspension():
|
||||
data = TestBasalSync.base.copy()
|
||||
data["basal"]["tempDeliveryEvents"] = []
|
||||
data["basal"]["algorithmDeliveryEvents"] = [
|
||||
{
|
||||
"y": 1.14,
|
||||
"duration": 300,
|
||||
"x": 1635187357 # 11:42:37
|
||||
},
|
||||
{
|
||||
"y": 0.8,
|
||||
"duration": 1198,
|
||||
"x": 1635187657 # 11:47:37
|
||||
},
|
||||
{
|
||||
"y": 0.8,
|
||||
"duration": 599,
|
||||
"x": 1635190967 # 12:42:47
|
||||
}
|
||||
]
|
||||
data["basal"]["profileDeliveryEvents"] = []
|
||||
data["suspensionDeliveryEvents"] = [
|
||||
{
|
||||
"suspendReason": "manual",
|
||||
"continuation": None,
|
||||
"x": 1635188855 # 12:07:35
|
||||
},
|
||||
{
|
||||
"suspendReason": "manual",
|
||||
"continuation": None,
|
||||
"x": 1635191566 # 12:52:46
|
||||
},
|
||||
]
|
||||
|
||||
return data
|
||||
|
||||
def test_process_ciq_basal_events_with_manual_suspension(self):
|
||||
data = TestBasalSync.get_example_ciq_basal_events_with_manual_suspension()
|
||||
|
||||
basalEvents = process_ciq_basal_events(data)
|
||||
self.assertEqual(len(basalEvents), 4)
|
||||
|
||||
self.assertEqual(basalEvents[0], TConnectEntry.parse_ciq_basal_entry(
|
||||
data["basal"]["algorithmDeliveryEvents"][0], delivery_type="algorithmDelivery"))
|
||||
|
||||
self.assertEqual(basalEvents[1], TConnectEntry.parse_ciq_basal_entry(
|
||||
data["basal"]["algorithmDeliveryEvents"][1], delivery_type="algorithmDelivery"))
|
||||
|
||||
self.assertEqual(basalEvents[2], TConnectEntry.manual_suspension_to_basal_entry(
|
||||
TConnectEntry.parse_suspension_entry(data["suspensionDeliveryEvents"][0]),
|
||||
seconds=2112, # 2112 seconds between 12:07:35 and 12:42:47
|
||||
))
|
||||
|
||||
self.assertEqual(basalEvents[3], TConnectEntry.parse_ciq_basal_entry(
|
||||
data["basal"]["algorithmDeliveryEvents"][2], delivery_type="algorithmDelivery"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -64,7 +64,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
NightscoutEntry.basal(0.8, 20.35, "2021-03-16 00:00:00-04:00", reason="tempDelivery"),
|
||||
NightscoutEntry.basal(0.799, 5.0, "2021-03-16 00:20:21-04:00", reason="profileDelivery"),
|
||||
NightscoutEntry.basal(0.797, 5.0, "2021-03-16 00:25:21-04:00", reason="algorithmDelivery"),
|
||||
NightscoutEntry.basal(0, 2693/60, "2021-03-16 00:30:21-04:00", reason="algorithmDelivery")
|
||||
NightscoutEntry.basal(0, 2693/60, "2021-03-16 00:30:21-04:00", reason="algorithmDelivery (control-iq suspension)")
|
||||
]})
|
||||
self.assertDictEqual(nightscout.put_entries, {})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
@@ -132,7 +132,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
self.assertDictEqual(dict(nightscout.uploaded_entries), {
|
||||
"treatments": [
|
||||
NightscoutEntry.basal(0.797, 5.0, "2021-03-16 00:25:21-04:00", reason="algorithmDelivery"),
|
||||
NightscoutEntry.basal(0, 2693/60, "2021-03-16 00:30:21-04:00", reason="algorithmDelivery")
|
||||
NightscoutEntry.basal(0, 2693/60, "2021-03-16 00:30:21-04:00", reason="algorithmDelivery (control-iq suspension)")
|
||||
]})
|
||||
self.assertDictEqual(nightscout.put_entries, {})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
@@ -176,7 +176,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
self.assertDictEqual(nightscout.uploaded_entries, {
|
||||
"treatments": [
|
||||
NightscoutEntry.basal(0.797, 5.0, "2021-03-16 00:25:21-04:00", reason="algorithmDelivery"),
|
||||
NightscoutEntry.basal(0, 2693/60, "2021-03-16 00:30:21-04:00", reason="algorithmDelivery")
|
||||
NightscoutEntry.basal(0, 2693/60, "2021-03-16 00:30:21-04:00", reason="algorithmDelivery (control-iq suspension)")
|
||||
]})
|
||||
self.assertEqual(len(nightscout.put_entries["treatments"]), 1)
|
||||
self.assertDictEqual(dict(nightscout.put_entries), {
|
||||
|
||||
Reference in New Issue
Block a user