before claude fix #1

This commit is contained in:
2025-12-23 06:09:34 -08:00
parent c505fb69a6
commit a23fa1b30d
83 changed files with 5682 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: Fitbit-Garmin Local Sync
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: December 22, 2025
**Feature**: [Link to spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- All validation items have been checked and the specification is ready for planning.

View File

@@ -0,0 +1,461 @@
openapi: 3.0.0
info:
title: Fitbit-Garmin Sync API
description: API for synchronizing health and fitness data between Fitbit and Garmin Connect platforms
version: 1.0.0
servers:
- url: http://localhost:8000
description: Development server
paths:
/api/status:
get:
summary: Get current sync status
description: Provides JSON data for the status dashboard including sync counts and recent logs
responses:
'200':
description: Current sync status
content:
application/json:
schema:
type: object
properties:
total_weight_records:
type: integer
description: Total number of weight records
synced_weight_records:
type: integer
description: Number of synced weight records
unsynced_weight_records:
type: integer
description: Number of unsynced weight records
total_activities:
type: integer
description: Total number of activities
downloaded_activities:
type: integer
description: Number of downloaded activities
recent_logs:
type: array
items:
$ref: '#/components/schemas/SyncLog'
/api/logs:
get:
summary: Get sync logs
description: Provides JSON data for the sync logs table
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
description: Number of log entries to return
- name: offset
in: query
schema:
type: integer
default: 0
description: Offset for pagination
responses:
'200':
description: Array of sync log entries
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/SyncLog'
/api/sync/weight:
post:
summary: Trigger weight sync
description: Starts the process of syncing weight data from Fitbit to Garmin
responses:
'200':
description: Weight sync initiated successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "started"
message:
type: string
example: "Weight sync process started"
job_id:
type: string
example: "weight-sync-12345"
/api/sync/activities:
post:
summary: Trigger activity sync
description: Starts the process of archiving activities from Garmin to local storage
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
days_back:
type: integer
description: Number of days to look back for activities
example: 30
responses:
'200':
description: Activity sync initiated successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "started"
message:
type: string
example: "Activity sync process started"
job_id:
type: string
example: "activity-sync-12345"
/api/setup/garmin:
post:
summary: Save Garmin credentials
description: Saves Garmin credentials from the setup form
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
username:
type: string
description: Garmin Connect username
password:
type: string
description: Garmin Connect password
responses:
'200':
description: Garmin credentials saved successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "success"
message:
type: string
example: "Garmin credentials saved"
/api/setup/fitbit:
post:
summary: Save Fitbit credentials
description: Saves Fitbit credentials and returns the auth URL
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
client_id:
type: string
description: Fitbit API client ID
client_secret:
type: string
description: Fitbit API client secret
responses:
'200':
description: Fitbit credentials saved and auth URL returned
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "success"
auth_url:
type: string
example: "https://www.fitbit.com/oauth2/authorize?..."
message:
type: string
example: "Fitbit credentials saved, please visit auth_url to authorize"
/api/setup/fitbit/callback:
post:
summary: Complete Fitbit OAuth flow
description: Completes the Fitbit OAuth flow with the callback URL
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
callback_url:
type: string
description: Full callback URL from the browser after authorizing the Fitbit app
responses:
'200':
description: Fitbit OAuth flow completed successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: "success"
message:
type: string
example: "Fitbit OAuth flow completed successfully"
/api/metrics/list:
get:
summary: List available metric types
description: Returns a list of available metric types and date ranges
responses:
'200':
description: List of available metric types
content:
application/json:
schema:
type: object
properties:
metric_types:
type: array
items:
type: string
example: ["steps", "heart_rate", "sleep", "calories"]
date_range:
type: object
properties:
start_date:
type: string
format: date
end_date:
type: string
format: date
/api/metrics/query:
get:
summary: Query health metrics
description: Allows filtering and retrieval of specific metrics by date range, type, or other criteria
parameters:
- name: metric_type
in: query
schema:
type: string
description: Type of metric to retrieve
- name: start_date
in: query
schema:
type: string
format: date
description: Start date for the query
- name: end_date
in: query
schema:
type: string
format: date
description: End date for the query
- name: limit
in: query
schema:
type: integer
default: 100
description: Number of records to return
responses:
'200':
description: Array of health metrics
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/HealthMetric'
/api/activities/list:
get:
summary: List available activities
description: Returns metadata for all downloaded/available activities
parameters:
- name: limit
in: query
schema:
type: integer
default: 50
description: Number of activities to return
- name: offset
in: query
schema:
type: integer
default: 0
description: Offset for pagination
responses:
'200':
description: Array of activity metadata
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Activity'
/api/activities/query:
get:
summary: Query activities
description: Allows advanced filtering of activities by type, date, duration, etc.
parameters:
- name: activity_type
in: query
schema:
type: string
description: Type of activity to filter
- name: start_date
in: query
schema:
type: string
format: date
description: Start date for the query
- name: end_date
in: query
schema:
type: string
format: date
description: End date for the query
- name: download_status
in: query
schema:
type: string
enum: [pending, downloaded, failed]
description: Download status to filter
responses:
'200':
description: Array of activities matching the filter
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Activity'
/api/health-data/summary:
get:
summary: Get health data summary
description: Provides aggregated health statistics
parameters:
- name: start_date
in: query
schema:
type: string
format: date
description: Start date for the summary
- name: end_date
in: query
schema:
type: string
format: date
description: End date for the summary
responses:
'200':
description: Aggregated health statistics
content:
application/json:
schema:
type: object
properties:
total_steps:
type: integer
avg_heart_rate:
type: number
format: float
total_sleep_hours:
type: number
format: float
avg_calories:
type: number
format: float
components:
schemas:
SyncLog:
type: object
properties:
id:
type: integer
operation:
type: string
enum: [weight_sync, activity_archive, metrics_download]
status:
type: string
enum: [started, in_progress, completed, failed]
message:
type: string
start_time:
type: string
format: date-time
end_time:
type: string
format: date-time
records_processed:
type: integer
records_failed:
type: integer
HealthMetric:
type: object
properties:
id:
type: integer
metric_type:
type: string
metric_value:
type: number
unit:
type: string
timestamp:
type: string
format: date-time
date:
type: string
format: date
source:
type: string
detailed_data:
type: object
Activity:
type: object
properties:
id:
type: integer
garmin_activity_id:
type: string
activity_name:
type: string
activity_type:
type: string
start_time:
type: string
format: date-time
duration:
type: integer
file_path:
type: string
file_type:
type: string
download_status:
type: string
enum: [pending, downloaded, failed]
downloaded_at:
type: string
format: date-time

View File

@@ -0,0 +1,117 @@
# Data Model: Fitbit-Garmin Local Sync
## Overview
This document defines the data models for the Fitbit-Garmin Local Sync application based on the key entities identified in the feature specification.
## Entity: Configuration
**Description**: Application settings including API credentials, sync settings, and database connection parameters
**Fields**:
- `id` (Integer): Unique identifier for the configuration record
- `fitbit_client_id` (String): Fitbit API client ID
- `fitbit_client_secret` (String): Fitbit API client secret (encrypted)
- `garmin_username` (String): Garmin Connect username
- `garmin_password` (String): Garmin Connect password (encrypted)
- `sync_settings` (JSON): Sync preferences and settings
- `created_at` (DateTime): Timestamp of creation
- `updated_at` (DateTime): Timestamp of last update
## Entity: Weight Record
**Description**: Individual weight entries with timestamps, values, and sync status with unique identifiers to prevent duplicate processing
**Fields**:
- `id` (Integer): Unique identifier for the weight record
- `fitbit_id` (String): Original Fitbit ID for the weight entry
- `weight` (Float): Weight value in user's preferred units
- `unit` (String): Weight unit (e.g., 'kg', 'lbs')
- `date` (Date): Date of the weight measurement
- `timestamp` (DateTime): Exact timestamp of the measurement
- `sync_status` (String): Sync status ('unsynced', 'synced', 'failed')
- `garmin_id` (String, nullable): ID of the record if synced to Garmin
- `created_at` (DateTime): Timestamp of record creation
- `updated_at` (DateTime): Timestamp of last update
## Entity: Activity Metadata
**Description**: Information about Garmin activities including download status, file content stored in database, and activity details
**Fields**:
- `id` (Integer): Unique identifier for the activity record
- `garmin_activity_id` (String): Original Garmin ID for the activity
- `activity_name` (String): Name of the activity
- `activity_type` (String): Type of activity (e.g., 'running', 'cycling')
- `start_time` (DateTime): Start time of the activity
- `duration` (Integer): Duration in seconds
- `file_content` (LargeBinary, nullable): Activity file content stored in database (base64 encoded)
- `file_type` (String): File type (.fit, .gpx, .tcx, etc.)
- `download_status` (String): Download status ('pending', 'downloaded', 'failed')
- `downloaded_at` (DateTime, nullable): Timestamp when downloaded
- `created_at` (DateTime): Timestamp of record creation
- `updated_at` (DateTime): Timestamp of last update
## Entity: Health Metric
**Description**: Comprehensive health data including type, timestamp, values across categories (steps, calories, heart rate, sleep, etc.)
**Fields**:
- `id` (Integer): Unique identifier for the health metric record
- `metric_type` (String): Type of metric (e.g., 'steps', 'heart_rate', 'sleep', 'calories')
- `metric_value` (Float): Value of the metric
- `unit` (String): Unit of measurement
- `timestamp` (DateTime): When the metric was recorded
- `date` (Date): Date of the metric
- `source` (String): Source of the metric ('garmin')
- `detailed_data` (JSON, nullable): Additional details specific to the metric type
- `created_at` (DateTime): Timestamp of record creation
- `updated_at` (DateTime): Timestamp of last update
## Entity: Sync Log
**Description**: Operation logs with timestamps, status, and results for monitoring and troubleshooting
**Fields**:
- `id` (Integer): Unique identifier for the sync log entry
- `operation` (String): Type of sync operation ('weight_sync', 'activity_archive', 'metrics_download')
- `status` (String): Status of the operation ('started', 'in_progress', 'completed', 'failed')
- `message` (String): Status message or error details
- `start_time` (DateTime): When the operation started
- `end_time` (DateTime, nullable): When the operation completed
- `records_processed` (Integer): Number of records processed
- `records_failed` (Integer): Number of records that failed
- `user_id` (Integer, nullable): Reference to user (if applicable)
## Entity: API Token
**Description**: OAuth tokens for Fitbit and Garmin with expiration tracking and refresh mechanisms
**Fields**:
- `id` (Integer): Unique identifier for the token record
- `token_type` (String): Type of token ('fitbit', 'garmin')
- `access_token` (String): Access token (encrypted)
- `refresh_token` (String): Refresh token (encrypted)
- `expires_at` (DateTime): When the token expires
- `scopes` (String): OAuth scopes granted
- `last_used` (DateTime): When the token was last used
- `created_at` (DateTime): Timestamp of record creation
- `updated_at` (DateTime): Timestamp of last update
## Entity: Auth Status
**Description**: Current authentication state for both Fitbit and Garmin, including token expiration times and last login information
**Fields**:
- `id` (Integer): Unique identifier for the auth status record
- `service_type` (String): Type of service ('fitbit', 'garmin')
- `username` (String): Username for the service (masked for security display)
- `authenticated` (Boolean): Whether currently authenticated
- `token_expires_at` (DateTime): When the current token expires
- `last_login` (DateTime): When the last successful login occurred
- `is_china` (Boolean): Whether using garmin.cn domain (Garmin only)
- `last_check` (DateTime): When status was last checked
- `created_at` (DateTime): Timestamp of record creation
- `updated_at` (DateTime): Timestamp of last update
## Relationships
- Configuration has many API Tokens
- Authentication Status references API Tokens
- Sync Logs reference Configuration
- Weight Records may reference API Tokens for sync operations
- Activity Metadata may reference API Tokens for download operations
- Health Metrics may reference API Tokens for retrieval operations
## Validation Rules
- Configuration records must have valid API credentials before sync operations
- Weight Records must have unique fitbit_id to prevent duplicates
- Activity Metadata records must have unique garmin_activity_id
- Health Metric records must have valid metric_type from allowed list
- Sync Log records must have valid operation and status values
- API Token records must be refreshed before expiration
- Authentication status must be updated when tokens are refreshed

View File

@@ -0,0 +1,103 @@
# Implementation Plan: Fitbit-Garmin Local Sync
**Branch**: `001-fitbit-garmin-sync` | **Date**: December 22, 2025 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/001-fitbit-garmin-sync/spec.md`
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
## Summary
This feature implements a standalone Python application that synchronizes health and fitness data between Fitbit and Garmin Connect platforms. The primary requirements include: 1) Weight data synchronization from Fitbit to Garmin, 2) Activity file archiving from Garmin to local storage, and 3) Comprehensive health metrics download from Garmin to local database. The system uses a web interface with API endpoints for user interaction and operates with local-only data storage for privacy.
## Technical Context
**Language/Version**: Python 3.11
**Primary Dependencies**: FastAPI, uvicorn, garminconnect, garth, fitbit, SQLAlchemy, Jinja2, psycopg2
**Storage**: PostgreSQL database for all data including configuration, health metrics, activity files, and authentication status information
**Testing**: pytest for unit and integration tests, contract tests for API endpoints
**Target Platform**: Linux server (containerized with Docker)
**Project Type**: Web application (backend API + web UI)
**Performance Goals**: Process 1000 activity files within 2 hours, sync weight data with 95% success rate, API responses under 3 seconds
**Constraints**: All sensitive data stored locally, offline-capable operation, secure storage of OAuth tokens
**Scale/Scope**: Single user system supporting personal health data synchronization
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
Based on the project constitution, this implementation needs to follow library-first principles, exposing functionality via web API. All new features should have tests written before implementation, with integration tests for API contracts and inter-service communication. The system must include structured logging and observability for debugging.
## Project Structure
### Documentation (this feature)
```text
specs/001-fitbit-garmin-sync/
├── plan.md # This file (/speckit.plan command output)
├── research.md # Phase 0 output (/speckit.plan command)
├── data-model.md # Phase 1 output (/speckit.plan command)
├── quickstart.md # Phase 1 output (/speckit.plan command)
├── contracts/ # Phase 1 output (/speckit.plan command)
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```
### Source Code (repository root)
```text
backend/
├── main.py
├── src/
│ ├── models/
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── weight_record.py
│ │ ├── activity.py
│ │ ├── health_metric.py
│ │ ├── sync_log.py
│ │ ├── api_token.py
│ │ └── auth_status.py
│ ├── services/
│ │ ├── __init__.py
│ │ ├── fitbit_client.py
│ │ ├── garmin_client.py
│ │ ├── postgresql_manager.py
│ │ └── sync_app.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── auth.py
│ │ ├── sync.py
│ │ ├── setup.py
│ │ └── metrics.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
├── templates/
│ ├── index.html
│ └── setup.html
├── static/
│ ├── css/
│ └── js/
├── requirements.txt
├── Dockerfile
└── docker-compose.yml
tests/
├── unit/
│ ├── test_models/
│ ├── test_services/
│ └── test_api/
├── integration/
│ └── test_sync_flow.py
└── contract/
└── test_api_contracts.py
```
**Structure Decision**: Web application structure selected to support the backend API and web UI requirements from the feature specification. The backend includes models for data representation, services for business logic, and API endpoints for user interaction.
## Complexity Tracking
> **Fill ONLY if Constitution Check has violations that must be justified**
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| External API dependencies | Required for Fitbit and Garmin integration | Direct DB access insufficient for external services |

View File

@@ -0,0 +1,102 @@
# Quickstart Guide: Fitbit-Garmin Local Sync
## Prerequisites
- Python 3.11+
- PostgreSQL database
- Docker and Docker Compose (for containerized deployment)
- Fitbit Developer Account (to create an app and get API credentials)
- Garmin Connect Account
## Setup
### 1. Clone and Install Dependencies
```bash
# Clone the repository
git clone <repository-url>
cd fitbit-garmin-sync
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
```
### 2. Database Setup
```bash
# Create PostgreSQL database
createdb fitbit_garmin_sync
# Update database configuration in application
# The application will handle schema creation automatically
```
### 3. Environment Configuration
Create a `.env` file with the following:
```env
DATABASE_URL=postgresql://username:password@localhost:5432/fitbit_garmin_sync
FITBIT_CLIENT_ID=your_fitbit_client_id
FITBIT_CLIENT_SECRET=your_fitbit_client_secret
FITBIT_REDIRECT_URI=http://localhost:8000/api/setup/fitbit/callback
```
### 4. Run the Application
```bash
# Using uvicorn directly
uvicorn main:app --host 0.0.0.0 --port 8000
# Or using Docker
docker-compose up --build
```
## Initial Configuration
1. Open the application in your browser at `http://localhost:8000`
2. Navigate to the Setup page (`/setup`)
3. Enter your Garmin Connect username and password
4. Enter your Fitbit Client ID and Client Secret
5. Click the authorization link provided to authenticate with Fitbit
6. Copy the full callback URL from your browser after authorizing and paste it into the input field on the setup page
## Using the Application
### Sync Weight Data
1. Go to the home page (`/`)
2. Click the "Sync Weight" button
3. Monitor the sync status in the logs table
### Archive Activities
1. Go to the home page (`/`)
2. Click the "Sync Activities" button
3. Enter the number of days back to look for activities
4. Monitor the sync status in the logs table
### View Health Metrics
1. Use the API endpoints to query health metrics:
- `/api/metrics/list` - List available metric types
- `/api/metrics/query` - Query specific metrics
- `/api/health-data/summary` - Get aggregated health statistics
## Docker Deployment
```bash
# Build and run with Docker Compose
docker-compose up --build
# The application will be available at http://localhost:8000
# PostgreSQL database will be automatically set up
```
## API Endpoints
See the full API documentation in the `contracts/api-contract.yaml` file or access the automatic documentation at `/docs` when running the application.

View File

@@ -0,0 +1,58 @@
# Research: Fitbit-Garmin Local Sync
## Overview
This document captures research findings for the Fitbit-Garmin Local Sync feature, addressing technology choices, best practices, and integration patterns.
## Decision: Python 3.11 as primary language
**Rationale**: Python is well-suited for API integrations and web applications. Version 3.11 offers performance improvements and is widely supported by the required libraries (FastAPI, garminconnect, fitbit).
**Alternatives considered**: Node.js/JavaScript, Go, Rust - Python has the most mature ecosystem for health data API integrations.
## Decision: FastAPI for web framework
**Rationale**: FastAPI provides automatic API documentation (OpenAPI), type validation, asynchronous support, and excellent performance. It's ideal for both the API endpoints and web UI rendering.
**Alternatives considered**: Flask (less modern features), Django (too heavy for this use case), Starlette (requires more manual work).
## Decision: garminconnect and garth libraries for Garmin integration
**Rationale**: garminconnect is the most actively maintained Python library for Garmin Connect API. garth handles authentication, including the complex authentication flow for Garmin's API.
**Alternatives considered**: Custom HTTP requests implementation (more error-prone), selenium for web scraping (against ToS and less reliable).
## Decision: Fitbit official Python library
**Rationale**: The official fitbit library provides proper OAuth 2.0 handling and is maintained by Fitbit. It includes all necessary endpoints for weight data retrieval.
**Alternatives considered**: Direct API calls with requests library (would require more OAuth management code).
## Decision: PostgreSQL for data storage
**Rationale**: PostgreSQL provides ACID compliance, robustness, and complex query capabilities needed for health metrics. It supports the data types needed for timestamps and metric values.
**Alternatives considered**: SQLite (simpler but less scalable), MongoDB (document-based which may not suit structured health data), MySQL (similar capabilities but PostgreSQL has better JSON support).
## Decision: SQLAlchemy as ORM
**Rationale**: SQLAlchemy provides database abstraction, migration support, and protection against SQL injection. It works well with FastAPI and supports asynchronous operations.
**Alternatives considered**: Peewee (simpler but less feature-rich), Django ORM (requires Django framework), direct database connectors (more error-prone).
## Decision: Docker for deployment
**Rationale**: Docker provides consistent deployment across environments, easy dependency management, and isolation. It's the standard for modern application deployment.
**Alternatives considered**: Direct installation on host system (harder to manage dependencies), virtual environments (doesn't solve system-level dependency issues).
## Decision: Jinja2 for templating
**Rationale**: Jinja2 is the standard Python templating engine, supported by FastAPI. It provides the right balance of functionality and simplicity for the web interface.
**Alternatives considered**: Mako, Chameleon (less common), building HTML responses directly (not maintainable).
## Authentication Research
- **Fitbit OAuth 2.0**: Requires app registration with Fitbit, supports refresh tokens for long-term access
- **Garmin authentication**: Uses garth library to handle OAuth 1.0a/2.0 hybrid, stores session tokens for reuse
- **Multi-Factor Authentication (MFA)**: Garmin may require MFA for accounts with enhanced security. The garth library handles MFA flows by prompting for verification codes when required
- **Security**: Both systems support proper token refresh and secure storage
## API Rate Limiting Considerations
- **Fitbit**: Has rate limits (150 req/hour for user endpoints) - need to implement backoff/retry logic
- **Garmin**: No official rate limits published, but need to be respectful to avoid being blocked
- **Best practice**: Implement exponential backoff and caching to minimize API calls
## Data Synchronization Strategy
- **Deduplication**: Use unique identifiers and timestamps to prevent duplicate processing
- **State tracking**: Store sync status in database to enable resumption of interrupted operations
- **Conflict resolution**: For weight data, prefer Fitbit as source of truth since the feature is to sync FROM Fitbit TO Garmin
## Error Handling Approach
- **Network errors**: Retry with exponential backoff
- **Authentication errors**: Detect and re-authenticate automatically
- **API errors**: Log with context and allow user to retry operations
- **Storage errors**: Validate disk space before downloading activity files

View File

@@ -0,0 +1,105 @@
# Feature Specification: Fitbit-Garmin Local Sync
**Feature Branch**: `001-fitbit-garmin-sync`
**Created**: December 22, 2025
**Status**: Draft
**Input**: User description: "Fitbit-Garmin Local Sync application to synchronize health and fitness data between Fitbit and Garmin Connect platforms"
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Sync Weight Data from Fitbit to Garmin (Priority: P1)
A user with both Fitbit and Garmin devices wants to transfer their weight data from Fitbit to Garmin in a secure, automated way. The user accesses the web interface, triggers the sync process, and the system safely transfers their weight history from Fitbit to Garmin without duplicating entries.
**Why this priority**: This is the foundational functionality that provides immediate value to users with both platforms, solving a common data silo problem.
**Independent Test**: Can be fully tested by configuring Fitbit and Garmin credentials, triggering a weight sync, and verifying weight entries appear in Garmin without duplication. Delivers core value of unified health data.
**Acceptance Scenarios**:
1. **Given** user has configured Fitbit and Garmin credentials, **When** user clicks "Sync Weight" button, **Then** system fetches weight history from Fitbit and uploads to Garmin, tracking synced entries to prevent duplicates
2. **Given** user has previously synced weight data, **When** user runs sync again, **Then** system only uploads new weight entries that haven't been synced before
---
### User Story 2 - Archive Activity Files from Garmin (Priority: P2)
A user wants to preserve their historical activity data from Garmin by downloading original files (.fit, .gpx, .tcx) and storing them in the database. The user accesses the web interface, triggers the activity archiving process, and the system downloads activity files and stores them in the PostgreSQL database with proper organization. The system handles multi-factor authentication flows required by Garmin.
**Why this priority**: Provides backup and archival capabilities that users value for data preservation and analysis, building on the core sync functionality.
**Independent Test**: Can be tested by triggering the activity archiving process and verifying original activity files are downloaded and stored in the database with proper organization.
**Acceptance Scenarios**:
1. **Given** user has configured Garmin credentials, **When** user clicks "Archive Activities" button, **Then** system fetches activity list and downloads original files stored in database
2. **Given** user has previously downloaded activity files, **When** user runs archiving again, **Then** system only downloads activities that haven't been downloaded before
---
### User Story 3 - Download Comprehensive Health Metrics from Garmin (Priority: P3)
A user wants to store all available health metrics from Garmin for local analysis and backup. The user accesses the web interface, triggers the metrics download process, and the system retrieves a comprehensive range of health metrics (steps, calories, heart rate, sleep, etc.) and stores them in a local database.
**Why this priority**: Provides complete health data backup and analysis capabilities, extending beyond basic sync to comprehensive data management.
**Independent Test**: Can be tested by triggering the metrics download and verifying that various types of health metrics are stored in the database with proper timestamps and types.
**Acceptance Scenarios**:
1. **Given** user has configured Garmin credentials, **When** user triggers health metrics download, **Then** system retrieves and stores comprehensive health metrics in local database
2. **Given** user wants to query health metrics, **When** user makes API call to metrics endpoint, **Then** system returns requested metrics filtered by date range and type
---
### Edge Cases
- What happens when API rate limits are exceeded during sync operations?
- How does system handle authentication token expiration during long-running sync processes?
- What occurs when local storage is insufficient for activity file downloads?
- How does system handle duplicate data detection when timestamps are slightly different?
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: System MUST authenticate with both Fitbit (OAuth 2.0) and Garmin Connect (username/password with garth library) APIs
- **FR-002**: System MUST securely store API credentials, tokens, and configuration in a PostgreSQL database
- **FR-003**: System MUST synchronize weight data from Fitbit to Garmin, including historical records
- **FR-004**: System MUST track synced entries to prevent duplicate uploads
- **FR-005**: System MUST download original activity files (.fit, .gpx, .tcx) from Garmin and store them in the PostgreSQL database
- **FR-006**: System MUST download comprehensive health metrics from Garmin including daily summaries, heart rate, sleep data, stress levels, and body composition
- **FR-007**: System MUST provide a web-based user interface with status dashboard and sync controls
- **FR-008**: System MUST provide API endpoints for triggering sync operations and querying data
- **FR-009**: System MUST implement robust error handling and retry mechanisms for API failures
- **FR-010**: System MUST support both global (garmin.com) and China (garmin.cn) Garmin domains
- **FR-011**: System MUST automatically refresh OAuth tokens when they expire
- **FR-012**: System MUST log all sync operations with timestamps, status, and results
- **FR-013**: System MUST allow users to trigger sync operations through UI buttons
- **FR-014**: System MUST support querying health metrics by date range, type, and other criteria
- **FR-015**: System MUST implement ACID-compliant data storage for consistency and integrity
- **FR-016**: System MUST handle multi-factor authentication flows for Garmin Connect when required by user's account settings
- **FR-017**: System MUST provide current authentication status and token expiration information in the web UI
- **FR-018**: System MUST securely store and manage Garmin OAuth tokens and session information
### Key Entities
- **Configuration**: Application settings including API credentials, sync settings, and database connection parameters
- **Weight Record**: Individual weight entries with timestamps, values, and sync status with unique identifiers to prevent duplicate processing
- **Activity Metadata**: Information about Garmin activities including download status, file content stored in database, and activity details
- **Health Metric**: Comprehensive health data including type, timestamp, values across categories (steps, calories, heart rate, sleep, etc.)
- **Sync Log**: Operation logs with timestamps, status, and results for monitoring and troubleshooting
- **API Token**: OAuth tokens for Fitbit and Garmin with expiration tracking and refresh mechanisms
- **Authentication Status**: Current authentication state for both Fitbit and Garmin, including token expiration times and last login information
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: Users can successfully sync weight data from Fitbit to Garmin with 95% success rate for valid entries
- **SC-002**: System can download and archive 1000 activity files without errors within 2 hours
- **SC-003**: System can retrieve and store comprehensive health metrics for a full year of data within 4 hours
- **SC-004**: All sensitive data (credentials, tokens, health stats) remains stored locally without external cloud services
- **SC-005**: User can complete setup and authentication for both Fitbit and Garmin within 10 minutes
- **SC-006**: System prevents duplicate data uploads/downloads with 99.9% accuracy
- **SC-007**: Web interface loads and responds to user actions within 3 seconds under normal conditions

View File

@@ -0,0 +1,256 @@
---
description: "Task list for Fitbit-Garmin Local Sync implementation"
---
# Tasks: Fitbit-Garmin Local Sync
**Input**: Design documents from `/specs/001-fitbit-garmin-sync/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification.
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
## Path Conventions
- **Single project**: `src/`, `tests/` at repository root
- **Web app**: `backend/src/`, `frontend/src/`
- **Mobile**: `api/src/`, `ios/src/` or `android/src/`
- Paths shown below assume single project - adjust based on plan.md structure
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Project initialization and basic structure
- [x] T001 Create project structure per implementation plan in backend/
- [x] T002 Initialize Python 3.11 project with FastAPI, SQLAlchemy, garminconnect, garth, fitbit dependencies in requirements.txt
- [x] T003 [P] Configure linting and formatting tools (black, flake8) in pyproject.toml
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
- [x] T004 Setup database schema and migrations framework using SQLAlchemy in backend/src/models/
- [x] T005 [P] Create base models (Configuration, API Token) in backend/src/models/config.py and backend/src/models/api_token.py
- [x] T006 [P] Setup API routing and middleware structure in backend/main.py
- [x] T007 Create database manager for PostgreSQL in backend/src/services/postgresql_manager.py
- [x] T008 Configure error handling and logging infrastructure in backend/src/utils/
- [x] T009 Setup environment configuration management in backend/src/utils/
- [x] T010 Create FastAPI app structure with proper routing in backend/main.py
- [x] T011 Setup Docker configuration with PostgreSQL in backend/Dockerfile and backend/docker-compose.yml
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
---
## Phase 3: User Story 1 - Sync Weight Data from Fitbit to Garmin (Priority: P1) 🎯 MVP
**Goal**: User can transfer weight data from Fitbit to Garmin without duplicating entries, with a web interface to trigger the sync.
**Independent Test**: Can be fully tested by configuring Fitbit and Garmin credentials, triggering a weight sync, and verifying weight entries appear in Garmin without duplication. Delivers core value of unified health data.
### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
- [ ] T012 [P] [US1] Contract test for /api/sync/weight endpoint in backend/tests/contract/test_weight_sync.py
- [ ] T013 [P] [US1] Integration test for weight sync flow in backend/tests/integration/test_weight_sync_flow.py
### Implementation for User Story 1
- [x] T014 [P] [US1] Create Weight Record model in backend/src/models/weight_record.py
- [x] T015 [P] [US1] Create Sync Log model in backend/src/models/sync_log.py
- [x] T016 [US1] Implement Fitbit Client service in backend/src/services/fitbit_client.py
- [x] T017 [US1] Implement Garmin Client service in backend/src/services/garmin_client.py
- [x] T018 [US1] Implement weight sync logic in backend/src/services/sync_app.py
- [x] T019 [US1] Implement weight sync API endpoint in backend/src/api/sync.py
- [x] T020 [US1] Create status API endpoint in backend/src/api/status.py
- [x] T021 [US1] Add web UI for weight sync in backend/templates/index.html
- [x] T022 [US1] Add logging for weight sync operations in backend/src/utils/helpers.py
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
---
## Phase 4: User Story 2 - Archive Activity Files from Garmin (Priority: P2)
**Goal**: User can download and archive Garmin activity files (.fit, .gpx, .tcx) to a local directory structure with proper organization.
**Independent Test**: Can be tested by triggering the activity archiving process and verifying original activity files are downloaded to the specified local directory with proper organization.
### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️
- [ ] T023 [P] [US2] Contract test for /api/sync/activities endpoint in backend/tests/contract/test_activities_sync.py
- [ ] T024 [P] [US2] Integration test for activity archiving flow in backend/tests/integration/test_activity_flow.py
### Implementation for User Story 2
- [x] T025 [P] [US2] Create Activity Metadata model in backend/src/models/activity.py
- [x] T026 [US2] Extend Garmin Client service with activity download methods in backend/src/services/garmin_client.py
- [x] T027 [US2] Implement activity archiving logic in backend/src/services/sync_app.py
- [x] T028 [US2] Implement activity sync API endpoint in backend/src/api/sync.py
- [x] T029 [US2] Add activity list endpoint in backend/src/api/activities.py
- [x] T030 [US2] Update web UI to include activity archiving in backend/templates/index.html
- [x] T031 [US2] Add logging for activity archiving operations in backend/src/utils/helpers.py
**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently
---
## Phase 5: User Story 3 - Download Comprehensive Health Metrics from Garmin (Priority: P3)
**Goal**: User can download and store comprehensive health metrics from Garmin in a local database with API endpoints for querying.
**Independent Test**: Can be tested by triggering the metrics download and verifying that various types of health metrics are stored in the database with proper timestamps and types.
### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️
- [ ] T032 [P] [US3] Contract test for /api/metrics endpoints in backend/tests/contract/test_metrics_api.py
- [ ] T033 [P] [US3] Integration test for health metrics download flow in backend/tests/integration/test_metrics_flow.py
### Implementation for User Story 3
- [x] T034 [P] [US3] Create Health Metric model in backend/src/models/health_metric.py
- [x] T035 [US3] Extend Garmin Client service with comprehensive metrics download methods in backend/src/services/garmin_client.py
- [x] T036 [US3] Implement health metrics download logic in backend/src/services/sync_app.py
- [x] T037 [US3] Implement metrics list and query endpoints in backend/src/api/metrics.py
- [x] T038 [US3] Implement health data summary endpoint in backend/src/api/metrics.py
- [x] T039 [US3] Add UI elements for metrics management in backend/templates/
- [x] T040 [US3] Add logging for metrics download operations in backend/src/utils/helpers.py
**Checkpoint**: All user stories should now be independently functional
---
## Phase 6: Setup API Endpoints for Configuration (Cross-cutting)
**Goal**: Implement configuration and authentication endpoints that support all user stories
- [x] T041 [P] Create setup API endpoints in backend/src/api/setup.py
- [x] T042 [P] Implement Garmin credentials save endpoint in backend/src/api/setup.py
- [x] T043 [P] Implement Fitbit credentials save and auth URL endpoint in backend/src/api/setup.py
- [x] T044 [P] Implement Fitbit OAuth callback endpoint in backend/src/api/setup.py
- [x] T045 Create logs endpoint in backend/src/api/logs.py
- [x] T046 Update UI with setup page in backend/templates/setup.html
- [x] T047 [P] Create auth status model in backend/src/models/auth_status.py
- [x] T048 [P] Create auth status API endpoint in backend/src/api/setup.py
---
## Phase 7: Polish & Cross-Cutting Concerns
**Purpose**: Improvements that affect multiple user stories
- [x] T047 [P] Documentation updates in backend/README.md
- [ ] T048 Code cleanup and refactoring
- [ ] T049 Performance optimization across all stories
- [ ] T050 [P] Additional unit tests (if requested) in backend/tests/unit/
- [ ] T051 Security hardening for OAuth token storage and API access
- [ ] T052 Run quickstart.md validation
- [ ] T053 Final integration testing across all features
- [x] T054 Update Docker configuration with all required services
- [x] T055 Create setup guide in backend/docs/setup.md
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies - can start immediately
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
- **User Stories (Phase 3+)**: All depend on Foundational phase completion
- User stories can then proceed in parallel (if staffed)
- Or sequentially in priority order (P1 → P2 → P3)
- **Configuration Phase (Phase 6)**: Can proceed in parallel with user stories but may be needed first
- **Polish (Final Phase)**: Depends on all desired user stories being complete
### User Story Dependencies
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable
- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable
### Within Each User Story
- Tests (if included) MUST be written and FAIL before implementation
- Models before services
- Services before endpoints
- Core implementation before integration
- Story complete before moving to next priority
### Parallel Opportunities
- All Setup tasks marked [P] can run in parallel
- All Foundational tasks marked [P] can run in parallel (within Phase 2)
- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
- All tests for a user story marked [P] can run in parallel
- Models within a story marked [P] can run in parallel
- Different user stories can be worked on in parallel by different team members
---
## Parallel Example: User Story 1
```bash
# Launch all tests for User Story 1 together (if tests requested):
Task: "Contract test for /api/sync/weight endpoint in backend/tests/contract/test_weight_sync.py"
Task: "Integration test for weight sync flow in backend/tests/integration/test_weight_sync_flow.py"
# Launch all models for User Story 1 together:
Task: "Create Weight Record model in backend/src/models/weight_record.py"
Task: "Create Sync Log model in backend/src/models/sync_log.py"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
3. Complete Phase 3: User Story 1
4. **STOP and VALIDATE**: Test User Story 1 independently
5. Deploy/demo if ready
### Incremental Delivery
1. Complete Setup + Foundational → Foundation ready
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
3. Add User Story 2 → Test independently → Deploy/Demo
4. Add User Story 3 → Test independently → Deploy/Demo
5. Each story adds value without breaking previous stories
### Parallel Team Strategy
With multiple developers:
1. Team completes Setup + Foundational together
2. Once Foundational is done:
- Developer A: User Story 1
- Developer B: User Story 2
- Developer C: User Story 3
3. Stories complete and integrate independently
---
## Notes
- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability
- Each user story should be independently completable and testable
- Verify tests fail before implementing
- Commit after each task or logical group
- Stop at any checkpoint to validate story independently
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence