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.expand_path('data/wma_2023_road.yml', __dir__).freeze
OPEN_STANDARDS_DATA_PATH =
File.expand_path('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

Instance Method Summary collapse

Instance Method Details

#age_grade(distance_km, time, age:, sex:) ⇒ Hash

Returns a full age-grading report for a race performance

Parameters:

  • distance_km (Numeric, String, Symbol)

    race distance in kilometres (5.0, 10.0, 21.0975, 42.195) or race key (:5k, :10k, :half_marathon, :marathon)

  • time (String, Numeric)

    performance time as HH:MM:SS / MM:SS, or total seconds

  • age (Integer)

    athlete age (must be >= 18)

  • sex (String, Symbol)

    male or female

Returns:

  • (Hash)

    age-grading result details



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/calcpace/age_grading.rb', line 58

def age_grade(distance_km, time, age:, sex:)
  distance_m = normalize_distance(distance_km)
  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

Parameters:

  • percent (Numeric)

    age-grade percentage

Returns:

  • (String)

    category label

Raises:

  • (ArgumentError)


99
100
101
102
103
104
105
106
107
108
109
# File 'lib/calcpace/age_grading.rb', line 99

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_km, time, age:, sex:) ⇒ Float

Returns only the age-grade percentage

Parameters:

  • distance_km (Numeric)

    race distance in kilometres

  • time (String, Numeric)

    performance time

  • age (Integer)

    athlete age

  • sex (String, Symbol)

    male or female

Returns:

  • (Float)

    age-grade percentage



91
92
93
# File 'lib/calcpace/age_grading.rb', line 91

def age_grade_percent(distance_km, time, age:, sex:)
  age_grade(distance_km, time, age: age, sex: sex)[:age_grade_percent]
end