Skip to content

Migrate spezi health kit#3

Open
max-rosenblattl wants to merge 13 commits intofeature/add-healthyllmfrom
migrate-spezi-health-kit
Open

Migrate spezi health kit#3
max-rosenblattl wants to merge 13 commits intofeature/add-healthyllmfrom
migrate-spezi-health-kit

Conversation

@max-rosenblattl
Copy link
Collaborator

@max-rosenblattl max-rosenblattl commented Aug 24, 2025

Migrate HealthKit to SpeziHealthKit

♻️ Current situation & Problem

The current implementation doesn't take advantage of SpeziHealthKit.

⚙️ Release Notes

This PR migrates from HealthKit to SpeziHealthKit, improving readability and simplifying the access of health data.

📚 Documentation

  • This PR mainly refactors the existing implementation
  • The added extension of HealthQuery should optimally be merged into the SpeziHealthKit framework.

✅ Testing

Tested on iPhone Air.

IMG_0126 IMG_0127 IMG_0129
Workout Info 1 Workout Info 2 Health Data
IMG_0136 IMG_0137 IMG_0138
Time Range Agg. 1 Time Range Agg. 2 Time Range Agg. 3

Code of Conduct & Contributing Guidelines

By creating and submitting this pull request, you agree to follow our Code of Conduct and Contributing Guidelines:

CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"HealthyLLM/Supporting Files/Preview Content\"";
DEVELOPMENT_TEAM = CQRZ4E7K9U;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revert the development team and bundle identifier after addressing all other PR comments

@codecov
Copy link

codecov bot commented Aug 24, 2025

Codecov Report

❌ Patch coverage is 5.00000% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 4.29%. Comparing base (a79c50c) to head (0b9ec99).

Files with missing lines Patch % Lines
...lthyLLM/HealthyLLM/Fetcher/HealthDataFetcher.swift 0.00% 13 Missing ⚠️
HealthyLLM/HealthyLLM/HealthDataInterpreter.swift 0.00% 4 Missing ⚠️
HealthyLLM/HealthyLLMAppDelegate.swift 33.34% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                    Coverage Diff                    @@
##           feature/add-healthyllm      #3      +/-   ##
=========================================================
- Coverage                    4.43%   4.29%   -0.13%     
=========================================================
  Files                          70      69       -1     
  Lines                         633     630       -3     
=========================================================
- Hits                           28      27       -1     
+ Misses                        605     603       -2     
Files with missing lines Coverage Δ
HealthyLLM/HealthyLLM/HealthyLLMChatView.swift 0.00% <ø> (ø)
HealthyLLM/Onboarding/HealthKitPermissions.swift 16.67% <ø> (ø)
HealthyLLMStudy/Study/StudyChatView.swift 0.00% <ø> (ø)
HealthyLLM/HealthyLLMAppDelegate.swift 66.67% <33.34%> (-33.33%) ⬇️
HealthyLLM/HealthyLLM/HealthDataInterpreter.swift 25.00% <0.00%> (ø)
...lthyLLM/HealthyLLM/Fetcher/HealthDataFetcher.swift 6.25% <0.00%> (-13.75%) ⬇️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a79c50c...0b9ec99. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +167 to +171

// One-off query for aggregating health data
// TODO: Create Pull Request for SpeziHealthKit
extension HealthKit {
public func statisticsQuery<Sample>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukaskollmer do you know if SpeziHealthKit already provides the functionality for one-off statistics queries in some way? If not, and you think it would be useful to others as well, I'm happy to create a PR!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think so; iirc i was planning to create a nice API around HKStatistics (eg so that you dont have to deal with the optional HKQuantity objects that will never be nil) but i didn't get around to it. we do have the @HealthKitStatisticsQuery for SwiftUI; would make sense to add a non-SwiftUI counterpart for one-off queries. we might even be able to reuse the code internally

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the @HealthKitStatisticsQuery API already contains an initial attempt at making this type safe; we have different init overloads to prevent the caller from passing in invalid aggregation option combinations

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukaskollmer and I had a small discussion on Monday about a very simple approach to make the SwiftUI property wrappers also available outside of the SwiftUI hierarchy and usable inside modules, we might want to tackle this in the future, IMO that would solve the issue here as well. But we might want merge this first and then iterate on it later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, @lukaskollmer let me know if you need some support, but for now I agree with merging first and iterating later 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@max-rosenblattl Would be great if you could create an issue in SpeziHealthKit that tracks this issue. I also saw that @phnagy was looking into some issues there; maybe that might be a great first issue with a real need and direct implementation pathway. (CC @lukaskollmer)

Copy link
Collaborator Author

@max-rosenblattl max-rosenblattl Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukaskollmer I phrased the issue in a way that suggests implementing your discussed approach; if you have any, it would be great to add some specific details from your discussion that could ease the implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing; thank you @max-rosenblattl!

Comment on lines -67 to -72
try? await self.fetchSample(
for: identifier,
unit: unit,
startDate: endDate,
endDate: .now
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, we’re aggregating time-series data into a single value (e.g., average heart rate over the last month). It might be more meaningful to use a different aggregation approach; for example, retrieving daily averages over the last month instead of just one number. This would also align better with future EmbedHealth integration, even if that’s a larger effort. Let me know what you think @PSchmiedmayer @LeonNissen @lukaskollmer

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree that a higher granularity would be desirable but wasn't the issue here that having too many samples could exceed the context window? (i might be confusing this with something else).

iirc another idea of mine at the time was that we could use different granularities depending on how far back the data was collected (eg weekly heart rate for > a year ago, daily average for > last week, and hourly averages for the past 7 days)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the main issue was the growing context window; that's why we constrained it to a few samples.

Agree with @lukaskollmer here for this approach. I think 7 days for a week, weekly samples for a month, or monthly samples for a year would make sense and don't dramatically exceed the context window. Would be good to verify this with some on-device testing @max-rosenblattl before we merge this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the insights! I think it makes a lot of sense, happy to tackle that.

Regarding testing on device: I will be able to do so in the very near future; until then if anyone has some spare minutes, I would appreciate it!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick update: finally got my new iPhone and could test it on device (results in PR description above)! Also adapted the statistics query to handle the discussed time-range based aggregation intervals. Just saw they were integrated in here too StanfordSpezi/SpeziHealthKit#65 , quite cool looking forward to the merge 🚀

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on all of this @max-rosenblattl 🚀

Copy link
Member

@PSchmiedmayer PSchmiedmayer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this and thank you for your initial comments @lukaskollmer 👍

Happy to see a first version merged once the CI is passing; we will have a decent amount of iterations here when we will embed the time series LLM components but that is a next step after that.

Comment on lines -67 to -72
try? await self.fetchSample(
for: identifier,
unit: unit,
startDate: endDate,
endDate: .now
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the main issue was the growing context window; that's why we constrained it to a few samples.

Agree with @lukaskollmer here for this approach. I think 7 days for a week, weekly samples for a month, or monthly samples for a year would make sense and don't dramatically exceed the context window. Would be good to verify this with some on-device testing @max-rosenblattl before we merge this.

Comment on lines +167 to +171

// One-off query for aggregating health data
// TODO: Create Pull Request for SpeziHealthKit
extension HealthKit {
public func statisticsQuery<Sample>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukaskollmer and I had a small discussion on Monday about a very simple approach to make the SwiftUI property wrappers also available outside of the SwiftUI hierarchy and usable inside modules, we might want to tackle this in the future, IMO that would solve the issue here as well. But we might want merge this first and then iterate on it later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Comments