1+ """
2+ Step 1.1 — Verify the core pipeline:
3+
4+ df (from fixture)
5+ → prepare_for_autotune_isf (autotune_prep)
6+ → run_autotune_isf_iterations (autotune_isf)
7+ → newISF
8+
9+ No SimGlucose. No AdaptiveLoopController. Just the data pipeline.
10+
11+ Run with:
12+ pytest tests/test_pipeline.py -v
13+ """
14+ from __future__ import annotations
15+
16+ import json
17+ from pathlib import Path
18+
19+ import pandas as pd
20+ import pytest
21+
22+ from loop_to_python_adaptive .autotune_isf import (
23+ run_autotune_isf_iterations ,
24+ extract_pump_isf ,
25+ extract_pump_basal ,
26+ extract_pump_cr ,
27+ )
28+ from loop_to_python_adaptive .autotune_prep import AutotunePrepConfig
29+
30+
31+ def find_repo_root (start : Path ) -> Path :
32+ p = start
33+ while True :
34+ if (p / "loop_to_python_adaptive" ).exists ():
35+ return p
36+ if p .parent == p :
37+ raise RuntimeError ("Could not find repo root." )
38+ p = p .parent
39+
40+
41+ @pytest .fixture
42+ def loop_input () -> dict :
43+ repo_root = find_repo_root (Path (__file__ ).resolve ())
44+ file = repo_root / "tests" / "test_files" / "loop_algorithm_input.json"
45+ assert file .exists (), f"Missing fixture: { file } "
46+ return json .loads (file .read_text (encoding = "utf-8" ))
47+
48+
49+ @pytest .fixture
50+ def df_window (loop_input ) -> pd .DataFrame :
51+ """Build a CGM DataFrame from the fixture's glucoseHistory."""
52+ glucose = loop_input ["glucoseHistory" ]
53+ idx = pd .to_datetime ([g ["date" ] for g in glucose ], utc = True )
54+ df = pd .DataFrame (
55+ {"CGM" : [float (g ["value" ]) for g in glucose ]},
56+ index = idx ,
57+ ).sort_index ()
58+ return df
59+
60+
61+ # ── Test 1: extractors work ───────────────────────────────────────────────────
62+
63+ def test_extract_pump_settings (loop_input ):
64+ """pump_isf, pump_basal, pump_cr can be extracted from the fixture."""
65+ isf = extract_pump_isf (loop_input )
66+ basal = extract_pump_basal (loop_input )
67+ cr = extract_pump_cr (loop_input )
68+
69+ assert isf > 0 , f"pump_isf={ isf } "
70+ assert basal > 0 , f"pump_basal={ basal } "
71+ assert cr > 0 , f"pump_cr={ cr } "
72+ print (f"\n pump_isf={ isf } , pump_basal={ basal } , pump_cr={ cr } " )
73+
74+
75+ # ── Test 2: full pipeline runs and ISF changes ────────────────────────────────
76+
77+ def test_pipeline_isf_changes (loop_input , df_window ):
78+ """
79+ Core Step 1.1 test:
80+ df → run_autotune_isf_iterations → newISF
81+ Done when: isf_before != isf_after
82+ """
83+ isf_before = extract_pump_isf (loop_input )
84+ print (f"\n ISF before: { isf_before } " )
85+
86+ result = run_autotune_isf_iterations (
87+ [df_window ],
88+ loop_algorithm_inputs = [loop_input ],
89+ n_iterations = 1 ,
90+ )
91+
92+ isf_after = result ["finalISF" ]
93+ last = result ["last_result" ]
94+
95+ print (f"ISF after: { isf_after } " )
96+ print (f"n_points: { last ['n_points' ]} " )
97+ print (f"reason: { last ['reason' ]} " )
98+ print (f"p50_ratio: { last ['p50_ratio' ]} " )
99+
100+ # Must have run successfully
101+ assert last ["reason" ] == "OK" , (
102+ f"Autotune did not run — reason: { last ['reason' ]} . "
103+ f"Only { last ['n_points' ]} ISF points found in fixture. "
104+ "Check that the fixture has enough basal-only periods."
105+ )
106+
107+ # ISF must have changed
108+ assert isf_before != isf_after , (
109+ f"ISF unchanged at { isf_before } . "
110+ "Autotune ran but produced no change — check p50_ratio above."
111+ )
112+
113+ # newISF must be finite and positive
114+ assert isf_after > 0
115+ assert isf_after < 500 # sanity upper bound
116+
117+
118+ # ── Test 3: multiple iterations converge ─────────────────────────────────────
119+
120+ def test_pipeline_multiple_iterations_stable (loop_input , df_window ):
121+ """
122+ Running 3 iterations should not crash and ISF should stay within bounds.
123+ """
124+ pump_isf = extract_pump_isf (loop_input )
125+
126+ result = run_autotune_isf_iterations (
127+ [df_window ],
128+ loop_algorithm_inputs = [loop_input ],
129+ n_iterations = 3 ,
130+ )
131+
132+ history = result ["isf_history" ]
133+ assert len (history ) == 3 , f"Expected 3 history entries, got { len (history )} "
134+
135+ for i , isf in enumerate (history ):
136+ assert isf > 0 , f"Iteration { i + 1 } : ISF={ isf } is not positive"
137+ # Must stay within autosens bounds (default 0.7–1.2 × pump_isf)
138+ assert isf >= pump_isf * 0.7 * 0.99 , f"ISF { isf } too low vs pump { pump_isf } "
139+ assert isf <= pump_isf / 0.7 * 1.01 , f"ISF { isf } too high vs pump { pump_isf } "
140+
141+ print (f"\n ISF history over 3 iterations: { history } " )
0 commit comments