feat: Update spec, fix bugs, improve UI/UX, and clean up code

This commit is contained in:
2025-12-25 08:33:01 -08:00
parent 8fe375a966
commit df9dcb2f79
21 changed files with 1741 additions and 1055 deletions

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: Fitbit/Garmin Data Sync Implementation
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2025-12-24
**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
- The initial specification was highly technical based on the user prompt. It has been revised to focus on user-facing requirements and outcomes, making it suitable for planning.

View File

@@ -0,0 +1,273 @@
openapi: 3.0.0
info:
title: Fitbit/Garmin Sync API
version: 1.0.0
description: API for synchronizing and retrieving fitness data from Garmin.
paths:
/api/sync/activities:
post:
summary: Trigger Activity Sync
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
days_back:
type: integer
default: 7
responses:
'200':
description: Sync completed
content:
application/json:
schema:
$ref: '#/components/schemas/SyncResponse'
'400':
description: Bad Request (e.g., Garmin not configured)
'500':
description: Internal Server Error
/api/sync/metrics:
post:
summary: Trigger Health Metrics Sync
responses:
'200':
description: Sync completed
content:
application/json:
schema:
$ref: '#/components/schemas/SyncResponse'
'400':
description: Bad Request (e.g., Garmin not configured)
'500':
description: Internal Server Error
/api/activities/list:
get:
summary: List Activities
parameters:
- name: limit
in: query
schema:
type: integer
default: 50
- name: offset
in: query
schema:
type: integer
default: 0
responses:
'200':
description: A list of activities
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ActivityResponse'
/api/activities/query:
get:
summary: Query Activities
parameters:
- name: activity_type
in: query
schema:
type: string
- name: start_date
in: query
schema:
type: string
format: date
- name: end_date
in: query
schema:
type: string
format: date
- name: download_status
in: query
schema:
type: string
responses:
'200':
description: A list of activities
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ActivityResponse'
/api/activities/download/{activity_id}:
get:
summary: Download Activity File
parameters:
- name: activity_id
in: path
required: true
schema:
type: string
responses:
'200':
description: The activity file
content:
application/octet-stream: {}
'404':
description: Activity not found or file not downloaded
/api/metrics/list:
get:
summary: List Available Metrics
responses:
'200':
description: A list of available metric types and date ranges
content:
application/json:
schema:
$ref: '#/components/schemas/MetricsListResponse'
/api/metrics/query:
get:
summary: Query Health Metrics
parameters:
- name: metric_type
in: query
schema:
type: string
- name: start_date
in: query
schema:
type: string
format: date
- name: end_date
in: query
schema:
type: string
format: date
- name: limit
in: query
schema:
type: integer
default: 100
responses:
'200':
description: A list of health metrics
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/HealthMetricResponse'
/api/health-data/summary:
get:
summary: Get Health Data Summary
parameters:
- name: start_date
in: query
schema:
type: string
format: date
- name: end_date
in: query
schema:
type: string
format: date
responses:
'200':
description: A summary of health data
content:
application/json:
schema:
$ref: '#/components/schemas/HealthDataSummary'
components:
schemas:
SyncResponse:
type: object
properties:
status:
type: string
message:
type: string
job_id:
type: string
ActivityResponse:
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_type:
type: string
download_status:
type: string
downloaded_at:
type: string
format: date-time
HealthMetricResponse:
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
nullable: true
MetricsListResponse:
type: object
properties:
metric_types:
type: array
items:
type: string
date_range:
type: object
properties:
start_date:
type: string
format: date
end_date:
type: string
format: date
HealthDataSummary:
type: object
properties:
total_steps:
type: integer
avg_heart_rate:
type: number
total_sleep_hours:
type: number
avg_calories:
type: number

View File

