eticu 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ INTERVALS_API_KEY=your_api_key_here
2
+ INTERVALS_ATHLETE_ID=i12345
@@ -0,0 +1,42 @@
1
+ name: Semantic Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+
8
+ permissions:
9
+ contents: write # Needed to push tags and releases to GitHub
10
+ id-token: write # Needed for trusted publishing to PyPI
11
+
12
+ jobs:
13
+ release:
14
+ runs-on: ubuntu-latest
15
+ environment:
16
+ name: pypi
17
+ url: https://pypi.org/p/eticu
18
+ steps:
19
+ - name: Checkout repository
20
+ uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v4
26
+ with:
27
+ version: "latest"
28
+
29
+ - name: Python Semantic Release
30
+ id: release
31
+ uses: python-semantic-release/python-semantic-release@v9.8.1
32
+ with:
33
+ github_token: ${{ secrets.GITHUB_TOKEN }}
34
+
35
+ - name: Build package
36
+ if: steps.release.outputs.released == 'true'
37
+ run: uv build
38
+
39
+ - name: Publish to PyPI
40
+ # Only publish if a new release was created
41
+ if: steps.release.outputs.released == 'true'
42
+ uses: pypa/gh-action-pypi-publish@release/v1
eticu-0.2.1/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ .env
10
+
11
+ # Virtual environments
12
+ .venv
13
+
14
+ 8020_cache/
15
+ .mypy_cache/
16
+ .pytest_cache/
17
+ .ruff_cache
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,41 @@
1
+ # CHANGELOG
2
+
3
+
4
+
5
+ ## v0.2.1 (2026-07-06)
6
+
7
+ ### Ci
8
+
9
+ * ci: add explicit build step after semantic release ([`5370e5a`](https://github.com/melihakay/8020-Fetcher/commit/5370e5ac0598451312efc36fc099bf7c151fed22))
10
+
11
+ ### Fix
12
+
13
+ * fix: trigger publisher ([`24eff65`](https://github.com/melihakay/8020-Fetcher/commit/24eff653c359e75ebd26d20c7a7a02791e6dd233))
14
+
15
+ ### Unknown
16
+
17
+ * Merge branch 'master' of https://github.com/melihakay/8020-Fetcher ([`8df2000`](https://github.com/melihakay/8020-Fetcher/commit/8df20006cce8f0a169dfd3870463e7ad4e08a03e))
18
+
19
+
20
+ ## v0.2.0 (2026-07-06)
21
+
22
+ ### Feature
23
+
24
+ * feat: added semantic versioning ([`d42e355`](https://github.com/melihakay/8020-Fetcher/commit/d42e355e822b9d7180aaaa4d4cfa16a5c74d43c1))
25
+
26
+ ### Fix
27
+
28
+ * fix: use default semantic-release build command ([`04315fc`](https://github.com/melihakay/8020-Fetcher/commit/04315fcbc5b133f5d4c2a4e5bc1a451ced3d0262))
29
+
30
+
31
+ ## v0.1.0 (2026-07-06)
32
+
33
+ ### Feature
34
+
35
+ * feat: added dynamic versioning and publisher ([`14573ca`](https://github.com/melihakay/8020-Fetcher/commit/14573ca967a4faf057e863dd078b679412746c02))
36
+
37
+ ### Unknown
38
+
39
+ * update readme ([`0c6f3a7`](https://github.com/melihakay/8020-Fetcher/commit/0c6f3a7e2a1c1f2eac406ac98eba9ec2b614ae96))
40
+
41
+ * initial version ([`68e92ec`](https://github.com/melihakay/8020-Fetcher/commit/68e92ec95c07469b0497d8dbeaa2076815e08c9d))
eticu-0.2.1/CLAUDE.md ADDED
@@ -0,0 +1,99 @@
1
+ # CLAUDE.md
2
+
3
+ Operational guide for working in this repo. **`ROADMAP.md` is the authoritative spec** — read it
4
+ first, especially §0 (the domain gotchas). This file is the how-to-work-here layer: commands,
5
+ conventions, and the short list of things that will bite you.
6
+
7
+ ## What this is
8
+
9
+ A Python package (`eticu`, CLI `eticu`) that scrapes the **free** 80/20 Endurance
10
+ workout library, converts each `.FIT` workout into a correct intervals.icu structured workout,
11
+ and uploads them into the intervals.icu workout library. Metric units only. Swims 25 m / 50 m
12
+ only. Reimplements and fixes `njg4ne/8020-intervals-icu` (TS, GPL-3.0).
13
+
14
+ Only ever use the **free public library**. Never scrape, embed, or redistribute paid 80/20
15
+ training plans.
16
+
17
+ ## Stack & tooling
18
+
19
+ - Python **3.12+**, **uv** for env/deps, `src/` layout.
20
+ - `ruff` (lint + format), `mypy --strict`, `pytest` (+ `respx` for HTTP mocking).
21
+ - Runtime deps (see ROADMAP §1): a FIT decoder (`garmin-fit-sdk` or `fitdecode`), `httpx`,
22
+ `selectolax`/`beautifulsoup4`, `typer`, `pydantic` + `pydantic-settings`.
23
+
24
+ ## Commands
25
+
26
+ ```bash
27
+ uv sync # install (creates .venv, resolves lockfile)
28
+ uv run eticu --help # CLI
29
+ uv run eticu all --dry-run # full pipeline, no network writes
30
+ uv run eticu all --cycling-power # rides as Power targets; run/swim stay HR
31
+
32
+ uv run pytest -q # tests
33
+ uv run ruff check . # lint
34
+ uv run ruff format . # format
35
+ uv run mypy src # typecheck
36
+ uv build # build wheel/sdist
37
+
38
+ uv add <pkg> # add runtime dep
39
+ uv add --dev <pkg> # add dev dep
40
+ ```
41
+
42
+ Run `ruff check`, `ruff format`, `mypy src`, and `pytest` before considering any change done.
43
+
44
+ ## Layout
45
+
46
+ ```
47
+ src/eticu/ models.py zones.py scrape.py download.py
48
+ fit_parse.py convert.py intervals_client.py upload.py cli.py
49
+ tests/ unit tests + fixtures/ (vendored sample .FIT + golden JSON/text)
50
+ ROADMAP.md the spec (phases, acceptance criteria, correctness checklist)
51
+ CLAUDE.md this file
52
+ .env.example INTERVALS_API_KEY=, INTERVALS_ATHLETE_ID=
53
+ ```
54
+
55
+ ## Conventions
56
+
57
+ - Full type hints everywhere; `mypy --strict` must pass. Prefer pure functions in `zones`,
58
+ `fit_parse`, and `convert` so they're trivially testable.
59
+ - Keep the `Workout`/`Step` model **unit-agnostic** (store seconds and meters). All unit and
60
+ target-syntax formatting happens in `convert.py`, never in the parser.
61
+ - Golden-file testing is the backbone. Fixtures live in `tests/fixtures/`. **CCI1 is the
62
+ canonical regression case** (ROADMAP §0.5) — it guards both the target-loss fix and the
63
+ repeat-scoping fix, and it must pass in **both** HR mode and `--cycling-power` mode.
64
+ - **No live network calls in tests.** Mock intervals.icu with `respx`; use vendored `.FIT`
65
+ fixtures rather than hitting 8020's site. Never commit an API key or real athlete ID.
66
+ - Conventional Commits; small, phase-aligned PRs that each leave the suite green.
67
+
68
+ ## Domain gotchas (full detail in ROADMAP §0 — do not re-derive)
69
+
70
+ - **Targets aren't in the FIT.** Steps use `target_type = open`; the zone is in the `notes`
71
+ string. Reading the file's target field gives you Z1 for everything — that's the bug. (§0.1)
72
+ - **Zone map inserts X and Y:** 80/20 `1,2,X,3,Y,4,5` → intervals.icu `Z1..Z7`, so **80/20
73
+ Zone 3 → Z4**. Assert this. (§0.2)
74
+ - **Repeats scope to the loop-back index only.** The repeat step names the message index to
75
+ return to; the `Nx` block wraps exactly that range up to the repeat instruction — steps after
76
+ it are siblings, not children. (§0.5)
77
+ - **Metric only.** Run/Ride distance → km; swim → meters (25 m increments). No `mi`, no `y`. (§0.6)
78
+ - **Swim 25 m / 50 m only**, never 25 y; each yields a name-suffixed workout. (§0.7)
79
+ - **Target type by sport:** Run/Swim always HR; Ride HR by default, **Power** under
80
+ `--cycling-power` (needs the athlete's FTP/Power zones; the Zwift/ERG use case). Same
81
+ Z-number mapping for both. (§0.3)
82
+ - **Folders are flat and fixed-named:** `80/20 - Run`, `80/20 - Bike`, `80/20 - Swim`
83
+ (intervals.icu doesn't nest folders; note "Bike" label vs `Ride` type). (§0.8)
84
+ - **Idempotent uploads:** re-running must not duplicate folders or workouts — look up by name,
85
+ then update or skip. (Phase 4)
86
+
87
+ ## Config & secrets
88
+
89
+ Read `INTERVALS_API_KEY` and `INTERVALS_ATHLETE_ID` from the environment via
90
+ `pydantic-settings` (`.env` locally, never committed — ship `.env.example`). Auth is HTTP Basic:
91
+ username literal `API_KEY`, password = the key; athlete ID goes in the path
92
+ (`https://intervals.icu/api/v1/athlete/{id}/...`). Verify exact endpoints and the bulk-upsert
93
+ semantics against `https://intervals.icu/api-docs.html` before relying on them.
94
+
95
+ ## Etiquette
96
+
97
+ Be a good citizen of both services: resumable downloads (skip existing, atomic writes), modest
98
+ concurrency and a small delay when scraping 8020, and idempotent writes to intervals.icu. Prefer
99
+ a `--dry-run` path for anything that mutates the remote library.
eticu-0.2.1/Makefile ADDED
@@ -0,0 +1,30 @@
1
+ .PHONY: install format lint test typecheck build publish clean
2
+
3
+ install:
4
+ uv sync
5
+
6
+ format:
7
+ uv run ruff format .
8
+
9
+ lint:
10
+ uv run ruff check .
11
+
12
+ typecheck:
13
+ uv run mypy src
14
+
15
+ test:
16
+ uv run pytest -q
17
+
18
+ build:
19
+ uv build
20
+
21
+ publish: build
22
+ uv publish
23
+
24
+ clean:
25
+ rm -rf dist/
26
+ rm -rf build/
27
+ rm -rf .ruff_cache/
28
+ rm -rf .pytest_cache/
29
+ rm -rf .mypy_cache/
30
+ find . -type d -name __pycache__ -exec rm -rf {} +
eticu-0.2.1/PKG-INFO ADDED
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: eticu
3
+ Version: 0.2.1
4
+ Summary: Convert 80/20 Endurance .FIT workouts to intervals.icu structured workouts
5
+ Project-URL: Repository, https://github.com/melihakay/8020-fetcher
6
+ Author-email: Melih Akay <melih.akay@metu.edu.tr>
7
+ License: MIT
8
+ Requires-Python: >=3.12
9
+ Requires-Dist: fitdecode>=0.10.0
10
+ Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: pydantic-settings>=2.3.0
12
+ Requires-Dist: pydantic>=2.7.0
13
+ Requires-Dist: selectolax>=0.3.17
14
+ Requires-Dist: typer>=0.12.0
15
+ Description-Content-Type: text/markdown
16
+
17
+ # eticu: 80/20 Endurance to Intervals.icu
18
+
19
+ `eticu` is a Python CLI pipeline that scrapes the **free** 80/20 Endurance workout library, parses and converts the `.FIT` files into fully structured [Intervals.icu](https://intervals.icu/) workouts, and idempotently uploads them to your Intervals.icu workout library.
20
+
21
+ It serves as a more robust, metric-first Python reimplementation of the [8020-intervals-icu](https://github.com/njg4ne/8020-intervals-icu) TypeScript project, completely fixing target loss issues during imports, nested repeat structures, and adding idempotent uploading.
22
+
23
+ > **Scope Note**: Brick (`BR*`) workouts do not exist as `.FIT` files in the library and are therefore also out of scope.
24
+
25
+ ## Installation & Setup
26
+
27
+ This project uses `uv` for dependency management.
28
+
29
+ 1. **Clone the repository:**
30
+ ```bash
31
+ git clone <repo-url>
32
+ cd 8020-fetcher
33
+ ```
34
+
35
+ 2. **Environment Variables:**
36
+ Copy `.env.example` to `.env` and set your Intervals.icu credentials:
37
+ ```env
38
+ INTERVALS_API_KEY=your_intervals_api_key
39
+ INTERVALS_ATHLETE_ID=your_intervals_athlete_id
40
+ ```
41
+ You can find these in your Intervals.icu settings.
42
+
43
+ ## Usage
44
+
45
+ To run the full pipeline (scrape → download → upload):
46
+ ```bash
47
+ uv run eticu all
48
+ ```
49
+
50
+ **Options:**
51
+ - `--dry-run`: Do everything except network writes to Intervals.icu.
52
+ - `--cycling-power`: Emit Power targets (instead of Heart Rate) for Ride workouts. (Run and Swim always remain HR).
53
+ - `--cache-dir <dir>`: Directory to cache downloaded `.FIT` files (defaults to `8020_cache`).
54
+
55
+ To convert and view a single downloaded `.FIT` file locally:
56
+ ```bash
57
+ uv run eticu convert 8020_cache/bike/CCI1.FIT
58
+ ```
59
+
60
+ To upload a training plan from a CSV file (e.g. `plans/olympic_distance_level_0.csv`):
61
+ ```bash
62
+ uv run eticu upload-plan plans/olympic_distance_level_0.csv --name "Olympic Level 0"
63
+ ```
64
+
65
+ Exercise names in the CSV's should be same as the 80/20 FIT files.
66
+
67
+ **Options:**
68
+ - `--pool-length`: Pool length in meters (e.g., 25 or 50) for swim workouts.
69
+ - `--dry-run`: Parse the CSV and list the plan without uploading.
70
+
71
+ ## Critical Prerequisites
72
+
73
+ For the generated workouts to be accurate, you **must** configure your Intervals.icu settings to match the 80/20 Endurance methodology.
74
+
75
+ ### 1. The 7-Zone Prerequisite
76
+ 80/20 uses a 7-level zone system with two crossover zones: `1, 2, X, 3, Y, 4, 5`.
77
+ Intervals.icu supports 7 zones (`Z1` to `Z7`). `eticu` maps them sequentially.
78
+ **You MUST update your Intervals.icu HR zones (and Power zones, if using `--cycling-power`) to match the numbers from the 80/20 zone calculator.**
79
+
80
+ | 80/20 Zone | Intervals.icu Zone |
81
+ |------------|--------------------|
82
+ | Zone 1 | Z1 |
83
+ | Zone 2 | Z2 |
84
+ | Zone X | Z3 |
85
+ | **Zone 3** | **Z4** |
86
+ | Zone Y | Z5 |
87
+ | Zone 4 | Z6 |
88
+ | Zone 5 | Z7 |
89
+
90
+ *Note: Because of crossover Zone X, 80/20's "Zone 3" maps to Intervals "Z4".*
91
+
92
+ ### 2. The `--cycling-power` Option & ERG Mode
93
+ By default, 80/20 workouts use Heart Rate targets. However, platforms like **Zwift** in ERG mode ignore HR targets; they require **Power (%FTP)**.
94
+
95
+ If you ride on Zwift/TrainerRoad, pass the `--cycling-power` flag. This converts all `Ride` workouts to use Power zones.
96
+ * **Prerequisite**: For this to work, you MUST have your FTP and 7-level Power zones correctly configured in your Intervals.icu settings. If you don't, power targets will be meaningless.
97
+
98
+ ## Core Behaviors
99
+
100
+ - **Metric Units**: All outputs are metric. Run and Ride distances are rounded to the nearest `0.1 km`. Swims are strictly handled in `meters` (`mtr` in Intervals.icu to distinguish from minutes).
101
+ - **Pool Lengths**: Only `25m` and `50m` swim workouts are fetched and processed. `25y` files are skipped.
102
+ - **Folder Layout**: Workouts are uploaded into three flat folders (created automatically if they don't exist):
103
+ - `80/20 - Run`
104
+ - `80/20 - Bike`
105
+ - `80/20 - Swim`
106
+ - **Idempotency**: The upload is completely idempotent. It matches workouts by exact name within the folder and skips identical workouts, preventing duplication if the script is run multiple times.
107
+
108
+ ## Credits & License
109
+ This project was heavily inspired by the GPL-3.0 licensed `njg4ne/8020-intervals-icu` project, expanding its capabilities into a fully-typed Python package with deeper FIT file introspection.
eticu-0.2.1/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # eticu: 80/20 Endurance to Intervals.icu
2
+
3
+ `eticu` is a Python CLI pipeline that scrapes the **free** 80/20 Endurance workout library, parses and converts the `.FIT` files into fully structured [Intervals.icu](https://intervals.icu/) workouts, and idempotently uploads them to your Intervals.icu workout library.
4
+
5
+ It serves as a more robust, metric-first Python reimplementation of the [8020-intervals-icu](https://github.com/njg4ne/8020-intervals-icu) TypeScript project, completely fixing target loss issues during imports, nested repeat structures, and adding idempotent uploading.
6
+
7
+ > **Scope Note**: Brick (`BR*`) workouts do not exist as `.FIT` files in the library and are therefore also out of scope.
8
+
9
+ ## Installation & Setup
10
+
11
+ This project uses `uv` for dependency management.
12
+
13
+ 1. **Clone the repository:**
14
+ ```bash
15
+ git clone <repo-url>
16
+ cd 8020-fetcher
17
+ ```
18
+
19
+ 2. **Environment Variables:**
20
+ Copy `.env.example` to `.env` and set your Intervals.icu credentials:
21
+ ```env
22
+ INTERVALS_API_KEY=your_intervals_api_key
23
+ INTERVALS_ATHLETE_ID=your_intervals_athlete_id
24
+ ```
25
+ You can find these in your Intervals.icu settings.
26
+
27
+ ## Usage
28
+
29
+ To run the full pipeline (scrape → download → upload):
30
+ ```bash
31
+ uv run eticu all
32
+ ```
33
+
34
+ **Options:**
35
+ - `--dry-run`: Do everything except network writes to Intervals.icu.
36
+ - `--cycling-power`: Emit Power targets (instead of Heart Rate) for Ride workouts. (Run and Swim always remain HR).
37
+ - `--cache-dir <dir>`: Directory to cache downloaded `.FIT` files (defaults to `8020_cache`).
38
+
39
+ To convert and view a single downloaded `.FIT` file locally:
40
+ ```bash
41
+ uv run eticu convert 8020_cache/bike/CCI1.FIT
42
+ ```
43
+
44
+ To upload a training plan from a CSV file (e.g. `plans/olympic_distance_level_0.csv`):
45
+ ```bash
46
+ uv run eticu upload-plan plans/olympic_distance_level_0.csv --name "Olympic Level 0"
47
+ ```
48
+
49
+ Exercise names in the CSV's should be same as the 80/20 FIT files.
50
+
51
+ **Options:**
52
+ - `--pool-length`: Pool length in meters (e.g., 25 or 50) for swim workouts.
53
+ - `--dry-run`: Parse the CSV and list the plan without uploading.
54
+
55
+ ## Critical Prerequisites
56
+
57
+ For the generated workouts to be accurate, you **must** configure your Intervals.icu settings to match the 80/20 Endurance methodology.
58
+
59
+ ### 1. The 7-Zone Prerequisite
60
+ 80/20 uses a 7-level zone system with two crossover zones: `1, 2, X, 3, Y, 4, 5`.
61
+ Intervals.icu supports 7 zones (`Z1` to `Z7`). `eticu` maps them sequentially.
62
+ **You MUST update your Intervals.icu HR zones (and Power zones, if using `--cycling-power`) to match the numbers from the 80/20 zone calculator.**
63
+
64
+ | 80/20 Zone | Intervals.icu Zone |
65
+ |------------|--------------------|
66
+ | Zone 1 | Z1 |
67
+ | Zone 2 | Z2 |
68
+ | Zone X | Z3 |
69
+ | **Zone 3** | **Z4** |
70
+ | Zone Y | Z5 |
71
+ | Zone 4 | Z6 |
72
+ | Zone 5 | Z7 |
73
+
74
+ *Note: Because of crossover Zone X, 80/20's "Zone 3" maps to Intervals "Z4".*
75
+
76
+ ### 2. The `--cycling-power` Option & ERG Mode
77
+ By default, 80/20 workouts use Heart Rate targets. However, platforms like **Zwift** in ERG mode ignore HR targets; they require **Power (%FTP)**.
78
+
79
+ If you ride on Zwift/TrainerRoad, pass the `--cycling-power` flag. This converts all `Ride` workouts to use Power zones.
80
+ * **Prerequisite**: For this to work, you MUST have your FTP and 7-level Power zones correctly configured in your Intervals.icu settings. If you don't, power targets will be meaningless.
81
+
82
+ ## Core Behaviors
83
+
84
+ - **Metric Units**: All outputs are metric. Run and Ride distances are rounded to the nearest `0.1 km`. Swims are strictly handled in `meters` (`mtr` in Intervals.icu to distinguish from minutes).
85
+ - **Pool Lengths**: Only `25m` and `50m` swim workouts are fetched and processed. `25y` files are skipped.
86
+ - **Folder Layout**: Workouts are uploaded into three flat folders (created automatically if they don't exist):
87
+ - `80/20 - Run`
88
+ - `80/20 - Bike`
89
+ - `80/20 - Swim`
90
+ - **Idempotency**: The upload is completely idempotent. It matches workouts by exact name within the folder and skips identical workouts, preventing duplication if the script is run multiple times.
91
+
92
+ ## Credits & License
93
+ This project was heavily inspired by the GPL-3.0 licensed `njg4ne/8020-intervals-icu` project, expanding its capabilities into a fully-typed Python package with deeper FIT file introspection.
@@ -0,0 +1,74 @@
1
+ # Test results [RESOLVED]
2
+
3
+ PS C:\Users\melih\Development\8020 Fetcher> uv run ruff check .
4
+ All checks passed!
5
+ PS C:\Users\melih\Development\8020 Fetcher> uv run ruff format .
6
+ 18 files left unchanged
7
+ PS C:\Users\melih\Development\8020 Fetcher> uv run pytest -q
8
+ ........................................ [100%]
9
+ 40 passed in 1.65s
10
+ PS C:\Users\melih\Development\8020 Fetcher> uv run mypy src
11
+ src\eticu\fit_parse.py:19: error: Skipping analyzing "fitdecode": module is installed, but missing library stubs or py.typed marker [import-untyped]
12
+ src\eticu\fit_parse.py:19: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
13
+ src\eticu\intervals_client.py:49: error: Unused "type: ignore" comment [unused-ignore]
14
+ src\eticu\intervals_client.py:50: error: Returning Any from function declared to return "list[dict[str, Any]]" [no-any-return]
15
+ src\eticu\intervals_client.py:77: error: Unused "type: ignore" comment [unused-ignore]
16
+ src\eticu\intervals_client.py:77: error: Returning Any from function declared to return "list[dict[str, Any]]" [no-any-return]
17
+ src\eticu\intervals_client.py:77: note: Error code "no-any-return" not covered by "type: ignore[return-value]" comment
18
+ Found 5 errors in 2 files (checked 11 source files)
19
+
20
+ # Loop issue [RESOLVED]
21
+
22
+ - 8020/Z1 1s Z1
23
+ - 8020/Z2 1s Z2
24
+ 2x
25
+ - 8020/Z3 1s Z4
26
+ - 8020/Z1 0s Z1
27
+
28
+ - 8020/Z2 1s Z2
29
+ - 8020/Z1 2s Z1
30
+
31
+ This is from intervals.icu
32
+
33
+ It is wrong, there should be a empty space before 2x so that loop is introduced correctly.
34
+
35
+ # Duration issue [RESOLVED]
36
+
37
+ for example in RF3:
38
+
39
+ - 8020/Z1 0s Z1 HR
40
+ - 8020/Z2 1s Z2 HR
41
+ - 8020/Z1 0s Z1 HR
42
+
43
+ (Fixed: FIT decode scaling for duration_value was incorrectly assumed to always be milliseconds. Duration parsing is now extracted as raw floating-point values since fitdecode automatically applies profile multipliers (producing seconds and meters directly).)
44
+
45
+ # Swim issue [RESOLVED]
46
+
47
+ INFO HTTP Request: GET https://intervals.icu/api/v1/athlete/i632678/folders "HTTP/1.1 200 OK"
48
+ INFO HTTP Request: POST https://intervals.icu/api/v1/athlete/i632678/workouts "HTTP/1.1 200 OK"
49
+ INFO Created workout 'SSI2 50m' (id=1064)
50
+ WARNING Step 4: no zone found in notes None — defaulting to Z1
51
+ WARNING Step has no zone; defaulting to 8020/Z1 (Z1)
52
+ WARNING Step has no zone; defaulting to 8020/Z1 (Z1)
53
+ WARNING Step has no zone; defaulting to 8020/Z1 (Z1)
54
+
55
+ (Fixed: convert.py no longer logs a warning for unzoned steps if their intensity is 'rest' or 'recovery', as these steps correctly don't require zones.)
56
+
57
+ # Too much workouts [RESOLVED]
58
+
59
+ INFO HTTP Request: POST https://intervals.icu/api/v1/athlete/i632678/workouts "HTTP/1.1 200 OK"
60
+ INFO Created workout 'STT2 50m' (id=1100)
61
+ created=1100 updated=0 skipped=0 error=0
62
+
63
+ while scrape says
64
+
65
+ Total: 550 (Run=190 Ride=218 Swim=142)
66
+
67
+ So everythin is posted twice
68
+
69
+ # Intervals ICU Syntax Guide [RESOLVED]
70
+
71
+ Use https://forum.intervals.icu/t/workout-builder-syntax-quick-guide/123701 for syntax. Reevaluate existing base with that guide.
72
+
73
+ (Evaluated and confirmed that the loops correctly use the empty line spacing syntax outlined in the guide, avoiding nested loop issues and correctly formatting `x` repeat blocks.)
74
+