22This file provides an API for calling the functions in the dynamic library. These functions are c-embeddings
33for swift functions, found in Sources/LoopAlgorithmToPython/LoopAlgorithmToPython.swift.
44"""
5- from loop_to_python_api .helpers import get_bytes_from_json
5+ import numpy as np
6+ import pandas as pd
67
8+ import loop_to_python_api .helpers as helpers
79import ctypes
810import os
911import ast
10-
12+ import time
1113
1214# swift_lib = ctypes.CDLL('python_api/libLoopAlgorithmToPython.dylib')
1315
@@ -31,7 +33,7 @@ def initialize_exception_handlers():
3133
3234
3335def generate_prediction (json_file , len = 72 ):
34- json_bytes = get_bytes_from_json (json_file )
36+ json_bytes = helpers . get_bytes_from_json (json_file )
3537
3638 swift_lib .generatePrediction .argtypes = [ctypes .c_char_p ]
3739 swift_lib .generatePrediction .restype = ctypes .POINTER (ctypes .c_double )
@@ -41,7 +43,7 @@ def generate_prediction(json_file, len=72):
4143
4244
4345def get_prediction_dates (json_file ):
44- json_bytes = get_bytes_from_json (json_file )
46+ json_bytes = helpers . get_bytes_from_json (json_file )
4547
4648 swift_lib .getPredictionDates .argtypes = [ctypes .c_char_p ]
4749 swift_lib .getPredictionDates .restype = ctypes .c_char_p
@@ -60,7 +62,7 @@ def get_prediction_values_and_dates(json_file):
6062
6163
6264def get_dose_recommendations (json_file ):
63- json_bytes = get_bytes_from_json (json_file )
65+ json_bytes = helpers . get_bytes_from_json (json_file )
6466
6567 swift_lib .getDoseRecommendations .argtypes = [ctypes .c_char_p ]
6668 swift_lib .getDoseRecommendations .restype = ctypes .c_char_p
@@ -72,7 +74,7 @@ def get_dose_recommendations(json_file):
7274
7375# "Glucose effect velocity" is equivalent to insulin counteraction effect (ICE)
7476def get_glucose_effect_velocity (json_file , len = 72 ):
75- json_bytes = get_bytes_from_json (json_file )
77+ json_bytes = helpers . get_bytes_from_json (json_file )
7678
7779 swift_lib .getGlucoseEffectVelocity .argtypes = [ctypes .c_char_p ]
7880 swift_lib .getGlucoseEffectVelocity .restype = ctypes .POINTER (ctypes .c_double )
@@ -82,7 +84,7 @@ def get_glucose_effect_velocity(json_file, len=72):
8284
8385
8486def get_glucose_effect_velocity_dates (json_file ):
85- json_bytes = get_bytes_from_json (json_file )
87+ json_bytes = helpers . get_bytes_from_json (json_file )
8688
8789 swift_lib .getGlucoseEffectVelocityDates .argtypes = [ctypes .c_char_p ]
8890 swift_lib .getGlucoseEffectVelocityDates .restype = ctypes .c_char_p
@@ -94,16 +96,27 @@ def get_glucose_effect_velocity_dates(json_file):
9496 return date_list
9597
9698
97- def get_glucose_velocity_values_and_dates (json_file ):
98- # TODO: Add validation of json dates here to be more flexible?
99+ def get_glucose_effect_velocity_and_dates (json_file ):
100+ json_bytes = helpers .get_bytes_from_json (json_file )
101+
102+ swift_lib .getGlucoseEffectVelocityAndDates .argtypes = [ctypes .c_char_p ]
103+ swift_lib .getGlucoseEffectVelocityAndDates .restype = ctypes .c_char_p
104+
105+ result = swift_lib .getGlucoseEffectVelocityAndDates (json_bytes ).decode ('utf-8' )
106+
107+ values = []
108+ dates = []
109+ # Parse the string that contains both dates and values
110+ for item in result .split ():
111+ date_str , float_str = item .split (',' )
112+ dates .append (pd .to_datetime (date_str ))
113+ values .append (float (float_str ))
99114
100- dates = get_glucose_effect_velocity_dates (json_file )
101- values = get_glucose_effect_velocity (json_file , len (dates ))
102115 return values , dates
103116
104117
105118def get_active_carbs (json_file ):
106- json_bytes = get_bytes_from_json (json_file )
119+ json_bytes = helpers . get_bytes_from_json (json_file )
107120
108121 swift_lib .getActiveCarbs .argtypes = [ctypes .c_char_p ]
109122 swift_lib .getActiveCarbs .restype = ctypes .c_double
@@ -112,14 +125,71 @@ def get_active_carbs(json_file):
112125
113126
114127def get_active_insulin (json_file ):
115- json_bytes = get_bytes_from_json (json_file )
128+ json_bytes = helpers . get_bytes_from_json (json_file )
116129
117130 swift_lib .getActiveInsulin .argtypes = [ctypes .c_char_p ]
118131 swift_lib .getActiveInsulin .restype = ctypes .c_double
119132
120133 return swift_lib .getActiveInsulin (json_bytes )
121134
122135
136+ def add_insulin_counteraction_effect_to_df (df , basal , isf , cr , insulin_type = 'novolog' ):
137+ """
138+ Takes a dataframe with at least the columns CGM, bolus, and basal.
139+ Important note: this function assumes you only give data for a single subject at a time.
140+ # TODO: The therapy settings should be columns in the dataframe so we can support schedules.
141+
142+ :param df: Dataframe with at least a "basal" and a "bolus" column, and a datetime index.
143+ :param basal: Basal rate
144+ :param isf: Insulin sensitivity factor
145+ :param cr: Carbohydrate ratio (will not impact the results)
146+ :param insulin_type: Which insulin profile to use to compute the insulin on board
147+ :return: The input df with columns for insulin on board and insulin counteraction effects.
148+ """
149+ data = df [['basal' , 'bolus' , 'CGM' ]].copy () # Extract only necessary data to improve performance
150+ data .loc [:, 'bolus' ] = data ['bolus' ].replace (0.0 , np .nan )
151+ json_data = helpers .get_json_loop_prediction_input_from_df (data , basal , isf , cr , data .index [- 1 ], insulin_type )
152+
153+ ice_values , dates = get_glucose_effect_velocity_and_dates (json_file = json_data )
154+ dates = [date .tz_localize (None ) for date in dates ] # If you want to align to UTC
155+
156+ df .loc [:, "ice" ] = np .nan
157+ df .loc [dates , "ice" ] = ice_values
158+ return df
159+
160+
161+ def add_insulin_on_board_to_df (df , basal , isf , cr , insulin_type = 'novolog' , lookback = 72 ):
162+ """
163+ Adding insulin on board to a dataframe to each row, by using data from the previous rows given by lookback.
164+ IMPORTANT NOTE: This function does not handle separate subjects within a single dataframe. A subject's data should
165+ be passed individually.
166+
167+ :param df: Dataframe with at least a "basal" and a "bolus" column, and a datetime index.
168+ :param basal: Basal rate
169+ :param isf: Insulin sensitivity factor
170+ :param cr: Carbohydrate ratio (will not impact the results)
171+ :param insulin_type: Which insulin profile to use to compute the insulin on board
172+ :param lookback: Lookback used to compute each iob value. Increasing lookback will lower performance, but will be
173+ necessary for insulin types that are long-lasting, or for high datetime frequencies. The default of 72 is based on
174+ 6 hours duration of 5-minute intervals.
175+ :return: The original dataframe with a new column "iob"
176+ """
177+ data = df [['basal' , 'bolus' ]].copy () # Extract only necessary data to improve performance
178+ data .loc [:, 'bolus' ] = data ['bolus' ].replace (0.0 , np .nan )
179+
180+ iobs = []
181+ for i , date in enumerate (data .index [1 :]):
182+ start_index = max (0 , i - lookback + 1 )
183+ json_data = helpers .get_json_loop_prediction_input_from_df (data .iloc [start_index :i + 1 ], basal , isf , cr ,
184+ data .index [i + 1 ], insulin_type = insulin_type )
185+ iob = get_active_insulin (json_data )
186+ iobs += [iob ]
187+
188+ df .loc [:, "iob" ] = np .nan
189+ df .loc [df .index [1 :], "iob" ] = iobs
190+ return df
191+
192+
123193# Calculating the percentage of carbohydrate absorption at the percent time, with piecewise linear model
124194# Input is percent as fraction
125195def percent_absorption_at_percent_time (percent_time ):
@@ -147,7 +217,7 @@ def linear_percent_rate_at_percent_time(percent_time):
147217
148218
149219def get_dynamic_carbs_on_board (json_file ):
150- json_bytes = get_bytes_from_json (json_file )
220+ json_bytes = helpers . get_bytes_from_json (json_file )
151221
152222 swift_lib .getDynamicCarbsOnBoard .argtypes = [ctypes .c_char_p ]
153223 swift_lib .getDynamicCarbsOnBoard .restype = ctypes .c_double
0 commit comments