diff --git a/tconnectsync/parser/tconnect.py b/tconnectsync/parser/tconnect.py index c944a12..7ab4cda 100644 --- a/tconnectsync/parser/tconnect.py +++ b/tconnectsync/parser/tconnect.py @@ -85,16 +85,20 @@ class TConnectEntry: @staticmethod def parse_bolus_entry(data): # All DateTime's are stored in the user's timezone. - complete = (data["ExtendedBolusIsComplete"] or data["BolusIsComplete"]) + def is_complete(s): + return s and int(s) == 1 + + complete = is_complete(data["ExtendedBolusIsComplete"]) or is_complete(data["BolusIsComplete"]) extended_bolus = ("extended" in data["Description"].lower()) return { "description": data["Description"], "complete": "1" if complete else "", "completion": data["CompletionStatusDesc"] if not extended_bolus else data["BolexCompletionStatusDesc"], - "request_time": TConnectEntry._datetime_parse(data["RequestDateTime"]).format() if complete and not extended_bolus else None, - "completion_time": TConnectEntry._datetime_parse(data["CompletionDateTime"]).format() if complete and not extended_bolus else None, + "request_time": TConnectEntry._datetime_parse(data["RequestDateTime"]).format() if not extended_bolus else None, + "completion_time": TConnectEntry._datetime_parse(data["CompletionDateTime"]).format() if not extended_bolus else None, "insulin": data["InsulinDelivered"], + "requested_insulin": data["ActualTotalBolusRequested"], "carbs": data["CarbSize"], "user_override": data["UserOverride"], "extended_bolus": "1" if extended_bolus else "", diff --git a/tconnectsync/sync/bolus.py b/tconnectsync/sync/bolus.py index adbaf9a..075f0d7 100644 --- a/tconnectsync/sync/bolus.py +++ b/tconnectsync/sync/bolus.py @@ -20,7 +20,7 @@ def process_bolus_events(bolusdata): if parsed["completion"] != "Completed": if parsed["insulin"] and float(parsed["insulin"]) > 0: # Count non-completed bolus if any insulin was delivered (vs. the amount of insulin requested) - parsed["description"] += " (%s)" % parsed["completion"] + parsed["description"] += " (%s: requested %s units)" % (parsed["completion"], parsed["requested_insulin"]) else: logger.warning("Skipping non-completed bolus data (was a bolus in progress?): %s parsed: %s" % (b, parsed)) continue diff --git a/tests/parser/test_tconnect.py b/tests/parser/test_tconnect.py index 9de1d23..5194cf6 100644 --- a/tests/parser/test_tconnect.py +++ b/tests/parser/test_tconnect.py @@ -147,6 +147,7 @@ class TestTConnectEntryBolus(unittest.TestCase): "request_time": "2021-04-01 12:53:36-04:00", "completion_time": "2021-04-01 12:58:26-04:00", "insulin": "13.53", + "requested_insulin": "13.53", "carbs": "75", "user_override": "0", "extended_bolus": "", @@ -207,6 +208,7 @@ class TestTConnectEntryBolus(unittest.TestCase): "request_time": "2021-04-01 23:21:58-04:00", "completion_time": "2021-04-01 23:23:17-04:00", "insulin": "1.25", + "requested_insulin": "1.25", "carbs": "0", "user_override": "1", "extended_bolus": "", @@ -267,12 +269,135 @@ class TestTConnectEntryBolus(unittest.TestCase): "request_time": "2021-04-02 00:59:13-04:00", "completion_time": "2021-04-02 01:00:47-04:00", "insulin": "1.70", + "requested_insulin": "1.70", "carbs": "0", "user_override": "0", "extended_bolus": "", "bolex_completion_time": None, "bolex_start_time": None }) + + entryStdIncompleteZero = { + "Type": "Bolus", + "Description": "Standard", + "BG": "144", + "IOB": "1.20", + "BolusRequestID": "9694.000", + "BolusCompletionID": "9694.000", + "CompletionDateTime": "2021-10-08T15:47:02", + "InsulinDelivered": "0.00", + "FoodDelivered": "0.00", + "CorrectionDelivered": "0.00", + "CompletionStatusID": "0", + "CompletionStatusDesc": "User Aborted", + "BolusIsComplete": "0", + "BolexCompletionID": "", + "BolexSize": "", + "BolexStartDateTime": "", + "BolexCompletionDateTime": "", + "BolexInsulinDelivered": "", + "BolexIOB": "", + "BolexCompletionStatusID": "", + "BolexCompletionStatusDesc": "", + "ExtendedBolusIsComplete": "", + "EventDateTime": "2021-10-08T15:46:56", + "RequestDateTime": "2021-10-08T15:46:56", + "BolusType": "Carb", + "BolusRequestOptions": "Standard", + "StandardPercent": "100.00", + "Duration": "0", + "CarbSize": "0", + "UserOverride": "1", + "TargetBG": "110", + "CorrectionFactor": "30.00", + "FoodBolusSize": "0.00", + "CorrectionBolusSize": "0.00", + "ActualTotalBolusRequested": "0.50", + "IsQuickBolus": "0", + "EventHistoryReportEventDesc": "0", + "EventHistoryReportDetails": "Food Bolus", + "NoteID": "CF 1:30 - Carb Ratio 1:6 - Target BG 110 | Override: Pump calculated Bolus = 0.0 units", + "IndexID": "0", + "Note": "1669328" + } + def test_parse_bolus_entry_std_incomplete_zero(self): + self.assertEqual( + TConnectEntry.parse_bolus_entry(self.entryStdIncompleteZero), + { + "description": "Standard", + "complete": "", + "completion": "User Aborted", + "request_time": "2021-10-08 15:46:56-04:00", + "completion_time": "2021-10-08 15:47:02-04:00", + "insulin": "0.00", + "requested_insulin": "0.50", + "carbs": "0", + "user_override": "1", + "extended_bolus": "", + "bolex_completion_time": None, + "bolex_start_time": None + }) + + entryStdIncompletePartial = { + "Type": "Bolus", + "Description": "Standard/Correction", + "BG": "189", + "IOB": "", + "BolusRequestID": "9261.000", + "BolusCompletionID": "9261.000", + "CompletionDateTime": "2021-09-06T12:24:47", + "InsulinDelivered": "1.82", + "FoodDelivered": "0.00", + "CorrectionDelivered": "1.82", + "CompletionStatusID": "1", + "CompletionStatusDesc": "Terminated by Alarm", + "BolusIsComplete": "0", + "BolexCompletionID": "", + "BolexSize": "", + "BolexStartDateTime": "", + "BolexCompletionDateTime": "", + "BolexInsulinDelivered": "", + "BolexIOB": "", + "BolexCompletionStatusID": "", + "BolexCompletionStatusDesc": "", + "ExtendedBolusIsComplete": "", + "EventDateTime": "2021-09-06T12:23:23", + "RequestDateTime": "2021-09-06T12:23:23", + "BolusType": "Carb", + "BolusRequestOptions": "Standard/Correction", + "StandardPercent": "100.00", + "Duration": "0", + "CarbSize": "0", + "UserOverride": "0", + "TargetBG": "110", + "CorrectionFactor": "30.00", + "FoodBolusSize": "0.00", + "CorrectionBolusSize": "2.63", + "ActualTotalBolusRequested": "2.63", + "IsQuickBolus": "0", + "EventHistoryReportEventDesc": "0", + "EventHistoryReportDetails": "Correction & Food Bolus", + "NoteID": "CF 1:30 - Carb Ratio 1:6 - Target BG 110", + "IndexID": "0", + "Note": "1589227" + } + def test_parse_bolus_entry_std_incomplete_partial(self): + self.assertEqual( + TConnectEntry.parse_bolus_entry(self.entryStdIncompletePartial), + { + "description": "Standard/Correction", + "complete": "", + "completion": "Terminated by Alarm", + "request_time": "2021-09-06 12:23:23-04:00", + "completion_time": "2021-09-06 12:24:47-04:00", + "insulin": "1.82", + "requested_insulin": "2.63", + "carbs": "0", + "user_override": "0", + "extended_bolus": "", + "bolex_completion_time": None, + "bolex_start_time": None + }) if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/sync/test_bolus.py b/tests/sync/test_bolus.py index 1cb108a..2410ba9 100644 --- a/tests/sync/test_bolus.py +++ b/tests/sync/test_bolus.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import unittest +import random from tconnectsync.sync.bolus import process_bolus_events from tconnectsync.parser.tconnect import TConnectEntry @@ -12,23 +13,72 @@ class TestBolusSync(unittest.TestCase): @staticmethod def get_example_csv_bolus_events(): return [ + TestTConnectEntryBolus.entryStdCorrection, + TestTConnectEntryBolus.entryStd, + TestTConnectEntryBolus.entryStdAutomatic, + TestTConnectEntryBolus.entryStdIncompletePartial + ] + + def test_process_bolus_events_standard(self): + bolusData = [ TestTConnectEntryBolus.entryStdCorrection, TestTConnectEntryBolus.entryStd, TestTConnectEntryBolus.entryStdAutomatic ] - def test_process_bolus_events(self): - bolusData = TestBolusSync.get_example_csv_bolus_events() - bolusEvents = process_bolus_events(bolusData) - self.assertEqual(len(bolusEvents), 3) + self.assertEqual(len(bolusEvents), len(bolusData)) self.assertListEqual(bolusEvents, [ - TConnectEntry.parse_bolus_entry(bolusData[0]), - TConnectEntry.parse_bolus_entry(bolusData[1]), - TConnectEntry.parse_bolus_entry(bolusData[2]) + TConnectEntry.parse_bolus_entry(d) for d in bolusData ]) + def test_process_bolus_events_update_partial_description(self): + stdData = [ + TestTConnectEntryBolus.entryStdCorrection, + TestTConnectEntryBolus.entryStd, + TestTConnectEntryBolus.entryStdAutomatic + ] + partialData = [ + TestTConnectEntryBolus.entryStdIncompletePartial + ] + + bolusData = stdData + partialData + + bolusEvents = process_bolus_events(bolusData) + self.assertEqual(len(bolusEvents), len(bolusData)) + + partialEntries = [ + TConnectEntry.parse_bolus_entry(e) for e in partialData + ] + + for e in partialEntries: + e["description"] += " (%s: requested %s units)" % (e["completion"], e["requested_insulin"]) + + self.assertListEqual(bolusEvents, [ + TConnectEntry.parse_bolus_entry(d) for d in stdData + ] + partialEntries) + + def test_process_bolus_events_skip_zero(self): + stdData = [ + TestTConnectEntryBolus.entryStdCorrection, + TestTConnectEntryBolus.entryStd, + TestTConnectEntryBolus.entryStdAutomatic + ] + zeroData = [ + TestTConnectEntryBolus.entryStdIncompleteZero + ] + bolusData = stdData + zeroData + + bolusEvents = process_bolus_events(bolusData) + self.assertEqual(len(bolusEvents), len(stdData)) + + self.assertListEqual(bolusEvents, [ + TConnectEntry.parse_bolus_entry(d) for d in stdData + ]) + + for d in zeroData: + self.assertNotIn(TConnectEntry.parse_bolus_entry(d), bolusEvents) if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/tests/test_process.py b/tests/test_process.py index 78bbc11..4ca380b 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -166,10 +166,11 @@ class TestProcessTimeRange(unittest.TestCase): tconnect.controliq.therapy_timeline = self.stub_therapy_timeline + bolusData = TestBolusSync.get_example_csv_bolus_events() def fake_therapy_timeline_csv(time_start, time_end): return { **self.stub_therapy_timeline_csv(time_start, time_end), - "bolusData": TestBolusSync.get_example_csv_bolus_events(), + "bolusData": bolusData, } tconnect.ws2.therapy_timeline_csv = fake_therapy_timeline_csv @@ -182,12 +183,13 @@ class TestProcessTimeRange(unittest.TestCase): process_time_range(tconnect, nightscout, start, end, pretend=False) pprint.pprint(nightscout.uploaded_entries) - self.assertEqual(len(nightscout.uploaded_entries["treatments"]), 3) + self.assertEqual(len(nightscout.uploaded_entries["treatments"]), len(bolusData)) self.assertDictEqual(dict(nightscout.uploaded_entries), { "treatments": [ NightscoutEntry.bolus(13.53, 75, "2021-04-01 12:58:26-04:00", notes="Standard/Correction"), NightscoutEntry.bolus(1.25, 0, "2021-04-01 23:23:17-04:00", notes="Standard (Override)"), NightscoutEntry.bolus(1.7, 0, "2021-04-02 01:00:47-04:00", notes="Automatic Bolus/Correction"), + NightscoutEntry.bolus(1.82, 0, "2021-09-06 12:24:47-04:00", notes="Standard/Correction (Terminated by Alarm: requested 2.63 units)"), ]}) self.assertDictEqual(nightscout.put_entries, {}) self.assertDictEqual(nightscout.deleted_entries, {})