Coverage for health / models / daily_metrics.py: 100%
112 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-02 17:44 +0800
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-02 17:44 +0800
1"""
2Pydantic models for daily health metrics.
4Includes models for steps, heart rate, sleep, stress, body battery, and more.
5"""
7from datetime import date as date_type, time as time_type
8from typing import Optional, List, Dict, Any
9from pydantic import BaseModel, Field
12class StepsData(BaseModel):
13 """Daily step count and related metrics."""
15 date: date_type = Field(..., description="Date of the data")
16 total_steps: Optional[int] = Field(default=None, description="Total steps taken")
17 total_distance_meters: Optional[float] = Field(
18 default=None, description="Total distance in meters"
19 )
20 calories_burned: Optional[int] = Field(
21 default=None, description="Calories burned from steps"
22 )
23 step_goal: Optional[int] = Field(default=None, description="Daily step goal")
24 raw_data: Optional[Dict[str, Any]] = Field(
25 default=None, description="Raw API response"
26 )
29class HeartRateData(BaseModel):
30 """Daily heart rate metrics."""
32 date: date_type = Field(..., description="Date of the data")
33 resting_heart_rate: Optional[int] = Field(
34 default=None, description="Resting heart rate in bpm"
35 )
36 min_heart_rate: Optional[int] = Field(
37 default=None, description="Minimum heart rate in bpm"
38 )
39 max_heart_rate: Optional[int] = Field(
40 default=None, description="Maximum heart rate in bpm"
41 )
42 average_heart_rate: Optional[int] = Field(
43 default=None, description="Average heart rate in bpm"
44 )
45 heart_rate_zones: Optional[Dict[str, int]] = Field(
46 default=None, description="Time in each heart rate zone (minutes)"
47 )
48 raw_data: Optional[Dict[str, Any]] = Field(
49 default=None, description="Raw API response"
50 )
53class SleepData(BaseModel):
54 """Daily sleep metrics."""
56 date: date_type = Field(..., description="Date of the sleep (night)")
57 total_sleep_seconds: Optional[int] = Field(
58 default=None, description="Total sleep time in seconds"
59 )
60 deep_sleep_seconds: Optional[int] = Field(
61 default=None, description="Deep sleep time in seconds"
62 )
63 light_sleep_seconds: Optional[int] = Field(
64 default=None, description="Light sleep time in seconds"
65 )
66 rem_sleep_seconds: Optional[int] = Field(
67 default=None, description="REM sleep time in seconds"
68 )
69 awake_seconds: Optional[int] = Field(
70 default=None, description="Time awake in seconds"
71 )
72 sleep_score: Optional[int] = Field(
73 default=None, description="Overall sleep quality score (0-100)"
74 )
75 sleep_start_time: Optional[time_type] = Field(
76 default=None, description="Sleep start time"
77 )
78 sleep_end_time: Optional[time_type] = Field(default=None, description="Sleep end time")
79 sleep_levels: Optional[List[Dict[str, Any]]] = Field(
80 default=None, description="Detailed sleep level timeline"
81 )
82 raw_data: Optional[Dict[str, Any]] = Field(
83 default=None, description="Raw API response"
84 )
87class StressData(BaseModel):
88 """Daily stress level metrics."""
90 date: date_type = Field(..., description="Date of the data")
91 average_stress_level: Optional[int] = Field(
92 default=None, description="Average stress level (0-100)"
93 )
94 max_stress_level: Optional[int] = Field(
95 default=None, description="Maximum stress level (0-100)"
96 )
97 rest_stress_duration_seconds: Optional[int] = Field(
98 default=None, description="Time in rest/low stress state"
99 )
100 activity_stress_duration_seconds: Optional[int] = Field(
101 default=None, description="Time in activity stress state"
102 )
103 low_stress_duration_seconds: Optional[int] = Field(
104 default=None, description="Time in low stress state"
105 )
106 medium_stress_duration_seconds: Optional[int] = Field(
107 default=None, description="Time in medium stress state"
108 )
109 high_stress_duration_seconds: Optional[int] = Field(
110 default=None, description="Time in high stress state"
111 )
112 stress_timeline: Optional[List[Any]] = Field(
113 default=None, description="Detailed stress timeline (list of [timestamp, value] pairs)"
114 )
115 raw_data: Optional[Dict[str, Any]] = Field(
116 default=None, description="Raw API response"
117 )
120class BodyBatteryData(BaseModel):
121 """Daily Body Battery metrics."""
123 date: date_type = Field(..., description="Date of the data")
124 charged: Optional[int] = Field(
125 default=None, description="Total Body Battery charged"
126 )
127 drained: Optional[int] = Field(
128 default=None, description="Total Body Battery drained"
129 )
130 highest_value: Optional[int] = Field(
131 default=None, description="Highest Body Battery value (0-100)"
132 )
133 lowest_value: Optional[int] = Field(
134 default=None, description="Lowest Body Battery value (0-100)"
135 )
136 most_recent_value: Optional[int] = Field(
137 default=None, description="Most recent Body Battery value"
138 )
139 timeline: Optional[List[Any]] = Field(
140 default=None, description="Detailed Body Battery timeline (list of [timestamp, value] pairs)"
141 )
142 raw_data: Optional[Dict[str, Any]] = Field(
143 default=None, description="Raw API response"
144 )
147class SpO2Data(BaseModel):
148 """Daily blood oxygen saturation metrics."""
150 date: date_type = Field(..., description="Date of the data")
151 average_spo2: Optional[float] = Field(
152 default=None, description="Average SpO2 percentage"
153 )
154 min_spo2: Optional[float] = Field(
155 default=None, description="Minimum SpO2 percentage"
156 )
157 max_spo2: Optional[float] = Field(
158 default=None, description="Maximum SpO2 percentage"
159 )
160 readings: Optional[List[Dict[str, Any]]] = Field(
161 default=None, description="Individual SpO2 readings"
162 )
163 raw_data: Optional[Dict[str, Any]] = Field(
164 default=None, description="Raw API response"
165 )
168class RespirationData(BaseModel):
169 """Daily respiration rate metrics."""
171 date: date_type = Field(..., description="Date of the data")
172 average_respiration_rate: Optional[float] = Field(
173 default=None, description="Average breaths per minute"
174 )
175 min_respiration_rate: Optional[float] = Field(
176 default=None, description="Minimum breaths per minute"
177 )
178 max_respiration_rate: Optional[float] = Field(
179 default=None, description="Maximum breaths per minute"
180 )
181 raw_data: Optional[Dict[str, Any]] = Field(
182 default=None, description="Raw API response"
183 )
186class HydrationData(BaseModel):
187 """Daily hydration tracking."""
189 date: date_type = Field(..., description="Date of the data")
190 total_intake_ml: Optional[int] = Field(
191 default=None, description="Total water intake in milliliters"
192 )
193 goal_ml: Optional[int] = Field(default=None, description="Daily hydration goal")
194 raw_data: Optional[Dict[str, Any]] = Field(
195 default=None, description="Raw API response"
196 )
199class FloorsData(BaseModel):
200 """Daily floors climbed."""
202 date: date_type = Field(..., description="Date of the data")
203 floors_climbed: Optional[int] = Field(
204 default=None, description="Number of floors climbed"
205 )
206 floors_descended: Optional[int] = Field(
207 default=None, description="Number of floors descended"
208 )
209 floor_goal: Optional[int] = Field(default=None, description="Daily floor goal")
210 raw_data: Optional[Dict[str, Any]] = Field(
211 default=None, description="Raw API response"
212 )
215class IntensityMinutesData(BaseModel):
216 """Daily intensity minutes."""
218 date: date_type = Field(..., description="Date of the data")
219 moderate_minutes: Optional[int] = Field(
220 default=None, description="Moderate intensity minutes"
221 )
222 vigorous_minutes: Optional[int] = Field(
223 default=None, description="Vigorous intensity minutes"
224 )
225 total_minutes: Optional[int] = Field(
226 default=None, description="Total intensity minutes"
227 )
228 weekly_goal: Optional[int] = Field(
229 default=None, description="Weekly intensity goal"
230 )
231 raw_data: Optional[Dict[str, Any]] = Field(
232 default=None, description="Raw API response"
233 )
236class HRVData(BaseModel):
237 """Daily heart rate variability metrics."""
239 date: date_type = Field(..., description="Date of the data")
240 hrv_value: Optional[float] = Field(
241 default=None, description="HRV value in milliseconds"
242 )
243 baseline_hrv: Optional[float] = Field(
244 default=None, description="Baseline HRV for comparison"
245 )
246 status: Optional[str] = Field(
247 default=None, description="HRV status (balanced, unbalanced, etc.)"
248 )
249 raw_data: Optional[Dict[str, Any]] = Field(
250 default=None, description="Raw API response"
251 )
254class RHRData(BaseModel):
255 """Daily resting heart rate."""
257 date: date_type = Field(..., description="Date of the data")
258 resting_heart_rate: Optional[int] = Field(
259 default=None, description="Resting heart rate in bpm"
260 )
261 raw_data: Optional[Dict[str, Any]] = Field(
262 default=None, description="Raw API response"
263 )
266class LifestyleLoggingData(BaseModel):
267 """Daily lifestyle logging from Garmin Lifestyle Logging feature.
269 Tracks behaviors the user logs in the Garmin Connect app:
270 alcohol, caffeine, meal quality/timing, light exercise, and intermittent fasting.
271 Boolean fields (NONE type) are True when the user checked that behavior.
272 Integer fields (QUANTITY type) reflect the number of drinks/cups logged per subtype.
273 """
275 date: date_type = Field(..., description="Date of the data")
277 # Alcohol (QUANTITY)
278 alcohol_logged: bool = Field(default=False, description="Any alcohol logged")
279 alcohol_beer: int = Field(default=0, description="Number of beers logged")
280 alcohol_wine: int = Field(default=0, description="Number of glasses of wine logged")
281 alcohol_spirit: int = Field(default=0, description="Number of spirit drinks logged")
282 alcohol_other: int = Field(default=0, description="Number of other alcohol drinks logged")
284 # Morning Caffeine (QUANTITY)
285 morning_caffeine_logged: bool = Field(default=False, description="Morning caffeine logged")
286 morning_caffeine_coffee: int = Field(default=0, description="Morning coffees")
287 morning_caffeine_tea: int = Field(default=0, description="Morning teas")
288 morning_caffeine_other: int = Field(default=0, description="Other morning caffeine drinks")
290 # Late Caffeine (QUANTITY) — important for sleep quality correlation
291 late_caffeine_logged: bool = Field(default=False, description="Late caffeine logged")
292 late_caffeine_coffee: int = Field(default=0, description="Late-day coffees")
293 late_caffeine_tea: int = Field(default=0, description="Late-day teas")
294 late_caffeine_other: int = Field(default=0, description="Other late-day caffeine drinks")
296 # Lifestyle behaviors (NONE type — boolean checked/unchecked)
297 light_exercise: bool = Field(default=False, description="Light exercise done")
298 healthy_meals: bool = Field(default=False, description="Healthy meals eaten")
299 heavy_meals: bool = Field(default=False, description="Heavy meals eaten")
300 late_meals: bool = Field(default=False, description="Late meals eaten")
301 intermittent_fasting: bool = Field(default=False, description="Intermittent fasting done")
303 raw_data: Optional[Dict[str, Any]] = Field(
304 default=None, description="Raw API response"
305 )