Skip to content

feat: add mobility context for instrument applicability - #3

Merged
murdercode merged 1 commit into
mainfrom
fitness-age-mobility-context
Aug 10, 2026
Merged

feat: add mobility context for instrument applicability#3
murdercode merged 1 commit into
mainfrom
fitness-age-mobility-context

Conversation

@murdercode

Copy link
Copy Markdown
Contributor

Problem

Many Apple Health-derived metrics are produced by step and gait detection. When that detection cannot run, the host does not receive a low value — it receives a value that does not describe the person. Wearables have been reported as recording zero steps for someone walking with a walker. The calculator had no way to tell that apart from genuine inactivity, so a wheelchair user was scored on steps they cannot take.

What this adds

FitnessAgeMobilityContext on FitnessAgeProfile, declaring which movement instruments can be observed:

Context Covers Removes
ambulatory (default) unaided walking, cane nothing
assistedAmbulation walker, crutches step-derived and gait-derived instruments
nonAmbulatory wheeled mobility the above, plus stand_hours

Inapplicable IDs are unioned with disabledMetricIds before scoring, so removal follows the already-documented path: the field is cleared, the domain divides by the observed local weights that remain, and data quality averages over observed components only.

This is a measurement-applicability input, not a diagnosis or clinical classification. No threshold, curve, local weight, domain weight, or confidence rule changed.

Where the boundaries come from

Boundaries follow measurement literature rather than a diagnosis list:

  • Step counting is described as applicable only to ambulatory populations, degrading further under irregular gait — Suzuki et al., JMIR Formative Research 2025.
  • Weight-bearing aids break step detection rather than adding noise: poor validity with gait aids, near-zero counts during walker use — Kooner et al., J Orthop Surg Res 2024. Wrist error with a walker 31.2% — Jaworski et al., IJERPH 2025.
  • Cane use sits on the other side: ~2% wrist error, which is noise, not inapplicability — same study. Cane users stay ambulatory.
  • Activity targets are not context-specific; WHO 2020 added recommendations for people living with disability, so activeEnergy and exerciseTime stay the applicable instruments instead of being replaced — Bull et al., BJSM 2020.

Every anchor is in the methodology source table.

Deliberately out of scope

Conditions where the instrument works but the expected value is debated (fatigue-related, mental-health-related). Step count is measurable there; changing its target without an evidence anchor would be a scoring change presented as an applicability change. That needs an Algorithm RFC.

Compatibility

No result drift. mobilityContext defaults to ambulatory, which removes nothing; encoded profiles omitting the field decode to ambulatory. Golden fixtures unchanged.

Verification

41 tests pass, 9 new. Two carry the weight:

  • golden parity intact — no drift for existing integrations
  • inapplicableInstrumentsAreRemovedRatherThanScoredAsZero — a context-driven removal produces byte-identical results to an input that never supplied those metrics. That is the invariant that makes this correct rather than charitable.

Many Apple Health-derived metrics are produced by step and gait detection. When
that detection cannot run the host does not receive a low value, it receives a
value that does not describe the person: wearables have been reported as
recording zero steps for someone walking with a walker. Scoring that as
inactivity is a measurement error, and the calculator previously had no way to
tell it apart from real data.

Adds `FitnessAgeMobilityContext` and `FitnessAgeProfile.mobilityContext` with
three values: `ambulatory` (unaided or cane), `assistedAmbulation` (walker or
crutches) and `nonAmbulatory` (wheeled). Metrics the declared context cannot
observe are unioned with `disabledMetricIds` before scoring, so they follow the
documented disabled-metric path: the field is cleared and each domain divides by
the observed local weights that remain. `FitnessAgeProfile.effectiveDisabledMetricIds`
exposes that union.

Boundaries follow the measurement literature rather than a diagnosis list. Step
detection breaks under weight-bearing walking aids, while cane use stays within
reported measurement noise, so cane users remain `ambulatory`. Standing is the
only instrument that separates the two non-default contexts. Conditions where the
instrument works but the expected value is debated are documented as out of scope,
because changing a target there would be a scoring change presented as an
applicability change.

This is a measurement-applicability input, not a diagnosis or clinical
classification. No threshold, curve, local weight, domain weight, or confidence
rule changed.

No result drift for existing callers: `mobilityContext` defaults to `ambulatory`,
which removes nothing, and encoded profiles that omit the field decode to
`ambulatory`. Golden fixtures are unchanged.

Signed-off-by: Stefano Novelli <murdercode@gmail.com>
@murdercode
murdercode merged commit c698018 into main Aug 10, 2026
5 checks passed
@murdercode
murdercode deleted the fitness-age-mobility-context branch August 10, 2026 10:01
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