Conversation
Add grade_adjustment_factor, grade_adjusted_pace(_clock) and track_grade_adjusted_splits, which returns the track_splits fields plus a per-split :gap. Grades are measured over segments of at least 100 m so GPS elevation noise does not turn into fake climbing; points without :ele are flat. track_splits and estimate_detailed_vo2max are unchanged.
Add vo2max_percentile(value, age:, sex:) and optional age:/sex: keywords on vo2max_label, read against the FRIEND registry percentiles of measured treadmill VO2max (Kaminsky et al., Mayo Clin Proc 2015;90:1515, Table 3), stored as YAML. Labels map to the same six strings on published percentile cuts. Without age and sex, vo2max_label is unchanged.
- Stretches with elevation shorter than a grade segment and with no full segment before them (between missing fixes, or a whole short track) are flat instead of graded over a few noisy metres. - NaN/infinite :ele counts as missing in grade segments instead of raising a misleading grade error. - track_splits skips the GAP bookkeeping, restoring its original cost. - FRIEND citation: 7,783 tests on adults free of known cardiovascular disease, not apparently healthy adults; document truncated float ages, the horizontal-distance assumption, and fix README order and the CHANGELOG signature.
Owner
Author
|
Superseded by #97 (released as 2.0.0). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase F4 of the physiology review. Purely additive: no version bump (CHANGELOG under
[Unreleased]), and the existing methods return the same values as before.baseline.rboutput is identical tobaseline_1.18.1.txt.D. Grade-adjusted pace (Minetti et al., 2002)
Cr(i) = 155.4·i⁵ − 30.4·i⁴ − 43.3·i³ + 46.3·i² + 19.5·i + 3.6(J·kg⁻¹·m⁻¹, R² = 0.999), checked against the paper (Fig. 1 legend; treadmill range −0.45 to +0.45; measured level cost 3.40 ± 0.24).grade_adjustment_factor(grade): Cr(i)/Cr(0). The grade is a fraction, clamped to ±0.45 (no extrapolation). Non-finite or non-numeric grades raiseArgumentError.grade_adjusted_pace(pace, grade, unit: :km): pace / factor in s/unit.unit:is validated only, because the factor is per distance and doesn't depend on the unit.grade_adjusted_pace_clock(pace, grade, unit: :km, compact: false).track_grade_adjusted_splits(points, split_km = 1.0, compact: false): the same:km/:elapsed/:paceastrack_splits(same boundaries), plus:gap. Each split's distance is weighted by the grade factor of the segments it covers. Noise handling: each grade is measured over a segment of at least 100 m of horizontal distance. A short leftover at the end of a stretch merges into the segment before it. If there is no full segment before it to merge into (it sits between missing fixes, or the whole track is that short), it counts as flat. A missing or NaN/infinite:elealso counts as flat and closes the current segment. Distances are horizontal, with no √(1+i²) slope correction (0.5% at 10%).estimate_detailed_vo2maxkeeps its heuristic; its docstring now points to GAP.E. VO2max norms by age and sex
Source: Kaminsky LA, Arena R, Myers J. Mayo Clin Proc 2015;90(11):1515–1523, Table 3, rows "Men/Women from FRIEND". VO2max was measured on a treadmill (CPX) in 7,783 tests on adults free of known CVD; DOI 10.1016/j.mayocp.2015.07.026, PMC4919021. The data is stored in
lib/calcpace/data/friend_2015_vo2max_percentiles.yml.The same Table 3 also has the Cooper Clinic norms from ACSM GETP 9th ed. (2014, pp. 88–93). I didn't use them for two reasons:
they are predicted from Balke treadmill time, not measured;
their spread is much narrower (the 95th percentile for men 20–29 is 55.5, against 66.3 measured).
vo2max_label(value, age: nil, sex: nil): without age/sex the result is exactly what it was before. With both, it uses the same six labels on published percentile cuts: ≥95 Elite, ≥90 Excellent, ≥75 Very Good, ≥50 Good, ≥25 Fair, else Beginner. Passing only one of the two raisesArgumentError.vo2max_percentile(value, age:, sex:): linear interpolation between the published percentiles, bounded to 5–95 and rounded to 1 decimal. Age bands are the published decades with no blending between them. Ages 18–19 use the 20–29 row, 80+ use the 70–79 row, and under 18 raises. Age and sex are validated with AgeGrading's validators.The new code lives in its own module (
Vo2maxNorms) to stay under the ModuleLength limit.Review
An adversarial review agent found no blockers. It did a byte-level comparison of
track_splits,track_distance,elevation_gain,vo2max_labelandestimate_detailed_vo2maxagainst main on 3000 random tracks, and the output was identical. It also checked the GAP apportionment against an exact reference and the YAML against PMC4919021. Its should-fix items are addressed in fd54e47::eleis handled;track_splitsspeed is back (it was 48% slower; now about 5%);Uncertainties