163 lines
6.1 KiB
Python
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)
|