VO2 max gauge: Garmin/Cooper Institute thresholds, add Superior category
- Replace ACSM estimates with exact Garmin/Cooper Institute values for 6 age brackets (20-29 through 70+) for both male and female - Add Superior (≥95th percentile) as the top category in purple; rename Very Poor → removed, categories are now Poor/Fair/Good/Excellent/Superior - Update getVo2Category to use lower-bound logic (count thresholds met) matching how Garmin publishes the data Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,38 +21,40 @@ const RANGES = [
|
|||||||
|
|
||||||
// ── VO2 Max gauge ────────────────────────────────────────────────────────────
|
// ── VO2 Max gauge ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// ACSM sex-specific VO2 max thresholds
|
// Garmin/Cooper Institute VO2 max thresholds
|
||||||
// [maxAge, [veryPoor_max, poor_max, fair_max, good_max]] (above good_max → Excellent)
|
// [maxAge, [fair_min, good_min, excellent_min, superior_min]]
|
||||||
|
// value < fair_min → Poor; >= superior_min → Superior
|
||||||
const VO2_MALE = [
|
const VO2_MALE = [
|
||||||
[29, [32, 36, 41, 45]],
|
[29, [41.7, 45.4, 51.1, 55.4]],
|
||||||
[39, [30, 34, 38, 43]],
|
[39, [40.5, 44.0, 48.3, 54.0]],
|
||||||
[49, [29, 33, 37, 42]],
|
[49, [38.5, 42.4, 46.4, 52.5]],
|
||||||
[59, [25, 30, 34, 38]],
|
[59, [35.6, 39.2, 43.4, 48.9]],
|
||||||
[69, [20, 24, 28, 32]],
|
[69, [32.3, 35.5, 39.5, 45.7]],
|
||||||
[Infinity, [17, 21, 24, 28]],
|
[Infinity, [29.4, 32.3, 36.7, 42.1]],
|
||||||
]
|
]
|
||||||
const VO2_FEMALE = [
|
const VO2_FEMALE = [
|
||||||
[29, [27, 33, 36, 41]],
|
[29, [36.1, 39.5, 43.9, 49.6]],
|
||||||
[39, [26, 30, 35, 39]],
|
[39, [34.4, 37.8, 42.4, 47.4]],
|
||||||
[49, [24, 28, 32, 36]],
|
[49, [33.0, 36.3, 39.7, 45.3]],
|
||||||
[59, [21, 24, 28, 32]],
|
[59, [30.1, 33.0, 36.7, 41.1]],
|
||||||
[69, [18, 21, 24, 28]],
|
[69, [27.5, 30.0, 33.0, 37.8]],
|
||||||
[Infinity, [15, 18, 22, 25]],
|
[Infinity, [25.9, 28.1, 30.9, 36.7]],
|
||||||
]
|
]
|
||||||
const VO2_CATEGORIES = [
|
const VO2_CATEGORIES = [
|
||||||
{ label: 'Very Poor', color: '#ef4444' },
|
{ label: 'Poor', color: '#ef4444' },
|
||||||
{ label: 'Poor', color: '#f97316' },
|
{ label: 'Fair', color: '#f97316' },
|
||||||
{ label: 'Fair', color: '#22c55e' },
|
{ label: 'Good', color: '#22c55e' },
|
||||||
{ label: 'Good', color: '#3b82f6' },
|
{ label: 'Excellent', color: '#3b82f6' },
|
||||||
{ label: 'Excellent', color: '#a855f7' },
|
{ label: 'Superior', color: '#a855f7' },
|
||||||
]
|
]
|
||||||
|
|
||||||
function getVo2Category(value, age, sex) {
|
function getVo2Category(value, age, sex) {
|
||||||
const table = sex === 'female' ? VO2_FEMALE : VO2_MALE
|
const table = sex === 'female' ? VO2_FEMALE : VO2_MALE
|
||||||
const row = table.find(([maxAge]) => age <= maxAge) || table[table.length - 1]
|
const row = table.find(([maxAge]) => age <= maxAge) || table[table.length - 1]
|
||||||
const thresholds = row[1]
|
const thresholds = row[1]
|
||||||
const idx = thresholds.findIndex(t => value <= t)
|
// thresholds are lower-bounds: count how many the value meets or exceeds
|
||||||
return idx === -1 ? VO2_CATEGORIES[4] : VO2_CATEGORIES[idx]
|
const idx = thresholds.reduce((n, t) => value >= t ? n + 1 : n, 0)
|
||||||
|
return VO2_CATEGORIES[idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
function Vo2MaxGauge({ value, birthYear, biologicalSex }) {
|
function Vo2MaxGauge({ value, birthYear, biologicalSex }) {
|
||||||
|
|||||||
Reference in New Issue
Block a user