A local ingestion and analysis tool for Strava activity data. Ingest your Strava export (CSV + FIT files) into a local SQLite database for analysis and visualization.
All data stays local - your activity data, GPS tracks, and training metrics are never uploaded anywhere.
- Dashboard: Summary statistics, activity breakdown charts, recent activities
- Activity Browser: Searchable, filterable table with detailed activity pages
- Maps: Interactive heatmap and route explorer with filters
- Training Metrics: TSS, CTL/ATL/TSB (fitness/fatigue/form), HR zones, TRIMP
- Analysis: Personal records, heart rate statistics
- Pace & Speed: Displays pace (min/mile) for runs, speed (mph) for other activities
Dashboard with activity stats, breakdown charts, and recent activities
Interactive heatmap of all your activities
CTL/ATL/TSB fitness and fatigue tracking over time
Detailed activity view with route map and metrics
# Clone the repository
git clone https://github.com/yourusername/strava-local.git
cd strava-local
# Run the interactive setup
python3 setup.pyThe setup script will:
- Check your Python version (3.10+ required)
- Create a virtual environment
- Install dependencies
- Initialize the database
- Optionally configure your athlete profile (max HR, LTHR, etc.)
- Go to Strava Settings
- Click "My Account" in the sidebar
- Click "Get Started" under "Download or Delete Your Account"
- Click "Request Your Archive"
- Wait for email (can take a few hours)
- Download and extract the ZIP file
- Copy these to the project root:
activities/- folder containing your .fit.gz filesactivities.csv- file with activity metadata
# Activate virtual environment (if not already)
source .venv/bin/activate
# Import activities from CSV and FIT files
python -m scripts.ingestThe ingestion is idempotent - re-running will update existing records rather than creating duplicates.
python -m scripts.compute_metricsThis calculates:
- TSS (Training Stress Score) - workout intensity
- TRIMP (Training Impulse) - HR-based training load
- HR Zones - time spent in each heart rate zone
- CTL/ATL/TSB - fitness, fatigue, and form over time
uvicorn web.app:app --reloadThen open http://localhost:8000 in your browser.
Note: The
--reloadflag enables auto-restart on file changes (useful for development). Omit it for production use.
For accurate training metrics, configure your athlete profile at /settings or during setup:
- Max Heart Rate: Your maximum heart rate (bpm)
- Resting Heart Rate: Your resting heart rate (bpm)
- LTHR: Lactate threshold heart rate (bpm)
- FTP: Functional threshold power (watts) - for cycling
- Weight: Body weight (kg) - for power-to-weight calculations
If not set, the system will estimate values based on your data.
strava-local/
├── activities/ # Your Strava FIT/GPX files (from export)
├── activities.csv # Your Strava activities CSV (from export)
├── data/ # SQLite database location
│ └── strava_local.db
├── db/ # Database models and schema
├── ingest/ # CSV and FIT file parsing
├── metrics/ # Training metrics computation
├── scripts/ # CLI entry points
├── web/ # Web application
│ ├── routes/ # API and page routes
│ ├── services/ # Business logic
│ ├── templates/ # Jinja2 HTML templates
│ └── static/ # CSS and JS files
├── setup.py # Interactive setup script
├── requirements.txt
└── README.md
python -m scripts.statspython -m scripts.latest# Heatmap of all GPS points
python -m scripts.map --heatmap
# All routes overlaid
python -m scripts.map --routes
# Single activity
python -m scripts.map --activity <ACTIVITY_ID>
# Filter by type or date
python -m scripts.map --routes --type Run
python -m scripts.map --heatmap --after 2024-01-01 --before 2024-12-31python -m scripts.ingest --csv activities.csv --fit-dir activities/ --db data/custom.dbOptions:
--csv PATH: Path to activities CSV file--fit-dir PATH: Path to directory containing FIT files--db PATH: Custom database path--quiet: Suppress progress output
from db.models import get_session, Activity, Stream
session = get_session()
# Find all runs with GPS
runs_with_gps = (
session.query(Activity)
.join(Stream)
.filter(Activity.activity_type == "Run")
.filter(Stream.has_gps == True)
.all()
)Or with SQLite CLI:
sqlite3 data/strava_local.db "SELECT activity_type, COUNT(*), SUM(distance)/1609 as miles FROM activities GROUP BY activity_type"Activity metadata from CSV: activity_id, name, activity_type, start_time, distance, moving_time, avg_speed, avg_hr, elevation_gain, calories, etc.
FIT file metadata: activity_id, fit_path, file_size, sha256, fit_sport
Time-series data from FIT files: route (GPS), heart_rate, altitude, has_gps
Computed training metrics: tss, trimp, intensity_factor, hr_z1_time through hr_z5_time
User settings: max_hr, resting_hr, lthr, ftp, weight_kg
Daily CTL/ATL/TSB values for fitness tracking
- Python 3.10+
- macOS/Linux/Windows
- FIT files are matched to CSV activities by activity ID or start time
- GPS routes are downsampled to max 500 points to reduce storage
- All units displayed in imperial (miles, feet, mph, min/mile)