33from dataclasses import dataclass
44from typing import Any , Optional
55
6- import numpy as np
6+ import pandas as pd
77
8-
9- @dataclass (frozen = True )
10- class AutotuneISFConfig :
11- min_points : int = 10
12- adjustment_fraction : float = 0.2 # oref0 uses 20%
13- autosens_max : float = 1.2 # oref0 default
14- autosens_min : float = 0.7 # oref0 default
15- min_bgi_abs : float = 1e-6 # avoid divide by tiny numbers
16- ratio_clip_low : float = 0.3 # hard clip to avoid insane outliers
17- ratio_clip_high : float = 3.0
8+ from loop_to_python_adaptive .loop_bgi import BGIConfig , prepare_isf_glucose_data
189
1910
20- def tune_isf_like_oref0 (
21- * ,
22- isf_current : float ,
23- isf_glucose_data : list [dict [str , Any ]],
24- pump_isf : Optional [float ] = None ,
25- config : AutotuneISFConfig | None = None ,
26- ) -> dict [str , Any ]:
11+ @dataclass (frozen = True )
12+ class AutotunePrepConfig :
2713 """
28- oref0-like ISF tuning:
29- ratio = 1 + deviation / BGI
30- fullNewISF = ISF * median(ratio)
31- newISF = (1-adjustment_fraction)*ISF + adjustment_fraction*adjustedISF
14+ Configuration for preparing inputs to autotune_isf.tune_isf_like_oref0.
3215 """
33- cfg = config or AutotuneISFConfig ()
34-
35- ratios : list [float ] = []
36- for p in isf_glucose_data :
37- dev = float (p ["deviation" ])
38- bgi = float (p ["BGI" ])
39- if abs (bgi ) < cfg .min_bgi_abs :
40- continue
41- r = 1.0 + dev / bgi
42- if not np .isfinite (r ):
43- continue
44- r = float (np .clip (r , cfg .ratio_clip_low , cfg .ratio_clip_high ))
45- ratios .append (r )
16+ action_duration_minutes : int = 360
17+ peak_activity_minutes : int = 75
18+ delay_minutes : int = 10
19+ history_hours : int = 16
20+ step_minutes : int = 5
4621
47- if len (ratios ) < cfg .min_points :
48- return {
49- "newISF" : float (isf_current ),
50- "p50_ratio" : None ,
51- "fullNewISF" : None ,
52- "adjustedISF" : None ,
53- "n_points" : len (ratios ),
54- "reason" : f"Only { len (ratios )} usable ISF points (<{ cfg .min_points } ); leaving ISF unchanged." ,
55- }
22+ cgm_col : str = "CGM"
23+ bgi_col : str = "BGI"
5624
57- p50_ratio = float (np .median (ratios ))
58- full_new_isf = float (isf_current * p50_ratio )
5925
60- adjusted_isf = full_new_isf
61- if pump_isf is not None and pump_isf > 0 :
62- # Match oref0 bounds:
63- # low autosens ratio = high ISF => maxISF = pumpISF / autosens_min
64- # high autosens ratio = low ISF => minISF = pumpISF / autosens_max
65- max_isf = pump_isf / cfg .autosens_min
66- min_isf = pump_isf / cfg .autosens_max
67- adjusted_isf = float (np .clip (adjusted_isf , min_isf , max_isf ))
68-
69- # Slow update like oref0
70- new_isf = (1.0 - cfg .adjustment_fraction ) * float (isf_current ) + cfg .adjustment_fraction * float (adjusted_isf )
26+ def prepare_for_autotune_isf (
27+ df : pd .DataFrame ,
28+ * ,
29+ loop_algorithm_input : dict ,
30+ config : Optional [AutotunePrepConfig ] = None ,
31+ ) -> dict [str , Any ]:
32+ """
33+ Prepare the exact inputs autotune_isf.tune_isf_like_oref0 needs.
7134
72- return {
73- "newISF" : float (new_isf ),
74- "p50_ratio" : p50_ratio ,
75- "fullNewISF" : full_new_isf ,
76- "adjustedISF" : float (adjusted_isf ),
77- "n_points" : len (ratios ),
78- "reason" : "OK" ,
79- }
35+ Returns:
36+ {
37+ "df": df_with_BGI,
38+ "isf_glucose_data": list[{"date","avgDelta","BGI","deviation"}...]
39+ }
40+ """
41+ cfg = config or AutotunePrepConfig ()
42+
43+ bgi_cfg = BGIConfig (
44+ action_duration_minutes = cfg .action_duration_minutes ,
45+ peak_activity_minutes = cfg .peak_activity_minutes ,
46+ delay_minutes = cfg .delay_minutes ,
47+ step_minutes = cfg .step_minutes ,
48+ history_hours = cfg .history_hours ,
49+ )
50+
51+ df2 , isf_glucose_data = prepare_isf_glucose_data (
52+ df ,
53+ loop_algorithm_input = loop_algorithm_input ,
54+ config = bgi_cfg ,
55+ cgm_col = cfg .cgm_col ,
56+ bgi_col = cfg .bgi_col ,
57+ )
58+
59+ return {"df" : df2 , "isf_glucose_data" : isf_glucose_data }
0 commit comments