You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi, I got a Whoop 5.0 a few days ago and was using it with NOOP, but since this app now has 5.0 support I wanted to check it out as well. I was wondering, are the metrics (eg. sleep score, recovery/strain, body age, ...) are computed with the same algorithms as in NOOP, or are there any major differences?
Edit: here's what AI told me after pointing it at both projects, but it would be nice to get a confirmation from a more knowledgeable human:
Slop response
Short answer
No — they are not the same algorithms. Both projects start from the same open, published primitives (Cole–Kripke, Walch DoG‑HR, van Hees z‑angle immobility, Task‑Force RMSSD/SDNN, Malik ectopic rejection, Karvonen %HRR / Edwards / Banister TRIMP, Tanaka HRmax, robust‑z personal baselines anchored to ≈58 %) and both explicitly say “not a reproduction of WHOOP’s proprietary score.” But every finished metric — sleep staging, the sleep score, recovery, strain, and fitness/body age — is put together differently. They share a philosophy and a hardware‑coord much more than a formula.
Important framing for what I read:
NOOP (/noop): the math lives in android/…/com/noop/analytics/*.kt and is described in docs/ANALYTICS.md. The Kotlin files self‑identify as “faithful Kotlin port of StrandAnalytics/…swift, verified byte‑for‑byte” — so the Android numbers equal the Swift numbers. Everything below about NOOP is from the Kotlin sources, cross‑checked against ANALYTICS.md.
OpenStrap edge (/edge): the actual metric math is in a separate sibling repo, OpenStrap/analytics, pinned in pubspec.yaml (SHA ebe45da…) and not vendored in this directory. So I derived the edge math from the kAlgoVersion changelog in edge/lib/compute/derivation_engine.dart (which narrates every algorithm change) plus the orchestration in edge/lib/compute/crossday_pipeline.dart and the imports it makes from package:openstrap_analytics/onehz.dart.
Cole–Kripke for spine; multi‑session detection + bridging, main‑session pick via AdvancedSleepStager (changelog v24)
epoch classifier
percentile‑band over session‑relative percentiles of RMSSD / HR / Respiration‑Rate‑Variability + motion fraction; deep = still ∧ RMSSD≥70pct ∧ HR≤25pct ∧ regular resp; default stager SleepStagerV2 uses an 11‑min HR‑flatness‑percentile deep gate, validated on a 44‑subject LOSO benchmark
cardioStager: weighted robust‑z over **Rk = mean
parasympathetic signal
RMSSD‑only. Frequency‑domain omitted intentionally — no scipy/neurokit on‑device (ANALYTICS.md, SleepStager.kt)
LF/HF via Lomb–Scargle is a core feature for REM (_windowRemFeatures, even had a hot‑path ANR bug, v45)
→ Same collection of raw inputs (Cole–Kripke + Walch DoG‑HR), but very different classifiers: NOOP stays in the percentile/HR‑flatness family and drops frequency‑domain; edge adds Lomb–Scargle LF/HF and validated against PSG with different feature choices.
edge (crossday_pipeline.dart:271): ana.sleepPerformance(lastTstSec, needSec) → a single‑term TST‑to‑need ratio. Need itself comes from ana.sleepNeed = baseline‑need + sleep debt + day‑strain bonus + nap credit, clamped to 6–11 h. The “coach” outputs (bedtime, cycle wake, target need) live separately in the Sleep Coach, not in a score.
→ Major difference: NOOP blends four axes; edge’s headline sleep number is TST‑vs‑need only.
Recovery
Same skeleton: HRV‑led robust‑z composite against a personal EWMA‑MAD baseline, anchored so z = 0 → ≈58 % (the WHOOP population mean both cite), with a cold‑start / “no honest score ⇒ null” guard.
edge (readinessComposite, readiness_lnrmssd): robust z (median + MAD) with a mean/SD z fallback when MAD == 0 on quantized baselines (v43/v46); readiness_lnrmssd is z of ln(RMSSD) over a 28‑day, self‑excluding baseline; RHR is gated on a real detected sleep session (v47) and skin‑temp is gated on its own signal; no‑impute discipline is the project’s most‑recurring invariant (AGENTS §3, §4.1/4.2).
→ Same idea, but NOOP’s is fully documented (fixed 5‑weight logistic); edge’s exact weights/squash live in the sibling analytics package and lean harder on robust‑z fallbacks and abstention edge cases.
Strain
Identical primitive math (and essentially identical constants):
Both: Karvonen HRR = HRmax − RHR, Edwards 5‑zone or Banister x·0.64·e^(b·x) with b = 1.92 (m) / 1.67 (f), log compression max·ln(TRIMP+1)/ln(D) with D = 7201 (StrainScorer.kt:71) exactly so Edwards’ 24h zone‑5 ceiling (5×1440 = 7200) maps to max. Tanaka HRmax = 208 − 0.7·age.
NOOP “Effort”: rescaled 0–21 → 0–100; maxStrain = 100, D unchanged (StrainScorer.kt:62,71). Adds a steps/active‑kcal floor for low‑cardio days.
edge: keeps two scales — a 0–21 WHOOP‑aligned Day Strain (headline) and a 0–100 Edwards effort (clinical.strain_effort, Karvonen %HRR — changelog v16). Plus a cross‑day EWMA ACWR load model (CTL/ATL/TSB) feeding the Strain Coach (changelog v20, crossday_pipeline.dart).
→ TRIMP/log curve is the same family with literally the same denominator; the difference is scale (NOOP merges to 0–100, edge keeps both) and that edge layers an ACWR load model on top. (Edge also auto‑detects workouts with an HR‑onset bypass — Whipp & Wasserman 1972 — for low‑limb‑swing activities, v51; NOOP has its own AutoWorkoutDetector.)
Body age / fitness age (the clearest divergence)
NOOP FitnessAgeEngine.kt: Nes 2011 HUNT non‑exercise VO₂max waist‑circumference model (coeffs verbatim: 100.27 − 0.296·age + 0.226·PA − 0.369·wc − 0.155·RHR for men, women variant too); PA‑index reconstructed on‑device from workout frequency/duration/intensity; self‑consistent inversion of the same equation at reference RHR=65 / PA=5 so the waist term cancels — headline fitness age needs only age/sex/RHR/PA. Weekly, ≥4‑of‑7‑night coverage gate, ±5‑year band, SEE 5.7/5.1.
edge (crossday_pipeline.dart:315–345): Uth et al. HRmax/RHR ratio VO₂max over baseline RHR, then ana.physiologicalAge()composites VO₂max + RHR + RMSSD + sleep duration + sleep efficiency + daily steps against age‑norms.
→ Fully different VO₂max model: NOOP is a non‑exercise HUNT equation inverted against a population reference; edge is a recovery‑based HR‑ratio plus a multi‑signal physiological‑age composite.
HRV cleaning (underlying all of the above)
Both: Task‑Force RMSSD/SDNN/pNN50, range filter (≈300–2000 ms), Malik 20% local‑median ectopic rejection.
NOOP HrvAnalyzer.kt: gap‑aware RMSSD/pNN50 (no spurious splice across dropped beats).
edge: all‑day RMSSD with Malik clip ≤220 ms (v23), Lipponen–Tarvainen signed‑dRR threshold, Poincaré irregular‑beat screen, LF/HF Lomb–Scargle for sleep and for all‑day HRV.
→ Edge exposes noticeably more HRV surface (frequency domain + Poincaré + dRR‑threshold ectopics); NOOP deliberately keeps it RMSSD‑only and documents the substitution.
Why the divergence, in one line
Both projects are clean‑room rewrites from the same published literature (and both refuse to reproduce WHOOP’s private scores), but they split the work along different lines: edge leans into PSG‑validated, frequency‑rich sleep/recovery (openstrap_analytics pins, DREAMT/kAlgoVersion changelog, robust‑z fallback discipline, Lomb–Scargle, ACWR load, Uth VO₂max) while NOOP leans into a transparent, scipy‑free, body‑anthropometry‑based model (Nes HUNT inversion for Fitness Age, percentile/HR‑flatness stagers, RMSSD‑only HRV, one 0–100 score axis). The numbers will usually track in direction with each other (both also track WHOOP in direction) but they are not produced by the same formulas, and they are not expected to match number‑for‑number.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Hi, I got a Whoop 5.0 a few days ago and was using it with NOOP, but since this app now has 5.0 support I wanted to check it out as well. I was wondering, are the metrics (eg. sleep score, recovery/strain, body age, ...) are computed with the same algorithms as in NOOP, or are there any major differences?
Edit: here's what AI told me after pointing it at both projects, but it would be nice to get a confirmation from a more knowledgeable human:
Slop response
Short answer
No — they are not the same algorithms. Both projects start from the same open, published primitives (Cole–Kripke, Walch DoG‑HR, van Hees z‑angle immobility, Task‑Force RMSSD/SDNN, Malik ectopic rejection, Karvonen %HRR / Edwards / Banister TRIMP, Tanaka HRmax, robust‑z personal baselines anchored to ≈58 %) and both explicitly say “not a reproduction of WHOOP’s proprietary score.” But every finished metric — sleep staging, the sleep score, recovery, strain, and fitness/body age — is put together differently. They share a philosophy and a hardware‑coord much more than a formula.
Important framing for what I read:
/noop): the math lives inandroid/…/com/noop/analytics/*.ktand is described indocs/ANALYTICS.md. The Kotlin files self‑identify as “faithful Kotlin port ofStrandAnalytics/…swift, verified byte‑for‑byte” — so the Android numbers equal the Swift numbers. Everything below about NOOP is from the Kotlin sources, cross‑checked againstANALYTICS.md.edge(/edge): the actual metric math is in a separate sibling repo,OpenStrap/analytics, pinned inpubspec.yaml(SHAebe45da…) and not vendored in this directory. So I derived the edge math from thekAlgoVersionchangelog inedge/lib/compute/derivation_engine.dart(which narrates every algorithm change) plus the orchestration inedge/lib/compute/crossday_pipeline.dartand the imports it makes frompackage:openstrap_analytics/onehz.dart.Metric‑by‑metric
Sleep staging
edgeSleepStager/SleepStagerV2: gravity‑stillness + HR‑confirm, te‑Lindert Cole–Kripke cross‑check (SleepStager.kt:1445, weights[106,54,58,76,230,74,67])AdvancedSleepStager(changelog v24)SleepStagerV2uses an 11‑min HR‑flatness‑percentile deep gate, validated on a 44‑subject LOSO benchmarkcardioStager: weighted robust‑z over **Rk = meanANALYTICS.md,SleepStager.kt)_windowRemFeatures, even had a hot‑path ANR bug, v45)→ Same collection of raw inputs (Cole–Kripke + Walch DoG‑HR), but very different classifiers: NOOP stays in the percentile/HR‑flatness family and drops frequency‑domain; edge adds Lomb–Scargle LF/HF and validated against PSG with different feature choices.
Sleep score
sleep_performance): a 4‑component composite — duration‑vs‑need 0.50, efficiency 0.20, restorative share (deep+REM)/asleep 0.20, consistency 0.10.edge(crossday_pipeline.dart:271):ana.sleepPerformance(lastTstSec, needSec)→ a single‑term TST‑to‑need ratio. Need itself comes fromana.sleepNeed= baseline‑need + sleep debt + day‑strain bonus + nap credit, clamped to 6–11 h. The “coach” outputs (bedtime, cycle wake, target need) live separately in the Sleep Coach, not in a score.→ Major difference: NOOP blends four axes; edge’s headline sleep number is TST‑vs‑need only.
Recovery
RecoveryScorer.kt): documented fixed weights — HRV 0.55, RHR 0.20, sleep 0.15, resp 0.05, skin‑temp 0.05 (symmetric−|dev|penalty); z =(v−μ)/(1.253·MAD); logistic squash100/(1+exp(−1.6·(z+0.20))). Min 4 valid nights to seed.edge(readinessComposite,readiness_lnrmssd): robust z (median + MAD) with a mean/SD z fallback when MAD == 0 on quantized baselines (v43/v46);readiness_lnrmssdis z of ln(RMSSD) over a 28‑day, self‑excluding baseline; RHR is gated on a real detected sleep session (v47) and skin‑temp is gated on its own signal; no‑impute discipline is the project’s most‑recurring invariant (AGENTS §3, §4.1/4.2).→ Same idea, but NOOP’s is fully documented (fixed 5‑weight logistic); edge’s exact weights/squash live in the sibling analytics package and lean harder on robust‑z fallbacks and abstention edge cases.
Strain
Identical primitive math (and essentially identical constants):
HRR = HRmax − RHR, Edwards 5‑zone or Banisterx·0.64·e^(b·x)with b = 1.92 (m) / 1.67 (f), log compressionmax·ln(TRIMP+1)/ln(D)with D = 7201 (StrainScorer.kt:71) exactly so Edwards’ 24h zone‑5 ceiling (5×1440 = 7200) maps to max. TanakaHRmax = 208 − 0.7·age.maxStrain = 100, D unchanged (StrainScorer.kt:62,71). Adds a steps/active‑kcal floor for low‑cardio days.edge: keeps two scales — a 0–21 WHOOP‑aligned Day Strain (headline) and a 0–100 Edwards effort (clinical.strain_effort, Karvonen %HRR — changelog v16). Plus a cross‑day EWMA ACWRloadmodel (CTL/ATL/TSB) feeding the Strain Coach (changelog v20,crossday_pipeline.dart).→ TRIMP/log curve is the same family with literally the same denominator; the difference is scale (NOOP merges to 0–100, edge keeps both) and that edge layers an ACWR load model on top. (Edge also auto‑detects workouts with an HR‑onset bypass — Whipp & Wasserman 1972 — for low‑limb‑swing activities, v51; NOOP has its own
AutoWorkoutDetector.)Body age / fitness age (the clearest divergence)
FitnessAgeEngine.kt: Nes 2011 HUNT non‑exercise VO₂max waist‑circumference model (coeffs verbatim:100.27 − 0.296·age + 0.226·PA − 0.369·wc − 0.155·RHRfor men, women variant too); PA‑index reconstructed on‑device from workout frequency/duration/intensity; self‑consistent inversion of the same equation at reference RHR=65 / PA=5 so the waist term cancels — headline fitness age needs only age/sex/RHR/PA. Weekly, ≥4‑of‑7‑night coverage gate, ±5‑year band, SEE 5.7/5.1.edge(crossday_pipeline.dart:315–345): Uth et al. HRmax/RHR ratio VO₂max over baseline RHR, thenana.physiologicalAge()composites VO₂max + RHR + RMSSD + sleep duration + sleep efficiency + daily steps against age‑norms.→ Fully different VO₂max model: NOOP is a non‑exercise HUNT equation inverted against a population reference; edge is a recovery‑based HR‑ratio plus a multi‑signal physiological‑age composite.
HRV cleaning (underlying all of the above)
HrvAnalyzer.kt: gap‑aware RMSSD/pNN50 (no spurious splice across dropped beats).edge: all‑day RMSSD with Malik clip ≤220 ms (v23), Lipponen–Tarvainen signed‑dRR threshold, Poincaré irregular‑beat screen, LF/HF Lomb–Scargle for sleep and for all‑day HRV.→ Edge exposes noticeably more HRV surface (frequency domain + Poincaré + dRR‑threshold ectopics); NOOP deliberately keeps it RMSSD‑only and documents the substitution.
Why the divergence, in one line
Both projects are clean‑room rewrites from the same published literature (and both refuse to reproduce WHOOP’s private scores), but they split the work along different lines: edge leans into PSG‑validated, frequency‑rich sleep/recovery (
openstrap_analyticspins, DREAMT/kAlgoVersionchangelog, robust‑z fallback discipline, Lomb–Scargle, ACWR load, Uth VO₂max) while NOOP leans into a transparent, scipy‑free, body‑anthropometry‑based model (Nes HUNT inversion for Fitness Age, percentile/HR‑flatness stagers, RMSSD‑only HRV, one 0–100 score axis). The numbers will usually track in direction with each other (both also track WHOOP in direction) but they are not produced by the same formulas, and they are not expected to match number‑for‑number.All reactions