@@ -0,0 +1,65 @@
# Data Model: Fitbit/Garmin Data Sync
**Date**: 2025-12-24
This document describes the data models for the entities involved in the Fitbit/Garmin data sync feature. The models are based on the existing SQLAlchemy models in the project.
## Activity
Represents a single fitness activity (e.g., running, cycling).
**SQLAlchemy Model**: `src/models/activity.py`
| Field | Type | Description |
| ------------------ | ------------ | ----------------------------------------------------------- |
| `id` | Integer | Primary key. |
| `garmin_activity_id` | String | The original unique identifier from Garmin Connect. |
| `activity_name` | String | The name of the activity (e.g., "Afternoon Run"). |
| `activity_type` | String | The type of activity (e.g., 'running', 'cycling'). |
| `start_time` | DateTime | The time the activity started. |
| `duration` | Integer | The duration of the activity in seconds. |
| `file_content` | LargeBinary | The original activity file content (e.g., .fit, .gpx, .tcx).|
| `file_type` | String | The file type of the original activity file. |
| `download_status` | String | The status of the file download ('pending', 'downloaded', 'failed'). |
| `downloaded_at` | DateTime | The timestamp when the file was downloaded. |
| `created_at` | DateTime | The timestamp when the record was created. |
| `updated_at` | DateTime | The timestamp when the record was last updated. |
## HealthMetric
Represents a single health data point (e.g., steps, heart rate variability).
**SQLAlchemy Model**: `src/models/health_metric.py`
| Field | Type | Description |
| -------------- | -------- | ----------------------------------------------------- |
| `id` | Integer | Primary key. |
| `metric_type` | String | The type of metric (e.g., 'steps', 'heart_rate'). |
| `metric_value` | Float | The value of the metric. |
| `unit` | String | The unit of measurement (e.g., 'steps', 'ms'). |
| `timestamp` | DateTime | The timestamp when the metric was recorded. |
| `date` | DateTime | The date of the metric. |
| `source` | String | The source of the metric (e.g., 'garmin'). |
| `detailed_data`| Text | Additional details, stored as a JSON string. |
| `created_at` | DateTime | The timestamp when the record was created. |
| `updated_at` | DateTime | The timestamp when the record was last updated. |
## SyncLog
Records the status and results of each synchronization operation.
**SQLAlchemy Model**: `src/models/sync_log.py`
| Field | Type | Description |
| ------------------- | -------- | -------------------------------------------------------------------- |
| `id` | Integer | Primary key. |
| `operation` | String | The type of operation (e.g., 'metrics_download', 'activity_sync'). |
| `status` | String | The status of the operation ('started', 'completed', 'failed'). |
| `message` | Text | A status message or error details. |
| `start_time` | DateTime | The timestamp when the operation started. |
| `end_time` | DateTime | The timestamp when the operation completed. |
| `records_processed` | Integer | The number of records successfully processed. |
- `records_failed` | Integer | The number of records that failed to process. |
| `user_id` | Integer | A reference to the user for whom the sync was run (if applicable). |
| `created_at` | DateTime | The timestamp when the record was created. |
| `updated_at` | DateTime | The timestamp when the record was last updated. |

View File

