INITIAL COMMIT
This commit is contained in:
62
docs/edge1030-testing.md
Normal file
62
docs/edge1030-testing.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Edge 1030 Testing Checklist
|
||||
|
||||
Use this after `probe_garmin.py dummy --schedule` or a successful clone upload.
|
||||
|
||||
## Sync
|
||||
|
||||
1. Open Garmin Connect on the phone or Garmin Express on the computer.
|
||||
2. Sync the Edge 1030.
|
||||
3. Wait for sync completion before checking menus.
|
||||
|
||||
## Check Workout Library
|
||||
|
||||
On the Edge 1030:
|
||||
|
||||
```text
|
||||
Training > Workouts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- The dummy workout or `GCClone YYYY-MM-DD ...` workout appears.
|
||||
- It is listed as a cycling workout.
|
||||
- Opening it shows structured steps.
|
||||
|
||||
## Check Scheduled Calendar
|
||||
|
||||
On the Edge 1030:
|
||||
|
||||
```text
|
||||
Training > Training Plan > calendar icon > scheduled date
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- The scheduled dummy/clone workout appears on the selected date.
|
||||
- It can be selected from the calendar entry.
|
||||
|
||||
## Check Navigation Plus Workout
|
||||
|
||||
1. Select a course or route as usual.
|
||||
2. Start navigation.
|
||||
3. Start the scheduled workout.
|
||||
4. Start the ride timer.
|
||||
|
||||
Expected:
|
||||
|
||||
- Route/navigation remains usable.
|
||||
- Structured workout prompts appear.
|
||||
- Step transitions and alerts work.
|
||||
|
||||
This is the key test because Extended Display is not acceptable for this project.
|
||||
|
||||
## Report Back
|
||||
|
||||
Report:
|
||||
|
||||
- Whether the workout appeared in `Training > Workouts`.
|
||||
- Whether it appeared in the calendar date view.
|
||||
- Whether it could be started.
|
||||
- Whether navigation continued while the workout ran.
|
||||
- Whether Garmin Coach marked anything complete after the activity uploaded.
|
||||
|
||||
194
docs/feasibility.md
Normal file
194
docs/feasibility.md
Normal file
@ -0,0 +1,194 @@
|
||||
# Garmin Coach to Edge 1030 Feasibility
|
||||
|
||||
Research date: 2026-06-16.
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The most realistic path is an unofficial Garmin Connect API probe:
|
||||
|
||||
1. Read the user's Garmin Connect calendar/training-plan data.
|
||||
2. Determine whether Garmin Coach cycling workout steps are exposed in the private training-plan/adaptive-plan JSON.
|
||||
3. If step details are present, create a normal Garmin Connect cycling workout and schedule it.
|
||||
4. Let the Edge 1030 sync that normal scheduled workout.
|
||||
|
||||
What is proven from source documentation/code:
|
||||
|
||||
- The Edge 1030 supports normal Garmin Connect structured workouts and scheduled workouts.
|
||||
- `python-garminconnect` has methods for listing normal workouts, uploading a workout JSON, scheduling/unscheduling workouts, deleting workouts, listing calendar entries, and fetching training/adaptive plan details.
|
||||
- The Garmin Connect workout JSON schema for normal workouts is partially represented by `python-garminconnect.workout`.
|
||||
|
||||
What is proven from this user's account probe:
|
||||
|
||||
- Garmin login/token reuse works with `python-garminconnect`.
|
||||
- The account has an active `FBT_ADAPTIVE` Garmin Cycling Coach plan.
|
||||
- The plan/calendar APIs expose dated Coach workout summaries, including name, date, sport type, duration, compact description, training effect label, and `workoutUuid`.
|
||||
- The Garmin Connect browser uses `/gc-api/workout-service/fbt-adaptive/{workoutUuid}` to fetch the full Coach workout with `workoutSegments` and `workoutSteps`.
|
||||
- The same detail is available to `python-garminconnect` through the Connect API path `/workout-service/fbt-adaptive/{workoutUuid}` without the browser `/gc-api` prefix.
|
||||
|
||||
What is still not proven:
|
||||
|
||||
- Whether the Edge 1030 accepts every cloned target type after sync.
|
||||
- Whether the endpoint remains stable as Garmin changes private APIs.
|
||||
- Whether a summary-derived approximation is good enough for the user's training use case.
|
||||
|
||||
The official Garmin Developer Training API is not a good fit for this use case. It is business/approval-gated, designed to publish third-party workouts/plans to users, and does not provide a documented way to read a personal Garmin Coach/adaptive plan workout from Garmin Connect.
|
||||
|
||||
## Feasibility Matrix
|
||||
|
||||
| Feature | Supported / Unsupported / Unknown | Tested / documented / guessed | API/package/source | Notes |
|
||||
|---|---:|---:|---|---|
|
||||
| Edge 1030 runs normal structured workouts from Garmin Connect | Supported | Officially documented; not tested here | Edge 1030 manual: Workouts, Starting a Workout | Manual says workouts can be created in Garmin Connect, transferred, scheduled, and run on device. |
|
||||
| Edge 1030 shows scheduled workouts in calendar/training plan | Supported | Officially documented; not tested here | Edge 1030 manual: Training Calendar | Menu path documented as `Training > Training Plan > calendar icon`. |
|
||||
| Official API can read Garmin Coach/adaptive workouts from personal account | Unsupported | Documented absence/inferred from API direction | Garmin Training API, Health API docs | Training API publishes workouts/plans to Garmin Connect; Health API reads health/activity metrics, not Coach workout definitions. |
|
||||
| Official API can create/schedule workouts | Supported with approval | Officially documented | Garmin Training API | It can publish workouts/training plans to the Garmin Connect calendar. |
|
||||
| Official API available for personal app | Unsupported for this use case | Officially documented | Garmin Developer Program FAQ | Program is for enterprise/business use and requires approval. |
|
||||
| Authenticate unofficially with Garmin Connect | Supported/fragile | Tested with user's account | `python-garminconnect` login/tokenstore source | Login worked and wrote `.garmin-tokens`. Future Garmin SSO changes remain a risk. |
|
||||
| `garth` as standalone auth library | Unsupported/fragile | Project README | `matin/garth` README | `garth` says it is deprecated and new logins may not work after Garmin auth changes. Prefer current `python-garminconnect` probe. |
|
||||
| Store/reuse Garmin tokens | Supported in source | Source-supported; untested here | `python-garminconnect` login and demo | Token directory can be loaded/dumped. Probe defaults to `.garmin-tokens`. |
|
||||
| List training plans | Supported | Tested with user's account | `Garmin.get_training_plans()` | Returned one `FBT_ADAPTIVE` plan: `2026 Fitness Plan test`. |
|
||||
| Detect active Garmin Coach/adaptive plan | Supported/partial | Tested with user's account | `trainingPlanCategory == FBT_ADAPTIVE` | The active cycling Coach plan is detectable. |
|
||||
| Fetch adaptive training-plan detail | Supported summary only | Tested with user's account | `get_adaptive_training_plan_by_id()` | Returns `taskList` with dated `taskWorkout` summaries, but no step arrays. |
|
||||
| Fetch Coach workout step details for date | Supported/fragile private API | Tested with user's account | `/workout-service/fbt-adaptive/{workoutUuid}` | Returns full `workoutSegments`/`workoutSteps` via normal `python-garminconnect` token auth. Browser shows equivalent `/gc-api/workout-service/fbt-adaptive/{workoutUuid}`. |
|
||||
| Read compact Coach workout summary | Supported | Tested with user's account | Adaptive plan `taskList[].taskWorkout` | Exposes fields such as `workoutName`, `estimatedDurationInSecs`, `workoutDescription`, `trainingEffectLabel`, `workoutPhrase`, `workoutUuid`. |
|
||||
| List Garmin calendar entries | Supported | Tested with user's account | `get_scheduled_workouts(year, month)` | Calendar contains `fbtAdaptiveWorkout` entries with `workoutUuid` and `trainingPlanId`. |
|
||||
| List normal workouts | Supported | Tested with user's account | `get_workouts()` | Returned normal cycling workouts. |
|
||||
| Fetch normal workout by ID | Supported in source | Source-supported; untested here | `get_workout_by_id()` | Useful for schema inspection and clone comparisons. |
|
||||
| Create/upload normal cycling workout | Supported in source | Source-supported; untested here | `upload_workout()`, `upload_cycling_workout()` | Probe includes a minimal dummy cycling workout upload test. |
|
||||
| Schedule normal workout on date | Supported in source | Source-supported; untested here | `schedule_workout(workout_id, YYYY-MM-DD)` | This is the preferred path if upload works. |
|
||||
| Delete generated workout | Supported in source | Implemented with guard; not account-tested | `delete_workout()` | Probe refuses to delete unless the ID is in the generated-prefix workout list and `--confirm` is passed. |
|
||||
| Unschedule generated workout | Supported in source | Implemented with guard; not account-tested | `unschedule_workout()` | Probe refuses to unschedule unless the scheduled ID is in the generated-prefix calendar list and `--confirm` is passed. |
|
||||
| Avoid duplicate clones | Partially supported | Implemented by name prefix/date | Probe code | Uses `GCClone YYYY-MM-DD` prefix. Full app should persist mappings in SQLite. |
|
||||
| Generate local FIT workout file | Possible | Official SDK documented; not implemented in probe | Garmin FIT Python SDK | SDK has an encoder. Need workout-file-specific implementation and device testing. |
|
||||
| Copy `.fit` to `GARMIN/NewFiles` | Unknown | Common Garmin behavior; not verified here | Not accepted as primary source | Treat only as manual fallback until tested on Edge 1030. |
|
||||
| Use `GARMIN/Workouts/Schedule` for date scheduling | Unknown | Not proven | No reliable official source found | Garmin Connect calendar scheduling is safer. |
|
||||
| Coach clone marks Garmin Coach workout complete | Unknown/likely unsupported | Guessed | Separate cloned workout | A clone is a normal workout, not the original Coach workout. Completion linkage may not happen. |
|
||||
|
||||
## Official Garmin API Findings
|
||||
|
||||
Garmin's official Training API is aimed at approved integrations that publish workouts and training plans into Garmin Connect. The Training API page says it can publish workouts/training plans to the Garmin Connect calendar, then users sync compatible devices.
|
||||
|
||||
This is directionally opposite of the required first step: reading a personal Garmin Coach/adaptive workout from Garmin Connect. I found no official endpoint for reading Garmin Coach plan workout definitions from a user's account.
|
||||
|
||||
The Developer Program FAQ states the Garmin Connect Developer Program is for enterprise/business use and applications are reviewed. That makes it unrealistic for a local personal tool unless the user already controls an approved business integration.
|
||||
|
||||
## Unofficial Garmin Connect Findings
|
||||
|
||||
The current `python-garminconnect` source exposes the important private endpoints:
|
||||
|
||||
- Workouts: `/workout-service`
|
||||
- Calendar: `/calendar-service`
|
||||
- Training plans: `/trainingplan-service/trainingplan`
|
||||
|
||||
Relevant methods in source:
|
||||
|
||||
- `get_workouts(start=0, limit=100)`
|
||||
- `get_workout_by_id(workout_id)`
|
||||
- `upload_workout(workout_json)`
|
||||
- `upload_cycling_workout(workout)`
|
||||
- `schedule_workout(workout_id, date_str)`
|
||||
- `get_scheduled_workouts(year, month)`
|
||||
- `get_scheduled_workout_by_id(scheduled_workout_id)`
|
||||
- `delete_workout(workout_id)`
|
||||
- `unschedule_workout(scheduled_workout_id)`
|
||||
- `get_training_plans()`
|
||||
- `get_training_plan_by_id(plan_id)`
|
||||
- `get_adaptive_training_plan_by_id(plan_id)`
|
||||
|
||||
The package demo routes plans with `trainingPlanCategory == "FBT_ADAPTIVE"` to the adaptive endpoint. That is the strongest lead for Garmin Coach/adaptive plan extraction, but it does not prove that cycling Coach step details are present for this user's account.
|
||||
|
||||
Authentication is a risk. The current `python-garminconnect` package has token storage and MFA support, while the older `garth` project now says new logins may not work because Garmin changed auth. The probe therefore treats login itself as a testable capability.
|
||||
|
||||
The probe also includes a read-only metadata call to `/workout-service/workout/types` and a normal-workout-by-ID dump. Those are intended to compare Garmin's current accepted workout schema against any Coach/adaptive JSON found during account testing.
|
||||
|
||||
For account testing, `probe_garmin.py report --dump-json` runs the read-only discovery checks in one pass and writes `debug/probe_report.json`. That report is the most compact evidence bundle for deciding whether the proof-of-concept can continue to clone testing.
|
||||
|
||||
### Account Probe Evidence
|
||||
|
||||
The user's June 16, 2026 probe found an active Garmin Cycling Coach adaptive plan:
|
||||
|
||||
- Plan ID: `45692639`
|
||||
- Category: `FBT_ADAPTIVE`
|
||||
- Name: `2026 Fitness Plan test`
|
||||
- Today's Coach workout: `Sprint`
|
||||
- Workout UUID: `fe26ad37-69e8-495b-9d3d-2ab10fb64334`
|
||||
- Estimated duration: 3120 seconds
|
||||
- Compact description: `2x13x0:10@All Out`
|
||||
|
||||
The adaptive plan detail endpoint returned `taskList[].taskWorkout` summaries, not `workoutSegments` or `workoutSteps`. The calendar endpoint returned `itemType=fbtAdaptiveWorkout` entries with matching `trainingPlanId` and `workoutUuid`.
|
||||
|
||||
The missing endpoint was found in the browser network analyzer:
|
||||
|
||||
```text
|
||||
/gc-api/workout-service/fbt-adaptive/{workoutUuid}
|
||||
```
|
||||
|
||||
For the June 16 `Sprint` workout, that response contains `workoutSegments` and nested repeat groups. Testing showed that the equivalent Connect API route also works:
|
||||
|
||||
```text
|
||||
/workout-service/fbt-adaptive/{workoutUuid}
|
||||
```
|
||||
|
||||
The CLI now uses that Connect API route when a `workoutUuid` is available. The browser `/gc-api` route returned `403` with the package token session, so it remains a browser-specific diagnostic path.
|
||||
|
||||
This means exact Coach-to-normal-workout cloning is feasible with the unofficial API.
|
||||
|
||||
## Workout Schema Findings
|
||||
|
||||
`python-garminconnect.workout` encodes the normal Garmin Connect workout schema enough for a dummy cycling workout:
|
||||
|
||||
- Sport type: cycling has `sportTypeId=2`, `sportTypeKey="cycling"`.
|
||||
- Step types include warmup, cooldown, interval, recovery, rest, repeat, other, and main.
|
||||
- End conditions include lap button, time, distance, calories, power, heart rate, iterations, fixed rest, fixed repetition, and reps.
|
||||
- Target types include no target, power zone, cadence, heart-rate zone, speed zone, pace zone, grade, heart-rate lap, power lap, and resistance.
|
||||
|
||||
The typed helpers only create simple time-based no-target steps. More exact Coach cloning will require preserving or constructing `targetType`, target values, and repeat groups from the raw Coach/adaptive JSON after the probe shows what Garmin returns.
|
||||
|
||||
Edge 1030 acceptance for target details is untested. The dummy workout intentionally starts with simple time-based no-target steps to separate "can upload/schedule/sync" from "can preserve all Coach targets".
|
||||
|
||||
## Direct Device / FIT Fallback
|
||||
|
||||
A local FIT export fallback is plausible but lower priority:
|
||||
|
||||
- Garmin's official FIT Python SDK can encode FIT files.
|
||||
- FIT workout files can represent structured workout concepts through FIT profile messages, but the probe does not implement this yet.
|
||||
- `fitparse`/`fitdecode` are primarily decoders and are not enough by themselves for reliable workout generation.
|
||||
- Copying files to Garmin device folders is not a good primary automation path because scheduling behavior on `GARMIN/Workouts/Schedule` is not reliably documented for the Edge 1030.
|
||||
|
||||
The safer first proof is still Garmin Connect upload + calendar scheduling. A FIT export can become a manual fallback if Garmin Connect upload/schedule works but Coach extraction or automated scheduling remains blocked.
|
||||
|
||||
## Stop / Continue Decision
|
||||
|
||||
Do not build the web app yet.
|
||||
|
||||
Continue to Phase 2 probe because:
|
||||
|
||||
- Normal workout upload/scheduling is source-supported and can be tested with a dummy workout.
|
||||
- Adaptive/Coach endpoint access is source-supported enough to probe.
|
||||
|
||||
Stop before Phase 3 unless the user confirms at least:
|
||||
|
||||
- The dummy scheduled workout appears on the Edge 1030.
|
||||
- Garmin Coach/adaptive JSON contains enough step data to clone through the FBT adaptive endpoint, or the agreed fallback is summary-derived/manual template generation.
|
||||
|
||||
If the FBT adaptive endpoint changes or becomes unavailable, the best fallback architecture is:
|
||||
|
||||
- Keep Garmin Connect login/calendar/workout discovery.
|
||||
- Fall back to parsing adaptive plan `taskWorkout` summaries and producing clearly labeled approximate normal workouts.
|
||||
- Allow manual review/edit of generated steps before upload.
|
||||
- Optionally export FIT workouts for manual device copy.
|
||||
- Do not claim unattended exact Garmin Coach cloning unless browser endpoint authentication is stable.
|
||||
|
||||
## Sources
|
||||
|
||||
- Garmin Training API: <https://developer.garmin.com/gc-developer-program/training-api/>
|
||||
- Garmin Developer Program FAQ: <https://developer.garmin.com/gc-developer-program/program-faq/>
|
||||
- Garmin Developer Program Overview: <https://developer.garmin.com/gc-developer-program/overview/>
|
||||
- Garmin Health API: <https://developer.garmin.com/gc-developer-program/health-api/>
|
||||
- Edge 1030 Workouts manual: <https://www8.garmin.com/manuals/webhelp/edge1030/EN-US/GUID-99D42128-10E4-4AA4-B961-58FD70A431A0.html>
|
||||
- Edge 1030 Following a Workout From Garmin Connect: <https://www8.garmin.com/manuals/webhelp/edge1030/EN-US/GUID-D6E80F0C-F319-47D1-AF95-0884F3386635.html>
|
||||
- Edge 1030 Starting a Workout: <https://www8.garmin.com/manuals/webhelp/edge1030/EN-US/GUID-5CA69960-1471-47A1-867E-820FD0B240C8.html>
|
||||
- Edge 1030 Training Calendar: <https://www8.garmin.com/manuals/webhelp/edge1030/EN-US/GUID-104A175C-4F28-4995-8476-42405A543F11.html>
|
||||
- `python-garminconnect`: <https://github.com/cyberjunky/python-garminconnect>
|
||||
- `python-garminconnect` workout models: <https://github.com/cyberjunky/python-garminconnect/blob/master/garminconnect/workout.py>
|
||||
- `garth` deprecation notice: <https://github.com/matin/garth>
|
||||
- Garmin FIT Python SDK: <https://github.com/garmin/fit-python-sdk>
|
||||
33
docs/limitations.md
Normal file
33
docs/limitations.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Limitations
|
||||
|
||||
## Unofficial Garmin APIs
|
||||
|
||||
This project uses private Garmin Connect endpoints through `python-garminconnect`. Garmin can change authentication, endpoint paths, payload schemas, or rate limits without notice.
|
||||
|
||||
## Garmin Coach Extraction
|
||||
|
||||
Garmin Coach/adaptive cycling workout steps are exposed through the private `/workout-service/fbt-adaptive/{workoutUuid}` endpoint for the tested account. This is still an unofficial endpoint: Garmin can change it, remove fields, or change authentication without notice.
|
||||
|
||||
## Garmin Coach Completion
|
||||
|
||||
A cloned workout is a separate normal Garmin Connect workout. Completing it may not mark the original Garmin Coach workout complete.
|
||||
|
||||
## Target Conversion
|
||||
|
||||
The first real Coach JSON contains time steps, nested repeats, heart-rate range targets, and instruction targets. Power, cadence, distance, open-ended, and lap-button-until-press targets may still need more examples before conversion can be considered complete.
|
||||
|
||||
The CLI performs conservative local validation before upload, but that does not prove Garmin Connect or the Edge 1030 will accept every target detail. It only catches obvious malformed payloads before account writes.
|
||||
|
||||
Offline dump analysis is intentionally structural. It can show whether saved JSON contains dated workout-like objects and whether a clone payload can be built, but it cannot prove Garmin Connect upload or Edge behavior.
|
||||
|
||||
## Device File Fallback
|
||||
|
||||
Generating FIT workout files is plausible, but direct device folder scheduling is not yet proven for the Edge 1030. Garmin Connect scheduling remains the safer first path.
|
||||
|
||||
## Authentication
|
||||
|
||||
MFA/token behavior depends on Garmin account state, region, rate limits, and current Garmin SSO behavior. Token files should be treated as sensitive and kept out of git.
|
||||
|
||||
## Redaction
|
||||
|
||||
Debug dumps are redacted on a best-effort basis. The redactor preserves Garmin workout schema identifiers needed for conversion, such as `stepTypeId` and `workoutTargetTypeId`, while removing known account, owner, token, email, and device identifier fields. Treat all `debug/` files as private anyway.
|
||||
99
docs/phase3-gate.md
Normal file
99
docs/phase3-gate.md
Normal file
@ -0,0 +1,99 @@
|
||||
# Phase 3 Gate
|
||||
|
||||
Do not build the Dockerized web app until these checks are complete.
|
||||
|
||||
## Local Evidence
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
uv run python scripts/local_check.py
|
||||
```
|
||||
|
||||
Required result:
|
||||
|
||||
- All local checks pass.
|
||||
- `debug/local_checks.json` is written.
|
||||
|
||||
This proves only that the checkout is internally consistent. It does not prove Garmin account, Garmin Connect, or Edge 1030 behavior.
|
||||
|
||||
## Garmin Account Evidence
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py report --dump-json
|
||||
```
|
||||
|
||||
Required result:
|
||||
|
||||
- Garmin login succeeds, including MFA if required.
|
||||
- `debug/probe_report.json` is written.
|
||||
- Normal workouts are visible or the report clearly records why they are not.
|
||||
- Coach/adaptive plan discovery either finds a matched workout for today/tomorrow or clearly shows that step extraction is missing.
|
||||
|
||||
Optional offline analysis:
|
||||
|
||||
```sh
|
||||
uv run python scripts/analyze_dump.py debug/coach_workout_today.json --date today --clone-dry-run
|
||||
```
|
||||
|
||||
If Coach tasks are visible but no step arrays are found, run:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py coach-endpoints --date today --dump-json
|
||||
```
|
||||
|
||||
Required result for exact cloning:
|
||||
|
||||
- At least one response contains `workoutSegments` or `workoutSteps`.
|
||||
|
||||
Current account evidence from June 16, 2026:
|
||||
|
||||
- The active `FBT_ADAPTIVE` cycling Coach plan is visible.
|
||||
- The `Sprint` workout is visible as a dated Coach task and calendar item.
|
||||
- Browser network analysis found `/gc-api/workout-service/fbt-adaptive/<workoutUuid>`, which returns `workoutSegments` and `workoutSteps`.
|
||||
- CLI access works through the equivalent Connect API route `/workout-service/fbt-adaptive/<workoutUuid>`.
|
||||
- `clone_today_workout.py --dry-run` succeeds from the CLI and preserves the visible step structure.
|
||||
|
||||
## Edge 1030 Evidence
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py dummy --date tomorrow --schedule
|
||||
```
|
||||
|
||||
Then sync and test on the Edge 1030.
|
||||
|
||||
Required result:
|
||||
|
||||
- Dummy workout appears in Garmin Connect.
|
||||
- Dummy workout appears under `Training > Workouts`.
|
||||
- Dummy workout appears under `Training > Training Plan > calendar icon > scheduled date`.
|
||||
- Dummy workout can start while normal Edge navigation is active.
|
||||
|
||||
## Clone Evidence
|
||||
|
||||
Only run if Coach/adaptive inspection shows plausible steps:
|
||||
|
||||
```sh
|
||||
uv run python scripts/clone_today_workout.py --date today --dry-run --dump-json
|
||||
uv run python scripts/clone_today_workout.py --date today --schedule
|
||||
```
|
||||
|
||||
Required result:
|
||||
|
||||
- Dry-run payload passes local validation.
|
||||
- Scheduled clone appears in Garmin Connect.
|
||||
- Scheduled clone appears on the Edge 1030.
|
||||
- Clone can start while normal Edge navigation is active.
|
||||
|
||||
## Decision
|
||||
|
||||
Build Phase 3 only if either:
|
||||
|
||||
- Dummy scheduling and Coach clone scheduling both work, or
|
||||
- Dummy scheduling works and we explicitly choose a fallback architecture because Coach extraction is unavailable.
|
||||
|
||||
If dummy scheduling fails, fix or replace the Garmin Connect scheduling path before building the web app.
|
||||
275
docs/testing.md
Normal file
275
docs/testing.md
Normal file
@ -0,0 +1,275 @@
|
||||
# Phase 2 Probe Testing
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```sh
|
||||
uv sync
|
||||
```
|
||||
|
||||
Optional environment setup:
|
||||
|
||||
```sh
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Fill `GARMIN_EMAIL` and `GARMIN_PASSWORD` in `.env`, or leave them blank and the scripts will prompt. Tokens are stored in `.garmin-tokens` by default.
|
||||
|
||||
Garmin Coach cycling detail currently comes from this Connect API endpoint:
|
||||
|
||||
```text
|
||||
/workout-service/fbt-adaptive/<workoutUuid>
|
||||
```
|
||||
|
||||
The browser network analyzer shows the same route under `/gc-api/...`, but the CLI should use the Connect API route with normal `python-garminconnect` tokens. If you are debugging the browser-only `/gc-api` route specifically, you can copy the `Cookie` request header into `.env`:
|
||||
|
||||
```text
|
||||
GARMIN_GC_API_COOKIE=JWT_WEB=...; ...
|
||||
```
|
||||
|
||||
Treat that cookie like a password. Do not commit `.env`, screenshots containing the cookie, or debug logs with request headers.
|
||||
|
||||
## Local Checkout Validation
|
||||
|
||||
```sh
|
||||
uv run python scripts/local_check.py
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS: ruff check .`
|
||||
- `PASS: ty check`
|
||||
- `PASS: pytest`
|
||||
- `PASS: ... scripts/probe_garmin.py dummy --date tomorrow --dry-run`
|
||||
- `debug/local_checks.json` is written.
|
||||
|
||||
This command does not log in to Garmin or upload anything.
|
||||
|
||||
## Login
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py login
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- If cached tokens work, it prints account/device summary.
|
||||
- If credentials are needed, it prompts for email/password.
|
||||
- If MFA is required, it prompts for the MFA code.
|
||||
- It must not print the Garmin password or MFA code.
|
||||
|
||||
## Discovery
|
||||
|
||||
Preferred one-pass read-only report:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py report --dump-json
|
||||
```
|
||||
|
||||
This writes `debug/probe_report.json`. It logs in once and collects account summary, training plans, workout type metadata, normal workouts, today/tomorrow calendar entries, and today/tomorrow Coach/adaptive match summaries. It continues through partial API failures and records errors in the report.
|
||||
|
||||
Individual discovery commands:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py plans
|
||||
uv run python scripts/probe_garmin.py types
|
||||
uv run python scripts/probe_garmin.py calendar --date today --dump-json
|
||||
uv run python scripts/probe_garmin.py calendar --date tomorrow --dump-json
|
||||
uv run python scripts/probe_garmin.py workouts --limit 25 --dump-json
|
||||
uv run python scripts/probe_garmin.py generated --prefix GCClone
|
||||
uv run python scripts/probe_garmin.py generated-calendar --date today --prefix GCClone
|
||||
```
|
||||
|
||||
Check:
|
||||
|
||||
- Training plans are listed.
|
||||
- Any plan marked `FBT_ADAPTIVE`, Coach, or adaptive is shown as a candidate.
|
||||
- Workout type metadata is written to `debug/workout_types.json`.
|
||||
- Calendar JSON dumps are written under `debug/`.
|
||||
- Normal workouts are visible.
|
||||
- Generated probe/clone workout templates are listed separately by prefix, if any exist.
|
||||
- Generated scheduled calendar entries are listed separately by prefix for the inspected month, if any exist.
|
||||
|
||||
If you have a normal cycling workout that already syncs to the Edge 1030, inspect it:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py workout <workout-id> --dump-json
|
||||
```
|
||||
|
||||
This writes `debug/workout_<workout-id>.json`, which is useful as a known-good schema reference.
|
||||
|
||||
`debug/` is gitignored because dumps can contain private Garmin account data even after redaction.
|
||||
|
||||
## Coach Workout Inspection
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py coach --date today --dump-json
|
||||
uv run python scripts/probe_garmin.py coach --date tomorrow --dump-json
|
||||
```
|
||||
|
||||
Useful output:
|
||||
|
||||
- `Workout-like objects with workoutSegments/workoutSteps: N`
|
||||
- A readable step summary with warmup/interval/recovery/cooldown/repeat steps.
|
||||
- `debug/coach_workout_YYYY-MM-DD.json` when a matched workout is found.
|
||||
- `debug/coach_workout_today.json` when today's matched workout is found.
|
||||
- The probe should fetch the full `fbt-adaptive` detail from `workoutUuid`.
|
||||
|
||||
Blocking output:
|
||||
|
||||
- `No parseable workoutSegments/workoutSteps found for this date.`
|
||||
|
||||
If blocked, report the command output and keep the redacted JSON in `debug/` for inspection.
|
||||
|
||||
If `coach` finds dated Coach task summaries but no step arrays, run the read-only endpoint sweep:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py coach-endpoints --date today --dump-json
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- It prints the plan ID, workout name, `workoutUuid`, and calendar item ID.
|
||||
- It tries candidate private endpoints and continues through 404/errors.
|
||||
- It writes `debug/coach_endpoint_probe_YYYY-MM-DD.json`.
|
||||
|
||||
Useful output:
|
||||
|
||||
- `At least one candidate endpoint returned workout-like step data.`
|
||||
|
||||
Blocking output:
|
||||
|
||||
- `No candidate endpoint returned workoutSegments/workoutSteps.`
|
||||
|
||||
For the June 16, 2026 account probe, `/workout-service/fbt-adaptive/<workoutUuid>` returned step arrays. `/workout-service/workout/uuid/<workoutUuid>` returned an empty list, and other candidate detail endpoints returned errors.
|
||||
|
||||
## Offline Dump Analysis
|
||||
|
||||
Analyze a saved dump without logging in to Garmin:
|
||||
|
||||
```sh
|
||||
uv run python scripts/analyze_dump.py debug/coach_workout_today.json --date today --clone-dry-run --dump-json
|
||||
```
|
||||
|
||||
Expected, if the dump contains a dated workout:
|
||||
|
||||
- Date-containing object count is shown.
|
||||
- Workout-like object count is shown.
|
||||
- A matched workout summary is printed.
|
||||
- `Local clone payload passed validation.` is printed if conversion is structurally safe.
|
||||
|
||||
If the dump does not contain a dated workout-like object, the command exits non-zero and prints:
|
||||
|
||||
```text
|
||||
No dated workout-like object was found.
|
||||
```
|
||||
|
||||
## Dummy Upload And Schedule Test
|
||||
|
||||
Dry run:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py dummy --date tomorrow --dry-run
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `Dummy workout payload passed local validation.`
|
||||
- The generated JSON is printed.
|
||||
- Nothing is uploaded.
|
||||
|
||||
Upload and schedule:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py dummy --date tomorrow --schedule
|
||||
```
|
||||
|
||||
After upload/schedule succeeds:
|
||||
|
||||
1. Sync the Edge 1030 with Garmin Connect / Garmin Express.
|
||||
2. Check `Training > Workouts`.
|
||||
3. Check `Training > Training Plan > calendar icon > tomorrow's date`.
|
||||
4. Start normal route/course navigation, then start the dummy workout.
|
||||
5. Confirm whether workout prompts and navigation can run together.
|
||||
|
||||
Report:
|
||||
|
||||
- Did the dummy workout appear in Garmin Connect?
|
||||
- Did it appear on the Edge 1030?
|
||||
- Did it appear in the calendar/date view?
|
||||
- Could it start while navigation was active?
|
||||
|
||||
## Clone Test
|
||||
|
||||
Dry run:
|
||||
|
||||
```sh
|
||||
uv run python scripts/clone_today_workout.py --date today --dry-run --dump-json
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `Converted clone payload passed local validation.`
|
||||
- The converted JSON is printed.
|
||||
- Nothing is uploaded.
|
||||
|
||||
Upload and schedule only if the dry run shows plausible steps:
|
||||
|
||||
```sh
|
||||
uv run python scripts/clone_today_workout.py --date today --schedule
|
||||
```
|
||||
|
||||
The clone is named:
|
||||
|
||||
```text
|
||||
GCClone YYYY-MM-DD <original workout name>
|
||||
```
|
||||
|
||||
Repeated runs skip an existing clone for the same date unless `--force` is passed.
|
||||
|
||||
Report:
|
||||
|
||||
- Did the cloned workout appear in Garmin Connect?
|
||||
- Did it appear on the Edge 1030 under workouts/calendar?
|
||||
- Can it be started on the Edge?
|
||||
- Can it run together with normal Edge navigation?
|
||||
- After completion, did Garmin Coach mark the original Coach workout complete?
|
||||
|
||||
## Generated Workout Cleanup
|
||||
|
||||
List generated workout templates:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py generated --prefix GCClone
|
||||
```
|
||||
|
||||
List generated scheduled calendar entries for the month containing `--date`:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py generated-calendar --date today --prefix GCClone
|
||||
```
|
||||
|
||||
Unschedule a generated calendar entry only if you are sure the scheduled ID is from the generated-prefix calendar list:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py generated-calendar --date today --prefix GCClone --unschedule-id <scheduled-id> --confirm
|
||||
```
|
||||
|
||||
Delete a generated workout template only if you are sure the ID is from the generated-prefix list:
|
||||
|
||||
```sh
|
||||
uv run python scripts/probe_garmin.py generated --prefix GCClone --delete-id <workout-id> --confirm
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- Without `--delete-id` or `--unschedule-id`, these commands are read-only.
|
||||
- With `--delete-id` or `--unschedule-id`, the command refuses to run unless `--confirm` is also passed.
|
||||
- The commands refuse IDs that are not in the generated-prefix list.
|
||||
|
||||
Unknown:
|
||||
|
||||
- Whether deleting the workout template also removes any scheduled calendar entry. Prefer unscheduling first, then check Garmin Connect calendar afterward and report what happened.
|
||||
|
||||
## Current Acceptance Gate
|
||||
|
||||
Phase 3 should not start until the dummy scheduled workout works on the Edge 1030 and the Coach inspection either exposes parseable steps or we explicitly choose a fallback architecture.
|
||||
Reference in New Issue
Block a user