Methods
Every number we show you, with the formula, the table and the pool it came from.
The short version
- We never compute a rating. No Elo, no MMR, no composite life score, and no norms derived from our own users — a ranking against a pool we happen to have signed up is not a fact about you.
- Where we place you at all, it is a lookup in a table somebody else published, dated on the page you read it on.
- The pool is always named next to the number. “62nd percentile” on its own is not a claim; “62nd percentile of people who compete in raw powerlifting” is.
- Most categories get no standing at all, and say why. That list is below too.
- The daily verdict names how yesterday departed from your own four-week norms, in your own units. No score, no weighting, and the rule that decides what gets a mention is published in full below.
The day's verdict — departures in your own swing
Every day the app names how yesterday departed from your own norms, in your own units, with the arithmetic printed beside each line. There is no daily score, no weighting, and no number that combines metrics — the rule below is the entire decision, and it is the same rule the app prints on its own “how this works” disclosure. Changing any constant on this page is an amendment to the decision record behind it, not a tuning pass.
The same 40 minutes of missing sleep is news for a metronome sleeper and weather for a shift worker. That is not a rough edge in the rule; it is the whole point of a scale built out of your own days, and it is the answer a product with one fixed threshold — or one proprietary weighting — cannot give you.
The norm
Your norm for a metric is the mean of the trailing 28 days ending the day before the day being judged — four weeks, the same window the weekly letter uses, so two surfaces of one product cannot disagree about what normal means.
- Observed days only, never interpolated. A day with no reading is dropped from the mean, not filled in and not counted as a zero. A day your phone recorded no steps at all is a real zero and does count; a day it recorded nothing because it was off is unknown, and unknown never becomes a number.
- At least 8 observed days in those 28, or there is no norm and the metric is not checked. The surface says so and names the floor rather than going quiet.
- A rhythm gate of 8 active days for the count-shaped metrics — the ones that accrue events over a day. Something that happens twice a month has no weekly rhythm and therefore cannot have a daily one.
The scale — your own swing
The bar cannot be a percentage: 5% is nothing for steps and enormous for a resting heart rate. So the scale is your own typical day-to-day distance from your own norm, over those same observed days:
swing = mean( |value on day i − norm| )
That is the mean absolute deviation about the mean, and two properties earned it the job. Both are worth stating, because one of them is a cost.
- It does not collapse on ties. The median absolute deviation — the usual robust choice — is exactly zero whenever more than half the window ties, which every integer count and every 1–5 rating arranges routinely; the interquartile range is zero on the same shape. A zero scale would make every departure infinite. This one is zero only when every observed day equals your norm exactly, and a metric with genuinely no daily scale is one we then decline to speak about.
- It is inflated by one wild day, on purpose. A single extreme day raises your swing, and a raised swing raises the bar for a fortnight. That makes the surface quieter after unusual days rather than louder — the opposite of what a standard deviation would do. For a surface whose failure mode is chattiness, an estimator that errs quiet is the one you want, and we would rather say that out loud than have you find it.
Both numbers — the norm and the swing — come from the 28 days before the day being judged, so that day is genuinely out of sample.
The bar, and the cap of 3
A metric earns a mention when the day lands at least 2.0× your swing away from your norm, it has a norm, the day has a known reading, and the difference is still non-zero once rounded to the precision it prints at. Absence is never a deviation.
At most 3 mentions print. They are ordered by how many swings out they are — a number that is never displayed, because a displayed ranking number across metrics is the score this product refuses to ship. Metrics that measure one construct (a resting heart rate from two devices, sleep summed from two apps) collapse to a single line before the cut; two instruments that disagree about the direction suppress each other entirely, because that is a data-quality fact and not a deviation.
The multiple is editorial, not inferential, and we will say where it came from: under the usual bell-curve assumption this swing runs about 0.8 of a standard deviation, so the bar sits near 1.6 of one — roughly a one-in-nine chance per metric per day, which leaves a typical reader with zero or one line most days. Lower, and something is “off” nearly every day, which is a score with extra steps. Higher, and the surface speaks once a week and stops being worth opening. It is revisited on a published cadence, and revising it means amending the record, not editing a config.
A quiet day is an answer and prints as one, with its denominator: “nothing unusual — 7 metrics were within your 4-week norms”. A surface that says nothing unusual while silently checking two metrics is lying, and printing the count is what makes the sentence checkable.
The day being judged
One complete day, on your own calendar — the timezone your account declares. The day in progress is never judged, for any metric: a partial day would report “steps 6,000 below your norm” at nine every morning. So the newest day the verdict can speak about is the one that has just ended, and the block prints which day it means, every time it renders.
Sleep is filed under the day you woke up, which is the convention the rest of the app uses. A night therefore reaches the verdict once that waking day is over — a newspaper is today’s paper about yesterday.
What is not here: no p-value, no correction
Elsewhere on this platform, findings that make a statistical claim carry a q-value and a multiple-comparison correction. These sentences carry neither, and that is a decision rather than an oversight. “You slept 6h05, 40 minutes under your 4-week norm of 6h45” is a description of four stored numbers: it is true whatever is or is not going on underneath, there is no hypothesis in it, and so there is nothing to reject and no p to correct.
What multiplicity buys instead is the editorial discipline above — a fixed bar, a hard cap, a deterministic order — so that adding metrics to the roster can never add claims to a day. The moment any copy here calls a day abnormal rather than saying what it was, that is a hypothesis and it needs the full apparatus before it ships.
What is checked — 9 things
The roster is curated and short, with the reason attached to each member. A metric being available is not a reason to check it daily.
- Sleep
/health/sleep/asleep_minutes— The line the whole surface is for. Additive across sources, so it enters split by source and collapses to one line before it prints. - Resting heart rate
/health/cardio/resting_heart_rate·/health/cardio/nightly_resting_heart_rate— Two names for one construct, joined in the manifests and printed once. An elevated morning resting heart rate is the one daily number with a real-world action behind it. - Heart-rate variability
/health/cardio/hrv_rmssd— The input the incumbents bury inside a recovery score. Shown raw, against your own swing. - Respiratory rate
/health/cardio/respiratory_rate— A nightly level whose day-scale departure is a classic early illness signal, and one that means nothing without a personal baseline. - Steps
/health/activity/steps— A complete-day total with a strong personal rhythm; your swing absorbs the weekday shape a fixed goal cannot. - Exercise minutes
/health/activity/exercise_minutes— A covered day with no training is a real zero, so a training gap is a value like any other. It clears the bar only when your norm is at least twice your swing — news for someone who trains almost every day, and not for someone whose week already contains rest days. - Screen time
/screen/time/total— The drift you suspect and cannot see, and a complete-day total. - Mood
/journal/mood/level— A 1–5 rating, which is exactly the tie-heavy shape that a median-based scale collapses on — see the swing below. A day well off your own mood norm is the single most useful thing this surface can name. - Mean glucose
/health/glucose/mean— For a CGM reader, a complete-day level whose daily departure is directly actionable.
What is deliberately not checked
This list is as much the design as the roster is, and the first entry is permanent.
- Every vendor composite. Recovery score, readiness, sleep score, day strain. Permanent, not a v1 deferral: naming a black box's deviation amplifies the black box, and its inputs are on the roster above already. We still display a vendor's own score where they publish one — rendering a number its publisher stands behind is a different act from minting one.
- Weight, VO₂max and body fat. Clock speed. A day's departure in weight is water, and the quarter's drift is the real story; the other two have no daily value to depart from.
- Bedtime. Clock arithmetic is circular — the mean of 23:50 and 00:10 is not noon — and the regularity ruler already owns consistency.
- Skin temperature deviation. Already a deviation from a vendor's baseline. Double-baselining is not a second opinion.
- Blood oxygen. Quantized at 98–99%, so the swing would be a rounding artefact rather than your own variability.
- Fitness load — CTL, ATL and form. Trailing-window constructions whose day-to-day movement is arithmetic on their own window, not news.
- Money, ratings and ladders. Lumpy and rent-shaped, or a standing — and a standing is the rest of this page's business, not a day's.
- Anything yes/no. A yes/no day is 0 or 1, so “deviation from the rate” fires exactly when you did the rarer thing, every time you do it — a tautology dressed as a finding. Habits ask a better question of the same fact.
- Lab results, ranks, and per-thing metrics. Refused structurally, whatever a manifest asks for: a few blood draws a year has no daily anything, a ladder rank is somebody else's number, and a metric keyed per game or per app has no single daily value to be a deviation of.
What has to be true before we place you
A category shows a named standing only when the ruler is external, absolute and instrument-compatible: published by someone else and citable, statable in your own units, and derived from measurements like the ones we hold. Where any of those fails, the surface prints the refusal and its reason where the rank would have been.
That is a deliberately hard bar, and most things do not clear it. Sleep duration has a U-shaped mortality curve, so a high percentile would be a warning dressed as a win. Books per year is self-reported and Goodhart-corrupted, and ranking it optimises the wrong life. Resting heart rate is not monotone-good — beta blockers and illness both move it the “right” way. HRV, steps and screen time fail on the same grounds. None of those get a standing, and none of them will get one because somebody asked nicely.
Strength — per-lift tiers
What we show. A tier per barbell lift — squat, bench press, deadlift — from your best estimated one-rep max over the last 90 days, read against lifters of the same published column and a similar bodyweight.
The formula. Your one-rep max is an Epley estimate — weight × (1 + reps ÷ 30)— taken over your non-warmup sets of 1–12 reps, with a true single counting as its own weight. Your bodyweight is the most recent one we hold, and if you have no scale connected, the one you typed into your profile. A measured weight always wins over a typed one.
The pool, and its skew. people who compete in raw powerlifting: everyone in this table entered a raw powerlifting meet, which is a self-selected group who train these three lifts on purpose. It is not the general population. A low percentile here is a statement about that pool, not about you.
The gap we cannot close. Your number is an estimate off a working set. Theirs is a third attempt on a platform with three judges. Expect the estimate to read a little high against them, and more so the further your sets are from a single. We say this on the sheet as well as here.
The table
Reduced from the OpenPowerlifting bulk archive, snapshot . Public domain dedication; attribution requested, not required. The archive we used hashes to 339a08c5217e0a11….
This ruler uses data from the OpenPowerlifting project, https://www.openpowerlifting.org. You may download a copy of the data at https://gitlab.com/openpowerlifting/opl-data.
4,001,901 rows in, 1,814,639 kept after the filters, 580,708 distinct lifters after deduplication. Download the same archive and run the script below to get these numbers back.
The cut points, and whose they are
The tier names are ours. The percentiles they sit at are 5, 20, 50, 80 and 95, read off the table above. So “Advanced” means “at or above the 80th percentile of people who compete in raw powerliftingat your bodyweight” and nothing else — it is not a certification and no federation issues it.
Everything below the 5th percentile of that pool is the first band, Untrained. Against people who compete, that is where most people who lift start.
| Bodyweight | Beginner | Novice | Intermediate | Advanced | Elite | n |
|---|---|---|---|---|---|---|
| 50 kg | 52.5 | 70 | 90 | 110 | 130 | 22,284 |
| 55 kg | 57.5 | 75 | 95 | 115 | 137.5 | 35,218 |
| 60 kg | 61.23 | 80 | 102.06 | 122.5 | 145 | 42,177 |
| 65 kg | 65 | 85 | 107.5 | 130 | 152.5 | 41,414 |
| 70 kg | 70 | 90 | 112.5 | 137.5 | 160 | 39,013 |
| 80 kg | 70 | 92.99 | 117.5 | 145 | 170 | 19,211 |
| 90 kg | 70 | 95 | 120 | 147.45 | 175 | 8,924 |
| Bodyweight | Beginner | Novice | Intermediate | Advanced | Elite | n |
|---|---|---|---|---|---|---|
| 60 kg | 77.5 | 105 | 137.5 | 170 | 197.5 | 22,714 |
| 70 kg | 100 | 130 | 162.5 | 192 | 220 | 53,237 |
| 80 kg | 115 | 150 | 180 | 210 | 240 | 62,062 |
| 90 kg | 127.5 | 165 | 195 | 227.5 | 260 | 62,347 |
| 100 kg | 140 | 175 | 210 | 242.5 | 277.5 | 46,103 |
| 110 kg | 140 | 180 | 217.5 | 252.5 | 290 | 22,648 |
| 120 kg | 145 | 190 | 230 | 272.5 | 310 | 16,150 |
| Bodyweight | Beginner | Novice | Intermediate | Advanced | Elite | n |
|---|---|---|---|---|---|---|
| 50 kg | 30 | 40 | 50 | 62.5 | 75 | 28,455 |
| 55 kg | 32.5 | 42.5 | 52.5 | 65 | 80 | 44,074 |
| 60 kg | 35 | 45 | 55 | 70 | 85 | 50,527 |
| 65 kg | 37.5 | 47.5 | 60 | 72.5 | 90 | 48,145 |
| 70 kg | 38.56 | 50 | 62.5 | 77 | 95 | 45,147 |
| 80 kg | 40 | 50 | 63.25 | 80 | 100 | 22,276 |
| 90 kg | 40 | 50 | 65 | 80 | 100 | 10,378 |
| Bodyweight | Beginner | Novice | Intermediate | Advanced | Elite | n |
|---|---|---|---|---|---|---|
| 60 kg | 52 | 70 | 90 | 112.5 | 135 | 32,624 |
| 70 kg | 67.5 | 90 | 110 | 130 | 150 | 75,520 |
| 80 kg | 80 | 100 | 122.5 | 142.88 | 165 | 85,623 |
| 90 kg | 87.5 | 110 | 132.5 | 155 | 180 | 84,535 |
| 100 kg | 95 | 120 | 145 | 170 | 195.5 | 64,232 |
| 110 kg | 95 | 125 | 154.2 | 182.5 | 210 | 34,312 |
| 120 kg | 100 | 130 | 162.5 | 192.5 | 220 | 23,377 |
| Bodyweight | Beginner | Novice | Intermediate | Advanced | Elite | n |
|---|---|---|---|---|---|---|
| 50 kg | 70 | 90 | 110 | 132.5 | 155 | 25,525 |
| 55 kg | 75 | 95 | 117.5 | 140 | 160 | 40,347 |
| 60 kg | 81.65 | 102.21 | 122.5 | 145 | 170 | 47,413 |
| 65 kg | 86.18 | 107.5 | 130 | 152.5 | 175 | 45,627 |
| 70 kg | 90 | 112.5 | 135 | 157.5 | 182.5 | 43,161 |
| 80 kg | 92.5 | 115 | 140 | 165 | 192.5 | 21,159 |
| 90 kg | 92.99 | 117.5 | 140 | 165 | 192.5 | 9,935 |
| Bodyweight | Beginner | Novice | Intermediate | Advanced | Elite | n |
|---|---|---|---|---|---|---|
| 60 kg | 100 | 132.5 | 167.5 | 200 | 230 | 25,682 |
| 70 kg | 127.01 | 160 | 192.5 | 225 | 252.5 | 61,684 |
| 80 kg | 145 | 180 | 210 | 240 | 272.5 | 70,991 |
| 90 kg | 157.5 | 192.5 | 226.8 | 257.5 | 287.5 | 71,171 |
| 100 kg | 165 | 204.12 | 237.5 | 272.5 | 305 | 53,214 |
| 110 kg | 165.56 | 206.38 | 242.5 | 280 | 315 | 27,192 |
| 120 kg | 172.5 | 212.5 | 250 | 290 | 325 | 18,927 |
The reduction, in full
This is the script that turned the archive into the tables above, verbatim. It is here because a ruler nobody can re-derive is a black box with a citation stapled to it.
#!/usr/bin/env python3
"""Reduce the OpenPowerlifting bulk CSV to a frozen strength ruler (ADR 0134).
This is a ONE-OFF, OFFLINE reduction. Nothing in the running platform calls
it; it produces a versioned JSON table that is checked in, cited, and served
verbatim. It is published on /methods so anybody can re-run it against the
same input and get the same table — a ruler nobody can audit is a black box
wearing a citation.
Input
The OpenPowerlifting bulk CSV distribution, dedicated to the public
domain by the project:
https://openpowerlifting.gitlab.io/opl-csv/files/openpowerlifting-latest.zip
Usage
python3 scripts/rulers/reduce_openpowerlifting.py \
--csv openpowerlifting-YYYY-MM-DD/openpowerlifting-YYYY-MM-DD-HASH.csv \
--zip openpowerlifting-latest.zip \
--snapshot YYYY-MM-DD \
--out go/platform/rulers/tables/strength_openpowerlifting.json
The pipeline, in the order the steps run
1. RAW ONLY. Keep Equipment == "Raw" (bare knees or knee sleeves). Wraps
and every equipped category add kilos no gym est-1RM has, so mixing
them in would move the whole curve away from the instrument we
measure people with.
2. TWO PUBLISHED COLUMNS. Keep Sex in {M, F}. These are the columns
OpenPowerlifting itself splits on, and the only ones the table can
honestly offer. "Mx" is left out because its n is far too small to
make a stable per-bodyweight curve, and a noisy curve presented as a
ruler is worse than no ruler. An account whose reference_table is
NONE or unset gets no placement at all, by design.
3. DROP NON-RESULTS. Skip Place in {DQ, DD, G, NS} and any lift whose
Best3xxxKg is missing or <= 0 — a failed or unattempted lift is
recorded as zero or negative, not as a small number.
4. PLAUSIBLE BODYWEIGHT. Require 35 <= BodyweightKg <= 200.
5. DEDUP TO ONE ROW PER LIFTER PER LIFT. A lifter with 40 meets in the
archive would otherwise be counted 40 times, tilting every percentile
toward whoever competes most often. We keep each lifter's best-ever
result for each lift, paired with the bodyweight they weighed in at
for THAT result: a lifter is one person in this table, at their best.
Lifters are keyed by the archive's own disambiguated Name (the
project appends "#2" to a second John Doe), which is the only lifter
identity the CSV carries.
6. BIN BY BODYWEIGHT. For each sex and lift, take knots every
KNOT_STEP kg and pool the lifters within +/- WINDOW_KG of the knot.
A knot is emitted only when its window holds at least MIN_SAMPLES
lifters; a thin tail is dropped rather than published as a curve.
7. PERCENTILES. At each knot the sorted window is read at the percentile
grid below by linear interpolation between order statistics (the
"linear" / type-7 definition). Between knots a lookup interpolates
linearly in bodyweight; outside the emitted knots it clamps to the
nearest one, and the surface says that it did.
8. DOTS. The DOTS total distribution is reduced the same way but WITHOUT
bodyweight bins, because DOTS already normalises for bodyweight. Only
full-power (SBD) results contribute.
What the output deliberately does NOT contain
No names, no meets, no dates, no rows — only order statistics over pools
of at least MIN_SAMPLES people. The reduction is one-way.
"""
import argparse
import csv
import hashlib
import json
import sys
from collections import defaultdict
# The percentile grid every emitted curve is sampled at. The five tier cut
# points (5/20/50/80/95) are members of it, so a tier boundary is read from
# the table rather than re-derived; the rest exist so a face can draw the
# distribution and invert it to place one lifter without ever shipping a row.
PERCENTILE_GRID = [1, 2.5, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 97.5, 99]
# The tier cut points (ADR 0134). The NAMES are ours; the percentiles they
# sit at are published on /methods so nobody has to trust the naming.
TIER_CUTS = [5, 20, 50, 80, 95]
TIER_NAMES = ["Untrained", "Beginner", "Novice", "Intermediate", "Advanced", "Elite"]
KNOT_STEP = 5.0 # kg between bodyweight knots
WINDOW_KG = 5.0 # +/- this much around a knot is pooled
MIN_SAMPLES = 500 # a knot below this is not published
LIFTS = {
# our lift key -> the CSV column holding the best of three
"squat": "Best3SquatKg",
"bench": "Best3BenchKg",
"deadlift": "Best3DeadliftKg",
}
BAD_PLACES = {"DQ", "DD", "G", "NS"}
# The DOTS polynomial, published by OpenPowerlifting and reproduced verbatim
# on /methods. DOTS = total_kg * 500 / poly(bodyweight_kg), with bodyweight
# clamped to the range the polynomial was fitted over.
DOTS_COEFFICIENTS = {
"M": (-0.000001093, 0.0007391293, -0.1918759221, 24.0900756, -307.75076),
"F": (-0.0000010706, 0.0005158568, -0.1126655495, 13.6175032, -57.96288),
}
DOTS_BW_CLAMP = {"M": (40.0, 210.0), "F": (40.0, 150.0)}
def quantile(sorted_values, pct):
"""The type-7 quantile: linear interpolation between order statistics.
Matches numpy's default and R's type 7, spelled out here so the pipeline
carries no dependency a reader would have to go and check.
"""
if not sorted_values:
return None
if len(sorted_values) == 1:
return sorted_values[0]
pos = (len(sorted_values) - 1) * (pct / 100.0)
low = int(pos)
high = min(low + 1, len(sorted_values) - 1)
frac = pos - low
return sorted_values[low] + (sorted_values[high] - sorted_values[low]) * frac
def parse_float(text):
if not text:
return None
try:
return float(text)
except ValueError:
return None
def dots(sex, total_kg, bodyweight_kg):
a, b, c, d, e = DOTS_COEFFICIENTS[sex]
low, high = DOTS_BW_CLAMP[sex]
bw = min(max(bodyweight_kg, low), high)
denom = a * bw**4 + b * bw**3 + c * bw**2 + d * bw + e
if denom <= 0:
return None
return total_kg * 500.0 / denom
def read_lifters(csv_path):
"""Steps 1-5: stream the CSV down to one record per lifter.
Returns {(sex, name): {"squat": (kg, bodyweight), ..., "total": (kg, bw)}}
"""
best = defaultdict(dict)
kept_rows = 0
total_rows = 0
with open(csv_path, newline="", encoding="utf-8") as handle:
for row in csv.DictReader(handle):
total_rows += 1
if row["Equipment"] != "Raw": # step 1
continue
sex = row["Sex"]
if sex not in ("M", "F"): # step 2
continue
if row["Place"] in BAD_PLACES: # step 3
continue
bodyweight = parse_float(row["BodyweightKg"])
if bodyweight is None or not (35.0 <= bodyweight <= 200.0): # step 4
continue
entry = best[(sex, row["Name"])]
kept_rows += 1
for lift, column in LIFTS.items(): # step 5
kilos = parse_float(row[column])
if kilos is None or kilos <= 0:
continue
if lift not in entry or kilos > entry[lift][0]:
entry[lift] = (kilos, bodyweight)
# Step 8's input: a DOTS total needs all three lifts, one meet.
if row["Event"] == "SBD":
total = parse_float(row["TotalKg"])
if total and total > 0:
if "total" not in entry or total > entry["total"][0]:
entry["total"] = (total, bodyweight)
return best, total_rows, kept_rows
def reduce_lift(records, sex, lift):
"""Steps 6-7 for one sex and one lift: knots -> percentile curves."""
by_bin = defaultdict(list)
for (row_sex, _name), entry in records.items():
if row_sex != sex or lift not in entry:
continue
kilos, bodyweight = entry[lift]
by_bin[int(bodyweight)].append(kilos)
knots = []
knot = 35.0
while knot <= 200.0:
pooled = []
for whole_kg in range(int(knot - WINDOW_KG), int(knot + WINDOW_KG) + 1):
pooled.extend(by_bin.get(whole_kg, ()))
if len(pooled) >= MIN_SAMPLES:
pooled.sort()
knots.append(
{
"bodyweight_kg": knot,
"n": len(pooled),
"kg": [round(quantile(pooled, p), 2) for p in PERCENTILE_GRID],
}
)
knot += KNOT_STEP
return knots
def reduce_dots(records, sex):
"""Step 8: one bodyweight-free DOTS distribution per sex."""
scores = []
for (row_sex, _name), entry in records.items():
if row_sex != sex or "total" not in entry:
continue
total, bodyweight = entry["total"]
score = dots(sex, total, bodyweight)
if score:
scores.append(score)
if len(scores) < MIN_SAMPLES:
return None
scores.sort()
return {
"n": len(scores),
"dots": [round(quantile(scores, p), 2) for p in PERCENTILE_GRID],
}
def sha256(path):
digest = hashlib.sha256()
with open(path, "rb") as handle:
for chunk in iter(lambda: handle.read(1 << 20), b""):
digest.update(chunk)
return digest.hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--csv", required=True, help="the extracted bulk CSV")
parser.add_argument("--zip", required=True, help="the downloaded zip, hashed into the citation")
parser.add_argument("--snapshot", required=True, help="the distribution date, YYYY-MM-DD")
parser.add_argument("--out", required=True)
args = parser.parse_args()
records, total_rows, kept_rows = read_lifters(args.csv)
print(f"rows: {total_rows} kept: {kept_rows} lifters: {len(records)}", file=sys.stderr)
table = {
"id": "strength_openpowerlifting_v1",
"version": 1,
"snapshot_date": args.snapshot,
# How this ruler ages (ADR 0154). The archive is a feed, so the table
# DRIFTS and the monitor can watch it with a clock.
"staleness": {
"policy": "DRIFTS",
"max_age_days": 180,
"rationale": (
"OpenPowerlifting republishes the bulk archive continuously as meets are "
"entered, so the same reduction re-run today returns different percentiles. "
"The snapshot date is the clock: past six months the tiers are quoting a pool "
"that has moved on, and the fix is mechanical — re-run the reduction and "
"commit the new snapshot."
),
},
"source": {
"name": "OpenPowerlifting",
"url": "https://www.openpowerlifting.org",
"data_url": "https://openpowerlifting.gitlab.io/opl-csv/files/openpowerlifting-latest.zip",
"license": "Public domain dedication; attribution requested, not required.",
"attribution": (
"This ruler uses data from the OpenPowerlifting project, "
"https://www.openpowerlifting.org. You may download a copy of "
"the data at https://gitlab.com/openpowerlifting/opl-data."
),
"archive_sha256": sha256(args.zip),
},
"pipeline": {
"script": "scripts/rulers/reduce_openpowerlifting.py",
"equipment": "Raw",
"dedup": "one row per lifter per lift: their best-ever result, at that meet's bodyweight",
"knot_step_kg": KNOT_STEP,
"window_kg": WINDOW_KG,
"min_samples": MIN_SAMPLES,
"quantile": "type-7 (linear interpolation between order statistics)",
"rows_read": total_rows,
"rows_kept": kept_rows,
"lifters": len(records),
},
"pool_label": "people who compete in raw powerlifting",
"percentile_grid": PERCENTILE_GRID,
"tier_cuts": TIER_CUTS,
"tier_names": TIER_NAMES,
"lifts": {},
"dots_total": {},
}
for lift in LIFTS:
table["lifts"][lift] = {}
for sex in ("F", "M"):
knots = reduce_lift(records, sex, lift)
table["lifts"][lift]["FEMALE" if sex == "F" else "MALE"] = knots
print(f"{lift} {sex}: {len(knots)} knots", file=sys.stderr)
for sex in ("F", "M"):
reduced = reduce_dots(records, sex)
if reduced:
table["dots_total"]["FEMALE" if sex == "F" else "MALE"] = reduced
with open(args.out, "w", encoding="utf-8") as handle:
json.dump(table, handle, indent=1, sort_keys=True)
handle.write("\n")
print(f"wrote {args.out}", file=sys.stderr)
if __name__ == "__main__":
main()
Accessory strength — lift-specific rulers
The earlier audit stopped at three refusals. A second pass found a licensed, instrument-compatible route: FitnessVolt publishes versioned, CC BY 4.0 gym distributions for overhead press and pull-up, and a separately labelled Romanian-deadlift model. We keep those evidence types separate instead of making the model look like a measured population.
The common clock. Like squat, bench and deadlift, these rows use your best qualifying estimate from the last 90 days, read at weekly boundaries with the same 14-day hold and 60-day stale rule. They do not enter DOTS, and they do not borrow the OpenPowerlifting pool or citation.
Snapshot ; API version 1.0.0, data version 2026-06-09. The API’s bodyweight classes are discrete: we select the published class and never smooth across its boundary. Read the FitnessVolt methodology.
The four full API responses and the extracted, licensed RDL table facts are frozen in one dated source archive, SHA-256 dce09287788e45e23b990512a64245482d296ebdef64ca153222c3ffd8581383. Every lift citation names those exact checked-in bytes; the mutable API endpoints and article page are discovery sources, not archives.
Overhead press
Measured self-reported distribution. FitnessVolt republishes sex-by-bodyweight-class percentile tables from the Symmetric Strength self-reported gym population. The population is measured but self-reported, and is kept separate from verified competition results.
Standing barbell overhead-press one-repetition maximum in kilograms: the external barbell load, read against the lifter's sex-specific bodyweight class.
Self-reported gym lifts are not competition-judged: technique, range of motion, 1RM estimation, rounding, and selection into a strength-tracking site can all shift this population away from the lifter being compared.
Strength standards data by FitnessVolt, sourced from the Symmetric Strength self-reported gym dataset. CC BY 4.0; FitnessVolt requires a visible attribution link on every page or product displaying the data. Pool size: 405,810 samples; the API does not promise they are unique people.
| Bodyweight class | 10th-24th percentile | 25th-49th percentile | 50th-74th percentile | 75th-89th percentile | 90th-94th percentile | 95th-98th percentile | 99th percentile or above | n |
|---|---|---|---|---|---|---|---|---|
| 47kg | 11.9 | 15.9 | 24 | 29.2 | 33.7 | 35.4 | 40.2 | 451 |
| 52kg | 16.7 | 22.5 | 27.2 | 31.8 | 36 | 39.7 | 42.4 | 1,832 |
| 57kg | 21.9 | 25.5 | 30 | 33.9 | 38 | 40.8 | 45 | 3,951 |
| 63kg | 23.1 | 26.5 | 30.7 | 35.8 | 39.8 | 43.2 | 50.3 | 4,677 |
| 69kg | 24 | 28.5 | 33.4 | 38.7 | 45 | 47.6 | 57.6 | 4,538 |
| 76kg | 24.2 | 28.4 | 34.4 | 40 | 45.4 | 49.2 | 63.3 | 2,898 |
| 84kg | 26.2 | 30.7 | 37 | 44 | 50.3 | 52.2 | 59.4 | 1,952 |
| 84+kg | 24.5 | 30.4 | 37.4 | 45.2 | 53.9 | 63.3 | 71.8 | 1,259 |
| Bodyweight class | 10th-24th percentile | 25th-49th percentile | 50th-74th percentile | 75th-89th percentile | 90th-94th percentile | 95th-98th percentile | 99th percentile or above | n |
|---|---|---|---|---|---|---|---|---|
| 59kg | 27.2 | 34.2 | 40.8 | 48 | 55 | 58.6 | 66.7 | 7,068 |
| 66kg | 34.7 | 40.8 | 47.6 | 55.4 | 62.4 | 66.7 | 75.6 | 27,809 |
| 74kg | 39 | 46.2 | 53.3 | 60.9 | 68 | 72.3 | 82.2 | 72,499 |
| 83kg | 43.1 | 50.7 | 58.7 | 67.4 | 75.5 | 79.8 | 90.7 | 115,964 |
| 93kg | 46.3 | 54 | 63.5 | 73.5 | 82 | 87.5 | 99.8 | 96,809 |
| 105kg | 47.6 | 57.2 | 67.7 | 78.7 | 89.1 | 96 | 110.1 | 42,492 |
| 120kg | 48.2 | 58.3 | 71.4 | 82.6 | 94.7 | 103.1 | 116.1 | 15,819 |
| 120+kg | 38 | 52.9 | 69.4 | 87.3 | 104.5 | 111.9 | 133.1 | 5,792 |
Romanian deadlift
Published model — no percentile. FitnessVolt labels these as modeled level tables, ratio-derived from base lifts anchored to OpenPowerlifting, and explicitly says they are modeled estimates rather than direct competition percentiles.
Five published one-repetition-maximum level thresholds by bodyweight. The source's whole-pound rows are retained verbatim and converted to kilograms with 1 lb = 0.45359237 kg; lookup uses the nearest published bodyweight row rather than inventing an interpolation.
This is a ratio-derived training model, not an observed distribution of Romanian-deadlift lifters. RDL range of motion is not federation-standardized, so logged lifts may not be the same movement; no percentile, sample size, or stronger-than share may be inferred.
FitnessVolt Romanian Deadlift Standards by Bodyweight; modeled level table derived from base lifts anchored to OpenPowerlifting. The page's Dataset metadata licenses these modeled rows under ODbL 1.0 (https://opendatacommons.org/licenses/odbl/1-0/). The re-keyed RDL database component is offered under the same terms; see go/platform/rulers/tables/LICENSE-DATA.md. No measured RDL pool or sample size is claimed.
| Bodyweight class | Beginner | Novice | Intermediate | Advanced | Elite | n |
|---|---|---|---|---|---|---|
| 90 lb | 20.87 | 34.47 | 52.62 | 74.39 | 98.88 | — |
| 100 lb | 22.68 | 37.19 | 55.79 | 78.02 | 103.42 | — |
| 110 lb | 24.49 | 39.46 | 58.51 | 81.65 | 107.05 | — |
| 120 lb | 26.31 | 41.28 | 61.23 | 84.82 | 110.68 | — |
| 130 lb | 28.12 | 43.54 | 63.5 | 87.54 | 113.85 | — |
| 140 lb | 29.48 | 45.36 | 66.22 | 90.26 | 117.03 | — |
| 150 lb | 30.84 | 47.17 | 68.49 | 92.99 | 120.2 | — |
| 160 lb | 32.21 | 48.99 | 70.31 | 95.25 | 122.92 | — |
| 170 lb | 33.57 | 50.8 | 72.57 | 97.98 | 125.65 | — |
| 180 lb | 34.93 | 52.62 | 74.39 | 100.24 | 128.37 | — |
| 190 lb | 36.29 | 53.98 | 76.2 | 102.06 | 130.63 | — |
| 200 lb | 37.65 | 55.34 | 78.02 | 104.33 | 132.9 | — |
| 210 lb | 39.01 | 57.15 | 79.83 | 106.14 | 135.17 | — |
| 220 lb | 39.92 | 58.51 | 81.19 | 107.95 | 137.44 | — |
| 230 lb | 41.28 | 59.87 | 83.01 | 110.22 | 139.25 | — |
| 240 lb | 42.18 | 61.23 | 84.37 | 111.58 | 141.52 | — |
| 250 lb | 43.09 | 62.14 | 86.18 | 113.4 | 143.34 | — |
| 260 lb | 44.45 | 63.5 | 87.54 | 115.21 | 145.15 | — |
| Bodyweight class | Beginner | Novice | Intermediate | Advanced | Elite | n |
|---|---|---|---|---|---|---|
| 110 lb | 28.12 | 47.17 | 72.57 | 102.97 | 137.44 | — |
| 120 lb | 33.11 | 53.98 | 80.74 | 112.94 | 148.78 | — |
| 130 lb | 38.56 | 60.33 | 88.45 | 122.02 | 159.21 | — |
| 140 lb | 43.54 | 66.68 | 96.16 | 131.09 | 169.19 | — |
| 150 lb | 48.53 | 72.57 | 103.42 | 139.71 | 179.17 | — |
| 160 lb | 53.52 | 78.47 | 110.68 | 147.87 | 188.24 | — |
| 170 lb | 58.06 | 84.37 | 117.48 | 155.58 | 197.31 | — |
| 180 lb | 63.05 | 90.26 | 124.28 | 163.29 | 205.93 | — |
| 190 lb | 67.59 | 95.71 | 130.63 | 171 | 214.1 | — |
| 200 lb | 72.12 | 101.15 | 136.98 | 178.26 | 222.26 | — |
| 210 lb | 76.66 | 106.59 | 142.88 | 185.07 | 229.97 | — |
| 220 lb | 80.74 | 111.58 | 149.23 | 191.87 | 237.68 | — |
| 230 lb | 85.28 | 116.57 | 154.67 | 198.67 | 244.94 | — |
| 240 lb | 89.36 | 121.56 | 160.57 | 205.02 | 252.2 | — |
| 250 lb | 93.89 | 126.55 | 166.01 | 211.37 | 259.45 | — |
| 260 lb | 97.98 | 131.09 | 171.46 | 217.27 | 265.81 | — |
| 270 lb | 101.6 | 136.08 | 176.9 | 223.17 | 272.61 | — |
| 280 lb | 105.69 | 140.61 | 181.89 | 229.06 | 278.96 | — |
| 290 lb | 109.77 | 144.7 | 187.33 | 234.96 | 285.31 | — |
| 300 lb | 113.4 | 149.23 | 192.32 | 240.4 | 291.66 | — |
| 310 lb | 117.03 | 153.77 | 196.86 | 245.85 | 297.56 | — |
Weighted pull-up
Measured self-reported distribution. FitnessVolt republishes sex-by-bodyweight-class percentile tables from the Symmetric Strength self-reported gym population. The population is measured but self-reported, and is kept separate from verified competition results.
Pull-up one-repetition maximum in kilograms of TOTAL SYSTEM WEIGHT (bodyweight plus external load), read against the lifter's sex-specific bodyweight class. An added-weight-only estimate is not this measure.
FitnessVolt’s source axis is total system weight. We keep both the estimate and the ruler on that axis throughout replay. Every externally loaded non-warmup Hevy pull-up set of 1–10 reps is considered; a true single is its observed system load. For 2–10 reps the exact rule is:
system e1RM = 100 × (bodyweight + added load) ÷ (48.8 + 53.8 × e^(−0.075 × reps))
That is the Wathan estimator used by the underlying Symmetric Strength methodology. We do not pick Hevy’s Epley-winning set, estimate the added plates alone, or mix a set-day value with a later scale reading. When no measured set-day weight exists, the current profile fallback is used; editing it intentionally recomputes those fallback-based estimates.
Self-reported pull-ups are not competition-judged and may differ in grip, range of motion, assistance, kipping, and 1RM estimation. The source requires total system weight, so an external-load-only metric is instrument-incompatible.
Strength standards data by FitnessVolt, sourced from the Symmetric Strength self-reported gym dataset. CC BY 4.0; FitnessVolt requires a visible attribution link on every page or product displaying the data. Pool size: 202,152 samples; the API does not promise they are unique people.
| Bodyweight class | 10th-24th percentile | 25th-49th percentile | 50th-74th percentile | 75th-89th percentile | 90th-94th percentile | 95th-98th percentile | 99th percentile or above | n |
|---|---|---|---|---|---|---|---|---|
| 47kg | 39.5 | 45.9 | 51 | 55.7 | 62.1 | 69.3 | 73.5 | 219 |
| 52kg | 39 | 53.2 | 58.1 | 60.7 | 64.8 | 67.2 | 75.3 | 943 |
| 57kg | 48.8 | 57.8 | 62.3 | 68.4 | 71.8 | 74.1 | 82.2 | 2,113 |
| 63kg | 45.6 | 59.9 | 65.3 | 72 | 78.5 | 82.6 | 90.7 | 2,323 |
| 69kg | 48.8 | 61.9 | 70.3 | 76.1 | 81.5 | 85.3 | 92 | 2,254 |
| 76kg | 51.1 | 69.1 | 76.7 | 84 | 92.5 | 95.2 | 100.4 | 903 |
| 84kg | 47 | 65.5 | 80.8 | 86.3 | 92.2 | 95.3 | 98 | 603 |
| 84+kg | 36 | 50.3 | 72.3 | 92.7 | 103.2 | 103.2 | 145.8 | 211 |
| Bodyweight class | 10th-24th percentile | 25th-49th percentile | 50th-74th percentile | 75th-89th percentile | 90th-94th percentile | 95th-98th percentile | 99th percentile or above | n |
|---|---|---|---|---|---|---|---|---|
| 59kg | 61.4 | 68 | 74.7 | 85 | 99.7 | 105.4 | 121.4 | 4,203 |
| 66kg | 74.1 | 79.8 | 85.9 | 95.3 | 104.5 | 109.5 | 119.1 | 15,501 |
| 74kg | 82 | 88.8 | 96 | 104.9 | 113.7 | 119.7 | 135 | 39,138 |
| 83kg | 90 | 97.5 | 105 | 114.6 | 124.2 | 131.7 | 145.1 | 60,622 |
| 93kg | 96.8 | 105.4 | 114.1 | 124.1 | 135.3 | 141.7 | 157.3 | 47,757 |
| 105kg | 98.7 | 112.3 | 123.7 | 134.4 | 145.5 | 154.2 | 174.5 | 18,425 |
| 120kg | 90.7 | 117.8 | 129.8 | 142.5 | 153.7 | 161.4 | 174.4 | 5,403 |
| 120+kg | 61.1 | 74.2 | 125.6 | 149.8 | 166.6 | 173.4 | 214.3 | 1,534 |
The vendor pipeline, in full
This is the script that fetches the four API tables, preserves only the RDL page’s licensed whole-pound facts, verifies the evidence labels, and writes both the canonical dated source archive and the smaller serving artifact.
#!/usr/bin/env python3
"""Vendor FitnessVolt accessory-lift standards.
This table deliberately keeps two different kinds of evidence separate:
* overhead press and pull-up are measured percentile tables from the
Symmetric Strength self-reported gym population, republished by the
FitnessVolt Strength Standards API under CC BY 4.0;
* Romanian deadlift is FitnessVolt's published MODELLED level table. Its
page says the figures are ratio-derived from base lifts anchored to
OpenPowerlifting and are not direct competition percentiles. We preserve
that statement in the artifact and never attach a percentile grid to RDL.
The API's bodyweight rows are competition-style classes, not points on a
continuous curve. In particular, ``120kg`` and ``120+kg`` (``84kg`` and
``84+kg`` for women) are distinct cohorts. The output therefore retains
the ordinary rows as inclusive upper bounds and the final plus row as an
open-ended class. It never interpolates between cohorts.
Each run writes two artifacts:
* one dated, canonical source archive containing the four complete API JSON
responses plus only the licensed RDL facts extracted from the HTML page;
* the smaller serving table derived from that archive.
The archive is the byte sequence named by every ``archive_sha256`` and
``data_url`` in the serving table. We do not hash mutable endpoints or retain
FitnessVolt's full HTML page and then point readers somewhere else.
Run from anywhere in the repository:
python3 scripts/rulers/vendor_fitnessvolt_strength.py
The fetch is intentionally strict. A changed API version, percentile grid,
source population, HTML table shape, or RDL evidence statement stops the
script instead of silently changing what the ruler means.
"""
from __future__ import annotations
import argparse
import datetime
from decimal import Decimal, ROUND_HALF_UP
import hashlib
import html
from html.parser import HTMLParser
import json
import pathlib
import re
import sys
import urllib.request
REPO = pathlib.Path(__file__).resolve().parents[2]
DEFAULT_OUT = (
REPO
/ "go"
/ "platform"
/ "rulers"
/ "tables"
/ "strength_fitnessvolt.json"
)
SOURCE_ARCHIVE_DIR = DEFAULT_OUT.parent / "sources"
SOURCE_ARCHIVE_ID = "fitnessvolt_strength_source_archive_v1"
SOURCE_ARCHIVE_REPOSITORY_URL = (
"https://raw.githubusercontent.com/JuicyPasta/QuantifiedLife/main/"
"go/platform/rulers/tables/sources/{filename}"
)
API_BASE = "https://fitnessvolt.com/wp-json/fvss/v1"
OPEN_DATA_URL = "https://fitnessvolt.com/strength-standards/strength-data/"
METHODOLOGY_URL = "https://fitnessvolt.com/strength-standards/methodology/"
RDL_URL = "https://fitnessvolt.com/strength-standards/romanian-deadlift/"
RDL_LICENSE_URL = "https://opendatacommons.org/licenses/odbl/1-0/"
CC_BY_LICENSE_URL = "https://creativecommons.org/licenses/by/4.0/"
EXPECTED_API_VERSION = "1.0.0"
EXPECTED_DATA_VERSION = "2026-06-09"
PERCENTILES = [10, 25, 50, 75, 90, 95, 99]
PERCENTILE_KEYS = [f"p{p}" for p in PERCENTILES]
KG_PER_LB_DECIMAL = Decimal("0.45359237")
USER_AGENT = (
"QuantifiedLife ruler snapshot "
"(https://quantifiedlife.io/methods; one manual fetch per refresh)"
)
OBSERVED_LIFTS = {
"overhead_press": {
"api_slug": "overhead_press",
"display_name": "Overhead press",
"formula_id": "strength_fitnessvolt_overhead_press_gym_v1",
"pool_label": "Symmetric Strength self-reported overhead-press samples",
"value_basis": (
"Standing barbell overhead-press one-repetition maximum in kilograms: "
"the external barbell load, read against the lifter's sex-specific "
"bodyweight class."
),
"caveat": (
"Self-reported gym lifts are not competition-judged: technique, range of "
"motion, 1RM estimation, rounding, and selection into a strength-tracking "
"site can all shift this population away from the lifter being compared."
),
},
"weighted_pullup": {
"api_slug": "pullup",
"display_name": "Weighted pull-up",
"formula_id": "strength_fitnessvolt_pullup_system_weight_gym_v1",
"pool_label": "Symmetric Strength self-reported pull-up samples",
"value_basis": (
"Pull-up one-repetition maximum in kilograms of TOTAL SYSTEM WEIGHT "
"(bodyweight plus external load), read against the lifter's sex-specific "
"bodyweight class. An added-weight-only estimate is not this measure."
),
"caveat": (
"Self-reported pull-ups are not competition-judged and may differ in grip, "
"range of motion, assistance, kipping, and 1RM estimation. The source "
"requires total system weight, so an external-load-only metric is "
"instrument-incompatible."
),
},
}
OBSERVED_BAND_NAMES = [
"Below published 10th percentile",
"10th-24th percentile",
"25th-49th percentile",
"50th-74th percentile",
"75th-89th percentile",
"90th-94th percentile",
"95th-98th percentile",
"99th percentile or above",
]
RDL_LEVEL_NAMES = ["Beginner", "Novice", "Intermediate", "Advanced", "Elite"]
RDL_BAND_NAMES = ["Below Beginner", *RDL_LEVEL_NAMES]
def fetch(url: str) -> bytes:
request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(request, timeout=45) as response:
if response.status != 200:
raise SystemExit(f"{url}: HTTP {response.status}")
return response.read()
def canonical_json(value: object) -> bytes:
"""The single byte representation hashed, checked in, and regenerated."""
return (
json.dumps(value, indent=1, sort_keys=True, ensure_ascii=True) + "\n"
).encode("utf-8")
def archive_filename(snapshot_date: str) -> str:
return f"fitnessvolt_strength_{snapshot_date}.json"
def archive_url(snapshot_date: str) -> str:
return SOURCE_ARCHIVE_REPOSITORY_URL.format(
filename=archive_filename(snapshot_date)
)
def archive_source(archive_hash: str, data_url: str) -> dict[str, str]:
"""Fields shared by the collective table and every per-lift source."""
return {"archive_sha256": archive_hash, "data_url": data_url}
def write_immutable_archive(path: pathlib.Path, body: bytes) -> None:
"""Create a dated archive, or prove that an existing one is identical."""
if path.exists() and path.read_bytes() != body:
raise SystemExit(
f"{path}: refusing to replace a dated source archive with different "
"bytes; choose a new --snapshot-date (or inspect with --archive-out)"
)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(body)
def clean_api_kg(value: object) -> float:
"""Collapse PHP's binary-float JSON noise to the published 0.1 kg."""
if not isinstance(value, (int, float)):
raise SystemExit(f"API threshold {value!r} is not numeric")
rounded = float(
Decimal(str(value)).quantize(Decimal("0.1"), rounding=ROUND_HALF_UP)
)
if rounded <= 0:
raise SystemExit(f"API threshold {value!r} is not positive")
return rounded
def api_url(slug: str, sex: str) -> str:
return (
f"{API_BASE}/standards/{slug}?format=table&sex={sex}"
"&source=gym&unit=kg"
)
def parse_bodyweight_class(label: str) -> tuple[float, bool]:
match = re.fullmatch(r"(\d+)(\+)?kg", label)
if match is None:
raise SystemExit(f"unknown FitnessVolt bodyweight class {label!r}")
return float(match.group(1)), match.group(2) is not None
def fetch_observed_lift(key: str, spec: dict[str, str]) -> tuple[dict, list[dict]]:
tables: dict[str, list[dict]] = {}
table_sizes: dict[str, int] = {}
archived_payloads: list[dict] = []
request_urls: list[str] = []
versions: set[str] = set()
data_versions: set[str] = set()
attribution: dict | None = None
for sex in ("female", "male"):
url = api_url(spec["api_slug"], sex)
raw = fetch(url)
try:
payload = json.loads(raw)
except json.JSONDecodeError as exc:
raise SystemExit(f"{url}: invalid JSON: {exc}") from exc
if payload.get("success") is not True:
raise SystemExit(f"{url}: API did not report success")
if payload.get("lift") != spec["api_slug"]:
raise SystemExit(f"{url}: lift = {payload.get('lift')!r}")
if payload.get("sex") != sex or payload.get("unit") != "kg":
raise SystemExit(f"{url}: wrong sex or unit")
if payload.get("source") != "self_reported_gym":
raise SystemExit(f"{url}: source = {payload.get('source')!r}")
if payload.get("format") != "table":
raise SystemExit(f"{url}: format = {payload.get('format')!r}")
request_urls.append(url)
archived_payloads.append(
{
"lift_key": key,
"request_url": url,
"response": payload,
"sex": sex,
}
)
versions.add(payload.get("api_version", ""))
data_versions.add(payload.get("data_version", ""))
if attribution is None:
attribution = payload.get("attribution")
elif payload.get("attribution") != attribution:
raise SystemExit(f"{url}: attribution changed between responses")
total = payload.get("total_sample_size")
if not isinstance(total, int) or total <= 0:
raise SystemExit(f"{url}: invalid total_sample_size {total!r}")
rows = payload.get("weight_classes")
if not isinstance(rows, list) or len(rows) < 2:
raise SystemExit(f"{url}: no bodyweight classes")
all_rows = [row for row in rows if row.get("weight_class") == "all"]
if len(all_rows) != 1 or all_rows[0].get("sample_size") != total:
raise SystemExit(f"{url}: missing or inconsistent all-bodyweights row")
cohorts: list[dict] = []
previous_upper = 0.0
for row in rows:
label = row.get("weight_class")
if label == "all":
continue
if not isinstance(label, str):
raise SystemExit(f"{url}: class with no label")
boundary, plus = parse_bodyweight_class(label)
if (plus and boundary != previous_upper) or (
not plus and boundary <= previous_upper
):
raise SystemExit(
f"{url}: bodyweight classes are not ascending at {label}"
)
n = row.get("sample_size")
if not isinstance(n, int) or n < 30:
raise SystemExit(f"{url}: {label} sample_size = {n!r}")
raw_percentiles = row.get("percentiles")
if not isinstance(raw_percentiles, dict) or list(raw_percentiles) != PERCENTILE_KEYS:
raise SystemExit(
f"{url}: {label} percentile grid = {list(raw_percentiles or {})!r}"
)
values = [clean_api_kg(raw_percentiles[name]) for name in PERCENTILE_KEYS]
if any(values[i] < values[i - 1] for i in range(1, len(values))):
raise SystemExit(f"{url}: {label} thresholds descend: {values}")
cohort = {
"bodyweight_class": label,
"kg": values,
"n": n,
"plus": plus,
}
if plus:
cohort["lower_bound_exclusive_kg"] = boundary
else:
cohort["upper_bodyweight_kg"] = boundary
previous_upper = boundary
cohorts.append(cohort)
if sum(1 for row in cohorts if row["plus"]) != 1 or not cohorts[-1]["plus"]:
raise SystemExit(f"{url}: final bodyweight class is not the sole plus class")
if cohorts[-1]["lower_bound_exclusive_kg"] != previous_upper:
raise SystemExit(f"{url}: plus class does not begin above the preceding class")
table = sex.upper()
tables[table] = cohorts
table_sizes[table] = total
if versions != {EXPECTED_API_VERSION}:
raise SystemExit(f"{key}: API versions = {sorted(versions)!r}")
if data_versions != {EXPECTED_DATA_VERSION}:
raise SystemExit(f"{key}: data versions = {sorted(data_versions)!r}")
if not attribution or attribution.get("required") is not True:
raise SystemExit(f"{key}: API no longer requires visible attribution")
lift = {
"band_names": OBSERVED_BAND_NAMES,
"caveat": spec["caveat"],
"display_name": spec["display_name"],
"evidence": {
"api_version": EXPECTED_API_VERSION,
"data_version": EXPECTED_DATA_VERSION,
"has_distribution": True,
"kind": "OBSERVED_SELF_REPORTED_PERCENTILES",
"request_urls": request_urls,
"statement": (
"FitnessVolt republishes sex-by-bodyweight-class percentile tables "
"from the Symmetric Strength self-reported gym population. The "
"population is measured but self-reported, and is kept separate "
"from verified competition results."
),
},
"formula_id": spec["formula_id"],
"percentile_grid": PERCENTILES,
"pool": {
"label": spec["pool_label"],
"population": "Symmetric Strength self-reported gym sample",
"table_sizes": table_sizes,
"total_size": sum(table_sizes.values()),
},
"source": {
"attribution": (
"Strength standards data by FitnessVolt, sourced from the "
"Symmetric Strength self-reported gym dataset."
),
"license": (
"CC BY 4.0; FitnessVolt requires a visible attribution link on "
"every page or product displaying the data."
),
"name": "FitnessVolt Strength Standards API",
"url": OPEN_DATA_URL,
},
"tables": tables,
"tier_cuts": PERCENTILES,
"value_basis": spec["value_basis"],
}
return lift, archived_payloads
class RDLTableParser(HTMLParser):
"""Extract only the two server-rendered bodyweight table bodies."""
def __init__(self) -> None:
super().__init__(convert_charrefs=True)
self.active_sex: str | None = None
self.in_cell = False
self.cell_text: list[str] = []
self.row: list[int] | None = None
self.rows: dict[str, list[list[int]]] = {"female": [], "male": []}
self.all_text: list[str] = []
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
attributes = dict(attrs)
if tag == "tbody" and attributes.get("id") in {"bw-female", "bw-male"}:
self.active_sex = attributes["id"][3:]
elif self.active_sex is not None and tag == "tr":
self.row = []
elif self.row is not None and tag == "td":
self.in_cell = True
self.cell_text = []
def handle_data(self, data: str) -> None:
self.all_text.append(data)
if self.in_cell:
self.cell_text.append(data)
def handle_endtag(self, tag: str) -> None:
if tag == "td" and self.in_cell:
text = " ".join("".join(self.cell_text).split())
if not re.fullmatch(r"\d+", text):
raise SystemExit(f"RDL table cell is not a whole number: {text!r}")
assert self.row is not None
self.row.append(int(text))
self.in_cell = False
self.cell_text = []
elif tag == "tr" and self.row is not None:
if len(self.row) != 6:
raise SystemExit(f"RDL bodyweight row has {len(self.row)} cells: {self.row}")
assert self.active_sex is not None
self.rows[self.active_sex].append(self.row)
self.row = None
elif tag == "tbody" and self.active_sex is not None:
self.active_sex = None
def lb_to_kg(value: int) -> float:
return float(
(Decimal(value) * KG_PER_LB_DECIMAL).quantize(
Decimal("0.01"), rounding=ROUND_HALF_UP
)
)
def fetch_rdl() -> tuple[dict, dict, str]:
raw = fetch(RDL_URL)
page = raw.decode("utf-8")
parser = RDLTableParser()
parser.feed(page)
text = " ".join(html.unescape(" ".join(parser.all_text)).split())
required_claims = [
"modeled level tables, ratio-derived from base lifts",
"modeled estimates, not direct competition percentiles",
]
for claim in required_claims:
if claim not in text:
raise SystemExit(
f"{RDL_URL}: evidence statement {claim!r} disappeared; "
"do not infer what the table means"
)
if RDL_LICENSE_URL not in page:
raise SystemExit(
f"{RDL_URL}: Dataset license {RDL_LICENSE_URL!r} disappeared; "
"do not vendor the modeled rows without a data license"
)
modified_match = re.search(r'"dateModified":"([^"T]+)(?:T[^"]*)?"', page)
if modified_match is None:
raise SystemExit(f"{RDL_URL}: no dateModified in page metadata")
page_version = modified_match.group(1)
tables: dict[str, list[dict]] = {}
archived_tables: dict[str, list[dict]] = {}
for sex in ("female", "male"):
rows = parser.rows[sex]
if len(rows) < 10:
raise SystemExit(f"{RDL_URL}: only {len(rows)} {sex} bodyweight rows")
previous_bw = 0
model_rows: list[dict] = []
archived_rows: list[dict] = []
for row in rows:
bodyweight_lb, *values_lb = row
if bodyweight_lb <= previous_bw:
raise SystemExit(f"{RDL_URL}: {sex} bodyweights are not ascending")
if any(values_lb[i] <= values_lb[i - 1] for i in range(1, len(values_lb))):
raise SystemExit(f"{RDL_URL}: {sex}/{bodyweight_lb} thresholds do not ascend")
model_rows.append(
{
"bodyweight_kg": lb_to_kg(bodyweight_lb),
"bodyweight_lb": bodyweight_lb,
"kg": [lb_to_kg(value) for value in values_lb],
"lb": values_lb,
}
)
archived_rows.append(
{
"bodyweight_lb": bodyweight_lb,
"level_thresholds_lb": values_lb,
}
)
previous_bw = bodyweight_lb
tables[sex.upper()] = model_rows
archived_tables[sex.upper()] = archived_rows
evidence_statement = (
"FitnessVolt labels these as modeled level tables, ratio-derived "
"from base lifts anchored to OpenPowerlifting, and explicitly says "
"they are modeled estimates rather than direct competition "
"percentiles."
)
lift = {
"band_names": RDL_BAND_NAMES,
"caveat": (
"This is a ratio-derived training model, not an observed distribution "
"of Romanian-deadlift lifters. RDL range of motion is not federation-"
"standardized, so logged lifts may not be the same movement; no "
"percentile, sample size, or stronger-than share may be inferred."
),
"display_name": "Romanian deadlift",
"evidence": {
"has_distribution": False,
"kind": "MODELLED",
"page_last_modified": page_version,
"request_urls": [RDL_URL],
"statement": evidence_statement,
},
"formula_id": "strength_fitnessvolt_rdl_model_v1",
"level_names": RDL_LEVEL_NAMES,
"pool": {
"label": "FitnessVolt ratio-derived model; no measured RDL cohort",
"population": "No direct Romanian-deadlift population",
"table_sizes": {},
"total_size": 0,
},
"source": {
"attribution": (
"FitnessVolt Romanian Deadlift Standards by Bodyweight; modeled "
"level table derived from base lifts anchored to OpenPowerlifting."
),
"license": (
"The page's Dataset metadata licenses these modeled rows under "
"ODbL 1.0 (https://opendatacommons.org/licenses/odbl/1-0/). The "
"re-keyed RDL database component is offered under the same terms; "
"see go/platform/rulers/tables/LICENSE-DATA.md."
),
"name": "FitnessVolt Romanian Deadlift Standards",
"url": RDL_URL,
},
"tables": tables,
"value_basis": (
"Five published one-repetition-maximum level thresholds by bodyweight. "
"The source's whole-pound rows are retained verbatim and converted to "
"kilograms with 1 lb = 0.45359237 kg; lookup uses the nearest published "
"bodyweight row rather than inventing an interpolation."
),
}
archived_source = {
"columns": [
"bodyweight_lb",
*[f"{name.lower()}_lb" for name in RDL_LEVEL_NAMES],
],
"evidence": {
"claims_verified_on_source_page": required_claims,
"kind": "MODELLED",
"statement": evidence_statement,
},
"extraction": (
"Only the server-rendered bodyweight table facts were retained; "
"the source page HTML is not archived."
),
"license": {
"evidence": "Dataset metadata on the source page",
"name": "Open Data Commons Open Database License 1.0",
"url": RDL_LICENSE_URL,
},
"page_last_modified": page_version,
"request_url": RDL_URL,
"tables": archived_tables,
}
return lift, archived_source, page_version
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--snapshot-date",
default=datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d"),
help="date to stamp on the fetched snapshot (default: today, UTC)",
)
parser.add_argument(
"--out",
type=pathlib.Path,
default=DEFAULT_OUT,
help=f"output path (default: {DEFAULT_OUT})",
)
parser.add_argument(
"--archive-out",
type=pathlib.Path,
help=(
"source-archive output path (default: "
f"{SOURCE_ARCHIVE_DIR}/fitnessvolt_strength_<snapshot-date>.json)"
),
)
args = parser.parse_args()
try:
datetime.date.fromisoformat(args.snapshot_date)
except ValueError as exc:
raise SystemExit(f"invalid --snapshot-date {args.snapshot_date!r}: {exc}") from exc
lifts: dict[str, dict] = {}
api_payloads: list[dict] = []
for key, spec in OBSERVED_LIFTS.items():
lift, archived_payloads = fetch_observed_lift(key, spec)
lifts[key] = lift
api_payloads.extend(archived_payloads)
sizes = lift["pool"]["table_sizes"]
print(
f"{key}: FEMALE n={sizes['FEMALE']:,}; MALE n={sizes['MALE']:,}",
file=sys.stderr,
)
rdl, archived_rdl, rdl_page_version = fetch_rdl()
lifts["romanian_deadlift"] = rdl
print(
"romanian_deadlift: "
f"FEMALE rows={len(rdl['tables']['FEMALE'])}; "
f"MALE rows={len(rdl['tables']['MALE'])} (MODELLED, no distribution)",
file=sys.stderr,
)
source_archive = {
"api": {
"license": {
"attribution_requirement": (
"FitnessVolt requires a visible attribution link wherever "
"the API data is displayed."
),
"name": "Creative Commons Attribution 4.0 International",
"source_url": OPEN_DATA_URL,
"url": CC_BY_LICENSE_URL,
},
"payloads": api_payloads,
},
"id": SOURCE_ARCHIVE_ID,
"license_notice": "go/platform/rulers/tables/LICENSE-DATA.md",
"romanian_deadlift": archived_rdl,
"snapshot_date": args.snapshot_date,
"version": 1,
}
source_archive_bytes = canonical_json(source_archive)
source_archive_hash = hashlib.sha256(source_archive_bytes).hexdigest()
source_archive_filename = archive_filename(args.snapshot_date)
source_archive_url = archive_url(args.snapshot_date)
source_archive_path = (
f"go/platform/rulers/tables/sources/{source_archive_filename}"
)
source_pointer = archive_source(source_archive_hash, source_archive_url)
for lift in lifts.values():
lift["source"].update(source_pointer)
table_source = {
**source_pointer,
"attribution": (
"FitnessVolt Strength Standards. OHP and pull-up data sourced from "
"the Symmetric Strength self-reported gym dataset; Romanian deadlift "
"is FitnessVolt's separately labeled modeled level table."
),
"license": (
"FitnessVolt's public API data is CC BY 4.0 with a required visible "
"attribution link. The modeled RDL rows are ODbL 1.0; see each "
"lift's source and go/platform/rulers/tables/LICENSE-DATA.md."
),
"name": "FitnessVolt Strength Standards",
"url": OPEN_DATA_URL,
}
table = {
"id": "strength_fitnessvolt_accessory_v1",
"version": 1,
"snapshot_date": args.snapshot_date,
"staleness": {
"policy": "DRIFTS",
"max_age_days": 180,
"rationale": (
"FitnessVolt republishes the API cohorts and the web model in place, "
"so a later fetch can change both the self-reported percentile rows "
"and the modeled RDL thresholds. Refreshing is mechanical: rerun "
"the vendor script, review the provenance and evidence labels, and "
"commit the new dated snapshot."
),
},
"source": table_source,
"pipeline": {
"api_version": EXPECTED_API_VERSION,
"conversion": "kg = lb * 0.45359237, rounded half-up to 0.01 kg",
"data_version": EXPECTED_DATA_VERSION,
"methodology_url": METHODOLOGY_URL,
"note": (
"OHP and pull-up preserve discrete API bodyweight classes and exact "
"published percentile points; the aggregate all-bodyweights rows are "
"excluded. RDL preserves the page's whole-pound model table and has "
"no percentile grid."
),
"rdl_page_last_modified": rdl_page_version,
"script": "scripts/rulers/vendor_fitnessvolt_strength.py",
"source_archive": source_archive_path,
},
"lifts": lifts,
}
archive_out = args.archive_out or SOURCE_ARCHIVE_DIR / source_archive_filename
write_immutable_archive(archive_out, source_archive_bytes)
print(f"wrote {archive_out} (sha256 {source_archive_hash})")
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_bytes(canonical_json(table))
print(f"wrote {args.out}")
if __name__ == "__main__":
main()
DOTS — the total, as detail only
DOTS normalises a powerlifting total for bodyweight. We show it one tap down on the strength sheet and never as a headline, because our version adds three estimates made on three different days from three different rep ranges — that is not a meet total and must not be compared with one as if it were.
The polynomial is OpenPowerlifting’s, not ours:
DOTS = total_kg × 500 ÷ (A·bw⁴ + B·bw³ + C·bw² + D·bw + E)
A, B, C, D, E —
female: -0.0000010706, 0.0005158568, -0.1126655495, 13.6175032, -57.96288
male : -0.000001093, 0.0007391293, -0.1918759221, 24.0900756, -307.75076Bodyweight is clamped to the range the polynomial was fitted over (40–210 kg for the male coefficients, 40–150 kg for the female ones), which is the formula’s own rule rather than a smoothing of ours.
Female tables, 131,321 lifters with a full-power total: Beginner at 199.53, Novice at 250.59, Intermediate at 305.65, Advanced at 364.26, Elite at 424.97.
Male tables, 281,282 lifters with a full-power total: Beginner at 232.58, Novice at 296.47, Intermediate at 351.75, Advanced at 403.71, Elite at 454.29.
Chess — their rating, and 100-point clubs
What we show. Your rating in each pool — bullet, blitz, rapid — exactly as the site issues it, plus the highest 100-point club you have ever reached, with the date you reached it. Six pools in all: three on Lichess and three on Chess.com, which are separate ladders on separate scales and are never merged.
We do not rate you.There is no chess Elo of ours anywhere. Lichess runs Glicko-2 and Chess.com runs Glicko; both see every game every player plays and we see one player’s, so their number is better than anything we could compute and we pass it through untouched — no smoothing, no weekly average, no conversion between the two sites.
A club is absolute, dated and never taken away. “1600 club — member since 3 November 2025” means your rating touched 1600 on that day. Losing rating afterwards does not remove it. There is no demotion event anywhere on this platform, and for a club there is not even a drop to record; the rating chart above it tells the truth about now.
The rating lives one tap down. Chess ratings arrive pre-loaded with rating anxiety, so the surfaces that lead are games played and how you have been going. A losing streak must not become a reason to avoid opening the app.
Lichess — the published distribution
Lichess publishes a weekly histogram of every established rating in each pool, and uses it itself to tell members they are “better than X%”. We read the same table with the same rule: you count as above a 25-point bucket when your rating exceeds that bucket’s midpoint. Buckets run 400–2800, which is Lichess’s own axis, not ours.
Snapshot . Lichess publishes this histogram on its own public statistics page; its database exports are released under CC0 and its site code under AGPL-3.0. We reproduce the counts with attribution and a link back.
Weekly rating distribution published by Lichess (lichess.org/stat/rating/distribution).
| Rating | Bullet | Blitz | Rapid |
|---|---|---|---|
| 800 | 3% | 3% | 6% |
| 1000 | 9% | 11% | 16% |
| 1200 | 22% | 24% | 32% |
| 1400 | 36% | 41% | 49% |
| 1500 | 44% | 50% | 58% |
| 1600 | 51% | 60% | 68% |
| 1800 | 67% | 77% | 83% |
| 2000 | 80% | 90% | 94% |
| 2200 | 91% | 97% | 98% |
| 2400 | 97% | 99% | 100% |
Percentiles are reported to whole percents on the sheet, against 336,259 bullet, 663,877 blitz, 472,854 rapid rated players. The pool is people who play enough online chess to hold an established rating — not chess players in general, and not the general population. Read the same page we snapshotted.
Chess.com — no distribution, and we say so
Chess.com publishes no rating distribution. It removed the percentile page it used to have and offers no endpoint that replaces it, so there is no honest percentile for a Chess.com rating and we show none. We will not estimate one from our own users, and we will not borrow Lichess’s — the two sites rate different people on different scales, and a number read off the wrong pool is worse than no number. Your rating and your club are Chess.com’s own; the sheet says exactly this where a Lichess sheet shows a percentile.
The snapshot, in full
This is the script that fetched the histogram above, verbatim. It runs by hand and the result is committed, because a reference table that can change while you are reading the page citing it is not a citation.
#!/usr/bin/env python3
"""Vendor Lichess's published weekly rating distribution (ADR 0134/0147).
Lichess publishes, for every leaderboardable pool, a histogram of how many
established players sit in each 25-point rating group:
https://lichess.org/stat/rating/distribution/blitz
That histogram is the only external, absolute, instrument-compatible ruler
that exists for a chess rating — it is Lichess's own count of Lichess's own
players, and it is the same number Lichess itself uses to tell a member they
are "better than 47.3% of blitz players". We reproduce it rather than
recompute anything, and we reproduce it as a DATED SNAPSHOT compiled into the
binary rather than fetching it per request: a reference table that can change
under the product is not a citation (ADR 0144).
Run it, commit the JSON it writes, re-run scripts/gen-chess-ruler.py so the
public /methods page shows the same numbers, and the snapshot date every
surface renders moves with the commit.
python3 scripts/rulers/fetch_lichess_distribution.py
The histogram is embedded in the page as JSON rather than served by an API:
there is no documented /api/rating/distribution endpoint and the obvious
guess 404s (checked 2026-08-18), so the page is the source. The extraction is
deliberately narrow and asserts the shape it expects, because a scrape that
degrades quietly is worse than one that fails.
The bucket axis is Lichess's, not ours. lila builds the list as
(Glicko.minRating.value to 2800 by percentileOf.group)
with `Glicko.minRating = 400` and `percentileOf.group = 25`
(modules/perfStat/src/main/PerfStatApi.scala,
modules/perfStat/src/main/package.scala), so bucket i covers ratings
[400 + 25i, 400 + 25i + 25) and there are 97 of them. The count is asserted
below: if Lichess moves its axis, this script stops rather than silently
re-labelling somebody else's numbers.
"""
import argparse
import datetime
import json
import os
import re
import sys
import urllib.request
REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
OUT = os.path.join(REPO, "go/platform/rulers/tables/chess_lichess_distribution.json")
PAGE = "https://lichess.org/stat/rating/distribution/{perf}"
# Lichess's own axis. See the module docstring for where each comes from.
MIN_RATING = 400
GROUP = 25
MAX_RATING = 2800
BUCKETS = (MAX_RATING - MIN_RATING) // GROUP + 1 # 97
# The three pools a standing is offered in. Lichess rates far more than
# these, but a category nobody plays is a row of "no signal" on a surface,
# and these are the three that the two platforms both run.
POOLS = {
"bullet": "Lichess bullet players with an established rating",
"blitz": "Lichess blitz players with an established rating",
"rapid": "Lichess rapid players with an established rating",
}
FREQ = re.compile(r'\{"freq":\s*\[[^\]]*\][^{}]*\}')
def fetch(perf):
url = PAGE.format(perf=perf)
request = urllib.request.Request(
url,
headers={
# Lichess asks that bots identify themselves. This runs by hand,
# three times, when a human regenerates the table.
"User-Agent": "QuantifiedLife ruler snapshot (https://quantifiedlife.io/methods)",
},
)
with urllib.request.urlopen(request, timeout=30) as response:
if response.status != 200:
raise SystemExit(f"{url}: HTTP {response.status}")
html = response.read().decode("utf-8")
match = FREQ.search(html)
if match is None:
raise SystemExit(
f"{url}: no embedded rating histogram found. Lichess has changed the "
"page shape; do not guess at the numbers, read the page and fix this script."
)
freq = json.loads(match.group(0))["freq"]
if len(freq) != BUCKETS:
raise SystemExit(
f"{url}: {len(freq)} buckets, expected {BUCKETS} "
f"({MIN_RATING}..{MAX_RATING} by {GROUP}). Lichess has moved its axis; "
"re-read lila's PerfStatApi before touching the constants above."
)
if any(not isinstance(n, int) or n < 0 for n in freq):
raise SystemExit(f"{url}: histogram holds a non-count")
if sum(freq) <= 0:
raise SystemExit(f"{url}: empty histogram")
return freq
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--snapshot-date",
default=datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d"),
help="the date this snapshot is stamped with (default: today, UTC)",
)
args = parser.parse_args()
pools = {}
for perf, label in POOLS.items():
freq = fetch(perf)
pools[perf] = {
"pool_label": label,
"n": sum(freq),
"freq": freq,
}
print(f"{perf}: {sum(freq):,} rated players across {BUCKETS} groups", file=sys.stderr)
table = {
"id": "chess_lichess_distribution_v1",
"version": 1,
"snapshot_date": args.snapshot_date,
# How this ruler ages (ADR 0154). Lichess publishes only the current
# histogram, so the table DRIFTS from the day after it is scraped.
"staleness": {
"policy": "DRIFTS",
"max_age_days": 90,
"rationale": (
"Lichess recomputes this histogram weekly and publishes only the current one, "
"so our copy starts diverging the day after it is scraped. Re-fetching costs "
"one page load, which is why the threshold here is tighter than the strength "
"ruler's."
),
},
"source": {
"name": "Lichess",
"url": "https://lichess.org",
"data_url": "https://lichess.org/stat/rating/distribution/blitz",
"license": "Lichess publishes this histogram on its own public statistics page; its "
"database exports are released under CC0 and its site code under AGPL-3.0. "
"We reproduce the counts with attribution and a link back.",
"attribution": "Weekly rating distribution published by Lichess "
"(lichess.org/stat/rating/distribution).",
},
"pipeline": {
"script": "scripts/rulers/fetch_lichess_distribution.py",
"min_rating": MIN_RATING,
"group": GROUP,
"max_rating": MAX_RATING,
"note": "Lichess's own histogram, copied verbatim. Nothing is fitted, smoothed, "
"interpolated or pooled across speeds. Bucket i counts established players "
f"rated [{MIN_RATING} + {GROUP}i, {MIN_RATING} + {GROUP}i + {GROUP}). The "
"percentile read off it is Lichess's own rule: a player counts as above a "
"bucket when their rating exceeds that bucket's midpoint.",
},
"pools": pools,
}
with open(OUT, "w", encoding="utf-8") as handle:
json.dump(table, handle, indent=1, sort_keys=True)
handle.write("\n")
print(f"wrote {OUT}")
if __name__ == "__main__":
main()
League — Riot's ladder, verbatim
What we show.One League standing: the highest current rank across ranked solo, ranked flex and every configured Riot account. The band is Riot’s tier and division — “Gold II”, or the bare apex name “Master” — and the number beside it is Riot’s whole in-division LP from the lptag. The metric’s numeric value is a continuous ladder encoding for charts; it is not display LP and never appears on the receipt as if it were.
No rating of ours.We do not calculate MMR, smooth Riot’s rank, or rename their tiers. A demotion changes the current row without ceremony and is not an event. A newly higher all-time named peak is a promotion: the weekly letter dates it, and the receipt keeps the full-history line “Peak: Gold II — 12 May 2026” permanently even if the current row later moves down.
Best of, never a blend.Each (queue, account) pair is its own entity with its own history, and the standing is the one whole entity currently standing highest — its tier, its division, its LP, its account name on the receipt. Ties break by LP, then queue and account name in fixed order, so the answer is deterministic. Accounts are never pooled: a peak from one account cannot attach to another account’s current rank, and the receipt lists every entity’s own current rank so “best” is inspectable rather than asserted.
Riot serves current ladder snapshots through LEAGUE-V4, not a history endpoint. QuantifiedLife observes those snapshots daily, replays the history it has actually seen, freezes gaps, and marks a standing stale after 60 days without a valid observation for the leading entity.
League — where a tier sits in the ladder
The population context, and whose it is.Riot does not publish a ranked distribution alongside the ladder, so the “above N%” line on a League receipt cites League of Graphs’ rank distribution — their published shares of recently active ranked players, computed from the Riot API — frozen at a dated capture: patch 16.6, all regions, archived . It is not Riot’s official figure, and the pool is players who queued recently, not everyone with an account.
Which sums are ours.The source publishes each rung’s share; the cumulative “share below your rung” is our addition over their published numbers, disclosed as ours on every receipt that prints one. Solo/duo is published per division, so a Gold II reader gets the share below Gold II exactly; flex is published per tier only, so a flex receipt says “at least” and refuses to resolve the division position the source does not publish. We still derive no MMR, scrape no league tables — this is a hand transcription of one archived page, dated and attributed — and estimate nothing from our own users.
| Tier | Share |
|---|---|
| Iron | 2.39% |
| Bronze | 16.9% |
| Silver | 23.5% |
| Gold | 24.4% |
| Platinum | 17.6% |
| Emerald | 10.1% |
| Diamond | 3.77% |
| Master | 1.1% |
| Grandmaster | 0.082% |
| Challenger | 0.034% |
| Tier | Share |
|---|---|
| Iron | 2.2% |
| Bronze | 13% |
| Silver | 26% |
| Gold | 22% |
| Platinum | 18% |
| Emerald | 13% |
| Diamond | 4.3% |
| Master | 0.48% |
| Grandmaster | 0.057% |
| Challenger | 0.03% |
Rank distribution from League of Graphs (leagueofgraphs.com), all regions, as archived 26 March 2026 (patch 16.6). League of Legends and Riot Games are trademarks of Riot Games, Inc.; League of Graphs is not endorsed by Riot Games.
The transcription, in full
The source’s terms rule out automated pulls, so this table is a hand transcription of one archived page, and the script below is what wrote it — with the checks it refuses to write without. Solo division shares ride in it too; the tier table above is their sums.
#!/usr/bin/env python3
"""Vendor League of Graphs' ranked-tier distribution as a frozen snapshot.
A ONE-OFF, OFFLINE re-keying on vendor_friend_vo2max.py's pattern: it writes a
versioned JSON table that is checked in, cited, and served verbatim, published
on /methods so anybody can re-run it and get the same table.
Source
League of Graphs, "Rank distribution", all regions, as captured by the
Internet Archive on 2026-03-26 (patch 16.6) — the newest archived capture;
the live page 403s automated fetches and its terms prohibit automated
queries, so the pipeline here is a HAND transcription of one dated
archived page, which is neither scraping nor a scheduled pull:
http://web.archive.org/web/20260326144525/https://www.leagueofgraphs.com/rankings/rank-distribution
League of Graphs computes its statistics from the Riot Games API. This
table is their published distribution reproduced as a small dated factual
excerpt with attribution; it is not Riot's official figure, and nothing
here derives an MMR or a rating (the ruler rule, ADR 0134).
Riot's 2026-03-02 /dev post ("MMR-to-Rank Distribution",
https://www.leagueoflegends.com/en-us/news/dev/dev-mmr-to-rank-distribution/)
is the citation for why any pre-March-2026 distribution is stale: the
ladder was recalibrated that week. This snapshot post-dates it.
What we take, and in what form
Ranked SOLO/DUO: the per-division "Rank %" shares (I/II/III/IV per tier)
plus the apex-tier shares. Ranked FLEX: the per-tier shares — League of
Graphs publishes flex at tier grain only. Shares are percentages of the
recently-active ranked population as League of Graphs counts it; the
page's own rounding makes each queue sum to roughly, not exactly, 100.
Any cumulative ("top X%" / "above N%") number a surface prints is OUR
addition over these published shares, and every surface that prints one
says so — the ADR 0155 disclosure rule for arithmetic of ours over
numbers of theirs.
VERIFICATION
Shares below were transcribed by hand from the archived page and are
checked before writing: every division share is positive, division sums
match the transcription, and each queue's total lands inside [97, 103]
(the page's own rounding tolerance).
"""
import json
import pathlib
OUT = pathlib.Path(__file__).resolve().parents[2] / (
"go/platform/rulers/tables/lol_leagueofgraphs_distribution.json"
)
# Ranked solo/duo, all regions, 2026-03-26 (patch 16.6): tier -> division
# shares in LADDER order (IV lowest first), apex tiers as a bare share.
SOLO = [
("iron", {"iv": 0.15, "iii": 0.23, "ii": 0.61, "i": 1.4}),
("bronze", {"iv": 4.6, "iii": 3.9, "ii": 4.3, "i": 4.1}),
("silver", {"iv": 6.9, "iii": 6.0, "ii": 5.9, "i": 4.7}),
("gold", {"iv": 8.6, "iii": 6.2, "ii": 5.6, "i": 4.0}),
("platinum", {"iv": 7.1, "iii": 4.5, "ii": 3.6, "i": 2.4}),
("emerald", {"iv": 4.4, "iii": 2.4, "ii": 1.8, "i": 1.5}),
("diamond", {"iv": 1.8, "iii": 0.78, "ii": 0.57, "i": 0.62}),
("master", 1.1),
("grandmaster", 0.082),
("challenger", 0.034),
]
# Ranked flex, all regions, same capture: tier shares only.
FLEX = [
("iron", 2.2),
("bronze", 13.0),
("silver", 26.0),
("gold", 22.0),
("platinum", 18.0),
("emerald", 13.0),
("diamond", 4.3),
("master", 0.48),
("grandmaster", 0.057),
("challenger", 0.03),
]
DIVISION_ORDER = ["iv", "iii", "ii", "i"]
def queue(rows: list, label: str, granularity: str) -> dict:
tiers = []
total = 0.0
for tier, shares in rows:
if isinstance(shares, dict):
divisions = []
for division in DIVISION_ORDER:
share = shares[division]
assert share > 0, f"{tier} {division} share must be positive"
divisions.append({"division": division, "share": share})
total += share
tiers.append(
{
"tier": tier,
"share": round(sum(d["share"] for d in divisions), 3),
"divisions": divisions,
}
)
else:
assert shares > 0, f"{tier} share must be positive"
tiers.append({"tier": tier, "share": shares})
total += shares
assert 97 <= total <= 103, f"{label} shares sum to {total:.2f}, outside [97, 103]"
return {"pool_label": label, "granularity": granularity, "tiers": tiers}
def main() -> None:
table = {
"id": "lol_leagueofgraphs_distribution_v1",
"version": 1,
"snapshot_date": "2026-03-26",
"source": {
"name": "League of Graphs rank distribution",
"url": "https://www.leagueofgraphs.com/rankings/rank-distribution",
"data_url": "http://web.archive.org/web/20260326144525/https://www.leagueofgraphs.com/rankings/rank-distribution",
"license": (
"League of Graphs states no reuse license; this is a small "
"dated factual excerpt reproduced with attribution, "
"transcribed by hand from an archived capture, never an "
"automated query. League of Graphs computes its statistics "
"from the Riot Games API. This is not Riot's official "
"distribution, and no MMR or rating is derived from it."
),
"attribution": (
"Rank distribution from League of Graphs "
"(leagueofgraphs.com), all regions, as archived 26 March 2026 "
"(patch 16.6). League of Legends and Riot Games are "
"trademarks of Riot Games, Inc.; League of Graphs is not "
"endorsed by Riot Games."
),
},
"staleness": {
"policy": "DRIFTS",
"max_age_days": 210,
"rationale": (
"The live distribution refreshes continuously and moves with "
"season resets and Riot recalibrations (the 2026-03-02 "
"MMR-to-rank recalibration reshaped Iron in a week, which is "
"also why nothing older than March 2026 may replace this "
"table). 210 days spans a ranked split with slack; the fix is "
"re-transcribing a newer dated capture by hand — the source's "
"terms rule out an automated pull."
),
},
"pipeline": {
"script": "scripts/rulers/vendor_leagueofgraphs_lol.py",
"capture": "Internet Archive, 2026-03-26 14:45:25 UTC",
"patch": "16.6",
"regions": "all",
"note": (
"Hand transcription of one dated archived page. Solo/duo is "
"published per division; flex per tier only. Cumulative "
"'above N%' figures a surface prints are our sums over these "
"published shares, disclosed as ours."
),
},
"queues": {
"ranked_solo": queue(
SOLO,
"recently active ranked solo/duo players across all regions",
"division",
),
"ranked_flex": queue(
FLEX,
"recently active ranked flex players across all regions",
"tier",
),
},
}
OUT.write_text(json.dumps(table, indent=1, sort_keys=True) + "\n")
print(f"wrote {OUT}")
if __name__ == "__main__":
main()
Cardio — VO₂max against the FRIEND registry
What we show.A band for your VO₂max — how much oxygen your body can use at full effort, in millilitres per kilogram of you per minute — read against people of the same published column and age decade. The number we place is the median of your last 28 days, not your best day: a VO₂max is a level, and a wrist estimate is noisy enough that taking its maximum would find your luckiest reading and call it fitness.
The gap we cannot close, and will not dress up. Your number is an estimate, worked out by a watch from your heart rate against your pace on an outdoor walk or run. Every number in the table below was measured, off a mask, on a treadmill, by a laboratory taking someone to exhaustion. Wrist estimates have run several mL/kg/min below a laboratory test in validation work, so expect this placement to read low. That gap is a bias, not a wobble — which is exactly why you will never see a plus-or-minus next to it here or in the app. An interval would dress a known, one-directional offset up as random noise, and that is the kind of false precision this whole page exists to refuse.
The pool, and its skew. apparently healthy US adults tested to exhaustion on a treadmill with a metabolic cart. Most of them turned up because somebody wanted their fitness measured, so this is not the general population either. A percentile here is a statement about that pool.
The table
The treadmill reference standards from the FRIEND registry, published and covering registry data through 2021-03-31: 16,278 maximal tests from 34 laboratories across the United States, with a maximal-effort criterion of peak RER >= 1.0. The article is open access under CC BY-NC-ND 4.0. The values below are facts re-keyed into our own structure, not a reproduction of the publisher's typeset table.
Kaminsky LA, Arena R, Myers J, Peterman JE, Bonikowske AR, Harber MP, Medina Inojosa JR, Lavie CJ, Squires RW. Updated Reference Standards for Cardiorespiratory Fitness Measured with Cardiopulmonary Exercise Testing: Data from the Fitness Registry and the Importance of Exercise National Database (FRIEND). Mayo Clinic Proceedings. 2022;97(2):285-293.
We take the treadmill column and not the cycle-ergometer one the same paper publishes, because the two sit about 4.5 mL/kg/min apart and your watch estimates from walking and running. Reading a wrist estimate against a cycle table would place you on an instrument nobody put you on. Read the paper — it is open access — and check every number below against its Table 3 (percentiles), Table 1 (sample sizes).
| Age | 10th | 20th | 30th | 40th | 50th | 60th | 70th | 80th | 90th | n |
|---|---|---|---|---|---|---|---|---|---|---|
| 20-29 | 28.6 | 35.2 | 40.0 | 43.6 | 46.5 | 49.0 | 51.9 | 54.5 | 58.6 | 1,278 |
| 30-39 | 24.9 | 29.8 | 33.5 | 37.0 | 39.7 | 43.4 | 46.4 | 50.0 | 55.5 | 1,473 |
| 40-49 | 22.1 | 26.7 | 29.7 | 32.4 | 35.3 | 37.9 | 40.9 | 45.2 | 50.8 | 2,119 |
| 50-59 | 18.6 | 22.2 | 24.5 | 26.9 | 29.2 | 31.8 | 34.3 | 38.3 | 43.4 | 2,082 |
| 60-69 | 15.8 | 18.5 | 20.7 | 22.8 | 24.6 | 26.5 | 28.7 | 32.0 | 37.1 | 1,663 |
| 70-79 | 13.6 | 15.9 | 17.3 | 19.1 | 20.6 | 22.2 | 23.8 | 25.9 | 29.4 | 776 |
| 80-89 | 12.9 | 14.8 | 16.1 | 16.6 | 17.6 | 18.4 | 20.0 | 21.4 | 22.8 | 173 |
| Age | 10th | 20th | 30th | 40th | 50th | 60th | 70th | 80th | 90th | n |
|---|---|---|---|---|---|---|---|---|---|---|
| 20-29 | 22.5 | 27.2 | 30.8 | 34.0 | 36.6 | 39.0 | 41.8 | 44.8 | 49.0 | 1,142 |
| 30-39 | 18.6 | 21.9 | 24.2 | 26.4 | 28.3 | 31.0 | 33.6 | 37.0 | 42.1 | 1,043 |
| 40-49 | 17.2 | 19.7 | 21.8 | 23.9 | 25.7 | 27.7 | 30.0 | 33.0 | 37.8 | 1,372 |
| 50-59 | 16.5 | 18.5 | 20.1 | 21.5 | 22.9 | 24.6 | 26.3 | 28.4 | 32.4 | 1,457 |
| 60-69 | 13.4 | 15.4 | 17.0 | 18.3 | 19.6 | 20.9 | 22.4 | 24.3 | 27.3 | 1,045 |
| 70-79 | 12.3 | 14.0 | 15.2 | 16.2 | 17.2 | 18.3 | 19.6 | 20.8 | 22.8 | 549 |
| 80-89 | 11.4 | 12.6 | 13.7 | 14.7 | 15.4 | 16.0 | 17.3 | 18.4 | 20.8 | 106 |
What sits between the printed numbers
The paper prints seven age decades and nine percentiles, and we interpolate straight lines between them — nothing smoother. Age: linear between decade midpoints, clamped flat outside 25-85. Each decade’s column is pinned at its midpoint (25, 35, … 85) and read linearly between midpoints, so your band cannot change on your birthday. Value: linear between published decile anchors; saturates at the 10th and 90th— beyond them we report “below the 10th” or “above the 90th” rather than inventing a 3rd or a 97th the paper never published.
We deliberately do not fit a curve through these anchors. A smooth model would look better and would be ours— it would turn somebody else’s cited facts into our own estimate while keeping their citation, which is the wrong direction for a page like this one.
The cut points, and whose they are
The band names are ours. The percentiles they sit at are 10, 30, 50, 70 and 90, read off the table above. So “Excellent” means “at or above the 90th percentile of apparently healthy US adults tested to exhaustion on a treadmill with a metabolic cartin your age decade” and nothing else. No registry, no professional body and no watch manufacturer issues these words — we chose them, and everything below the 10th percentile of that pool is the first band, Low.
The re-keying, in full
There is no archive to reduce here — FRIEND publishes a paper, not a dataset — so the numbers above were typed in by hand from the printed table. That is a step where a mistake is silent, so the script below refuses to write the table at all unless the re-keyed numbers reproduce the paper’s own sample sizes, its per-cell means from a second table, and the two decline statistics in its abstract. It is here, verbatim, for the same reason the other scripts are: a ruler nobody can re-derive is a black box with a citation stapled to it.
#!/usr/bin/env python3
"""Vendor the 2022 FRIEND VO2max reference standards as a frozen ruler (ADR 0134).
This is a ONE-OFF, OFFLINE re-keying. Nothing in the running platform calls it;
it writes a versioned JSON table that is checked in, cited, and served
verbatim. It is published on /methods so anybody can re-run it and get the same
table -- a ruler nobody can audit is a black box wearing a citation.
Unlike scripts/rulers/reduce_openpowerlifting.py there is no bulk archive to
reduce here. FRIEND publishes its reference standards as a PAPER, so the input
is a printed table and the honest pipeline is: re-key the numbers by hand, then
verify them hard enough that a transcription slip cannot survive. That is what
the VERIFICATION section below is for, and it is the whole reason this file
exists rather than a hand-written JSON blob.
Source
Kaminsky LA, Arena R, Myers J, Peterman JE, Bonikowske AR, Harber MP,
Medina Inojosa JR, Lavie CJ, Squires RW. "Updated Reference Standards for
Cardiorespiratory Fitness Measured with Cardiopulmonary Exercise Testing:
Data from the Fitness Registry and the Importance of Exercise National
Database (FRIEND)." Mayo Clin Proc. 2022;97(2):285-293.
https://doi.org/10.1016/j.mayocp.2021.08.020
The version of record is open access under CC BY-NC-ND 4.0
(http://creativecommons.org/licenses/by-nc-nd/4.0/), stated on the
article's own first page.
What we take, and in what form
The NUMBERS ONLY, re-keyed into our own structure (#559's licensing pass):
Table 3's treadmill rows and Table 1's per-cell sample sizes and Table 4's
per-cell means. We never reproduce the publisher's typeset table, never
crop or screenshot it, and /methods redraws the cut points in our own
design with the full citation beside them. Facts are not copyrightable
(Feist v. Rural, 499 U.S. 340) and Elsevier's own permissions policy asks
for no permission when creating an original table from factual data; what
IS theirs is the typesetting, which we do not take.
Anchor on the 2022 paper and NEVER mix the 2015 anchors into it: the 2022
treadmill values run 1.5-4.6 mLO2/kg/min lower than 2015's, so a table
holding some of each would be a ruler that measures nothing.
TREADMILL ONLY, and why
The paper publishes treadmill and cycle-ergometer standards separately,
and they are 4.5 mLO2/kg/min apart on average -- the same person places
differently depending on which machine they were tested on. Every VO2max
we hold is a wrist estimate derived from outdoor walking and running
(Apple's HKQuantityTypeIdentifierVO2Max), so treadmill is the mode our
instrument resembles. Vendoring cycle as well would be shipping a column
nothing may honestly read.
RER >= 1.0, and why
The paper's Table 3 uses peak RER >= 1.0 as its maximal-effort criterion
and reports the RER >= 1.1 variant in a supplement. We take the printed
Table 3. The paper itself observes the two criteria differ by <= 1.0
mLO2/kg/min in the mean across every cell, which is smaller than the
wearable bias the sheet already discloses.
Usage
python3 scripts/rulers/vendor_friend_vo2max.py \
--out go/platform/rulers/tables/cardio_friend_vo2max.json
Run with --check to verify without writing.
"""
import argparse
import json
import sys
TABLE_ID = "cardio_friend_vo2max_v1"
# The article's own dates. SNAPSHOT_DATE is the issue the version of record
# appeared in; DATA_THROUGH is when the registry data behind it ends. A
# published reference standard does not drift the way a scraped histogram
# does -- it goes stale when a newer edition is published -- and /methods says
# so rather than letting a large "days old" number imply a decay that is not
# happening.
SNAPSHOT_DATE = "2022-02-01"
DATA_THROUGH = "2021-03-31"
# The day a human last read the DOI and confirmed this is still the current
# edition. This, not SNAPSHOT_DATE, is what the staleness monitor's clock runs
# from (ADR 0154): a re-check that finds nothing new changes no number here, so
# it must be recordable without touching the snapshot. Bump it when you look.
LAST_CHECKED_DATE = "2026-08-19"
# The percentiles the paper publishes: deciles, 10th through 90th. NOT the
# 5/10/25/50/75/90/95 grid #559's licensing pass guessed at from the 2015
# paper's shape -- the 2022 update prints deciles, and the anchors a ruler
# interpolates between have to be the ones its source actually published.
PERCENTILE_GRID = [10, 20, 30, 40, 50, 60, 70, 80, 90]
# The age axis. The paper bins by decade; we pin each decade's column at its
# MIDPOINT and interpolate linearly between midpoints, so nobody's band jumps
# on a birthday (#559). Outside 25..85 the nearest column is used unchanged
# and the surface says that it clamped.
AGE_KNOTS = [25, 35, 45, 55, 65, 75, 85]
AGE_LABELS = ["20-29", "30-39", "40-49", "50-59", "60-69", "70-79", "80-89"]
# The band cuts, on the published grid, and OUR names for the bands they
# start. ADR 0134: the names are ours, the percentiles they sit at are the
# table's, and /methods says exactly that. TIER_NAMES is one longer than
# TIER_CUTS -- the first name is the band BELOW the first cut.
TIER_CUTS = [10, 30, 50, 70, 90]
TIER_NAMES = ["Low", "Below average", "Fair", "Above average", "Good", "Excellent"]
# ---------------------------------------------------------------------------
# The re-keyed numbers. Everything below this line came off the printed page.
# ---------------------------------------------------------------------------
# Table 3, treadmill rows. percentile -> sex -> one value per age group, in
# AGE_LABELS order. Keyed exactly as printed, in mLO2*kg-1*min-1.
TREADMILL_PERCENTILES = {
90: {
"MALE": [58.6, 55.5, 50.8, 43.4, 37.1, 29.4, 22.8],
"FEMALE": [49.0, 42.1, 37.8, 32.4, 27.3, 22.8, 20.8],
},
80: {
"MALE": [54.5, 50.0, 45.2, 38.3, 32.0, 25.9, 21.4],
"FEMALE": [44.8, 37.0, 33.0, 28.4, 24.3, 20.8, 18.4],
},
70: {
"MALE": [51.9, 46.4, 40.9, 34.3, 28.7, 23.8, 20.0],
"FEMALE": [41.8, 33.6, 30.0, 26.3, 22.4, 19.6, 17.3],
},
60: {
"MALE": [49.0, 43.4, 37.9, 31.8, 26.5, 22.2, 18.4],
"FEMALE": [39.0, 31.0, 27.7, 24.6, 20.9, 18.3, 16.0],
},
50: {
"MALE": [46.5, 39.7, 35.3, 29.2, 24.6, 20.6, 17.6],
"FEMALE": [36.6, 28.3, 25.7, 22.9, 19.6, 17.2, 15.4],
},
40: {
"MALE": [43.6, 37.0, 32.4, 26.9, 22.8, 19.1, 16.6],
"FEMALE": [34.0, 26.4, 23.9, 21.5, 18.3, 16.2, 14.7],
},
30: {
"MALE": [40.0, 33.5, 29.7, 24.5, 20.7, 17.3, 16.1],
"FEMALE": [30.8, 24.2, 21.8, 20.1, 17.0, 15.2, 13.7],
},
20: {
"MALE": [35.2, 29.8, 26.7, 22.2, 18.5, 15.9, 14.8],
"FEMALE": [27.2, 21.9, 19.7, 18.5, 15.4, 14.0, 12.6],
},
10: {
"MALE": [28.6, 24.9, 22.1, 18.6, 15.8, 13.6, 12.9],
"FEMALE": [22.5, 18.6, 17.2, 16.5, 13.4, 12.3, 11.4],
},
}
# Table 1, treadmill, RER >= 1.0: how many tests each column is drawn from.
TREADMILL_N = {
"MALE": [1278, 1473, 2119, 2082, 1663, 776, 173],
"FEMALE": [1142, 1043, 1372, 1457, 1045, 549, 106],
}
# The totals the paper prints for those same cohorts, used below to catch a
# mis-keyed n rather than to be published.
PRINTED_N_TOTALS = {"MALE": 9564, "FEMALE": 6714}
PRINTED_TREADMILL_TESTS = 16278
# Table 4, treadmill, RER >= 1.0: the per-cell MEANS. These are re-keyed from
# a DIFFERENT table for one reason -- to check Table 3 against. They are not
# published in our JSON; nothing on any surface reads a mean.
TREADMILL_MEANS = {
"MALE": [45.2, 40.0, 35.8, 30.2, 25.4, 21.2, 17.9],
"FEMALE": [36.3, 29.5, 26.6, 23.8, 20.0, 17.5, 15.9],
}
# The two headline statistics the abstract states, reproduced below from the
# means above. If a re-keyed mean is wrong these stop matching.
PRINTED_DECLINE_PER_DECADE = 4.0 # mLO2*kg-1*min-1, treadmill, over 6 decades
PRINTED_DECLINE_PERCENT = 13.5
POOL_LABEL = (
"apparently healthy US adults tested to exhaustion on a treadmill "
"with a metabolic cart"
)
SOURCE = {
"name": "FRIEND registry (Kaminsky et al., 2022)",
"url": "https://doi.org/10.1016/j.mayocp.2021.08.020",
"data_url": "https://www.mayoclinicproceedings.org/article/S0025-6196(21)00645-5/fulltext",
"license": (
"The article is open access under CC BY-NC-ND 4.0. The values below are "
"facts re-keyed into our own structure, not a reproduction of the "
"publisher's typeset table."
),
"attribution": (
"Reference values from Kaminsky LA, Arena R, Myers J, et al. Updated "
"Reference Standards for Cardiorespiratory Fitness Measured with "
"Cardiopulmonary Exercise Testing: Data from the Fitness Registry and the "
"Importance of Exercise National Database (FRIEND). Mayo Clin Proc. "
"2022;97(2):285-293. doi:10.1016/j.mayocp.2021.08.020"
),
"citation": (
"Kaminsky LA, Arena R, Myers J, Peterman JE, Bonikowske AR, Harber MP, "
"Medina Inojosa JR, Lavie CJ, Squires RW. Updated Reference Standards for "
"Cardiorespiratory Fitness Measured with Cardiopulmonary Exercise Testing: "
"Data from the Fitness Registry and the Importance of Exercise National "
"Database (FRIEND). Mayo Clinic Proceedings. 2022;97(2):285-293."
),
}
# ---------------------------------------------------------------------------
# VERIFICATION. Every check here exists to catch a transcription slip in the
# block above, because a hand-keyed ruler with no check is a rumour.
# ---------------------------------------------------------------------------
def verify(report):
"""Raise SystemExit on the first failure; report() every check that passed."""
def fail(message):
raise SystemExit("FRIEND table verification failed: " + message)
grid = sorted(TREADMILL_PERCENTILES)
if grid != sorted(PERCENTILE_GRID):
fail("keyed percentiles %s do not match the grid %s" % (grid, PERCENTILE_GRID))
for sex in ("MALE", "FEMALE"):
# 1. Within one age column the value must rise with the percentile.
for age_index, label in enumerate(AGE_LABELS):
column = [TREADMILL_PERCENTILES[p][sex][age_index] for p in PERCENTILE_GRID]
if any(b <= a for a, b in zip(column, column[1:])):
fail("%s %s is not ascending across percentiles: %s" % (sex, label, column))
# 2. Along one percentile row fitness must not RISE with age. The
# paper's own finding is a monotone decline; equality is allowed
# (women's 90th is flat from 70-79 to 80-89) but a rise means a
# column was keyed out of order.
for p in PERCENTILE_GRID:
row = TREADMILL_PERCENTILES[p][sex]
if any(b > a for a, b in zip(row, row[1:])):
fail("%s p%d rises with age: %s" % (sex, p, row))
if len(TREADMILL_N[sex]) != len(AGE_LABELS):
fail("%s has %d sample counts for %d age groups" % (sex, len(TREADMILL_N[sex]), len(AGE_LABELS)))
if sum(TREADMILL_N[sex]) != PRINTED_N_TOTALS[sex]:
fail("%s sample counts sum to %d, printed total is %d"
% (sex, sum(TREADMILL_N[sex]), PRINTED_N_TOTALS[sex]))
# 3. The two per-sex totals must add to the treadmill test count in the
# abstract.
total = sum(PRINTED_N_TOTALS.values())
if total != PRINTED_TREADMILL_TESTS:
fail("sample counts total %d, abstract says %d treadmill tests" % (total, PRINTED_TREADMILL_TESTS))
report("sample counts: %d men + %d women = %d treadmill tests, as printed"
% (PRINTED_N_TOTALS["MALE"], PRINTED_N_TOTALS["FEMALE"], total))
# 4. Men above women in every cell -- the paper states this for every age
# group, so a swapped pair of columns dies here.
for p in PERCENTILE_GRID:
for age_index, label in enumerate(AGE_LABELS):
male = TREADMILL_PERCENTILES[p]["MALE"][age_index]
female = TREADMILL_PERCENTILES[p]["FEMALE"][age_index]
if male <= female:
fail("p%d %s: men %.1f is not above women %.1f" % (p, label, male, female))
report("men above women in all %d cells, as the paper states"
% (len(PERCENTILE_GRID) * len(AGE_LABELS)))
# 5. CROSS-TABLE. Each cell's mean (Table 4) must land between that cell's
# 40th and 60th percentile (Table 3). The two tables were keyed from
# different pages, so a slip in either breaks this, and a slip near the
# middle of a distribution -- the values a real reading is most likely
# to land on -- is exactly what it catches.
for sex in ("MALE", "FEMALE"):
for age_index, label in enumerate(AGE_LABELS):
mean = TREADMILL_MEANS[sex][age_index]
low = TREADMILL_PERCENTILES[40][sex][age_index]
high = TREADMILL_PERCENTILES[60][sex][age_index]
if not low <= mean <= high:
fail("%s %s: mean %.1f is outside its own 40th-60th percentile [%.1f, %.1f]"
% (sex, label, mean, low, high))
report("every cell's mean sits inside its own 40th-60th percentile (Table 4 vs Table 3)")
# 6. CROSS-ABSTRACT. Reproduce the abstract's decline-per-decade figures
# from the keyed means. Six decades separate the 20s column from the
# 80s column.
decades = len(AGE_LABELS) - 1
absolute = []
percent = []
for sex in ("MALE", "FEMALE"):
youngest = TREADMILL_MEANS[sex][0]
oldest = TREADMILL_MEANS[sex][-1]
absolute.append((youngest - oldest) / decades)
percent.append((1 - (oldest / youngest) ** (1.0 / decades)) * 100)
got_absolute = sum(absolute) / len(absolute)
got_percent = sum(percent) / len(percent)
if abs(got_absolute - PRINTED_DECLINE_PER_DECADE) > 0.1:
fail("decline per decade computes to %.2f, abstract says %.1f"
% (got_absolute, PRINTED_DECLINE_PER_DECADE))
if abs(got_percent - PRINTED_DECLINE_PERCENT) > 0.5:
fail("percent decline per decade computes to %.2f, abstract says %.1f"
% (got_percent, PRINTED_DECLINE_PERCENT))
report("decline per decade recomputes to %.2f mLO2/kg/min and %.1f%%, "
"abstract says %.1f and %.1f%%"
% (got_absolute, got_percent, PRINTED_DECLINE_PER_DECADE, PRINTED_DECLINE_PERCENT))
# 7. Our band cuts have to be ON the published grid, or a band floor would
# be a number we invented rather than one the table printed.
for cut in TIER_CUTS:
if cut not in PERCENTILE_GRID:
fail("band cut %s is not on the published percentile grid" % cut)
if len(TIER_NAMES) != len(TIER_CUTS) + 1:
fail("%d band names for %d cuts" % (len(TIER_NAMES), len(TIER_CUTS)))
report("band cuts %s all sit on the published grid" % TIER_CUTS)
if len(AGE_KNOTS) != len(AGE_LABELS):
fail("%d age knots for %d age groups" % (len(AGE_KNOTS), len(AGE_LABELS)))
report("all checks passed")
def build():
columns = {}
for sex in ("FEMALE", "MALE"):
knots = []
for age_index, age in enumerate(AGE_KNOTS):
knots.append(
{
"age_years": age,
"age_label": AGE_LABELS[age_index],
"n": TREADMILL_N[sex][age_index],
"vo2max": [
TREADMILL_PERCENTILES[p][sex][age_index] for p in PERCENTILE_GRID
],
}
)
columns[sex] = knots
return {
"id": TABLE_ID,
"version": 1,
"snapshot_date": SNAPSHOT_DATE,
# How this ruler ages (ADR 0154), and it is not by the calendar. A
# paper does not drift; it is SUPERSEDED, once, when the registry
# publishes a newer edition. LAST_CHECKED_DATE is the clock — the day
# somebody last read the DOI — and bumping it is a one-line commit
# that records a real check without faking a snapshot.
"staleness": {
"policy": "SUPERSEDED",
"recheck_every_days": 365,
"last_checked_date": LAST_CHECKED_DATE,
"recheck_question": (
"Has the FRIEND registry published a cardiorespiratory-fitness reference "
"standard newer than the 2022 edition (Kaminsky et al., Mayo Clin Proc "
"2022;97(2):285-293)?"
),
"recheck_url": "https://doi.org/10.1016/j.mayocp.2021.08.020",
"rationale": (
"This is a paper, not a feed. The 2022 percentiles do not change, so the "
"snapshot date being years ago is not decay, and reporting it as decay would "
"be a false alarm that trains everyone to ignore the real one. It goes stale "
"exactly once — when the registry publishes a newer edition, as it did in "
"2015, 2017, 2019 and 2022 — and no age threshold of ours can detect that, "
"because the signal is on a DOI a human has to read. So the monitor asks on a "
"yearly cadence, comfortably inside the shortest gap between editions, and "
"the clock it runs from is the day somebody last looked rather than the day "
"the paper was published."
),
},
"source": SOURCE,
"pipeline": {
"script": "scripts/rulers/vendor_friend_vo2max.py",
"mode": "treadmill",
"effort_criterion": "peak RER >= 1.0",
"source_table": "Table 3 (percentiles), Table 1 (sample sizes)",
"data_through": DATA_THROUGH,
"tests": PRINTED_TREADMILL_TESTS,
"age_interpolation": "linear between decade midpoints, clamped flat outside 25-85",
"value_interpolation": "linear between published decile anchors; saturates at the 10th and 90th",
"note": (
"The published percentiles, re-keyed by hand and verified against the "
"paper's own sample sizes, per-cell means and abstract statistics. "
"Nothing is fitted, smoothed or regressed: a curve of ours through "
"somebody else's anchors would be our model wearing their citation."
),
},
"pool_label": POOL_LABEL,
"percentile_grid": PERCENTILE_GRID,
"tier_cuts": TIER_CUTS,
"tier_names": TIER_NAMES,
"columns": columns,
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", help="where to write the table")
parser.add_argument("--check", action="store_true", help="verify only, write nothing")
args = parser.parse_args()
verify(lambda line: print("ok: " + line, file=sys.stderr))
if args.check:
return
if not args.out:
raise SystemExit("--out is required unless --check is given")
table = build()
with open(args.out, "w", encoding="utf-8") as handle:
json.dump(table, handle, indent=1, sort_keys=True)
handle.write("\n")
print("wrote " + args.out, file=sys.stderr)
if __name__ == "__main__":
main()
HRV — nightly RMSSD against 8.2 million wrists
What we show.A band for your nightly heart-rate variability — RMSSD, in milliseconds, the number a WHOOP strap or an Oura ring reports for a night’s sleep. The number we place is the median of your last 28 nights: nightly HRV swings with a late meal or a drink, and the middle of a month is the level, where a best would just find your calmest night and call it fitness.
The pool. Fitbit wearers worldwide in the sleep-dominated 6-7am hour: 8,203,261people, in the study’s own words a sleep-dominated hour, measured as RMSSD over 5-min windows; per user, the median of acceptable windows within the hour. People who bought a fitness tracker and wore it to bed are more health-attentive than the general population, and Fitbit’s nightly arithmetic is not your vendor’s — same construct, a few milliseconds of definitional slack. Both caveats print on the receipt.
Quartiles, and nothing finer.The appendix publishes the 25th, 50th and 75th percentiles per sex per age bin — no tails. So the four bands are the quartiles under our names, the highest placement this ruler can honestly show is the top quarter, and a reading outside the published anchors says “below the 25th” or “above the 75th” rather than inventing a 5th or a 95th. The rank the app wears for it caps one rung below the ladder’s top for the same reason: a ruler that cannot see past the 75th percentile does not award its highest mark.
Natarajan A, Pantelopoulos A, Emir-Farinas H, Natarajan P. Heart rate variability with photoplethysmography in 8 million individuals: a cross-sectional study. Lancet Digital Health. 2020;2(12):e650-e657.
The article is open access under CC BY-NC-ND 4.0. The values here are facts re-keyed into our own structure, not a reproduction of the publisher's typeset appendix. Read the appendix and check every number below against its Supplementary appendix Table S3, RMSSD, 0600-0700h.
| Age | 25th | 50th | 75th |
|---|---|---|---|
| 20-21 | 45 | 66 | 96 |
| 25-26 | 35 | 54 | 79 |
| 30-31 | 34 | 49 | 71 |
| 35-36 | 31 | 43 | 62 |
| 40-41 | 27 | 38 | 54 |
| 45-46 | 25 | 34 | 48 |
| 50-51 | 23 | 31 | 42 |
| 55-56 | 21 | 29 | 39 |
| 60-61 | 20 | 27 | 37 |
| Age | 25th | 50th | 75th |
|---|---|---|---|
| 20-21 | 37 | 56 | 85 |
| 25-26 | 32 | 48 | 73 |
| 30-31 | 31 | 45 | 67 |
| 35-36 | 29 | 41 | 60 |
| 40-41 | 26 | 37 | 52 |
| 45-46 | 24 | 33 | 46 |
| 50-51 | 22 | 31 | 42 |
| 55-56 | 22 | 29 | 40 |
| 60-61 | 21 | 28 | 38 |
Between the printed bins: linear between bin centers, clamped flat outside 20.5-60.5, so a band cannot change on a birthday, and outside 20–61 the nearest published bin is read with the clamp stated on the sheet. Nothing is fitted — the paper’s own power-law model is deliberately not used, because a fitted curve is the authors’ estimate and this page only reproduces their anchors.
Direction, narrowly. A higher nightly RMSSD quartile is the rewarded direction within the ordinary range, which is what lets this category carry a band at all. An HRV far outside that range — either way — is a conversation with a doctor, not a score, and the sheet says so.
The re-keying, in full
The quartiles were typed in by hand from the published appendix and cross-checked against the preprint’s identical table; the script refuses to write the table unless every bin’s quartiles ascend and every column’s medians fall with age, both true of the source.
#!/usr/bin/env python3
"""Vendor the Natarajan 2020 nightly-RMSSD reference values as a frozen ruler.
A ONE-OFF, OFFLINE re-keying, on vendor_friend_vo2max.py's pattern: nothing in
the running platform calls it; it writes a versioned JSON table that is checked
in, cited, and served verbatim, and it is published on /methods so anybody can
re-run it and get the same table.
Source
Natarajan A, Pantelopoulos A, Emir-Farinas H, Natarajan P. "Heart rate
variability with photoplethysmography in 8 million individuals: a
cross-sectional study." Lancet Digital Health. 2020;2(12):e650-e657.
https://doi.org/10.1016/S2589-7500(20)30246-6
The article is open access under CC BY-NC-ND 4.0. The percentile values
live in the supplementary appendix (mmc1.pdf), Table S3 ("HRV Features -
Time Domain"); the bioRxiv preprint (https://doi.org/10.1101/772285)
publishes the same medians and quartiles in machine-readable text and is
the cross-check used below.
What we take, and in what form
The NUMBERS ONLY, re-keyed into our own structure (the FRIEND licensing
pass, unchanged): Table S3's RMSSD rows for the 0600-0700h block — the
sleep-dominated hour, the paper's own words ("significantly more usable
data when the subjects were asleep") — per sex, per published age bin:
the 25th percentile, the median, and the 75th percentile, in
milliseconds. Facts are not copyrightable (Feist v. Rural, 499 U.S. 340);
the publisher's typeset table is never reproduced, cropped or
screenshotted, and /methods redraws the quartiles in our own design with
the full citation beside them.
The 1800-1900h (awake) block is deliberately NOT vendored: our input is
the nightly sleep RMSSD a WHOOP strap or an Oura ring reports, and the
awake block is a different physiological state wearing the same unit.
The paper's own power-law fit (appendix Table S1) is also NOT vendored:
a fitted curve is the authors' model, and the ruler rule is anchors only,
interpolated linearly between the published bins — a curve of ours (or
theirs) through the anchors would smooth what the source actually says.
Age bins
The appendix publishes one-year slices at five-year spacing ("age = 20
includes users between 20 and 21"), 20 through 60. Each bin's knot sits
at its center (20.5, 25.5, ... 60.5); the platform interpolates linearly
between knots and clamps flat outside them, stating both on the sheet.
Per-bin sample sizes
Table S1 publishes user counts by decade, not by the S3 bins, so the
knots carry n=0 ("not published at this grain") and every surface cites
the study-level pool instead: 8,203,261 included users.
VERIFICATION
Every value below was read from the published appendix PDF
(https://ars.els-cdn.com/content/image/1-s2.0-S2589750020302466-mmc1.pdf,
rasterized — the digit font defeats text extraction) and cross-checked
against the bioRxiv preprint's identical table. Two invariants are
enforced before writing: quartiles strictly ascend within every knot, and
medians strictly descend with age within every sex — both true of the
published table, and either failing means a transcription slip.
"""
import json
import pathlib
OUT = pathlib.Path(__file__).resolve().parents[2] / (
"go/platform/rulers/tables/hrv_natarajan_rmssd.json"
)
# Table S3, RMSSD (ms), 0600-0700h block: age bin -> (p25, p50, p75).
FEMALE = {
20: (37, 56, 85),
25: (32, 48, 73),
30: (31, 45, 67),
35: (29, 41, 60),
40: (26, 37, 52),
45: (24, 33, 46),
50: (22, 31, 42),
55: (22, 29, 40),
60: (21, 28, 38),
}
MALE = {
20: (45, 66, 96),
25: (35, 54, 79),
30: (34, 49, 71),
35: (31, 43, 62),
40: (27, 38, 54),
45: (25, 34, 48),
50: (23, 31, 42),
55: (21, 29, 39),
60: (20, 27, 37),
}
# The study-level pool: 10,424,196 sampled, 8,203,261 included (paper, Methods).
INCLUDED_USERS = 8_203_261
def knots(rows: dict) -> list:
out = []
for age, (p25, p50, p75) in sorted(rows.items()):
assert p25 < p50 < p75, f"quartiles not ascending at age {age}"
out.append(
{
"age_label": f"{age}-{age + 1}",
"age_years": age + 0.5,
"n": 0,
"rmssd": [p25, p50, p75],
}
)
medians = [k["rmssd"][1] for k in out]
assert medians == sorted(medians, reverse=True), "medians not descending with age"
return out
def main() -> None:
table = {
"id": "hrv_natarajan_rmssd_v1",
"version": 1,
"snapshot_date": "2020-12-01",
"source": {
"name": "Natarajan et al., Lancet Digital Health 2020",
"url": "https://doi.org/10.1016/S2589-7500(20)30246-6",
"data_url": "https://ars.els-cdn.com/content/image/1-s2.0-S2589750020302466-mmc1.pdf",
"license": (
"The article is open access under CC BY-NC-ND 4.0. The values "
"here are facts re-keyed into our own structure, not a "
"reproduction of the publisher's typeset appendix."
),
"attribution": (
"Reference values from Natarajan A, Pantelopoulos A, "
"Emir-Farinas H, Natarajan P. Heart rate variability with "
"photoplethysmography in 8 million individuals: a "
"cross-sectional study. Lancet Digit Health. "
"2020;2(12):e650-e657. doi:10.1016/S2589-7500(20)30246-6"
),
"citation": (
"Natarajan A, Pantelopoulos A, Emir-Farinas H, Natarajan P. "
"Heart rate variability with photoplethysmography in 8 million "
"individuals: a cross-sectional study. Lancet Digital Health. "
"2020;2(12):e650-e657."
),
},
"staleness": {
"policy": "SUPERSEDED",
"last_checked_date": "2026-08-20",
"recheck_every_days": 365,
"recheck_question": (
"Has a larger consumer-PPG, sleep-window RMSSD percentile "
"reference been published that supersedes Natarajan 2020 — in "
"particular, a peer-reviewed Apple Heart & Movement Study or "
"All of Us HRV reference table with age/sex percentiles?"
),
"recheck_url": "https://doi.org/10.1016/S2589-7500(20)30246-6",
"rationale": (
"A paper does not drift: the 2020 quartiles do not change, so "
"snapshot age is not decay. It goes stale exactly once — when "
"a larger compatible-instrument reference is published — and "
"only a human reading the literature can see that, so the "
"monitor asks yearly."
),
},
"pipeline": {
"script": "scripts/rulers/vendor_natarajan_hrv.py",
"source_table": "Supplementary appendix Table S3, RMSSD, 0600-0700h",
"hour_block": "0600-0700h, the sleep-dominated hour",
"window_method": (
"RMSSD over 5-min windows; per user, the median of acceptable "
"windows within the hour"
),
"age_interpolation": "linear between bin centers, clamped flat outside 20.5-60.5",
"value_interpolation": "linear between published quartile anchors; saturates at the 25th and 75th",
"included_users": INCLUDED_USERS,
"note": (
"The published quartiles, re-keyed by hand and cross-checked "
"against the bioRxiv preprint (10.1101/772285). Nothing is "
"fitted or smoothed; the paper's own power-law fit is "
"deliberately not used."
),
},
"pool_label": "Fitbit wearers worldwide in the sleep-dominated 6-7am hour",
"percentile_grid": [25, 50, 75],
"tier_cuts": [25, 50, 75],
"tier_names": ["Bottom quarter", "Below median", "Above median", "Top quarter"],
"columns": {"FEMALE": knots(FEMALE), "MALE": knots(MALE)},
}
OUT.write_text(json.dumps(table, indent=1, sort_keys=True) + "\n")
print(f"wrote {OUT}")
if __name__ == "__main__":
main()
Steps — daily median against 54,509 participants
What we show.A band for your daily step count, as Apple Health’s cross-source daily total or an Oura ring reports it. The number we place is the median across your last 28 days: a single day swings with the weather, the middle of a month is the level, and a best would just find your one long hike.
The pool. All of Us research participants who wore a Fitbit, combined bring-your-own-device and free-device cohorts: 54,509participants of the All of Us Research Program’s general activity cohort, each contributing their own median daily steps over their whole donation window. The paper itself calls the cohort a broad convenience sample, not representative of the US population — many participants were given their device free by the program’s device-distribution study — and that caveat prints on the receipt.
The sex column, and nothing crossed. Table 2 publishes sex and age strata separately, never jointly; this ruler places against the sex column alone and the sheet says so. The published age rows ride in age_context for /methods only. Fabricating a joint sex-by-age distribution from two published marginals would be modeling, and this page only reproduces anchors.
Quartiles, and nothing finer.Table 2 publishes the 25th, 50th and 75th percentiles per stratum — no tails. So the four bands are the quartiles under our names, the highest placement this ruler can honestly show is the top quarter, and a reading outside the published anchors says “below the 25th” or “above the 75th” rather than inventing a 5th or a 95th. The rank the app wears for it caps one rung below the ladder’s top for the same reason.
The number is gameable, and the sheet says so. Steps are the easiest metric on the standings page to inflate — a wrist swung at a desk counts, and no ruler can tell it from a walk. The receipt carries that sentence verbatim, because a placement that pretends otherwise is a placement you can buy with a hand gesture.
The instrument.The pool wore Fitbit devices and the program ingests Fitbit’s own summaries; your steps come from a different vendor’s algorithm. The paper notes step estimates are algorithm-sensitive — cohorts measured with raw accelerometry report roughly 9,000–9,600 steps a day where this pool’s median is 6,454. As a sanity cross-check on the consumer-wearable construct: eFHS (Shapira-Daniels et al., JMIR 2023;25:e43123, CC BY; Apple Watch, n = 923): median 7,227 (IQR 5,699-8,970) daily steps — a different instrument, cited as a sanity cross-check only.
Patten T, Preble EA, Master H, et al. The All of Us Research Program's wearables dataset. Nature Medicine. 2026;32:2302-2310.
The article is open access under CC BY 4.0. The values here are facts re-keyed into our own structure, not a reproduction of the publisher's table. Read the table and check every number below against its Table 2, median (IQR) daily steps, general activity cohort.
| Stratum | 25th | 50th | 75th | n |
|---|---|---|---|---|
| Men | 5,046 | 7,264 | 10,001 | 17,134 |
| Women | 4,225 | 6,114 | 8,442 | 37,161 |
| Stratum | 25th | 50th | 75th | n |
|---|---|---|---|---|
| 18-24 | 5,112 | 6,948 | 9,017 | 2,878 |
| 25-34 | 4,908 | 6,732 | 8,848 | 8,583 |
| 35-44 | 4,582 | 6,564 | 9,045 | 9,373 |
| 45-54 | 4,500 | 6,500 | 8,955 | 9,393 |
| 55-64 | 4,448 | 6,608 | 9,460 | 10,677 |
| 65-74 | 4,061 | 6,169 | 8,908 | 10,338 |
| 75-84 | 3,225 | 5,098 | 7,427 | 3,068 |
| 85+ | 1,774 | 3,460 | 5,353 | 199 |
The re-keying, in full
The quartiles were typed in by hand from the journal’s own HTML table (the article is CC BY 4.0); the script refuses to write the table unless every stratum’s quartiles ascend and the published female quartiles sit below the male at every anchor, both true of the source.
#!/usr/bin/env python3
"""Vendor the All of Us daily-steps reference quartiles as a frozen ruler.
A ONE-OFF, OFFLINE re-keying on vendor_natarajan_hrv.py's pattern: nothing in
the running platform calls it; it writes a versioned JSON table that is
checked in, cited, and served verbatim, and it is published on /methods so
anybody can re-run it and get the same table.
Source
Patten T, Preble EA, Master H, et al. "The All of Us Research Program's
wearables dataset." Nature Medicine. 2026;32:2302-2310.
https://doi.org/10.1038/s41591-026-04352-3
The article is open access under CC BY 4.0 (verified on the article's
"Rights and permissions" section, 2026-08-22), which permits reuse and
adaptation with attribution. The values live in Table 2 ("Baseline
wearables outcomes by cohort with select demographic group comparisons"),
which the journal publishes as an HTML table — no PDF rasterization was
needed; the numbers below were read from
https://www.nature.com/articles/s41591-026-04352-3/tables/2 on
2026-08-22.
What we take, and in what form
The NUMBERS ONLY, re-keyed into our own structure (the FRIEND licensing
pass, unchanged): Table 2's median (IQR) daily steps for the GENERAL
ACTIVITY COHORT column — the combined BYOD + WEAR pool, n = 54,509 —
per published sex-at-birth stratum. Facts are not copyrightable (Feist
v. Rural, 499 U.S. 340); the publisher's table is never reproduced,
and /methods redraws the quartiles in our own design with the full
citation beside them.
The BYOD-only and WEAR-only columns are deliberately NOT vendored: the
split is a recruitment artifact (bring-your-own-device versus a free
distributed device), not a population a reader belongs to, and the
combined cohort is the paper's own headline pool.
⚠ Table 2's strata cross sex OR age, NEVER both — no joint sex x age
distribution is published, so this ruler places against the sex column
alone and SAYS SO. The age rows and the full-cohort row are carried in
the JSON as published context for /methods (the age gradient is real:
the 75-84 row's median is 5,098 and the 85+ row's 3,460, against 6,454
overall), but nothing places against them: fabricating a joint
distribution from two marginals is exactly the modeling the ruler rule
refuses.
Suppressed strata (Intersex, Other, "Prefer not to answer" — n < 20
under the All of Us Data and Statistics Dissemination Policy) publish
no quartiles and therefore cannot be columns.
Instrument
The pool wore Fitbit devices (41 models; the program ingests Fitbit API
summaries, not raw accelerometry). Our metric is /health/activity/steps
from Apple Health's cross-source daily dedup or an Oura ring. The paper
itself notes step estimates are algorithm-sensitive (UK Biobank and
NHANES report ~9,000-9,600 steps/day from raw accelerometry, versus
6,454 here) — the sheet carries that caveat. Compatible-instrument
cross-check, NOT vendored (different device, small n): Shapira-Daniels
et al., eFHS, J Med Internet Res 2023;25:e43123 (CC BY; Apple Watch;
n = 923) reports median 7,227 (IQR 5,699-8,970) daily steps.
VERIFICATION
Every value below was read from the published HTML table and the
invariants enforced before writing: quartiles strictly ascend within
every stratum, the female medians sit below the male medians (true of
the published table), and the per-stratum n values sum to less than the
cohort n (Unknown/Intersex/Other rows are suppressed or unpublished).
Any failure means a transcription slip.
"""
from __future__ import annotations
import json
import pathlib
OUT = (
pathlib.Path(__file__).resolve().parents[2]
/ "go"
/ "platform"
/ "rulers"
/ "tables"
/ "steps_allofus_daily.json"
)
# Table 2, "General activity cohort" column: median (n; IQR) daily steps.
FULL_COHORT = {"n": 54509, "steps": [4432.0, 6454.0, 8958.0]}
COLUMNS = {
"FEMALE": {"label": "Female", "n": 37161, "steps": [4225.0, 6114.0, 8442.0]},
"MALE": {"label": "Male", "n": 17134, "steps": [5046.0, 7264.0, 10001.0]},
}
# The published age rows, context only (see the module docstring): the ruler
# never places against them because sex x age is not published jointly.
AGE_CONTEXT = [
{"age_label": "18-24", "n": 2878, "steps": [5112.0, 6948.0, 9017.0]},
{"age_label": "25-34", "n": 8583, "steps": [4908.0, 6732.0, 8848.0]},
{"age_label": "35-44", "n": 9373, "steps": [4582.0, 6564.0, 9045.0]},
{"age_label": "45-54", "n": 9393, "steps": [4500.0, 6500.0, 8955.0]},
{"age_label": "55-64", "n": 10677, "steps": [4448.0, 6608.0, 9460.0]},
{"age_label": "65-74", "n": 10338, "steps": [4061.0, 6169.0, 8908.0]},
{"age_label": "75-84", "n": 3068, "steps": [3225.0, 5098.0, 7427.0]},
{"age_label": "85+", "n": 199, "steps": [1774.0, 3460.0, 5353.0]},
]
TABLE = {
"id": "steps_allofus_daily_v1",
"version": 1,
"snapshot_date": "2026-04-27",
"source": {
"name": "Patten et al., Nature Medicine 2026 (All of Us wearables dataset)",
"url": "https://doi.org/10.1038/s41591-026-04352-3",
"data_url": "https://www.nature.com/articles/s41591-026-04352-3/tables/2",
"license": (
"The article is open access under CC BY 4.0. The values here are "
"facts re-keyed into our own structure, not a reproduction of the "
"publisher's table."
),
"citation": (
"Patten T, Preble EA, Master H, et al. The All of Us Research "
"Program's wearables dataset. Nature Medicine. 2026;32:2302-2310."
),
"attribution": (
"Reference values from Patten T, Preble EA, Master H, et al. The "
"All of Us Research Program's wearables dataset. Nat Med. "
"2026;32:2302-2310. doi:10.1038/s41591-026-04352-3 (CC BY 4.0)"
),
},
"staleness": {
"policy": "SUPERSEDED",
"rationale": (
"A paper does not drift: the published quartiles do not change, so "
"snapshot age is not decay. It goes stale exactly once — when a "
"larger compatible-instrument reference is published, or when All "
"of Us publishes a newer characterization of the same growing "
"dataset — and only a human reading the literature can see that, "
"so the monitor asks yearly."
),
"recheck_every_days": 365,
"last_checked_date": "2026-08-22",
"recheck_question": (
"Has a newer or larger consumer-wearable daily-steps percentile "
"reference been published that supersedes Patten 2026 — in "
"particular a newer All of Us wearables characterization, or a "
"peer-reviewed Apple Heart & Movement Study steps table with "
"sex/age percentiles?"
),
"recheck_url": "https://doi.org/10.1038/s41591-026-04352-3",
},
"pipeline": {
"script": "scripts/rulers/vendor_allofus_steps.py",
"source_table": "Table 2, median (IQR) daily steps, general activity cohort",
"cohort": (
"The combined BYOD + WEAR general activity cohort (n = 54,509); "
"per-participant median daily steps over each participant's whole "
"donation window"
),
"value_interpolation": (
"linear between published quartile anchors; saturates at the 25th "
"and 75th"
),
"strata_note": (
"Table 2 publishes sex and age strata separately, never jointly; "
"this ruler places against the sex column alone and the sheet "
"says so. The published age rows ride in age_context for "
"/methods only."
),
"included_participants": 54509,
"cross_check": (
"eFHS (Shapira-Daniels et al., JMIR 2023;25:e43123, CC BY; Apple "
"Watch, n = 923): median 7,227 (IQR 5,699-8,970) daily steps — a "
"different instrument, cited as a sanity cross-check only."
),
"note": (
"The published quartiles, re-keyed by hand from the journal's own "
"HTML table. Nothing is fitted, smoothed, or combined across "
"strata."
),
},
"pool_label": (
"All of Us research participants who wore a Fitbit, combined "
"bring-your-own-device and free-device cohorts"
),
"percentile_grid": [25.0, 50.0, 75.0],
"tier_cuts": [25.0, 50.0, 75.0],
"tier_names": ["Bottom quarter", "Below median", "Above median", "Top quarter"],
"full_cohort": FULL_COHORT,
"columns": {
key: {"label": c["label"], "n": c["n"], "steps": c["steps"]}
for key, c in COLUMNS.items()
},
"age_context": AGE_CONTEXT,
}
def check() -> None:
for key, c in COLUMNS.items():
q = c["steps"]
assert q == sorted(q) and len(set(q)) == 3, f"{key} quartiles not ascending"
assert FULL_COHORT["steps"] == sorted(FULL_COHORT["steps"])
for row in AGE_CONTEXT:
assert row["steps"] == sorted(row["steps"]), f"{row['age_label']} not ascending"
for i in range(3):
assert (
COLUMNS["FEMALE"]["steps"][i] < COLUMNS["MALE"]["steps"][i]
), "published female quartiles sit below male at every anchor"
assert COLUMNS["FEMALE"]["n"] + COLUMNS["MALE"]["n"] < FULL_COHORT["n"], (
"sex strata must undercount the cohort (suppressed rows exist)"
)
def main() -> None:
check()
OUT.write_text(json.dumps(TABLE, indent=2, ensure_ascii=False) + "\n")
print(f"wrote {OUT}")
if __name__ == "__main__":
main()
Anchors — when nobody publishes a curve
Two categories below are compared against the population without a percentile anywhere on them, and the absence is the honest part. An anchor is what we build when a category has a published, citable average from an instrument like yours — but no distribution: bands at stated offsets from that average, with the reason there is no percentile printed in the slot a curve would fill. Nothing is fitted. We do not assume the spread is bell-shaped, convert an offset into a percentile, or invent tails the source never printed. The offsets and the band names are ours; the numbers are theirs; both sheets say which is which.
Sleep — bedtime consistency, anchored
Your number is the spread — the standard deviation — of your weekday bedtimes over the trailing 28 days, from the onset of each night’s sleep as your ring or strap recorded it. Duration is deliberately not ranked (its relationship with health is U-shaped; see the refusals below); consistency is the one axis of sleep where the research points a single direction, and it is compared as a spread in minutes, never as hours slept.
The anchor is the largest published pool measured on an instrument like yours: 220,000 Oura Ring wearers across 35 countries (Willoughby et al. 2023, Sleep Medicine, Supplementary Table S2f, CC BY 4.0). Its USA column: an average weekday bedtime spread of 52.64 minutes per person, give or take 28.25across people. Across the paper’s countries the average runs from 45.27 (New Zealand) to 71.33 (Japan) minutes.
| Band | From |
|---|---|
| Steadier than typical | below the first cut |
| Typical | 24.39 min |
| Less steady than typical | 80.89 min |
| Far less steady than typical | 109.14 min |
Why no percentile: the paper publishes means and spreads, not a distribution, and bedtime spreads are right-skewed — reading a percentile off a mean and an SD would be a normality assumption dressed as a citation. The one percentile grid that exists is UK Biobank’s sleep-regularity work (Windred 2021/2024), measured on research accelerometers — a different instrument, which is exactly why the app cites it as context (the most-regular fifth of 60,977 adults held onset spreads of roughly 20–40 minutes) and never as your rank. The National Sleep Foundation’s timing consensus (Sletten 2023) found the evidence insufficient to set any single number, so no surface of ours claims one; the quantified risk anchors the app does cite — a one-hour onset spread associated with 23% higher metabolic-syndrome and 18% higher cardiovascular risk — are Huang & Redline’s (2019, 2020), named as such.
Two more constructions, stated: the statistic is weekday nights only (the nights that end on a Monday-to-Friday morning), because that is what the paper measured; and vendor sleep scores are refused as inputs — Oura’s score and WHOOP’s performance are single-vendor composites, and a standing on a composite would launder a model through a mirror. The onset clock alone feeds this.
Screen — your distracting apps, anchored
We do not rank your screen time. We do not even decide what “distracting” means: you pick the apps that count, the standing averages exactly those apps’ daily minutes over the trailing 28 reported days, and changing the set changes the number — it is your own definition, which is the point. A day your phone reported but your chosen apps never appeared counts as a real zero; averaging only the days you opened them would rank you on your worst days.
The anchor is a metered panel, not a survey — self-reported screen time is famously off by hours: Ofcom’s Online Nation 2025 (Ipsos iris, ~10,000 metered UK participants, May 2025) prints, per UK online adult per day: Meta apps 70 minutes and ByteDance apps 15 minutes. The 85-minute anchor is their sum, and the addition is ours — the report never sums them. Alphabet’s bucket is excluded on purpose: it folds search and mail in with YouTube, and an anchor should not launder a search engine into distraction.
| Band | From |
|---|---|
| Under half the average | below the first cut |
| Below the average | 42.5 min |
| Above the average | 85 min |
| More than double the average | 170 min |
Why no percentile: nobody publishes a distribution of logged app minutes for adults. Metered panels and market reports publish averages only, and the one logged distribution in print covers two hundred US teenagers on Android — the wrong pool twice over. Until better data exists, the published average above is the whole comparison. Two instrument facts ride every sheet: the panel meters phones, tablets and computers together while your number is your phone alone (expect to read low against it), and iPhone per-app minutes are floors rounded down to the phone’s reporting step while Android’s are exact.
Lab results — a line somebody drew, or nothing
A blood or urine result is the one place where a comparison is genuinely available off the shelf and genuinely easy to get wrong. Every row of a lab report already carries an H or an L and a printed reference interval, and turning that column into a rank would take about four lines of code. We do not, for three reasons. A reference interval belongs to one laboratory, computed on its own analyser against its own reference population — two laboratories flag the same blood differently. A flag has no direction: an H on ferritin, an H on HDL cholesterol and an H on sodium are three unrelated statements. And a 95% interval flags one healthy person in twenty by construction, which the WHO says out loud about its own haemoglobin cutoffs.
So the flag and the interval travel with your result as provenance, printed on the receipt in their own block, and they never decide a band. What decides a band is a clinical decision limit: a line a named body drew in a dated publication, in the unit your report prints, for the kind of assay that produced your number. That is a fourth shape of ruler — it has no distribution behind it, so the clinical block has no percentile and says why. For ten compatible measurements, a second block can now describe where the measured value sits in a US population distribution. It never changes the clinical band or turns the decision limit into a curve.
We audited all 84 measurements the importer reads, one at a time. 14 earned a threshold. 70 did not, and each of those carries the citation that establishes why — a refusal with no citation is an opinion. Nothing here is a diagnosis or medical advice; a guideline category is a population-level classification, and what it means for you is a conversation with a clinician who can see the rest of your history.
Transcribed by hand from the publishers’ own documents on 2026-08-19, each threshold carrying the sentence it came from. There is no pipeline to re-run here: a clinical guideline is a document a person reads, and claiming a script would be a lie about how this table is maintained. The file itself is go/platform/rulers/tables/labs_biomarker_rulers.json.
Lab population context — NHANES
What we show.A neutral percentile by measured value among US adults represented by the CDC’s NHANES 2017–March 2020 pre-pandemic survey, split into the published female or male table and age 20–39, 40–59 or 60+. It reads “59th percentile by measured value”, never “top 41%”, “better than” or “healthier than”. A high value can be helpful, harmful, both, or neither depending on the biomarker and the person. This is position, not a target.
The pool.Examined adults age 20 or older in the US civilian, noninstitutionalized population represented by that survey. Participants recorded as pregnant are excluded. Everyone else remains: people with diagnoses, prescriptions and interventions are not filtered out, so this is not a “healthy-control” range. The chosen table tells us which published column to read; it is not an identity inferred by the platform. Age at the draw is derived from the birth year on the profile, because the profile does not hold a birthday.
The reduction. The script joins the ten pinned CDC public-use files by participant, uses the full-exam weight WTMECPRP except for fasting glucose and triglycerides, which use WTSAFPRP, and computes a survey-weighted midpoint empirical CDF. It publishes each whole percentile from P5 through P95. A result beyond that grid is called only “below P5” or “at or above P95”; no tail is invented. Every cell has at least 300 observed participants. These are point estimates; no confidence interval is calculated.
Estimated GFR is the one derived input: the script applies the 2021 CKD-EPI creatinine equation to the NHANES standardized creatinine value. The other nine rows use the named NHANES measurement or the stated arithmetic derivation.
Public longevity dashboards are useful as dated examples, but one person’s biomarker panel is an N=1 benchmark, not a percentile distribution. That is why the receipt uses NHANES for population position rather than presenting a public Blueprint biomarker value as though it described a population.
Re-run scripts/rulers/reduce_nhanes_labs.py to download and SHA-256 verify every source file and rebuild the Go table. The complete P5–P95 table used by the API is reproduced below; sample size n is the unweighted observed count in that cell.
Hemoglobin A1c — P5 through P95
NHANES glycohemoglobin result LBXGH, percent. Survey weight WTMECPRP.
| Percentile | Female 20-39 n=1,240 | Female 40-59 n=1,407 | Female 60+ n=1,454 | Male 20-39 n=1,116 | Male 40-59 n=1,265 | Male 60+ n=1,523 |
|---|---|---|---|---|---|---|
| P5 | 4.8 | 5.0 | 5.1 | 4.6 | 4.9 | 5.1 |
| P6 | 4.8 | 5.0 | 5.1 | 4.7 | 5.0 | 5.1 |
| P7 | 4.8 | 5.0 | 5.2 | 4.7 | 5.0 | 5.2 |
| P8 | 4.8 | 5.0 | 5.2 | 4.8 | 5.0 | 5.2 |
| P9 | 4.8 | 5.0 | 5.2 | 4.8 | 5.1 | 5.2 |
| P10 | 4.9 | 5.1 | 5.2 | 4.9 | 5.1 | 5.2 |
| P11 | 4.9 | 5.1 | 5.3 | 4.9 | 5.1 | 5.3 |
| P12 | 4.9 | 5.1 | 5.3 | 4.9 | 5.1 | 5.3 |
| P13 | 4.9 | 5.1 | 5.3 | 4.9 | 5.1 | 5.3 |
| P14 | 4.9 | 5.2 | 5.3 | 4.9 | 5.1 | 5.3 |
| P15 | 4.9 | 5.2 | 5.3 | 4.9 | 5.2 | 5.3 |
| P16 | 4.9 | 5.2 | 5.4 | 4.9 | 5.2 | 5.4 |
| P17 | 5.0 | 5.2 | 5.4 | 5.0 | 5.2 | 5.4 |
| P18 | 5.0 | 5.2 | 5.4 | 5.0 | 5.2 | 5.4 |
| P19 | 5.0 | 5.2 | 5.4 | 5.0 | 5.2 | 5.4 |
| P20 | 5.0 | 5.2 | 5.4 | 5.0 | 5.2 | 5.4 |
| P21 | 5.0 | 5.3 | 5.4 | 5.0 | 5.2 | 5.4 |
| P22 | 5.0 | 5.3 | 5.4 | 5.0 | 5.3 | 5.4 |
| P23 | 5.0 | 5.3 | 5.4 | 5.0 | 5.3 | 5.4 |
| P24 | 5.0 | 5.3 | 5.4 | 5.0 | 5.3 | 5.5 |
| P25 | 5.0 | 5.3 | 5.5 | 5.0 | 5.3 | 5.5 |
| P26 | 5.0 | 5.3 | 5.5 | 5.1 | 5.3 | 5.5 |
| P27 | 5.1 | 5.3 | 5.5 | 5.1 | 5.3 | 5.5 |
| P28 | 5.1 | 5.3 | 5.5 | 5.1 | 5.3 | 5.5 |
| P29 | 5.1 | 5.3 | 5.5 | 5.1 | 5.3 | 5.5 |
| P30 | 5.1 | 5.3 | 5.5 | 5.1 | 5.4 | 5.5 |
| P31 | 5.1 | 5.4 | 5.5 | 5.1 | 5.4 | 5.5 |
| P32 | 5.1 | 5.4 | 5.5 | 5.1 | 5.4 | 5.6 |
| P33 | 5.1 | 5.4 | 5.5 | 5.1 | 5.4 | 5.6 |
| P34 | 5.1 | 5.4 | 5.6 | 5.1 | 5.4 | 5.6 |
| P35 | 5.1 | 5.4 | 5.6 | 5.1 | 5.4 | 5.6 |
| P36 | 5.1 | 5.4 | 5.6 | 5.2 | 5.4 | 5.6 |
| P37 | 5.1 | 5.4 | 5.6 | 5.2 | 5.4 | 5.6 |
| P38 | 5.1 | 5.4 | 5.6 | 5.2 | 5.4 | 5.6 |
| P39 | 5.2 | 5.4 | 5.6 | 5.2 | 5.4 | 5.6 |
| P40 | 5.2 | 5.4 | 5.6 | 5.2 | 5.4 | 5.7 |
| P41 | 5.2 | 5.4 | 5.6 | 5.2 | 5.4 | 5.7 |
| P42 | 5.2 | 5.4 | 5.6 | 5.2 | 5.5 | 5.7 |
| P43 | 5.2 | 5.5 | 5.6 | 5.2 | 5.5 | 5.7 |
| P44 | 5.2 | 5.5 | 5.6 | 5.2 | 5.5 | 5.7 |
| P45 | 5.2 | 5.5 | 5.6 | 5.2 | 5.5 | 5.7 |
| P46 | 5.2 | 5.5 | 5.7 | 5.2 | 5.5 | 5.7 |
| P47 | 5.2 | 5.5 | 5.7 | 5.3 | 5.5 | 5.7 |
| P48 | 5.2 | 5.5 | 5.7 | 5.3 | 5.5 | 5.8 |
| P49 | 5.2 | 5.5 | 5.7 | 5.3 | 5.5 | 5.8 |
| P50 | 5.2 | 5.5 | 5.7 | 5.3 | 5.5 | 5.8 |
| P51 | 5.3 | 5.5 | 5.7 | 5.3 | 5.6 | 5.8 |
| P52 | 5.3 | 5.5 | 5.7 | 5.3 | 5.6 | 5.8 |
| P53 | 5.3 | 5.5 | 5.7 | 5.3 | 5.6 | 5.8 |
| P54 | 5.3 | 5.6 | 5.8 | 5.3 | 5.6 | 5.8 |
| P55 | 5.3 | 5.6 | 5.8 | 5.3 | 5.6 | 5.8 |
| P56 | 5.3 | 5.6 | 5.8 | 5.3 | 5.6 | 5.9 |
| P57 | 5.3 | 5.6 | 5.8 | 5.3 | 5.6 | 5.9 |
| P58 | 5.3 | 5.6 | 5.8 | 5.3 | 5.6 | 5.9 |
| P59 | 5.3 | 5.6 | 5.8 | 5.3 | 5.6 | 5.9 |
| P60 | 5.3 | 5.6 | 5.8 | 5.4 | 5.7 | 5.9 |
| P61 | 5.3 | 5.6 | 5.9 | 5.4 | 5.7 | 6.0 |
| P62 | 5.3 | 5.6 | 5.9 | 5.4 | 5.7 | 6.0 |
| P63 | 5.3 | 5.6 | 5.9 | 5.4 | 5.7 | 6.0 |
| P64 | 5.4 | 5.7 | 5.9 | 5.4 | 5.7 | 6.0 |
| P65 | 5.4 | 5.7 | 5.9 | 5.4 | 5.7 | 6.0 |
| P66 | 5.4 | 5.7 | 5.9 | 5.4 | 5.7 | 6.0 |
| P67 | 5.4 | 5.7 | 5.9 | 5.4 | 5.7 | 6.1 |
| P68 | 5.4 | 5.7 | 5.9 | 5.4 | 5.8 | 6.1 |
| P69 | 5.4 | 5.7 | 6.0 | 5.4 | 5.8 | 6.1 |
| P70 | 5.4 | 5.7 | 6.0 | 5.4 | 5.8 | 6.1 |
| P71 | 5.4 | 5.7 | 6.0 | 5.4 | 5.8 | 6.2 |
| P72 | 5.4 | 5.8 | 6.0 | 5.5 | 5.8 | 6.2 |
| P73 | 5.4 | 5.8 | 6.0 | 5.5 | 5.9 | 6.2 |
| P74 | 5.4 | 5.8 | 6.1 | 5.5 | 5.9 | 6.3 |
| P75 | 5.5 | 5.8 | 6.1 | 5.5 | 5.9 | 6.3 |
| P76 | 5.5 | 5.8 | 6.1 | 5.5 | 5.9 | 6.4 |
| P77 | 5.5 | 5.8 | 6.1 | 5.5 | 5.9 | 6.4 |
| P78 | 5.5 | 5.9 | 6.2 | 5.5 | 6.0 | 6.5 |
| P79 | 5.5 | 5.9 | 6.2 | 5.5 | 6.0 | 6.5 |
| P80 | 5.5 | 5.9 | 6.2 | 5.6 | 6.0 | 6.6 |
| P81 | 5.5 | 5.9 | 6.3 | 5.6 | 6.1 | 6.6 |
| P82 | 5.5 | 6.0 | 6.3 | 5.6 | 6.1 | 6.7 |
| P83 | 5.6 | 6.0 | 6.4 | 5.6 | 6.2 | 6.8 |
| P84 | 5.6 | 6.0 | 6.4 | 5.6 | 6.2 | 6.8 |
| P85 | 5.6 | 6.0 | 6.5 | 5.6 | 6.3 | 6.8 |
| P86 | 5.6 | 6.1 | 6.5 | 5.7 | 6.4 | 6.9 |
| P87 | 5.7 | 6.2 | 6.5 | 5.7 | 6.5 | 7.0 |
| P88 | 5.7 | 6.2 | 6.6 | 5.7 | 6.6 | 7.1 |
| P89 | 5.7 | 6.3 | 6.7 | 5.7 | 6.7 | 7.2 |
| P90 | 5.7 | 6.4 | 6.8 | 5.7 | 6.9 | 7.2 |
| P91 | 5.8 | 6.6 | 6.8 | 5.8 | 7.0 | 7.3 |
| P92 | 5.8 | 6.7 | 7.0 | 5.8 | 7.4 | 7.3 |
| P93 | 5.9 | 6.8 | 7.2 | 5.8 | 7.6 | 7.5 |
| P94 | 6.0 | 7.0 | 7.4 | 5.9 | 7.9 | 7.7 |
| P95 | 6.0 | 7.4 | 7.6 | 5.9 | 8.5 | 7.9 |
Lab glucose — P5 through P95
NHANES fasting plasma glucose LBXGLU, mg/dL. Survey weight WTSAFPRP; fasting participants only.
| Percentile | Female 20-39 n=616 | Female 40-59 n=712 | Female 60+ n=708 | Male 20-39 n=552 | Male 40-59 n=622 | Male 60+ n=757 |
|---|---|---|---|---|---|---|
| P5 | 85 | 86 | 89 | 87 | 91 | 94 |
| P6 | 86 | 86 | 91 | 87 | 93 | 95 |
| P7 | 86 | 87 | 92 | 88 | 93 | 95 |
| P8 | 86 | 88 | 92 | 88 | 93 | 95 |
| P9 | 87 | 89 | 92 | 88 | 93 | 96 |
| P10 | 87 | 89 | 93 | 89 | 94 | 96 |
| P11 | 87 | 89 | 94 | 90 | 95 | 97 |
| P12 | 88 | 90 | 94 | 90 | 95 | 97 |
| P13 | 88 | 90 | 94 | 90 | 96 | 97 |
| P14 | 89 | 90 | 94 | 90 | 96 | 98 |
| P15 | 89 | 91 | 95 | 91 | 96 | 99 |
| P16 | 89 | 91 | 95 | 91 | 97 | 100 |
| P17 | 89 | 92 | 95 | 92 | 97 | 100 |
| P18 | 89 | 92 | 96 | 92 | 98 | 101 |
| P19 | 90 | 92 | 96 | 93 | 98 | 101 |
| P20 | 90 | 93 | 96 | 93 | 98 | 101 |
| P21 | 90 | 93 | 97 | 93 | 99 | 102 |
| P22 | 91 | 94 | 97 | 93 | 99 | 102 |
| P23 | 91 | 94 | 97 | 94 | 99 | 102 |
| P24 | 91 | 95 | 98 | 94 | 99 | 102 |
| P25 | 91 | 95 | 98 | 94 | 100 | 103 |
| P26 | 92 | 95 | 98 | 95 | 100 | 103 |
| P27 | 92 | 95 | 99 | 95 | 100 | 103 |
| P28 | 92 | 96 | 99 | 95 | 100 | 104 |
| P29 | 92 | 96 | 99 | 95 | 100 | 104 |
| P30 | 93 | 97 | 100 | 95 | 100 | 105 |
| P31 | 93 | 97 | 100 | 95 | 101 | 105 |
| P32 | 93 | 97 | 100 | 96 | 101 | 105 |
| P33 | 93 | 97 | 100 | 96 | 101 | 105 |
| P34 | 93 | 97 | 100 | 96 | 101 | 106 |
| P35 | 93 | 98 | 101 | 97 | 101 | 106 |
| P36 | 94 | 98 | 101 | 97 | 102 | 107 |
| P37 | 94 | 98 | 101 | 97 | 102 | 107 |
| P38 | 94 | 98 | 102 | 97 | 102 | 107 |
| P39 | 94 | 99 | 102 | 98 | 102 | 107 |
| P40 | 94 | 99 | 102 | 98 | 103 | 107 |
| P41 | 95 | 100 | 102 | 98 | 103 | 108 |
| P42 | 95 | 100 | 103 | 98 | 104 | 109 |
| P43 | 95 | 100 | 103 | 98 | 104 | 109 |
| P44 | 95 | 100 | 103 | 99 | 104 | 110 |
| P45 | 95 | 100 | 104 | 99 | 105 | 110 |
| P46 | 95 | 101 | 104 | 99 | 105 | 110 |
| P47 | 96 | 101 | 104 | 99 | 105 | 111 |
| P48 | 96 | 101 | 104 | 99 | 105 | 111 |
| P49 | 96 | 101 | 105 | 100 | 105 | 112 |
| P50 | 96 | 101 | 106 | 100 | 105 | 112 |
| P51 | 97 | 101 | 106 | 100 | 106 | 113 |
| P52 | 97 | 102 | 106 | 100 | 106 | 113 |
| P53 | 97 | 102 | 107 | 100 | 106 | 114 |
| P54 | 97 | 102 | 107 | 100 | 106 | 114 |
| P55 | 97 | 102 | 107 | 101 | 107 | 114 |
| P56 | 97 | 103 | 108 | 101 | 107 | 115 |
| P57 | 97 | 103 | 108 | 101 | 107 | 115 |
| P58 | 97 | 104 | 109 | 102 | 108 | 116 |
| P59 | 98 | 104 | 109 | 102 | 108 | 117 |
| P60 | 98 | 104 | 109 | 102 | 108 | 117 |
| P61 | 98 | 104 | 110 | 102 | 109 | 117 |
| P62 | 99 | 104 | 110 | 103 | 109 | 119 |
| P63 | 99 | 105 | 111 | 103 | 109 | 119 |
| P64 | 99 | 105 | 111 | 103 | 110 | 119 |
| P65 | 99 | 105 | 111 | 103 | 110 | 120 |
| P66 | 100 | 105 | 112 | 103 | 110 | 120 |
| P67 | 100 | 106 | 112 | 104 | 110 | 120 |
| P68 | 100 | 106 | 113 | 104 | 111 | 121 |
| P69 | 100 | 107 | 114 | 104 | 111 | 122 |
| P70 | 101 | 107 | 115 | 105 | 112 | 123 |
| P71 | 101 | 108 | 116 | 105 | 112 | 125 |
| P72 | 102 | 108 | 117 | 106 | 112 | 126 |
| P73 | 102 | 109 | 117 | 106 | 113 | 127 |
| P74 | 102 | 109 | 118 | 107 | 113 | 127 |
| P75 | 103 | 110 | 118 | 107 | 114 | 128 |
| P76 | 103 | 110 | 119 | 107 | 115 | 130 |
| P77 | 104 | 111 | 119 | 107 | 115 | 130 |
| P78 | 104 | 111 | 119 | 108 | 116 | 130 |
| P79 | 105 | 111 | 120 | 108 | 117 | 132 |
| P80 | 105 | 112 | 122 | 108 | 118 | 135 |
| P81 | 106 | 113 | 123 | 108 | 118 | 137 |
| P82 | 106 | 114 | 125 | 109 | 119 | 139 |
| P83 | 106 | 115 | 126 | 109 | 120 | 140 |
| P84 | 107 | 115 | 127 | 109 | 120 | 141 |
| P85 | 107 | 117 | 128 | 109 | 122 | 145 |
| P86 | 107 | 118 | 129 | 110 | 124 | 150 |
| P87 | 109 | 120 | 130 | 110 | 125 | 154 |
| P88 | 110 | 122 | 131 | 110 | 129 | 158 |
| P89 | 110 | 123 | 132 | 111 | 133 | 160 |
| P90 | 111 | 127 | 133 | 111 | 136 | 163 |
| P91 | 112 | 129 | 136 | 112 | 144 | 168 |
| P92 | 114 | 132 | 138 | 113 | 154 | 172 |
| P93 | 117 | 139 | 142 | 115 | 164 | 175 |
| P94 | 117 | 149 | 151 | 115 | 178 | 189 |
| P95 | 120 | 153 | 156 | 118 | 230 | 197 |
Total cholesterol — P5 through P95
NHANES total cholesterol LBXTC, mg/dL. Survey weight WTMECPRP.
| Percentile | Female 20-39 n=1,223 | Female 40-59 n=1,383 | Female 60+ n=1,407 | Male 20-39 n=1,095 | Male 40-59 n=1,243 | Male 60+ n=1,494 |
|---|---|---|---|---|---|---|
| P5 | 126 | 148 | 131 | 127 | 125 | 116 |
| P6 | 127 | 150 | 137 | 129 | 130 | 117 |
| P7 | 131 | 152 | 139 | 130 | 131 | 119 |
| P8 | 133 | 154 | 142 | 131 | 133 | 120 |
| P9 | 135 | 157 | 144 | 134 | 136 | 122 |
| P10 | 137 | 157 | 146 | 135 | 139 | 125 |
| P11 | 138 | 158 | 148 | 138 | 143 | 126 |
| P12 | 140 | 160 | 150 | 139 | 146 | 128 |
| P13 | 141 | 160 | 151 | 140 | 147 | 130 |
| P14 | 141 | 162 | 152 | 141 | 150 | 131 |
| P15 | 142 | 163 | 153 | 143 | 151 | 132 |
| P16 | 143 | 164 | 155 | 145 | 153 | 134 |
| P17 | 145 | 164 | 156 | 146 | 155 | 135 |
| P18 | 146 | 166 | 157 | 146 | 156 | 136 |
| P19 | 147 | 167 | 159 | 148 | 158 | 137 |
| P20 | 148 | 168 | 160 | 148 | 159 | 139 |
| P21 | 149 | 169 | 161 | 149 | 160 | 140 |
| P22 | 150 | 170 | 163 | 149 | 162 | 141 |
| P23 | 151 | 171 | 164 | 150 | 162 | 143 |
| P24 | 152 | 172 | 165 | 151 | 163 | 144 |
| P25 | 153 | 173 | 166 | 152 | 165 | 145 |
| P26 | 153 | 174 | 169 | 152 | 166 | 146 |
| P27 | 154 | 175 | 170 | 153 | 168 | 147 |
| P28 | 155 | 176 | 171 | 155 | 169 | 147 |
| P29 | 156 | 177 | 173 | 156 | 169 | 149 |
| P30 | 157 | 178 | 175 | 156 | 171 | 149 |
| P31 | 158 | 179 | 176 | 158 | 172 | 151 |
| P32 | 159 | 180 | 178 | 159 | 173 | 151 |
| P33 | 161 | 181 | 179 | 159 | 174 | 152 |
| P34 | 161 | 181 | 179 | 160 | 175 | 153 |
| P35 | 162 | 182 | 180 | 161 | 177 | 155 |
| P36 | 162 | 183 | 181 | 161 | 178 | 157 |
| P37 | 164 | 183 | 182 | 162 | 178 | 158 |
| P38 | 164 | 184 | 184 | 164 | 179 | 160 |
| P39 | 165 | 185 | 185 | 164 | 180 | 161 |
| P40 | 165 | 186 | 186 | 165 | 180 | 162 |
| P41 | 166 | 187 | 187 | 167 | 182 | 163 |
| P42 | 166 | 187 | 188 | 167 | 183 | 165 |
| P43 | 167 | 189 | 189 | 169 | 184 | 165 |
| P44 | 168 | 190 | 191 | 169 | 186 | 166 |
| P45 | 168 | 190 | 192 | 171 | 187 | 167 |
| P46 | 169 | 191 | 193 | 172 | 188 | 168 |
| P47 | 170 | 192 | 194 | 173 | 190 | 169 |
| P48 | 171 | 193 | 194 | 174 | 191 | 169 |
| P49 | 172 | 195 | 195 | 175 | 192 | 170 |
| P50 | 172 | 195 | 197 | 176 | 193 | 171 |
| P51 | 173 | 196 | 197 | 177 | 194 | 173 |
| P52 | 174 | 198 | 198 | 178 | 195 | 174 |
| P53 | 174 | 199 | 199 | 179 | 196 | 174 |
| P54 | 175 | 199 | 200 | 180 | 197 | 175 |
| P55 | 176 | 200 | 200 | 180 | 198 | 176 |
| P56 | 177 | 201 | 202 | 182 | 198 | 177 |
| P57 | 177 | 202 | 203 | 183 | 199 | 178 |
| P58 | 178 | 203 | 204 | 184 | 200 | 179 |
| P59 | 179 | 204 | 205 | 185 | 202 | 180 |
| P60 | 180 | 205 | 207 | 187 | 203 | 181 |
| P61 | 180 | 206 | 208 | 188 | 204 | 183 |
| P62 | 181 | 207 | 209 | 188 | 205 | 184 |
| P63 | 182 | 209 | 210 | 189 | 206 | 185 |
| P64 | 184 | 210 | 211 | 190 | 207 | 186 |
| P65 | 184 | 211 | 212 | 191 | 208 | 188 |
| P66 | 185 | 211 | 213 | 192 | 209 | 189 |
| P67 | 185 | 212 | 214 | 193 | 210 | 189 |
| P68 | 186 | 213 | 215 | 194 | 212 | 190 |
| P69 | 187 | 214 | 216 | 195 | 214 | 192 |
| P70 | 188 | 215 | 217 | 196 | 215 | 193 |
| P71 | 188 | 216 | 218 | 198 | 217 | 195 |
| P72 | 190 | 217 | 219 | 199 | 218 | 196 |
| P73 | 191 | 218 | 220 | 201 | 219 | 197 |
| P74 | 192 | 219 | 222 | 202 | 220 | 199 |
| P75 | 194 | 221 | 223 | 204 | 221 | 200 |
| P76 | 194 | 222 | 224 | 206 | 223 | 202 |
| P77 | 195 | 224 | 225 | 207 | 224 | 202 |
| P78 | 196 | 225 | 226 | 209 | 225 | 203 |
| P79 | 198 | 226 | 227 | 211 | 226 | 204 |
| P80 | 199 | 228 | 228 | 212 | 226 | 207 |
| P81 | 200 | 229 | 230 | 213 | 228 | 207 |
| P82 | 201 | 230 | 233 | 214 | 230 | 210 |
| P83 | 203 | 233 | 234 | 215 | 232 | 212 |
| P84 | 206 | 235 | 236 | 217 | 235 | 214 |
| P85 | 208 | 237 | 237 | 219 | 238 | 218 |
| P86 | 209 | 239 | 240 | 221 | 240 | 221 |
| P87 | 210 | 240 | 243 | 223 | 242 | 222 |
| P88 | 212 | 242 | 249 | 225 | 244 | 225 |
| P89 | 214 | 244 | 252 | 228 | 247 | 228 |
| P90 | 217 | 247 | 254 | 231 | 248 | 229 |
| P91 | 218 | 252 | 258 | 233 | 250 | 233 |
| P92 | 221 | 255 | 262 | 239 | 253 | 234 |
| P93 | 224 | 259 | 266 | 243 | 255 | 235 |
| P94 | 227 | 264 | 269 | 245 | 259 | 238 |
| P95 | 229 | 267 | 274 | 251 | 263 | 241 |
HDL cholesterol — P5 through P95
NHANES direct HDL cholesterol LBDHDD, mg/dL. Survey weight WTMECPRP.
| Percentile | Female 20-39 n=1,223 | Female 40-59 n=1,383 | Female 60+ n=1,407 | Male 20-39 n=1,095 | Male 40-59 n=1,243 | Male 60+ n=1,494 |
|---|---|---|---|---|---|---|
| P5 | 35 | 37 | 37 | 31 | 31 | 32 |
| P6 | 35 | 38 | 38 | 32 | 32 | 33 |
| P7 | 36 | 39 | 39 | 33 | 32 | 34 |
| P8 | 37 | 39 | 40 | 33 | 32 | 34 |
| P9 | 38 | 40 | 41 | 34 | 33 | 34 |
| P10 | 39 | 41 | 41 | 34 | 34 | 35 |
| P11 | 39 | 41 | 42 | 35 | 34 | 35 |
| P12 | 40 | 41 | 43 | 35 | 35 | 36 |
| P13 | 40 | 42 | 43 | 35 | 35 | 36 |
| P14 | 41 | 42 | 44 | 36 | 35 | 36 |
| P15 | 41 | 43 | 45 | 36 | 36 | 37 |
| P16 | 42 | 43 | 45 | 37 | 36 | 37 |
| P17 | 42 | 44 | 45 | 37 | 36 | 38 |
| P18 | 43 | 44 | 46 | 37 | 37 | 38 |
| P19 | 43 | 45 | 47 | 38 | 37 | 38 |
| P20 | 44 | 45 | 47 | 38 | 37 | 39 |
| P21 | 44 | 46 | 48 | 38 | 38 | 39 |
| P22 | 45 | 46 | 48 | 39 | 38 | 39 |
| P23 | 45 | 47 | 48 | 39 | 38 | 40 |
| P24 | 46 | 47 | 48 | 39 | 39 | 40 |
| P25 | 46 | 47 | 49 | 40 | 39 | 40 |
| P26 | 47 | 48 | 50 | 40 | 39 | 40 |
| P27 | 47 | 48 | 50 | 40 | 39 | 41 |
| P28 | 47 | 48 | 51 | 40 | 40 | 41 |
| P29 | 48 | 48 | 51 | 41 | 40 | 41 |
| P30 | 48 | 48 | 52 | 41 | 41 | 41 |
| P31 | 48 | 49 | 52 | 41 | 41 | 42 |
| P32 | 48 | 49 | 53 | 41 | 41 | 42 |
| P33 | 48 | 50 | 53 | 41 | 41 | 42 |
| P34 | 49 | 50 | 53 | 41 | 41 | 42 |
| P35 | 49 | 50 | 54 | 41 | 42 | 42 |
| P36 | 50 | 51 | 54 | 42 | 42 | 42 |
| P37 | 50 | 51 | 54 | 42 | 42 | 43 |
| P38 | 51 | 52 | 55 | 42 | 42 | 43 |
| P39 | 51 | 52 | 55 | 42 | 43 | 43 |
| P40 | 52 | 52 | 56 | 43 | 43 | 43 |
| P41 | 52 | 53 | 56 | 43 | 44 | 44 |
| P42 | 53 | 53 | 56 | 43 | 44 | 44 |
| P43 | 53 | 54 | 57 | 44 | 44 | 44 |
| P44 | 53 | 54 | 57 | 44 | 45 | 45 |
| P45 | 54 | 54 | 57 | 44 | 45 | 45 |
| P46 | 54 | 54 | 58 | 44 | 45 | 45 |
| P47 | 54 | 55 | 58 | 45 | 45 | 45 |
| P48 | 55 | 55 | 58 | 45 | 46 | 46 |
| P49 | 55 | 56 | 58 | 46 | 46 | 46 |
| P50 | 55 | 56 | 59 | 46 | 47 | 46 |
| P51 | 55 | 57 | 59 | 46 | 47 | 47 |
| P52 | 56 | 57 | 60 | 47 | 48 | 48 |
| P53 | 56 | 57 | 60 | 47 | 48 | 48 |
| P54 | 57 | 58 | 60 | 47 | 48 | 48 |
| P55 | 57 | 58 | 60 | 48 | 48 | 48 |
| P56 | 58 | 58 | 61 | 48 | 48 | 49 |
| P57 | 58 | 59 | 62 | 48 | 49 | 49 |
| P58 | 58 | 59 | 62 | 48 | 49 | 49 |
| P59 | 59 | 60 | 62 | 48 | 49 | 50 |
| P60 | 59 | 60 | 63 | 49 | 49 | 50 |
| P61 | 60 | 60 | 63 | 49 | 49 | 50 |
| P62 | 60 | 61 | 64 | 49 | 50 | 51 |
| P63 | 60 | 61 | 65 | 50 | 50 | 51 |
| P64 | 60 | 62 | 65 | 50 | 50 | 51 |
| P65 | 61 | 63 | 66 | 50 | 51 | 52 |
| P66 | 62 | 63 | 66 | 51 | 51 | 52 |
| P67 | 62 | 63 | 66 | 51 | 52 | 53 |
| P68 | 62 | 64 | 66 | 51 | 52 | 53 |
| P69 | 63 | 65 | 67 | 51 | 52 | 53 |
| P70 | 63 | 66 | 67 | 52 | 52 | 53 |
| P71 | 63 | 66 | 68 | 53 | 53 | 54 |
| P72 | 63 | 66 | 68 | 53 | 53 | 54 |
| P73 | 64 | 67 | 69 | 54 | 54 | 54 |
| P74 | 64 | 68 | 69 | 54 | 54 | 55 |
| P75 | 65 | 69 | 70 | 54 | 54 | 55 |
| P76 | 65 | 69 | 71 | 55 | 55 | 56 |
| P77 | 66 | 70 | 72 | 56 | 56 | 56 |
| P78 | 66 | 71 | 72 | 56 | 56 | 57 |
| P79 | 66 | 72 | 73 | 57 | 56 | 58 |
| P80 | 67 | 72 | 74 | 57 | 57 | 59 |
| P81 | 67 | 72 | 75 | 58 | 58 | 59 |
| P82 | 69 | 73 | 76 | 59 | 59 | 60 |
| P83 | 69 | 73 | 77 | 59 | 59 | 60 |
| P84 | 70 | 74 | 78 | 60 | 60 | 61 |
| P85 | 71 | 75 | 79 | 60 | 60 | 62 |
| P86 | 72 | 75 | 80 | 61 | 61 | 63 |
| P87 | 73 | 76 | 81 | 62 | 63 | 63 |
| P88 | 74 | 77 | 82 | 63 | 64 | 65 |
| P89 | 75 | 78 | 83 | 64 | 65 | 66 |
| P90 | 76 | 80 | 85 | 65 | 66 | 66 |
| P91 | 77 | 80 | 86 | 66 | 66 | 68 |
| P92 | 79 | 83 | 88 | 66 | 67 | 69 |
| P93 | 81 | 85 | 91 | 68 | 69 | 70 |
| P94 | 82 | 87 | 94 | 69 | 70 | 72 |
| P95 | 84 | 88 | 95 | 70 | 71 | 72 |
Triglycerides — P5 through P95
NHANES fasting triglyceride result LBXTR, mg/dL. Survey weight WTSAFPRP; fasting participants only.
| Percentile | Female 20-39 n=608 | Female 40-59 n=701 | Female 60+ n=689 | Male 20-39 n=539 | Male 40-59 n=612 | Male 60+ n=748 |
|---|---|---|---|---|---|---|
| P5 | 29 | 40 | 41 | 33 | 43 | 43 |
| P6 | 31 | 40 | 43 | 35 | 45 | 44 |
| P7 | 33 | 42 | 47 | 38 | 48 | 47 |
| P8 | 35 | 43 | 48 | 39 | 49 | 49 |
| P9 | 36 | 44 | 51 | 40 | 50 | 51 |
| P10 | 37 | 46 | 52 | 40 | 51 | 53 |
| P11 | 37 | 47 | 53 | 41 | 53 | 53 |
| P12 | 39 | 49 | 54 | 42 | 54 | 54 |
| P13 | 40 | 50 | 54 | 43 | 55 | 56 |
| P14 | 41 | 51 | 55 | 46 | 57 | 56 |
| P15 | 42 | 54 | 57 | 46 | 59 | 58 |
| P16 | 43 | 56 | 58 | 47 | 60 | 58 |
| P17 | 43 | 56 | 59 | 48 | 61 | 60 |
| P18 | 44 | 57 | 60 | 49 | 62 | 61 |
| P19 | 45 | 57 | 61 | 50 | 62 | 63 |
| P20 | 45 | 58 | 62 | 50 | 63 | 65 |
| P21 | 46 | 60 | 63 | 52 | 63 | 66 |
| P22 | 47 | 61 | 65 | 52 | 65 | 67 |
| P23 | 47 | 62 | 66 | 53 | 67 | 69 |
| P24 | 48 | 63 | 67 | 55 | 67 | 70 |
| P25 | 49 | 64 | 69 | 56 | 68 | 71 |
| P26 | 49 | 65 | 71 | 56 | 71 | 73 |
| P27 | 50 | 66 | 71 | 56 | 72 | 75 |
| P28 | 51 | 67 | 72 | 57 | 73 | 78 |
| P29 | 51 | 67 | 73 | 57 | 75 | 78 |
| P30 | 51 | 67 | 74 | 60 | 75 | 79 |
| P31 | 52 | 68 | 75 | 61 | 76 | 80 |
| P32 | 52 | 69 | 77 | 62 | 78 | 81 |
| P33 | 53 | 71 | 77 | 63 | 80 | 82 |
| P34 | 53 | 72 | 78 | 65 | 82 | 83 |
| P35 | 54 | 73 | 78 | 66 | 82 | 84 |
| P36 | 54 | 75 | 79 | 66 | 84 | 85 |
| P37 | 55 | 75 | 79 | 67 | 84 | 86 |
| P38 | 56 | 77 | 79 | 68 | 86 | 87 |
| P39 | 57 | 78 | 83 | 69 | 87 | 87 |
| P40 | 57 | 80 | 84 | 69 | 88 | 88 |
| P41 | 58 | 82 | 84 | 70 | 90 | 90 |
| P42 | 59 | 84 | 87 | 72 | 90 | 92 |
| P43 | 60 | 85 | 87 | 73 | 91 | 92 |
| P44 | 61 | 86 | 88 | 74 | 92 | 94 |
| P45 | 63 | 87 | 90 | 77 | 94 | 96 |
| P46 | 64 | 88 | 92 | 78 | 95 | 97 |
| P47 | 65 | 88 | 94 | 78 | 98 | 97 |
| P48 | 66 | 90 | 95 | 79 | 99 | 98 |
| P49 | 66 | 90 | 97 | 80 | 102 | 99 |
| P50 | 68 | 91 | 97 | 82 | 104 | 101 |
| P51 | 68 | 93 | 98 | 82 | 105 | 102 |
| P52 | 69 | 94 | 99 | 84 | 106 | 103 |
| P53 | 70 | 97 | 100 | 85 | 109 | 104 |
| P54 | 71 | 99 | 102 | 85 | 111 | 106 |
| P55 | 72 | 99 | 104 | 88 | 114 | 107 |
| P56 | 73 | 101 | 106 | 90 | 116 | 109 |
| P57 | 75 | 102 | 107 | 94 | 116 | 110 |
| P58 | 76 | 104 | 108 | 95 | 118 | 111 |
| P59 | 77 | 105 | 110 | 96 | 119 | 111 |
| P60 | 77 | 107 | 112 | 97 | 121 | 113 |
| P61 | 78 | 109 | 112 | 99 | 123 | 114 |
| P62 | 79 | 109 | 113 | 102 | 127 | 115 |
| P63 | 81 | 111 | 116 | 103 | 129 | 118 |
| P64 | 84 | 113 | 117 | 104 | 133 | 120 |
| P65 | 85 | 115 | 119 | 106 | 135 | 122 |
| P66 | 86 | 117 | 122 | 110 | 142 | 122 |
| P67 | 87 | 119 | 123 | 111 | 143 | 125 |
| P68 | 89 | 121 | 125 | 114 | 147 | 126 |
| P69 | 94 | 122 | 126 | 115 | 150 | 129 |
| P70 | 96 | 122 | 128 | 116 | 153 | 130 |
| P71 | 97 | 123 | 130 | 119 | 155 | 131 |
| P72 | 98 | 126 | 131 | 121 | 156 | 131 |
| P73 | 101 | 128 | 132 | 124 | 157 | 134 |
| P74 | 102 | 129 | 134 | 130 | 160 | 138 |
| P75 | 106 | 133 | 135 | 131 | 164 | 139 |
| P76 | 109 | 136 | 137 | 133 | 168 | 142 |
| P77 | 111 | 139 | 138 | 141 | 170 | 143 |
| P78 | 112 | 141 | 140 | 142 | 175 | 150 |
| P79 | 116 | 142 | 142 | 144 | 182 | 152 |
| P80 | 119 | 144 | 142 | 147 | 185 | 155 |
| P81 | 121 | 147 | 144 | 148 | 185 | 155 |
| P82 | 123 | 150 | 148 | 153 | 187 | 157 |
| P83 | 125 | 153 | 152 | 158 | 198 | 160 |
| P84 | 130 | 155 | 156 | 161 | 198 | 160 |
| P85 | 133 | 156 | 158 | 170 | 203 | 162 |
| P86 | 138 | 163 | 164 | 179 | 208 | 165 |
| P87 | 143 | 166 | 167 | 189 | 215 | 170 |
| P88 | 148 | 170 | 172 | 190 | 221 | 177 |
| P89 | 151 | 175 | 178 | 196 | 230 | 185 |
| P90 | 158 | 179 | 184 | 211 | 237 | 197 |
| P91 | 163 | 185 | 186 | 224 | 245 | 199 |
| P92 | 167 | 189 | 190 | 232 | 257 | 215 |
| P93 | 171 | 194 | 200 | 251 | 274 | 228 |
| P94 | 182 | 202 | 209 | 265 | 304 | 232 |
| P95 | 194 | 215 | 220 | 274 | 314 | 242 |
Non-HDL cholesterol — P5 through P95
LBXTC minus LBDHDD on the same NHANES participant, mg/dL. Survey weight WTMECPRP.
| Percentile | Female 20-39 n=1,223 | Female 40-59 n=1,383 | Female 60+ n=1,407 | Male 20-39 n=1,095 | Male 40-59 n=1,243 | Male 60+ n=1,494 |
|---|---|---|---|---|---|---|
| P5 | 69 | 88 | 77 | 73 | 85 | 67 |
| P6 | 71 | 91 | 80 | 76 | 87 | 71 |
| P7 | 74 | 93 | 83 | 77 | 89 | 73 |
| P8 | 77 | 95 | 84 | 79 | 91 | 75 |
| P9 | 78 | 95 | 86 | 82 | 93 | 75 |
| P10 | 79 | 96 | 88 | 84 | 95 | 76 |
| P11 | 80 | 97 | 89 | 86 | 98 | 77 |
| P12 | 82 | 99 | 91 | 87 | 99 | 78 |
| P13 | 83 | 100 | 92 | 90 | 101 | 81 |
| P14 | 84 | 101 | 94 | 91 | 102 | 82 |
| P15 | 85 | 102 | 95 | 92 | 104 | 84 |
| P16 | 86 | 104 | 95 | 93 | 106 | 85 |
| P17 | 87 | 105 | 97 | 95 | 108 | 86 |
| P18 | 89 | 105 | 98 | 96 | 109 | 88 |
| P19 | 90 | 106 | 100 | 97 | 110 | 89 |
| P20 | 91 | 107 | 101 | 98 | 111 | 91 |
| P21 | 91 | 108 | 103 | 98 | 112 | 92 |
| P22 | 92 | 110 | 104 | 100 | 113 | 93 |
| P23 | 93 | 112 | 104 | 100 | 114 | 94 |
| P24 | 94 | 113 | 106 | 101 | 116 | 95 |
| P25 | 95 | 115 | 106 | 102 | 117 | 97 |
| P26 | 96 | 116 | 108 | 104 | 119 | 97 |
| P27 | 97 | 116 | 110 | 105 | 119 | 98 |
| P28 | 98 | 117 | 112 | 105 | 120 | 99 |
| P29 | 99 | 118 | 113 | 106 | 121 | 100 |
| P30 | 99 | 119 | 114 | 107 | 122 | 101 |
| P31 | 100 | 120 | 115 | 108 | 123 | 103 |
| P32 | 101 | 121 | 115 | 109 | 125 | 104 |
| P33 | 101 | 123 | 117 | 110 | 127 | 105 |
| P34 | 102 | 123 | 118 | 111 | 127 | 105 |
| P35 | 102 | 124 | 119 | 112 | 128 | 107 |
| P36 | 103 | 124 | 120 | 113 | 129 | 108 |
| P37 | 104 | 126 | 121 | 114 | 129 | 109 |
| P38 | 105 | 127 | 122 | 115 | 130 | 110 |
| P39 | 105 | 128 | 123 | 116 | 131 | 111 |
| P40 | 106 | 129 | 124 | 116 | 132 | 112 |
| P41 | 107 | 130 | 125 | 118 | 133 | 112 |
| P42 | 107 | 131 | 126 | 118 | 134 | 113 |
| P43 | 108 | 132 | 127 | 120 | 135 | 114 |
| P44 | 110 | 133 | 128 | 121 | 137 | 115 |
| P45 | 110 | 134 | 129 | 122 | 138 | 116 |
| P46 | 111 | 134 | 129 | 124 | 138 | 117 |
| P47 | 112 | 135 | 131 | 125 | 140 | 118 |
| P48 | 113 | 137 | 131 | 126 | 140 | 119 |
| P49 | 114 | 138 | 131 | 127 | 142 | 120 |
| P50 | 115 | 138 | 132 | 128 | 143 | 120 |
| P51 | 115 | 138 | 133 | 130 | 144 | 122 |
| P52 | 116 | 139 | 135 | 130 | 145 | 123 |
| P53 | 117 | 140 | 135 | 131 | 147 | 125 |
| P54 | 118 | 140 | 137 | 133 | 149 | 126 |
| P55 | 118 | 141 | 137 | 134 | 150 | 127 |
| P56 | 120 | 142 | 138 | 135 | 151 | 129 |
| P57 | 120 | 143 | 139 | 136 | 152 | 130 |
| P58 | 121 | 144 | 140 | 137 | 152 | 130 |
| P59 | 121 | 145 | 141 | 138 | 154 | 131 |
| P60 | 122 | 146 | 142 | 139 | 155 | 132 |
| P61 | 123 | 147 | 145 | 139 | 157 | 134 |
| P62 | 123 | 148 | 147 | 141 | 158 | 135 |
| P63 | 124 | 150 | 147 | 142 | 159 | 135 |
| P64 | 125 | 151 | 149 | 143 | 159 | 136 |
| P65 | 126 | 152 | 150 | 144 | 161 | 137 |
| P66 | 128 | 153 | 151 | 145 | 161 | 138 |
| P67 | 129 | 154 | 152 | 146 | 162 | 139 |
| P68 | 130 | 155 | 154 | 148 | 163 | 140 |
| P69 | 130 | 155 | 155 | 150 | 165 | 141 |
| P70 | 132 | 156 | 155 | 151 | 166 | 142 |
| P71 | 133 | 158 | 157 | 152 | 167 | 144 |
| P72 | 134 | 158 | 158 | 153 | 168 | 145 |
| P73 | 134 | 160 | 159 | 155 | 170 | 146 |
| P74 | 135 | 162 | 161 | 157 | 171 | 148 |
| P75 | 137 | 163 | 162 | 158 | 172 | 149 |
| P76 | 138 | 164 | 162 | 159 | 173 | 150 |
| P77 | 140 | 166 | 164 | 162 | 175 | 152 |
| P78 | 141 | 167 | 165 | 164 | 176 | 154 |
| P79 | 142 | 169 | 165 | 167 | 177 | 155 |
| P80 | 142 | 170 | 166 | 168 | 179 | 156 |
| P81 | 144 | 171 | 168 | 168 | 180 | 160 |
| P82 | 146 | 173 | 169 | 170 | 182 | 162 |
| P83 | 148 | 174 | 170 | 171 | 183 | 163 |
| P84 | 149 | 176 | 172 | 172 | 184 | 165 |
| P85 | 151 | 177 | 174 | 174 | 186 | 168 |
| P86 | 152 | 178 | 176 | 178 | 189 | 169 |
| P87 | 153 | 181 | 178 | 179 | 190 | 170 |
| P88 | 155 | 183 | 179 | 183 | 193 | 174 |
| P89 | 157 | 185 | 183 | 186 | 195 | 177 |
| P90 | 163 | 186 | 189 | 190 | 198 | 179 |
| P91 | 166 | 189 | 192 | 193 | 202 | 182 |
| P92 | 169 | 192 | 197 | 194 | 206 | 185 |
| P93 | 172 | 195 | 202 | 196 | 209 | 190 |
| P94 | 176 | 200 | 209 | 198 | 212 | 192 |
| P95 | 181 | 209 | 215 | 203 | 217 | 195 |
High-sensitivity CRP — P5 through P95
NHANES high-sensitivity CRP LBXHSCRP, mg/L. Survey weight WTMECPRP.
| Percentile | Female 20-39 n=1,217 | Female 40-59 n=1,370 | Female 60+ n=1,401 | Male 20-39 n=1,088 | Male 40-59 n=1,237 | Male 60+ n=1,476 |
|---|---|---|---|---|---|---|
| P5 | 0.3 | 0.3 | 0.4 | 0.3 | 0.4 | 0.4 |
| P6 | 0.3 | 0.4 | 0.5 | 0.3 | 0.4 | 0.4 |
| P7 | 0.3 | 0.4 | 0.5 | 0.3 | 0.4 | 0.4 |
| P8 | 0.3 | 0.4 | 0.5 | 0.3 | 0.4 | 0.5 |
| P9 | 0.4 | 0.5 | 0.5 | 0.3 | 0.5 | 0.5 |
| P10 | 0.4 | 0.5 | 0.6 | 0.4 | 0.5 | 0.5 |
| P11 | 0.4 | 0.5 | 0.6 | 0.4 | 0.5 | 0.5 |
| P12 | 0.4 | 0.5 | 0.6 | 0.4 | 0.5 | 0.5 |
| P13 | 0.4 | 0.6 | 0.7 | 0.4 | 0.6 | 0.5 |
| P14 | 0.5 | 0.6 | 0.7 | 0.4 | 0.6 | 0.6 |
| P15 | 0.5 | 0.7 | 0.7 | 0.4 | 0.6 | 0.6 |
| P16 | 0.5 | 0.7 | 0.7 | 0.5 | 0.6 | 0.6 |
| P17 | 0.5 | 0.7 | 0.7 | 0.5 | 0.7 | 0.6 |
| P18 | 0.6 | 0.8 | 0.8 | 0.5 | 0.7 | 0.7 |
| P19 | 0.6 | 0.8 | 0.8 | 0.5 | 0.7 | 0.7 |
| P20 | 0.6 | 0.8 | 0.8 | 0.6 | 0.7 | 0.7 |
| P21 | 0.6 | 0.9 | 0.9 | 0.6 | 0.8 | 0.7 |
| P22 | 0.6 | 0.9 | 0.9 | 0.6 | 0.8 | 0.8 |
| P23 | 0.6 | 0.9 | 0.9 | 0.6 | 0.8 | 0.8 |
| P24 | 0.7 | 1.0 | 1.0 | 0.6 | 0.8 | 0.8 |
| P25 | 0.7 | 1.0 | 1.0 | 0.6 | 0.9 | 0.8 |
| P26 | 0.7 | 1.0 | 1.0 | 0.6 | 0.9 | 0.8 |
| P27 | 0.7 | 1.0 | 1.0 | 0.7 | 1.0 | 0.8 |
| P28 | 0.7 | 1.1 | 1.1 | 0.7 | 1.0 | 0.9 |
| P29 | 0.8 | 1.1 | 1.1 | 0.7 | 1.0 | 0.9 |
| P30 | 0.8 | 1.1 | 1.1 | 0.7 | 1.0 | 0.9 |
| P31 | 0.8 | 1.1 | 1.1 | 0.8 | 1.1 | 1.0 |
| P32 | 0.8 | 1.2 | 1.2 | 0.8 | 1.1 | 1.0 |
| P33 | 0.9 | 1.3 | 1.2 | 0.8 | 1.1 | 1.1 |
| P34 | 0.9 | 1.3 | 1.3 | 0.8 | 1.1 | 1.1 |
| P35 | 0.9 | 1.3 | 1.3 | 0.9 | 1.2 | 1.1 |
| P36 | 1.0 | 1.4 | 1.4 | 0.9 | 1.2 | 1.2 |
| P37 | 1.0 | 1.5 | 1.4 | 1.0 | 1.2 | 1.2 |
| P38 | 1.0 | 1.6 | 1.5 | 1.0 | 1.3 | 1.2 |
| P39 | 1.1 | 1.6 | 1.5 | 1.0 | 1.3 | 1.3 |
| P40 | 1.1 | 1.7 | 1.6 | 1.0 | 1.4 | 1.3 |
| P41 | 1.1 | 1.8 | 1.6 | 1.1 | 1.4 | 1.3 |
| P42 | 1.2 | 1.9 | 1.7 | 1.1 | 1.4 | 1.3 |
| P43 | 1.3 | 1.9 | 1.7 | 1.1 | 1.5 | 1.4 |
| P44 | 1.3 | 2.0 | 1.8 | 1.1 | 1.6 | 1.5 |
| P45 | 1.4 | 2.1 | 1.8 | 1.2 | 1.6 | 1.5 |
| P46 | 1.5 | 2.1 | 1.9 | 1.2 | 1.6 | 1.6 |
| P47 | 1.6 | 2.2 | 2.0 | 1.3 | 1.6 | 1.6 |
| P48 | 1.6 | 2.2 | 2.0 | 1.3 | 1.7 | 1.7 |
| P49 | 1.7 | 2.4 | 2.1 | 1.3 | 1.7 | 1.7 |
| P50 | 1.7 | 2.5 | 2.1 | 1.4 | 1.7 | 1.8 |
| P51 | 1.8 | 2.6 | 2.2 | 1.4 | 1.8 | 1.8 |
| P52 | 1.9 | 2.7 | 2.2 | 1.5 | 1.8 | 1.8 |
| P53 | 2.0 | 2.8 | 2.2 | 1.5 | 1.9 | 1.9 |
| P54 | 2.1 | 2.8 | 2.3 | 1.6 | 1.9 | 1.9 |
| P55 | 2.2 | 2.9 | 2.4 | 1.6 | 2.0 | 2.0 |
| P56 | 2.3 | 2.9 | 2.4 | 1.7 | 2.1 | 2.0 |
| P57 | 2.4 | 3.0 | 2.5 | 1.8 | 2.2 | 2.1 |
| P58 | 2.6 | 3.1 | 2.6 | 1.8 | 2.2 | 2.2 |
| P59 | 2.6 | 3.1 | 2.7 | 1.9 | 2.3 | 2.3 |
| P60 | 2.7 | 3.2 | 2.7 | 1.9 | 2.3 | 2.4 |
| P61 | 2.8 | 3.3 | 2.9 | 1.9 | 2.5 | 2.5 |
| P62 | 2.9 | 3.4 | 2.9 | 2.0 | 2.5 | 2.6 |
| P63 | 3.1 | 3.5 | 3.0 | 2.1 | 2.6 | 2.6 |
| P64 | 3.2 | 3.6 | 3.1 | 2.2 | 2.7 | 2.8 |
| P65 | 3.3 | 3.8 | 3.2 | 2.2 | 2.8 | 2.9 |
| P66 | 3.5 | 3.9 | 3.3 | 2.3 | 2.9 | 2.9 |
| P67 | 3.6 | 4.0 | 3.4 | 2.3 | 3.0 | 3.0 |
| P68 | 3.7 | 4.1 | 3.6 | 2.4 | 3.1 | 3.1 |
| P69 | 3.9 | 4.3 | 3.7 | 2.5 | 3.2 | 3.1 |
| P70 | 4.1 | 4.4 | 3.8 | 2.7 | 3.2 | 3.2 |
| P71 | 4.2 | 4.6 | 3.9 | 2.8 | 3.3 | 3.4 |
| P72 | 4.3 | 5.0 | 4.0 | 2.8 | 3.4 | 3.6 |
| P73 | 4.5 | 5.3 | 4.2 | 2.9 | 3.5 | 3.6 |
| P74 | 4.7 | 5.4 | 4.3 | 3.0 | 3.7 | 3.8 |
| P75 | 4.9 | 5.6 | 4.4 | 3.0 | 3.8 | 4.0 |
| P76 | 5.1 | 5.8 | 4.6 | 3.1 | 3.9 | 4.0 |
| P77 | 5.3 | 5.8 | 4.7 | 3.3 | 4.1 | 4.1 |
| P78 | 5.4 | 6.0 | 5.0 | 3.5 | 4.2 | 4.2 |
| P79 | 5.6 | 6.3 | 5.2 | 3.6 | 4.3 | 4.4 |
| P80 | 5.8 | 6.5 | 5.4 | 3.7 | 4.4 | 4.5 |
| P81 | 6.3 | 6.7 | 5.8 | 3.9 | 4.5 | 4.6 |
| P82 | 6.5 | 7.1 | 6.1 | 4.0 | 4.6 | 4.8 |
| P83 | 6.9 | 7.3 | 6.2 | 4.2 | 4.8 | 5.1 |
| P84 | 7.3 | 7.6 | 6.4 | 4.4 | 5.1 | 5.3 |
| P85 | 7.5 | 7.8 | 6.7 | 4.6 | 5.4 | 5.7 |
| P86 | 7.9 | 8.4 | 6.8 | 5.1 | 5.8 | 5.8 |
| P87 | 8.3 | 8.8 | 7.0 | 5.3 | 5.9 | 6.1 |
| P88 | 8.6 | 8.9 | 7.2 | 5.7 | 6.2 | 6.6 |
| P89 | 9.0 | 9.4 | 7.7 | 5.8 | 6.5 | 7.0 |
| P90 | 9.6 | 10.2 | 8.2 | 6.2 | 6.8 | 7.8 |
| P91 | 10.1 | 11.3 | 8.5 | 6.4 | 7.1 | 8.5 |
| P92 | 10.9 | 11.9 | 9.3 | 6.4 | 7.7 | 9.9 |
| P93 | 11.6 | 12.3 | 10.0 | 6.8 | 8.2 | 10.9 |
| P94 | 12.2 | 13.2 | 11.7 | 7.1 | 9.2 | 11.5 |
| P95 | 13.3 | 14.8 | 12.1 | 8.0 | 10.4 | 12.4 |
Ferritin — P5 through P95
NHANES ferritin LBXFER, ng/mL. Survey weight WTMECPRP.
| Percentile | Female 20-39 n=1,224 | Female 40-59 n=1,396 | Female 60+ n=1,426 | Male 20-39 n=1,102 | Male 40-59 n=1,253 | Male 60+ n=1,501 |
|---|---|---|---|---|---|---|
| P5 | 9 | 9 | 23 | 39 | 45 | 25 |
| P6 | 10 | 10 | 25 | 43 | 48 | 27 |
| P7 | 11 | 12 | 26 | 47 | 53 | 32 |
| P8 | 11 | 13 | 29 | 50 | 55 | 36 |
| P9 | 12 | 14 | 29 | 55 | 58 | 40 |
| P10 | 13 | 15 | 32 | 59 | 63 | 42 |
| P11 | 13 | 15 | 34 | 62 | 68 | 47 |
| P12 | 14 | 16 | 36 | 65 | 72 | 51 |
| P13 | 15 | 18 | 37 | 68 | 75 | 53 |
| P14 | 16 | 18 | 39 | 71 | 77 | 56 |
| P15 | 16 | 20 | 41 | 74 | 80 | 59 |
| P16 | 16 | 22 | 42 | 78 | 81 | 60 |
| P17 | 17 | 23 | 43 | 81 | 82 | 63 |
| P18 | 18 | 25 | 45 | 84 | 84 | 64 |
| P19 | 19 | 26 | 45 | 85 | 88 | 66 |
| P20 | 19 | 27 | 47 | 88 | 92 | 68 |
| P21 | 20 | 29 | 49 | 89 | 95 | 71 |
| P22 | 21 | 31 | 50 | 90 | 100 | 73 |
| P23 | 22 | 33 | 53 | 94 | 103 | 74 |
| P24 | 23 | 33 | 54 | 95 | 106 | 77 |
| P25 | 25 | 34 | 56 | 97 | 109 | 79 |
| P26 | 25 | 36 | 59 | 100 | 112 | 83 |
| P27 | 26 | 36 | 62 | 102 | 113 | 86 |
| P28 | 26 | 38 | 63 | 104 | 116 | 88 |
| P29 | 27 | 38 | 64 | 104 | 117 | 90 |
| P30 | 28 | 40 | 67 | 106 | 120 | 93 |
| P31 | 28 | 43 | 68 | 107 | 123 | 95 |
| P32 | 29 | 44 | 70 | 108 | 128 | 98 |
| P33 | 29 | 45 | 71 | 110 | 130 | 99 |
| P34 | 31 | 46 | 73 | 112 | 133 | 102 |
| P35 | 31 | 48 | 74 | 114 | 135 | 103 |
| P36 | 32 | 49 | 75 | 116 | 137 | 106 |
| P37 | 33 | 50 | 77 | 118 | 139 | 109 |
| P38 | 34 | 51 | 78 | 122 | 143 | 111 |
| P39 | 34 | 53 | 80 | 125 | 146 | 114 |
| P40 | 35 | 55 | 82 | 129 | 149 | 116 |
| P41 | 36 | 58 | 84 | 130 | 151 | 118 |
| P42 | 37 | 60 | 85 | 131 | 153 | 121 |
| P43 | 38 | 62 | 88 | 137 | 155 | 123 |
| P44 | 38 | 64 | 90 | 139 | 158 | 126 |
| P45 | 39 | 65 | 93 | 141 | 160 | 126 |
| P46 | 40 | 66 | 95 | 142 | 164 | 130 |
| P47 | 41 | 66 | 96 | 144 | 169 | 133 |
| P48 | 42 | 67 | 97 | 149 | 173 | 137 |
| P49 | 43 | 69 | 101 | 152 | 178 | 139 |
| P50 | 44 | 70 | 102 | 154 | 183 | 143 |
| P51 | 45 | 71 | 105 | 157 | 184 | 145 |
| P52 | 47 | 73 | 106 | 158 | 189 | 148 |
| P53 | 48 | 74 | 109 | 162 | 191 | 149 |
| P54 | 49 | 74 | 111 | 163 | 196 | 155 |
| P55 | 50 | 76 | 114 | 164 | 201 | 159 |
| P56 | 52 | 80 | 116 | 165 | 203 | 161 |
| P57 | 53 | 81 | 118 | 166 | 207 | 165 |
| P58 | 54 | 83 | 121 | 169 | 211 | 168 |
| P59 | 54 | 84 | 122 | 172 | 214 | 171 |
| P60 | 56 | 86 | 123 | 175 | 220 | 177 |
| P61 | 58 | 88 | 125 | 176 | 226 | 181 |
| P62 | 59 | 93 | 126 | 178 | 231 | 184 |
| P63 | 59 | 95 | 128 | 181 | 237 | 190 |
| P64 | 61 | 98 | 130 | 183 | 244 | 197 |
| P65 | 62 | 98 | 133 | 186 | 248 | 201 |
| P66 | 62 | 100 | 135 | 192 | 251 | 205 |
| P67 | 63 | 102 | 136 | 194 | 254 | 211 |
| P68 | 64 | 105 | 139 | 195 | 261 | 216 |
| P69 | 66 | 107 | 142 | 200 | 264 | 222 |
| P70 | 68 | 109 | 144 | 205 | 267 | 225 |
| P71 | 70 | 113 | 149 | 210 | 273 | 227 |
| P72 | 70 | 116 | 153 | 214 | 280 | 234 |
| P73 | 72 | 119 | 157 | 215 | 289 | 238 |
| P74 | 73 | 121 | 163 | 221 | 292 | 244 |
| P75 | 74 | 124 | 166 | 226 | 295 | 246 |
| P76 | 76 | 127 | 174 | 230 | 297 | 255 |
| P77 | 77 | 130 | 179 | 236 | 300 | 266 |
| P78 | 79 | 133 | 183 | 244 | 307 | 272 |
| P79 | 81 | 137 | 188 | 252 | 309 | 279 |
| P80 | 83 | 139 | 190 | 255 | 315 | 286 |
| P81 | 85 | 143 | 194 | 260 | 319 | 294 |
| P82 | 86 | 148 | 197 | 268 | 323 | 309 |
| P83 | 88 | 155 | 200 | 273 | 336 | 324 |
| P84 | 92 | 160 | 207 | 278 | 344 | 327 |
| P85 | 96 | 163 | 212 | 284 | 356 | 345 |
| P86 | 99 | 171 | 217 | 298 | 363 | 352 |
| P87 | 102 | 183 | 226 | 305 | 385 | 367 |
| P88 | 106 | 194 | 234 | 313 | 398 | 380 |
| P89 | 110 | 202 | 241 | 322 | 419 | 396 |
| P90 | 116 | 209 | 251 | 339 | 442 | 413 |
| P91 | 118 | 225 | 266 | 348 | 464 | 422 |
| P92 | 123 | 239 | 284 | 364 | 491 | 459 |
| P93 | 129 | 251 | 299 | 379 | 524 | 484 |
| P94 | 135 | 262 | 321 | 394 | 528 | 512 |
| P95 | 139 | 289 | 360 | 428 | 556 | 586 |
Hemoglobin — P5 through P95
NHANES complete-blood-count haemoglobin LBXHGB, g/dL. Survey weight WTMECPRP.
| Percentile | Female 20-39 n=1,248 | Female 40-59 n=1,410 | Female 60+ n=1,466 | Male 20-39 n=1,121 | Male 40-59 n=1,271 | Male 60+ n=1,533 |
|---|---|---|---|---|---|---|
| P5 | 11.4 | 11.1 | 11.5 | 13.6 | 13.4 | 11.9 |
| P6 | 11.6 | 11.3 | 11.6 | 13.6 | 13.5 | 12.2 |
| P7 | 11.8 | 11.5 | 11.7 | 13.8 | 13.6 | 12.4 |
| P8 | 11.9 | 11.6 | 11.8 | 13.8 | 13.7 | 12.6 |
| P9 | 12.0 | 11.8 | 11.9 | 13.9 | 13.8 | 12.7 |
| P10 | 12.1 | 11.9 | 12.0 | 13.9 | 13.9 | 12.8 |
| P11 | 12.2 | 12.0 | 12.2 | 14.0 | 13.9 | 12.9 |
| P12 | 12.2 | 12.1 | 12.2 | 14.1 | 14.0 | 13.0 |
| P13 | 12.3 | 12.2 | 12.3 | 14.2 | 14.0 | 13.1 |
| P14 | 12.4 | 12.3 | 12.3 | 14.2 | 14.1 | 13.2 |
| P15 | 12.4 | 12.4 | 12.4 | 14.2 | 14.1 | 13.2 |
| P16 | 12.4 | 12.4 | 12.4 | 14.3 | 14.2 | 13.3 |
| P17 | 12.4 | 12.5 | 12.5 | 14.3 | 14.2 | 13.3 |
| P18 | 12.5 | 12.5 | 12.5 | 14.4 | 14.3 | 13.4 |
| P19 | 12.5 | 12.6 | 12.5 | 14.4 | 14.3 | 13.5 |
| P20 | 12.6 | 12.6 | 12.6 | 14.4 | 14.3 | 13.6 |
| P21 | 12.6 | 12.7 | 12.6 | 14.4 | 14.4 | 13.6 |
| P22 | 12.6 | 12.7 | 12.7 | 14.5 | 14.4 | 13.7 |
| P23 | 12.7 | 12.8 | 12.7 | 14.5 | 14.5 | 13.7 |
| P24 | 12.7 | 12.8 | 12.8 | 14.5 | 14.5 | 13.7 |
| P25 | 12.7 | 12.9 | 12.8 | 14.5 | 14.5 | 13.8 |
| P26 | 12.8 | 12.9 | 12.8 | 14.6 | 14.5 | 13.8 |
| P27 | 12.8 | 12.9 | 12.9 | 14.6 | 14.6 | 13.9 |
| P28 | 12.8 | 13.0 | 12.9 | 14.7 | 14.6 | 14.0 |
| P29 | 12.8 | 13.0 | 12.9 | 14.7 | 14.6 | 14.0 |
| P30 | 12.9 | 13.0 | 13.0 | 14.8 | 14.7 | 14.0 |
| P31 | 12.9 | 13.0 | 13.0 | 14.8 | 14.7 | 14.1 |
| P32 | 12.9 | 13.1 | 13.1 | 14.8 | 14.7 | 14.1 |
| P33 | 13.0 | 13.1 | 13.1 | 14.8 | 14.7 | 14.2 |
| P34 | 13.0 | 13.1 | 13.1 | 14.8 | 14.8 | 14.2 |
| P35 | 13.0 | 13.2 | 13.1 | 14.9 | 14.8 | 14.2 |
| P36 | 13.0 | 13.2 | 13.2 | 14.9 | 14.8 | 14.2 |
| P37 | 13.1 | 13.2 | 13.2 | 14.9 | 14.8 | 14.3 |
| P38 | 13.1 | 13.3 | 13.2 | 14.9 | 14.9 | 14.3 |
| P39 | 13.1 | 13.3 | 13.2 | 15.0 | 14.9 | 14.4 |
| P40 | 13.2 | 13.3 | 13.2 | 15.0 | 15.0 | 14.4 |
| P41 | 13.2 | 13.3 | 13.3 | 15.0 | 15.0 | 14.4 |
| P42 | 13.2 | 13.4 | 13.3 | 15.0 | 15.0 | 14.5 |
| P43 | 13.2 | 13.4 | 13.4 | 15.0 | 15.0 | 14.5 |
| P44 | 13.3 | 13.4 | 13.4 | 15.1 | 15.0 | 14.5 |
| P45 | 13.3 | 13.4 | 13.4 | 15.1 | 15.0 | 14.5 |
| P46 | 13.3 | 13.5 | 13.4 | 15.1 | 15.1 | 14.6 |
| P47 | 13.3 | 13.5 | 13.4 | 15.1 | 15.1 | 14.6 |
| P48 | 13.4 | 13.5 | 13.5 | 15.1 | 15.1 | 14.6 |
| P49 | 13.4 | 13.5 | 13.5 | 15.2 | 15.2 | 14.7 |
| P50 | 13.4 | 13.6 | 13.5 | 15.2 | 15.2 | 14.7 |
| P51 | 13.4 | 13.6 | 13.5 | 15.2 | 15.2 | 14.7 |
| P52 | 13.4 | 13.6 | 13.6 | 15.3 | 15.3 | 14.8 |
| P53 | 13.5 | 13.7 | 13.6 | 15.3 | 15.3 | 14.8 |
| P54 | 13.5 | 13.7 | 13.7 | 15.3 | 15.3 | 14.8 |
| P55 | 13.5 | 13.7 | 13.7 | 15.3 | 15.3 | 14.9 |
| P56 | 13.5 | 13.7 | 13.7 | 15.4 | 15.4 | 14.9 |
| P57 | 13.6 | 13.8 | 13.7 | 15.4 | 15.4 | 15.0 |
| P58 | 13.6 | 13.8 | 13.8 | 15.4 | 15.5 | 15.0 |
| P59 | 13.6 | 13.8 | 13.8 | 15.5 | 15.5 | 15.0 |
| P60 | 13.6 | 13.9 | 13.8 | 15.5 | 15.5 | 15.0 |
| P61 | 13.7 | 13.9 | 13.9 | 15.5 | 15.5 | 15.1 |
| P62 | 13.7 | 13.9 | 13.9 | 15.5 | 15.5 | 15.1 |
| P63 | 13.7 | 13.9 | 13.9 | 15.6 | 15.6 | 15.1 |
| P64 | 13.7 | 13.9 | 13.9 | 15.6 | 15.6 | 15.2 |
| P65 | 13.8 | 14.0 | 13.9 | 15.6 | 15.6 | 15.2 |
| P66 | 13.8 | 14.0 | 14.0 | 15.6 | 15.6 | 15.2 |
| P67 | 13.8 | 14.0 | 14.0 | 15.6 | 15.6 | 15.3 |
| P68 | 13.8 | 14.1 | 14.0 | 15.7 | 15.7 | 15.3 |
| P69 | 13.9 | 14.1 | 14.1 | 15.7 | 15.7 | 15.3 |
| P70 | 13.9 | 14.1 | 14.1 | 15.7 | 15.7 | 15.4 |
| P71 | 13.9 | 14.1 | 14.2 | 15.7 | 15.8 | 15.4 |
| P72 | 14.0 | 14.2 | 14.2 | 15.7 | 15.8 | 15.5 |
| P73 | 14.0 | 14.2 | 14.2 | 15.8 | 15.8 | 15.5 |
| P74 | 14.0 | 14.2 | 14.2 | 15.8 | 15.8 | 15.6 |
| P75 | 14.0 | 14.3 | 14.3 | 15.8 | 15.9 | 15.6 |
| P76 | 14.1 | 14.3 | 14.3 | 15.9 | 15.9 | 15.6 |
| P77 | 14.1 | 14.3 | 14.3 | 15.9 | 15.9 | 15.7 |
| P78 | 14.1 | 14.4 | 14.3 | 15.9 | 15.9 | 15.7 |
| P79 | 14.2 | 14.4 | 14.4 | 16.0 | 16.0 | 15.7 |
| P80 | 14.2 | 14.5 | 14.4 | 16.0 | 16.0 | 15.8 |
| P81 | 14.2 | 14.6 | 14.5 | 16.0 | 16.0 | 15.8 |
| P82 | 14.3 | 14.6 | 14.5 | 16.0 | 16.0 | 15.8 |
| P83 | 14.3 | 14.7 | 14.6 | 16.1 | 16.1 | 15.9 |
| P84 | 14.4 | 14.7 | 14.6 | 16.1 | 16.2 | 15.9 |
| P85 | 14.4 | 14.7 | 14.7 | 16.2 | 16.2 | 16.0 |
| P86 | 14.5 | 14.7 | 14.7 | 16.2 | 16.2 | 16.0 |
| P87 | 14.5 | 14.8 | 14.8 | 16.2 | 16.3 | 16.1 |
| P88 | 14.6 | 14.8 | 14.9 | 16.3 | 16.3 | 16.2 |
| P89 | 14.6 | 14.9 | 14.9 | 16.4 | 16.4 | 16.2 |
| P90 | 14.7 | 14.9 | 15.0 | 16.4 | 16.4 | 16.3 |
| P91 | 14.7 | 15.0 | 15.1 | 16.5 | 16.5 | 16.4 |
| P92 | 14.8 | 15.0 | 15.2 | 16.5 | 16.5 | 16.5 |
| P93 | 14.8 | 15.1 | 15.2 | 16.6 | 16.6 | 16.5 |
| P94 | 14.9 | 15.2 | 15.4 | 16.7 | 16.7 | 16.7 |
| P95 | 15.0 | 15.2 | 15.4 | 16.8 | 16.8 | 16.9 |
Estimated GFR — P5 through P95
NIDDK's race-free 2021 CKD-EPI creatinine equation over NHANES serum creatinine LBXSCR, age and sex; mL/min/1.73 m2. Survey weight WTMECPRP.
| Percentile | Female 20-39 n=1,219 | Female 40-59 n=1,377 | Female 60+ n=1,401 | Male 20-39 n=1,089 | Male 40-59 n=1,238 | Male 60+ n=1,482 |
|---|---|---|---|---|---|---|
| P5 | 85 | 68 | 44 | 81 | 68 | 47 |
| P6 | 86 | 70 | 46 | 83 | 70 | 50 |
| P7 | 88 | 72 | 47 | 85 | 70 | 52 |
| P8 | 90 | 74 | 49 | 87 | 72 | 52 |
| P9 | 91 | 75 | 51 | 88 | 72 | 54 |
| P10 | 91 | 76 | 52 | 89 | 74 | 56 |
| P11 | 93 | 78 | 53 | 90 | 75 | 57 |
| P12 | 94 | 78 | 54 | 91 | 76 | 57 |
| P13 | 95 | 79 | 55 | 92 | 76 | 59 |
| P14 | 96 | 80 | 56 | 93 | 77 | 60 |
| P15 | 96 | 81 | 57 | 94 | 78 | 60 |
| P16 | 97 | 82 | 58 | 95 | 78 | 62 |
| P17 | 98 | 83 | 58 | 96 | 79 | 62 |
| P18 | 99 | 83 | 59 | 96 | 80 | 63 |
| P19 | 99 | 84 | 60 | 97 | 80 | 64 |
| P20 | 100 | 85 | 61 | 98 | 81 | 64 |
| P21 | 101 | 86 | 62 | 98 | 81 | 65 |
| P22 | 101 | 86 | 62 | 98 | 82 | 66 |
| P23 | 101 | 86 | 63 | 99 | 83 | 67 |
| P24 | 102 | 87 | 63 | 100 | 83 | 67 |
| P25 | 102 | 88 | 64 | 101 | 84 | 67 |
| P26 | 103 | 88 | 65 | 101 | 84 | 68 |
| P27 | 103 | 89 | 65 | 102 | 85 | 69 |
| P28 | 104 | 89 | 66 | 103 | 86 | 69 |
| P29 | 105 | 90 | 67 | 103 | 87 | 70 |
| P30 | 105 | 90 | 67 | 104 | 87 | 71 |
| P31 | 106 | 91 | 68 | 105 | 88 | 71 |
| P32 | 107 | 92 | 68 | 105 | 88 | 72 |
| P33 | 108 | 93 | 69 | 106 | 89 | 72 |
| P34 | 109 | 93 | 70 | 107 | 90 | 73 |
| P35 | 110 | 94 | 70 | 108 | 90 | 73 |
| P36 | 110 | 94 | 71 | 108 | 90 | 73 |
| P37 | 111 | 95 | 71 | 109 | 91 | 74 |
| P38 | 111 | 96 | 72 | 110 | 91 | 75 |
| P39 | 111 | 96 | 72 | 110 | 92 | 75 |
| P40 | 112 | 97 | 73 | 110 | 92 | 76 |
| P41 | 113 | 97 | 73 | 111 | 92 | 77 |
| P42 | 113 | 98 | 74 | 111 | 93 | 77 |
| P43 | 114 | 99 | 75 | 112 | 94 | 77 |
| P44 | 114 | 99 | 75 | 112 | 94 | 78 |
| P45 | 115 | 100 | 75 | 113 | 95 | 78 |
| P46 | 115 | 100 | 76 | 113 | 95 | 79 |
| P47 | 116 | 100 | 77 | 114 | 95 | 80 |
| P48 | 116 | 101 | 77 | 114 | 96 | 81 |
| P49 | 116 | 101 | 78 | 115 | 96 | 81 |
| P50 | 116 | 101 | 78 | 115 | 96 | 82 |
| P51 | 117 | 102 | 79 | 116 | 97 | 82 |
| P52 | 117 | 102 | 80 | 116 | 98 | 82 |
| P53 | 118 | 102 | 80 | 116 | 99 | 83 |
| P54 | 118 | 103 | 80 | 116 | 99 | 84 |
| P55 | 118 | 103 | 81 | 116 | 99 | 84 |
| P56 | 119 | 103 | 82 | 117 | 100 | 85 |
| P57 | 119 | 103 | 83 | 117 | 101 | 85 |
| P58 | 119 | 103 | 83 | 117 | 101 | 86 |
| P59 | 119 | 104 | 84 | 117 | 101 | 86 |
| P60 | 120 | 104 | 84 | 117 | 102 | 87 |
| P61 | 120 | 104 | 85 | 118 | 102 | 87 |
| P62 | 120 | 104 | 86 | 118 | 102 | 87 |
| P63 | 120 | 105 | 86 | 118 | 102 | 88 |
| P64 | 120 | 105 | 87 | 119 | 103 | 88 |
| P65 | 121 | 105 | 88 | 119 | 103 | 88 |
| P66 | 121 | 105 | 88 | 119 | 104 | 89 |
| P67 | 121 | 106 | 88 | 120 | 104 | 89 |
| P68 | 121 | 106 | 89 | 120 | 105 | 90 |
| P69 | 122 | 107 | 90 | 120 | 105 | 91 |
| P70 | 122 | 107 | 91 | 120 | 105 | 91 |
| P71 | 122 | 107 | 91 | 121 | 105 | 92 |
| P72 | 122 | 108 | 91 | 121 | 105 | 92 |
| P73 | 123 | 108 | 92 | 121 | 106 | 93 |
| P74 | 123 | 108 | 92 | 122 | 106 | 93 |
| P75 | 123 | 108 | 93 | 122 | 106 | 93 |
| P76 | 124 | 109 | 93 | 122 | 107 | 94 |
| P77 | 124 | 109 | 94 | 122 | 107 | 94 |
| P78 | 124 | 109 | 94 | 123 | 107 | 95 |
| P79 | 124 | 109 | 94 | 123 | 107 | 95 |
| P80 | 125 | 110 | 95 | 123 | 108 | 95 |
| P81 | 125 | 110 | 95 | 124 | 108 | 96 |
| P82 | 125 | 111 | 96 | 124 | 108 | 96 |
| P83 | 126 | 111 | 96 | 125 | 109 | 96 |
| P84 | 126 | 111 | 96 | 125 | 109 | 96 |
| P85 | 126 | 112 | 96 | 125 | 110 | 97 |
| P86 | 126 | 112 | 97 | 125 | 110 | 97 |
| P87 | 127 | 112 | 97 | 126 | 111 | 97 |
| P88 | 127 | 112 | 98 | 126 | 111 | 98 |
| P89 | 127 | 113 | 99 | 127 | 111 | 98 |
| P90 | 128 | 113 | 99 | 127 | 112 | 99 |
| P91 | 128 | 113 | 99 | 127 | 112 | 99 |
| P92 | 129 | 114 | 99 | 128 | 113 | 99 |
| P93 | 129 | 114 | 100 | 128 | 113 | 100 |
| P94 | 130 | 115 | 100 | 128 | 113 | 101 |
| P95 | 130 | 115 | 101 | 129 | 114 | 101 |
Hemoglobin A1c — American Diabetes Association, Standards of Care in Diabetes
| Band | From | Published as |
|---|---|---|
| Below the prediabetes range | — | Everything below the first cut. |
| Prediabetes | 5.7 % | A1C 5.7-6.4% (39-47 mmol/mol) — Table 2.2, "Criteria defining prediabetes in nonpregnant individuals". |
| Diabetes range | 6.5 % | A1C >=6.5% (>=48 mmol/mol) — Table 2.1, "Criteria for the diagnosis of diabetes in nonpregnant individuals". |
This report named no assay, so this reads your result as an NGSP-certified laboratory method traceable to the DCCT reference assay — what US clinical laboratories are graded against, and what the % scale itself implies, since the IFCC scale reports mmol/mol. It would not be a safe assumption for a home kit or a waived point-of-care device.
- Edition
- Standards of Care in Diabetes—2026, Section 2, Tables 2.1 and 2.2; doi:10.2337/dc26-S002; released 2025-12-08
- Specimen
- whole blood, reported in %
- Assay
- A laboratory HbA1c certified by the National Glycohemoglobin Standardization Program as traceable to the DCCT reference assay. Standardization: NGSP / DCCT.
- Applies only when
- your report prints %; the laboratory resolved the value rather than printing a bound like ">60"
- Direction
- Lower is better, as far as the literature supports.
- Re-checked
- 2026-08-19, every 180 days. Has the ADA published a Standards of Care edition later than 2026 in which Table 2.1 or Table 2.2 states an A1C cut point other than 5.7% and 6.5%?
- Attribution
- American Diabetes Association Professional Practice Committee. 2. Diagnosis and Classification of Diabetes: Standards of Care in Diabetes—2026. Diabetes Care 2026;49(Suppl. 1):S27-S49. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- The ADA is explicit that one number diagnoses nobody: "In the absence of a clear clinical diagnosis... confirmatory tests are necessary to establish the diagnosis. This can be accomplished by two abnormal screening test results, measured either at the same time or at two different time points."
- Risk does not begin at the cut point. The ADA's own footnote to Table 2.2 says risk "is continuous, extending below the lower limit of the range and becoming disproportionately greater at the higher end of the range", so the line between the first two bands is drawn across a gradient.
- Several common conditions break the link between A1C and average glucose, and where they apply this scale does not: "In conditions associated with an altered relationship between A1C and glycemia, such as some hemoglobin variants, pregnancy, glucose-6-phosphate dehydrogenase deficiency, HIV, and conditions that may alter red blood cell turnover, plasma glucose criteria should be used to diagnose diabetes."
- A low A1C is not automatically a better A1C. Shortened red-cell survival from haemolysis, recent bleeding or a recent transfusion lowers A1C without lowering average glucose.
- Both ADA tables are captioned "in nonpregnant individuals". These cut points are not the pregnancy criteria and not the paediatric tables, and this platform holds no pregnancy field to check that with.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The lower boundary is genuinely disputed. NICE (PH38, 2012) puts non-diabetic hyperglycaemia at 6.0-6.4%, as did the 2009 International Expert Committee, so an A1C of 5.8% is prediabetes to the ADA and normal to NICE. The >=6.5% diagnostic cut point is agreed across ADA, WHO, NICE and IDF.
Lab glucose — American Diabetes Association, Standards of Care in Diabetes
| Band | From | Published as |
|---|---|---|
| Below the prediabetes range | — | Everything below the first cut. |
| Impaired fasting glucose | 100 mg/dL | Fasting plasma glucose 100-125 mg/dL (5.6-6.9 mmol/L) — Table 2.2, "Criteria defining prediabetes in nonpregnant individuals". |
| Diabetes range | 126 mg/dL | Fasting plasma glucose >=126 mg/dL (7.0 mmol/L) — Table 2.1. "Fasting is defined as no caloric intake for at least 8 h." |
This report named no assay, so this reads your result as a venous plasma glucose from a clinical laboratory. A fingerstick meter reading is not that: meters are permitted a far wider error and the ADA does not place anybody on these cut points with one.
- Edition
- Standards of Care in Diabetes—2026, Section 2, Tables 2.1 and 2.2; doi:10.2337/dc26-S002
- Specimen
- plasma, reported in mg/dL
- Assay
- A venous plasma glucose measured by a clinical laboratory. Standardization: NIST SRM 917/965 traceability, as CLIA proficiency testing grades glucose against.
- Applies only when
- your report prints mg/dL; the laboratory resolved the value rather than printing a bound like ">60"; the draw is recorded as fasting
- Direction
- U-shaped — both tails are adverse, and the bands name both.
- Re-checked
- 2026-08-19, every 180 days. Has the ADA published a Standards of Care edition later than 2026 in which Table 2.1 or Table 2.2 states a fasting plasma glucose cut point other than 100 mg/dL and 126 mg/dL?
- Attribution
- American Diabetes Association Professional Practice Committee. 2. Diagnosis and Classification of Diabetes: Standards of Care in Diabetes—2026. Diabetes Care 2026;49(Suppl. 1):S27-S49. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- This band is shown only on a draw the report recorded as fasting, because the ADA defines these cut points on one: "Fasting is defined as no caloric intake for at least 8 h." A draw whose fasting state the report did not state gets the printed refusal instead, because "we do not know" is not "yes".
- The ADA requires confirmation before anything follows from a single number: "In the absence of a clear clinical diagnosis... confirmatory tests are necessary to establish the diagnosis."
- The lowest band is named by position and not called normal. A fasting glucose can also be too low — the ADA's own hypoglycaemia classification starts at 70 mg/dL — and no band here says anything about that tail.
- Both ADA tables are captioned "in nonpregnant individuals", and gestational diabetes uses a different fasting threshold. This platform holds no pregnancy field.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The WHO and the IDF put impaired fasting glucose at 110-125 mg/dL, not 100-125, so a fasting glucose of 105 mg/dL is prediabetes to the ADA and normal to the WHO. The >=126 mg/dL diagnostic cut point is agreed.
Total cholesterol — NCEP Adult Treatment Panel III
| Band | From | Published as |
|---|---|---|
| Desirable | — | Everything below the first cut. |
| Borderline high | 200 mg/dL | Table II.2-4, "ATP III Classification of Total Cholesterol": 200-239 mg/dL Borderline high. |
| High | 240 mg/dL | Table II.2-4: >=240 mg/dL High. |
This report named no assay, so this reads your result as a clinical laboratory cholesterol standardized against the CDC reference method — the programme US laboratories are certified through, and the one ATP III's numbers were set on.
- Edition
- NIH Publication No. 02-5215 (September 2002), Table II.2-4, page II-5
- Specimen
- serum, reported in mg/dL
- Assay
- An enzymatic total cholesterol assay run by a clinical laboratory. Standardization: CDC Lipid Standardization Program / Cholesterol Reference Method Laboratory Network.
- Applies only when
- your report prints mg/dL; the laboratory resolved the value rather than printing a bound like ">60"
- Direction
- Position only — we draw no direction on this one.
- Re-checked
- 2026-08-19, every 365 days. Has any US body republished a total-cholesterol CLASSIFICATION table with cut points other than 200 and 240 mg/dL? The 2018 AHA/ACC and 2026 ACC/AHA cholesterol guidelines are risk-based and publish no replacement classification, which is why ATP III's table is still the one cited.
- Attribution
- Third Report of the National Cholesterol Education Program (NCEP) Expert Panel on Detection, Evaluation, and Treatment of High Blood Cholesterol in Adults (Adult Treatment Panel III), Final Report. NIH Publication No. 02-5215, September 2002. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- Total cholesterol is a sum, and the sum hides its parts: a high total driven by high HDL cholesterol is not the same finding as one driven by high LDL cholesterol. ATP III itself treats LDL cholesterol as "the primary target of therapy", not this number.
- ATP III is an adult table, aged 20 and over. It is not the paediatric classification, and cholesterol rises substantially through pregnancy — this platform holds no pregnancy field to check that with.
- Nothing on this scale is a treatment decision. Current US guidance drives treatment off overall cardiovascular risk rather than off a cholesterol category, so a band here does not correspond to anything a clinician would act on by itself.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
LDL cholesterol — NCEP Adult Treatment Panel III
| Band | From | Published as |
|---|---|---|
| Optimal | — | Everything below the first cut. |
| Near optimal / above optimal | 100 mg/dL | Table II.2-4, "ATP III Classification of LDL Cholesterol": 100-129 mg/dL Near optimal/above optimal. |
| Borderline high | 130 mg/dL | Table II.2-4: 130-159 mg/dL Borderline high. |
| High | 160 mg/dL | Table II.2-4: 160-189 mg/dL High. |
| Very high | 190 mg/dL | Table II.2-4: >=190 mg/dL Very high. |
The report does not say whether this LDL cholesterol was calculated from the other lipids or measured directly, and the two are not interchangeable at the margins. ATP III's classification was set on calculated values, so that is what this reads yours as.
- Edition
- NIH Publication No. 02-5215 (September 2002), Table II.2-4, page II-5
- Specimen
- serum, reported in mg/dL
- Assay
- An LDL cholesterol result from a clinical laboratory, calculated or direct. Standardization: CDC Lipid Standardization Program / Cholesterol Reference Method Laboratory Network.
- Applies only when
- your report prints mg/dL; the laboratory resolved the value rather than printing a bound like ">60"; the same draw's triglycerides are under 400 mg/dL
- Direction
- Position only — we draw no direction on this one.
- Re-checked
- 2026-08-19, every 365 days. Has any US body republished an LDL-cholesterol CLASSIFICATION table with cut points other than 100/130/160/190 mg/dL? The 2018 and 2026 ACC/AHA guidelines are risk-based and publish thresholds for treatment decisions rather than a replacement classification.
- Attribution
- Third Report of the National Cholesterol Education Program (NCEP) Expert Panel on Detection, Evaluation, and Treatment of High Blood Cholesterol in Adults (Adult Treatment Panel III), Final Report. NIH Publication No. 02-5215, September 2002. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- "Optimal" is ATP III's own word for the lowest band and it is a population classification, not a verdict on you. The panel set it against coronary risk in untreated adults; it is not a target anybody has given you.
- If your triglycerides on the same draw were at or above 400 mg/dL this ruler refuses rather than banding you, because the equation most laboratories calculate LDL cholesterol with stops being accurate there.
- ATP III is an adult table, aged 20 and over, and it is not the paediatric classification. LDL cholesterol rises through pregnancy and this platform holds no pregnancy field.
- Newer US guidelines do not classify LDL cholesterol at all — they drive treatment off overall cardiovascular risk. A band here is a position on a 2002 table, not a current treatment recommendation.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The 2019 ESC/EAS guideline sets goals as low as 55 mg/dL for very-high-risk patients, which is a treatment target conditioned on risk rather than a classification of the population, and is not comparable with these bands.
HDL cholesterol — NCEP Adult Treatment Panel III
| Band | From | Published as |
|---|---|---|
| Low HDL cholesterol | — | Everything below the first cut. |
| Between ATP III's two cut points (our label) | 40 mg/dL | Table II.3-2, "ATP III Classification of HDL Cholesterol", names only two categories: <40 mg/dL Low HDL cholesterol and >=60 mg/dL High HDL cholesterol. The span between them is unnamed by the panel, so this label is ours and says only where the value sits. |
| High HDL cholesterol | 60 mg/dL | Table II.3-2: >=60 mg/dL High HDL cholesterol. |
This report named no assay, so this reads your result as a standardized clinical-laboratory HDL cholesterol. Direct HDL assays from different manufacturers disagree most in exactly the samples that are least ordinary, which is worth knowing near a cut point.
- Edition
- NIH Publication No. 02-5215 (September 2002), Table II.3-2, page II-10
- Specimen
- serum, reported in mg/dL
- Assay
- A direct HDL cholesterol assay run by a clinical laboratory. Standardization: CDC Lipid Standardization Program / Cholesterol Reference Method Laboratory Network.
- Applies only when
- your report prints mg/dL; the laboratory resolved the value rather than printing a bound like ">60"
- Direction
- Position only — we draw no direction on this one.
- Re-checked
- 2026-08-19, every 365 days. Has any US body republished an HDL-cholesterol classification with cut points other than 40 and 60 mg/dL, or has evidence of harm at very high HDL cholesterol moved a body to publish an upper category?
- Attribution
- Third Report of the National Cholesterol Education Program (NCEP) Expert Panel on Detection, Evaluation, and Treatment of High Blood Cholesterol in Adults (Adult Treatment Panel III), Final Report. NIH Publication No. 02-5215, September 2002. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- The middle band's name is ours, not the panel's. ATP III names only two categories — low below 40 and high at or above 60 — and says nothing about the span between them, so the label there states a position and claims nothing else.
- "High HDL cholesterol" is ATP III's category name, not a compliment. In the Copenhagen General Population Study all-cause mortality was lowest around 73 mg/dL in men and 93 mg/dL in women and rose again above that, roughly doubling in men at or above 116 mg/dL. This is one of the biomarkers where more is not better, and no band here draws a direction.
- The cut point is sex-blind on purpose, and ATP III says so: "A categorical low HDL cholesterol should be defined as a level of <40 mg/dL, in both men and women." Other bodies disagree.
- ATP III is an adult table, aged 20 and over. HDL cholesterol rises substantially through pregnancy and the paediatric cut points are not 40 and 60; this platform holds no pregnancy field to check either with.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The National Lipid Association classifies low HDL cholesterol as below 40 mg/dL in men and below 50 mg/dL in women, and publishes no high category at all — so a woman at 45 mg/dL sits between ATP III's cut points here and in the NLA's low category there.
Triglycerides — NCEP Adult Treatment Panel III
| Band | From | Published as |
|---|---|---|
| Normal | — | Everything below the first cut. |
| Borderline high | 150 mg/dL | Table II.3-1, "ATP III Classification of Serum Triglycerides": 150-199 mg/dL Borderline high. |
| High | 200 mg/dL | Table II.3-1: 200-499 mg/dL High. |
| Very high | 500 mg/dL | Table II.3-1: >=500 mg/dL Very high. |
This report named no assay, so this reads your result as a standardized clinical-laboratory triglyceride measurement — the programme ATP III's numbers were set on.
- Edition
- NIH Publication No. 02-5215 (September 2002), Table II.3-1, page II-7
- Specimen
- serum, reported in mg/dL
- Assay
- An enzymatic triglyceride assay run by a clinical laboratory. Standardization: CDC Lipid Standardization Program.
- Applies only when
- your report prints mg/dL; the laboratory resolved the value rather than printing a bound like ">60"; the draw is recorded as fasting
- Direction
- Position only — we draw no direction on this one.
- Re-checked
- 2026-08-19, every 365 days. Has any US body republished a triglyceride classification with cut points other than 150/200/500 mg/dL? The 2018 ACC/AHA guideline uses 175 mg/dL as a risk-enhancing factor, which is a risk modifier rather than a replacement classification.
- Attribution
- Third Report of the National Cholesterol Education Program (NCEP) Expert Panel on Detection, Evaluation, and Treatment of High Blood Cholesterol in Adults (Adult Treatment Panel III), Final Report. NIH Publication No. 02-5215, September 2002. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- This band is shown only on a draw the report recorded as fasting. Triglycerides rise for hours after a meal — that is what they do — so a non-fasting number placed on a fasting table would be a manufactured result. A draw whose fasting state the report did not state gets the printed refusal instead.
- "Normal" is ATP III's own word for the lowest band. It is the panel's category name and not a statement that nothing else about your lipids matters.
- ATP III is an adult table, aged 20 and over. Triglycerides roughly double through the third trimester of pregnancy and the paediatric cut points differ; this platform holds no pregnancy field.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The 2018 AHA/ACC cholesterol guideline treats fasting triglycerides at or above 175 mg/dL as a risk-enhancing factor, which lands inside ATP III's borderline-high band.
Non-HDL cholesterol — National Lipid Association
| Band | From | Published as |
|---|---|---|
| Desirable | — | Everything below the first cut. |
| Above desirable | 130 mg/dL | Table 1: non-HDL-C 130-159 mg/dL, "Above desirable". |
| Borderline high | 160 mg/dL | Table 1: non-HDL-C 160-189 mg/dL, "Borderline high". |
| High | 190 mg/dL | Table 1: non-HDL-C 190-219 mg/dL, "High". |
| Very high | 220 mg/dL | Table 1: non-HDL-C >=220 mg/dL, "Very high". |
Non-HDL cholesterol is a subtraction the laboratory performs, so it inherits both of its inputs' assays. This report named no assay; this reads both as standardized clinical-laboratory measurements.
- Edition
- J Clin Lipidol 2015;9(2):129-169, Table 1, "Classifications of cholesterol and triglyceride levels in mg/dL"
- Specimen
- serum, reported in mg/dL
- Assay
- Total cholesterol minus HDL cholesterol, both from a standardized clinical-laboratory lipid panel. Standardization: CDC Lipid Standardization Program.
- Applies only when
- your report prints mg/dL; the laboratory resolved the value rather than printing a bound like ">60"
- Direction
- Position only — we draw no direction on this one.
- Re-checked
- 2026-08-19, every 365 days. Has the National Lipid Association, or any other US body, republished a non-HDL-cholesterol classification with cut points other than 130/160/190/220 mg/dL?
- Attribution
- Jacobson TA, Ito MK, Maki KC, et al. National Lipid Association Recommendations for Patient-Centered Management of Dyslipidemia: Part 1 — Full Report. J Clin Lipidol 2015;9(2):129-169, Table 1. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- Non-HDL cholesterol is not measured; it is your total cholesterol minus your HDL cholesterol. Anything that moves either of those moves this, and it carries both of their uncertainties.
- The NLA's bands sit 30 mg/dL above its LDL-cholesterol bands by construction, because non-HDL cholesterol includes the cholesterol carried by triglyceride-rich particles as well.
- This is an adult classification. It is not the paediatric table, and this platform holds no pregnancy field.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
Apolipoprotein B — National Lipid Association
| Band | From | Published as |
|---|---|---|
| Below the National Lipid Association's risk-enhancing threshold | — | Everything below the first cut. |
| At or above the risk-enhancing threshold | 130 mg/dL | "An LDL-C level of 160 mg/dL and an apoB level of 130 mg/dL correspond to approximately the 90th percentiles of the untreated adult population (Table 1), both of which are risk-enhancing factors and warrant a clinician-patient discussion regarding initiation or intensification of LLT." |
This report named no assay, so this reads your result as a clinical-laboratory apolipoprotein B calibrated to the WHO/IFCC reference material. The consensus itself notes the imprecision that remains: bias below about 4 mg/dL and a coefficient of variation usually between 5% and 6%, which near 130 mg/dL is roughly plus or minus 7 mg/dL.
- Edition
- NLA expert clinical consensus, published online 2024-09-04; doi:10.1016/j.jacl.2024.08.013
- Specimen
- serum, reported in mg/dL
- Assay
- An immunoassay for apolipoprotein B run by a clinical laboratory. Standardization: WHO/IFCC reference material SP3-07.
- Applies only when
- your report prints mg/dL; the laboratory resolved the value rather than printing a bound like ">60"
- Direction
- Position only — we draw no direction on this one.
- Re-checked
- 2026-08-19, every 365 days. Has the National Lipid Association, or the ACC/AHA multisociety dyslipidemia guideline, published an apolipoprotein B risk-enhancing threshold other than 130 mg/dL?
- Attribution
- Soffer DE, Marston NA, Maki KC, et al. Role of apolipoprotein B in the clinical management of cardiovascular risk in adults: An expert clinical consensus from the National Lipid Association. J Clin Lipidol 2024;18(5):e647-e663. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- There are two bands and one line, because the consensus publishes one threshold. This is not a graded scale and the distance from the line is not a score.
- The consensus's own percentile table (NHANES 2005-2016, n=12,696) puts the 90th percentile of untreated US adults at 125 mg/dL and the 95th at 137 mg/dL, so 130 mg/dL sits between them. The threshold is a rounded trigger for a conversation, not a percentile, and no percentile is printed here.
- That percentile table is explicitly "adults 18-85 years of age not receiving lipid-modifying therapy". If you take a statin, ezetimibe or a PCSK9 inhibitor the population anchor behind this threshold does not describe you, and this platform holds no medication field.
- Repeat draws move across this single line. With a coefficient of variation of 5-6%, an apolipoprotein B near 130 mg/dL can land on either side of it from one draw to the next without anything about you having changed.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The 2019 ESC/EAS guideline states apolipoprotein B goals by risk stratum — below 65 mg/dL at very high risk, below 80 at high risk, below 100 at moderate risk — which are treatment targets conditioned on risk rather than a population threshold, and are not comparable with this line.
Lipoprotein(a) — National Lipid Association
| Band | From | Published as |
|---|---|---|
| Low risk | — | Everything below the first cut. |
| Intermediate risk | 75 nmol/L | "Individuals with Lp(a) levels <75 nmol/L (30 mg/dL) are considered low risk... individuals with Lp(a) levels between 75 and 125 nmol/L (30-50 mg/dL) are at intermediate risk." |
| High risk | 125 nmol/L | "Individuals with Lp(a) levels >=125 nmol/L (50 mg/dL) are considered high risk." |
This report does not name the assay that produced this number, and for Lp(a) that is not a detail. The National Lipid Association's own words are that "harmonization of values obtained from different assays, even those reporting in nmol/L, has yet to be undertaken": its 75 and 125 nmol/L boundaries are written for an immunochemical assay insensitive to apolipoprotein(a) isoform size, and an isoform-sensitive assay reporting in the same unit can land the same blood on the other side of a line. Upload a report that prints its method and this fills in.
- Edition
- NLA focused update, J Clin Lipidol 2024;18(3):e308-e319; doi:10.1016/j.jacl.2024.03.001
- Specimen
- serum, reported in nmol/L
- Assay
- An immunochemical Lp(a) assay reporting in nmol/L, calibrated against the WHO/IFCC secondary reference material. Standardization: WHO/IFCC SRM 2B — recommended, but the statement's own words are that "harmonization of values obtained from different assays, even those reporting in nmol/L, has yet to be undertaken".
- Applies only when
- your report prints nmol/L; the laboratory resolved the value rather than printing a bound like ">60"
- Direction
- Position only — we draw no direction on this one.
- Re-checked
- 2026-08-19, every 365 days. Has the National Lipid Association or the ACC/AHA multisociety dyslipidemia guideline moved the 75 and 125 nmol/L risk boundaries, or has a global standardization regime for Lp(a) assays finally been established?
- Attribution
- Koschinsky ML, Bajaj A, Boffa MB, et al. A focused update to the 2019 NLA scientific statement on use of lipoprotein(a) in clinical practice. J Clin Lipidol 2024;18(3):e308-e319. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- Lp(a) assays are not globally standardized, and the NLA says so itself: "harmonization of values obtained from different assays, even those reporting in nmol/L, has yet to be undertaken". Near a boundary, a different laboratory could put the same blood in a different band.
- The same statement carries a Class III recommendation against converting between units: "the use of a factor to convert Lp(a) values from mg/dL to nmol/L is not recommended". Nothing here converts anything — a result printed in any unit other than nmol/L gets the refusal instead of a band.
- Lp(a) is largely genetically set and changes little over a lifetime, so a repeat draw is a check on the assay rather than a check on you. A band here is not something a month of anything will move.
- Lp(a) distributions differ substantially by ancestry — median levels are higher in people of African ancestry and lower in people of East Asian ancestry — while these risk categories are published as one set. The NLA states they "apply across races and ethnicities", and this platform holds no ancestry field and would not use one.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The 2026 ACC/AHA multisociety dyslipidemia guideline keeps 125 nmol/L and adds a further tier at or above 250 nmol/L. That tier is not shown here, because these bands are one publication's and mixing two guidelines' ladders into one would leave the citation unable to say which line came from where.
High-sensitivity CRP — CDC and American Heart Association
| Band | From | Published as |
|---|---|---|
| Low | — | Everything below the first cut. |
| Average | 1 mg/L | Table 4: "Relative Risk Category and Average hs-CRP Level / Low <1 mg/L / Average 1.0 to 3.0 mg/L / High >3.0 mg/L". |
| High | above 3 mg/L | Table 4: "High >3.0 mg/L" — the source puts 3.0 itself in Average, so this floor is exclusive. |
| Not interpretable for cardiovascular risk | above 10 mg/L | "If a level of >10 mg/L is identified, the test should be repeated and the patient examined for sources of infection or inflammation. That result of >10 mg/L should then be discarded." — 10.0 itself is still High, so this floor is exclusive. |
This report named no assay, so this reads your result as a high-sensitivity assay of the class the statement considered — those with "acceptable precisions down to or below 0.3 mg/L". A standard CRP assay, which does not resolve below about 3-5 mg/L, cannot place anybody on this scale at all.
- Edition
- Circulation 2003;107:499-511, Table 4, "Laboratory Testing" item 3 (Class IIa, Level of Evidence B)
- Specimen
- serum, reported in mg/L
- Assay
- A high-sensitivity CRP immunoassay with acceptable precision at or below 0.3 mg/L, reported in mg/L. Standardization: WHO/IFCC CRP reference preparation; the statement's own Class I recommendation is that "hs-CRP results should be expressed as mg/L only".
- Applies only when
- your report prints mg/L; the laboratory resolved the value rather than printing a bound like ">60"; — and the published categories are defined on 2 draws, so one shows as provisional
- Direction
- Position only — we draw no direction on this one.
- Re-checked
- 2026-08-19, every 365 days. Has the AHA, the ACC or the CDC published a replacement hs-CRP risk-category table? The 2025 ACC inflammation clinical statement discusses a 2 mg/L threshold and the 2026 ACC/AHA dyslipidemia guideline mentions selective hs-CRP use; if either publishes named categories in mg/L, this table is superseded.
- Attribution
- Pearson TA, Mensah GA, Alexander RW, et al. Markers of Inflammation and Cardiovascular Disease: Application to Clinical and Public Health Practice. A Statement for Healthcare Professionals From the Centers for Disease Control and Prevention and the American Heart Association. Circulation 2003;107:499-511, Table 4. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- The top band is not the far end of a scale, it is an instruction to throw the result away. Above 10 mg/L the statement says to repeat the test and look for infection or inflammation, and to discard the number for cardiovascular purposes — so nothing above that line means "worse" here.
- The published categories are for the AVERAGE of two draws, not one: the table's own column reads "Average hs-CRP Level", and the same table says "Measurement of markers should be done twice (averaging results), optimally two weeks apart". With a single draw on file this standing shows as provisional, and no averaging is done across draws taken months apart because that is not what the statement asked for.
- hs-CRP is not specific to anything in particular: "Current assays are not specific for atherosclerosis and thus are not useful in the setting of other systemic inflammatory or infectious processes." A cold, a flare, an injury or a hard training week moves it.
- The statement is explicit that this is not a screening test for the general population, and recommends against using it that way.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. A 2025 ACC clinical statement on inflammation discusses a lower boundary at 2 mg/L rather than 3 mg/L. It is not reproduced here because the figure could not be read from the publisher's own document, and a threshold nobody has verified is exactly what this table exists to not print.
Ferritin — World Health Organization
| Band | From | Published as |
|---|---|---|
| Iron deficiency | — | Everything below the first cut. |
| Between the WHO's two thresholds (our label) | 15 ng/mL | Table 1, Adults (20-59) and Older persons (60+) rows: "<15" for iron deficiency and ">150 females >200 males" for risk of iron overload. The WHO names no category for the span between them, so this label is ours and states only where the value sits. |
| Risk of iron overload | 150 ng/mL (female column) / 200 ng/mL (male column) | Recommendation 1.3: ferritin "exceeding 150 ug/L in menstruating women and 200 ug/L in men and non-menstruating women who are otherwise healthy"; Table 1 states the same cut-offs as ">150 females" and ">200 males". |
This report named no assay, so this reads your result as calibrated to the WHO international standard. That is an assumption and not a fact: the guideline RECOMMENDS such calibration — "Use of the WHO international reference standard of ferritin is recommended for calibration of all commercial kits" — which is a recommendation precisely because it is not universal.
- Edition
- 2020 first edition, ISBN 978-92-4-000012-4; Table 1 and Recommendations 1.1-1.4
- Specimen
- serum, reported in ng/mL (the source states its thresholds in ug/L)
- Assay
- A ferritin immunoassay calibrated against the WHO international standard. Standardization: WHO 4th International Standard for Ferritin (Human, Recombinant), NIBSC 19/118, which replaced the 3rd IS (NIBSC 94/572) that the 2020 guideline text cites.
- Applies only when
- your report prints ng/mL; the laboratory resolved the value rather than printing a bound like ">60"; you have chosen a published column to read
- Direction
- U-shaped — both tails are adverse, and the bands name both.
- Re-checked
- 2026-08-19, every 365 days. Has the WHO issued a revised ferritin guideline? Watch items: the 2025 Lancet Global Health physiological-threshold work and the American Society of Hematology's draft proposing 20 ng/mL, both of which are challengers rather than WHO editions as of this check.
- Attribution
- WHO guideline on use of ferritin concentrations to assess iron status in individuals and populations. Geneva: World Health Organization; 2020. ISBN 978-92-4-000012-4. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- The upper threshold needs a published column and this account's answer chooses which one to read. The WHO states it two ways in the same document — Table 1 says "females" and "males", while Recommendation 1.3 says "menstruating women" and "men and non-menstruating women" — and this platform holds neither menstrual nor menopausal status. For a woman past menopause the WHO's own recommendation points at the 200 ug/L line even though the table's column says 150.
- Ferritin rises with inflammation whatever your iron stores are doing, which is why the WHO publishes a separate, higher threshold of 70 ug/L for iron deficiency in the presence of infection or inflammation. A normal-looking ferritin during an illness can hide a real deficiency.
- The whole upper band assumes you are, in the guideline's words, "otherwise healthy" and free of inflammation. This platform cannot check either.
- The units differ in name only. The WHO prints these thresholds in ug/L and this report prints ng/mL; one ug/L is one ng/mL exactly, so no number has been converted.
- This is the adult table. The WHO publishes a separate, lower deficiency threshold of 12 ug/L for children under five.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The American Gastroenterological Association's 2020 technical review favours a much higher deficiency threshold — around 45 ng/mL — in patients who are already anaemic, reporting far better sensitivity there (0.85) than at 15 ng/mL (0.59). So a ferritin of 30 ng/mL is not deficient on the WHO's line and is on the AGA's.
25-OH vitamin D — Institute of Medicine (now the National Academy of Medicine)
| Band | From | Published as |
|---|---|---|
| At risk of deficiency | — | Everything below the first cut. |
| Potentially at risk for inadequacy | 12 ng/mL | "This committee's review of data suggests that persons are at risk of deficiency relative to bone health at serum 25OHD levels of below 30 nmol/L (12 ng/mL). Some, but not all, persons are potentially at risk for inadequacy at serum 25OHD levels between 30 and 50 nmol/L (12 and 20 ng/mL)." |
| At or above the IOM's sufficiency level | 20 ng/mL | "Practically all persons are sufficient at serum 25OHD levels of at least 50 nmol/L (20 ng/mL). Serum 25OHD concentrations above 75 nmol/L (30 ng/mL) are not consistently associated with increased benefit." |
| May be reason for concern | 50 ng/mL | "There may be reason for concern at serum 25OHD levels above 125 nmol/L (50 ng/mL)." |
This report named no assay, so this reads your result as a standardized total 25-hydroxyvitamin D. Whether this particular laboratory's method is certified could not be established, and immunoassays and mass-spectrometry methods have historically disagreed by several ng/mL on the same sample.
- Edition
- 2011 edition, ISBN 978-0-309-16394-1; the Summary's serum 25OHD conclusions
- Specimen
- serum, reported in ng/mL
- Assay
- A serum total 25-hydroxyvitamin D assay. Standardization: CDC Vitamin D Standardization-Certification Program / NIST-Ghent reference measurement procedure.
- Applies only when
- your report prints ng/mL; the laboratory resolved the value rather than printing a bound like ">60"
- Direction
- U-shaped — both tails are adverse, and the bands name both.
- Re-checked
- 2026-08-19, every 365 days. Has the National Academy of Medicine issued a revised Dietary Reference Intake for vitamin D? The Endocrine Society's 2024 guideline is not one — it states it is "not meant to replace the current DRIs for vitamin D".
- Attribution
- Institute of Medicine. Dietary Reference Intakes for Calcium and Vitamin D. Washington, DC: The National Academies Press; 2011. Summary, "Conclusions about Vitamin D Deficiency in the United States and Canada". Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- The third band's name is a position, not a verdict about you. The IOM's sentence is about a population — "practically all persons are sufficient" at that level — and it is not a statement that any individual above the line is fine.
- More is not better here, which is why there is a fourth band. The IOM found levels above 30 ng/mL "not consistently associated with increased benefit" and named concern above 50 ng/mL; the NIH links potential adverse effects particularly above 60 ng/mL.
- Every one of these numbers is the IOM's own parenthetical. The report is stated in nmol/L and in ng/mL side by side in the source sentence, so nothing here has been converted by this platform.
- The IOM set these against BONE health specifically. Claims about vitamin D and other outcomes were not what these cut points were drawn for.
- The Endocrine Society's 2024 guideline suggests against routine 25-hydroxyvitamin D testing in the general adult population at all, in age-stratified recommendations covering under 50, 50 to 74, 75 and over, and pregnancy. It does not replace these DRIs, and it does disagree about whether the test is worth doing.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The Endocrine Society's 2011 guideline used 30 ng/mL as its sufficiency target and called 20-29 ng/mL insufficiency, which would move most readers a band. Its 2024 guideline steps back from naming levels at all.
Hemoglobin — World Health Organization
| Band | From | Published as |
|---|---|---|
| Severe anaemia | — | Everything below the first cut. |
| Moderate anaemia | 8 g/dL | Table 3, "Adults, 15-65 years": Severe <80 g/L, Moderate 80-109 g/L. Read here as g/dL: 80 g/L is 8.0 g/dL. |
| Mild anaemia | 11 g/dL | Table 3: Mild 110-119 g/L for nonpregnant women, 110-129 g/L for men. Read here as g/dL: 110 g/L is 11.0 g/dL. |
| No anaemia | 12 g/dL (female column) / 13 g/dL (male column) | Table 2 and Table 3, "Adults, 15-65 years": no anaemia at >=120 g/L for nonpregnant women and >=130 g/L for men. Read here as g/dL: 12.0 and 13.0. |
This report named no assay, so this reads your result as a venous whole-blood haemoglobin, which is what the WHO's cutoffs are set on. A capillary fingerstick result is not convertible to a venous one and should not be placed on this scale.
- Edition
- 2024 first edition, published 5 March 2024, ISBN 978-92-4-008854-2; supersedes WHO/NMH/NHD/MNM/11.1 (2011)
- Specimen
- whole blood, reported in g/dL (the source states its thresholds in g/L)
- Assay
- A venous whole-blood haemoglobin measured on an automated haematology analyser. Standardization: ICSH haemiglobincyanide reference method, via the WHO international reference reagent.
- Applies only when
- your report prints g/dL; the laboratory resolved the value rather than printing a bound like ">60"; you have chosen a published column to read; your age is between 15 and 65, the range the table publishes
- Direction
- Position only — we draw no direction on this one.
- Re-checked
- 2026-08-19, every 365 days. Has the WHO issued a haemoglobin-cutoff guideline later than the 2024 edition, or extended its adult rows beyond the 15-65 year range it currently publishes?
- Attribution
- WHO. Guideline on haemoglobin cutoffs to define anaemia in individuals and populations. Geneva: World Health Organization; 2024. ISBN 978-92-4-008854-2. Tables 2 and 3. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- The cutoffs ARE a fifth percentile, and the WHO says what that means: "The 5th percentile implies that 95% of healthy individuals would have a higher haemoglobin value, and 5% of healthy individuals would still have a lower haemoglobin value than the cutoff." One healthy person in twenty sits below the line by construction.
- "No anaemia" is the top band and it is open-ended, because this guideline is about anaemia only. It says nothing whatever about a haemoglobin that is too HIGH, and a high value is a real finding that no band here will name.
- The WHO publishes adult rows only for ages 15 to 65 and reports insufficient data above 65, so a draw outside that range gets the printed refusal rather than the nearest row.
- Unadjusted values under-detect anaemia at altitude and in people who smoke — the WHO recommends adjusting downwards before comparing — so this band can read "No anaemia" for someone who has it. Nothing here is adjusted.
- These are the nonpregnant rows. In pregnancy the WHO's cutoffs are lower and differ by trimester, and this platform holds no pregnancy field, so a pregnant reader would be shown the wrong row.
- The WHO prints these in g/L and this report prints g/dL; 130 g/L is 13.0 g/dL — the same quantity with the decimal point moved, not a conversion.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. Beutler and Waalen's analysis of NHANES and the Scripps-Kaiser cohort put the lower limit at 13.7 g/dL for white men under 60 and 12.9 g/dL for black men under 60, i.e. above the WHO's single 13.0 g/dL line in one group and below it in another, on the same argument the WHO's own guideline makes about population-specific distributions.
Estimated GFR — KDIGO 2024 Clinical Practice Guideline for the Evaluation and Management of Chronic Kidney Disease
| Band | From | Published as |
|---|---|---|
| G5 — kidney failure | — | Everything below the first cut. |
| G4 — severely decreased | 15 mL/min/1.73m2 | Table 2 | GFR categories in CKD: "G4 / 15-29 / Severely decreased". |
| G3b — moderately to severely decreased | 30 mL/min/1.73m2 | Table 2 | GFR categories in CKD: "G3b / 30-44 / Moderately to severely decreased". |
| G3a — mildly to moderately decreased | 45 mL/min/1.73m2 | Table 2 | GFR categories in CKD: "G3a / 45-59 / Mildly to moderately decreased". |
| G2 — mildly decreased | 60 mL/min/1.73m2 | Table 2 | GFR categories in CKD: "G2 / 60-89 / Mildly decreased". |
| G1 — normal or high | 90 mL/min/1.73m2 | Table 2 | GFR categories in CKD: "G1 / >=90 / Normal or high". |
This report named no creatinine assay, so this reads the number as coming from an IDMS-traceable creatinine, which US clinical laboratories have been standardized to since the National Kidney Disease Education Program's recalibration. The equation carries the weight here rather than the assay: the 2021 CKD-EPI equation is defined on standardized creatinine and a laboratory running it on anything else would be misusing its own formula.
- Edition
- KDIGO 2024 Clinical Practice Guideline for the Evaluation and Management of CKD, Chapter 1, Table 2; Kidney Int. 2024;105(4S):S117-S314; released 2024-03
- Specimen
- serum, reported in mL/min/1.73m2 (the source states its thresholds in ml/min per 1.73 m2)
- Assay
- A serum creatinine measured by a method traceable to an isotope-dilution mass spectrometry reference measurement procedure. Standardization: IDMS traceability (NIST SRM 967).
- Equation
- CKD-EPI 2021. An eGFR is not measured, it is computed, and KDIGO grades a filtration rate estimated by a named equation. This report did not name one. The 2021 CKD-EPI equation, the 2009 CKD-EPI equation and MDRD disagree by several units near the boundary between G2 and G3a, so which equation produced this number decides which category it falls in — and nothing is compared until the report says which it was.
- Applies only when
- your report prints mL/min/1.73m2; the report names the estimating equation, and it is CKD-EPI 2021; the laboratory resolved the value rather than printing a bound like ">60"
- Direction
- Higher is better, as far as the literature supports.
- Re-checked
- 2026-08-19, every 180 days. Has KDIGO published a CKD guideline edition later than 2024 in which the GFR categories are bounded at anything other than 15, 30, 45, 60 and 90 ml/min per 1.73 m2, or in which the recommended estimating equation for adults is no longer the 2021 CKD-EPI creatinine equation?
- Attribution
- Kidney Disease: Improving Global Outcomes (KDIGO) CKD Work Group. KDIGO 2024 Clinical Practice Guideline for the Evaluation and Management of Chronic Kidney Disease. Kidney Int. 2024;105(4S):S117-S314. Source
- This is a position on somebody else's published scale, not a diagnosis and not medical advice. A guideline category is a population-level classification; what it means for you is a conversation with a clinician who can see the rest of your history.
- A GFR CATEGORY IS NOT A DIAGNOSIS OF CHRONIC KIDNEY DISEASE. KDIGO defines CKD as abnormalities of kidney structure or function present for more than three months, and grades it by cause, GFR category AND albuminuria category. This number is one of those three and it is from one day: G2 on a single draw is not "stage 2 CKD", and G1 is not a clean bill of kidney health, because albuminuria can be abnormal at any GFR.
- KDIGO's own words on the top two categories: "GFR categories G1 or G2 do not fulfill the criteria for CKD in the absence of evidence of kidney damage." That is why the highest band here is named "normal or high" rather than "best" — the category does not distinguish a healthy kidney from a hyperfiltering one, and a rising eGFR is not automatically a better one.
- These categories are the ADULT ones. KDIGO publishes separate guidance for children, and the estimating equations differ; this platform holds no pregnancy field either, and eGFR is not validated in pregnancy.
- An estimating equation is a population average applied to one person. It is least reliable at the extremes of muscle mass — a bodybuilder, an amputee, somebody with cirrhosis — because it reads creatinine, which muscle produces. A cystatin C-based estimate is what KDIGO suggests where the creatinine one is in doubt.
- KDIGO prints this unit as "ml/min per 1.73 m2" and this report prints "mL/min/1.73m2". They are the same unit, spelled two ways, and no number was converted.
- The H or L flag and the reference interval printed beside this result are BioReference's own, set on its own assay against its own reference population. They are shown as provenance and they never place you in a band — the cited guideline's cut points do, and the two can disagree.
- Where bodies disagree. The categories themselves are not disputed: 15/30/45/60/90 ml/min per 1.73 m2 are KDIGO's and are reproduced by NICE (NG203) and by the US National Kidney Foundation. What is disputed is the estimate fed into them. NICE recommends CKD-EPI 2009 rather than the 2021 refit, so a UK laboratory and a US one can report different eGFRs for the same creatinine and land on different sides of the G2/G3a line — which is exactly why the equation has to be named before any of this is read.
The 70 measurements we will not rank
Grouped by the reason, because the reasons repeat and 70paragraphs would be unreadable. Each one links the publication that establishes the refusal, and each is printed on that measurement’s own page in the app, where the number it is about already is.
One laboratory's own interval (13)
The only line anybody draws here is a reference interval, computed by each laboratory on its own analyser against its own reference population. It is not comparable between laboratories, so it is not a ruler.
- Blood urea nitrogen. No guideline body publishes risk bands or named categories for BUN in ambulatory adults — KDIGO does not stage kidney disease on urea at all. The only comparator that exists is a laboratory's own wide reference interval, and that interval is wide because BUN moves with protein intake, catabolism, and hydration, none of which the platform knows. A reference interval is not a ruler. Hosten AO. 'BUN and Creatinine.' In: Walker HK, Hall WD, Hurst JW, eds. Clinical Methods: The History, Physical, and Laboratory Examinations. 3rd ed. Boston: Butterworths; 1990. Chapter 193 (NCBI Bookshelf NBK305) — corroborated by MedlinePlus (NIH), BUN blood test: 'Normal values may vary among different labs.'
- Chloride. No guideline body publishes named bands or outcome-anchored cut points for serum chloride; the only thing anyone publishes is a reference interval, which is one laboratory's own interval and not a ruler. Chloride is interpreted only alongside sodium, bicarbonate and the anion gap, never alone. MedlinePlus Medical Encyclopedia (A.D.A.M.), 'Chloride test - blood', U.S. National Library of Medicine, review date 2025-05-19
- Creatinine. No guideline body publishes named bands for serum creatinine itself. Kidney guidelines stage on estimated GFR precisely because the same creatinine means different things in different bodies — it tracks muscle mass, diet, and tubular secretion as much as filtration — so the only thing the raw number can be compared against is one laboratory's own sex-specific reference interval, which is provenance, not a ruler. KDIGO 2024 Clinical Practice Guideline for the Evaluation and Management of Chronic Kidney Disease, Chapter 1 (Kidney International 2024;105(Suppl 4S)) — corroborated by MedlinePlus (NIH): 'Creatinine levels that are considered normal for you will depend on how much muscle you have, what you eat, your age, and how active you are' and 'Creatinine testing alone isn't the best way to check how well your kidneys are working.'
- Total protein. No external body publishes named bands or outcome-anchored cut points for total protein; the only published number is a reference interval, which is one laboratory's own interval on one biuret instrument and is provenance, not a ruler. Both tails are abnormal (low from loss or malnutrition, high from paraprotein or dehydration) and neither is banded. MedlinePlus Medical Encyclopedia (A.D.A.M.), 'Total protein', U.S. National Library of Medicine, review date 2025-05-19
- Absolute basophils. The absolute basophil count sits at the bottom of the analyser's dynamic range, where counting statistics dominate and automated channels agree least with microscopy, and no external body publishes bands for it. Basophilia matters clinically only as a clue inside a myeloproliferative-neoplasm work-up alongside the rest of the count and molecular testing; a low basophil count means nothing. There is no absolute external scale to place this on, only one laboratory's interval. Ozarda Y, Biochemia Medica 2016;26(1):5-16 (doi:10.11613/BM.2016.001); Doig K, Thompson LA, American Society for Clinical Laboratory Science 2017;30(3):186-193
- Absolute lymphocytes. No body publishes absolute lymphocyte-count bands for adults. The only externally published thresholds are disease-defining criteria that require something a count cannot supply — demonstrated clonality by flow cytometry for a lymphoproliferative disorder, or an identified cause for lymphopenia. Both tails are adverse, and the interval on the report is one laboratory's own on its own analyser. Ozarda Y, "Reference intervals: current status, recent developments and future considerations", Biochemia Medica 2016;26(1):5-16 (doi:10.11613/BM.2016.001)
- Mean corpuscular volume. MCV's microcytic/normocytic/macrocytic language is a step inside an anaemia work-up, not a standing: it only means something once anaemia has been established on haemoglobin, and the boundaries used are the laboratory's own interval on its own analyser rather than a threshold any guideline publishes. MCV is also method- and handling-dependent (impedance versus optical sizing, and red-cell swelling with time in EDTA), so a single universal cut point is not instrument-compatible. Ozarda Y, "Reference intervals: current status, recent developments and future considerations", Biochemia Medica 2016;26(1):5-16 (doi:10.11613/BM.2016.001)
- Absolute monocytes. There is no external absolute monocyte ruler. Published monocyte criteria appear only inside haematological disease definitions that additionally require the elevation to persist for months and demand exclusion of reactive causes — recovery from infection, inflammation, stress — so a single value cannot be positioned. The printed interval is one laboratory's own. Ozarda Y, Biochemia Medica 2016;26(1):5-16 (doi:10.11613/BM.2016.001), summarising CLSI/IFCC EP28-A3c (C28-A3)
- Red blood cell count. WHO defines anaemia on haemoglobin concentration, not on red-cell count, and no guideline publishes absolute red-cell-count bands for adults. What the report shows is one laboratory's own 95% interval on its own analyser; promoting that into a band is exactly the move ADR 0134 forbids. A person with thalassaemia trait can carry a high red-cell count with low haemoglobin, so the count has no direction of its own. World Health Organization, Guideline on haemoglobin cutoffs to define anaemia in individuals and populations (2024); Ozarda Y, Biochemia Medica 2016;26(1):5-16
- White blood cell count. There is no external, absolute white-cell ruler: the only numbers anyone prints are reference intervals, which are descriptive of one laboratory's own reference population and analyser and are re-verified per laboratory under ISO 15189. Both tails are adverse (leukopenia and leukocytosis), the published disease thresholds that exist are clinical decision limits inside a diagnostic work-up rather than positions on a scale, and the total count is uninterpretable without the differential beneath it. Ozarda Y, "Reference intervals: current status, recent developments and future considerations", Biochemia Medica 2016;26(1):5-16 (doi:10.11613/BM.2016.001), summarising CLSI/IFCC EP28-A3c (C28-A3)
- Serum iron. No guideline body publishes absolute categories for serum iron. Every number available for it — including the one printed on this report — is a single laboratory's own reference interval for its own analyzer and population, and a reference interval is not a ruler. WHO's own individual-level iron indicator is ferritin, not serum iron. WHO guideline on use of ferritin concentrations to assess iron status in individuals and populations (2020), Recommendation 1.1 — WHO's individual-level iron indicator is ferritin; it issues no serum-iron cut-off
- Total iron-binding capacity. TIBC is a surrogate for circulating transferrin and has no published absolute category system from any guideline body. It is also method-dependent — some laboratories measure it directly as iron plus unsaturated iron-binding capacity, others calculate it from an immunoassayed transferrin — so a number carries no cross-lab meaning without the method, which this wire does not carry. AGA Technical Review on Gastrointestinal Evaluation of Iron Deficiency Anemia (Gastroenterology, 2020) — the transferrin-based measures are described as imprecise and no TIBC cut-off is issued
- Thyroid-stimulating hormone. The governing guideline says in as many words that the upper limit of normal for TSH is set by the individual laboratory's own assay, and that the interval shifts with age. That is the definition of a lab-specific reference interval, which this platform's rules bar from becoming a band. TSH is also homeostatically regulated with no monotonic direction — both tails are abnormal and neither is a score. AACE/ATA Clinical Practice Guidelines for Hypothyroidism in Adults (Garber et al., Thyroid 2012;22:1200), Recommendation graded (I, A)
Held flat by the body, with both tails adverse (5)
The body holds these inside a narrow range on purpose. Both directions are abnormal, no published table names positions on both sides, and a scale would have to invent a direction that does not exist.
- Calcium. The only published named bands for calcium classify hypercalcaemia in patients who already have malignancy, and their lowest band (<12 mg/dL) swallows every value a healthy adult will ever report — a one-band ruler carries no position. Every published criterion is stated on ALBUMIN-ADJUSTED calcium and relative to a lab-specific upper limit of normal, and none is interpretable without PTH. El-Hajj Fuleihan G, et al. Treatment of Hypercalcemia of Malignancy in Adults: An Endocrine Society Clinical Practice Guideline. J Clin Endocrinol Metab 2023;108(3):507-528, doi:10.1210/clinem/dgac621
- Potassium. The published named categories cover only the HIGH tail (moderate/severe hyperkalemia) and are action triggers inside a kidney-disease guideline, not positions on a scale; and the same guideline warns that the measurement itself is variable enough that a single value cannot be graded. Both tails are adverse. KDIGO 2024 Clinical Practice Guideline for the Evaluation and Management of Chronic Kidney Disease. Kidney International 2024;105(4S):S117-S314
- Sodium. The only published named bands for serum sodium are a disease classification of the LOW tail only (mild/moderate/profound hyponatraemia); no source names bands on both sides, so a position on a sodium ruler cannot be honestly drawn. Both tails are adverse and the number is only interpretable with volume status, osmolality and medications. Spasovski G, Vanholder R, Allolio B, et al. Clinical practice guideline on diagnosis and treatment of hyponatraemia. European Journal of Endocrinology 2014;170:G1-G47 (ERBP/ESICM/ESE), doi:10.1530/EJE-13-1020
- Urine pH. Urine pH is the kidney's acid-base exhaust and moves within a single meal: protein and acidic fruit push it down, a high-citrate diet pushes it up, and both tails carry real pathology (renal tubular acidosis at one end, urease-producing infection at the other). No body publishes named position categories on either side, so the U-shaped exception does not open, and the strip's own accuracy is only 0.5 to 1 pH unit. Hitzeman N, Greer D, Carpio E. Office-Based Urinalysis: A Comprehensive Review. Am Fam Physician. 2022;106(1):27-35
- Urine specific gravity. Specific gravity reports how much water the user drank in the last few hours, not a durable state, and both tails are abnormal for opposite reasons - high means dehydration or glucosuria, low means dilution, diabetes insipidus or adrenal insufficiency. The U-shaped exception does not open because the published sources name causes, not position categories, and the IFCC-IUPAC nomenclature declares the quantity to be in procedure-defined units with no traceability to a certified reference material, which fails instrument compatibility outright. Hitzeman N, Greer D, Carpio E. Office-Based Urinalysis: A Comprehensive Review. Am Fam Physician. 2022;106(1):27-35
A category, not a measurement (16)
Dipstick grades, colours and microscopy ranges are ordinal readings. Whatever number they arrive as is an artefact of importing them, and ranking an artefact would be ranking our own encoding.
- Urine appearance. Appearance is a nominal observation (CLEAR, HAZY, CLOUDY) whose only published quantity is a likelihood ratio inside an already-symptomatic population, and the same finding is produced by benign phosphate crystals in alkaline urine. A likelihood ratio conditioned on symptoms is a diagnostic aid, not a position on a scale. Hitzeman N, Greer D, Carpio E. Office-Based Urinalysis: A Comprehensive Review. Am Fam Physician. 2022;106(1):27-35
- Urine bacteria. Bacteria seen on microscopy cannot distinguish infection from contamination or from the normal urinary microbiota that healthy people carry, and the only published numeric limits (100,000 CFU/mL) belong to urine culture, a different examination we do not have. Without symptoms and without a culture there is no external threshold to read. Hitzeman N, Greer D, Carpio E. Office-Based Urinalysis: A Comprehensive Review. Am Fam Physician. 2022;106(1):27-35
- Urine bilirubin. The European guideline classes urinary bile pigments as obsolete tests, superseded by blood measurements with better diagnostic performance. There is no current published band structure for a test the field has retired, and the pad is an ordinal rank degraded by light exposure and vitamin C. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med
- Urine blood. The American Urological Association's Choosing Wisely recommendation is that microhematuria must not be diagnosed from a dipstick at all, and the pad reacts to exercise, menses and myoglobin from muscle breakdown as readily as to bladder pathology. A published instruction not to conclude anything from this test is the opposite of a ruler. American Urological Association Choosing Wisely recommendation, as reproduced in Am Fam Physician. 2022;106(1):27-35
- Urine color. Urine color is a nominal label (YELLOW, AMBER, RED) with no published ordering: the authoritative tables list causes, not ranks, and beets, rifampin or a multivitamin move it exactly as far as disease does. There is no external body that publishes ordered color bands, so there is nothing to place a standing against. Hitzeman N, Greer D, Carpio E. Office-Based Urinalysis: A Comprehensive Review. Am Fam Physician. 2022;106(1):27-35
- Urine crystals. The European guideline explicitly recommends that crystals not even be looked for or reported on routine specimens, because in most cases they record transient supersaturation from last night's dinner or from the specimen cooling in transit. A finding the publishing body says should not be routinely reported cannot carry a ranked standing. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med
- Urine epithelial cells. Squamous epithelial cells measure how the user caught the sample, not a state of the body, and the guideline says the evidence no longer supports even that use. There is no published ladder, and any number here would be a standing on midstream technique. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med
- Urine glucose. Urine glucose is an ordinal pad rank that the European guideline states is not a sensitive screening tool for diabetes, having been replaced by blood glucose and HbA1c. Its direction also inverts by medication: SGLT-2 inhibitors work by deliberately producing glucosuria, so a positive pad is the drug succeeding, not the patient failing. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med
- Urine granular casts. The published statement about granular casts is a disease association, not a graded scale: they suggest renal disease or urinary stasis, with no counts, no cut points and no named positions. The report also gives a bin or a word, never a standardized count, and phase-contrast versus bright-field optics change what the technologist sees. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med
- Urine ketones. The nitroprusside pad misses beta-hydroxybutyrate, the dominant ketone body, and the guideline says its unspecific reactions and missing reference material obscure clinical interpretation outright. Direction is also not fixed: the same 2+ is the intended result of a ketogenic diet or a fast and an emergency in diabetes, so no honest good/bad ordering exists. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med
- Urine leukocyte esterase. The pad reports an ordinal rank (negative, trace, 1+, 2+, 3+) whose mapping to an actual leukocyte concentration is set by each manufacturer's detection and confirmation limits, and the published clinical association is stated in cells per litre, not in pad ranks. There is no external body that publishes bands on the rank itself. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med
- Urine nitrites. Nitrite is a binary detector whose published performance is conditioned on the user already having urinary symptoms, and a negative result explicitly does not exclude infection because many uropathogens do not reduce nitrate and short bladder dwell time defeats the reaction. A symptom-conditioned yes/no detector has no positions to rank. Hitzeman N, Greer D, Carpio E. Office-Based Urinalysis: A Comprehensive Review. Am Fam Physician. 2022;106(1):27-35 (SORT key recommendation)
- Urine protein. A real published ladder exists for urinary protein - KDIGO's A1/A2/A3 albuminuria categories - but it is defined on the albumin-to-creatinine ratio in mg/g, and KDIGO explicitly instructs that a positive reagent strip be replaced by that quantitative ratio before anything is concluded. The dipstick pad is an ordinal rank whose mg/dL equivalence is approximate and brand-specific, so this metric is the wrong instrument for the only ruler that exists. KDIGO 2024 Clinical Practice Guideline for the Evaluation and Management of Chronic Kidney Disease, Practice Point 1.3.1.2
- Urine red blood cell casts. Erythrocyte casts are a present-or-absent finding that, when present, points directly at bleeding from the renal parenchyma; there is no published gradation and no meaningful position between "none" and "present". Ranking a binary alarm would be an invented ladder, and the correct product response is the finding itself, not a standing. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med
- Urine red blood cells. A named number does exist here - three or more RBCs per high-power field defines microhematuria in adults - but it is a diagnostic case definition requiring a properly spun specimen and a clinical work-up, not a ladder of positions, and the same guideline body forbids diagnosing it from a single test. Our value is a bin string such as "0-2" in a unit the European guideline calls non-standard, so the count the definition speaks about is not what arrives on the wire. Hitzeman N, Greer D, Carpio E. Office-Based Urinalysis: A Comprehensive Review. Am Fam Physician. 2022;106(1):27-35
- Urine white blood cells. Per-high-power-field counts are explicitly a non-standard unit that the European guideline says must be converted to particles per litre before clinical interpretation, because the number depends on how much supernatant the technologist poured off and on the delay before the slide was read. The report also sends a bin string such as "0-5", not a count, and the only published cut point is conditioned on the person already having urinary symptoms. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med, Recommendation 42
A ratio nobody publishes categories for (14)
The report computes these from two other results. No guideline body classifies the ratio itself, and building categories out of its components' categories would be a composite of ours — which this platform does not make.
- Albumin-to-globulin ratio. No professional body publishes cut points for the albumin/globulin ratio; the searchable literature is study-specific ROC-derived cut points in oncology cohorts (0.83, 1.25, 1.50 in different papers) plus commercial wellness sites, which is the signature of no ruler existing. The ratio is a derived number that inherits two assays' reference intervals and the albumin method bias, so its 'normal' floats with the instrument. Duly EB, Grimason S, Grimason P, Barnes G, Trinick TR. Measurement of serum albumin by capillary zone electrophoresis, bromocresol green, bromocresol purple, and immunoassay methods. J Clin Pathol 2003;56(10):780-781 (the assay bias the ratio inherits through both its numerator and its denominator)
- BUN-to-creatinine ratio. The only published cut point for this ratio — roughly 20:1 — is a bedside differential-diagnosis heuristic used to separate prerenal from intrinsic causes in a patient who is ALREADY azotemic and whose volume status is known. It is not a population ruler, no guideline body publishes named bands for it, and it inherits the method dependence of both analytes (it is 'unitless' only when both are read in mg/dL). Applying it to a well outpatient would manufacture a verdict the literature does not support. Workeneh BT, Agraharkar M, et al. 'Prerenal Kidney Failure.' StatPearls (NCBI Bookshelf NBK560678) — a differential-diagnosis heuristic conditioned on established azotemia, not a set of population categories
- Basophils. Basophils are the rarest lineage in the differential, so the percentage is both a share of a moving denominator and a very small number carrying large counting imprecision; automated basophil channels are the least concordant with microscopy of the five. No external body publishes basophil percentage bands, and the report's interval is one laboratory's own. Doig K, Thompson LA, American Society for Clinical Laboratory Science 2017;30(3):186-193; Ozarda Y, Biochemia Medica 2016;26(1):5-16
- Eosinophils. Every published eosinophil threshold — blood eosinophilia, hypereosinophilia, the counts used to select asthma biologics — is stated as an absolute concentration in x10(9)/L, never as a percentage of the white count. The percentage therefore has no external ruler at all, only the reporting laboratory's own interval. Valent P, Klion AD, Horny H-P, et al., "Contemporary consensus proposal on criteria and classification of eosinophilic disorders and related syndromes", Journal of Allergy and Clinical Immunology 2012;130(3):607-612.e9 (doi:10.1016/j.jaci.2012.02.019)
- Lymphocytes. Relative lymphocyte percentage is the textbook example of the artefact: a normal lymphocyte concentration reads as 'relative lymphopenia' purely because neutrophils rose during an infection. No external body publishes percentage bands, and the percentage is arithmetically dependent on every other cell line in the same differential. Doig K, Thompson LA, "A Methodical Approach to Interpreting the White Blood Cell Parameters of the Complete Blood Count", American Society for Clinical Laboratory Science 2017;30(3):186-193
- Mean corpuscular hemoglobin. MCH is haemoglobin divided by the red-cell count — a derived ratio of two analyser outputs, with no published external band table. No guideline body states MCH thresholds; the only numbers available are the reporting laboratory's own interval, which these rules class as provenance. Its clinical use is as a discriminant inside a microcytosis work-up alongside MCV and ferritin, never as a rank on its own. Ozarda Y, Biochemia Medica 2016;26(1):5-16 (doi:10.11613/BM.2016.001), summarising CLSI/IFCC EP28-A3c
- Mean corpuscular hemoglobin concentration. MCHC is haemoglobin divided by the haematocrit, and the haematocrit is itself computed as MCV x RBC — a ratio built on a derived quantity, so it carries every method artefact of both. Laboratories use MCHC mainly as an internal flag for spurious results (lipaemia, cold agglutinins, in-vitro haemolysis) and, at the high end, as a hint toward hereditary spherocytosis inside a clinical work-up. No external body publishes MCHC bands, and the report's interval is one laboratory's own. Wennecke G, "Hematocrit — a review of different analytical methods", Acute Care Testing (Radiometer), September 2004; Ozarda Y, Biochemia Medica 2016;26(1):5-16
- Monocytes. Monocyte percentage is a share of the white count with no externally published band table; it rises and falls with the other lineages regardless of the monocyte concentration itself. Where published monocyte criteria exist at all they are disease definitions requiring both a percentage AND an absolute count sustained over months plus exclusion of reactive causes — a work-up, not a position on a scale. Doig K, Thompson LA, American Society for Clinical Laboratory Science 2017;30(3):186-193; Jones AR et al., Clinical and Laboratory Haematology 1995;17(2):115-23
- Neutrophils. A differential percentage is a share of the total white count, so it moves when any other lineage moves: the same neutrophil concentration reads as a different percentage on a day lymphocytes rise. No body publishes percentage bands, and standard laboratory practice is to interpret the absolute count instead. There is nothing here to place. Doig K, Thompson LA, "A Methodical Approach to Interpreting the White Blood Cell Parameters of the Complete Blood Count", American Society for Clinical Laboratory Science 2017;30(3):186-193; Jones AR, Twedt D, Hellman R, "Absolute versus proportional differential leucocyte counts", Clinical and Laboratory Haematology 1995;17(2):115-23
- ApoB-to-ApoA1 ratio. No guideline body publishes categories for the apoB/apoA1 ratio. It is a research and epidemiology instrument (INTERHEART, AMORIS report it in quintiles of the study population, which is a norm derived from a cohort, not an absolute ruler), and the NLA 2024 apoB consensus, the 2019 ESC/EAS guideline and the 2026 ACC/AHA dyslipidemia guideline all set thresholds on apoB alone, never on the ratio. Both of the ratio's components are separately audited here; the ratio adds a number with no published band edges. Third Report of the NCEP Expert Panel (ATP III), NIH/NHLBI, 2002 - the panel's general position on treating lipid ratios as targets
- Cholesterol-to-HDL ratio. The one US guideline that examined this ratio head-on, NCEP ATP III, explicitly declined to make it a target: the ratio is already subsumed inside the Framingham risk equations, and ATP III would not define it as a primary or secondary lipid target. No later guideline (2018 or 2026 ACC/AHA, 2019 ESC/EAS, NLA) publishes named categories for it either. Its components - total cholesterol and HDL-C - are the things guidelines classify. Third Report of the National Cholesterol Education Program (NCEP) Expert Panel on Detection, Evaluation, and Treatment of High Blood Cholesterol in Adults (Adult Treatment Panel III), Final Report, NIH/NHLBI, 2002, section II.a.7 "Total cholesterol/HDL-cholesterol ratio"
- HDL share of cholesterol. HDL as a share of total cholesterol is the reciprocal of the total-cholesterol/HDL ratio expressed as a percentage, and it inherits that ratio's refusal: no guideline body publishes categories for it, and ATP III specifically declined to make the cholesterol ratio a target of therapy. We found no dated, citable publication that names bands on this percentage at all. Third Report of the NCEP Expert Panel (ATP III), NIH/NHLBI, 2002, section II.a.7
- LDL-to-HDL ratio. No guideline body publishes categories for the LDL/HDL ratio. ATP III's reasoning against cholesterol ratios as targets applies a fortiori - it discusses the total-cholesterol/HDL ratio because that is the ratio inside the Framingham equations, and treats no LDL/HDL ratio at all, while every guideline since classifies LDL-C on its own scale. Any LDL/HDL band we printed would be invented. Third Report of the NCEP Expert Panel (ATP III), NIH/NHLBI, 2002, section II.a.7
- Triglyceride-to-HDL ratio. The TG/HDL-C ratio is a research surrogate for insulin resistance with no guideline-endorsed cut point. The proposed thresholds in the literature are population-fitted ROC optima that move with sex and ancestry - roughly 2.5 for women and 2.8 for men in pooled studies, about 3.0 for non-Hispanic whites and Mexican Americans versus 2.0 for non-Hispanic blacks - which means any single band edge we printed would be a norm borrowed from one cohort, not an absolute ruler. It is also fasting-sensitive through its triglyceride term. Baneu P, et al., "The Triglyceride/HDL Ratio as a Surrogate Biomarker for Insulin Resistance," Biomedicines 2024
Only interpretable with clinical context (8)
A number here means different things depending on facts the platform does not hold and must never infer. Placing one on a scale would drop exactly the context that makes it mean anything.
- Alkaline phosphatase. A total alkaline phosphatase value does not say which organ produced it — liver, bone or intestine — and the guideline requires a GGT or an isoenzyme fractionation before the number can be read at all, neither of which is on our wire. Published treatment targets in cholestatic liver disease are expressed as multiples of the local ULN rather than absolute U/L, and the level shifts with age, sex, pregnancy and a recent meal. Kwo PY, Cohen SM, Lim JK. ACG Clinical Guideline: Evaluation of Abnormal Liver Chemistries. Am J Gastroenterol 2017;112:18-35, doi:10.1038/ajg.2016.517
- Total bilirubin. Every citable bilirubin threshold is stated as a MULTIPLE of the laboratory's own upper limit of normal (FDA's Hy's Law uses >2xULN), so none is absolute in mg/dL; and total bilirubin is uninterpretable until it is fractionated into conjugated and unconjugated, which this panel does not report. The most common cause of a raised total bilirubin is benign Gilbert's syndrome, in 3-7% of the US population, so a 'worse' band would mislabel a harmless genotype. Kwo PY, Cohen SM, Lim JK. ACG Clinical Guideline: Evaluation of Abnormal Liver Chemistries. Am J Gastroenterol 2017;112:18-35, doi:10.1038/ajg.2016.517
- Carbon dioxide. The one citable numeric threshold for bicarbonate is a treatment trigger conditioned on having chronic kidney disease, and the same guideline warns the high side is adverse too — so it names no position for an adult without CKD. Major bodies also disagree on the number (KDIGO 18 mmol/L vs KDOQI 22 mEq/L). KDIGO 2024 Clinical Practice Guideline for the Evaluation and Management of Chronic Kidney Disease. Kidney International 2024;105(4S):S117-S314
- Absolute eosinophils. The published thresholds here are real and absolute — 0.5 x10(9)/L for eosinophilia, 1.5 x10(9)/L for hypereosinophilia — but they are not a ruler by this ADR's terms: hypereosinophilia is defined only on two measurements at least a month apart (or immediately when life-threatening end-organ damage is evolving), and the classification that follows turns on bone-marrow, tissue and molecular findings the platform will never hold. The low tail carries no meaning at all (eosinopenia is normal and steroid-induced), so the source defines one side only, and defines it as a work-up trigger rather than a position. Valent P, Klion AD, Horny H-P, et al., "Contemporary consensus proposal on criteria and classification of eosinophilic disorders and related syndromes", Journal of Allergy and Clinical Immunology 2012;130(3):607-612.e9 (doi:10.1016/j.jaci.2012.02.019)
- Absolute neutrophils. A single universal lower threshold for the absolute neutrophil count is demonstrably wrong. Two thirds of Black individuals in the cited cohort carry the Duffy-null phenotype, whose healthy median ANC is about 2820/uL against 5005/uL in non-null individuals, and nearly a quarter sit below the 2000/uL lower limit many laboratories print while being entirely healthy. Placing a person on a scale whose floor depends on a genotype the platform does not hold — and must never infer from race — would manufacture a false 'low' standing for a large population. The upper tail is equally context-bound: neutrophilia follows infection, stress, exercise and corticosteroids. Merz LE, Story CM, Osei MA, et al., "Absolute neutrophil count by Duffy status among healthy Black and African American adults", Blood Advances 2023;7(3):317-320 (doi:10.1182/bloodadvances.2022007679); Merz LE, Osei MA, Story CM, et al., "Development of Duffy Null-Specific Absolute Neutrophil Count Reference Ranges", JAMA 2023;329(23):2088-2089 (doi:10.1001/jama.2023.7467)
- Platelet count. Both tails are adverse and neither is a rank. The familiar 150 and 450 x10(3)/uL lines are one laboratory's reference interval, not a published ruler, and the externally published platelet thresholds that do exist are diagnostic or treatment decision limits inside a work-up that requires excluding everything else — including EDTA-dependent pseudothrombocytopenia, an in-vitro artefact of the very tube this specimen was drawn into, which is settled on a blood film rather than a number. Neunert C, Terrell DR, Arnold DM, et al., "American Society of Hematology 2019 guidelines for immune thrombocytopenia", Blood Advances 2019 (PMC6963252)
- Iron saturation. The only widely published transferrin saturation number — 45% — is not a band edge but a gated diagnostic trigger: EASL and AASLD apply it only to a FASTING, REPEATED measurement, and only in combination with ferritin and HFE genotype. There is no published category on the low side that is assay-independent, and saturation is a derived ratio of two analytes (serum iron / TIBC) that this audit already refuses individually. AGA Technical Review on Gastrointestinal Evaluation of Iron Deficiency Anemia (Gastroenterology, 2020); see also EASL Clinical Practice Guidelines on haemochromatosis (J Hepatol, 2022), which require TSAT >45% to be confirmed on a fasting repeat and read together with ferritin and p.Cys282Tyr HFE genotype
- Urine hyaline casts. Hyaline casts appear in healthy people who are simply dehydrated or who exercised hard, and they dissolve out of alkaline urine before the microscope ever sees them. The same count therefore means opposite things depending on the user's last workout, their fluid intake and the specimen's transit time, so no external body publishes bands for it. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med
The threshold belongs to an assay the report does not name (9)
Published thresholds exist, but they are specific to a method, analyser or equation, and this report does not say which produced the number. Applying them anyway would be a guess wearing a citation.
- Albumin. Serum albumin is not comparable between methods: bromocresol green reads roughly 3.9-4.5 g/L (0.4-0.45 g/dL) higher than bromocresol purple or electrophoresis, which is larger than the width of most decision zones, and our wire carries no assay field. The absolute cut points that do exist (Child-Pugh 3.5 and 2.8 g/dL) are staging points inside diagnosed cirrhosis, not positions for a healthy adult. Duly EB, Grimason S, Grimason P, Barnes G, Trinick TR. Measurement of serum albumin by capillary zone electrophoresis, bromocresol green, bromocresol purple, and immunoassay methods. J Clin Pathol 2003;56(10):780-781
- ALT. The ACG guideline's 'true healthy normal' is not one number but a spread across studies (29-33 IU/L male, 19-25 IU/L female), and the guideline itself calls for a national standardization effort that has not happened — a source asking for a ruler to be built is not a ruler. Laboratory ULNs for ALT ranged from 31 to 72 U/L across 67 labs in a single state, and P5P-supplemented and unsupplemented assays differ by about 2.8 IU/L against a threshold near 30, with no assay field on our wire. Kwo PY, Cohen SM, Lim JK. ACG Clinical Guideline: Evaluation of Abnormal Liver Chemistries. Am J Gastroenterol 2017;112:18-35, doi:10.1038/ajg.2016.517
- AST. No body publishes a healthy-normal AST in absolute U/L — the ACG guideline's severity categories are all multiples of the reporting laboratory's own ULN — and the measurement itself is not comparable across labs because assays run with and without pyridoxal-5'-phosphate differ by about 5.8 IU/L for AST, with no assay field on our wire. AST is also not liver-specific: it rises with skeletal muscle work, which for this user is a routine training effect rather than a liver signal. Lee J-S, Lee K, Kim SM, et al. Effects of Pyridoxal-5'-Phosphate on Aminotransferase Activity Assay. Lab Med Online 2017;7(3):128-134, doi:10.3343/lmo.2017.7.3.128
- Hematocrit. The haematocrit an analyser reports is not measured, it is computed as MCV x RBC, so it inherits both of their method errors and differs systematically from the ICSH centrifuged packed-cell-volume reference method. WHO defines anaemia on haemoglobin, not on haematocrit, and no body publishes absolute haematocrit bands; the only numbers on the report are one laboratory's own interval, which is provenance, not a ruler. Both tails are adverse and neither is bounded by a citable external table. Wennecke G, "Hematocrit — a review of different analytical methods", Acute Care Testing (Radiometer), September 2004; ICSH, Recommendations for reference method for the packed cell volume (ICSH Standard 2001)
- Absolute immature granulocytes. The absolute IG count is a vendor-specific analyser channel with no standardisation programme behind it and no guideline thresholds. Published intervals come from single-centre studies on one instrument, and their own authors conclude that each region must establish its own; the analyser count is also not interchangeable with the manual microscopic differential. A number whose meaning is defined by the instrument that produced it cannot be an external, instrument-compatible ruler. Monteiro WO, Bó SD, Farias MG, Castro SM, "Definition of Reference Range for the Immature Granulocytes Parameter Provided by a Hematology Analyzer", Clinical Laboratory 2021;67(1) (doi:10.7754/Clin.Lab.2020.200439)
- Immature granulocytes. IG% is a proprietary analyser channel, not a standardised measurand: it is not interchangeable with the manual microscopic differential it replaces, its error grows with the value itself, and the published 'reference ranges' are single-laboratory studies whose own authors conclude that each region must derive its own. It is a percentage on top of that, so it inherits the moving-denominator problem as well. No external absolute ruler exists. Monteiro WO, Bó SD, Farias MG, Castro SM, "Definition of Reference Range for the Immature Granulocytes Parameter Provided by a Hematology Analyzer", Clinical Laboratory 2021;67(1) (doi:10.7754/Clin.Lab.2020.200439)
- Mean platelet volume. MPV is the clearest instrument-incompatibility in the panel: there is no established international standard, values differ by analysis principle (impedance, optical, image), by gating strategy, and by the red cells in the same sample, and platelets swell in EDTA so the number depends on how long the tube sat before it was run. An ICSH inter-laboratory study in 2022 was still only demonstrating the feasibility of a candidate MPV standard. A threshold stated on one analyser is not a threshold on ours. Harrison P, Price J, Didembourg M, et al., "Feasibility of a mean platelet volume standard: an international council for standardization in hematology (ICSH) inter-laboratory study", Platelets 2022;33(8):1159-1167 (doi:10.1080/09537104.2022.2060956)
- Folate. WHO is the only body with folate thresholds and it states that they are population-level indicators that cannot be read on an individual, and that they are tied to the microbiological assay with results shifting between calibrators and microorganisms. US clinical laboratories including BioReference report folate by competitive protein-binding immunoassay, not the microbiological assay, so WHO's numbers are not instrument-compatible with this report. WHO Guideline: Optimal Serum and Red Blood Cell Folate Concentrations in Women of Reproductive Age for Prevention of Neural Tube Defects (2015)
- Vitamin B12. NIH's own fact sheet states outright that the boundary between normal and deficient serum B12 varies by method and by laboratory. Serum B12 is also a poor standalone indicator — the commonly quoted 200–300 pg/mL zone is explicitly a grey zone requiring methylmalonic acid to resolve — so no assay-independent absolute band system exists to publish. NIH Office of Dietary Supplements, Vitamin B12 — Health Professional Fact Sheet
Nobody publishes a threshold at all (5)
There is no external, absolute line to cite. That is the whole answer, and inventing one is the thing this page exists to promise we do not do.
- Globulin. Globulin is not measured — it is total protein minus albumin, so it inherits both assays' method bias, including the BCG/BCP albumin difference. The only published cut point we could find is a disease-screening trigger for antibody deficiency, stated in g/L and explicitly tied to one albumin methodology, which is not a position ruler. Jolles S, Borrell R, Zouwail S, et al. Calculated globulin (CG) as a screening test for antibody deficiency. Clinical & Experimental Immunology 2014;177(3):671-678, doi:10.1111/cei.12369
- Red cell distribution width. No guideline body publishes RDW cut points. Worse, 'RDW' is two different quantities depending on the analyser — RDW-CV (a percentage that mathematically contains MCV) and RDW-SD (a width in femtolitres read off the histogram) — and the wire carries no field saying which BioReference reported, only that the unit is %. A quantity that is arithmetically entangled with MCV and carries only a laboratory-specific interval cannot be placed on an external absolute scale, notwithstanding the large epidemiological literature associating high RDW with mortality. Yang K, Pan Y, Yan B, et al., "Red blood cell distribution width-standard deviation but not red blood cell distribution width-coefficient of variation as a potential index for the diagnosis of iron-deficiency anemia in mid-pregnancy women", Open Life Sciences 2021;16(1):1213-1218 (doi:10.1515/biol-2021-0120)
- Apolipoprotein A1. No guideline body publishes named categories or cut points for apoA1 itself. NCEP ATP III examined it and declined to recommend it at all, and the best recent mortality data are U-shaped - both very low and very high apoA1 track higher mortality - so we cannot honestly assert even a direction, let alone a band edge. A laboratory's printed apoA1 reference interval is that laboratory's own interval, not a ruler. Third Report of the National Cholesterol Education Program (NCEP) Expert Panel on Detection, Evaluation, and Treatment of High Blood Cholesterol in Adults (Adult Treatment Panel III), Final Report, NIH/NHLBI, 2002; and Faaborg-Andersen CC, et al., "U-shaped relationship between apolipoprotein A1 levels and mortality risk in men and women," European Journal of Preventive Cardiology 2023;30(4):293-304
- VLDL cholesterol. No guideline publishes an ordered, named classification of VLDL cholesterol. ATP III offers only a single definitional sentence — that a normal VLDL-C is under 30 mg/dL — inside its derivation of the non-HDL-C goal, and names no categories above or below it. A reported VLDL-C is also not a measurement: it is triglycerides divided by five inside the Friedewald calculation, so any band would silently restate the triglyceride band, and the division is invalid above 400 mg/dL of triglycerides. The only other number available is BioReference's own reference interval, which is provenance and never a ruler. NCEP ATP III Final Report, NIH Publication No. 02-5215 (September 2002), Section II.3 — read alongside the same report's "If the triglyceride level is below 400 mg/dL, this value can be divided by five to estimate the VLDL-cholesterol level" and "For persons with triglycerides over 400 mg/dL, estimation of LDL cholesterol by this method is not accurate."
- Urine urobilinogen. The European guideline states in as many words that no commonly available comparison method exists for urinary urobilinogen and that the test is obsolete for detecting liver disease. With no comparison method there is no traceability, no cross-instrument compatibility, and no published threshold to state - each manufacturer is told to document its own validation. Kouri TT, Hofmann W, Falbo R, Oyaert M, Schubert S, Gertsen JB, Merens A, Pestel-Caron M. The EFLM European Urinalysis Guideline 2023. Clin Chem Lab Med, Table 15 footnote
How a standing moves
- Weekly, not daily. Tiers are evaluated at week boundaries. The one thing that moves daily is the “how far to the next tier” line, which runs on your best right now — and says so, because it and the tier are two different clocks.
- A crossing waits two weeks. A tier only changes once the new one has held for 14 days, in either direction. There is no countdown anywhere; you see the outcome, not the wait.
- Not logging never lowers anything. A gap freezes a standing where it was. After 60 days it is marked stale, which says the number is old — it does not make the number smaller.
- A drop is not an event. No notification, no badge removal, nothing to lose. Your dated best is kept; the chart tells the truth about now. A chess club goes further: it is absolute, so it does not move at all once earned, and it waits for nothing — a hold exists to stop a band flickering across a line, and something that only ever goes up cannot flicker.
- A percentile is never a headline. It appears on the receipt sheet, with the pool named beside it, and nowhere else.
What we refuse to rank, and why
- Sleep duration. Its relationship with health is U-shaped, so a high percentile would be a warning printed as a win. Bedtime consistency is the one axis of sleep that earned a comparison, and even that is an anchor with the percentile refusal printed on it — see above.
- Books. Books per year is self-reported and easy to game by reading shorter books. Ranking it optimises the wrong life.
- Resting heart rate. The only published percentile table is NHANES’s seated, awake clinical pulse — a different quantity that runs 10–15 bpm above a sleeping wrist reading, so placing yours on it would flatter everyone by construction. The largest wearable cohort publishes a mean and a range, not percentiles, and a resting heart rate never stands in for a VO₂max either way: beta blockers and illness both move it the “right” way. No VO₂max source, no cardio placement. (Nightly HRV once sat in this list; it left when a compatible-instrument percentile table was published — see its section above. This is what an amendment on new evidence looks like.)
- Steps.Nobody publishes fine-grained step percentiles for a phone-and-watch instrument; the best available is a Fitbit cohort’s quartiles, and steps are the easiest number on this page to game by carrying your phone differently.
- Total screen time. A total indicts the maps, the boarding passes and the camera, and ranking it low is an invitation to game the measurement. The distracting-apps anchor above compares only a set you chose yourself, against a metered average, with no percentile anywhere.
- Any score of our own. No composite, no life score, no fitness age, and no Elo for anything we do not have opponents for. Chess and league ratings, when we show them, are the platforms’ own numbers passed through unchanged.
- Percentiles in the doctor’s PDF. Handing a clinician a wearable estimate placed on a lab-registry table would be worse than useless. That document carries raw values, trends and provenance.
When this page is wrong
Reference data that ages silently becomes a quiet lie, so every table here carries the date of the snapshot it was reduced from, and so does every place the app shows a number from it. But a date is not a monitor. Each table also declares how it ages and how long it may go unattended, in the same file the numbers live in, and a scheduled job reads those declarations every week and files an issue against anything that has passed its own limit.
The declarations below are rendered from that file, not written here. A page describing a policy in prose is a page that can disagree with the policy — which is the failure this whole section is about.
- The strength ruler — drifts
OpenPowerlifting republishes the bulk archive continuously as meets are entered, so the same reduction re-run today returns different percentiles. The snapshot date is the clock: past six months the tiers are quoting a pool that has moved on, and the fix is mechanical — re-run the reduction and commit the new snapshot.
Snapshot . We re-cut it at least every 180 days; past that, the monitor says so.
- The accessory-strength rulers — drifts
FitnessVolt republishes the API cohorts and the web model in place, so a later fetch can change both the self-reported percentile rows and the modeled RDL thresholds. Refreshing is mechanical: rerun the vendor script, review the provenance and evidence labels, and commit the new dated snapshot.
Snapshot . We re-cut it at least every 180 days; past that, the monitor says so.
- The chess distribution — drifts
Lichess recomputes this histogram weekly and publishes only the current one, so our copy starts diverging the day after it is scraped. Re-fetching costs one page load, which is why the threshold here is tighter than the strength ruler's.
Snapshot . We re-cut it at least every 90 days; past that, the monitor says so.
- The cardio ruler — superseded
This is a paper, not a feed. The 2022 percentiles do not change, so the snapshot date being years ago is not decay, and reporting it as decay would be a false alarm that trains everyone to ignore the real one. It goes stale exactly once — when the registry publishes a newer edition, as it did in 2015, 2017, 2019 and 2022 — and no age threshold of ours can detect that, because the signal is on a DOI a human has to read. So the monitor asks on a yearly cadence, comfortably inside the shortest gap between editions, and the clock it runs from is the day somebody last looked rather than the day the paper was published.
Snapshot , last re-checked . Every 365 days the monitor asks a person the one question a clock cannot answer — Has the FRIEND registry published a cardiorespiratory-fitness reference standard newer than the 2022 edition (Kaminsky et al., Mayo Clin Proc 2022;97(2):285-293)?
- The HRV ruler — superseded
A paper does not drift: the 2020 quartiles do not change, so snapshot age is not decay. It goes stale exactly once — when a larger compatible-instrument reference is published — and only a human reading the literature can see that, so the monitor asks yearly.
Snapshot , last re-checked . Every 365 days the monitor asks a person the one question a clock cannot answer — Has a larger consumer-PPG, sleep-window RMSSD percentile reference been published that supersedes Natarajan 2020 — in particular, a peer-reviewed Apple Heart & Movement Study or All of Us HRV reference table with age/sex percentiles?
- The steps ruler — superseded
A paper does not drift: the published quartiles do not change, so snapshot age is not decay. It goes stale exactly once — when a larger compatible-instrument reference is published, or when All of Us publishes a newer characterization of the same growing dataset — and only a human reading the literature can see that, so the monitor asks yearly.
Snapshot , last re-checked . Every 365 days the monitor asks a person the one question a clock cannot answer — Has a newer or larger consumer-wearable daily-steps percentile reference been published that supersedes Patten 2026 — in particular a newer All of Us wearables characterization, or a peer-reviewed Apple Heart & Movement Study steps table with sex/age percentiles?
- The League distribution — drifts
The live distribution refreshes continuously and moves with season resets and Riot recalibrations (the 2026-03-02 MMR-to-rank recalibration reshaped Iron in a week, which is also why nothing older than March 2026 may replace this table). 210 days spans a ranked split with slack; the fix is re-transcribing a newer dated capture by hand — the source's terms rule out an automated pull.
Snapshot . We re-cut it at least every 210 days; past that, the monitor says so.
- The laboratory population ruler — superseded
This is a frozen historical survey distribution. It changes only when NCHS releases a newer complete, method-compatible cycle or corrects a pinned public-use file.
Snapshot , last re-checked . Every 365 days the monitor asks a person the one question a clock cannot answer — Has NCHS released a newer complete NHANES cycle containing all ten compatible laboratory inputs and final survey weights, or corrected one of the pinned 2017-March 2020 files?
- The sleep anchor — superseded
A paper does not drift: the 2023 Oura numbers are the 2023 Oura numbers forever. It goes stale only when somebody publishes a better pool — larger, percentile-gridded, or closer to this product's users — which the Apple Heart & Movement Study preprint may become. So the monitor asks on a cadence rather than asserting an age.
Snapshot , last re-checked . Every 365 days the monitor asks a person the one question a clock cannot answer — Has a newer or larger consumer-wearable study published sleep-timing-variability norms in a peer-reviewed venue — in particular, has the Apple Heart & Movement Study sleep-consistency analysis (a 2026 preprint at the URL below, >81,000 Apple Watch wearers) been published with distributions?
- The screen anchor — drifts
Online Nation is an annual series: Ofcom republishes the same measurements every December from the following May's Ipsos iris fieldwork, so the anchor decays from the day it was keyed and the fix is mechanical — re-key the new edition's figure and commit. 430 days is the annual cadence plus slack for a late edition.
Snapshot . We re-cut it at least every 430 days; past that, the monitor says so.
If you think a number in the app disagrees with what this page says, that is a bug and we want it: the app shows the same citation on the sheet the number appears on.