@@ -0,0 +1,77 @@
# Implementation Plan: Fitbit/Garmin Data Sync
**Branch**: `002-fitbit-garmin-sync` | **Date**: 2025-12-24 | **Spec**: [link](./spec.md)
**Input**: Feature specification from `specs/002-fitbit-garmin-sync/spec.md`
## Summary
This feature will implement synchronization of activities and health metrics from Garmin Connect to the application's database. The implementation will involve wiring up existing backend stub endpoints to a sync service that uses the `garth` library to fetch data from Garmin. The synced data will be stored in a PostgreSQL database and exposed through a series of new API endpoints for querying and retrieval.
## Technical Context
**Language/Version**: Python 3.11
**Primary Dependencies**: FastAPI, Uvicorn, SQLAlchemy, Pydantic, garth
**Storage**: PostgreSQL
**Testing**: pytest, pytest-asyncio
**Target Platform**: Linux server (via Docker)
**Project Type**: Web application (backend)
**Performance Goals**: Standard web application performance, with API responses under 500ms p95.
**Constraints**: The solution should be implemented within the existing `backend` service.
**Scale/Scope**: The initial implementation will support syncing data for a single user.
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
- **Test-First**: The implementation will follow a test-driven approach. For each endpoint and service function, a corresponding set of unit and/or integration tests will be written before the implementation.
*VERDICT: The plan adheres to the core principles of the constitution.*
## Project Structure
### Documentation (this feature)
```text
specs/002-fitbit-garmin-sync/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output
│ └── api-contract.yaml
└── tasks.md # Phase 2 output (created by /speckit.tasks)
```
### Source Code (repository root)
The implementation will be contained within the existing `backend` directory structure.
```text
backend/
├── src/
│ ├── api/
│ │ ├── activities.py
│ │ ├── metrics.py
│ │ └── sync.py
│ ├── models/
│ │ ├── activity.py
│ │ └── health_metric.py
│ └── services/
│ ├── sync_app.py
│ └── garmin/
│ └── client.py
└── tests/
├── integration/
│ ├── test_sync_flow.py
└── unit/
├── test_api/
│ ├── test_activities.py
│ └── test_metrics.py
└── test_services/
└── test_sync_app.py
```
**Structure Decision**: The plan adheres to the existing project structure, which is a single backend service. New functionality will be added to the appropriate existing modules.
## Complexity Tracking
*No constitutional violations to justify.*

View File

@@ -0,0 +1,78 @@
# Quickstart: Fitbit/Garmin Data Sync
**Date**: 2025-12-24
This guide provides instructions on how to set up and run the application to test the Fitbit/Garmin data sync feature.
## Prerequisites
- Docker and Docker Compose
- Python 3.11
- An active Garmin Connect account
## Setup
1. **Clone the repository**:
```bash
git clone <repository-url>
cd <repository-name>
```
2. **Set up environment variables**:
- Copy the `.env.example` file to `.env`.
- Fill in the required environment variables, including your Garmin Connect email and password.
```bash
cp .env.example .env
# Edit .env with your credentials
```
3. **Build and run the application**:
```bash
docker-compose up --build
```
The application will be available at `http://localhost:8000`.
## Testing the Endpoints
You can use `curl` or any API client to test the new endpoints.
### 1. Trigger Activity Sync
```bash
curl -X POST http://localhost:8000/api/sync/activities \
-H "Content-Type: application/json" \
-d '{"days_back": 7}'
```
### 2. List Activities
```bash
curl http://localhost:8000/api/activities/list
```
### 3. Trigger Health Metrics Sync
```bash
curl -X POST http://localhost:8000/api/sync/metrics
```
### 4. Query Health Metrics
```bash
curl "http://localhost:8000/api/metrics/query?metric_type=steps&limit=10"
```
### 5. Verify Data in Database
You can connect to the PostgreSQL database to verify that the data has been synced correctly.
```bash
docker exec -it <postgres-container-name> psql -U postgres -d fitbit_garmin_sync
```
Then, run SQL queries to inspect the `activities` and `health_metrics` tables:
```sql
SELECT * FROM activities;
SELECT * FROM health_metrics;
```

View File

@@ -0,0 +1,30 @@
# Research: Fitbit/Garmin Data Sync
**Date**: 2025-12-24
This document outlines the research performed for the Fitbit/Garmin data sync feature.
## `garth` Library for Garmin Communication
**Decision**: The `garth` library will be used for all communication with the Garmin Connect API.
**Rationale**:
- The user's initial technical specification explicitly mentioned and provided examples using `garth`.
- The library appears to be actively maintained and provides the necessary functionality for fetching activities and health metrics.
- It handles the authentication flow with Garmin, which is a complex part of the integration.
**Alternatives considered**:
- `garminconnect`: The user's `requirements.txt` includes this library, but the technical details provided in the prompt favored `garth`. `garth` seems to be a more modern library for interacting with the Garmin API.
**Key Findings from User Prompt & `garth` Documentation:**
- The user's prompt provided detailed code snippets for using `garth` to fetch `DailySteps` and `DailyHRV`. This will serve as a strong starting point for the implementation.
- The `garth` library offers a variety of other metric types that can be explored for future enhancements (e.g., stress, sleep).
- Authentication is handled by the `garth.client.Garth` class, which needs to be configured with the user's credentials. The application already has a mechanism for storing API tokens, which will be used to store the Garmin credentials.
## API Endpoint Design
**Decision**: The API endpoints will be implemented as described in the user's initial technical specification.
**Rationale**: The user provided a complete and well-defined set of API endpoints, including request/response models and URL paths. This design is consistent with a standard RESTful API and meets all the requirements of the feature.
**Alternatives considered**: None. The user's specification was clear and complete.

