calcpace 1.9.4 → 1.9.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad0a05f7bb09cab7b54e4c01ec1fd36c274d87369d6991893372845765e8b525
4
- data.tar.gz: b51863dbd58e508ba47bcdd8cf9f60f52c43dd78099e39591021753aeffac01e
3
+ metadata.gz: db7effb21592146022bf3df34ecdb8a8688fb4d79d618c43c5b99a9912ac967a
4
+ data.tar.gz: 2e009f5c0fa7856f65c3f8a447fa9a4de2e51e5057bf1606810d3bc38e3d9a02
5
5
  SHA512:
6
- metadata.gz: e1e69f141956c85101541603d622ca3a667f1d8cf0dbe4d71f7410d4beaa6404f809125754854087ee737d0651029185493ce9b09f755f559b99aa53bd3068cd
7
- data.tar.gz: c9218b303fb0c9288c904500998a86d81f48dc388b05c62a42c52f93829c03f3d43f14f7b60b88bd9b7046b41b0089100ca7f9e748a37f2021e29cfd5e464da3
6
+ metadata.gz: 129467189d43ea17a4ac31f371e813b08a12d7d0482a3f4717414087db3ffa0f5134478e5134cb512c5c6f242f7eab57b2172bf40b61107ed7a204f2043bf78e
7
+ data.tar.gz: c716d04864bdf92ab64259c938a05d1cce552a9c35738f1b7b76848b4c32d36119ed71c9ddcd8c0725b4002c660eee8cb51e5a020fdf9ac7d29d062ea3801002
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [1.9.5] - 2026-05-02
11
+
12
+ ### Added
13
+ - Age-grading module (`AgeGrading`) with:
14
+ - `age_grade(distance_km, time, age:, sex:)`
15
+ - `age_grade_percent(distance_km, time, age:, sex:)`
16
+ - `age_grade_label(percent)`
17
+ - Versioned data file loader using YAML + `YAML.safe_load` from
18
+ `lib/calcpace/data/wma_2023_road.yml`
19
+ - Interpolation support for in-between ages (e.g., 57 between 55 and 60)
20
+ - Initial road-race support: 5K, 10K, half marathon, marathon
21
+ - WMA 2023 one-year age factors integrated for `M`/`F` road distances in meters
22
+ - Source: World Masters Athletics (WMA) competition rules documents
23
+ https://world-masters-athletics.org/documents/competition-rules/
24
+ - Race-style age-factored time rounding up to the next hundredth
25
+ - Test suite for validation, interpolation, and error handling
26
+
8
27
  ## [1.9.4] - 2026-04-18
9
28
 
10
29
  ### Added
@@ -145,5 +164,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
145
164
 
146
165
  See git history for changes in earlier versions.
147
166
 
148
- [Unreleased]: https://github.com/0jonjo/calcpace/compare/v1.6.0...HEAD
167
+ [Unreleased]: https://github.com/0jonjo/calcpace/compare/v1.9.5...HEAD
168
+ [1.9.5]: https://github.com/0jonjo/calcpace/compare/v1.9.4...v1.9.5
149
169
  [1.6.0]: https://github.com/0jonjo/calcpace/releases/tag/v1.6.0
data/Gemfile.lock CHANGED
@@ -7,7 +7,7 @@ GEM
7
7
  bigdecimal (4.1.0)
8
8
  date (3.5.1)
9
9
  docile (1.4.1)
10
- erb (6.0.2)
10
+ erb (6.0.4)
11
11
  json (2.19.2)
12
12
  json-schema (6.2.0)
