2025-11-17 06:26:37 -08:00
2025-10-06 12:54:15 -07:00
2025-11-17 06:26:37 -08:00
2025-11-17 06:26:37 -08:00
2025-08-30 19:26:07 -07:00
2025-09-26 07:30:15 -07:00
2025-10-06 12:54:15 -07:00
2025-10-06 12:54:15 -07:00
2025-08-31 12:44:45 -07:00
2025-10-06 12:54:15 -07:00
2025-10-06 12:54:15 -07:00
2025-10-06 12:54:15 -07:00
2025-09-26 07:30:15 -07:00
2025-10-07 06:45:32 -07:00
2025-11-17 06:26:37 -08:00
2025-11-17 06:26:37 -08:00
2025-11-17 06:26:37 -08:00
2025-11-17 06:26:37 -08:00
2025-09-26 07:30:15 -07:00
2025-08-31 07:40:38 -07:00

Garmin Analyser

A comprehensive Python application for analyzing Garmin workout data from FIT, TCX, and GPX files, as well as direct integration with Garmin Connect. Provides detailed power, heart rate, and performance analysis with beautiful visualizations and comprehensive reports via a modular command-line interface.

Features

  • Multi-format Support: Parse FIT files. TCX and GPX parsing is not yet implemented and is planned for a future enhancement.
  • Garmin Connect Integration: Direct download from Garmin Connect
  • Comprehensive Analysis: Power, heart rate, speed, elevation, and zone analysis
  • Advanced Metrics: Normalized Power, Intensity Factor, Training Stress Score
  • Interactive Charts: Power curves, heart rate zones, elevation profiles
  • Detailed Reports: HTML, PDF, and Markdown reports with customizable templates
  • Interval Detection: Automatic detection and analysis of workout intervals
  • Performance Tracking: Long-term performance trends and summaries

Installation

Requirements

  • Python 3.8 or higher
  • pip package manager

Install Dependencies

pip install -r requirements.txt

Database Setup (New Feature)

The application now uses SQLite with Alembic for database migrations to track downloaded activities. To initialize the database:

# Run database migrations
alembic upgrade head

Optional Dependencies

For PDF report generation:

pip install weasyprint

Quick Start

Basic Usage

The application uses a subcommand-based CLI structure. Here are some basic examples:

Analyze a single workout file:

python main.py analyze --file path/to/workout.fit --report --charts

Analyze all workouts in a directory:

python main.py batch --directory path/to/workouts --summary --format html

Download and analyze latest workout from Garmin Connect:

python main.py analyze --garmin-connect --report --charts

Download all cycling activities from Garmin Connect:

python main.py download --all --limit 100 --output-dir data/garmin_downloads

Download only missing activities (not already in database or filesystem):

python main.py download --missing --output-dir data/garmin_downloads

Dry-run to see what would be downloaded without actually downloading:

python main.py download --missing --dry-run --output-dir data/garmin_downloads

Re-analyze previously downloaded workouts:

python main.py reanalyze --input-dir data/garmin_downloads --output-dir reports/reanalysis --charts --report

Force re-download of specific activity (bypasses database tracking):

python main.py download --workout-id 123456789 --force

Show current configuration:

python main.py config --show

Command Line Options

For a full list of commands and options, run:

python main.py --help
python main.py [command] --help

Example output for python main.py --help:

usage: main.py [-h] [--verbose] {analyze,batch,download,reanalyze,config} ...

Analyze Garmin workout data from files or Garmin Connect

positional arguments:
  {analyze,batch,download,reanalyze,config}
                        Available commands
    analyze             Analyze a single workout or download from Garmin Connect
    batch               Analyze multiple workout files in a directory
    download            Download activities from Garmin Connect
    reanalyze           Re-analyze all downloaded activities
    config              Manage configuration

options:
  -h, --help            show this help message and exit
  --verbose, -v         Enable verbose logging

Example output for python main.py analyze --help:

usage: main.py analyze [-h] [--file FILE] [--garmin-connect] [--workout-id WORKOUT_ID]
                       [--ftp FTP] [--max-hr MAX_HR] [--zones ZONES] [--cog COG]
                       [--output-dir OUTPUT_DIR] [--format {html,pdf,markdown}]
                       [--charts] [--report]

Analyze a single workout or download from Garmin Connect

options:
  -h, --help            show this help message and exit
  --file FILE, -f FILE  Path to workout file (FIT, TCX, or GPX)
  --garmin-connect      Download and analyze latest workout from Garmin Connect
  --workout-id WORKOUT_ID
                         Analyze specific workout by ID from Garmin Connect
  --ftp FTP             Functional Threshold Power (W)
  --max-hr MAX_HR       Maximum heart rate (bpm)
  --zones ZONES         Path to zones configuration file
  --cog COG             Cog size (teeth) for power calculations. Auto-detected if not provided
  --output-dir OUTPUT_DIR
                         Output directory for reports and charts
  --format {html,pdf,markdown}
                         Report format
  --charts              Generate charts
  --report              Generate comprehensive report
  --force               Force download even if activity already exists in database
  --missing             Download only activities not already in database or filesystem
  --dry-run             Show what would be downloaded without actually downloading

Configuration:

Set Garmin credentials in .env file: GARMIN_EMAIL and GARMIN_PASSWORD. Configure zones in config/config.yaml or use --zones flag. Override FTP with --ftp flag, max HR with --max-hr flag.

Output:

Reports saved to output/ directory by default. Charts saved to output/charts/ when --charts is used.

Deprecation Notice

The Text User Interface (TUI) and legacy analyzer have been removed in favor of the more robust and maintainable modular command-line interface (CLI) implemented solely in main.py. The cli.py file has been removed. All functionality from the legacy components has been successfully migrated to the modular stack.

Setup credentials

Canonical environment variables:

  • GARMIN_EMAIL
  • GARMIN_PASSWORD

Single source of truth:

  • Credentials are centrally accessed via get_garmin_credentials(). If GARMIN_EMAIL is not set but GARMIN_USERNAME is present, the username value is used as email and a one-time deprecation warning is logged. GARMIN_USERNAME is deprecated and will be removed in a future version.

Linux/macOS (bash/zsh):

export GARMIN_EMAIL="you@example.com"
export GARMIN_PASSWORD="your-app-password"

Windows PowerShell:

$env:GARMIN_EMAIL = "you@example.com"
$env:GARMIN_PASSWORD = "your-app-password"

.env sample:

GARMIN_EMAIL=you@example.com
GARMIN_PASSWORD=your-app-password

Note on app passwords:

  • If your Garmin account uses two-factor authentication or app-specific passwords, create an app password in your Garmin account settings and use it for GARMIN_PASSWORD.

Parity and unaffected behavior:

  • Authentication and download parity is maintained. Original ZIP downloads and FIT extraction workflows are unchanged in clients/garmin_client.py.
  • Alternate format downloads (FIT, TCX, GPX) are unaffected by this credentials change.

Database Tracking

The application now tracks downloaded activities in a SQLite database (garmin_analyser.db) to avoid redundant downloads and provide download history.

Database Schema

The database tracks:

  • Activity ID and metadata
  • Download status and timestamps
  • File checksums and sizes
  • Error information for failed downloads

Database Location

By default, the database is stored at:

  • garmin_analyser.db in the project root directory

Migration Commands

# Initialize database schema
alembic upgrade head

# Create new migration (for developers)
alembic revision --autogenerate -m "description"

# Check migration status
alembic current

# Downgrade database
alembic downgrade -1

Configuration

Basic Configuration

Create a config/config.yaml file:

# Garmin Connect credentials
# Credentials are provided via environment variables (GARMIN_EMAIL, GARMIN_PASSWORD).
# Do not store credentials in config.yaml. See "Setup credentials" in README.

# Output settings
output_dir: output
log_level: INFO

# Training zones
zones:
  ftp: 250  # Functional Threshold Power (W)
  max_heart_rate: 185  # Maximum heart rate (bpm)
  
  power_zones:
    - name: Active Recovery
      min: 0
      max: 55
      percentage: true
    - name: Endurance
      min: 56
      max: 75
      percentage: true
    - name: Tempo
      min: 76
      max: 90
      percentage: true
    - name: Threshold
      min: 91
      max: 105
      percentage: true
    - name: VO2 Max
      min: 106
      max: 120
      percentage: true
    - name: Anaerobic
      min: 121
      max: 150
      percentage: true
  
  heart_rate_zones:
    - name: Zone 1
      min: 0
      max: 60
      percentage: true
    - name: Zone 2
      min: 60
      max: 70
      percentage: true
    - name: Zone 3
      min: 70
      max: 80
      percentage: true
    - name: Zone 4
      min: 80
      max: 90
      percentage: true
    - name: Zone 5
      min: 90
      max: 100
      percentage: true

Advanced Configuration

You can also specify zones configuration in a separate file:

# zones.yaml
ftp: 275
max_heart_rate: 190

power_zones:
  - name: Recovery
    min: 0
    max: 50
    percentage: true
  - name: Endurance
    min: 51
    max: 70
    percentage: true
  # ... additional zones

Usage Examples

Single Workout Analysis

# Analyze a single FIT file with custom FTP
python main.py --file workouts/2024-01-15-ride.fit --ftp 275 --report --charts

# Generate PDF report
python main.py --file workouts/workout.tcx --format pdf --report

# Quick analysis with verbose output
python main.py --file workout.gpx --verbose --report

Batch Analysis

# Analyze all files in a directory
python main.py --directory data/workouts/ --summary --charts --format html

# Analyze with custom zones
python main.py --directory data/workouts/ --zones config/zones.yaml --summary

Reports: normalized variables example

Reports consume normalized speed and heart rate keys in templates. Example (HTML template):

