analytics(healthkit): wire historical HealthKit reference into reports (KC-51 ph4e)
Completes 'available HealthKit trends' for weekly/monthly reports. Adds HealthKitManager.dailyReference (3 parallel bucketed HKStatisticsCollectionQuery for steps/active-calories/resting-HR over ~13 months), AnalyticsService. refreshHealthReference to cache and merge it into DaySamples, and avgSteps/ avgActiveCalories/avgRestingHR on PeriodSummary (nil when absent — honest). Surfaced in AnalyticsView's weekly/monthly cards. Verified standalone (swiftc, 8/8): aggregation correct when present, nil when absent, workout metrics unaffected, reports stay deterministic. Tests added to AnalyticsEngineTests. This completes all analytics logic — remaining work is the first Xcode build (device, user-run) to catch any SwiftUI compile issues. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -170,6 +170,30 @@ final class AnalyticsEngineTests: XCTestCase {
|
||||
XCTAssertEqual(s.consistency, 2.0/3.0, accuracy: 0.0001)
|
||||
}
|
||||
|
||||
// MARK: - HealthKit reference aggregation (partial/missing data honest)
|
||||
|
||||
func testSummarizeAggregatesHealthKitAveragesWhenPresent() {
|
||||
let days = [
|
||||
DaySample(dateKey: "2026-07-06", didWorkout: true, scheduled: true, sessions: 1, sets: 20, volume: 1000,
|
||||
steps: 8000, activeCalories: 500, restingHeartRate: 55),
|
||||
DaySample(dateKey: "2026-07-07", didWorkout: true, scheduled: true, sessions: 1, sets: 18, volume: 900,
|
||||
steps: 10000, activeCalories: 600, restingHeartRate: 57),
|
||||
DaySample(dateKey: "2026-07-08", didWorkout: false, scheduled: false) // no HK reference
|
||||
]
|
||||
let s = AnalyticsEngine.summarize(days)
|
||||
XCTAssertEqual(s.avgSteps, 9000)
|
||||
XCTAssertEqual(s.avgActiveCalories, 550)
|
||||
XCTAssertEqual(s.avgRestingHR, 56)
|
||||
}
|
||||
|
||||
func testSummarizeHealthKitNilWhenAbsent() {
|
||||
let s = AnalyticsEngine.summarize([DaySample(dateKey: "2026-07-06", didWorkout: true, scheduled: true, sets: 20, volume: 1000)])
|
||||
XCTAssertNil(s.avgSteps)
|
||||
XCTAssertNil(s.avgActiveCalories)
|
||||
XCTAssertNil(s.avgRestingHR)
|
||||
XCTAssertEqual(s.volume, 1000) // workout metrics unaffected
|
||||
}
|
||||
|
||||
// MARK: - Reports (idempotency / determinism)
|
||||
|
||||
func testWeeklyReportDeterministic() {
|
||||
|
||||
Reference in New Issue
Block a user