13
13
  addressable (~> 2.8)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Calcpace [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&r=r&ts=1683906897&type=6e&v=1.9.3&x2=0)](https://badge.fury.io/rb/calcpace)
1
+ # Calcpace [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&r=r&ts=1683906897&type=6e&v=1.9.5&x2=0)](https://badge.fury.io/rb/calcpace)
2
2
 
3
3
  A Ruby gem for running and cycling calculations: pace, time, distance, unit conversions, race predictions, GPS track analysis, and VO2max estimation.
4
4
 
@@ -124,6 +124,44 @@ calc.track_splits(points, 1.0) # => [{ km: 1, elapsed: 312, pace: "05:12" }, ...
124
124
 
125
125
  ---
126
126
 
127
+ ### Age Grading (Road Races)
128
+
129
+ Age grading compares race results across different ages and sexes by using
130
+ age factors and open standards.
131
+
132
+ ```ruby
133
+ result = calc.age_grade(10.0, '00:45:00', age: 55, sex: :male)
134
+ # => {
135
+ # age_grade_percent: 64.6,
136
+ # category: "Local Class",
137
+ # age_graded_time_seconds: 2376.0,
138
+ # age_graded_time_clock: "00:39:36",
139
+ # open_standard_seconds: 1571.0,
140
+ # open_standard_clock: "00:26:11",
141
+ # factor: 0.88,
142
+ # table_version: "WMA_2023_ONE_YEAR_FACTORS_V1"
143
+ # }
144
+
145
+ calc.age_grade_percent(5.0, '00:22:30', age: 40, sex: :female) # => 74.1
146
+ calc.age_grade_label(74.1) # => "Regional Class"
147
+ ```
148
+
149
+ Supported distances: 5K, 10K, half marathon, marathon.
150
+
151
+ Age factors are based on WMA 2023 one-year age grading tables:
152
+ https://world-masters-athletics.org/documents/competition-rules/
153
+
154
+ Open standards used in `open_standard_seconds` / `open_standard_clock` are loaded
155
+ from the bundled WMA 2023 open standards dataset
156
+ (`lib/calcpace/data/wma_2023_open_standards.yml`).
157
+
158
+ Field meanings:
159
+ - `age_graded_time_clock`: your result after applying the WMA age factor (normalized performance time).
160
+ - `open_standard_clock`: the open standard reference time used to compute the percentage for that distance/sex.
161
+ - `age_grade_percent`: `(open_standard_seconds / age_graded_time_seconds) * 100`.
162
+
163
+ ---
164
+
127
165
  ### VO2max Estimation
128
166
 
129
167
  Estimate aerobic fitness from a race result using the **Daniels & Gilbert formula** (1979):
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ # Module for age-grading race performances with a versioned table
6
+ #
7
+ # Age grading allows fairer comparison across ages by applying an age factor
8
+ # to the raw performance time.
9
+ #
10
+ # Current scope:
11
+ # - Common road distances: 5K, 10K, half marathon, marathon
12
+ # - Sex: male/female
13
+ # - Age: 18+
14
+ # - Data file is versioned and replaceable (`lib/calcpace/data/wma_2023_road.yml`)
15
+ #
16
+ # Returned values include:
17
+ # - age grade percentage
18
+ # - age-graded time
19
+ # - open standard time for the selected distance/sex
20
+ # - performance category
21
+ # rubocop:disable Metrics/ModuleLength
22
+ module AgeGrading
23
+ DATA_PATH = File.expand_path('data/wma_2023_road.yml', __dir__).freeze
24
+ OPEN_STANDARDS_DATA_PATH = File.expand_path('data/wma_2023_open_standards.yml', __dir__).freeze
25
+ WMA_DATA = YAML.safe_load_file(DATA_PATH, permitted_classes: [],
26
+ aliases: false).freeze
27
+ OPEN_STANDARDS_DATA = YAML.safe_load_file(OPEN_STANDARDS_DATA_PATH, permitted_classes: [],
28
+ aliases: false).freeze
29
+ TABLE_VERSION = OPEN_STANDARDS_DATA.fetch('meta').fetch('table_version').freeze
30
+
31
+ AGE_GRADE_LABELS = OPEN_STANDARDS_DATA.fetch('age_grade_classifications').map do |entry|
32
+ { min: entry.fetch('min').to_f, label: entry.fetch('label') }
33
+ end.freeze
34
+
35
+ DISTANCE_TO_METERS = {
36
+ 5.0 => '5000',
37
+ 10.0 => '10000',
38
+ 21.0975 => '21097',
39
+ 42.195 => '42195'
40
+ }.freeze
41
+ RACE_TO_METERS = {
42
+ '5k' => '5000',
43
+ '10k' => '10000',
44
+ 'half_marathon' => '21097',
45
+ 'marathon' => '42195'
46
+ }.freeze
47
+
48
+ SUPPORTED_DISTANCES_KM = DISTANCE_TO_METERS.keys.freeze
49
+
50
+ # Returns a full age-grading report for a race performance
51
+ #
52
+ # @param distance_km [Numeric, String, Symbol] race distance in kilometres
53
+ # (5.0, 10.0, 21.0975, 42.195) or race key (:5k, :10k, :half_marathon, :marathon)
54
+ # @param time [String, Numeric] performance time as HH:MM:SS / MM:SS, or total seconds
55
+ # @param age [Integer] athlete age (must be >= 18)
56
+ # @param sex [String, Symbol] male or female
57
+ # @return [Hash] age-grading result details
58
+ def age_grade(distance_km, time, age:, sex:)
59
+ distance_m = normalize_distance(distance_km)
60
+ seconds = parse_time_seconds(time)
61
+ age_value = normalize_age(age)
62
+ sex_value = normalize_sex(sex)
63
+
64
+ check_positive(seconds, 'Time')
65
+
66
+ factor = interpolated_factor(sex_value, age_value, distance_m)
67
+ age_graded_time = round_up_hundredth(seconds * factor)
68
+ open_standard = open_standard_seconds(sex_value, distance_m)
69
+ grade_percent = (open_standard / age_graded_time) * 100.0
70
+ rounded_percent = grade_percent.round(1)
71
+
72
+ {
73
+ age_grade_percent: rounded_percent,
74
+ category: age_grade_label(rounded_percent),
75
+ age_graded_time_seconds: age_graded_time,
76
+ age_graded_time_clock: convert_to_clocktime(age_graded_time),
77
+ open_standard_seconds: open_standard,
78
+ open_standard_clock: convert_to_clocktime(open_standard),
79
+ factor: factor.round(4),
80
+ table_version: TABLE_VERSION
81
+ }
82
+ end
83
+
84
+ # Returns only the age-grade percentage
85
+ #
86
+ # @param distance_km [Numeric] race distance in kilometres
87
+ # @param time [String, Numeric] performance time
88
+ # @param age [Integer] athlete age
89
+ # @param sex [String, Symbol] male or female
90
+ # @return [Float] age-grade percentage
91
+ def age_grade_percent(distance_km, time, age:, sex:)
92
+ age_grade(distance_km, time, age: age, sex: sex)[:age_grade_percent]
93
+ end
94
+
95
+ # Returns a descriptive label for an age-grade percentage
96
+ #
97
+ # @param percent [Numeric] age-grade percentage
98
+ # @return [String] category label
99
+ def age_grade_label(percent)
100
+ percent_value = begin
101
+ Float(percent)
102
+ rescue ArgumentError, TypeError
103
+ raise ArgumentError, 'Age-grade percent must be a numeric value greater than or equal to 0'
104
+ end
105
+
106
+ raise ArgumentError, 'Age-grade percent must be greater than or equal to 0' if percent_value.negative?
107
+
108
+ AGE_GRADE_LABELS.find { |entry| percent_value >= entry[:min] }[:label]
109
+ end
110
+
111
+ private
112
+
113
+ def normalize_distance(distance_km)
114
+ if distance_km.is_a?(String) || distance_km.is_a?(Symbol)
115
+ key = distance_km.to_s.strip.downcase
116
+ return RACE_TO_METERS.fetch(key) if RACE_TO_METERS.key?(key)
117
+
118
+ raise ArgumentError,
119
+ "Unsupported race '#{distance_km}'. Supported: #{RACE_TO_METERS.keys.join(', ')}"
120
+ end
121
+
122
+ distance = distance_km.to_f
123
+ check_positive(distance, 'Distance')
124
+
125
+ match = SUPPORTED_DISTANCES_KM.find { |value| (distance - value).abs <= 0.001 }
126
+ return DISTANCE_TO_METERS.fetch(match) if match
127
+
128
+ raise ArgumentError,
129
+ "Unsupported distance #{distance_km}km. Supported: #{SUPPORTED_DISTANCES_KM.join(', ')}"
130
+ end
131
+
132
+ def parse_time_seconds(time)
133
+ return time.to_f if time.is_a?(Numeric)
134
+
135
+ check_time(time.to_s)
136
+ convert_to_seconds(time.to_s)
137
+ end
138
+
139
+ def normalize_age(age)
140
+ age_value = Integer(age)
141
+ rescue ArgumentError, TypeError
142
+ raise ArgumentError, 'Age must be an integer greater than or equal to 18'
143
+ else
144
+ raise ArgumentError, 'Age must be at least 18' if age_value < 18
145
+
146
+ age_value
147
+ end
148
+
149
+ def normalize_sex(sex)
150
+ normalized = sex.to_s.strip.downcase.to_sym
151
+ return normalized if %i[male female].include?(normalized)
152
+
153
+ raise ArgumentError, "Sex must be 'male' or 'female'"
154
+ end
155
+
156
+ def interpolated_factor(sex, age, distance_m)
157
+ table = factor_table(sex, distance_m)
158
+ ages = table.keys.map(&:to_i).sort
159
+
160
+ return table.fetch(ages.first).to_f if age <= ages.first
161
+ return table.fetch(ages.last).to_f if age >= ages.last
162
+
163
+ lower_age, upper_age = neighboring_ages(ages, age)
164
+ return table.fetch(lower_age).to_f if lower_age == upper_age
165
+
166
+ interpolated_value(table, lower_age, upper_age, age)
167
+ end
168
+
169
+ def factor_table(sex, distance_m)
170
+ WMA_DATA.fetch(sex_key(sex)).fetch(distance_m)
171
+ end
172
+
173
+ def sex_key(sex)
174
+ sex == :male ? 'M' : 'F'
175
+ end
176
+
177
+ def open_standard_seconds(sex, distance_m)
178
+ OPEN_STANDARDS_DATA.fetch('open_standards_seconds').fetch(sex_key(sex)).fetch(distance_m).to_f
179
+ end
180
+
181
+ def round_up_hundredth(value)
182
+ (value * 100.0).ceil / 100.0
183
+ end
184
+
185
+ def neighboring_ages(ages, age)
186
+ lower_age = ages.select { |value| value <= age }.max
187
+ upper_age = ages.select { |value| value >= age }.min
188
+ [lower_age, upper_age]
189
+ end
190
+
191
+ def interpolated_value(table, lower_age, upper_age, age)
192
+ lower_factor = table.fetch(lower_age).to_f
193
+ upper_factor = table.fetch(upper_age).to_f
194
+ ratio = (age - lower_age).to_f / (upper_age - lower_age)
195
+ lower_factor + ((upper_factor - lower_factor) * ratio)
196
+ end
197
+ end
198
+ # rubocop:enable Metrics/ModuleLength
@@ -0,0 +1,30 @@
1
+ meta:
2
+ source: "WMA/Masters Rankings age grading tables (2023)"
3
+ url: "https://howardgrubb.co.uk/athletics/wmatnf23.html"
4
+ table_version: "WMA_2023_ONE_YEAR_FACTORS_V1"
5
+
6
+ age_grade_classifications:
7
+ - min: 100.0
8
+ label: "Approximate World Record Level"
9
+ - min: 90.0
10
+ label: "World Class"
11
+ - min: 80.0
12
+ label: "National Class"
13
+ - min: 70.0
14
+ label: "Regional Class"
15
+ - min: 60.0
16
+ label: "Local Class"
17
+ - min: 0.0
18
+ label: "Developing"
19
+
20
+ open_standards_seconds:
21
+ M:
22
+ "5000": 755.0
23
+ "10000": 1571.0
24
+ "21097": 3451.0
25
+ "42195": 7269.0
26
+ F:
27
+ "5000": 846.0
28
+ "10000": 1741.0
29
+ "21097": 3772.0
30
+ "42195": 8044.0
@@ -0,0 +1,16 @@
1
+
2
+ F:
3
+ "1500": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 0.9960, 34: 0.9886, 35: 0.9812, 36: 0.9738, 37: 0.9664, 38: 0.9590, 39: 0.9515, 40: 0.9441, 41: 0.9367, 42: 0.9293, 43: 0.9218, 44: 0.9144, 45: 0.9069, 46: 0.8995, 47: 0.8921, 48: 0.8846, 49: 0.8772, 50: 0.8697, 51: 0.8623, 52: 0.8548, 53: 0.8473, 54: 0.8399, 55: 0.8324, 56: 0.8249, 57: 0.8175, 58: 0.8100, 59: 0.8025, 60: 0.7951, 61: 0.7876, 62: 0.7801, 63: 0.7726, 64: 0.7651, 65: 0.7576, 66: 0.7501, 67: 0.7427, 68: 0.7352, 69: 0.7277, 70: 0.7202, 71: 0.7127, 72: 0.7052, 73: 0.6976, 74: 0.6897, 75: 0.6812, 76: 0.6722, 77: 0.6628, 78: 0.6529, 79: 0.6425, 80: 0.6316, 81: 0.6202, 82: 0.6083, 83: 0.5960, 84: 0.5832, 85: 0.5698, 86: 0.5561, 87: 0.5418, 88: 0.5270, 89: 0.5118, 90: 0.4960, 91: 0.4798, 92: 0.4631, 93: 0.4460, 94: 0.4283, 95: 0.4102, 96: 0.3915, 97: 0.3724, 98: 0.3528, 99: 0.3327, 100: 0.3122, 101: 0.2911, 102: 0.2696, 103: 0.2476, 104: 0.2251, 105: 0.2021, 106: 0.1787, 107: 0.1547, 108: 0.1303, 109: 0.1054, 110: 0.0800 }
4
+ "3000": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 1.0000, 34: 1.0000, 35: 1.0000, 36: 1.0000, 37: 1.0000, 38: 0.9930, 39: 0.9849, 40: 0.9767, 41: 0.9685, 42: 0.9603, 43: 0.9521, 44: 0.9438, 45: 0.9355, 46: 0.9271, 47: 0.9188, 48: 0.9104, 49: 0.9019, 50: 0.8935, 51: 0.8850, 52: 0.8765, 53: 0.8680, 54: 0.8594, 55: 0.8509, 56: 0.8423, 57: 0.8336, 58: 0.8250, 59: 0.8163, 60: 0.8076, 61: 0.7989, 62: 0.7901, 63: 0.7813, 64: 0.7725, 65: 0.7637, 66: 0.7549, 67: 0.7460, 68: 0.7371, 69: 0.7282, 70: 0.7193, 71: 0.7103, 72: 0.7013, 73: 0.6923, 74: 0.6833, 75: 0.6743, 76: 0.6652, 77: 0.6561, 78: 0.6470, 79: 0.6379, 80: 0.6288, 81: 0.6196, 82: 0.6098, 83: 0.5993, 84: 0.5881, 85: 0.5762, 86: 0.5637, 87: 0.5505, 88: 0.5366, 89: 0.5220, 90: 0.5067, 91: 0.4908, 92: 0.4742, 93: 0.4569, 94: 0.4390, 95: 0.4204, 96: 0.4010, 97: 0.3811, 98: 0.3604, 99: 0.3391, 100: 0.3171, 101: 0.2944, 102: 0.2711, 103: 0.2470, 104: 0.2223, 105: 0.1970, 106: 0.1709, 107: 0.1442, 108: 0.1168, 109: 0.0887, 110: 0.0600 }
5
+ "5000": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 1.0000, 34: 1.0000, 35: 0.9974, 36: 0.9904, 37: 0.9833, 38: 0.9761, 39: 0.9689, 40: 0.9615, 41: 0.9541, 42: 0.9467, 43: 0.9392, 44: 0.9316, 45: 0.9239, 46: 0.9162, 47: 0.9084, 48: 0.9006, 49: 0.8926, 50: 0.8847, 51: 0.8766, 52: 0.8685, 53: 0.8603, 54: 0.8521, 55: 0.8438, 56: 0.8355, 57: 0.8271, 58: 0.8186, 59: 0.8101, 60: 0.8015, 61: 0.7929, 62: 0.7842, 63: 0.7755, 64: 0.7667, 65: 0.7578, 66: 0.7489, 67: 0.7399, 68: 0.7309, 69: 0.7219, 70: 0.7128, 71: 0.7036, 72: 0.6944, 73: 0.6851, 74: 0.6758, 75: 0.6665, 76: 0.6571, 77: 0.6476, 78: 0.6381, 79: 0.6286, 80: 0.6190, 81: 0.6094, 82: 0.5997, 83: 0.5893, 84: 0.5783, 85: 0.5665, 86: 0.5541, 87: 0.5410, 88: 0.5272, 89: 0.5127, 90: 0.4975, 91: 0.4816, 92: 0.4650, 93: 0.4478, 94: 0.4299, 95: 0.4112, 96: 0.3919, 97: 0.3719, 98: 0.3513, 99: 0.3299, 100: 0.3079, 101: 0.2852, 102: 0.2618, 103: 0.2377, 104: 0.2129, 105: 0.1875, 106: 0.1613, 107: 0.1345, 108: 0.1070, 109: 0.0788, 110: 0.0500 }
6
+ "10000": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 1.0000, 34: 0.9937, 35: 0.9869, 36: 0.9801, 37: 0.9731, 38: 0.9661, 39: 0.9591, 40: 0.9519, 41: 0.9447, 42: 0.9374, 43: 0.9301, 44: 0.9227, 45: 0.9152, 46: 0.9077, 47: 0.9001, 48: 0.8925, 49: 0.8848, 50: 0.8770, 51: 0.8692, 52: 0.8613, 53: 0.8533, 54: 0.8453, 55: 0.8373, 56: 0.8291, 57: 0.8210, 58: 0.8127, 59: 0.8045, 60: 0.7961, 61: 0.7877, 62: 0.7793, 63: 0.7708, 64: 0.7623, 65: 0.7537, 66: 0.7450, 67: 0.7363, 68: 0.7276, 69: 0.7188, 70: 0.7100, 71: 0.7011, 72: 0.6922, 73: 0.6832, 74: 0.6742, 75: 0.6651, 76: 0.6560, 77: 0.6468, 78: 0.6376, 79: 0.6284, 80: 0.6191, 81: 0.6098, 82: 0.6004, 83: 0.5903, 84: 0.5795, 85: 0.5679, 86: 0.5556, 87: 0.5425, 88: 0.5287, 89: 0.5142, 90: 0.4990, 91: 0.4830, 92: 0.4662, 93: 0.4488, 94: 0.4306, 95: 0.4116, 96: 0.3920, 97: 0.3716, 98: 0.3505, 99: 0.3286, 100: 0.3060, 101: 0.2827, 102: 0.2586, 103: 0.2339, 104: 0.2084, 105: 0.1821, 106: 0.1551, 107: 0.1275, 108: 0.0990, 109: 0.0699, 110: 0.0400 }
7
+ "21097": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 0.9935, 34: 0.9869, 35: 0.9802, 36: 0.9734, 37: 0.9666, 38: 0.9596, 39: 0.9526, 40: 0.9455, 41: 0.9384, 42: 0.9311, 43: 0.9238, 44: 0.9164, 45: 0.9090, 46: 0.9014, 47: 0.8938, 48: 0.8862, 49: 0.8784, 50: 0.8706, 51: 0.8627, 52: 0.8548, 53: 0.8468, 54: 0.8387, 55: 0.8306, 56: 0.8224, 57: 0.8141, 58: 0.8058, 59: 0.7974, 60: 0.7889, 61: 0.7804, 62: 0.7718, 63: 0.7632, 64: 0.7545, 65: 0.7457, 66: 0.7369, 67: 0.7280, 68: 0.7191, 69: 0.7101, 70: 0.7011, 71: 0.6920, 72: 0.6828, 73: 0.6736, 74: 0.6644, 75: 0.6551, 76: 0.6457, 77: 0.6363, 78: 0.6268, 79: 0.6173, 80: 0.6078, 81: 0.5982, 82: 0.5879, 83: 0.5769, 84: 0.5653, 85: 0.5530, 86: 0.5401, 87: 0.5265, 88: 0.5122, 89: 0.4972, 90: 0.4816, 91: 0.4653, 92: 0.4484, 93: 0.4307, 94: 0.4125, 95: 0.3935, 96: 0.3739, 97: 0.3536, 98: 0.3327, 99: 0.3111, 100: 0.2888, 101: 0.2659, 102: 0.2424, 103: 0.2181, 104: 0.1932, 105: 0.1677, 106: 0.1414, 107: 0.1146, 108: 0.0870, 109: 0.0588, 110: 0.0300 }
8
+ "42195": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 1.0000, 34: 1.0000, 35: 0.9982, 36: 0.9918, 37: 0.9854, 38: 0.9789, 39: 0.9722, 40: 0.9654, 41: 0.9585, 42: 0.9515, 43: 0.9444, 44: 0.9371, 45: 0.9298, 46: 0.9223, 47: 0.9148, 48: 0.9071, 49: 0.8993, 50: 0.8915, 51: 0.8835, 52: 0.8754, 53: 0.8672, 54: 0.8590, 55: 0.8506, 56: 0.8421, 57: 0.8336, 58: 0.8249, 59: 0.8162, 60: 0.8073, 61: 0.7984, 62: 0.7894, 63: 0.7803, 64: 0.7711, 65: 0.7618, 66: 0.7524, 67: 0.7430, 68: 0.7335, 69: 0.7239, 70: 0.7142, 71: 0.7044, 72: 0.6946, 73: 0.6846, 74: 0.6746, 75: 0.6646, 76: 0.6544, 77: 0.6442, 78: 0.6339, 79: 0.6235, 80: 0.6131, 81: 0.6025, 82: 0.5914, 83: 0.5795, 84: 0.5670, 85: 0.5538, 86: 0.5400, 87: 0.5254, 88: 0.5103, 89: 0.4944, 90: 0.4779, 91: 0.4608, 92: 0.4429, 93: 0.4245, 94: 0.4053, 95: 0.3855, 96: 0.3651, 97: 0.3439, 98: 0.3222, 99: 0.2997, 100: 0.2767, 101: 0.2529, 102: 0.2285, 103: 0.2035, 104: 0.1778, 105: 0.1515, 106: 0.1245, 107: 0.0968, 108: 0.0685, 109: 0.0396, 110: 0.0100 }
9
+
10
+ M:
11
+ "1500": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 0.9973, 34: 0.9911, 35: 0.9849, 36: 0.9786, 37: 0.9723, 38: 0.9660, 39: 0.9596, 40: 0.9532, 41: 0.9468, 42: 0.9403, 43: 0.9338, 44: 0.9272, 45: 0.9206, 46: 0.9140, 47: 0.9073, 48: 0.9006, 49: 0.8939, 50: 0.8871, 51: 0.8803, 52: 0.8734, 53: 0.8666, 54: 0.8596, 55: 0.8527, 56: 0.8457, 57: 0.8387, 58: 0.8316, 59: 0.8246, 60: 0.8174, 61: 0.8103, 62: 0.8031, 63: 0.7959, 64: 0.7887, 65: 0.7814, 66: 0.7741, 67: 0.7668, 68: 0.7594, 69: 0.7520, 70: 0.7446, 71: 0.7371, 72: 0.7296, 73: 0.7221, 74: 0.7146, 75: 0.7070, 76: 0.6994, 77: 0.6918, 78: 0.6835, 79: 0.6746, 80: 0.6651, 81: 0.6549, 82: 0.6440, 83: 0.6325, 84: 0.6204, 85: 0.6076, 86: 0.5942, 87: 0.5801, 88: 0.5654, 89: 0.5501, 90: 0.5341, 91: 0.5175, 92: 0.5002, 93: 0.4823, 94: 0.4638, 95: 0.4446, 96: 0.4248, 97: 0.4043, 98: 0.3832, 99: 0.3614, 100: 0.3390, 101: 0.3160, 102: 0.2923, 103: 0.2680, 104: 0.2431, 105: 0.2175, 106: 0.1913, 107: 0.1644, 108: 0.1369, 109: 0.1088, 110: 0.0800 }
12
+ "3000": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 1.0000, 34: 1.0000, 35: 0.9993, 36: 0.9922, 37: 0.9851, 38: 0.9780, 39: 0.9708, 40: 0.9636, 41: 0.9564, 42: 0.9491, 43: 0.9419, 44: 0.9345, 45: 0.9272, 46: 0.9199, 47: 0.9125, 48: 0.9051, 49: 0.8976, 50: 0.8901, 51: 0.8827, 52: 0.8751, 53: 0.8676, 54: 0.8600, 55: 0.8524, 56: 0.8448, 57: 0.8372, 58: 0.8295, 59: 0.8218, 60: 0.8141, 61: 0.8064, 62: 0.7986, 63: 0.7908, 64: 0.7830, 65: 0.7752, 66: 0.7674, 67: 0.7595, 68: 0.7516, 69: 0.7437, 70: 0.7357, 71: 0.7278, 72: 0.7198, 73: 0.7118, 74: 0.7038, 75: 0.6957, 76: 0.6877, 77: 0.6796, 78: 0.6715, 79: 0.6634, 80: 0.6552, 81: 0.6464, 82: 0.6367, 83: 0.6263, 84: 0.6151, 85: 0.6032, 86: 0.5905, 87: 0.5771, 88: 0.5629, 89: 0.5480, 90: 0.5323, 91: 0.5158, 92: 0.4986, 93: 0.4806, 94: 0.4619, 95: 0.4425, 96: 0.4222, 97: 0.4013, 98: 0.3795, 99: 0.3570, 100: 0.3338, 101: 0.3098, 102: 0.2851, 103: 0.2596, 104: 0.2333, 105: 0.2063, 106: 0.1785, 107: 0.1500, 108: 0.1208, 109: 0.0908, 110: 0.0600 }
13
+ "5000": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 1.0000, 34: 1.0000, 35: 1.0000, 36: 1.0000, 37: 0.9943, 38: 0.9863, 39: 0.9782, 40: 0.9701, 41: 0.9621, 42: 0.9540, 43: 0.9460, 44: 0.9380, 45: 0.9299, 46: 0.9219, 47: 0.9139, 48: 0.9059, 49: 0.8980, 50: 0.8900, 51: 0.8820, 52: 0.8740, 53: 0.8661, 54: 0.8582, 55: 0.8502, 56: 0.8423, 57: 0.8344, 58: 0.8265, 59: 0.8185, 60: 0.8106, 61: 0.8028, 62: 0.7949, 63: 0.7870, 64: 0.7791, 65: 0.7713, 66: 0.7634, 67: 0.7556, 68: 0.7477, 69: 0.7399, 70: 0.7321, 71: 0.7242, 72: 0.7164, 73: 0.7086, 74: 0.7008, 75: 0.6930, 76: 0.6852, 77: 0.6775, 78: 0.6697, 79: 0.6619, 80: 0.6541, 81: 0.6464, 82: 0.6367, 83: 0.6263, 84: 0.6151, 85: 0.6032, 86: 0.5905, 87: 0.5771, 88: 0.5629, 89: 0.5480, 90: 0.5323, 91: 0.5158, 92: 0.4986, 93: 0.4806, 94: 0.4619, 95: 0.4425, 96: 0.4222, 97: 0.4013, 98: 0.3795, 99: 0.3570, 100: 0.3338, 101: 0.3098, 102: 0.2851, 103: 0.2596, 104: 0.2333, 105: 0.2063, 106: 0.1785, 107: 0.1500, 108: 0.1208, 109: 0.0908, 110: 0.0600 }
14
+ "10000": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 1.0000, 34: 0.9973, 35: 0.9897, 36: 0.9822, 37: 0.9747, 38: 0.9672, 39: 0.9597, 40: 0.9523, 41: 0.9449, 42: 0.9375, 43: 0.9301, 44: 0.9228, 45: 0.9155, 46: 0.9082, 47: 0.9009, 48: 0.8937, 49: 0.8865, 50: 0.8793, 51: 0.8722, 52: 0.8650, 53: 0.8579, 54: 0.8509, 55: 0.8438, 56: 0.8368, 57: 0.8298, 58: 0.8228, 59: 0.8158, 60: 0.8089, 61: 0.8019, 62: 0.7950, 63: 0.7882, 64: 0.7813, 65: 0.7745, 66: 0.7677, 67: 0.7609, 68: 0.7541, 69: 0.7474, 70: 0.7407, 71: 0.7340, 72: 0.7273, 73: 0.7206, 74: 0.7140, 75: 0.7073, 76: 0.7007, 77: 0.6942, 78: 0.6868, 79: 0.6787, 80: 0.6698, 81: 0.6601, 82: 0.6496, 83: 0.6383, 84: 0.6263, 85: 0.6135, 86: 0.5999, 87: 0.5856, 88: 0.5704, 89: 0.5545, 90: 0.5378, 91: 0.5203, 92: 0.5021, 93: 0.4830, 94: 0.4632, 95: 0.4426, 96: 0.4213, 97: 0.3991, 98: 0.3762, 99: 0.3524, 100: 0.3279, 101: 0.3027, 102: 0.2766, 103: 0.2498, 104: 0.2221, 105: 0.1937, 106: 0.1646, 107: 0.1346, 108: 0.1038, 109: 0.0723, 110: 0.0400 }
15
+ "21097": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 1.0000, 34: 1.0000, 35: 0.9953, 36: 0.9885, 37: 0.9816, 38: 0.9748, 39: 0.9680, 40: 0.9612, 41: 0.9544, 42: 0.9476, 43: 0.9408, 44: 0.9340, 45: 0.9272, 46: 0.9204, 47: 0.9137, 48: 0.9069, 49: 0.9002, 50: 0.8934, 51: 0.8867, 52: 0.8799, 53: 0.8732, 54: 0.8665, 55: 0.8598, 56: 0.8531, 57: 0.8464, 58: 0.8397, 59: 0.8331, 60: 0.8264, 61: 0.8197, 62: 0.8131, 63: 0.8064, 64: 0.7998, 65: 0.7931, 66: 0.7865, 67: 0.7799, 68: 0.7733, 69: 0.7666, 70: 0.7600, 71: 0.7534, 72: 0.7468, 73: 0.7396, 74: 0.7318, 75: 0.7233, 76: 0.7142, 77: 0.7044, 78: 0.6941, 79: 0.6831, 80: 0.6715, 81: 0.6592, 82: 0.6463, 83: 0.6328, 84: 0.6187, 85: 0.6039, 86: 0.5885, 87: 0.5725, 88: 0.5558, 89: 0.5385, 90: 0.5206, 91: 0.5021, 92: 0.4829, 93: 0.4631, 94: 0.4426, 95: 0.4216, 96: 0.3999, 97: 0.3776, 98: 0.3546, 99: 0.3310, 100: 0.3068, 101: 0.2820, 102: 0.2565, 103: 0.2304, 104: 0.2036, 105: 0.1763, 106: 0.1483, 107: 0.1197, 108: 0.0904, 109: 0.0605, 110: 0.0300 }
16
+ "42195": { 30: 1.0000, 31: 1.0000, 32: 1.0000, 33: 1.0000, 34: 1.0000, 35: 1.0000, 36: 1.0000, 37: 1.0000, 38: 0.9947, 39: 0.9876, 40: 0.9804, 41: 0.9733, 42: 0.9661, 43: 0.9589, 44: 0.9517, 45: 0.9445, 46: 0.9372, 47: 0.9299, 48: 0.9226, 49: 0.9153, 50: 0.9079, 51: 0.9005, 52: 0.8931, 53: 0.8857, 54: 0.8783, 55: 0.8708, 56: 0.8633, 57: 0.8558, 58: 0.8483, 59: 0.8407, 60: 0.8331, 61: 0.8255, 62: 0.8179, 63: 0.8103, 64: 0.8026, 65: 0.7950, 66: 0.7873, 67: 0.7796, 68: 0.7718, 69: 0.7641, 70: 0.7563, 71: 0.7485, 72: 0.7407, 73: 0.7329, 74: 0.7250, 75: 0.7165, 76: 0.7074, 77: 0.6976, 78: 0.6871, 79: 0.6760, 80: 0.6643, 81: 0.6519, 82: 0.6388, 83: 0.6251, 84: 0.6108, 85: 0.5958, 86: 0.5801, 87: 0.5638, 88: 0.5469, 89: 0.5293, 90: 0.5110, 91: 0.4921, 92: 0.4726, 93: 0.4524, 94: 0.4316, 95: 0.4101, 96: 0.3879, 97: 0.3651, 98: 0.3417, 99: 0.3176, 100: 0.2929, 101: 0.2675, 102: 0.2415, 103: 0.2148, 104: 0.1875, 105: 0.1595, 106: 0.1309, 107: 0.1017, 108: 0.0718, 109: 0.0412, 110: 0.0100 }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Calcpace
4
- VERSION = '1.9.4'
4
+ VERSION = '1.9.5'
5
5
  end
