Add matched training signals, daily recovery and performance analysis

This commit is contained in:
Peter Stockings
2026-10-03 13:22:32 +10:00
parent e6e2f22479
commit 418e03583a
37 changed files with 1380 additions and 47 deletions
+112
View File
@@ -1,5 +1,117 @@
# Run analysis DSL
## Deep training signals
`intervals` reads persisted, non-overlapping continuous moving windows of up to
30 seconds. Recording breaks over ten seconds, distance resets, stops and
implausible movement end a window; windows never join across those breaks.
Filter `duration >= 25 s` to remove short fragments. `elapsed` and
`moving_offset` are minutes since recording start and usable moving start.
Elapsed offsets use relative time even when FIT timestamps are Unix timestamps.
```text
intervals
during last 16 weeks
reduce by run min 3 min
where elapsed >= 10 min
and duration >= 25 s
and pace between 6:30 and 7:00 /km
and grade between -2 and 2 %
and grade_variability <= 2 %
and pace_variability <= 15 %
and sensor_coverage >= 90 %
and hr_artifact_percent = 0 %
summarize mean(heart_rate) as "Matched HR"
by run
sort by date asc
compare none
chart line
```
Filters run **before** `reduce by run`. A session needs the specified total
matched moving minutes to contribute one observation. Within-session HR and
mechanics are weighted by usable sensor time; `mean` across reduced observations
weights each session equally. `count()` then counts independent sessions rather
than windows. Matched windows can be separated in time. This is different from
requiring one uninterrupted three-minute section. Charts show distinct-session
and usable-minute coverage, and run plots preserve calendar spacing.
`grade` is duration-weighted signed grade using the existing smoothed elevation
model; `grade_variability` is its population SD in percentage points. GAP and
grade require at least 80% terrain coverage. Window-level cadence, power,
stride length, vertical oscillation and ground contact include sensor coverage
metrics. HR outside 30–240 bpm or a jump exceeding 20 bpm per second is excluded
and counted in `hr_artifact_percent`. These coarse checks do not detect every
sensor artifact or cadence lock. Terrain, HR lag, weather and session intent
remain confounders; the matcher does not claim to remove them.
After reduction, `heart_rate_drift` is recomputed over matched moving-time
halves, excluding any filtered warm-up. Each half needs at least ten minutes of
joint usable HR and terrain, and 80% of the half's time. Halves must differ by
at most 10% in speed and one percentage point of mean grade. Otherwise the
result is missing. Positive means lower speed/HR efficiency later. This is a
descriptive comparison, not an overtraining diagnosis. Recording-level flags
require reduction to independent sessions; a baseline still needs ten runs.
`efforts` expands the persisted best elapsed windows. Use `where distance = 5 km`
and `min(effort_time)` for a 5 km progression. Whole runs expose `best_1km`,
`best_2km`, `best_5km`, and `best_10km`, which accept second/minute filters and
display elapsed min:sec. No attempt at that distance remains missing. These
efforts are windows inside runs, not standardized races.
```text
days
during last 16 weeks
long run distance >= 10 km
summarize latest(days_since_long_run), latest(rolling_28d_distance)
by day
compare none
chart line
display overlay
```
`days` includes rest days. `sessions`, `days_since_run`, `days_since_long_run`,
`run_day_streak`, and `long_run_share` expose continuity and concentration.
`long_run_share` is the largest individual run's share of the current seven-day
distance. Daily 7/28-day windows include the current local day plus the previous
6/27 local days, including history before the query. Whole-run rolling metrics
continue to use elapsed 168/672-hour windows. Missing prior long-run history
does not become a fabricated gap. `latest(field)` selects the latest input by
timestamp regardless of sort order, retaining missing values on that input;
it is useful for current dashboard values and cannot define a baseline band.
`health` reads owner-only Garmin resting HR, overnight HRV, sleep duration and
stress observations (`resting_hr`, `hrv`, `sleep_hours`, `stress`). Enable the
optional recovery sync in Settings. The worker refreshes today/yesterday and
backfills a bounded batch of earlier dates. Provider gaps and unsupported watch
observations stay missing; no readiness score is inferred. Daily inputs link
to their contributing runs when present; rest/recovery-only days do not invent
activity links. Daily sources also support calendar rolling calculations.
Private activity notes expose `perceived_effort` (1–10) and `fatigue` (0–10).
They are never shown on public or friend activity pages. Settings accepts two
personal HR boundaries. `lower_hr_minutes`, `middle_hr_minutes`, and
`upper_hr_minutes` sum actual usable moving sample time, rather than assigning
an entire run/window according to its average HR. Boundary HR belongs to the
higher band. Missing thresholds mean missing results; physiological zones are
never assumed.
`route similar to ID` restricts recordings to an approximate full-course match
against one of your own runs. The comparison resamples compact geometry along
distance, requires endpoints within 100 m, length within 10%, mean course
separation ≤100 m and 90th-percentile separation ≤200 m. Different directions
and recording breaks are excluded; geometry shorter than 500 m is unavailable.
This is an exploratory same-start course matcher, not exact named-segment
recognition. Always inspect the contributing courses.
Operators can run `flask --app app install-training-signals --email ACCOUNT`
to save editable examples and pin the first five. `--pace-low`, `--pace-high`
and `--long-run` customize matching. Re-running preserves existing library
edits. Recompute old recordings with `backfill-analysis` (version 3).
`backfill-health --email ACCOUNT --days 90` imports bounded recovery history
using the normal account session lock and encrypted token rotation; only this
account's recovery sync is enabled.
## Account library and live dashboard
Save an analysis with a name, investigation collection and notes. **Save as new**