{# See workout_report.html #}
<p>Sport: {{ metadata.sport }} ({{ metadata.sub_sport }})</p>
<p>Speed: {{ summary.avg_speed_kmh|default(0) }} km/h; HR: {{ summary.avg_hr|default(0) }} bpm</p>

Garmin Connect Integration

# Download and analyze last 30 days
python main.py --garmin-connect --report --charts --summary

# Download specific period
python main.py --garmin-connect --report --output-dir reports/january/

# Download only missing activities (smart sync)
python main.py download --missing --output-dir data/garmin_downloads

# Preview what would be downloaded (dry-run)
python main.py download --missing --dry-run --output-dir data/garmin_downloads

# Force re-download of all activities (bypass database)
python main.py download --all --force --output-dir data/garmin_downloads

Output Structure

The application creates the following output structure:

output/
├── charts/
│   ├── workout_20240115_143022_power_curve.png
│   ├── workout_20240115_143022_heart_rate_zones.png
│   └── ...
├── reports/
│   ├── workout_report_20240115_143022.html
│   ├── workout_report_20240115_143022.pdf
│   └── summary_report_20240115_143022.html
└── logs/
    └── garmin_analyser.log

garmin_analyser.db          # SQLite database for download tracking
alembic/                    # Database migration scripts

Analysis Features

Power Analysis

  • Average Power: Mean power output
  • Normalized Power: Adjusted power accounting for variability
  • Maximum Power: Peak power output
  • Power Zones: Time spent in each power zone
  • Power Curve: Maximum power for different durations

Heart Rate Analysis

  • Average Heart Rate: Mean heart rate
  • Maximum Heart Rate: Peak heart rate
  • Heart Rate Zones: Time spent in each heart rate zone
  • Heart Rate Variability: Analysis of heart rate patterns

Performance Metrics

  • Intensity Factor (IF): Ratio of Normalized Power to FTP
  • Training Stress Score (TSS): Overall training load
  • Variability Index: Measure of power consistency
  • Efficiency Factor: Ratio of Normalized Power to Average Heart Rate

Interval Detection

  • Automatic detection of high-intensity intervals
  • Analysis of interval duration, power, and recovery
  • Summary of interval performance

Analysis outputs and normalized naming

The analyzer and report pipeline now provide normalized keys for speed and heart rate to ensure consistent units and naming across code and templates. See WorkoutAnalyzer.analyze_workout() and ReportGenerator._prepare_report_data() for implementation details.

  • Summary keys:
    • summary.avg_speed_kmh — Average speed in km/h (derived from speed_mps)
    • summary.avg_hr — Average heart rate in beats per minute (bpm)
  • Speed analysis keys:
    • speed_analysis.avg_speed_kmh — Average speed in km/h
    • speed_analysis.max_speed_kmh — Maximum speed in km/h
  • Heart rate analysis keys:
    • heart_rate_analysis.avg_hr — Average heart rate (bpm)
    • heart_rate_analysis.max_hr — Maximum heart rate (bpm)
  • Backward-compatibility aliases maintained in code:
    • summary.avg_speed — Alias of avg_speed_kmh
    • summary.avg_heart_rate — Alias of avg_hr

Guidance: templates should use the normalized names going forward.

Templates: variables and metadata

Templates should reference normalized variables and the workout metadata fields:

  • Use metadata.sport and metadata.sub_sport instead of activity_type.
  • Example snippet referencing normalized keys:
    • speed: {{ summary.avg_speed_kmh }} km/h; HR: {{ summary.avg_hr }} bpm
  • For defensive rendering, Jinja defaults may be used (e.g., {{ summary.avg_speed_kmh|default(0) }}), though normalized keys are expected to be present.

Reference templates:

Migration note

  • Legacy template fields avg_speed and avg_heart_rate are deprecated; the code provides aliases (summary.avg_speed → avg_speed_kmh, summary.avg_heart_rate → avg_hr) to prevent breakage temporarily.
  • Users should update custom templates to use avg_speed_kmh and avg_hr.
  • metadata.activity_type is replaced by metadata.sport and metadata.sub_sport.

Customization

Custom Report Templates

You can customize report templates by modifying the files in visualizers/templates/:

  • workout_report.html: HTML report template
  • workout_report.md: Markdown report template
  • summary_report.html: Summary report template

Adding New Analysis Metrics

Extend the WorkoutAnalyzer class in analyzers/workout_analyzer.py:

def analyze_custom_metric(self, workout: WorkoutData) -> dict:
    """Analyze custom metric."""
    # Your custom analysis logic here
    return {'custom_metric': value}

Custom Chart Types

Add new chart types in visualizers/chart_generator.py:

def generate_custom_chart(self, workout: WorkoutData, analysis: dict) -> str:
    """Generate custom chart."""
    # Your custom chart logic here
    return chart_path

Troubleshooting

Common Issues

File Not Found Errors

  • Ensure file paths are correct and files exist
  • Check file permissions

Garmin Connect Authentication

  • Verify GARMIN_EMAIL and GARMIN_PASSWORD environment variables (or entries in your .env) are set; fallback from GARMIN_USERNAME logs a one-time deprecation warning via get_garmin_credentials()
  • Check internet connection
  • Ensure Garmin Connect account is active

Missing Dependencies

  • Run pip install -r requirements.txt
  • For PDF support: pip install weasyprint

Performance Issues

  • For large datasets, use batch processing
  • Consider using --summary flag for multiple files

Database Issues

  • If database becomes corrupted, delete garmin_analyser.db and run alembic upgrade head
  • Check database integrity: sqlite3 garmin_analyser.db "PRAGMA integrity_check;"

Debug Mode

Enable verbose logging for troubleshooting:

python main.py --verbose --file workout.fit --report

API Reference

Core Classes

  • WorkoutData: Main workout data structure
  • WorkoutAnalyzer: Performs workout analysis
  • ChartGenerator: Creates visualizations
  • ReportGenerator: Generates reports
  • GarminClient: Handles Garmin Connect integration

Example API Usage

from pathlib import Path
from config.settings import Settings
from parsers.file_parser import FileParser
from analyzers.workout_analyzer import WorkoutAnalyzer

# Initialize components
settings = Settings('config/config.yaml')
parser = FileParser()
analyzer = WorkoutAnalyzer(settings.zones)

# Parse and analyze workout
workout = parser.parse_file(Path('workout.fit'))
analysis = analyzer.analyze_workout(workout)

# Access results
print(f"Average Power: {analysis['summary']['avg_power']} W")
print(f"Training Stress Score: {analysis['summary']['training_stress_score']}")

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Submit a pull request

License

MIT License - see LICENSE file for details.

Support

For issues and questions:

  • Check the troubleshooting section
  • Review log files in output/logs/
  • Open an issue on GitHub
Description
No description provided
Readme 1.6 MiB
Languages
Python 96.1%
HTML 3.9%