# Feature Specification: Fitbit/Garmin Data Sync **Feature Branch**: `002-fitbit-garmin-sync` **Created**: 2025-12-24 **Status**: Implemented ## User Scenarios & Testing *(mandatory)* ### User Story 1 - Sync Activities from Garmin (Priority: P1) As a user, I want to trigger a synchronization of my recent activities from my Garmin account so that my fitness data is stored and available within the application. **Why this priority**: This is a core feature, enabling users to bring their primary workout data into the system. **Independent Test**: This can be tested by a user triggering a sync and verifying their recent activities appear in their activity list. **Acceptance Scenarios**: 1. **Given** I have connected my Garmin account, **When** I trigger an activity sync, **Then** my activities from the last 30 days appear in my activity list. 2. **Given** my Garmin account is not connected, **When** I attempt to trigger a sync, **Then** I see a message instructing me to connect my account first. --- ### User Story 2 - View and Query Activities (Priority: P2) As a user, I want to list my synced activities, filter them by criteria like activity type and date, and download the original activity file for my records. **Why this priority**: Allows users to find, analyze, and export their workout data. **Independent Test**: A user can navigate to the activity list, apply filters, and attempt to download a file. **Acceptance Scenarios**: 1. **Given** I have synced activities, **When** I view the activity list, **Then** I see a paginated list of my activities, sorted by most recent first. 2. **Given** I have a list of activities, **When** I filter by "running" and a specific date range, **Then** I only see running activities within that range. 3. **Given** an activity has its original file available, **When** I click "Download", **Then** the file is downloaded to my device. --- ### User Story 3 - Sync Health Metrics from Garmin (Priority: P1) As a user, I want to trigger a synchronization of my daily health metrics (like steps and HRV) from my Garmin account to track my wellness over time. **Why this priority**: Provides users with a holistic view of their health beyond just workouts. **Independent Test**: A user can trigger a metric sync and see updated data in their health dashboard or metric query views. **Acceptance Scenarios**: 1. **Given** I have connected my Garmin account, **When** I trigger a health metric sync, **Then** my metrics from the last 30 days are updated. 2. **Given** data is synced, **When** I view my dashboard, **Then** I see my latest step count and HRV data. --- ### User Story 4 - View and Query Health Metrics (Priority: P2) As a user, I want to see a list of all the metric types I've synced, query my data for specific time ranges, and view a high-level summary. **Why this priority**: Enables users to analyze trends and understand their health data. **Independent Test**: A user can navigate to the metrics section, select a metric type and date range, and view the resulting data and summary. **Acceptance Scenarios**: 1. **Given** I have synced health metrics, **When** I visit the metrics page, **Then** I see a list of all available metric types (e.g., "Steps", "HRV"). 2. **Given** I have synced step data, **When** I query for "Steps" in the last week, **Then** I see a chart or list of my daily step counts for that period. 3. **Given** I have synced data, **When** I view the health summary for the last month, **Then** I see accurate totals and averages for my key metrics. ### Edge Cases - What happens if the Garmin service is unavailable during a sync? - How does the system handle a user revoking access from the Garmin side? - What should be displayed for a day with no data for a specific metric? - How does the system handle a sync that is interrupted mid-way? ## Requirements *(mandatory)* ### UI/UX Improvements - **UI-001**: The dashboard (`/`) MUST display current sync statistics (total activities, downloaded activities) and recent sync logs in a user-friendly table. - **UI-002**: All user interactions (e.g., triggering syncs, fetching data) MUST provide feedback via non-blocking toast notifications instead of disruptive `alert()` popups. - **UI-003**: The dashboard MUST provide clear buttons to trigger activity sync, health metrics sync, and navigate to setup/configuration and API documentation. - **UI-004**: The dashboard MUST clearly display a link to the `/setup` page for authentication. ### Authentication and Setup - **AS-001**: The system MUST provide an endpoint (`/setup/auth-status`) to check the current authentication status for Garmin. (Fitbit status is a placeholder). - **AS-002**: Users MUST be able to initiate a connection to their Garmin account by providing their username and password via a `POST` to `/setup/garmin`. The system automatically configures the correct Garmin domain (`garmin.com` or `garmin.cn`) based on the `is_china` flag. - **AS-003**: The system MUST handle Garmin's Multi-Factor Authentication (MFA) process. If MFA is required, the `/setup/garmin` endpoint will return `mfa_required` status, and the user must complete the process by sending the verification code to `/setup/garmin/mfa`. - **AS-004**: The system MUST securely store the resulting `garth` authentication tokens (OAuth1 and OAuth2) and serializable MFA state in the database for future use, associated with the 'garmin' token type. - **AS-005**: The system MUST provide an endpoint (`/setup/garmin/test-token`) to validate the stored Garmin authentication tokens by attempting to fetch user profile information via `garth.UserProfile.get()`. ### Functional Requirements - **FR-001**: Users MUST be able to trigger a manual synchronization of their Garmin activities via a `POST` request to the `/api/sync/activities` endpoint. This endpoint accepts a `days_back` parameter to specify the sync range. - **FR-002**: The system MUST fetch activity metadata from Garmin using `garth.client.connectapi("/activitylist-service/activities/search/activities", ...)` and store users' Garmin activities in the database, including `garmin_activity_id`, `activity_name`, `activity_type` (extracted from `activityType.typeKey`), `start_time`, `duration`, `file_type`, `download_status`, `downloaded_at`, and the binary `file_content`. - **FR-003**: When downloading activity files, the system MUST attempt to download in preferred formats (`original`, `tcx`, `gpx`, `fit`) sequentially until a successful download is achieved. The `garth.client.download()` method MUST be used with a dynamically constructed path like `/download-service/export/{file_type}/activity/{activity_id}`. - **FR-004**: Users MUST be able to view a paginated list of their synchronized activities via a `GET` request to the `/api/activities/list` endpoint. - **FR-005**: Users MUST be able to filter their activities by `activity_type`, `start_date`, `end_date`, and `download_status` via a `GET` request to the `/api/activities/query` endpoint. - **FR-006**: Users MUST be able to download the original data file for an individual activity via a `GET` request to the `/api/activities/download/{activity_id}` endpoint. - **FR-007**: Users MUST be able to trigger a manual synchronization of their Garmin health metrics via a `POST` request to the `/api/sync/metrics` endpoint. This endpoint accepts a `days_back` parameter. - **FR-008**: The system MUST fetch daily health metrics (steps, HRV, sleep) using `garth.stats.steps.DailySteps.list()`, `garth.stats.hrv.DailyHRV.list()`, and `garth.data.sleep.SleepData.list()` respectively. - **FR-009**: The system MUST store users' Garmin health metrics in the database, including `metric_type` ('steps', 'hrv', 'sleep'), `metric_value`, `unit`, `timestamp`, `date`, and `source` ('garmin'). - **FR-010**: Users MUST be able to see which types of health metrics are available for querying via a `GET` request to the `/api/metrics/list` endpoint. - **FR-011**: Users MUST be able to query their health metrics by `metric_type`, `start_date`, and `end_date` via a `GET` request to the `/api/metrics/query` endpoint. - **FR-012**: Users MUST be able to view a summary of their health data (e.g., totals, averages) over a specified time period via a `GET` request to the `/api/health-data/summary` endpoint. - **FR-013**: The system MUST provide clear feedback on the status of a synchronization. This includes a summary of sync status available via the `/api/status` endpoint and a detailed list of synchronization logs available via the `/api/logs` endpoint. - **FR-014**: The system MUST handle synchronization failures gracefully and log errors for troubleshooting. - **FR-015**: Users MUST be able to trigger a manual synchronization of their weight data via a `POST` request to the `/api/sync/weight` endpoint. (Note: Actual implementation for weight sync is currently a placeholder). ### Key Entities - **Activity**: Represents a single fitness activity. - `id`: Unique identifier in the database. - `garmin_activity_id`: The ID from Garmin. - `activity_name`: User-defined name of the activity. - `activity_type`: Type of activity (e.g., 'running', 'cycling'), extracted from `activityType.typeKey`. - `start_time`: The start time of the activity. - `duration`: Duration of the activity in seconds. - `file_type`: The format of the downloaded file ('tcx', 'gpx', 'fit', or 'original'). - `file_content`: The binary content of the activity file. - `download_status`: The status of the file download from Garmin ('pending', 'downloaded', 'failed'). - `downloaded_at`: Timestamp when the file was downloaded. - **Health Metric**: Represents a single health data point for a specific day. - `id`: Unique identifier in the database. - `metric_type`: The type of metric (e.g., 'steps', 'hrv', 'sleep'). - `metric_value`: The value of the metric. - `unit`: The unit of measurement. - `timestamp`: The precise timestamp of the metric. - `date`: The date the metric was recorded. - `source`: The source of the data (e.g., 'garmin'). - **Sync Log**: Records the outcome of each synchronization attempt. - `id`: Unique identifier in the database. - `operation`: The type of sync operation ('activity_sync', 'health_metric_sync', 'weight_sync'). - `status`: The outcome ('started', 'completed', 'completed_with_errors', 'failed'). - `message`: A descriptive message about the outcome. - `start_time`: When the sync began. - `end_time`: When the sync completed. - `records_processed`: Number of records successfully processed. - `records_failed`: Number of records that failed. - **APIToken**: Stores authentication tokens for third-party services. - `id`: Unique identifier in the database. - `token_type`: The service the token is for ('garmin', 'fitbit'). - `garth_oauth1_token`: The OAuth1 token for Garmin (JSON string). - `garth_oauth2_token`: The OAuth2 token for Garmin (JSON string). - `mfa_state`: Stores serializable MFA state (JSON string) if MFA is required during login. - `mfa_expires_at`: Timestamp indicating when the MFA state expires. ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001**: 95% of users can successfully sync their last 30 days of activities from Garmin on the first attempt. - **SC-002**: 100% of successfully synced activities appear in the user's activity list within 1 minute of sync completion. - **SC-003**: 99% of activity list filtering operations return accurate results in under 2 seconds. - **SC-004**: Users can successfully download the original data file for any activity where one is present. - **SC-005**: 95% of users can successfully sync their last 30 days of health metrics from Garmin, including steps and HRV. - **SC-006**: The health data summary for a 30-day period is calculated and displayed in under 3 seconds. - **SC-007**: The system provides a user-friendly error message within 10 seconds if a Garmin sync fails due to authentication or service availability issues.