Files
2026-07-30 23:25:20 -04:00

177 lines
5.0 KiB
Python

"""
Health Data Provider - Abstract interface for health data sources.
This module provides an abstraction layer that allows switching between
different health data backends (Google Fit, Health Connect, etc.) without
changing the API layer.
"""
from abc import ABC, abstractmethod
from typing import List, Dict, Any, Optional
from datetime import datetime
from enum import Enum
class DataSource(str, Enum):
"""Enum for tracking data provenance."""
HEALTH_CONNECT = "health_connect"
MANUAL = "manual"
class HealthDataProvider(ABC):
"""
Abstract base class for health data providers.
Implementations must provide methods to fetch daily metrics,
workout sessions, and sleep data from their respective sources.
"""
@property
@abstractmethod
def source(self) -> DataSource:
"""Return the data source identifier."""
pass
@abstractmethod
async def fetch_daily_metrics(
self,
access_token: Optional[str],
days: int = 7,
timezone: str = "UTC",
user_id: Optional[int] = None
) -> List[Dict[str, Any]]:
"""
Fetch aggregated daily health metrics.
Args:
access_token: OAuth token or API key
days: Number of days to fetch
timezone: User's timezone for day boundaries
user_id: Optional user ID for logging
Returns:
List of daily metric dictionaries with keys:
- date: date object
- step_count: int
- calories_burned: float
- distance_meters: float
- sleep_duration_minutes: int
- deep_sleep_minutes: int
- light_sleep_minutes: int
- rem_sleep_minutes: int
- awake_duration_minutes: int
- avg_heart_rate: int
- min_heart_rate: int
- max_heart_rate: int
- avg_spo2: float
- pai_score: float
"""
pass
@abstractmethod
async def fetch_workouts(
self,
access_token: Optional[str],
days: int = 30
) -> List[Dict[str, Any]]:
"""
Fetch workout sessions.
Args:
access_token: OAuth token or API key
days: Number of days to fetch
Returns:
List of workout dictionaries with keys:
- start_time: datetime
- end_time: datetime
- activity_type: str (human readable)
- activity_type_id: int (source-specific ID)
- duration_seconds: int
- distance_meters: float
- calories: float
- avg_hr: int
- max_hr: int
- min_hr: int
"""
pass
@abstractmethod
async def fetch_sleep_sessions(
self,
access_token: Optional[str],
days: int = 30
) -> List[Dict[str, Any]]:
"""
Fetch sleep sessions for total duration validation.
Args:
access_token: OAuth token or API key
days: Number of days to fetch
Returns:
List of sleep session dictionaries with keys:
- date: date object (wake-up date)
- duration_minutes: int
- start_time: datetime
- end_time: datetime
- type: str
"""
pass
@abstractmethod
def get_auth_url(self) -> str:
"""
Get the OAuth authorization URL for this provider.
Returns:
Authorization URL string
"""
pass
class HealthProviderFactory:
"""
Factory for creating health data provider instances.
Uses the HEALTH_PROVIDER environment variable to determine
which provider to instantiate.
"""
_providers: Dict[str, type] = {}
@classmethod
def register(cls, name: str, provider_class: type):
"""Register a provider class with a name."""
cls._providers[name] = provider_class
@classmethod
def get_provider(cls, name: Optional[str] = None) -> HealthDataProvider:
"""
Get a provider instance by name.
Args:
name: Provider name. If None, uses HEALTH_PROVIDER env var.
Returns:
HealthDataProvider instance
Raises:
ValueError: If provider name is not registered
"""
from backend.core.config import settings
provider_name = name or getattr(settings, 'HEALTH_PROVIDER', 'health_connect')
if provider_name not in cls._providers:
available = list(cls._providers.keys())
raise ValueError(
f"Unknown provider '{provider_name}'. Available: {available}"
)
return cls._providers[provider_name]()
@classmethod
def list_providers(cls) -> List[str]:
"""List all registered provider names."""
return list(cls._providers.keys())