Module: AgeGrading
- Included in:
- Calcpace
- Defined in:
- lib/calcpace/age_grading.rb
Overview
Module for age-grading race performances with a versioned table
Age grading allows fairer comparison across ages by applying an age factor to the raw performance time.
Current scope:
- Common road distances: 5K, 10K, half marathon, marathon
- Sex: male/female
- Age: 18+
- Data file is versioned and replaceable (
lib/calcpace/data/wma_2023_road.yml)
Returned values include:
- age grade percentage
- age-graded time
- open standard time for the selected distance/sex
- performance category rubocop:disable Metrics/ModuleLength
Constant Summary collapse
- DATA_PATH =
File.('data/wma_2023_road.yml', __dir__).freeze
- OPEN_STANDARDS_DATA_PATH =
File.('data/wma_2023_open_standards.yml', __dir__).freeze
- WMA_DATA =
YAML.safe_load_file(DATA_PATH, permitted_classes: [], aliases: false).freeze
- OPEN_STANDARDS_DATA =
YAML.safe_load_file(OPEN_STANDARDS_DATA_PATH, permitted_classes: [], aliases: false).freeze
- TABLE_VERSION =
OPEN_STANDARDS_DATA.fetch('meta').fetch('table_version').freeze
- AGE_GRADE_LABELS =
OPEN_STANDARDS_DATA.fetch('age_grade_classifications').map do |entry| { min: entry.fetch('min').to_f, label: entry.fetch('label') } end.freeze
- DISTANCE_TO_METERS =
{ 5.0 => '5000', 10.0 => '10000', 21.0975 => '21097', 42.195 => '42195' }.freeze
- RACE_TO_METERS =
{ '5k' => '5000', '10k' => '10000', 'half_marathon' => '21097', 'marathon' => '42195' }.freeze
- SUPPORTED_DISTANCES_KM =
DISTANCE_TO_METERS.keys.freeze
- STANDARD_DISTANCE_TOLERANCE_RATIO =
How far a distance may sit from a standard and still be graded as it. 2% is the same window calcpace.app uses to decide a run "is a 5K", so the gem and the site never disagree about the same run. It stays a matching tolerance, not an interpolation: a distance outside it has no WMA factor and is refused
0.02- MINIMUM_DISTANCE_TOLERANCE_KM =
Floor for the window above, so a future shorter standard still matches
0.001
Instance Method Summary collapse
-
#age_grade(distance, time, age:, sex:, distance_unit: nil) ⇒ Hash
Returns a full age-grading report for a race performance.
-
#age_grade_label(percent) ⇒ String
Returns a descriptive label for an age-grade percentage.
-
#age_grade_percent(distance, time, age:, sex:, distance_unit: nil) ⇒ Float
Returns only the age-grade percentage.
Instance Method Details
#age_grade(distance, time, age:, sex:, distance_unit: nil) ⇒ Hash
Returns a full age-grading report for a race performance
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/calcpace/age_grading.rb', line 75 def age_grade(distance, time, age:, sex:, distance_unit: nil) distance_m = normalize_distance(distance, distance_unit) seconds = parse_time_seconds(time) age_value = normalize_age(age) sex_value = normalize_sex(sex) check_positive(seconds, 'Time') factor = interpolated_factor(sex_value, age_value, distance_m) age_graded_time = round_up_hundredth(seconds * factor) open_standard = open_standard_seconds(sex_value, distance_m) grade_percent = (open_standard / age_graded_time) * 100.0 rounded_percent = grade_percent.round(1) { age_grade_percent: rounded_percent, category: age_grade_label(rounded_percent), age_graded_time_seconds: age_graded_time, age_graded_time_clock: convert_to_clocktime(age_graded_time), open_standard_seconds: open_standard, open_standard_clock: convert_to_clocktime(open_standard), factor: factor.round(4), table_version: TABLE_VERSION } end |
#age_grade_label(percent) ⇒ String
Returns a descriptive label for an age-grade percentage
118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/calcpace/age_grading.rb', line 118 def age_grade_label(percent) percent_value = begin Float(percent) rescue ArgumentError, TypeError raise ArgumentError, 'Age-grade percent must be a numeric value greater than or equal to 0' end raise ArgumentError, 'Age-grade percent must be greater than or equal to 0' if percent_value.negative? AGE_GRADE_LABELS.find { |entry| percent_value >= entry[:min] }[:label] end |
#age_grade_percent(distance, time, age:, sex:, distance_unit: nil) ⇒ Float
Returns only the age-grade percentage
110 111 112 |
# File 'lib/calcpace/age_grading.rb', line 110 def age_grade_percent(distance, time, age:, sex:, distance_unit: nil) age_grade(distance, time, age: age, sex: sex, distance_unit: distance_unit)[:age_grade_percent] end |