Skip to content

feat: add metabolic age derived value#1417

Open
DanyPM wants to merge 1 commit into
oliexdev:masterfrom
DanyPM:feat/metabolic-age
Open

feat: add metabolic age derived value#1417
DanyPM wants to merge 1 commit into
oliexdev:masterfrom
DanyPM:feat/metabolic-age

Conversation

@DanyPM

@DanyPM DanyPM commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Add Metabolic Age derived value

Summary

Adds Metabolic Age as a new derived measurement value, computed automatically for every measurement that carries a body-composition signal (body-fat % or lean body mass). It follows the existing derived-value pattern (isDerived row, recomputed on every insert/update) alongside BMI, BMR, TDEE, etc.

Scientific basis

Metabolic age answers: at what age would a population-average metabolism match this person's actual metabolism? We derive it by inverting the age term of an established BMR equation:

  • Actual BMR — Katch-McArdle: BMR = 370 + 21.6 × FFM(kg). This uses fat-free mass, so it reflects the individual's real body composition (muscle mass), not just body size. FFM is taken from a measured LBM value, or computed as weight × (1 − bodyFat%/100).
  • Reference BMR-vs-age — Mifflin-St Jeor: BMR = 10w + 6.25h − 5·age + s, with sex constant s = +5 (male) / −161 (female). This is the population-normative curve at the user's own weight, height and sex, and it is linear in age.
  • Metabolic age is the age at which the reference curve equals the actual BMR. Solving the linear equation:
metabolicAge = (10w + 6.25h + s − actualBMR) / 5

A higher actual BMR (more lean mass) yields a younger metabolic age, and vice-versa — the expected physiological direction. The result is clamped to a plausible 15–99 range.

Both formulas are peer-reviewed and already used elsewhere in openScale (Mifflin-St Jeor for BMR; Katch-McArdle in StandardImpedanceLib).

Why a body-composition signal is required

If the "actual" BMR were itself the anthropometric Mifflin-St Jeor value, inverting the curve would trivially return the chronological age. The metric is only meaningful when the actual BMR is derived from fat-free mass. Metabolic age is therefore computed only when the measurement has body-fat % or LBM; plain manual weigh-ins are skipped (and any stale value is removed).

Changes

  • New type MeasurementTypeKey.METABOLIC_AGE and unit UnitType.YEARS (displayed as "N years", whole numbers); dedicated hourglass icon.
  • Computation DerivedValuesCalculator.processMetabolicAgeCalculation(...) plus fat-free-mass derivation (LBM preferred, else weight + body-fat, with unit normalisation) in the recalculation pass.
  • Persistence Room DB version 16 with MIGRATION_15_16 seeding the new derived type; exported schema 16.json.
  • Retroactive backfill existing measurements that already carry body-fat/LBM gain a metabolic-age value on next launch — the one-time derived-value backfill gate was widened (and its logging made specific about which values are missing). The check is idempotent and self-terminating.
  • Display rendered generically by the existing overview/graph/table screens; UCUM export maps YEARS → "a".

Tests

  • DerivedValuesCalculatorFormulasTest — formula correctness (male/female constants, monotonicity: higher FFM → younger age, clamping, null-guards).
  • DerivedValuesCalculatorIntegrationTest — end-to-end against a real Room DB: value is persisted from weight + body-fat and removed when the body-composition source disappears.
  • MigrationTest — full migration chain (incl. 15→16) produces the new type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant