import pytest import profile_data as pd_ def _raw_profile(units="mg/dl", default_name="Default"): return { "defaultProfile": default_name, "store": { "Default": { "units": units, "dia": "5", "basal": [ {"time": "00:00", "value": "0.8"}, {"time": "06:00", "value": "1.2"}, {"time": "22:00", "value": "0.7"}, ], "sens": [{"time": "00:00", "value": "50"}], "carbratio": [{"time": "00:00", "value": "10"}], "target_low": [{"time": "00:00", "value": "90"}], "target_high": [{"time": "00:00", "value": "120"}], } }, } def test_parse_profile_returns_empty_dict_for_empty_input(): assert pd_.parse_profile({}) == {} def test_parse_profile_converts_mgdl_to_mmol(): profile = pd_.parse_profile(_raw_profile(units="mg/dl")) assert profile["units"] == "mmol" # 90 mg/dl -> ~5.0 mmol/L, 120 mg/dl -> ~6.7 mmol/L, 50 mg/dl (ISF) -> ~2.8 mmol/L assert profile["target_low"][0]["value"] == pytest.approx(5.0, abs=0.05) assert profile["target_high"][0]["value"] == pytest.approx(6.7, abs=0.05) assert profile["sens"][0]["value"] == pytest.approx(2.8, abs=0.05) def test_parse_profile_leaves_mmol_profiles_unconverted(): profile = pd_.parse_profile(_raw_profile(units="mmol")) assert profile["target_low"][0]["value"] == pytest.approx(90.0) def test_parse_profile_does_not_convert_basal_or_carbratio(): profile = pd_.parse_profile(_raw_profile(units="mg/dl")) assert profile["basal"][0]["value"] == pytest.approx(0.8) assert profile["carbratio"][0]["value"] == pytest.approx(10.0) def test_parse_profile_sorts_basal_segments_by_time(): raw = _raw_profile() raw["store"]["Default"]["basal"] = [ {"time": "22:00", "value": "0.7"}, {"time": "00:00", "value": "0.8"}, {"time": "06:00", "value": "1.2"}, ] profile = pd_.parse_profile(raw) assert [seg["minutes"] for seg in profile["basal"]] == [0, 360, 1320] def test_parse_profile_falls_back_to_first_profile_when_default_name_missing(): raw = _raw_profile(default_name="DoesNotExist") profile = pd_.parse_profile(raw) assert profile["dia"] == pytest.approx(5.0) def test_parse_profile_dia_and_name(): profile = pd_.parse_profile(_raw_profile()) assert profile["dia"] == pytest.approx(5.0) assert profile["name"] == "Default" def test_value_at_returns_active_segment(): segments = [{"minutes": 0, "value": 0.8}, {"minutes": 360, "value": 1.2}, {"minutes": 1320, "value": 0.7}] assert pd_.value_at(segments, 100) == pytest.approx(0.8) assert pd_.value_at(segments, 360) == pytest.approx(1.2) assert pd_.value_at(segments, 1000) == pytest.approx(1.2) assert pd_.value_at(segments, 1350) == pytest.approx(0.7) def test_value_at_wraps_to_last_segment_before_first(): segments = [{"minutes": 360, "value": 1.2}, {"minutes": 1320, "value": 0.7}] assert pd_.value_at(segments, 100) == pytest.approx(0.7) def test_value_at_empty_segments_returns_none(): assert pd_.value_at([], 100) is None def test_segments_table_computes_end_times_from_next_segment(): segments = [{"minutes": 0, "value": 0.8}, {"minutes": 360, "value": 1.2}] rows = pd_.segments_table(segments) assert rows == [ {"start": "00:00", "end": "06:00", "value": 0.8}, {"start": "06:00", "end": "24:00", "value": 1.2}, ] def test_segments_to_hourly_bars_closes_the_24h_loop(): segments = [{"minutes": 0, "value": 0.8}, {"minutes": 360, "value": 1.2}] bars = pd_.segments_to_hourly_bars(segments) assert bars[-1] == {"hour": 24.0, "value": 0.8} def test_segments_to_hourly_bars_empty_input(): assert pd_.segments_to_hourly_bars([]) == [] def test_total_daily_basal_weights_by_segment_duration(): # 6h @ 0.8 + 18h @ 1.2 = 4.8 + 21.6 = 26.4 segments = [{"minutes": 0, "value": 0.8}, {"minutes": 360, "value": 1.2}] assert pd_.total_daily_basal(segments) == pytest.approx(26.4) def test_total_daily_basal_empty_input(): assert pd_.total_daily_basal([]) == 0.0