In July 2026 we rebuilt the way GearCheck turns a blood panel into a number. Scores moved. The median across our user base fell from 85 to 76, and the practical ceiling dropped from 100 to 91. Nobody got sicker overnight. The old number was flattering, and it was flattering in a way that hid exactly the cases that mattered most.
This article explains what the score actually measures, why a single number is a hard thing to build honestly, and where the current model still has limits you should know about. If you use GearCheck, this is the reasoning behind every number you see. If you do not, it is a reasonable template for interrogating any health score you are handed.
A blood panel is between fifteen and ninety measurements. Compressing that into one integer throws information away by definition. The only question worth asking is whether it throws away the right information.
Consider two athletes. The first has eleven markers sitting slightly outside optimal: mildly elevated liver enzymes, a marginal lipid panel, unremarkable inflammation. Not one of them would prompt a phone call. The second has ten markers dead-centre and one that is not: an eGFR of 48, meaning his kidneys are filtering at roughly half the rate they should.
Average the deviations and the first athlete scores worse. He has more things wrong. But the second athlete has stage 3 chronic kidney disease and does not know it. Any scoring model that rewards him for his nine good markers is not just imprecise — it is actively dangerous, because it delivers reassurance precisely where alarm was warranted.
💡A health score is only useful if it is hardest to get a good one when something is genuinely wrong.
That is the design constraint everything below follows from. The model must be able to say: one thing here is bad enough that the rest does not rescue it.
It is worth being direct about the previous version, because the failure is instructive and it is common.
The old pipeline asked a language model to write the report, then read the score back out of the prose it had produced. A regular expression scanned the generated HTML for section headings and severity words, counted what it found, and turned that into a number. The narrative came first; the score was reverse-engineered from it.
This works until it does not. If the model wrote three sentences about a borderline cholesterol reading and one clause about a critically low eGFR, the scraper counted three severity signals against one. Verbosity became weight. Worse, the score was not reproducible: the same panel run twice could produce different prose and therefore a different number, with no way to explain the gap to anyone.
The current architecture inverts that. A deterministic rules engine evaluates the panel and emits findings as objects: each one carries a rule identifier, a severity, the specific measurements that triggered it, and the mechanism connecting them. Those objects drive the score. Only afterwards does a language model turn them into readable prose — and it is constrained to the findings it was handed. It phrases; it does not select.
There is one place a language model does contribute a finding rather than a sentence. A rule table is finite, and a panel can show something no rule was written for. So after the rules have run, a model is shown the markers no finding covered and asked whether any of them warrant one. Those assessed findings appear in your report and are labelled as what they are. They do not touch your score.
Why an assessed finding cannot move your number
Every finding gets a severity on a fixed four-point vocabulary, and every level maps to a fixed fraction of that domain's risk budget. This is a closed list on purpose: an open-ended severity scale is an invitation to drift.
Severity vocabulary
| Marker | Level | Risk contribution |
|---|---|---|
| Critical | Organ function is measurably impaired, or a value sits in a range associated with acute events | 1.00 — the full risk weight of that domain |
| High | A clear pathological signal that warrants medical review, not just monitoring | 0.60 |
| Moderate | Outside the athletic target range with a plausible mechanism, but not yet clinical | 0.35 |
| Low | A drift worth watching; often the earliest useful signal | 0.15 |
Severity itself is continuous underneath. A marker does not jump from fine to moderate at a threshold; it ramps. Each marker has a distance parameter — internally we call it the full-deviation point — defining how far past its reference boundary a value must sit before it counts as maximally deviant for its severity band. An ALT of 45 U/L and an ALT of 400 U/L both sit above the reference range, and the model does not pretend they are the same event.
Some markers are deliberately excluded from scoring altogether. Sodium tells you about hydration and diet on the day of the draw. Absolute lymphocyte count moves with a cold. These are descriptive: they appear in your report with context, and they do not move the number, because a score that swings on whether you drank enough water that morning is measuring the wrong thing.
Findings are grouped into eight physiological domains, and the domains carry different weights. This is a value judgement and it should be stated plainly rather than buried: we weight by how irreversible the damage is and how strongly the domain predicts hard outcomes.
Domain weights
| Marker | Domain | Weight and reasoning |
|---|---|---|
| Kidneys | 0.186 | Nephron loss does not reverse. Damage accumulates silently for years and is usually detected too late. |
| Cardiovascular | 0.186 | The dominant cause of death in AAS-using populations, and the one most responsive to early action. |
| Blood | 0.140 | Haematocrit and viscosity drive acute events — stroke, embolism — on a timescale of weeks, not decades. |
| Liver | 0.112 | Serious, but genuinely reversible when the cause is removed. Weighted below kidneys for exactly that reason. |
| Metabolic | 0.112 | Insulin resistance is a strong long-horizon predictor and is highly modifiable. |
| Hormones | 0.084 | Central to how you feel and to long-term bone and cardiovascular health, but rarely the acute threat. |
| Inflammation | 0.065 | A powerful amplifier of other risks more than a standalone one — which is how the model uses it. |
| Thyroid | 0.045 | Consequential when genuinely disordered, but assay noise and non-thyroidal illness produce many false signals. |
| Iron | 0.040 | Ferritin and saturation move with donation, blast and inflammation. Real, common and correctable — which is why it carries weight instead of sitting in a footnote. |
| Vitamins | 0.030 | The lightest domain, deliberately. Deficiency is worth knowing and worth fixing, but it is not what kills this population. |
Iron and vitamins are recent additions. They were previously measured, shown in the report, and then left out of the arithmetic — a reader could be frankly iron-deficient, read about it in their own report, and watch the headline number ignore it. They now carry weight, small and proportionate to what they actually predict. Adding two domains rescaled the other eight; the ratios between them are unchanged.
Within one domain, several findings often point at the same underlying problem. Elevated creatinine, reduced eGFR and raised cystatin C are not three independent kidney problems — they are three views of one. Adding their severities would triple-count a single issue.
The model combines them the way you would combine overlapping evidence for the same conclusion: it treats each finding as partial evidence of domain-level risk, multiplies the probabilities that each one is not the whole story, and takes the complement. Statisticians call the underlying shape a noisy-OR. Then it normalises by the square root of the number of findings, so that a domain with six mild findings does not accumulate more apparent risk than a domain with one severe one.
Finally — and this is the part that matters — a floor is applied. The domain's risk can never be lower than the risk of its single worst finding. No amount of surrounding normality dilutes a critical result. That one rule is what stops the eGFR-of-48 athlete from being rescued by his nine good markers.
Eight domain scores then have to become one number. A weighted average is the obvious move and the wrong one, for the same reason as before: it lets seven healthy systems bury one failing one.
Instead the domains are combined with a weighted power mean using an exponent of 0.5, which sits between an arithmetic and a harmonic mean. In practice this pulls the result toward the weakest domain rather than the middle. Then two hard constraints apply on top.
Constraint 1: the worst-domain ceiling
Your overall score cannot exceed your worst domain score by more than 35 points. If your kidneys score 40, your overall score is capped at 75 no matter how immaculate everything else is. The margin only applies when that domain has at least two markers behind it, so a single odd result on a thin panel cannot drag your whole report down on its own.
Constraint 2: the organ-failure override
When a finding indicates that an organ system is failing rather than strained, the overall score is capped at 30 outright. At that point the number stops being a nuanced summary and becomes a single instruction: take this to a doctor now. There is no combination of good markers that lifts it.
This is the change most people notice first, and the one we think is most important.
A fifteen-marker panel cannot tell you your kidneys are healthy. It can tell you it did not find a kidney problem, which is a much weaker statement. The previous model scored an unmeasured domain as though it were fine, which meant the cheapest, thinnest panel reliably produced the best-looking score. That is a perverse incentive and we shipped it for a long time.
💡Absence of evidence was being scored as evidence of absence. A thin panel bought a good number.
Now every score carries a coverage figure: how much of the risk model your panel actually addressed. Coverage does not subtract points — an unmeasured domain is not a problem, it is an unknown. Instead it caps the maximum achievable score. You cannot reach 100 on a panel that never looked at your kidneys, because the model has no basis for that claim.
Below a coverage threshold, the report shows the word provisionalalongside the number. The score still exists and the findings are still real, but the label warns you that it rests on a partial picture.
Provisional was not enough. A panel of three hormones was still being handed a two-digit verdict on your health, and a number on a page carries authority no qualifying label can take back. There is now a floor beneath which the report prints an em dash and says why.
What a panel must clear to be scored at all
| Marker | Requirement | Why this threshold |
|---|---|---|
| At least 3 domains | 3 | Two domains cannot describe a body. A panel that looks only at hormones is a hormone report, not a health assessment. |
| At least 8 markers | 8 | Below this the arithmetic is dominated by whichever handful of markers happened to be ordered. |
| Effective coverage 0.40 | 40% | Weighted by depth, not by count — see below. Four shallow domains can still fail this where two deep ones pass. |
Coverage is measured by depth rather than by how many tubes were filled. The question a domain has to answer is not "how many markers do you have" but "can these markers tell us whether this domain is healthy". Some single markers can: cystatin C answers for the kidneys, GGT for the liver, TSH for the thyroid, hs-CRP for inflammation, HbA1c for metabolic, ApoB for cardiovascular. One of those counts a domain as fully covered. Without one, the domain's coverage grows with the square root of the markers present against what a real panel would carry — so the second marker adds a great deal and the sixth adds little.
Haematocrit shows why single thresholds fail and what replaced them.
The standard approach flags anything above roughly 52% and escalates from there. Applied to an AAS-using population this produces constant alarm, because elevated haematocrit is an expected pharmacological effect rather than a disease. Alarm that fires on everyone gets ignored, including on the person it was meant for.
The model now uses four bands, and — critically — the severity of the top band depends on whether the blood is actually more likely to clot, not on the haematocrit alone.
Haematocrit bands
| Marker | Range | How the model treats it |
|---|---|---|
| Up to 0.52 | Within expectation for an enhanced athlete | No finding. Reported with context. |
| 0.52 – 0.54 | Elevated, common, usually manageable | Low severity. Worth tracking across panels. |
| 0.54 – 0.58 | Meaningfully raised viscosity | Moderate severity, escalating with cofactors. |
| Above 0.58 | High, but severity depends on context | High on its own; critical when viscosity cofactors are present. |
The cofactors are elevated CRP or hs-CRP, a raised platelet count, and elevated blood pressure. Each independently increases clotting risk, and together with a high haematocrit they compound rather than add. A haematocrit of 0.58 with clean inflammation, normal platelets and controlled blood pressure is a different clinical object from a haematocrit of 0.58 with hs-CRP at 4 mg/L, platelets at 420 and blood pressure at 150/95 — even though a threshold-based system reports them identically.
Blood pressure is the single most powerful modifiable cardiovascular risk factor in this population, and until recently the model accepted your reading and then never looked at it. It does now, and it is graded on a curve rather than at a threshold: risk starts accruing above 120/80 and climbs steeply, so 128/82 registers as a nudge while 155/98 registers as a serious finding rather than the next category up.
A single reading is discounted against a series. Pressure taken once, in a clinic, after you drove there, is weaker evidence than three readings at home — so one reading carries roughly sixty percent of the weight a series does. The reading, its grade and the reasoning appear in your report, and a genuinely elevated pressure is raised as a key finding rather than buried in a table.
Most markers are graded by how far outside their reference range they sit. HbA1c is not. It is read on a fixed ladder of absolute values, because its diagnostic thresholds are only a few percent apart and a curve built for wide-ranging markers cannot see them: under the previous model an HbA1c at the diabetes threshold contributed a severity of 0.10, and a fasting glucose of 7.0 mmol/L contributed 0.026. The metabolic domain sat at a median of 100 across every panel we hold.
The HbA1c ladder
| Marker | HbA1c | Most the metabolic domain can score |
|---|---|---|
| Genuinely good | below 5.5% | 100 — nothing is deducted |
| Creeping | 5.5% | 90 |
| Creeping | 5.6% | 80 |
| Prediabetic range | 5.7% | 70 |
| Prediabetic range | 5.9% | 50 |
| Prediabetic range | 6.1% | 30 |
| Top of prediabetes | 6.4% and above | 0 |
Ten points per additional 0.1%. This is deliberately stricter than clinical convention — the domain reaches zero at the top of the prediabetic range rather than at the 6.5% diagnosis — because by the time a diagnosis is available the thing worth catching happened years earlier.
A cystatin C of 86 mg/L is sixty-five times the upper bound of the reference range. It is not a kidney in trouble; it is a unit confusion or a transcription slip. The model used to grade values like that at maximum severity, which meant a data-entry error could tell a healthy reader their kidneys had failed. A haemoglobin of 0.35 mmol/L and a haematocrit of 1.00 — a hundred percent packed cells — were doing the same thing elsewhere in the fleet.
Readings outside what a living person can produce are now set aside: shown as received, marked as a reporting error, excluded from the score, and invisible to every finding rule. The bounds sit at the edge of survivability and not at the edge of unusual, because the failure mode in the other direction is far worse. A haematocrit of 0.70 on a heavy blast, a potassium of 6.8, a glucose of 30 in ketoacidosis and a white count of 150 are all real, all urgent, and all keep their full weight. eGFR is deliberately left out of the check entirely: an eGFR of 3 is survivable on dialysis and must never be dismissed as noise.
A score is only as good as the yardstick underneath it, and this is where most general-purpose tools fail an enhanced athlete.
Laboratory reference ranges are constructed as the central 95% of a reference population. They describe what is common, not what is healthy, and the population they describe is not you. A range built from sedentary adults will call a lifter's creatinine abnormal because he carries more muscle, and will call a testosterone level of 8 nmol/L acceptable because plenty of the reference population sits there.
GearCheck maintains its own medical logic — target ranges built for athletes and for people on hormonal protocols — and that document takes precedence over whatever the lab printed. Where our logic has no specific target for a marker, the lab range is used as the fallback. Where you have set your own range because you and your doctor have a reason to, yours wins over both.
Reference range precedence
| Marker | Source | When it applies |
|---|---|---|
| Your custom range | Highest | Whenever you have set one. A range you and your physician chose is not overridden by anything. |
| GearCheck medical logic | Next | Athlete- and protocol-adjusted targets, applied wherever we have a defensible one. |
| Lab reference range | Fallback | Only where neither of the above has a target for that marker. |
One example of why this matters. HDL cholesterol was, until recently, scored in our engine against a floor of 1.5 mmol/L. That number came from an optimisation target, not a risk threshold, and it made a perfectly workable HDL of 1.1 read as suppressed in every report it appeared in. It is now 1.0, matching the level at which risk actually starts to rise. Roughly a fifth of our users saw their cardiovascular domain move on that single correction.
There is a split running through the whole pipeline, and it is the single most important thing to understand about how GearCheck reports are produced.
The model decides what is wrong. The code decides how much risk that represents.
Rules cannot cover everything. Medicine has more patterns than any rule table encodes, and a domain with three unusual-looking markers and no matching rule should not be silently reported as clear. So for domains where the deterministic engine produced no finding, a language model is given the measured values, both the athletic target band and the clinical normal band, and asked for an assessment.
What it returns is constrained hard. It must choose a severity from the same closed four-level vocabulary. It must cite specific measured values as evidence, and every citation is checked against the actual panel — an assessment referencing a marker that was not measured is discarded rather than displayed. Where a deterministic rule already covers the marker, the model's severity is capped at the rule's severity; it can agree or soften, never escalate past the rule.
A second pass looks across domains for patterns connecting findings that already exist — the elevated liver enzymes and the lipid shift and the insulin resistance that together suggest one metabolic story rather than three unrelated ones. It is only permitted to connect findings the engine already produced, in at least two domains that already carry findings. It cannot introduce a new problem through the back door of a pattern claim.
When this model went live, most scores fell. Across our user base the median went from 85 to 76 and the ceiling from 100 to 91. Four changes account for nearly all of it.
Coverage now caps the maximum
Thin panels used to score well by not measuring anything that could go wrong. They now score as what they are: incomplete. This is the largest single contributor, and it affects the previously highest scores most.
The worst domain now constrains the total
The 35-point ceiling means a single struggling system is visible in the headline number instead of being averaged into invisibility.
Unmeasured is no longer treated as fine
A domain with no markers used to contribute a perfect sub-score. It now contributes nothing at all and reduces your coverage instead.
Athlete ranges cut in both directions
Tighter targets than the lab's mean some values that read as normal now read as suboptimal. The same correction moves other markers the other way — the HDL floor above is an example where the change was in your favour.
Reports generated before the change keep the number they were issued with. We do not retroactively restate a report you have already read and possibly discussed with a doctor. If you want an old panel scored under the current model, regenerating it will do that, and the report will say which model produced it.
A few things worth internalising, now that you know what the number is made of.
Publishing the reasoning means publishing the limits too.
The domain weights are a judgement, not a measurement. They come from the literature on irreversibility and outcome prediction plus clinical reasoning, and another defensible model would choose slightly different numbers. They are not derived from an outcome dataset, because no outcome dataset for this population exists at the scale required.
Single panels have limited resolution. One measurement cannot separate a genuine trend from assay variation. The model treats changes below roughly 10% between panels as noise for exactly this reason, but a single panel has no trend to draw on at all. Your third panel is meaningfully more informative than your first.
Rules cover the known patterns. The language model assessment exists to cover the gaps, and it is constrained precisely because it is the least predictable part of the pipeline. Unusual presentations remain the hardest case for any automated system, including this one.
Some markers still lack athlete-specific targets. Where our medical logic has no defensible target, the lab's range is used — and for enhanced athletes that range is frequently wrong. We are working through these marker by marker. It is slow because each one requires a defensible source rather than a guess.
