added getting data from nightscout

This commit is contained in:
2026-07-25 08:54:08 +02:00
parent c02110a82b
commit 60735de445
12 changed files with 613 additions and 285 deletions
+54 -32
View File
@@ -1,52 +1,74 @@
import pandas as pd
import pytest
from data_processing import filter_by_date, hourly_average
from data_processing import classify_treatments, glucose_status, summarize_time_in_range
@pytest.fixture
def sample_df():
def sample_glucose():
return pd.DataFrame(
{
"timestamp": pd.to_datetime(
[
"2026-07-22 08:05",
"2026-07-22 08:40",
"2026-07-22 09:10",
"2026-07-23 08:05", # andere dag, moet uitgefilterd worden
]
["2026-07-25 08:00", "2026-07-25 09:00", "2026-07-25 10:00", "2026-07-25 11:00"]
),
"glucose_value": [6.0, 8.0, 7.0, 12.0],
"mmol": [3.5, 6.0, 11.0, 7.0], # laag, in bereik, hoog, in bereik
}
)
def test_filter_by_date_keeps_only_selected_day(sample_df):
result = filter_by_date(sample_df, pd.Timestamp("2026-07-22").date())
assert len(result) == 3
assert (result["timestamp"].dt.date == pd.Timestamp("2026-07-22").date()).all()
@pytest.fixture
def sample_treatments():
return pd.DataFrame(
{
"timestamp": pd.to_datetime(
[
"2026-07-25 08:00",
"2026-07-25 12:00",
"2026-07-24 09:00",
"2026-07-20 09:00",
]
),
"eventType": ["Meal Bolus", "Correction Bolus", "Site Change", "Sensor Change"],
"insulin": [4.0, 1.5, None, None],
"carbs": [45.0, None, None, None],
}
)
def test_hourly_average_computes_mean_per_hour(sample_df):
filtered = filter_by_date(sample_df, pd.Timestamp("2026-07-22").date())
result = hourly_average(filtered)
hour_8 = result.loc[result["hour"] == 8, "glucose_value"].iloc[0]
hour_9 = result.loc[result["hour"] == 9, "glucose_value"].iloc[0]
assert hour_8 == pytest.approx(7.0) # gemiddelde van 6.0 en 8.0
assert hour_9 == pytest.approx(7.0)
def test_glucose_status_classifies_correctly():
assert glucose_status(3.5) == "low"
assert glucose_status(6.0) == "in_range"
assert glucose_status(11.0) == "high"
def test_hourly_average_handles_empty_input():
empty = pd.DataFrame(columns=["timestamp", "glucose_value"])
result = hourly_average(empty)
assert result.empty
assert list(result.columns) == ["hour", "glucose_value"]
def test_summarize_time_in_range(sample_glucose):
result = summarize_time_in_range(sample_glucose)
assert result == {"low": 25.0, "in_range": 50.0, "high": 25.0}
def test_hourly_average_only_returns_hours_with_data(sample_df):
filtered = filter_by_date(sample_df, pd.Timestamp("2026-07-22").date())
result = hourly_average(filtered)
# alleen uur 8 en 9 hebben data op deze dag, dus precies 2 rijen
assert len(result) == 2
def test_summarize_time_in_range_handles_empty_input():
empty = pd.DataFrame(columns=["timestamp", "mmol"])
result = summarize_time_in_range(empty)
assert result == {"low": 0.0, "in_range": 0.0, "high": 0.0}
def test_classify_treatments_splits_into_expected_subsets(sample_treatments):
events = classify_treatments(sample_treatments)
assert len(events["insulin"]) == 2
assert set(events["insulin"]["insulin"]) == {4.0, 1.5}
assert len(events["carbs"]) == 1
assert events["carbs"].iloc[0]["carbs"] == 45.0
assert len(events["site_changes"]) == 1
assert events["site_changes"].iloc[0]["eventType"] == "Site Change"
assert len(events["sensor_changes"]) == 1
assert events["sensor_changes"].iloc[0]["eventType"] == "Sensor Change"
def test_classify_treatments_handles_empty_input():
empty = pd.DataFrame(columns=["timestamp", "eventType", "insulin", "carbs"])
events = classify_treatments(empty)
assert all(df.empty for df in events.values())
+98
View File
@@ -0,0 +1,98 @@
from datetime import datetime, timedelta, timezone
import pytest
import nightscout_client as ns
class FakeResponse:
def __init__(self, payload):
self._payload = payload
def raise_for_status(self):
pass
def json(self):
return self._payload
def test_mgdl_to_mmol_converts_correctly():
assert ns.mgdl_to_mmol(126.0) == pytest.approx(7.0, abs=0.05)
def test_base_url_requires_nightscout_url(monkeypatch):
monkeypatch.delenv("NIGHTSCOUT_URL", raising=False)
with pytest.raises(ns.NightscoutConfigError):
ns._base_url()
def test_base_url_strips_trailing_slash(monkeypatch):
monkeypatch.setenv("NIGHTSCOUT_URL", "https://example.com/")
assert ns._base_url() == "https://example.com"
def test_auth_prefers_token_over_api_secret(monkeypatch):
monkeypatch.setenv("NIGHTSCOUT_TOKEN", "my-token")
monkeypatch.setenv("NIGHTSCOUT_API_SECRET", "my-secret")
headers, params = ns._auth()
assert params == {"token": "my-token"}
assert headers == {}
def test_auth_hashes_api_secret(monkeypatch):
monkeypatch.delenv("NIGHTSCOUT_TOKEN", raising=False)
monkeypatch.setenv("NIGHTSCOUT_API_SECRET", "my-secret")
headers, params = ns._auth()
assert params == {}
assert headers["API-SECRET"] != "my-secret" # nooit in plaintext
assert len(headers["API-SECRET"]) == 40 # sha1 hexdigest
def test_fetch_entries_converts_and_filters_by_time(monkeypatch):
monkeypatch.setenv("NIGHTSCOUT_URL", "https://example.com")
monkeypatch.delenv("NIGHTSCOUT_TOKEN", raising=False)
monkeypatch.delenv("NIGHTSCOUT_API_SECRET", raising=False)
now = datetime.now(timezone.utc)
recent = now - timedelta(hours=1)
too_old = now - timedelta(hours=48)
payload = [
{"date": int(recent.timestamp() * 1000), "sgv": 126},
{"date": int(too_old.timestamp() * 1000), "sgv": 90},
]
monkeypatch.setattr(ns.requests, "get", lambda *a, **k: FakeResponse(payload))
result = ns.fetch_entries(hours=24)
assert len(result) == 1
assert result.iloc[0]["mmol"] == pytest.approx(7.0, abs=0.05)
def test_fetch_entries_handles_empty_response(monkeypatch):
monkeypatch.setenv("NIGHTSCOUT_URL", "https://example.com")
monkeypatch.setattr(ns.requests, "get", lambda *a, **k: FakeResponse([]))
result = ns.fetch_entries(hours=24)
assert result.empty
assert list(result.columns) == ["timestamp", "mmol"]
def test_fetch_treatments_fills_missing_columns(monkeypatch):
monkeypatch.setenv("NIGHTSCOUT_URL", "https://example.com")
now = datetime.now(timezone.utc)
payload = [
{"created_at": now.isoformat(), "eventType": "Site Change"},
]
monkeypatch.setattr(ns.requests, "get", lambda *a, **k: FakeResponse(payload))
result = ns.fetch_treatments(hours=24)
assert len(result) == 1
assert result.iloc[0]["eventType"] == "Site Change"
assert pd_isna(result.iloc[0]["insulin"])
assert pd_isna(result.iloc[0]["carbs"])
def pd_isna(value):
import pandas as pd
return pd.isna(value)