forecose 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
forecose/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .forecose import DexcomForecast
2
+
3
+ __all__ = [
4
+ "DexcomForecast"
5
+ ]
forecose/forecose.py ADDED
@@ -0,0 +1,116 @@
1
+ """Dexcom prediction class."""
2
+
3
+ import numpy as np
4
+
5
+ import pandas as pd
6
+
7
+ import timesfm
8
+
9
+ from pydexcom import Dexcom
10
+
11
+ class DexcomForecast:
12
+ """Class for a time-series forecasting model construction of CGM data."""
13
+
14
+ def __init__(
15
+ self,
16
+ cgm_history: np.ndarray,
17
+ last_timestamp: pd.Timestamp,
18
+ sampling_interval: pd.Timedelta,
19
+ context_len: int = 288,
20
+ horizon: int = 12
21
+ ) -> None:
22
+ """
23
+ Compiles the TimesFM model.
24
+ """
25
+ self.context_len = context_len
26
+ self.horizon = horizon
27
+
28
+ self.cgm_history = cgm_history[-self.context_len:]
29
+ self.last_timestamp = last_timestamp
30
+ self.sampling_interval = sampling_interval
31
+
32
+ # initalise and compile the TimesFM model
33
+ self.model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
34
+ "google/timesfm-2.5-200m-pytorch"
35
+ )
36
+ self.model.compile(
37
+ timesfm.ForecastConfig(
38
+ max_context=self.context_len,
39
+ max_horizon=self.horizon,
40
+ normalize_inputs=True,
41
+ use_continuous_quantile_head=True,
42
+ infer_is_positive=True,
43
+ fix_quantile_crossing=True,
44
+ )
45
+ )
46
+
47
+ @classmethod
48
+ def from_dexcom(
49
+ cls,
50
+ dexcom: Dexcom,
51
+ context_len: int = 288,
52
+ horizon: int = 12
53
+ ):
54
+ """Pull data from an active pydexcom session."""
55
+ if not isinstance(dexcom, Dexcom):
56
+ raise TypeError("Expected an object of type pydexcom.Dexcom.")
57
+
58
+ # extract data
59
+ readings = dexcom.get_glucose_readings(
60
+ minutes=1440, max_count=context_len
61
+ )
62
+ if not readings:
63
+ raise RuntimeError("No readings returned from Dexcom Share API.")
64
+
65
+ # process data structures
66
+ df = pd.DataFrame(
67
+ [{"Time": r.datetime, "Glucose": r.mmol_l} for r in reversed(readings)]
68
+ )
69
+ df["Time"] = pd.to_datetime(df["Time"], utc=True).dt.tz_convert("Europe/London")
70
+ df = df.sort_values("Time").reset_index(drop=True)
71
+
72
+ cgm_history = df["Glucose"].to_numpy(dtype=float)
73
+ last_timestamp = df["Time"].iloc[-1]
74
+ sampling_interval = pd.Timedelta(df["Time"].diff().median())
75
+
76
+ # return instance
77
+ return cls(
78
+ cgm_history=cgm_history,
79
+ last_timestamp=last_timestamp,
80
+ sampling_interval=sampling_interval,
81
+ context_len=context_len,
82
+ horizon=horizon
83
+ )
84
+
85
+ def forecast(self) -> pd.DataFrame:
86
+ """Returns point and quantile forecasts of time series data."""
87
+ if len(self.cgm_history) == 0:
88
+ raise ValueError("No historical data provided for forecasting.")
89
+
90
+ # run inference
91
+ point_forecast, quantile_forecast = self.model.forecast(
92
+ horizon=self.horizon,
93
+ inputs=[self.cgm_history],
94
+ )
95
+
96
+ point_vals = point_forecast[0]
97
+ quant_vals = quantile_forecast[0]
98
+
99
+ future_timestamps = pd.date_range(
100
+ start=self.last_timestamp + self.sampling_interval,
101
+ periods=self.horizon,
102
+ freq=self.sampling_interval,
103
+ )
104
+
105
+ # compile
106
+ forecast_df = pd.DataFrame({
107
+ "timestamp": future_timestamps,
108
+ "predicted_glucose": np.clip(point_vals, *(2.2, 22.2)),
109
+ "q10": np.clip(quant_vals[:, 0], *(2.2, 22.2)),
110
+ "q25": np.clip(quant_vals[:, 1], *(2.2, 22.2)),
111
+ "q50": np.clip(quant_vals[:, 4], *(2.2, 22.2)),
112
+ "q75": np.clip(quant_vals[:, 7], *(2.2, 22.2)),
113
+ "q90": np.clip(quant_vals[:, 8], *(2.2, 22.2)),
114
+ })
115
+
116
+ return forecast_df
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: forecose
3
+ Version: 0.1.0
4
+ Summary: A time-series forecasting extension for pydexcom using Google's TimesFM
5
+ Author: Alexander Sadler
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: numpy
8
+ Requires-Dist: pandas
9
+ Requires-Dist: pydexcom
10
+ Requires-Dist: timesfm[torch]>=2.0.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ A time-series forecasting extension for [pydexcom](https://github.com/gagebenne/pydexcom) using Google's [TimesFM](https://github.com/google-research/timesfm). Used to predict immediate, short term blood glucose readings.
14
+
15
+ > All modelling and forecasting is performed locally on your device. The only external connections made are with:
16
+ > - Dexcom Share API: fetching CGM readings following the `pydexcom` approach.
17
+ > - HuggingFace: one-time download of the forecasting model weights on the first run.
18
+
19
+ ## Quick Start
20
+ 1. Ensure that you have installed the pydexcom package and [enabled the Share service](https://provider.dexcom.com/education-research/cgm-education-use/videos/setting-dexcom-share-and-follow) within your [Dexcom G7 / G6 / G5 / G4](https://www.dexcom.com/apps).
21
+
22
+ `pip install pydexcom`
23
+
24
+ 2. Initialise `pydexcom` with your Dexcom credentials (below shows the simplist route, refere to pydexcom for further instruction).
25
+
26
+ ```python
27
+ >>> from pydexcom import Dexcom
28
+ >>> dexcom = Dexcom(username="username", password="password")
29
+ ```
30
+
31
+ 3. Generate a prediction.
32
+
33
+ ```python
34
+ >>> from forecose import DexcomForecast
35
+ >>> forecaster = DexcomForecast.from_dexcom(
36
+ dexcom=dexcom, # pull recent readings from your active 'Dexcom' session
37
+ context_len=288, # uses prior day's readings as context
38
+ horizon=12 # predicts the next hour
39
+ )
40
+ >>> predictions = forecaster.forecast()
41
+ >>> print(predictions.head())
42
+ timestamp predicted_glucose q10 q25 q50 q75 q90
43
+ 0 2026-06-25 11:01:19.199000+01:00 8.243370 8.244899 8.125346 8.214749 8.266071 8.309934
44
+ 1 2026-06-25 11:06:19.199000+01:00 8.050682 8.073329 7.738788 8.018662 8.208736 8.303193
45
+ 2 2026-06-25 11:11:19.199000+01:00 7.897943 7.879324 7.332723 7.783697 8.082028 8.256586
46
+ 3 2026-06-25 11:16:19.199000+01:00 7.767045 7.738261 6.965607 7.621467 8.026337 8.236394
47
+ 4 2026-06-25 11:21:19.199000+01:00 7.615633 7.667328 6.668064 7.442780 7.972524 8.216294
48
+ ```
@@ -0,0 +1,5 @@
1
+ forecose/__init__.py,sha256=1sZANKnJHKphkWyyBaMovicQrkh6BiuRh1oNW8K3YoU,76
2
+ forecose/forecose.py,sha256=8hInzDuxa_jrWKnjNT00KdgLlC5tSDxJFNmZQijm1ss,3875
3
+ forecose-0.1.0.dist-info/METADATA,sha256=Vy8lOQdWpeHRYrsTkw5PfAwi2JRYJy6TwHXgOvNKfPE,2403
4
+ forecose-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
5
+ forecose-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any