1.x updates

This commit is contained in:
2026-05-19 08:34:22 -04:00
parent 3f3fce62d3
commit 9d91ac8ebc
53 changed files with 4541 additions and 2111 deletions

View File

@@ -1,26 +1,59 @@
# Garmin → local SQLite
# openrun — endurance running analytics
A small, self-contained pipeline that pulls Garmin Connect data into a local SQLite database and a stack of pandas notebooks. The aim is a *reference* you can run against your own data — not a polished product — so this README focuses on what's in the data, what gets computed from it, and exactly how each derived metric is defined.
A local-first, open-source endurance-training analysis pipeline. Garmin data in (live API or official export), SQLite on disk, pandas notebooks out. Banister CTL/ATL/TSB, Pa:HR decoupling (per-mile and per-second from FIT), Garmin-configured HR-zone time-in-zone, route clustering, race-plan projection with forward CTL/ATL/TSB.
This README focuses on what's in the data, what gets computed from it, and how each derived metric is defined. The package name `openrun` is provisional; the local repo can be renamed without code changes.
```
garmin/
├── auth.py # Path B: OAuth login for live API
├── sync.py # Path B: incremental sync via garth
├── ingest_export.py # Path A: parse Garmin Connect data export
├── link_fit_files.py # match Takeout FIT files to activities by start_time
├── compute_time_in_zone.py # cache per-activity HR-zone breakdowns
├── analysis.py # shared loaders + derived metrics
├── db.py # schema + connection helpers
├── data/garmin.db # SQLite (created on first run)
└── notebooks/
├── 01_overview.ipynb # data coverage, recent activity
├── 02_running.ipynb # volume, pace, ACWR, PRs
├── 03_recovery.ipynb # sleep, HRV, RHR, body battery
── 04_efficiency.ipynb # m/beat trends year over year
├── 05_intra_run.ipynb # decoupling, cadence, routes, HR zones
── 06_race_plan.ipynb # build a periodised plan + adherence tracker
openrun/
├── pyproject.toml # packaged as `openrun` (src layout, hatchling)
├── openrun.toml # user config (HR zones, weight, LTHR, race calendar)
├── src/openrun/
│ ├── __init__.py # re-exports the common API
├── config.py # UserProfile, HRZones, BanisterParams, TOML loader
│ ├── db.py # SQLite schema + connection helper
│ ├── model.py # loaders + derived metrics (was analysis.py)
│ └── ingest/
│ ├── auth.py # Garmin OAuth login
├── garmin_api.py # Path B: incremental sync via garth
├── garmin_export.py # Path A: parse Connect/Takeout JSON + index FITs
├── fit_linker.py # match Takeout FITs to activities by session.start_time
── time_in_zone.py # cache per-activity HR-zone breakdowns
├── examples/notebooks/
── 01_overview.ipynb # data coverage, recent activity
│ ├── 02_running.ipynb # volume, pace, PMC (Banister), PRs
│ ├── 03_recovery.ipynb # sleep, HRV, RHR, body battery
│ ├── 04_efficiency.ipynb # m/beat trends year over year
│ ├── 05_intra_run.ipynb # decoupling, cadence, routes, HR zones
│ └── 06_race_plan.ipynb # periodised plan + tracker + projected PMC
├── data/garmin.db # SQLite (created on first run)
└── {auth,sync,ingest_export,link_fit_files,compute_time_in_zone,db,analysis}.py
# thin top-level shims for back-compat
```
Most callers want:
```python
from openrun import open_conn, load_activities, banister, daily_training_load_series
conn = open_conn()
runs = load_activities(conn, type='running')
pmc = banister(daily_training_load_series(conn))
```
User-specific values (HR zones, LTHR, weight, race calendar) live in `openrun.toml` — config discovery walks up from cwd looking for it. See [openrun.toml](openrun.toml) for the schema.
CLI entry points (after `uv sync`):
```bash
openrun-auth # OAuth login (Path B)
openrun-sync [--full] # incremental Garmin Connect sync
openrun-ingest <export> # parse a Connect/Takeout export
openrun-link-fit <export> # match Takeout FITs to activities by content
openrun-time-in-zone # populate the activity_time_in_zone cache
```
Top-level shims (`uv run sync.py`, `uv run ingest_export.py`, etc.) still work — they re-export `main()` from the corresponding `openrun.ingest.*` module.
---
## 1 — Source data