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

1""" 

2Pydantic models for daily health metrics. 

3 

4Includes models for steps, heart rate, sleep, stress, body battery, and more. 

5""" 

6 

7from datetime import date as date_type, time as time_type 

8from typing import Optional, List, Dict, Any 

9from pydantic import BaseModel, Field 

10 

11 

12class StepsData(BaseModel): 

13 """Daily step count and related metrics.""" 

14 

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 ) 

27 

28 

29class HeartRateData(BaseModel): 

30 """Daily heart rate metrics.""" 

31 

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 ) 

51 

52 

53class SleepData(BaseModel): 

54 """Daily sleep metrics.""" 

55 

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 ) 

85 

86 

87class StressData(BaseModel): 

88 """Daily stress level metrics.""" 

89 

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 ) 

118 

119 

120class BodyBatteryData(BaseModel): 

121 """Daily Body Battery metrics.""" 

122 

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 ) 

145 

146 

147class SpO2Data(BaseModel): 

148 """Daily blood oxygen saturation metrics.""" 

149 

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 ) 

166 

167 

168class RespirationData(BaseModel): 

169 """Daily respiration rate metrics.""" 

170 

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 ) 

184 

185 

186class HydrationData(BaseModel): 

187 """Daily hydration tracking.""" 

188 

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 ) 

197 

198 

199class FloorsData(BaseModel): 

200 """Daily floors climbed.""" 

201 

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 ) 

213 

214 

215class IntensityMinutesData(BaseModel): 

216 """Daily intensity minutes.""" 

217 

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 ) 

234 

235 

236class HRVData(BaseModel): 

237 """Daily heart rate variability metrics.""" 

238 

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 ) 

252 

253 

254class RHRData(BaseModel): 

255 """Daily resting heart rate.""" 

256 

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 ) 

264 

265 

266class LifestyleLoggingData(BaseModel): 

267 """Daily lifestyle logging from Garmin Lifestyle Logging feature. 

268 

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 """ 

274 

275 date: date_type = Field(..., description="Date of the data") 

276 

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") 

283 

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") 

289 

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") 

295 

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") 

302 

303 raw_data: Optional[Dict[str, Any]] = Field( 

304 default=None, description="Raw API response" 

305 )