User's exact direction (2026-06-16):
"for intensity lets work on the nomenclature. use RPE instead of
other terms for instensity (e.g. RPE6-7, could also use a range.
RPE6-7 generally corrosponds to zone 2—roughly the intensity at
which you would hold a conversation but it would be uncomfortable—
Peter Attia n.d.). Also keep this in mind for the parser, as when
a coach creates a workout in the parser they have user moderate or
another more ambigous term. when they use ambiguious terms prompt
the user to assign an RPE to that term. so we can collect and save
the swimming data accordingly. And then in future know that when
the coach writes moderate in the parser you know that corrolates
to RPE6-7."
The current intensity column on swimmer_pbs and workout_sets is free-text: 'max', 'moderate', 'threshold', 'easy'. These are ambiguous — different coaches mean different things by "moderate" (zone 2 vs zone 3). The flywheel needs a SHARED scale to compare across coaches and across history.
RPE (Rate of Perceived Exertion) is the standard scale in modern swim coaching (CR10 / OMNI: 1-10, where 1 = easy, 10 = all-out). It supports ranges (RPE 6-7), which matches the reality that lap-to-lap effort varies within a single set.
RPE6-7 = zone 2 = "the intensity at which you would hold a conversation but it would be uncomfortable" (Peter Attia, n.d., citing the conversational-pace test as a practical zone 2 marker).
The parser shouldn't FORCE the coach to speak RPE. The coach naturally says "moderate" or "hard" or "easy recovery." The parser should:
"tempo", "cruise", "all-out", "tough") in the workout description
coach_vocabulary(coach_id, user_term, rpe) that maps the coach's natural language to RPE
corner so the coach can override)
(4-5 / 6-7 / 8-9 / 10). The coach taps one. The mapping is saved.
RPE6-7. The parser learns the coach's vocabulary over time.
This is the data flywheel applied to the coach's language, not just their PBs. Each session, the parser either uses a known mapping (silent) or learns a new one (prompt). Over a season, the coach's vocabulary is fully mapped, and they can keep using their natural language without thinking about RPE.
The collected coach_vocabulary rows are also a per-coach "intensity training" dataset. Future analysis: "this coach's 'moderate' = RPE6-7, but the average across all coaches maps 'moderate' to RPE7-8" — useful for new-coach onboarding.
| User term (parser) | RPE mapping | Reasoning |
|---|---|---|
| "easy", "recovery" | RPE4-5 | Easy aerobic, can hold conversation |
| "moderate", "cruise" | RPE6-7 | Zone 2, conversational but uncomfortable |
| "hard", "threshold", "tempo" | RPE8-9 | Comfortably hard, lactate threshold |
| "max", "sprint", "all-out" | RPE10 | All-out, no range |
RPE 1-3 is unused in swim training (too easy to be relevant). RPE supports ranges ("RPE6-7" is two numbers; single values parse to a degenerate range [n, n]).
The v1.13 5-way match key is (swimmer, stroke, distance, intensity, gear). With RPE-based intensity, the match algorithm changes:
| Tier | Logic | Example |
|---|---|---|
| 1. Exact | workout RPE == PB RPE | "RPE6-7" matches "RPE6-7" |
| 1.5. Range overlap | workout RPE range ∩ PB RPE range ≠ ∅ | "RPE6-7" matches "RPE7" (the 7 overlaps) |
| 2. Same RPE, any gear | workout RPE == PB RPE, gear differs | "RPE6-7 @ paddles" matches "RPE6-7 @ no gear" |
| 3. Any RPE, same stroke/distance | any intensity overlap | "RPE6-7" matches "RPE4-5" (broader fallback) |
The algorithm parses RPE strings ("RPE6-7") into numeric ranges (e.g., [6, 7]). Single values parse to [n, n]. Legacy terms ('max', 'moderate', 'threshold', 'easy') parse via a fallback table at query time, so v1.14 ships non-destructively.
The pairing engine's accuracy depends on the predictor knowing the swimmer's pace for THE specific intensity the coach is asking for. Today, the predictor uses the PB at whatever intensity is closest to the workout's intensity (the 5-way match). With RPE:
swimmer's RPE6-7 PB → exact match → tight σ → high confidence
only the swimmer's RPE10 PB → tier 3 fallback → loose σ → low confidence (correctly: the predictor doesn't know the swimmer's moderate pace)
The RPE system makes the confidence calibration tighter and more honest about the actual training intensity, not the race intensity.
| Step | What | Why |
|---|---|---|
| 1 | Add coach_vocabulary table | (coach_id, user_term, rpe, created_at). The parser's learning state. |
| 2 | Update WorkoutSet.intensityLabel to store RPE syntax | "RPE6-7" becomes the canonical format from this point forward |
| 3 | Add RPE parser to SessionStartView | Detects ambiguous terms in the workout description, shows inline RPE picker, saves vocabulary |
| 4 | Update PBService to parse RPE strings | Tier 1.5 (range overlap) and updated tiered fallbacks |
| 5 | Map legacy 'max'/'moderate'/etc. at query time | Non-destructive — v1.14 ships, v2 migrates |
| 6 | CLI test scenario: "RPE range overlap" | Verify Tier 1.5 works correctly |
Published and managed by TARS, an AI co-author built on Nathan's gbrain.