22This module maps loop data to fit oref0 autotune format.
33It takes predictions from Loop Algorithm and computes BGI equivalent
44the dataframe returned has bgi, deviation, avgDelta
5- BGI from ExponentialInsulinModel (LoopAlgorithm)
6- via insulin_percent_effect_remaining, mirroring oref0's:
7- BGI = -iob.activity * sens * 5
8- where iob.activity is the instantaneous insulin activity (U/min).
5+
96"""
107from __future__ import annotations
118from dataclasses import dataclass
@@ -93,7 +90,7 @@ def insulin_activity_at(
9390 return - (per_after - per_before ) / (2 * dt )
9491
9592
96-
93+ '''
9794@dataclass(frozen=True)
9895class BGIConfig:
9996 action_duration_minutes: int
@@ -117,6 +114,70 @@ def generate_bgi_series_from_insulin_prediction(loop_algorithm_input: dict) -> p
117114 # Typically negative during insulin action because predicted glucose is descending.
118115 bgi = pred.shift(-1) - pred
119116 return bgi
117+ '''
118+ def generate_bgi_series_from_predictions (
119+ df : pd .DataFrame ,
120+ * ,
121+ json_history : list [dict ],
122+ ) -> pd .Series :
123+ """
124+ Compute BGI(t) = pred(t+5m) - pred(t) using Loop's own predictions.
125+
126+ Each json snapshot in json_history was built at a specific prediction_start.
127+ We call get_prediction_values_and_dates for each snapshot and extract the
128+ BGI at the prediction_start timestamp only — this gives us one reliable
129+ BGI value per snapshot that is internally consistent with what Loop computed.
130+
131+ The resulting sparse series is then reindexed onto df's index using
132+ forward-fill, which is appropriate because the Loop prediction is valid
133+ for the 5-min window starting at prediction_start.
134+
135+ This is the correct BGI method because:
136+ - It uses exactly the same insulin model and dose history that Loop used
137+ - It avoids the mismatch between our Python activity model and Loop's
138+ internal Swift model
139+ - It is consistent with oref0's intent (BGI = expected BG change per 5min
140+ from insulin alone)
141+ """
142+ out = _to_utc_index (df )
143+
144+ bgi_points : dict [pd .Timestamp , float ] = {}
145+
146+ for json_input in json_history :
147+ try :
148+ values , dates = get_prediction_values_and_dates (json_input )
149+ except Exception :
150+ continue
151+
152+ if not values or not dates :
153+ continue
154+
155+ p_idx = pd .to_datetime (dates , utc = True )
156+ pred = pd .Series (values , index = p_idx , dtype = "float64" ).sort_index ()
157+
158+ # BGI at each prediction point = pred(t+5m) - pred(t)
159+ bgi_series = pred .shift (- 1 ) - pred
160+
161+ # Extract only the value at the prediction_start (first point),
162+ # which is the BGI that was valid when this snapshot was taken
163+ if len (bgi_series ) >= 1 :
164+ ts = bgi_series .index [0 ]
165+ val = bgi_series .iloc [0 ]
166+ if not pd .isna (val ):
167+ bgi_points [ts ] = val
168+
169+ if not bgi_points :
170+ # Fallback: return NaN series
171+ return pd .Series (float ("nan" ), index = out .index , dtype = "float64" )
172+
173+ sparse = pd .Series (bgi_points , dtype = "float64" ).sort_index ()
174+
175+ # Forward-fill onto df index so every CGM row gets a BGI value
176+ combined = sparse .reindex (
177+ sparse .index .union (out .index )
178+ ).ffill ().reindex (out .index )
179+
180+ return combined
120181
121182'''
122183def generate_bgi_series_from_activity(
@@ -190,7 +251,7 @@ def add_bgi_to_history_df(
190251 bgi_col : str = "BGI" ,
191252 align : AlignMode = "ffill" ,
192253 loop_algorithm_input : dict | None = None ,
193-
254+ json_history : list [ dict ] | None = None ,
194255) -> pd .DataFrame :
195256 """
196257 Adds a BGI column to the given history dataframe by generating a BGI series from predictions.
@@ -201,18 +262,20 @@ def add_bgi_to_history_df(
201262 loop_algorithm_input = api .get_loop_algorithm_input ()
202263 insulin_type = loop_algorithm_input .get ("insulinType" , "novolog" )
203264
204- bgi_pred = generate_bgi_series_from_insulin_prediction (loop_algorithm_input )
265+ out [bgi_col ] = generate_bgi_series_from_predictions (
266+ out ,
267+ json_history = json_history ,)
205268
206269
207270 # Aligning timestamps needed for bgi from predictions as they are "in the future"
208- if align == "ffill" :
209- out [bgi_col ] = bgi_pred .reindex (out .index , method = "ffill" )
210- elif align == "nearest" :
211- out [bgi_col ] = bgi_pred .reindex (out .index , method = "nearest" )
212- elif align == "strict" :
213- out [bgi_col ] = bgi_pred .reindex (out .index )
214- else :
215- raise ValueError ("align must be one of: 'ffill', 'nearest', 'strict'." )
271+ # if align == "ffill":
272+ # out[bgi_col] = bgi_pred.reindex(out.index, method="ffill")
273+ # elif align == "nearest":
274+ # out[bgi_col] = bgi_pred.reindex(out.index, method="nearest")
275+ # elif align == "strict":
276+ # out[bgi_col] = bgi_pred.reindex(out.index)
277+ # else:
278+ # raise ValueError("align must be one of: 'ffill', 'nearest', 'strict'.")
216279
217280 #bgi using activity model
218281 # out[bgi_col] = generate_bgi_series_from_activity(
0 commit comments