feat: Update spec, fix bugs, improve UI/UX, and clean up code
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
65
FitnessSync/specs/002-fitbit-garmin-sync/data-model.md
Normal file
65
FitnessSync/specs/002-fitbit-garmin-sync/data-model.md
Normal 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. |
|
||||
77
FitnessSync/specs/002-fitbit-garmin-sync/plan.md
Normal file
77
FitnessSync/specs/002-fitbit-garmin-sync/plan.md
Normal 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.*
|
||||
78
FitnessSync/specs/002-fitbit-garmin-sync/quickstart.md
Normal file
78
FitnessSync/specs/002-fitbit-garmin-sync/quickstart.md
Normal 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;
|
||||
```
|
||||
30
FitnessSync/specs/002-fitbit-garmin-sync/research.md
Normal file
30
FitnessSync/specs/002-fitbit-garmin-sync/research.md
Normal 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.
|
||||
157
FitnessSync/specs/002-fitbit-garmin-sync/spec.md
Normal file
157
FitnessSync/specs/002-fitbit-garmin-sync/spec.md
Normal 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.
|
||||
215
FitnessSync/specs/002-fitbit-garmin-sync/tasks.md
Normal file
215
FitnessSync/specs/002-fitbit-garmin-sync/tasks.md
Normal 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
|
||||
Reference in New Issue
Block a user