calculators.ts (9250B)
1 /* 2 * Contains types and functions for core calculator functionality 3 */ 4 5 import * as racePrediction from '@/core/racePrediction'; 6 import type { RacePredictionOptions } from '@/core/racePrediction'; 7 import { TargetTypes, workoutTargetToString } from '@/core/targets'; 8 import type { StandardTarget, WorkoutTarget } from '@/core/targets'; 9 import { DistanceUnits, UnitSystems, convertDistance, detectDefaultUnitSystem, formatDistance, 10 formatDuration, formatPace, getDefaultDistanceUnit, getDefaultPaceUnit } from '@/core/units'; 11 import type { DistanceTime } from '@/core/units'; 12 13 /* 14 * The four main calculators (batch and unit calculators not included) 15 * 16 * Used to determine available options and target set format 17 */ 18 export enum Calculators { 19 Pace = 'pace', 20 Race = 'race', 21 Split = 'split', 22 Workout = 'workout', 23 } 24 25 /* 26 * The type for the available race statistics 27 */ 28 export interface RaceStats { 29 purdyPoints: number, 30 vo2Max: number, 31 vo2: number, 32 vo2MaxPercentage: number, 33 }; 34 35 /* 36 * The type for the options specific to each calculator 37 */ 38 export interface GlobalOptions { 39 defaultUnitSystem: UnitSystems, 40 racePredictionOptions: RacePredictionOptions, 41 }; 42 export interface SplitOptions { 43 selectedTargetSet: string, 44 }; 45 export interface PaceOptions extends SplitOptions { 46 input: DistanceTime, 47 }; 48 export type RaceOptions = PaceOptions; 49 export interface WorkoutOptions extends PaceOptions { 50 customTargetNames: boolean, 51 }; 52 export interface BatchOptions { 53 calculator: Calculators.Pace | Calculators.Race | Calculators.Workout, 54 increment: number, 55 input: DistanceTime, 56 label: string, 57 rows: number, 58 }; 59 60 /* 61 * The two possible result fields of a target result: "key" and "value" 62 */ 63 export enum ResultType { 64 Key = 'key', 65 Value = 'value', 66 }; 67 68 /* 69 * The type for target results 70 */ 71 export interface TargetResult { 72 key: string, 73 value: string, 74 pace: string, 75 result: ResultType, 76 sort: number, 77 }; 78 79 /* 80 * The default input and options for each calculator 81 */ 82 export const defaultGlobalOptions: GlobalOptions = { 83 defaultUnitSystem: detectDefaultUnitSystem(), 84 racePredictionOptions: racePrediction.defaultRacePredictionOptions, 85 }; 86 export const defaultInput: DistanceTime = { 87 distanceValue: 5, 88 distanceUnit: DistanceUnits.Kilometers, 89 time: 1200, 90 }; 91 export const defaultBatchOptions: BatchOptions = { 92 calculator: Calculators.Workout, 93 increment: 15, 94 input: defaultInput, 95 label: '', 96 rows: 20, 97 }; 98 export const defaultPaceOptions: PaceOptions = { 99 input: defaultInput, 100 selectedTargetSet: '_pace_targets', 101 }; 102 export const defaultRaceOptions: RaceOptions = { 103 input: defaultInput, 104 selectedTargetSet: '_race_targets', 105 }; 106 export const defaultSplitOptions: SplitOptions = { 107 selectedTargetSet: '_split_targets', 108 }; 109 export const defaultWorkoutOptions: WorkoutOptions = { 110 customTargetNames: false, 111 input: defaultInput, 112 selectedTargetSet: '_workout_targets', 113 }; 114 115 /** 116 * Calculate results for a standard target 117 * @param {DistanceTime} input The input pace 118 * @param {StandardTarget} target The standard target 119 * @param {Function} calculateTime The function for calculating time results 120 * @param {Function} calculateDistance The function for calculating distance results 121 * @param {UnitSystems} defaultUnitSystem The default unit system (imperial or metric) 122 * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations 123 * @returns {TargetResult} The result 124 */ 125 function calculateStandardResult(input: DistanceTime, target: StandardTarget, 126 calculateTime: (d1: number, t1: number, d2: number) => number, 127 calculateDistance: (t1: number, d1: number, t2: number) => number, defaultUnitSystem: UnitSystems, 128 preciseDurations: boolean = true): TargetResult { 129 130 let distanceValue, distanceUnit, time; 131 const d1 = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters); 132 if (target.type === TargetTypes.Distance) { 133 // Add target distance to result 134 distanceValue = target.distanceValue; 135 distanceUnit = target.distanceUnit; 136 137 // Calculate time result 138 const d2 = convertDistance(target.distanceValue, target.distanceUnit, DistanceUnits.Meters); 139 time = calculateTime(d1, input.time, d2); 140 } else { 141 // Add target time to result 142 time = target.time; 143 144 // Calculate distance result 145 const d2 = calculateDistance(input.time, d1, target.time); 146 const units = getDefaultDistanceUnit(defaultUnitSystem); 147 distanceValue = convertDistance(d2, DistanceUnits.Meters, units); 148 distanceUnit = units; 149 } 150 151 return { 152 // Convert distance to key string 153 key: formatDistance({ distanceValue, distanceUnit }, target.type === TargetTypes.Time), 154 155 // Convert time to time string 156 value: formatDuration(time, 3, preciseDurations ? 2 : 0, target.type === TargetTypes.Distance), 157 158 // Convert pace to pace string 159 pace: formatPace({ time, distanceValue, distanceUnit }, getDefaultPaceUnit(defaultUnitSystem)), 160 161 // Convert dist/time result to key/value 162 result: target.type === TargetTypes.Distance ? ResultType.Value : ResultType.Key, 163 164 // Use time (in seconds) as sort key 165 sort: time, 166 }; 167 } 168 169 /** 170 * Calculate paces from a target 171 * @param {DistanceTime} input The input pace 172 * @param {StandardTarget} target The pace target 173 * @param {UnitSystems} defaultUnitSystem The default unit system (imperial or metric) 174 * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations 175 * @returns {TargetResult} The result 176 */ 177 export function calculatePaceResults(input: DistanceTime, target: StandardTarget, 178 defaultUnitSystem: UnitSystems, 179 preciseDurations: boolean): TargetResult { 180 181 return calculateStandardResult(input, target, (d1, t1, d2) => ((t1 / d1) * d2), 182 (t1, d1, t2) => ((d1 / t1) * t2), defaultUnitSystem, preciseDurations); 183 } 184 185 /** 186 * Predict race results from a target 187 * @param {DistanceTime} input The input race 188 * @param {StandardTarget} target The race target 189 * @param {RacePredictionOptions} racePredictionOptions The race prediction options 190 * @param {UnitSystems} defaultUnitSystem The default unit system (imperial or metric) 191 * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations 192 * @returns {TargetResult} The result 193 */ 194 export function calculateRaceResults(input: DistanceTime, target: StandardTarget, 195 racePredictionOptions: RacePredictionOptions, 196 defaultUnitSystem: UnitSystems, preciseDurations: boolean 197 ): TargetResult { 198 199 return calculateStandardResult(input, target, 200 (d1, t1, d2) => racePrediction.predictTime(d1, t1, d2, racePredictionOptions), 201 (t1, d1, t2) => racePrediction.predictDistance(t1, d1, t2, racePredictionOptions), 202 defaultUnitSystem, preciseDurations); 203 } 204 205 /** 206 * Calculate race statistics from an input race 207 * @param {DistanceTime} input The input race 208 * @returns {RaceStats} The race statistics 209 */ 210 export function calculateRaceStats(input: DistanceTime): RaceStats { 211 const d1 = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters); 212 213 return { 214 purdyPoints: racePrediction.getPurdyPoints(d1, input.time), 215 vo2Max: racePrediction.getVO2Max(d1, input.time), 216 vo2: racePrediction.getVO2(d1, input.time), 217 vo2MaxPercentage: racePrediction.getVO2Percentage(input.time) * 100, 218 } 219 } 220 221 /** 222 * Predict workout results from a target 223 * @param {DistanceTime} input The input race 224 * @param {WorkoutTarget} target The workout target 225 * @param {RacePredictionOptions} racePredictionOptions The race prediction options 226 * @param {Boolean} customTargetNames Whether to use custom target names 227 * @param {Boolean} preciseDurations Whether to return precise, unrounded, durations 228 * @returns {TargetResult} The result 229 */ 230 export function calculateWorkoutResults(input: DistanceTime, target: WorkoutTarget, 231 racePredictionOptions: RacePredictionOptions, 232 customTargetNames: boolean, preciseDurations: boolean 233 ): TargetResult { 234 // Initialize distance and time variables 235 const d1 = convertDistance(input.distanceValue, input.distanceUnit, DistanceUnits.Meters); 236 const t1 = input.time; 237 const d3 = convertDistance(target.splitValue, target.splitUnit, DistanceUnits.Meters); 238 let d2, t2; 239 240 // Calculate result 241 if (target.type === 'distance') { 242 // Convert target distance into meters 243 d2 = convertDistance(target.distanceValue, target.distanceUnit, DistanceUnits.Meters); 244 245 // Get workout split prediction 246 t2 = racePrediction.predictTime(d1, input.time, d2, racePredictionOptions); 247 } else { 248 t2 = target.time; 249 250 // Get workout split prediction 251 d2 = racePrediction.predictDistance(t1, d1, t2, racePredictionOptions); 252 } 253 const t3 = (t2 / d2) * d3; 254 255 // Return result 256 return { 257 key: (customTargetNames && target.customName) || workoutTargetToString(target), 258 value: formatDuration(t3, 3, preciseDurations ? 2 : 0, true), 259 pace: '', // Pace not used in workout calculator 260 result: ResultType.Value, 261 sort: t3, 262 } 263 }