mirror of
https://github.com/bckelley/tconnectsync.git
synced 2026-08-24 03:34:12 -05:00
internally track bolus from CIQ API identically at par with WS2
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -16,3 +16,10 @@ class Bolus:
|
||||
extended_bolus: str # "1" / "0"
|
||||
bolex_completion_time: str
|
||||
bolex_start_time: str
|
||||
|
||||
def to_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
@property
|
||||
def is_extended_bolus(self):
|
||||
return self.extended_bolus == "1"
|
||||
@@ -6,7 +6,9 @@ from ..secret import TIMEZONE_NAME
|
||||
|
||||
|
||||
def _datetime_parse(date):
|
||||
return arrow.get(date, tzinfo=TIMEZONE_NAME).format()
|
||||
# consistent format with ws2 endpoint
|
||||
return arrow.get(date, tzinfo=TIMEZONE_NAME).format("YYYY-MM-DD HH:mm:ssZZ")
|
||||
|
||||
class TherapyEvent:
|
||||
type = None
|
||||
eventDateTime = None
|
||||
@@ -96,7 +98,7 @@ class BolusTherapyEvent(TherapyEvent):
|
||||
self.bg = json.get("bg")
|
||||
self.user_override = json.get("userOverride")
|
||||
self.extended_bolus = json.get("bolusRequestOptions") == "Extended"
|
||||
if self.extended_bolus:
|
||||
if self.extended_bolus and self.complete:
|
||||
# TODO(https://github.com/jwoglom/tconnectsync/issues/19): read more extended bolus info
|
||||
self.complete = json.get("bolex", {}).get("extendedBolusIsComplete")
|
||||
self.completion = json.get("bolex", {}).get("completionStatusDesc")
|
||||
@@ -116,7 +118,7 @@ class BolusTherapyEvent(TherapyEvent):
|
||||
completion_time=_datetime_parse(self.completion_time),
|
||||
insulin=str(self.insulin),
|
||||
requested_insulin=str(self.requested_insulin),
|
||||
carbs=str(self.carbs or ""),
|
||||
carbs=str(self.carbs or "0"), # Nightscout expects non-empty carbs
|
||||
bg=str(self.bg or ""),
|
||||
user_override=str(self.user_override),
|
||||
extended_bolus="1" if self.extended_bolus else "0",
|
||||
|
||||
@@ -48,7 +48,7 @@ class NightscoutEntry:
|
||||
}
|
||||
if bg:
|
||||
if bg_type not in (NightscoutEntry.SENSOR, NightscoutEntry.FINGER):
|
||||
raise InvalidBolusTypeException
|
||||
raise InvalidBolusTypeException("bg_type: %s (%s)" % (bg_type, data))
|
||||
|
||||
data.update({
|
||||
"glucose": str(bg),
|
||||
|
||||
@@ -131,10 +131,10 @@ def process_time_range(tconnect, nightscout, time_start, time_end, pretend, feat
|
||||
if BOLUS in features:
|
||||
bolusEvents = []
|
||||
if ciqBolusData:
|
||||
bolusEvents = process_bolus_therapy_events(ciqBolusData)
|
||||
bolusEvents = process_bolus_events(ciqBolusData, source="ciq")
|
||||
|
||||
if csvBolusData and not bolusEvents:
|
||||
bolusEvents = process_bolus_events(csvBolusData)
|
||||
bolusEvents = process_bolus_events(csvBolusData, source="csv")
|
||||
added += ns_write_bolus_events(nightscout, bolusEvents, pretend=pretend, include_bg=(BOLUS_BG in features), time_start=time_start, time_end=time_end)
|
||||
|
||||
if csvIobData:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import arrow
|
||||
import logging
|
||||
from tconnectsync.domain.bolus import Bolus
|
||||
|
||||
from tconnectsync.sync.cgm import find_event_at
|
||||
|
||||
@@ -15,17 +16,23 @@ logger = logging.getLogger(__name__)
|
||||
"""
|
||||
Given bolus data input from the therapy timeline CSV, converts it into a digestable format.
|
||||
"""
|
||||
def process_bolus_events(bolusdata, cgmEvents=None):
|
||||
def process_bolus_events(bolusdata, cgmEvents=None, source=""):
|
||||
bolusEvents = []
|
||||
|
||||
for b in bolusdata:
|
||||
parsed = TConnectEntry.parse_bolus_entry(b)
|
||||
parsed = None
|
||||
if source == "ciq":
|
||||
parsed = b.to_bolus()
|
||||
else:
|
||||
parsed = TConnectEntry.parse_bolus_entry(b)
|
||||
|
||||
assert type(parsed) == Bolus
|
||||
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: 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))
|
||||
logger.warning("Skipping non-completed %s bolus data (was a bolus in progress?): %s parsed: %s" % (source, b, parsed))
|
||||
continue
|
||||
|
||||
if parsed.bg and cgmEvents:
|
||||
@@ -34,7 +41,7 @@ def process_bolus_events(bolusdata, cgmEvents=None):
|
||||
|
||||
bolusEvents.append(parsed)
|
||||
|
||||
bolusEvents.sort(key=lambda event: arrow.get(event.request_time if not event.extended_bolus else event.bolex_start_time))
|
||||
bolusEvents.sort(key=lambda event: arrow.get(event.request_time if not event.is_extended_bolus else event.bolex_start_time))
|
||||
|
||||
return bolusEvents
|
||||
|
||||
@@ -72,7 +79,7 @@ def ns_write_bolus_events(nightscout, bolusEvents, pretend=False, include_bg=Fal
|
||||
|
||||
add_count = 0
|
||||
for event in bolusEvents:
|
||||
created_at = event.completion_time if not event.extended_bolus else event.bolex_start_time
|
||||
created_at = event.completion_time if not event.is_extended_bolus else event.bolex_start_time
|
||||
if last_upload_time and arrow.get(created_at) <= last_upload_time:
|
||||
if pretend:
|
||||
logger.info("Skipping basal event before last upload time: %s (time range: %s - %s)" % (event, time_start, time_end))
|
||||
|
||||
@@ -180,7 +180,7 @@ class TestBolusTherapyEvent(unittest.TestCase):
|
||||
completion_time="2022-07-21 11:55:24-04:00",
|
||||
insulin="2.9",
|
||||
requested_insulin="2.9",
|
||||
carbs="",
|
||||
carbs="0",
|
||||
bg="254",
|
||||
user_override="0",
|
||||
extended_bolus="0",
|
||||
@@ -263,7 +263,7 @@ class TestBolusTherapyEvent(unittest.TestCase):
|
||||
completion_time="2022-08-09 23:20:04-04:00",
|
||||
insulin="0.2",
|
||||
requested_insulin="0.2",
|
||||
carbs="",
|
||||
carbs="0",
|
||||
bg="131",
|
||||
user_override="1",
|
||||
extended_bolus="1",
|
||||
@@ -351,10 +351,17 @@ class TestBolusTherapyEvent(unittest.TestCase):
|
||||
completion_time="2022-08-09 23:20:04-04:00",
|
||||
insulin="0.2",
|
||||
requested_insulin="0.2",
|
||||
carbs="",
|
||||
carbs="0",
|
||||
bg="131",
|
||||
user_override="1",
|
||||
extended_bolus="1",
|
||||
bolex_completion_time="2022-08-09 23:35:03-04:00",
|
||||
bolex_start_time="2022-08-09 23:20:04-04:00"
|
||||
)))
|
||||
)))
|
||||
|
||||
|
||||
BOLUS_FULL_EXAMPLES = [
|
||||
TestBolusTherapyEvent.standardJson,
|
||||
TestBolusTherapyEvent.correctionJson,
|
||||
TestBolusTherapyEvent.extendedBolusJson
|
||||
]
|
||||
+56
-2
@@ -8,6 +8,7 @@ import copy
|
||||
from tconnectsync.process import process_time_range
|
||||
from tconnectsync.parser.nightscout import EXERCISE_EVENTTYPE, IOB_ACTIVITYTYPE, SLEEP_EVENTTYPE, NightscoutEntry
|
||||
from tconnectsync.features import BASAL, BOLUS, IOB, PUMP_EVENTS
|
||||
from tests.domain.test_therapy_event import BOLUS_FULL_EXAMPLES, TestCGMTherapyEvent
|
||||
|
||||
from .api.fake import TConnectApi
|
||||
from .nightscout_fake import NightscoutApi
|
||||
@@ -20,6 +21,11 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
|
||||
def stub_therapy_timeline(self, time_start, time_end):
|
||||
return copy.deepcopy(TestBasalSync.base)
|
||||
|
||||
def stub_ciq_therapy_events(self, time_start, time_end):
|
||||
return {
|
||||
"event": []
|
||||
}
|
||||
|
||||
def stub_therapy_timeline_csv(self, time_start, time_end):
|
||||
return {
|
||||
@@ -53,6 +59,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
return TestBasalSync.get_example_ciq_basal_events()
|
||||
|
||||
tconnect.controliq.therapy_timeline = fake_therapy_timeline
|
||||
tconnect.controliq.therapy_events = self.stub_ciq_therapy_events
|
||||
tconnect.ws2.therapy_timeline_csv = self.stub_therapy_timeline_csv
|
||||
|
||||
nightscout = NightscoutApi()
|
||||
@@ -88,6 +95,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
return TestBasalSync.get_example_ciq_basal_events()
|
||||
|
||||
tconnect.controliq.therapy_timeline = fake_therapy_timeline
|
||||
tconnect.controliq.therapy_events = self.stub_ciq_therapy_events
|
||||
tconnect.ws2.therapy_timeline_csv = self.stub_therapy_timeline_csv
|
||||
|
||||
nightscout = NightscoutApi()
|
||||
@@ -116,6 +124,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
return TestBasalSync.get_example_ciq_basal_events()
|
||||
|
||||
tconnect.controliq.therapy_timeline = fake_therapy_timeline
|
||||
tconnect.controliq.therapy_events = self.stub_ciq_therapy_events
|
||||
tconnect.ws2.therapy_timeline_csv = self.stub_therapy_timeline_csv
|
||||
|
||||
nightscout = NightscoutApi()
|
||||
@@ -159,6 +168,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
return TestBasalSync.get_example_ciq_basal_events()
|
||||
|
||||
tconnect.controliq.therapy_timeline = fake_therapy_timeline
|
||||
tconnect.controliq.therapy_events = self.stub_ciq_therapy_events
|
||||
tconnect.ws2.therapy_timeline_csv = self.stub_therapy_timeline_csv
|
||||
|
||||
nightscout = NightscoutApi()
|
||||
@@ -193,8 +203,8 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
|
||||
"""No data in Nightscout. Uploads all bolus data from tconnect."""
|
||||
def test_new_ciq_bolus_data(self):
|
||||
"""No data in Nightscout. Uploads all bolus data from tconnect via the WS2 API."""
|
||||
def test_new_ciq_bolus_data_from_ws2(self):
|
||||
tconnect = TConnectApi()
|
||||
|
||||
# datetimes are unused by the API fake
|
||||
@@ -202,6 +212,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
end = datetime.datetime(2021, 4, 21, 12, 0)
|
||||
|
||||
tconnect.controliq.therapy_timeline = self.stub_therapy_timeline
|
||||
tconnect.controliq.therapy_events = self.stub_ciq_therapy_events
|
||||
|
||||
bolusData = TestBolusSync.get_example_csv_bolus_events()
|
||||
def fake_therapy_timeline_csv(time_start, time_end):
|
||||
@@ -231,6 +242,46 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
self.assertDictEqual(nightscout.put_entries, {})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
|
||||
"""No data in Nightscout. Uploads all bolus data from tconnect via CIQ therapy_events."""
|
||||
def test_new_ciq_bolus_data_from_ciq_therapy_events(self):
|
||||
tconnect = TConnectApi()
|
||||
|
||||
# datetimes are unused by the API fake
|
||||
start = datetime.datetime(2022, 8, 9, 12, 0)
|
||||
end = datetime.datetime(2021, 8, 10, 12, 0)
|
||||
|
||||
tconnect.controliq.therapy_timeline = self.stub_therapy_timeline
|
||||
|
||||
def fake_ciq_therapy_events(time_start, time_end):
|
||||
return {
|
||||
"event": BOLUS_FULL_EXAMPLES
|
||||
}
|
||||
tconnect.controliq.therapy_events = fake_ciq_therapy_events
|
||||
|
||||
tconnect.ws2.therapy_timeline_csv = self.stub_therapy_timeline_csv
|
||||
|
||||
nightscout = NightscoutApi()
|
||||
|
||||
nightscout.last_uploaded_entry = self.stub_last_uploaded_entry
|
||||
nightscout.last_uploaded_activity = self.stub_last_uploaded_activity
|
||||
|
||||
process_time_range(tconnect, nightscout, start, end, pretend=False, features=[BOLUS, BASAL])
|
||||
|
||||
pprint.pprint(nightscout.uploaded_entries)
|
||||
self.assertEqual(len(nightscout.uploaded_entries["treatments"]), len(BOLUS_FULL_EXAMPLES))
|
||||
self.assertDictEqual(dict(nightscout.uploaded_entries), {
|
||||
"treatments": [
|
||||
# BGs are excluded because BG is not enabled in features for this test
|
||||
NightscoutEntry.bolus(2.9, 0, "2022-07-21 11:55:24-04:00", notes="Automatic Bolus/Correction"),
|
||||
NightscoutEntry.bolus(4.17, 25, "2022-07-21 12:29:21-04:00", notes="Standard"),
|
||||
# NOTE: the extended bolus is 0.2+0.2 but we currently only surface standard
|
||||
# BUG: inconsistency: we use the completion timestamp as the event timestamp for standard boluses,
|
||||
# but the standard strand time for extended
|
||||
NightscoutEntry.bolus(0.2, 0, "2022-08-09 23:20:04-04:00", notes="Extended 50.00%/0.00 (Override) (Extended)"),
|
||||
]})
|
||||
self.assertDictEqual(nightscout.put_entries, {})
|
||||
self.assertListEqual(nightscout.deleted_entries, [])
|
||||
|
||||
"""No data in Nightscout. Nothing should be updated in Nightscout without the BOLUS feature."""
|
||||
def test_bolus_data_not_updated_without_feature(self):
|
||||
tconnect = TConnectApi()
|
||||
@@ -271,6 +322,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
end = datetime.datetime(2021, 4, 21, 12, 0)
|
||||
|
||||
tconnect.controliq.therapy_timeline = self.stub_therapy_timeline
|
||||
tconnect.controliq.therapy_events = self.stub_ciq_therapy_events
|
||||
|
||||
iobData = TestIOBSync.get_example_csv_iob_events()
|
||||
def fake_therapy_timeline_csv(time_start, time_end):
|
||||
@@ -307,6 +359,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
end = datetime.datetime(2021, 4, 21, 12, 0)
|
||||
|
||||
tconnect.controliq.therapy_timeline = self.stub_therapy_timeline
|
||||
tconnect.controliq.therapy_events = self.stub_ciq_therapy_events
|
||||
|
||||
iobData = TestIOBSync.get_example_csv_iob_events()
|
||||
def fake_therapy_timeline_csv(time_start, time_end):
|
||||
@@ -454,6 +507,7 @@ class TestProcessTimeRange(unittest.TestCase):
|
||||
}
|
||||
|
||||
tconnect.controliq.therapy_timeline = fake_therapy_timeline
|
||||
tconnect.controliq.therapy_events = self.stub_ciq_therapy_events
|
||||
tconnect.ws2.therapy_timeline_csv = self.stub_therapy_timeline_csv
|
||||
tconnect.ws2.basalsuspension = self.stub_ws2_basalsuspension
|
||||
|
||||
|
||||
Reference in New Issue
Block a user