Files
HabitForge/backend/integrations/health_connect_provider.py
T
2026-07-30 23:25:20 -04:00

163 lines
6.1 KiB
Python

"""
Health Connect Provider - Implementation for Health Connect API.
This module provides the Health Connect implementation that reads data
pushed from the Android companion app via the /api/health-connect/sync endpoint.
"""
import datetime
from typing import Dict, Any, List, Optional
from backend.integrations.health_provider import (
HealthDataProvider,
DataSource,
HealthProviderFactory
)
class HealthConnectProvider(HealthDataProvider):
"""
Health Connect implementation of HealthDataProvider.
This provider reads data that has been pushed from an Android companion app
via the /api/health-connect/sync endpoint and stored in the database.
"""
@property
def source(self) -> DataSource:
return DataSource.HEALTH_CONNECT
def get_auth_url(self) -> str:
"""
Health Connect doesn't use OAuth - permissions are granted on-device.
Returns a URL to download the Android companion app.
"""
# TODO: Replace with actual Play Store URL when app is published
return "https://play.google.com/store/apps/details?id=com.habitforge.companion"
async def fetch_daily_metrics(
self,
access_token: Optional[str], # Not used for Health Connect, but required by interface
days: int = 7,
timezone: str = "UTC",
user_id: Optional[int] = None
) -> List[Dict[str, Any]]:
"""
Fetch daily metrics from Health Connect data store.
Reads data that was pushed from the Android companion app.
"""
if not user_id:
print("[HealthConnect] ERROR: user_id is required")
return []
print(f"[HealthConnect] Fetching daily metrics for user {user_id}, last {days} days")
from backend.database import SessionLocal
from backend.models.health_connect import HealthConnectDailyMetrics
from datetime import timedelta, date as date_type
db = SessionLocal()
try:
start_date = date_type.today() - timedelta(days=days)
metrics = db.query(HealthConnectDailyMetrics).filter(
HealthConnectDailyMetrics.user_id == user_id,
HealthConnectDailyMetrics.date >= start_date
).order_by(HealthConnectDailyMetrics.date.desc()).all()
if not metrics:
print(f"[HealthConnect] No data found. Ensure Android app has synced data.")
return []
print(f"[HealthConnect] Found {len(metrics)} days of data")
# Convert to standard format
return [
{
"date": m.date,
"step_count": m.step_count or 0,
"calories_burned": m.calories_burned or 0.0,
"distance_meters": m.distance_meters or 0.0,
"sleep_duration_minutes": m.sleep_duration_minutes or 0,
"deep_sleep_minutes": m.deep_sleep_minutes or 0,
"light_sleep_minutes": m.light_sleep_minutes or 0,
"rem_sleep_minutes": m.rem_sleep_minutes or 0,
"awake_duration_minutes": m.awake_duration_minutes or 0,
"avg_heart_rate": m.avg_heart_rate or 0,
"min_heart_rate": m.min_heart_rate or 0,
"max_heart_rate": m.max_heart_rate or 0,
"avg_spo2": m.avg_spo2 or 0.0,
"pai_score": 0.0, # Calculate if needed
"weight": m.weight,
"resting_heart_rate": m.resting_heart_rate,
"hrv": None
}
for m in metrics
]
finally:
db.close()
async def fetch_workouts(
self,
access_token: Optional[str],
days: int = 30
) -> List[Dict[str, Any]]:
"""
Fetch exercise sessions from Health Connect data store.
"""
# Note: We need user_id but the interface doesn't pass it for workouts
# This is a limitation we'll need to address in the interface
print(f"[HealthConnect] fetch_workouts called")
from backend.database import SessionLocal
from backend.models.health_connect import HealthConnectExerciseSession
from datetime import timedelta
db = SessionLocal()
try:
start_date = datetime.datetime.now() - timedelta(days=days)
# Query all sessions (we can't filter by user without user_id)
# In practice, this would be called with user context
sessions = db.query(HealthConnectExerciseSession).filter(
HealthConnectExerciseSession.start_time >= start_date
).order_by(HealthConnectExerciseSession.start_time.desc()).all()
return [
{
"start_time": s.start_time,
"end_time": s.end_time,
"activity_type": s.exercise_type_name,
"activity_type_id": s.exercise_type,
"duration_seconds": s.duration_seconds,
"distance_meters": s.distance_meters or 0.0,
"calories": s.calories_burned or 0.0,
"avg_hr": s.avg_heart_rate,
"max_hr": s.max_heart_rate,
"min_hr": s.min_heart_rate
}
for s in sessions
]
finally:
db.close()
async def fetch_sleep_sessions(
self,
access_token: Optional[str],
days: int = 30
) -> List[Dict[str, Any]]:
"""
Fetch sleep sessions from Health Connect data store.
Note: Health Connect stores sleep as part of daily metrics.
This returns an empty list as sleep is handled in fetch_daily_metrics.
"""
print(f"[HealthConnect] fetch_sleep_sessions called")
# Sleep data is included in daily metrics for Health Connect
return []
# Register the provider with the factory
HealthProviderFactory.register("health_connect", HealthConnectProvider)