|
1 | 1 | # Life-as-Code: Personal Health Analytics Portal |
2 | 2 |
|
3 | | -A private, multi-user, data-driven portal to track and analyze your health, fitness, and performance metrics from services like Garmin and Hevy. |
| 3 | +A comprehensive, private, multi-user health analytics platform that aggregates and analyzes your health, fitness, and performance data from multiple sources including Garmin Connect, Hevy, and Apple HealthKit. Transform your health data into actionable insights with advanced analytics, AI-powered daily briefings, and beautiful visualizations. |
| 4 | + |
| 5 | +## π― What Life-as-Code Does |
| 6 | + |
| 7 | +**π Data Integration** |
| 8 | +- **Garmin Connect**: Sleep, HRV, heart rate, weight, body composition, stress, and activity data |
| 9 | +- **Hevy**: Workout sets, exercises, weights, RPE, and training progression |
| 10 | +- **Apple HealthKit**: Energy expenditure, steps, HRV, and comprehensive health metrics |
| 11 | +- **Manual Import**: Support for CSV/XML data from wearables and health apps |
| 12 | + |
| 13 | +**π Advanced Analytics** |
| 14 | +- **Personal Dashboard**: Interactive charts and trends for all health metrics |
| 15 | +- **AI Daily Briefings**: LLM-generated insights and recommendations based on your data |
| 16 | +- **Correlation Analysis**: Discover relationships between sleep, training, recovery, and performance |
| 17 | +- **Health Scoring**: Personalized thresholds and status indicators for key metrics |
| 18 | + |
| 19 | +**π Privacy-First Architecture** |
| 20 | +- **Self-Hosted**: Complete control over your sensitive health data |
| 21 | +- **Multi-User**: Secure isolation between users with encrypted credential storage |
| 22 | +- **No Cloud Dependencies**: Your data never leaves your infrastructure |
4 | 23 |
|
5 | 24 | ## π Quick Start with Docker |
6 | 25 |
|
@@ -144,6 +163,258 @@ This project is for personal use. Please respect the terms of service for Garmin |
144 | 163 |
|
145 | 164 | --- |
146 | 165 |
|
| 166 | +# Database Schema |
| 167 | + |
| 168 | +Life-as-Code uses a PostgreSQL database with the following tables: |
| 169 | + |
| 170 | +## User |
| 171 | +**Table:** `users` |
| 172 | + |
| 173 | +**Description:** User accounts for multi-user support. |
| 174 | + |
| 175 | +**Columns:** |
| 176 | +- `id`: INTEGER (required) π |
| 177 | +- `username`: VARCHAR(80) (required) π β |
| 178 | +- `password_hash`: VARCHAR(200) (required) |
| 179 | +- `encryption_key_sealed`: TEXT |
| 180 | +- `created_at`: DATETIME |
| 181 | + |
| 182 | +**Relationships:** |
| 183 | +- `credentials` β UserCredentials |
| 184 | +- `settings` β UserSettings |
| 185 | +- `sleep_data` β Sleep |
| 186 | +- `weight_data` β Weight |
| 187 | +- `heart_rate_data` β HeartRate |
| 188 | +- `stress_data` β Stress |
| 189 | +- `hrv_data` β HRV |
| 190 | +- `energy_data` β Energy |
| 191 | +- `steps` β Steps |
| 192 | +- `workout_sets` β WorkoutSet |
| 193 | +- `data_syncs` β DataSync |
| 194 | + |
| 195 | +## UserCredentials |
| 196 | +**Table:** `user_credentials` |
| 197 | + |
| 198 | +**Description:** Encrypted user credentials for external APIs. |
| 199 | + |
| 200 | +**Columns:** |
| 201 | +- `id`: INTEGER (required) π |
| 202 | +- `user_id`: INTEGER (required) π β |
| 203 | +- `garmin_email`: VARCHAR(200) |
| 204 | +- `encrypted_garmin_password`: VARCHAR(500) |
| 205 | +- `encrypted_hevy_api_key`: VARCHAR(500) |
| 206 | +- `created_at`: DATETIME |
| 207 | +- `updated_at`: DATETIME |
| 208 | + |
| 209 | +**Relationships:** |
| 210 | +- `user` β User |
| 211 | + |
| 212 | +## UserSettings |
| 213 | +**Table:** `user_settings` |
| 214 | + |
| 215 | +**Description:** User-specific settings and thresholds for personalized analysis. |
| 216 | + |
| 217 | +**Columns:** |
| 218 | +- `id`: INTEGER (required) π |
| 219 | +- `user_id`: INTEGER (required) π β |
| 220 | +- `hrv_good_threshold`: INTEGER |
| 221 | +- `hrv_moderate_threshold`: INTEGER |
| 222 | +- `deep_sleep_good_threshold`: INTEGER |
| 223 | +- `deep_sleep_moderate_threshold`: INTEGER |
| 224 | +- `total_sleep_good_threshold`: FLOAT |
| 225 | +- `total_sleep_moderate_threshold`: FLOAT |
| 226 | +- `training_high_volume_threshold`: INTEGER |
| 227 | +- `created_at`: DATETIME |
| 228 | +- `updated_at`: DATETIME |
| 229 | + |
| 230 | +**Relationships:** |
| 231 | +- `user` β User |
| 232 | + |
| 233 | +## Sleep |
| 234 | +**Table:** `sleep` |
| 235 | + |
| 236 | +**Description:** Sleep data from Garmin Connect. |
| 237 | + |
| 238 | +**Columns:** |
| 239 | +- `id`: INTEGER (required) π |
| 240 | +- `user_id`: INTEGER (required) π π |
| 241 | +- `date`: DATE (required) π |
| 242 | +- `deep_minutes`: FLOAT |
| 243 | +- `light_minutes`: FLOAT |
| 244 | +- `rem_minutes`: FLOAT |
| 245 | +- `awake_minutes`: FLOAT |
| 246 | +- `total_sleep_minutes`: FLOAT |
| 247 | +- `sleep_score`: INTEGER |
| 248 | +- `created_at`: DATETIME |
| 249 | + |
| 250 | +**Relationships:** |
| 251 | +- `user` β User |
| 252 | + |
| 253 | +**Constraints:** |
| 254 | +- UniqueConstraint: `_user_sleep_date_uc` |
| 255 | + |
| 256 | +## HRV |
| 257 | +**Table:** `hrv` |
| 258 | + |
| 259 | +**Description:** Heart Rate Variability data from Garmin Connect and Apple Watch. |
| 260 | + |
| 261 | +**Columns:** |
| 262 | +- `id`: INTEGER (required) π |
| 263 | +- `user_id`: INTEGER (required) π π |
| 264 | +- `date`: DATE (required) π |
| 265 | +- `hrv_avg`: FLOAT |
| 266 | +- `hrv_status`: VARCHAR(50) |
| 267 | +- `created_at`: DATETIME |
| 268 | + |
| 269 | +**Relationships:** |
| 270 | +- `user` β User |
| 271 | + |
| 272 | +**Constraints:** |
| 273 | +- UniqueConstraint: `_user_hrv_date_uc` |
| 274 | + |
| 275 | +## Weight |
| 276 | +**Table:** `weight` |
| 277 | + |
| 278 | +**Description:** Weight and body composition data from Garmin Connect. |
| 279 | + |
| 280 | +**Columns:** |
| 281 | +- `id`: INTEGER (required) π |
| 282 | +- `user_id`: INTEGER (required) π π |
| 283 | +- `date`: DATE (required) π |
| 284 | +- `weight_kg`: FLOAT |
| 285 | +- `bmi`: FLOAT |
| 286 | +- `body_fat_pct`: FLOAT |
| 287 | +- `muscle_mass_kg`: FLOAT |
| 288 | +- `bone_mass_kg`: FLOAT |
| 289 | +- `water_pct`: FLOAT |
| 290 | +- `created_at`: DATETIME |
| 291 | + |
| 292 | +**Relationships:** |
| 293 | +- `user` β User |
| 294 | + |
| 295 | +## HeartRate |
| 296 | +**Table:** `heart_rate` |
| 297 | + |
| 298 | +**Description:** Heart rate data from Garmin Connect. |
| 299 | + |
| 300 | +**Columns:** |
| 301 | +- `id`: INTEGER (required) π |
| 302 | +- `user_id`: INTEGER (required) π π |
| 303 | +- `date`: DATE (required) π |
| 304 | +- `resting_hr`: INTEGER |
| 305 | +- `max_hr`: INTEGER |
| 306 | +- `avg_hr`: INTEGER |
| 307 | +- `created_at`: DATETIME |
| 308 | + |
| 309 | +**Relationships:** |
| 310 | +- `user` β User |
| 311 | + |
| 312 | +**Constraints:** |
| 313 | +- UniqueConstraint: `_user_heart_rate_date_uc` |
| 314 | + |
| 315 | +## Stress |
| 316 | +**Table:** `stress` |
| 317 | + |
| 318 | +**Description:** Daily stress data from Garmin. |
| 319 | + |
| 320 | +**Columns:** |
| 321 | +- `id`: INTEGER (required) π |
| 322 | +- `user_id`: INTEGER (required) π π |
| 323 | +- `date`: DATE (required) π |
| 324 | +- `avg_stress`: FLOAT |
| 325 | +- `max_stress`: FLOAT |
| 326 | +- `stress_level`: VARCHAR(20) |
| 327 | +- `rest_stress`: FLOAT |
| 328 | +- `activity_stress`: FLOAT |
| 329 | +- `created_at`: DATETIME |
| 330 | + |
| 331 | +**Relationships:** |
| 332 | +- `user` β User |
| 333 | + |
| 334 | +**Constraints:** |
| 335 | +- UniqueConstraint: `unique_user_stress_date` |
| 336 | + |
| 337 | +## Energy |
| 338 | +**Table:** `energy` |
| 339 | + |
| 340 | +**Description:** Daily energy expenditure data from Apple Watch and Garmin. |
| 341 | + |
| 342 | +**Columns:** |
| 343 | +- `id`: INTEGER (required) π |
| 344 | +- `user_id`: INTEGER (required) π π |
| 345 | +- `date`: DATE (required) π |
| 346 | +- `active_energy`: FLOAT |
| 347 | +- `basal_energy`: FLOAT |
| 348 | +- `created_at`: DATETIME |
| 349 | + |
| 350 | +**Relationships:** |
| 351 | +- `user` β User |
| 352 | + |
| 353 | +**Constraints:** |
| 354 | +- UniqueConstraint: `_user_date_energy_uc` |
| 355 | + |
| 356 | +## Steps |
| 357 | +**Table:** `steps` |
| 358 | + |
| 359 | +**Description:** Daily steps and distance data from Garmin Connect. |
| 360 | + |
| 361 | +**Columns:** |
| 362 | +- `id`: INTEGER (required) π |
| 363 | +- `user_id`: INTEGER (required) π π |
| 364 | +- `date`: DATE (required) π |
| 365 | +- `total_steps`: INTEGER |
| 366 | +- `total_distance`: FLOAT |
| 367 | +- `step_goal`: INTEGER |
| 368 | +- `created_at`: DATETIME |
| 369 | + |
| 370 | +**Relationships:** |
| 371 | +- `user` β User |
| 372 | + |
| 373 | +**Constraints:** |
| 374 | +- UniqueConstraint: `_user_date_steps_uc` |
| 375 | + |
| 376 | +## WorkoutSet |
| 377 | +**Table:** `workout_sets` |
| 378 | + |
| 379 | +**Description:** Individual workout sets from Hevy. |
| 380 | + |
| 381 | +**Columns:** |
| 382 | +- `id`: INTEGER (required) π |
| 383 | +- `user_id`: INTEGER (required) π π |
| 384 | +- `date`: DATE (required) π |
| 385 | +- `exercise`: VARCHAR(200) (required) |
| 386 | +- `weight_kg`: FLOAT |
| 387 | +- `reps`: INTEGER |
| 388 | +- `rpe`: FLOAT |
| 389 | +- `set_type`: VARCHAR(50) |
| 390 | +- `duration_seconds`: INTEGER |
| 391 | +- `distance_meters`: FLOAT |
| 392 | +- `created_at`: DATETIME |
| 393 | + |
| 394 | +**Relationships:** |
| 395 | +- `user` β User |
| 396 | + |
| 397 | +## DataSync |
| 398 | +**Table:** `data_sync` |
| 399 | + |
| 400 | +**Description:** Track when data was last synced from external sources. |
| 401 | + |
| 402 | +**Columns:** |
| 403 | +- `id`: INTEGER (required) π |
| 404 | +- `user_id`: INTEGER (required) π π |
| 405 | +- `source`: VARCHAR(50) (required) |
| 406 | +- `data_type`: VARCHAR(50) (required) |
| 407 | +- `last_sync_date`: DATE |
| 408 | +- `last_sync_timestamp`: DATETIME |
| 409 | +- `records_synced`: INTEGER |
| 410 | +- `status`: VARCHAR(20) |
| 411 | +- `error_message`: TEXT |
| 412 | + |
| 413 | +**Relationships:** |
| 414 | +- `user` β User |
| 415 | + |
| 416 | +--- |
| 417 | + |
147 | 418 | **Built with**: Python, Flask, Dash, PostgreSQL, Docker |
148 | 419 | **Supported APIs**: Garmin Connect, Hevy |
149 | 420 | **Architecture**: Multi-user, containerized, security-first |
0 commit comments