data/lib/calcpace.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'calcpace/calculator'
4
4
  require_relative 'calcpace/cameron_predictor'
5
+ require_relative 'calcpace/age_grading'
5
6
  require_relative 'calcpace/checker'
6
7
  require_relative 'calcpace/converter'
7
8
  require_relative 'calcpace/converter_chain'
@@ -35,6 +36,7 @@ require_relative 'calcpace/vo2max_estimator'
35
36
  #
36
37
  # @see https://github.com/0jonjo/calcpace
37
38
  class Calcpace
39
+ include AgeGrading
38
40
  include Calculator
39
41
  include CameronPredictor
40
42
  include Checker
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.9.4
4
+ version: 1.9.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Gilberto Saraiva
@@ -31,11 +31,14 @@ files:
31
31
  - Rakefile
32
32
  - calcpace.gemspec
33
33
  - lib/calcpace.rb
34
+ - lib/calcpace/age_grading.rb
34
35
  - lib/calcpace/calculator.rb
35
36
  - lib/calcpace/cameron_predictor.rb
36
37
  - lib/calcpace/checker.rb
37
38
  - lib/calcpace/converter.rb
38
39
  - lib/calcpace/converter_chain.rb
40
+ - lib/calcpace/data/wma_2023_open_standards.yml
41
+ - lib/calcpace/data/wma_2023_road.yml
39
42
  - lib/calcpace/errors.rb
40
43
  - lib/calcpace/pace_calculator.rb
41
44
  - lib/calcpace/pace_converter.rb