Garmin Local Archive





[](https://github.com/Wewoc/Garmin_Local_Archive/actions/workflows/test-suite.yml)
[](https://glama.ai/mcp/servers/Wewoc/Garmin_Local_Archive)
**Archive and analyze your Garmin Health data local.**
*Privacy first — inspired by European principles.*
---
## A look at the app
<a href="src/screenshots/GUI-Page_1.jpg"><img src="src/screenshots/GUI-Page_1.jpg" width="150" title="Home tab — fixed top area with connection status, archive stats by device, and Daily Actions (Sync / Mirror / Timer); dashboard viewer below."></a>
<a href="src/screenshots/GUI-Page_2.jpg"><img src="src/screenshots/GUI-Page_2.jpg" width="150" title="Files tab — in-app XLSX viewer with date, steps, resting HR, body battery, sleep duration and quality per day."></a>
<a href="src/screenshots/GUI-Page_3.jpg"><img src="src/screenshots/GUI-Page_3.jpg" width="150" title="Settings tab — two-column layout: credentials, paths, and sync config on the left; sync controls, timer, mirror, and output options on the right."></a>
<a href="src/screenshots/GUI-Page_4.jpg"><img src="src/screenshots/GUI-Page_4.jpg" width="150" title="Chat tab — chat against a local Ollama model or a Cloud LLM (Anthropic/OpenAI), with optional live tool-calling against your archive via the MCP server for questions the daily summary alone can't answer. Chat sessions can be saved and resumed."></a>
<a href="src/screenshots/GUI-Page_5.jpg"><img src="src/screenshots/GUI-Page_5.jpg" width="150" title="MCP Server tab — backend, port and headless-mode selection, Save Settings and Start MCP Server buttons."></a>
<sub>Home · Files · Settings · Chat · MCP Server — click any tab to view full size.</sub>
---
## Download
| Version | Description | Requires |
|---|---|---|
| [Garmin_Local_Archive_Standalone.zip](https://github.com/Wewoc/Garmin_Local_Archive/releases/latest) | **Recommended — no setup needed** | Nothing |
| [Garmin_Local_Archive.zip](https://github.com/Wewoc/Garmin_Local_Archive/releases/latest) | Standard version | Python 3.10+ |
No install, no terminal. Download, unzip, run.
Standard version: install dependencies first — `pip install -r requirements.txt`.
The app checks GitHub for a new version on startup. Updates can be
downloaded and applied automatically; on the Standalone version this
includes a restart, on the standard version you start it again yourself.
---
## Try it first
No Garmin account yet, or just want to see it in action? A synthetic
180-day demo archive (fictional data, clearly marked as such) lets you
explore dashboards, Chat and the MCP server without connecting a real
account. See [QUICKSTART.txt](src/docs/QUICKSTART.txt), "Try it first".
---
## Project status & disclaimer
> GNU General Public License v3.0 — provided as-is, without warranty of any kind, express or implied.
- **Not an official Garmin product:** This tool is not affiliated with, endorsed, or supported by Garmin.
- **Not medical advice:** All health metrics, reference ranges, and dashboard data are for personal informational use only — not a substitute for medical advice.
- **AI and health data — handle with care:** If you use an external AI service (ChatGPT, Claude, Gemini) to interpret your data: never upload documents containing your name, date of birth, or other identifying information. Cloud AI services store what you send — linked to your account. Use a local model (Ollama) or at minimum a session without login. This includes the app's own in-app **Chat** tab — its optional Cloud backend (Anthropic/OpenAI) sends your question and any archive data the model requests to that provider's API, same as pasting into their website; with the Ollama backend, local models stay fully local, while an Ollama cloud model (name ending in `-cloud`) is processed on Ollama's servers. AI responses on health topics are statistically generated — not medically validated. Treat them as a first orientation, not a conclusion.
- **Context data:** Weather data is provided by Open-Meteo and Brightsky (DWD), pollen data and air quality data by Open-Meteo — accuracy and availability are not guaranteed. Air quality data (CAMS dataset) is available from approximately 2020 onwards.
- **Early stage:** Core functionality is stable. APIs and internal structure may still change.
- **No guaranteed support:** Development happens when time and interest allow.
- **Use at your own risk:** I am not responsible for data loss or Garmin account issues.
- **Feedback welcome:** If something feels off — logic, structure, results — open an issue.
**Scope & limitations:** Local-first, personal use, no enterprise ambitions.
- Relies on python-garminconnect. Since Garmin does not offer a public API for personal use, moste non-commercial tools share this dependency. To mitigate unannounced upstream changes, GLA automatically detects and logs structural API shifts.
- Local test suites cover the full pipeline plus a separate build-output validation suite. The full suite also runs automatically via GitHub Actions on every push to main (Windows runner), alongside dead-code and CVE-relevance checks; CodeQL security scanning runs on every push/PR to main
- HTML dashboards require a one-time internet connection to download Plotly (~3 MB) — cached locally after that
- Per-day checkpointing: an interrupted sync resumes from the last completed day, no full re-sync required
- Historical data quality depends on Garmin servers
This project is built for my own use. If it happens to be useful to others, feel free to use it — but evaluate it like any other unverified open-source tool.
**What this is not:**
Garmin Connect is still required — the app pulls data from there via API.
**A note on cloud folders:** the archive itself is stored as plaintext on disk — if `garmin_data/` lives inside a cloud-synced folder, that data gets uploaded automatically. See [SECURITY.md](SECURITY.md#container-security) for details and the encrypted Mirror alternative. This tool does not replace Connect, the Garmin app, or your device sync. It has no cloud component, no remote access, and no sharing features. The GUI and EXE are Windows-only.
---
## Why this exists
I wanted to ask an AI questions about my health data without sending that data to another cloud service. So I built a local alternative instead.
What "intraday resolution" actually means in practice:
| Metric | API resolution | Data points / day |
|---|---|---|
| Heart Rate | ~1 minute | up to 1,440 |
| Stress | ~3 minutes | up to 480 |
| Body Battery | ~15 minutes | up to 96 |
| SpO2 | ~1 hour | up to 24 |
| Respiration | variable | variable |
*→ For the full story, see [MINDSET.md](src/docs/MINDSET.md).*
---
## What makes this different
This project is as much a statement as it is a tool.
This is not a data export script — it maintains a complete, consistent
local copy of your Garmin data over time. Your data stays in open formats, readable and analyzable with any tool you choose. Local AI, cloud AI, or no AI at all. **Your data, your call.**
| Feature | Garmin Connect | Cloud-AI Bridges | **Garmin Local Archive** |
| :--- | :--- | :--- | :--- |
| **Data storage** | Garmin servers (USA) | US AI servers | **Your machine** |
| **Privacy risk** | Medium | High (training data risk) | **Minimal** |
| **Access** | Online only | Requires subscription | **100% offline** |
| **History** | Erodes over time | Depends on source | **Permanent local copy** |
---
## AI-assisted development
I can't write Python. The architecture, module boundaries, and decisions are mine. Every line of code is Claude's.
*→ How this collaboration actually worked — who had which idea, where Claude was wrong — is documented in [MINDSET.md](src/docs/MINDSET.md).*
---
## How it works
The app works in two modes: **live sync** pulls recent data directly from Garmin Connect via API; **Bulk Import** loads your complete history from a Garmin GDPR export ZIP — this is the primary path for recovering years of data that the API no longer serves.
Everything is stored locally in structured formats (JSON, Excel, HTML dashboards). Once downloaded, nothing is transmitted anywhere.
---
### Bulk Import
The GDPR export from Garmin contains your complete daily history.
The **Bulk Import** feature fills in the rest: request your full data export from Garmin (typically ready in 20–30 minutes), point the app at the ZIP, and your complete daily history lands in the local archive — in the same format as live API data. Days already present with good quality are skipped automatically.
---
### Dashboards
The built-in dashboards cover roughly 90% of what most users are looking for — without any AI at all. For deeper analysis, your data is prepared in a format any local AI can work with directly.
| Dashboard | What it shows | Output |
|---|---|---|
| **Health Analysis** | HRV, Resting HR, SpO2, Sleep, Body Battery, Stress — daily values vs 90-day personal baseline vs age/fitness-adjusted reference ranges. Flags days outside range. | HTML, Mobile HTML, JSON + AI prompt |
| **Timeseries** | Intraday heart rate, stress, SpO2, body battery and respiration as zoomable charts across any date range. | HTML, Excel |
| **Heatmap** | Six intraday metrics (Heart Rate, Steps, Stress, Body Battery, SpO2, Respiration) as time-of-day × date grids — spot daily rhythms and irregularities at a glance. | HTML |
| **Daily Overview** | All summary fields in one flat table, one row per day. | Excel |
| **Health + Context** | Garmin health metrics alongside local weather and pollen data. | HTML, Excel |
| **Sleep Dashboard** | One row per night — segmented phase bar (Deep / Light / REM / Awake), sleep duration, score, quality badge, feedback label, HRV, Body Battery, and **7-day HRV moving average** (computed from archive, no extra API call). Color-coded numbers via continuous gradient against personal reference ranges. Inspired by [Garmin's own HRV pattern guide](https://www.garmin.com/en-US/blog/fitness/understanding-the-hrv-status-on-your-garmin-smartwatch/). | HTML, Excel |
| **Sleep & Recovery** | HRV, Body Battery, Sleep duration and phase breakdown (Deep / Light / REM / Awake) alongside weather and pollen context. Intraday detail per day. | HTML |
| **Explorer** | Free metric exploration — choose up to 4 metrics from all Garmin daily fields plus weather, pollen, and air quality on a shared time axis. Sleep phase breakdown and sleep quality log included. Built-in field descriptions and air quality interpretation guide. | HTML |
| **Custom Dashboard** | Pick any combination of Garmin daily fields and Context fields, set a date range, and build a one-off dashboard — no fixed field list, no specialist file written to disk. Field selections can be saved as named presets for reuse. Optional AES-256 encryption for the HTML output. | HTML, Excel |
<img src="src/screenshots/Create_report.jpg" width="800" alt="Garmin Local Archive — Create Report">
<br><sub>Create Dashboards — select dashboards and export as HTML, Excel or JSON.</sub>
<img src="src/screenshots/Dashboard.jpg" width="800" alt="Garmin Health Analysis Dashboard">
<br><sub>Analysis dashboard — daily values vs 90-day personal baseline vs age/fitness-adjusted reference ranges.</sub>
<img src="src/screenshots/sleep_dashboard.jpg" width="800" alt="Garmin Health Analysis Dashboard">
<br><sub>One row per night — segmented phase bar, duration, sleep score, quality badge, Garmin feedback text, HRV, and Body Battery. Numbers are color-coded against personal reference ranges.</sub>
<img src="src/screenshots/dashboard_mobile_landscape.jpg" width="800" alt="Garmin Health Analysis Dashboard">
<br><sub>Analysis dashboard mobile version — daily values vs 90-day personal baseline vs age/fitness-adjusted reference ranges.</sub>
**Live Tracking** — a separate, always-current view: today's progression (Body Battery, Heart Rate, Steps, Stress) plus last night's sleep summary, refreshed automatically after every sync and on demand via an "Update Live" button in the Home tab. Not part of the Create Dashboards selection above — it has its own trigger, by design.
---
### Export
Export Data writes your archive to external formats for other tools to
consume — a separate layer from the dashboards above, reading the same
broker data. Available from the **Dashboards** section's "Export Data"
button, per-adapter grid (toggle Health/Context/Metadata independently
per format):
| Adapter | Format | Notes |
|---|---|---|
| JSON | `.json` | Full archive, 1:1 broker output |
| CSV | `.csv` | One row per day, `health_<field>`/`context_<source>_<field>` columns — built for Excel/LibreOffice. Intraday/series fields excluded (too large for a usable spreadsheet). Metadata in a separate file. Delimiter (`;`/`,`) and decimal separator chosen together, so German Excel opens it correctly. |
| InfluxDB Line Protocol | `.txt` | Full resolution incl. intraday/series data — for feeding [garmin-grafana](https://github.com/arpanghosh8453/garmin-grafana) or similar tools without a Garmin API call of their own. |
"Save & Add to Daily Sync" persists your selection and runs it
automatically as part of every daily sync — no need to export by hand
after each sync.
<img src="src/screenshots/export_data.jpg" width="800" alt="Garmin Local Archive — Export Data">
<br><sub>Export Data — per-adapter grid for Health/Context/Metadata, JSON/CSV/InfluxDB.</sub>
---
### Chat
A native chat panel is built into the app (**Chat** tab, renamed
from "Ollama-Chat") — no separate setup beyond having Ollama
itself installed and a model pulled, or a Cloud API key configured. Two
independent choices, made before Start:
- **Backend** — a local Ollama model (fully local, no setup beyond Ollama
itself), or a Cloud LLM (Anthropic or OpenAI — requires an API
key, configured on the **MCP Server** tab; see the AI disclaimer above
for what that means for your data).
- **Source** — the archived daily-summary snapshot (fast, no live query,
the original behavior), or live **MCP tool-calling** —
the model queries your archive on demand via the same MCP server external
tools use, for questions the daily snapshot alone can't answer.
Replies stream in as they're generated for every combination except
Ollama + MCP tool-calling (Ollama's own streaming support for tool-calling
is not yet reliable upstream, so that one path waits for the complete
answer). Chat sessions auto-save and can be reloaded later via the
**Chat History** button — a session using live MCP data can always be
resumed; a session using the daily snapshot can only be resumed if that
snapshot hasn't changed since, otherwise it opens read-only. For connecting
external tools (Open WebUI, AnythingLLM, Claude Desktop) for more advanced
document/RAG workflows instead, see [`docs/README_APP.md`](src/docs/README_APP.md) and the **MCP
Server** section below.
---
### MCP Server
Garmin Local Archive exposes your archive to local LLMs via the [Model
Context Protocol](https://modelcontextprotocol.io/) — ask an LLM about your
health and context data directly, without exporting files or copy-pasting
into a chat window. Runs as its own standalone process, independent of the
main app.
Two ways to run it: a **▶️ Start MCP Server** button on the app's own
**MCP Server** tab (uses the same archive path and settings as the rest of
the app), or the standalone `mcp_server.exe` — a self-contained tool with
its own small window, usable even without the main app installed.
<img src="src/screenshots/GUI-MCP_1.jpg" width="500" alt="Garmin Local Archive — Standalone MCP Server">
<br><sub>Standalone MCP server window (<code>mcp_server.exe</code>) — runs independently of the main app, with its own archive path, backend selection, and live log.</sub>
Running Open WebUI (or any other MCP client) inside Docker? An opt-in
"Extra allowed hosts" field on the MCP Server tab lets you add
`host.docker.internal` — pre-filled by default — to the server's allowed-
host list, since the underlying MCP SDK otherwise rejects connections
that don't arrive as `127.0.0.1`/`localhost`.
Works with Ollama (local models, default) or an optional cloud LLM backend —
your choice, no default push toward either. Point your MCP-compatible
client (Claude Desktop, Open WebUI, or similar) at the server and start
asking questions.
**Connect:** Streamable HTTP at `http://127.0.0.1:<port>/mcp` (default
port `8756`, configurable on the MCP Server tab) — bound to localhost
only, not reachable from other machines by default. See "Extra allowed
hosts" above for Docker clients.
**Tools exposed** (see [`docs/REFERENCE_MCP.md`](src/docs/REFERENCE_MCP.md) for full signatures):
| Tool | What it does |
|---|---|
| `query_health` | Daily health metrics (e.g. sleep, stress, heart rate, body battery) for a date or date range |
| `query_context` | Weather, pollen and air-quality data for a date or date range |
| `query_fit_activities` | FIT activity data — *planned, not yet available* (see `docs/ROADMAP.md`, "FIT Pipeline") |
| `query_raw` | Archived data as stored in `raw/`, without value extraction — for list and event fields (e.g. nutrition logs, lifestyle logging) that the other tools cannot reduce to a daily value; health domain only. Floors, weigh-ins and intensity minutes exist both ways: `query_health` holds the daily value, and the two tools point to each other |
| `get_archive_metadata` | Archive coverage and quality info (date ranges, missing days, data quality) |
| `list_available_fields` | Which fields the archive actually contains, per domain |
| `refresh_cache` | Rebuilds the server's local query cache on demand, e.g. right after a sync |
| `baselines` | A health field's rolling 90-day average and normal range, day by day |
| `anomalies` | Days (or multi-day streaks) where a health field broke from its own recent pattern |
| `gaps` | Days with no usable data in a given domain and date range |
| `correlate` | How closely two fields move together over time — any combination of health and context, with optional lag (e.g. "does X today predict Y a few days later") |
Example: *"How did my sleep and stress compare over the last two weeks?"*
— the model calls the tools above itself and answers from your real
archive, no manual export needed. No archive yet? See
[Try it first](#try-it-first) above.
---
## Architecture
### Token security & Login
Garmin login works via SSO — logging in with email and password on every
run triggers Captcha or MFA. The solution: log in once manually, and
Garmin returns an OAuth token that handles all subsequent runs for
approximately one year. This token is equivalent to a logged-in session
and must not sit unprotected on disk.
The token is encrypted at rest. Details on the encryption design and threat model: [SECURITY.md](SECURITY.md)
---
### Pipeline
Live sync and Bulk Import both flow through the same validation and quality pipeline before anything lands in the local archive — the diagram below shows the full picture.
> [!TIP]
> **Pipeline Architecture:** For a detailed view of the v1.3.4 data flow including the validation layer and self-healing loop, open [screenshots/flowchart_v134.html](src/screenshots/flowchart_v134.html) in your browser.
---
### System Architecture
The diagram below shows how all components relate to each other as of v1.7.x — from API ingestion and context collection through the broker layer to dashboard export. The broker layer also includes `gateway_map`, a cross-domain routing layer used by the local MCP server among other consumers — existing dashboards are unaffected and continue to query `health_map`/`context_map` directly.

---
### What is included
The project is structured into six focused layers — Garmin pipeline, Context pipeline, Data brokers, Dashboard layer, Export/MCP layer, Desktop app. Each layer has a single responsibility — collect, validate, assess, broker, or render. No crossover between layers.
The diagram above shows how the layers connect. Each module is self-contained and designed to be extended — for a script-by-script reference (what each module does, owns, and how to add new ones), see [`docs/MAINTENANCE_GLOBAL.md`](src/docs/MAINTENANCE_GLOBAL.md).
The desktop app includes a **Background Timer** — once started, it automatically repairs failed/incomplete days, upgrades bulk-imported days within its 180-day retry window, fills missing days, keeps a raw API-response backup current, retroactively adds newly supported data fields (like step count) to already-archived days, and re-fetches bulk-imported days for the handful of data fields the official Garmin export never contains at all (HRV, SpO2, Body Battery, respiration, training status, race predictions, max metrics) — regardless of how old the day is, since that gap has nothing to do with intraday resolution — with no further manual steps in between. The timer must be started manually and only runs while the app is open — it does not resume automatically after a restart.
Data is stored in two root folders:
```
garmin_data/
├── raw/ – complete API dumps (~500 KB/day) — permanent archive / basis for dashboards and analysis
├── source/ – unmodified API responses (~250 KB/day) — replay-safe intraday backup
├── summary/ – compact daily JSONs (~2 KB/day) — basis for dashboards and analysis
└── log/ – session logs, quality register, encrypted token
context_data/
├── weather/summary/ – daily weather archive (Open-Meteo)
├── pollen/summary|raw/ – daily / hourly pollen archive (Open-Meteo Air Quality)
├── brightsky/summary|raw/ – daily / hourly weather archive (Brightsky DWD)
└── airquality/summary|raw/ – daily / hourly air quality archive (Open-Meteo Air Quality)
```
---
See [`docs/MAINTENANCE_GLOBAL.md`](src/docs/MAINTENANCE_GLOBAL.md) for full technical documentation, how to add new fields, troubleshooting, and developer notes.
---
## Testing
The test suites (one list in `run_tests.ps1`, counts in `docs/METRICS.md`) cover the full pipeline — no network, no API required
(the Chat tab's Cloud LLM/MCP-tool-calling tests mock every SDK/HTTP call,
same as everything else here):
```bash
python tests/test_local.py # Garmin pipeline — cross-module checks (determinism, invariants, robustness, end-to-end)
python tests/test_<module>.py # Garmin pipeline — one file per module of src/garmin/ (19 files, e.g. test_writer.py, test_quality.py, test_collector.py; shared setup in tests/gla_testenv.py)
python tests/test_local_context.py # Context pipeline (external APIs mocked)
python tests/test_dashboard.py # Dashboard pipeline
python tests/test_broker.py # Broker layer (health_map / gateway_map routing, metadata_map)
python tests/test_mcp.py # MCP layer (mcp_map protocol translation)
python tests/test_export.py # Export Layer
python tests/test_app_logic.py # App layer (entry points, path resolution)
pytest tests/test_qt_app.py # PyQt6 App layer
python tests/test_static.py # ruff + bandit + regression guards
python tests/test_build_output.py # Build output validation (run after build)
pytest tests/test_cloud_llm.py # Chat tab — Cloud LLM connector
pytest tests/test_mcp_tool_chat.py # Chat tab — Ollama + MCP tool-calling
pytest tests/test_cloud_tool_chat.py # Chat tab — Cloud + MCP tool-calling, streaming
pytest tests/test_chat_session_store.py # Chat tab — session save/load/resume
pytest tests/test_cloud_credential_store.py # Chat tab — API key storage in Windows Credential Manager
pytest tests/test_mcp_process.py # MCP Server Start/Stop process control
python tests/test_updater.py # T2 + T3 self-updater
```
`build_all.py` runs `run_tests.ps1` — all suites, one list, the pytest ones included — as the pre-build gate; a failing suite aborts the build before either target is built. `test_build_output.py` and `test_app_logic.py` run automatically after both builds complete, as post-build gates. `test_qt_app.py` and the Chat-tab/MCP-process suites are part of that list and can still be run on their own via `pytest`.
GUI changes are verified manually before release. Automated builds and release packaging run via a manually-triggered GitHub Actions workflow (`build-release.yml`) — not on every push, kept as a deliberate one-click step so not every commit cuts a release.
---
> ⚠️ **API Usage Notice:** This project uses an unofficial interface. Large-scale data retrieval (e.g., syncing long time ranges in a single run) may trigger rate limiting or temporary IP blocks by Garmin (HTTP 429).
>
> It is recommended to:
> - fetch data in smaller increments
> - include delays between requests
> - allow cool-down periods between sync sessions
---
*Built with Claude · [☕ buy me a coffee](https://ko-fi.com/wewoc)*
TDQS
Scored across 11 tools
Each tool has a clearly distinct role: query_health vs query_context split cleanly by body vs external data, baselines vs anomalies vs gaps vs correlate each target a different analytical question, and query_raw is explicitly scoped against query_health with guidance on when to prefer each. The descriptions actively cross-reference and steer the agent away from the most likely confusions.
All names are lowercase snake_case, but the pattern mixes verb_noun forms (query_health, refresh_cache, get_archive_metadata, list_available_fields) with bare-noun analytical tools (baselines, anomalies, gaps, correlate). Readable and predictable enough, but not a single consistent convention.
Eleven tools is well within the ideal range and each one earns its place: discovery, health/context querying, raw access, three analytics tools, gap listing, cache refresh, and archive metadata. No redundant or filler tools.
For a read-only local archive the query/analytics surface is thorough (field discovery, daily and series queries, raw access, baselines, anomalies, gaps, correlation, metadata, cache refresh). The main gap is that FIT support is entirely non-functional — query_fit_activities is a stub and gaps 'fit' always degrades — which leaves a whole promised domain uncovered.