easy-or-not
Are your easy runs actually easy?
Most runners who stop improving are not lazy. They run almost everything at the same medium effort, too hard to recover from and too easy to make them faster. This tool reads your own runs and tells you how much of your time went where, how much of your "easy" running drifted into the middle, whether you are getting faster at the same heart rate, and your training load from week to week.
It was built for watches like the Vivoactive 5, which record every heartbeat but leave Training Load to Garmin's more expensive models.
It works on FIT files you have exported, or straight from your account with a Garmin Personal API key. Everything runs on your machine. Your data and your key never leave it.
30 runs, heart-rate bands from your Garmin running zones
easy below 154 bpm middle 154-172 bpm hard 173+ bpm
Where your running time went
easy 47% ██████████████░░░░░░░░░░░░░░░░
middle 27% ████████░░░░░░░░░░░░░░░░░░░░░░
hard 25% ████████░░░░░░░░░░░░░░░░░░░░░░
A common target is around 80% easy with the rest mostly hard.
Your easy runs (18 of 30, sessions excluded)
44% of easy-run time was in the middle band.
Thu 08 Oct 32 min 68% middle (treadmill)
Mon 14 Sep 32 min 65% middle (outdoor)
Sun 04 Oct 32 min 56% middle (outdoor)
Same heart rate, faster? (outdoor easy runs only)
1.40 -> 1.43 metres per heartbeat (+2.5%).
Higher means you cover more ground for the same effort.
Training load (heart-rate minutes, weighted 1 to 5 by zone)
Last 7 days to Thu 22 Oct: 412
Your average week over the last 4: 380
Ratio 1.08: in line with your recent weeks.
(Example output from synthetic data.)
Install
Python 3.12 or newer.
uv tool install easy-or-not
# or: pipx install easy-or-not
Use it with your Garmin account
easy-or-not setup # once: paste your Personal API key
easy-or-not # your last 8 weeks of runs
easy-or-not garmin --days 90
setup tells you where to create the key in Garmin Connect (Settings, Personal API Token), reads it without showing it on screen, saves it to ~/.config/garmin/connect-personal-api-key readable only by you, and checks it with Garmin.
Authentication is handled by Garmin's official Python client, which also reads GARMIN_PERSONAL_API_KEY if you prefer an environment variable. Original activity files are cached in ~/.cache/easy-or-not, so later runs only download new activities. Your heart-rate zones are read from your Garmin settings.
Use it with exported files
In Garmin Connect on the web, open an activity, click the gear icon and choose Export Original. Point the tool at the files or the folder:
easy-or-not --max-hr 192 files ~/Downloads/runs/
Without the API there are no Garmin zones to read, so pass your max heart rate.
What it measures
Bands. Easy, middle and hard come from your heart-rate zones. By default easy is zones 1 and 2, middle is zone 3, hard is zones 4 and 5. Garmin's default zones start zone 3 at 70% of max HR, which is stricter than the roughly 80% easy ceiling used in polarised-training research. If you are on Garmin's defaults and everything looks "middle", try --easy-up-to 3. With --max-hr, the bands are below 80%, 80 to 90%, and above 90% of max.
Easy runs. Any run with less than 10% of its time in the hard band. That is a session-free run, whatever you meant it to be.
Efficiency. Metres covered per heartbeat while in the easy band, ignoring the first five minutes while heart rate catches up. The tool compares the median of your first third of outdoor easy runs with your last third. It needs at least six.
What gets left out. Activities with under 10 minutes of heart-rate data (a GPS test, a watch started by accident), and walks logged as runs (median cadence under 140 steps per minute). A walk home or a watch left recording after the run is trimmed off the end, so it does not count as easy running. The report says how many were left out and why.
Zones that look too low. If you held a median heart rate in zone 5 for half an hour or more, the report says your zones are probably set too low rather than telling you that you ran half an hour flat out.
Training load. Each run's load is its Edwards TRIMP: minutes in each heart-rate zone multiplied by the zone number, so a minute in zone 5 counts five times a minute in zone 1. The report compares the last 7 days with your average week over the last 4 (it needs 4 weeks of runs). Below 0.8 is lighter than usual, 0.8 to 1.3 is in line, 1.3 to 1.5 is a step up, above 1.5 is a big jump. Runs under 10 minutes still count towards load; walks do not. From your Garmin account, load is measured up to today, so a week off shows as a week off. It is a heart-rate estimate, not Garmin's Training Load, and it is only as right as your zones.
Pauses. Gaps of more than 10 seconds between samples (traffic lights, a stopped watch) are not counted.
Limits
- Treadmill pace is an estimate. Indoors, the watch works out speed from your wrist, so treadmill runs count towards time in zone but are left out of the efficiency trend.
- Heart rate is only as good as the sensor. Wrist HR can lock on to cadence, especially in the first few minutes or in the cold. A chest strap gives cleaner numbers.
- Your zones decide the answer. If the max heart rate in your Garmin settings is a guess, the bands are too. If you know your real max (from a race finish or a hard hill effort), pass
--max-hr. - It describes, it does not coach. "44% of your easy running was in the middle" is a fact about your data. What to do about it is up to you.
Development
uv venv && uv pip install -e '.[dev]'
uv run pytest -q
uv run ruff check .
The tests build real FIT files in memory, so nothing personal is committed. *.fit, *.zip and key files are git-ignored.
Licence
MIT. Not affiliated with Garmin.
Metadata
Release files for easy-or-not 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| easy_or_not-0.1.1.tar.gz | 22.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| easy_or_not-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.5 kB
Release files / easy_or_not-0.1.1.tar.gz
| Download URL | easy_or_not-0.1.1.tar.gz |
|---|---|
| Size | 22.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
34eb26ede551b0fe1af517dd86b8d1d8e7cb3e902fb6da335fe989be823bbb06
|
|
BLAKE2b-256 checksum How to use checksums |
953a1551cc6812e446008bd7d836f48b77ce127ed361759193f572a0e31a2b50
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / easy_or_not-0.1.1-py3-none-any.whl
| Download URL | easy_or_not-0.1.1-py3-none-any.whl |
|---|---|
| Size | 21.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
04a6ec8e79acb3ee6c2340ae10720f27ed07f3c32130dc20d425afab5729bedf
|
|
BLAKE2b-256 checksum How to use checksums |
b122577e98e04def0bb4b26ef478257620b316835b90e42d3a21e9bfc7349011
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logSecurity for easy-or-not 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for easy_or_not-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.