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
Yesterday I had not connected yet and had no HR data during the day. I did however two rides. with data.
I exported the go data from strava and imported it. It currently is parsed and states to have imported 1885 heart rate signals.
It however does not feed the HR data and does not calculate an effort score.
What you expected instead
Partial HR data during the day and an effort score.
Steps to reproduce
1.export from strava
2.import to noop
3.notification mentions data is imported
4. Workout is added with data about HR
5. No effort score
6. No HR data
Strap log
Before you submit
I attached my exported NOOP .zip (not a screenshot of the Live screen). If my bug is a clearly reproducible UI issue I described exactly how to see it instead.
I attached or pasted a strap log above — or my bug is a clearly reproducible UI issue that doesn't need one (e.g. a typo, a button that does nothing) and I've described exactly how to see it.
I checked the Troubleshooting guide — especially "history syncs but no data" (most often the strap isn't banking history to flash; charge it to 100% and reconnect).
I checked the FAQ and — if I'm on a WHOOP 5.0 / MG — the 5.0/MG status page, and this isn't an already-known limit (e.g. SpO₂, or recovery/sleep on the 5/MG).
I've left out anything personal (strap serial, account email, raw export).
Dug through the import → scoring path to see exactly why an imported ride with HR produces neither day HR nor an Effort score. Sharing the full picture, because a fix here reverses a deliberate design decision and touches the scoring pipeline — probably a maintainer call on direction.
Why it happens
The importer keeps only aggregates.ActivityFileImporter parses the per-sample HR but reduces it to avgHr / maxHr / hrSampleCount on the WorkoutRow; the per-sample HR time series is discarded (the ImportedWorkout shape has no sample field). The row is stored with strain = null — and this is intentional:
WorkoutRow(… strain = null, // never a fabricated cardiovascular strain) (Android ActivityFileImporter)
// … upsert one workout row under the "activity-file" source, refresh. No HR Effort is touched. (macOS DataSourcesView)
Day "Effort" is computed from the day HR stream, not from workout strains. In AnalyticsEngine.analyzeDay, Strain/Effort is "cardiovascular load over the full CALENDAR day", derived from dayHr, which the engine reads as hrSamples(deviceId: owner, …) — the resolved day owner's HR. Imported activity-file HR is not in any HR-sample stream at all, and even if it were (under the activity-file source), the day-Effort read wouldn't see it unless that source reaches dayHr.
The existing under-scored-workout rescore doesn't apply.rescoreManualWorkouts (the #137-tagged path) is gated to source == "manual" and reads HR under the engine's own deviceId, so activity-file rows are skipped and their HR (under a different source) wouldn't be read anyway.
So the imported ride is stored honestly (avg/max HR, distance, route) but is deliberately kept out of HR/Effort.
What a fix would require
To give the ride a day Effort + partial day HR, three things, on both platforms (byte-parity):
(A) Persist the real per-sample HR under the activity-file source (thread the samples through the importer → StreamStore.insert / repo.insertHr).
(B) Reach the day-Effort read — either let day-owner resolution treat a strap-less day as owned by activity-file, or union activity-file HR into the dayHr read the way workouts already union apple-health (realWorkouts = workouts(deviceId) + workouts("apple-health")). The union is the less-invasive option (doesn't touch the "one source per day" invariant).
(C, optional) Also score the workout row's own strain from the persisted samples (generalize rescoreManualWorkouts to activity-file, reading HR under that source).
The judgement call
This reverses the explicit "No HR Effort is touched" decision. The nuance in its favour: the GPX/FIT HR is real measured data, not a fabricated estimate — so surfacing it as day HR + Effort isn't the "fabricated strain" the current guard avoids. But it's a scoring-pipeline change and would need on-device validation (import a GPX with HR on a strap-less day, confirm the Effort ring lights and the day HR chart fills).
Happy to put up a PR for whichever direction you prefer — I'd lean toward (A) + the (B) dayHr-union, as the smallest change that lights Effort without reworking owner resolution. Would just want your OK on reversing the "No HR Effort is touched" stance first.
Thanks @digitalerdude for the thorough feedback. Really appreciated.
I understand the current behaviour and when wearing the strap at all times, this is not even necessary. Given I did not have an all day reading, I hoped to fix it this way. I therefore believe that the A+B (union) makes for a good suggestion.
Activity
digitalerdude commentedon Jul 10, 2026
Dug through the import → scoring path to see exactly why an imported ride with HR produces neither day HR nor an Effort score. Sharing the full picture, because a fix here reverses a deliberate design decision and touches the scoring pipeline — probably a maintainer call on direction.
Why it happens
The importer keeps only aggregates.
ActivityFileImporterparses the per-sample HR but reduces it toavgHr/maxHr/hrSampleCounton theWorkoutRow; the per-sample HR time series is discarded (theImportedWorkoutshape has no sample field). The row is stored withstrain = null— and this is intentional:WorkoutRow(… strain = null, // never a fabricated cardiovascular strain)(AndroidActivityFileImporter)// … upsert one workout row under the "activity-file" source, refresh. No HR Effort is touched.(macOSDataSourcesView)Day "Effort" is computed from the day HR stream, not from workout strains. In
AnalyticsEngine.analyzeDay, Strain/Effort is "cardiovascular load over the full CALENDAR day", derived fromdayHr, which the engine reads ashrSamples(deviceId: owner, …)— the resolved day owner's HR. Imported activity-file HR is not in any HR-sample stream at all, and even if it were (under theactivity-filesource), the day-Effort read wouldn't see it unless that source reachesdayHr.The existing under-scored-workout rescore doesn't apply.
rescoreManualWorkouts(the#137-tagged path) is gated tosource == "manual"and reads HR under the engine's owndeviceId, soactivity-filerows are skipped and their HR (under a different source) wouldn't be read anyway.So the imported ride is stored honestly (avg/max HR, distance, route) but is deliberately kept out of HR/Effort.
What a fix would require
To give the ride a day Effort + partial day HR, three things, on both platforms (byte-parity):
activity-filesource (thread the samples through the importer →StreamStore.insert/repo.insertHr).activity-file, or unionactivity-fileHR into thedayHrread the way workouts already unionapple-health(realWorkouts = workouts(deviceId) + workouts("apple-health")). The union is the less-invasive option (doesn't touch the "one source per day" invariant).strainfrom the persisted samples (generalizerescoreManualWorkoutstoactivity-file, reading HR under that source).The judgement call
This reverses the explicit "No HR Effort is touched" decision. The nuance in its favour: the GPX/FIT HR is real measured data, not a fabricated estimate — so surfacing it as day HR + Effort isn't the "fabricated strain" the current guard avoids. But it's a scoring-pipeline change and would need on-device validation (import a GPX with HR on a strap-less day, confirm the Effort ring lights and the day HR chart fills).
Happy to put up a PR for whichever direction you prefer — I'd lean toward (A) + the (B) dayHr-union, as the smallest change that lights Effort without reworking owner resolution. Would just want your OK on reversing the "No HR Effort is touched" stance first.
bartmuskala commentedon Jul 10, 2026
Thanks @digitalerdude for the thorough feedback. Really appreciated.
I understand the current behaviour and when wearing the strap at all times, this is not even necessary. Given I did not have an all day reading, I hoped to fix it this way. I therefore believe that the A+B (union) makes for a good suggestion.
In terms of priorities I can live without :).
test(ryanbr#137): pin partial-day Effort — imported ride island score…
import: light day Effort from an imported ride's HR on a strap-less d…
Activity import: don't write a single workout's steps as the DAY's to…
import: light day Effort from an imported ride's HR on a strap-less d…
import: light day Effort from an imported ride's HR on a strap-less d…