View File

@@ -0,0 +1,157 @@
# 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.

View File

@@ -0,0 +1,215 @@
# Tasks: Fitbit/Garmin Data Sync
**Input**: Design documents from `specs/002-fitbit-garmin-sync/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
**Tests**: Tests are included based on the TDD principle mentioned in the constitution.
**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
- Paths shown below assume single project - adjust based on plan.md structure
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Project initialization and basic structure
- [ ] T001 Review `sync_app.py` in `backend/src/services/sync_app.py`
- [ ] T002 Review `garmin/client.py` in `backend/src/services/garmin/client.py`
---
## 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
- [ ] T003 Add error handling to all endpoints in `backend/src/api/`
- [ ] T004 Add logging at key points in `backend/src/api/` and `backend/src/services/`
- [ ] T005 Add missing imports for API Token, SyncApp, GarminClient, setup_logger to `backend/src/api/sync.py`
- [ ] T006 Add missing imports for Response, Activity to `backend/src/api/activities.py`
- [ ] T007 Add missing imports for func, HealthMetric to `backend/src/api/metrics.py`
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
---
## Phase 3: User Story 1 - Sync Activities from Garmin (Priority: P1) 🎯 MVP
**Goal**: Users can trigger a synchronization of their recent activities from their Garmin account so that their fitness data is stored and available within the application.
**Independent Test**: Trigger a sync and verify recent activities appear in the activity list. Also, verify error message when Garmin account is not connected.
### Tests for User Story 1
- [ ] T008 [US1] Write unit tests for `POST /api/sync/activities` endpoint in `backend/tests/unit/test_api/test_sync.py`
- [ ] T009 [US1] Write integration tests for activity sync flow in `backend/tests/integration/test_sync_flow.py`
### Implementation for User Story 1
- [ ] T010 [US1] Implement activity sync logic in `backend/src/api/sync.py` (replace mock response with actual sync logic)
- [ ] T011 [US1] Implement `sync_activities` method in `backend/src/services/sync_app.py` to use `GarminClient`
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
---
## Phase 4: User Story 3 - Sync Health Metrics from Garmin (Priority: P1)
**Goal**: Users can trigger a synchronization of their daily health metrics (like steps and HRV) from their Garmin account to track their wellness over time.
**Independent Test**: Trigger a metric sync and see updated data in their health dashboard or metric query views.
### Tests for User Story 3
- [ ] T012 [US3] Write unit tests for `POST /api/sync/metrics` endpoint in `backend/tests/unit/test_api/test_sync.py`
- [ ] T013 [US3] Write integration tests for health metrics sync flow in `backend/tests/integration/test_sync_flow.py`
### Implementation for User Story 3
- [ ] T014 [US3] Create `POST /api/sync/metrics` endpoint in `backend/src/api/sync.py`
- [ ] T015 [US3] Implement `sync_health_metrics` method in `backend/src/services/sync_app.py` to use `garth` library
**Checkpoint**: At this point, User Stories 1 AND 3 should both work independently
---
## Phase 5: User Story 2 - View and Query Activities (Priority: P2)
**Goal**: Users can list their synced activities, filter them by criteria like activity type and date, and download the original activity file for their records.
**Independent Test**: Navigate to the activity list, apply filters, and attempt to download a file.
### Tests for User Story 2
- [ ] T016 [US2] Write unit tests for `GET /api/activities/list` endpoint in `backend/tests/unit/test_api/test_activities.py`
- [ ] T017 [US2] Write unit tests for `GET /api/activities/query` endpoint in `backend/tests/unit/test_api/test_activities.py`
- [ ] T018 [US2] Write unit tests for `GET /api/activities/download/{activity_id}` endpoint in `backend/tests/unit/test_api/test_activities.py`
### Implementation for User Story 2
- [ ] T019 [US2] Implement `list_activities` endpoint logic in `backend/src/api/activities.py`
- [ ] T020 [US2] Implement `query_activities` endpoint logic in `backend/src/api/activities.py`
- [ ] T021 [US2] Implement `download_activity` endpoint logic in `backend/src/api/activities.py`
**Checkpoint**: At this point, User Stories 1, 3 and 2 should all work independently
---
## Phase 6: User Story 4 - View and Query Health Metrics (Priority: P2)
**Goal**: Users can see a list of all the metric types they've synced, query their data for specific time ranges, and view a high-level summary.
**Independent Test**: Navigate to the metrics section, select a metric type and date range, and view the resulting data and summary.
### Tests for User Story 4
- [ ] T022 [US4] Write unit tests for `GET /api/metrics/list` endpoint in `backend/tests/unit/test_api/test_metrics.py`
- [ ] T023 [US4] Write unit tests for `GET /api/metrics/query` endpoint in `backend/tests/unit/test_api/test_metrics.py`
- [ ] T024 [US4] Write unit tests for `GET /api/health-data/summary` endpoint in `backend/tests/unit/test_api/test_metrics.py`
### Implementation for User Story 4
- [ ] T025 [US4] Implement `list_available_metrics` endpoint logic in `backend/src/api/metrics.py`
- [ ] T026 [US4] Implement `query_metrics` endpoint logic in `backend/src/api/metrics.py`
- [ ] T027 [US4] Implement `get_health_summary` endpoint logic in `backend/src/api/metrics.py`
**Checkpoint**: All user stories should now be independently functional
---
## Phase 7: Polish & Cross-Cutting Concerns
**Purpose**: Improvements that affect multiple user stories
- [ ] T028 Review error handling across all new endpoints in `backend/src/api/`
- [ ] T029 Review logging implementation across `backend/src/api/` and `backend/src/services/`
- [ ] T030 Add/update documentation for new API endpoints (e.g., OpenAPI spec, inline comments) in `specs/002-fitbit-garmin-sync/contracts/api-contract.yaml` and relevant Python files.
- [ ] T031 Run quickstart.md validation as described in `specs/002-fitbit-garmin-sync/quickstart.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 → P1 → P2 → P2)
- **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 3 (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 4 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1/US3/US2 but should be independently testable
### Within Each User Story
- Tests MUST be written and FAIL before implementation
- Models before services (where applicable)
- Services before endpoints (where applicable)
- Core implementation before integration
- Story complete before moving to next priority
### Parallel Opportunities
- All Setup tasks can run in parallel.
- All Foundational tasks can run in parallel (within Phase 2).
- Once Foundational phase completes, user stories can start in parallel by different team members.
- Tests for a user story can run in parallel.
- Implementation tasks within a user story can be identified for parallel execution where there are no dependencies.
## Implementation Strategy
### MVP First (User Stories 1 & 3)
1. Complete Phase 1: Setup
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
3. Complete Phase 3: User Story 1
4. Complete Phase 4: User Story 3
5. **STOP and VALIDATE**: Test User Stories 1 and 3 independently.
6. 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 3 → Test independently → Deploy/Demo
4. Add User Story 2 → Test independently → Deploy/Demo
5. Add User Story 4 → Test independently → Deploy/Demo
6. 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 3
- Developer C: User Story 2
- Developer D: User Story 4
3. Stories complete and integrate independently
---
## Notes
- Tasks with file paths indicate specific files to be created or modified.
- 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