Files
geniusrun/docs/superpowers/specs/2026-07-17-profile-taxonomy-analysis-engine-design.md
Christophe Vila f9d85e16ef Rename smartrun to geniusrun throughout the codebase
Updates the Go module path, cmd/smartrund -> cmd/geniusrund, the
smartrun-dev skill, .gitignore, and every reference in docs/CLAUDE.md
to match.
2026-07-24 21:08:07 +02:00

238 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Profile, workout taxonomy & analysis engine foundations
**Status:** Draft — approved by user, pending implementation planning
**Date:** 2026-07-17
## Context
geniusrun currently classifies runs into arbitrary, user-created "workout
kinds" using absolute pace/HR thresholds, and has no concept of a user
profile — Garmin credentials are passed as environment variables at process
start, and there is exactly one hardcoded set of engine parameters.
This spec refines that into a fixed running-specific taxonomy, replaces
absolute-pace classification with structural and relative-to-self signals,
adds warm-up/work/cool-down phase detection with per-phase heart-rate
analysis, and introduces a single-profile settings model so all of this is
tunable without redeploying.
Two related features are explicitly **out of scope** for this spec, per the
user's direction to discuss them later once this foundation exists:
- **Deep per-phase workout analysis** beyond the HR metrics described in
section 5 (the user has more detailed expectations to describe once phase
segmentation exists to hang them on).
- **Adaptive pace recommendation** ("delta" between the user's declared pace
ranges and workout-derived ones) — still undecided whether this needs an
AI component or can be fully deterministic.
The pace ranges and expected HR zones introduced here exist as **data
capture** for that future delta feature; nothing in this spec computes or
displays a delta.
## Goals
1. A single-profile settings model: Garmin credentials + all tunable engine
parameters, stored in SQLite instead of environment variables, edited
through one settings screen.
2. A fixed, 7-type running workout taxonomy replacing the current
user-created "workout kinds," each with a user-editable target pace
range (no history — the synced activity log *is* the history).
3. Classification driven by structure and by an activity's *relative*
standing among its own recent history — never by matching the user's
declared target pace ranges.
4. Deterministic warm-up/work/cool-down phase detection per activity, with
per-phase heart-rate analysis (zone, drift, basic recovery signal),
configurable per workout type.
5. A reusable "classification preview" — every activity scored against
every workout type at once — as the shared tool for iteratively tuning
natural-language rule definitions against real history.
6. A "recompute" action that re-runs phase detection and reclassification
across all activities, since relative metrics and phase parameters can
change what a past activity should have been classified as.
## Non-goals
- Multi-profile switching / multi-tenant data (explicitly declined — one
active profile at a time; the schema does not need per-row profile
scoping, a singleton row is sufficient).
- The delta/recommendation calculation itself (section above).
- Deep per-phase analysis beyond HR zone/drift/basic recovery signal.
- Any AI/LLM involvement at runtime — classification and phase detection
are fully deterministic, tuned offline against real data before being
encoded as rules/parameters.
## 1. Profile
A new **`profile`** table, a singleton row (`id = 1`, following the existing
`sync_state` pattern), replacing environment-variable Garmin credentials and
consolidating all tunable engine parameters:
- `garmin_email`, `garmin_password` — moved out of env vars; entered once
via the profile screen.
- `rolling_window_days` (default `90`) — the population window for
relative-classification metrics (section 3).
- `max_heart_rate`, `resting_heart_rate` — inputs to the Heart Rate Reserve
(Karvonen) calculation used for zone detection (section 5).
- Five HR zone ranges, each a `(min_pct, max_pct)` pair of "% of heart rate
reserve," seeded with standard defaults (Z1 5060, Z2 6070, Z3 7080,
Z4 8090, Z5 90100) and user-editable.
- Per-workout-type phase-detection parameters (section 4) — e.g.
`easy_warmup_minutes`, `easy_cooldown_minutes`, `interval_warmup_minutes`,
one pair per non-lap-based type.
`internal/config` (env-var loading) shrinks to just `GENIUSRUN_ADDR`,
`GENIUSRUN_DB_PATH`, and the mcp-garmin subprocess paths — everything
runtime-tunable moves into `profile`.
**Validation** (rejected at save time, not silently accepted):
resting HR < max HR; zone ranges ascending and non-overlapping,
collectively covering 0100%.
**Credential updates & re-authentication.** `garmin.Client` currently takes
a fixed `Config` (including email/password) at construction and lazily
spawns the mcp-garmin subprocess on first use — credentials are only ever
whatever the process was launched with. Saving new Garmin credentials via
the profile screen must:
1. Update the running `garmin.Client`'s credentials in memory.
2. Reset its "started" state and terminate any already-spawned subprocess
(which would otherwise still be running under the *old* credentials).
The next "Connect to Garmin" click — the connect button and MFA-code entry
UI already built for the previous single-env-var-credential model — then
spawns a fresh subprocess with the newly-saved credentials and proceeds
through the existing authenticate/MFA flow unchanged. No new UI is needed;
saving the profile just makes that existing flow reachable at any time,
not only right after process launch.
## 2. Workout taxonomy & pace ranges
The existing `workout_kinds` table is reseeded with exactly seven fixed
rows and loses its "create new" / "delete" affordances in the UI — only
`rule_json` (and the new phase/HR fields below) remain editable per type:
`Easy Run`, `Long Run`, `Threshold 30'`, `Threshold 60'`, `Tempo`,
`Interval`, `MAS Test`.
A new **`workout_type_paces`** table, one row per workout kind:
- `pace_min_sec_per_km`, `pace_max_sec_per_km` — entered/displayed as
`m:ssm:ss`.
- `expected_hr_zone` — which of the 5 zones this type should predominantly
sit in (e.g. Easy Run → Zone 2), used later by the per-phase HR check
(section 5), not by classification.
Neither field has history — overwritten in place when the user updates
them, per the user's explicit direction (the activity log is the history).
## 3. Classification: relative & structural metrics
New metrics available to `internal/classify`'s condition-tree engine,
computed fresh at classification time (no cached/materialized column):
- **`distance_percentile`, `duration_percentile`** — this activity's rank
(01) among all *running* activities (any type except MAS Test) whose
start date falls within `rolling_window_days` of *this activity's own
date* (not "today" — so reclassifying an old activity is stable and
doesn't depend on when reclassification happens to run). Computed via a
straightforward SQL ranking query over `activities`.
- **`lap_pace_consistency`** — coefficient of variation of pace across the
work phase specifically (depends on section 4's phase boundaries), a low
value indicating "constant pace" — the structural signal the user
described for Long Run, replacing any pace-target comparison.
- Existing metrics (`lap_interval_pattern`, `lap_hr_drift_bpm_per_min`,
`lap_hr_recovery_bpm_per_min`, `aerobic_training_effect`, etc.) remain
available unchanged.
**`ReclassifyAll`**: a new bulk operation alongside the existing per-activity
`ClassifyActivity`, iterating every stored activity and re-evaluating it.
Necessary because relative metrics depend on the whole population — a
rolling-window setting change (or a new activity altering others'
percentile rank) can change what an old activity should be classified as,
not just new ones. Per-activity failures are logged and counted, not fatal
to the batch.
**Classification preview**: a new read path (`GET
/api/classification-preview` or similar) returning, for every activity,
*every* workout type's evaluation — matched or not, with score — not just
the ones that crossed the confidence threshold (unlike `kind_assignments`,
which only records matched candidates). This is the shared tool for the
natural-language rule-tuning sessions: change a rule, hit preview, see the
whole table shift.
## 4. Phase segmentation
A new **`activity_phases`** table: one row per detected phase per activity
`activity_id`, `phase_label` (`warmup` / `work` / `cooldown` for
continuous types; `warmup` / `work_1` / `rest_1` / `work_2` / ... /
`cooldown` for Interval), `start_elapsed_seconds`, `end_elapsed_seconds`.
Two phase-detection strategies, selected per workout type (not one
one-size-fits-all algorithm):
- **`fixed_duration`** (Easy, Long, Tempo, Threshold-30/60, first pass at
MAS Test): warm-up = first *N* configured minutes (`profile`'s
per-type setting), cool-down = last *M* configured minutes, work =
everything between.
- **`lap_intensity`** (Interval): reuses the existing Garmin lap
`IntensityType` tagging (ACTIVE/REST) built earlier — warm-up = before
the first ACTIVE lap, work = the ACTIVE/REST lap sequence itself (each
rep an individually labeled phase), cool-down = after the last one.
Phases are computed as part of the existing post-sync detail-fill step
(`internal/sync`), stored once, and only recomputed via the explicit
recompute action (section 6) — the same lifecycle as classification.
**Per-phase heart-rate analysis**, computed from `activity_samples` within
each phase's elapsed-time window:
- `avg_hr`, `max_hr` over the phase.
- `hr_zone` — the phase's average HR mapped to one of the 5 Karvonen zones
from `profile`, compared against that workout type's `expected_hr_zone`
(section 2) so a mismatch (e.g. an Easy Run run in Zone 4) is visible.
- `hr_drift_bpm_per_min` — reuses the existing lap-level drift regression
(already generic over any sample window), applied to the phase's window
instead of a lap's.
- A basic recovery signal for cool-down/rest phases (reusing the existing
HR-recovery regression). The more nuanced version the user described —
correlating HR fall against recovery *pace*, not HR alone — captures its
raw ingredients here (per-phase HR trend + per-phase pace) but the
composite "recovery quality" metric itself is deferred to the future deep
per-phase analysis discussion.
## 5. Settings panel & frontend
- **Profile screen**: Garmin credentials, rolling window, max/resting HR,
the 5 zone ranges, per-type phase parameters, and (from section 2)
per-type pace ranges + expected HR zone. One screen, one save, validated
per section 1.
- **Recompute action**: re-runs phase segmentation then `ReclassifyAll`
across all activities. Reuses the existing sync-progress-banner pattern
(live done/total) since this walks the whole history.
- **Classification preview page**: the table from section 3.
- **Activity detail page** (new — no per-activity view exists today):
pace/HR-vs-time chart (Recharts) with phase bands overlaid as
`ReferenceArea`s, plus a per-phase readout (HR zone, drift, avg pace).
## 6. Testing & error handling
- Phase detection and the new classification metrics are pure functions
tested against fixture sample/lap data, independent of the database or
Garmin — same pattern as the existing `internal/classify` tests.
- `ReclassifyAll` and the phase-recompute pass are per-activity fault
isolated: one activity's failure is logged and counted, not fatal to the
batch; the recompute status reports a failure count alongside progress.
- Profile save validates HR zone ranges (ascending, non-overlapping, full
0100% coverage) and resting-HR-less-than-max-HR before persisting,
returning a specific error message rather than accepting invalid state.
## Open questions carried forward (not blocking this spec)
- Exact per-type natural-language rule definitions and phase parameters —
to be tuned interactively against the user's real Garmin history using
the classification preview (section 3) once this foundation is built.
- The deep per-phase analysis feature beyond HR zone/drift/recovery.
- Whether the pace/HR-zone delta recommendation is AI-assisted or fully
deterministic.