Repository navigation
fix(health): count only unique asleep intervals in daily summaries - #40
Merged
Merged
Conversation
Filter sleep analysis to recognised asleep categories and merge overlapping intervals within each start-date day bucket. Cover XML parsing, category raw values, invalid intervals and CSV/JSON export with regression tests. Co-authored-by: OpenAI Codex <noreply@openai.com>
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.
Daily Health export summaries now count only recognised asleep categories and merge overlapping intervals within each start-date day bucket. Awake, in-bed, unknown categories and invalid intervals no longer inflate the duration, and CSV/JSON exports carry the corrected result.
Completes the sleep-duration correctness slice. Part of #6. Closes #38. The parent epic remains open.
Verification
mise run lintfailed with 503 violations, including 11 errors. An untouched archive of main atbe32d20reproduced the same 503 violations and 11 errors with SwiftLint 0.62.2. Existing failures are tracked in Restore the repository-wide SwiftLint gate #39.SCRIPT_INPUT_FILE_COUNT=2 SCRIPT_INPUT_FILE_0="$PWD/Sources/BetterFit/Models/DailyHealthSummary.swift" SCRIPT_INPUT_FILE_1="$PWD/Tests/BetterFitTests/SleepSummaryTests.swift" mise exec -- swiftlint lint --strict --config .swiftlint.yml --use-script-input-files.mise run buildpassed.mise run testpassed with 23 pipeline checks, 151 XCTest tests, and 17 Swift Testing tests in two suites. Two new parameterised tests cover 21 category inputs.mise run buildandmise run teston the PR head, including the XML-to-CSV/JSON regression test. CI proof.mise run ios:build:cipassed withBUILD SUCCEEDED. iOS build output.mise run ios:coveragepassed, including the iOS UI test suite and coverage report. UI test output.mise run ios:e2epassed. All ten PR checks are green, including build, unit tests, UI coverage, mobile e2e and security checks. Mobile e2e output.git diff --checkpassed.Proof
Unit-test transcript capture succeeded. No UI changed, so no simulator screenshot or UI-test capture was attempted. Scratch logs remain under
.tmp/proof/and are not committed.The mixed XML fixture contains a nine-hour in-bed interval, an awake interval, three asleep stages and an overlapping record from a second device. It contains four hours asleep. Before the fix, the regression test exposed a 15-hour total.
After the fix, the parser-to-summary-to-export test asserts
sleepDuration == 14400, the CSV row2026-04-19T00:00:00Z,,,,,,,,14400, and JSON"sleepDuration" : 14400, then decodes the JSON back to the same summaries.The following seven new tests passed. Category tests include legacy asleep, unspecified, core, deep, REM and numeric HealthKit values, plus rejected awake, in-bed, unknown and non-finite values.
Changed-file lint also passed.
The iOS CI capture also confirms the app still builds and its existing UI suite passes.
Review notes
Mobile e2e completed successfully with three tests passing first time and one passing on retry. The initial failure was
app did not come to foregroundine2e/tests/helpers.ts:60, before the active-workout test reached its behaviour assertions. The successful retry and original error are both visible in the linked job output.The existing allocation by start date is preserved, including for intervals crossing midnight. Unioning asleep intervals does not prioritise devices or subtract conflicting awake samples. Cross-midnight splitting, source prioritisation, live HealthKit ingestion and UI remain separate slices. Public model fields and serialisation shapes are unchanged.
Category meanings were checked against Apple's sleep-analysis documentation, and numeric values against the installed HealthKit SDK header. The parser and aggregation remain Foundation only.