calcpace 1.9.10 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -1
- data/README.md +31 -3
- data/calcpace.gemspec +5 -4
- data/lib/calcpace/training_zones.rb +123 -0
- data/lib/calcpace/version.rb +1 -1
- data/lib/calcpace.rb +2 -0
- metadata +9 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d89d16ea156af81fa6cd59976eebf680d435b38b41a65fcd46ee79ec2ea5fe6
|
|
4
|
+
data.tar.gz: b79d25c3f1da091543d1f1a930350bdd846ff15369f7e8476303e99aeccbcc79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1582ebc32a95e1e7702881c0efc16da68768a6616382bb1fb8102e83c9be665ac3f8eb3524f1581f0ec82db1d05e1db2eedf2217b822f959a2dc62711e6b8aa5
|
|
7
|
+
data.tar.gz: 9b95fd1feacc28b87a6477788a76bf141651cfa0ee254d4923ccc44cb9f6af63feb666046835c01d3ff5ddd65fb1632ef5d7825b540933ea3295f1ae406f55d3
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.10.0] - 2026-07-11
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Training Zones module (`TrainingZones`)
|
|
14
|
+
- `training_paces(vo2max)`: personalized Easy/Marathon/Threshold/Interval/Repetition
|
|
15
|
+
pace bands per km, inverting the Daniels & Gilbert velocity equation
|
|
16
|
+
- `training_paces_from_race(distance_km, time)`: pace bands straight from a race result
|
|
17
|
+
- `hr_zones(hr_max:, hr_rest:)`: five Karvonen (Heart Rate Reserve) heart-rate zones
|
|
18
|
+
- Structured results (`PaceBand`, `HrZone`) with seconds and clock formats
|
|
19
|
+
|
|
10
20
|
## [1.9.10] - 2026-07-09
|
|
11
21
|
|
|
12
22
|
### Changed
|
|
@@ -205,7 +215,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
205
215
|
|
|
206
216
|
See git history for changes in earlier versions.
|
|
207
217
|
|
|
208
|
-
[Unreleased]: https://github.com/0jonjo/calcpace/compare/v1.
|
|
218
|
+
[Unreleased]: https://github.com/0jonjo/calcpace/compare/v1.10.0...HEAD
|
|
219
|
+
[1.10.0]: https://github.com/0jonjo/calcpace/compare/v1.9.10...v1.10.0
|
|
209
220
|
[1.9.6]: https://github.com/0jonjo/calcpace/compare/v1.9.5...v1.9.6
|
|
210
221
|
[1.9.5]: https://github.com/0jonjo/calcpace/compare/v1.9.4...v1.9.5
|
|
211
222
|
[1.6.0]: https://github.com/0jonjo/calcpace/releases/tag/v1.6.0
|
data/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Calcpace [](https://badge.fury.io/rb/calcpace)
|
|
2
2
|
|
|
3
|
-
A Ruby gem for
|
|
3
|
+
A Ruby gem for runners: pace, time, and distance calculations, unit conversions, race predictions, GPS track analysis, age grading, VO2max estimation, and training zones.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```ruby
|
|
8
|
-
gem 'calcpace', '~> 1.
|
|
8
|
+
gem 'calcpace', '~> 1.10.0'
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
@@ -276,6 +276,34 @@ easy.value # => 29.3 (underestimates real aerobic capacity)
|
|
|
276
276
|
|
|
277
277
|
---
|
|
278
278
|
|
|
279
|
+
### Training Zones
|
|
280
|
+
|
|
281
|
+
Personalized training paces (Daniels' Running Formula) and Karvonen heart-rate zones:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
zones = calc.training_paces(50.0)
|
|
285
|
+
zones[:threshold].fast_clock # => "00:04:15" per km
|
|
286
|
+
zones[:easy].slow_clock # => "00:05:52" per km
|
|
287
|
+
|
|
288
|
+
calc.training_paces_from_race(10.0, '00:40:00') # from a recent race result
|
|
289
|
+
|
|
290
|
+
calc.hr_zones(hr_max: 190, hr_rest: 55)
|
|
291
|
+
# => [#<struct zone=1, min_bpm=123, max_bpm=136>, ... zone=5, max_bpm=190]
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
| Zone | %VO2max | Purpose |
|
|
295
|
+
|------|---------|---------|
|
|
296
|
+
| Easy | 59–74% | Base building, recovery |
|
|
297
|
+
| Marathon | 75–84% | Marathon race pace |
|
|
298
|
+
| Threshold | 83–88% | Lactate threshold, tempo runs |
|
|
299
|
+
| Interval | 95–100% | VO2max development |
|
|
300
|
+
| Repetition | 105–110% | Speed and running economy |
|
|
301
|
+
|
|
302
|
+
Pace accuracy vs published VDOT tables: within a few seconds per km
|
|
303
|
+
(threshold matches exactly; easy band is a range heuristic).
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
279
307
|
### Other Utilities
|
|
280
308
|
|
|
281
309
|
```ruby
|
data/calcpace.gemspec
CHANGED
|
@@ -8,11 +8,12 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ['João Gilberto Saraiva']
|
|
9
9
|
spec.email = ['joaogilberto@tuta.io']
|
|
10
10
|
|
|
11
|
-
spec.summary = '
|
|
12
|
-
spec.description = 'Ruby gem for
|
|
13
|
-
'unit conversions (30+ units), race predictions (Riegel & Cameron), ' \
|
|
11
|
+
spec.summary = 'Running calculations: pace, race predictions, GPS track analysis, VO2max, and training zones.'
|
|
12
|
+
spec.description = 'Ruby gem for runners: pace, time, and distance calculations, ' \
|
|
13
|
+
'unit conversions (30+ units), race time predictions (Riegel & Cameron), ' \
|
|
14
14
|
'GPS track analysis (Haversine, elevation gain, per-km splits), ' \
|
|
15
|
-
'
|
|
15
|
+
'age grading (WMA 2023), VO2max estimation (Daniels & Gilbert), and ' \
|
|
16
|
+
'personalized training zones (Daniels paces & Karvonen heart-rate zones).'
|
|
16
17
|
spec.homepage = 'https://github.com/0jonjo/calcpace'
|
|
17
18
|
spec.metadata['source_code_uri'] = spec.homepage
|
|
18
19
|
spec.license = 'MIT'
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Module for deriving personalized training zones from VO2max
|
|
4
|
+
#
|
|
5
|
+
# Training paces invert the Daniels & Gilbert (1979) velocity equation:
|
|
6
|
+
# VO2 = -4.60 + 0.182258 * v + 0.000104 * v² (v in m/min)
|
|
7
|
+
# Solving the quadratic for v at a target %VO2max gives the running
|
|
8
|
+
# velocity for each training intensity (Daniels' Running Formula).
|
|
9
|
+
#
|
|
10
|
+
# Heart rate zones use the Karvonen method (Heart Rate Reserve):
|
|
11
|
+
# target = hr_rest + pct * (hr_max - hr_rest)
|
|
12
|
+
module TrainingZones
|
|
13
|
+
# Training intensities as fraction of VO2max (Daniels' Running Formula)
|
|
14
|
+
TRAINING_INTENSITIES = {
|
|
15
|
+
easy: { low: 0.59, high: 0.74 },
|
|
16
|
+
marathon: { low: 0.75, high: 0.84 },
|
|
17
|
+
threshold: { low: 0.83, high: 0.88 },
|
|
18
|
+
interval: { low: 0.95, high: 1.00 },
|
|
19
|
+
repetition: { low: 1.05, high: 1.10 }
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
# A pace band for one training zone (paces per kilometre).
|
|
23
|
+
# slow = lower-intensity end of the band, fast = higher-intensity end.
|
|
24
|
+
PaceBand = Struct.new(:slow_seconds, :fast_seconds, :slow_clock, :fast_clock)
|
|
25
|
+
|
|
26
|
+
# Heart-rate zone boundaries as fractions of Heart Rate Reserve (Karvonen)
|
|
27
|
+
HR_ZONE_BOUNDARIES = [0.50, 0.60, 0.70, 0.80, 0.90, 1.00].freeze
|
|
28
|
+
|
|
29
|
+
# One heart-rate training zone (1 = recovery … 5 = maximal)
|
|
30
|
+
HrZone = Struct.new(:zone, :min_bpm, :max_bpm)
|
|
31
|
+
|
|
32
|
+
# Derives training pace bands from a VO2max value
|
|
33
|
+
#
|
|
34
|
+
# @param vo2max [Numeric] VO2max in ml/kg/min (must be > 0)
|
|
35
|
+
# @return [Hash{Symbol => PaceBand}] keys: :easy, :marathon, :threshold,
|
|
36
|
+
# :interval, :repetition — paces per km
|
|
37
|
+
# @raise [Calcpace::NonPositiveInputError] if vo2max is not positive
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# calc.training_paces(50.0)[:threshold].fast_clock #=> "00:04:15"
|
|
41
|
+
def training_paces(vo2max)
|
|
42
|
+
check_positive(vo2max.to_f, 'VO2max')
|
|
43
|
+
|
|
44
|
+
TRAINING_INTENSITIES.transform_values do |band|
|
|
45
|
+
slow = pace_seconds_at_pct(vo2max.to_f, band[:low])
|
|
46
|
+
fast = pace_seconds_at_pct(vo2max.to_f, band[:high])
|
|
47
|
+
|
|
48
|
+
PaceBand.new(
|
|
49
|
+
slow_seconds: slow,
|
|
50
|
+
fast_seconds: fast,
|
|
51
|
+
slow_clock: convert_to_clocktime(slow),
|
|
52
|
+
fast_clock: convert_to_clocktime(fast)
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Derives training pace bands from a recent race result
|
|
58
|
+
#
|
|
59
|
+
# Convenience wrapper: estimates VO2max via Daniels & Gilbert
|
|
60
|
+
# (see Vo2maxEstimator#estimate_vo2max) and derives the bands from it.
|
|
61
|
+
#
|
|
62
|
+
# @param distance_km [Numeric] race distance in kilometres (must be > 0)
|
|
63
|
+
# @param time [String, Integer] finish time as "HH:MM:SS" / "MM:SS" or total seconds
|
|
64
|
+
# @return [Hash{Symbol => PaceBand}] same shape as #training_paces
|
|
65
|
+
# @raise [Calcpace::NonPositiveInputError] if distance or time are not positive
|
|
66
|
+
# @raise [Calcpace::InvalidTimeFormatError] if time string is malformed
|
|
67
|
+
#
|
|
68
|
+
# @example
|
|
69
|
+
# calc.training_paces_from_race(10.0, '00:40:00')[:easy].slow_clock #=> "00:05:42"
|
|
70
|
+
def training_paces_from_race(distance_km, time)
|
|
71
|
+
training_paces(estimate_vo2max(distance_km, time))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Computes the five Karvonen heart-rate training zones
|
|
75
|
+
#
|
|
76
|
+
# target_bpm = hr_rest + pct * (hr_max - hr_rest)
|
|
77
|
+
#
|
|
78
|
+
# @param hr_max [Numeric] maximum heart rate in bpm (must be > 0)
|
|
79
|
+
# @param hr_rest [Numeric] resting heart rate in bpm (must be > 0 and < hr_max)
|
|
80
|
+
# @return [Array<HrZone>] five contiguous zones from Z1 (50–60% HRR) to Z5 (90–100% HRR)
|
|
81
|
+
# @raise [Calcpace::NonPositiveInputError] if any rate is not positive
|
|
82
|
+
# @raise [Calcpace::Error] if hr_rest >= hr_max
|
|
83
|
+
#
|
|
84
|
+
# @example
|
|
85
|
+
# calc.hr_zones(hr_max: 190, hr_rest: 55).last.max_bpm #=> 190
|
|
86
|
+
def hr_zones(hr_max:, hr_rest:)
|
|
87
|
+
max = hr_max.to_f
|
|
88
|
+
rest = hr_rest.to_f
|
|
89
|
+
check_heart_rates(max, rest)
|
|
90
|
+
|
|
91
|
+
reserve = max - rest
|
|
92
|
+
points = HR_ZONE_BOUNDARIES.map { |pct| (rest + (pct * reserve)).round }
|
|
93
|
+
|
|
94
|
+
points.each_cons(2).with_index(1).map do |(min_bpm, max_bpm), zone|
|
|
95
|
+
HrZone.new(zone: zone, min_bpm: min_bpm, max_bpm: max_bpm)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def check_heart_rates(hr_max, hr_rest)
|
|
102
|
+
check_positive(hr_max, 'Maximum heart rate')
|
|
103
|
+
check_positive(hr_rest, 'Resting heart rate')
|
|
104
|
+
return if hr_rest < hr_max
|
|
105
|
+
|
|
106
|
+
raise Calcpace::Error,
|
|
107
|
+
"Resting heart rate (#{hr_rest}) must be lower than maximum heart rate (#{hr_max})"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Inverts Daniels & Gilbert: velocity (m/min) that demands a given VO2
|
|
111
|
+
def velocity_at_vo2(vo2)
|
|
112
|
+
a = 0.000104
|
|
113
|
+
b = 0.182258
|
|
114
|
+
c = -(4.60 + vo2)
|
|
115
|
+
|
|
116
|
+
(-b + Math.sqrt((b**2) - (4 * a * c))) / (2 * a)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def pace_seconds_at_pct(vo2max, pct)
|
|
120
|
+
velocity = velocity_at_vo2(vo2max * pct)
|
|
121
|
+
(60_000.0 / velocity).round
|
|
122
|
+
end
|
|
123
|
+
end
|
data/lib/calcpace/version.rb
CHANGED
data/lib/calcpace.rb
CHANGED
|
@@ -13,6 +13,7 @@ require_relative 'calcpace/pace_converter'
|
|
|
13
13
|
require_relative 'calcpace/race_predictor'
|
|
14
14
|
require_relative 'calcpace/race_splits'
|
|
15
15
|
require_relative 'calcpace/track_calculator'
|
|
16
|
+
require_relative 'calcpace/training_zones'
|
|
16
17
|
require_relative 'calcpace/vo2max_estimator'
|
|
17
18
|
|
|
18
19
|
# Calcpace - A Ruby gem for pace, distance, and time calculations
|
|
@@ -49,6 +50,7 @@ class Calcpace
|
|
|
49
50
|
include RacePredictor
|
|
50
51
|
include RaceSplits
|
|
51
52
|
include TrackCalculator
|
|
53
|
+
include TrainingZones
|
|
52
54
|
include Vo2maxEstimator
|
|
53
55
|
|
|
54
56
|
# Creates a new Calcpace instance
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: calcpace
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- João Gilberto Saraiva
|
|
@@ -9,9 +9,11 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
|
-
description: 'Ruby gem for
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
description: 'Ruby gem for runners: pace, time, and distance calculations, unit conversions
|
|
13
|
+
(30+ units), race time predictions (Riegel & Cameron), GPS track analysis (Haversine,
|
|
14
|
+
elevation gain, per-km splits), age grading (WMA 2023), VO2max estimation (Daniels
|
|
15
|
+
& Gilbert), and personalized training zones (Daniels paces & Karvonen heart-rate
|
|
16
|
+
zones).'
|
|
15
17
|
email:
|
|
16
18
|
- joaogilberto@tuta.io
|
|
17
19
|
executables: []
|
|
@@ -47,6 +49,7 @@ files:
|
|
|
47
49
|
- lib/calcpace/race_predictor.rb
|
|
48
50
|
- lib/calcpace/race_splits.rb
|
|
49
51
|
- lib/calcpace/track_calculator.rb
|
|
52
|
+
- lib/calcpace/training_zones.rb
|
|
50
53
|
- lib/calcpace/version.rb
|
|
51
54
|
- lib/calcpace/vo2max_estimator.rb
|
|
52
55
|
homepage: https://github.com/0jonjo/calcpace
|
|
@@ -72,6 +75,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
72
75
|
requirements: []
|
|
73
76
|
rubygems_version: 4.0.10
|
|
74
77
|
specification_version: 4
|
|
75
|
-
summary:
|
|
76
|
-
|
|
78
|
+
summary: 'Running calculations: pace, race predictions, GPS track analysis, VO2max,
|
|
79
|
+
and training zones.'
|
|
77
80
|
test_files: []
|