Repository navigation
fix: round the imported Apple step total on Apple, matching Android - #1540
Merged
Merged
Conversation
…1535 follow-up) Found reviewing #1535. The same Apple Health export produced a different stored step count on each platform: Apple steps: d.steps.map { Int($0) } truncates Android steps = d.steps?.let { Math.round(it).toInt() } rounds A fractional daily total is reachable: the aggregator accumulates per source (`stepsBySource[...] += v`) before taking the winning source's figure, and nothing constrains a source to whole counts. On such a day Apple stored a total one lower than Android for the same import -- stored-data divergence on an imported column. Rounding is the side to converge on, and this is an oversight rather than a policy: every other count and rate converted on the SAME rows already rounds (avgHr, maxHr, walkingHr, restingHr all use `Int($0.rounded())`), and Android rounds throughout. `steps` was the only field on the row that did not. Both conversion sites now route through one `stepsInt` helper. Extracted because the mapping lived inside a file-IO function and had no test seam at all -- the same shape #1535 used for `dailyMetricRow`, so a test exercises the production conversion instead of a copy of it. Swift's `.rounded()` is half-away-from-zero and Kotlin's `Math.round` is half-up. They differ only for negatives, which a step count cannot be, and the test pins that agreement rather than assuming it. No Android change: it already rounds. No backfill -- this corrects future imports, and re-importing an affected export now stores the same value both platforms would. Verified: doc lint clean; app-build for the app-target Swift -- see the PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #1535. Found while reviewing that PR's Apple import mapping — not introduced by it.
The divergence
The same Apple Health export stored a different step count on each platform:
A fractional daily total is reachable rather than theoretical:
AppleHealthAggregatoraccumulates per source (stepsBySource[…] += v) before the winning source's figure is taken, and nothing constrains a source to whole counts. On such a day Apple stored a total one lower than Android from the same file — stored-data divergence on an imported column, which is the class the parity contract exists to catch.Why rounding is the side to converge on
This is an oversight, not a policy. Every other count and rate converted on the same rows already rounds —
avgHr,maxHr,walkingHron theAppleDailyrow andrestingHron theDailyMetricrow all useInt($0.rounded()).stepswas the only field on the row that didn't, and Android rounds throughout. So Apple was inconsistent with itself before it was inconsistent with Android.On the extraction
Both sites now go through one
AppleHealthImport.stepsInt. I extracted it because the conversion lived inside a file-IO function and had no test seam at all — the same shape #1535 used fordailyMetricRow, so the test exercises the production conversion rather than a copy of it.Swift's
.rounded()is half-away-from-zero; Kotlin'sMath.roundis half-up. They differ only for negatives, which a step count cannot be — the test pins that agreement rather than assuming it, since it is the one input class where the two languages' rules could part company.Scope
maxofstepsBySource(Morning recap: dedup on the reported night, not the calendar day (fixes #567 midnight re-fire) #589), both accumulate with+=. Only the final Double→Int step differed.map { Int($0) }now appears nowhere in the Swift tree, so these two were the only ones.Verification
Test Strandat 1,191 tests, 0 failures — 1,188 before, so all three new tests ran rather than being silently skipped.One thing stated plainly: I did not revert-and-rerun to prove the tests fail against the old behaviour, as I have on recent PRs. Here it is arithmetic rather than logic —
Int(10432.6)is 10432 and the test demands 10433 — so a CI cycle would confirm something already certain from the expression.