Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WakaAge

Android app (Kotlin + Jetpack Compose) that computes a Fitness Age from Health Connect data.

Package namespace: com.phischmi.wakaage — derived from the GitHub owner. Change namespace/applicationId in app/build.gradle.kts and the package directories if you want a different one.

App icon and branding are deliberately out of scope for this version.


Module layout

Module Type Contains
:domain plain Kotlin/JVM the entire fitness-age algorithm, reference tables, unit tests
:app Android Health Connect access, Room history, ViewModel, Compose UI

:domain has no dependency on Android, Compose or Health Connect — only the Kotlin stdlib and java.time. That is what makes the algorithm exhaustively testable on the JVM: ./gradlew :domain:test needs no emulator and no Android SDK.

:app  ──depends on──▶  :domain
 │                       │
 │ HealthDataRepository   │ FitnessInput ──▶ FitnessAgeCalculator ──▶ FitnessAgeResult
 │ (Health Connect ──▶ FitnessInput)
 │
 └─ FitnessAgeViewModel ──▶ FitnessAgeUiMapper ──▶ FitnessAgeUiState ──▶ Compose

The algorithm

Four steps, all in :domain:

  1. Reduce. Each Health Connect data type collapses to one robust metric — median resting HR, median nightly RMSSD, MET-minutes per week, a 0–100 sleep index, body fat % or BMI, and an estimated VO2max. Implausible samples are filtered first.

  2. Score. Every metric is compared against an age- and sex-normed reference curve (NormCurve), producing a domain age: the age at which that value would be average. Two mappings exist, chosen per metric in the reference file:

    • CURVE_INVERSION — used where the population mean falls steeply and monotonically with age (VO2max, RMSSD). Literally "at what age is your value the average?".
    • Z_SCORE_OFFSET — used where the age gradient is too flat or non-monotonic to invert (resting HR, activity, sleep, body composition). The z-score against the same-age norm is converted into years via an explicit yearsPerSd.

    Every domain age is capped at ± maxDeviationYears from the calendar age so one extreme (or spoofed) metric cannot run away with the result.

  3. Renormalise. Domains without enough data are dropped and the remaining weights are scaled proportionally, so the effective weights always sum to 1 regardless of how much data exists.

  4. Combine. The weighted mean of the surviving domain ages is the fitness age. A confidence score reports the weighted share of all six domains that was actually backed by well-covered data (Σ weightᵢ × coverageᵢ / Σ weight), so it reaches 1.0 only with all six domains at full window coverage.

Below 3 scored domains no number is shown at all — the UI switches to an onboarding view that reports how many domains are still needed.

Domains and default weights

Domain Metric Weight Mapping
Cardiorespiratory fitness estimated VO2max (ml/kg/min) 0.35 curve inversion
Activity MET-min/week 0.20 z-score offset
Resting heart rate bpm 0.15 z-score offset
Heart rate variability RMSSD (ms) 0.10 curve inversion
Sleep composite index 0–100 0.10 z-score offset
Body composition body fat % (fallback BMI) 0.10 z-score offset

CRF is derived: it needs either (BMI + activity) for the NASA/JSC non-exercise equation, or a resting heart rate for the HRmax/HRrest ratio. With both, the two estimates are blended.

Reference values — read this before shipping

All reference numbers live in one file: domain/src/main/kotlin/com/phischmi/wakaage/domain/reference/FitnessReferenceData.kt

The structure is final; the values are provisional. They are rounded, literature-derived placeholders chosen so the algorithm behaves sensibly end-to-end — not a verbatim transcription of any published table, and they must not be presented to users as such. Each block names the source it should be reconciled against ([S1]…[S12]).

To swap in the definitive tables, replace the NormAnchor lists. Nothing outside that file has to change. FitnessReferenceDataTest guards the invariants that must survive the swap: weights sum to 1, every domain has a weight/requirement/citation, inversion curves stay monotone, and feeding a population mean back in returns the age it came from.

Citations shown in the UI live next to them in ScientificSource.kt.

Scope of this version

Built: fitness-age calculation, the hero visualisation, the domain breakdown with per-domain detail sheets, and the missing-data view.

Deliberately not built: streaks, trend charts, notifications. The architecture does not preclude them — every run (including the ones too sparse to display) is already written to a Room database via ScoreHistoryRepository, and observeRuns() is there waiting. No migration will be needed to add the trend view.

Build

./gradlew :domain:test          # algorithm tests, no Android SDK required
./gradlew test                  # all unit tests
./gradlew :app:assembleDebug    # debug APK

local.properties must point at an Android SDK for anything touching :app.

Disclaimer

WakaAge is not a medical device. The fitness age is a comparison against reference values and does not replace clinical assessment. The app reads from Health Connect only — it never writes health data and sends nothing to a server.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages