Skip to main content

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)

Source distribution for easy-or-not 0.1.1
File Size Uploaded Provenance
easy_or_not-0.1.1.tar.gz 22.3 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for easy-or-not 0.1.1
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page