Skip to content

Commit 5171d39

Browse files
author
Sigrid Tofte Thiis
committed
making bgi out of prediction causes less drift
1 parent b58447e commit 5171d39

2 files changed

Lines changed: 85 additions & 15 deletions

File tree

loop_to_python_adaptive/autotune_isf.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ def run_autotune_isf_iterations(
253253
loop_algorithm_inputs: list[dict], # one per window, aligned with df_windows
254254
n_iterations: int = 1, # number of passes
255255
cfg: AutotuneISFConfig = AutotuneISFConfig(),
256+
json_history_list: list[list[dict]] | None = None,
256257
) -> dict[str, Any]:
257258
"""
258259
Run ISF autotune for `n_iterations` passes over the data windows.
@@ -307,10 +308,16 @@ def run_autotune_isf_iterations(
307308
zip(df_windows, loop_algorithm_inputs)
308309
):
309310
print(f" Window {i + 1}/{len(df_windows)} ...", end=" ", flush=True)
311+
window_json_history = (
312+
json_history_list[i]
313+
if json_history_list is not None and i < len(json_history_list)
314+
else None
315+
)
310316
result = prepare_for_autotune_isf(
311317
df_window,
312318
loop_algorithm_input=loop_input,
313319
cfg=prep_cfg,
320+
json_history=window_json_history, # ← ADD
314321
)
315322
window_isf_points = result["ISFGlucoseData"]
316323
all_isf_points.extend(window_isf_points)

loop_to_python_adaptive/loop_oref_mapping.py

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
This module maps loop data to fit oref0 autotune format.
33
It takes predictions from Loop Algorithm and computes BGI equivalent
44
the 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
"""
107
from __future__ import annotations
118
from 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)
9895
class 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
'''
122183
def 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

Comments
 (0)