Skip to content

file upload rework; HealthKit stats (WIP) - #197

Draft
lukaskollmer wants to merge 14 commits into
mainfrom
lukas/data-spec
Draft

file upload rework; HealthKit stats (WIP)#197
lukaskollmer wants to merge 14 commits into
mainfrom
lukas/data-spec

Conversation

@lukaskollmer

@lukaskollmer lukaskollmer commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

♻️ Current situation & Problem

see #180, #190, #191

⚙️ Release Notes

  • reworked file uploading into a SwiftData-db-backed thing (see the issues linked above)
  • added explicit canonical units to all custom quantity types w/in MHC, and made the unitless QuantitySample.value property private (to force callers to explicitly specify a unit each time)
  • added a (still WIP, but actually functional apart from the fact that it currently is missing almost all of the sample types) HealthKit statistics calculator

📚 Documentation

n/a

✅ Testing

not yet

Code of Conduct & Contributing Guidelines

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

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8edad487-70d2-4e6f-9ca5-ab1cd2ec1a15

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@PSchmiedmayer PSchmiedmayer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Happy to see merged once the things we discussed are incorporated + more tests. Good to drive the statistics based on the Firebase setup in the short term + ensure that we have the structure alignment document in place 🚀

)
managedFileUpload.scheduleForUpload(results.compactMap { $0 }, category: .historicalHealthUpload)
let exportedFileUrls = results.compactMap { $0 }
Task { [managedFileUpload, logger] in

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we keep track of the task and/or have some checks to not run them in parallel?

for descriptor in descriptors {
taskGroup.addTask {
do {
try await self._run(descriptor, lastNMonths: 1, accountDoc: accountDoc)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should probably from enrollment on.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

the current local-HealthKit-query-based implementation of the dashboard doesn't take the enrollment date into acount and instead simply fetches e.g. the last 2 weeks, without capping this based on the enrollment date.

IMO we should still compute a full year of stats; the app can then decide how much (and which sub-range) of that it wants to query...

extension HealthKitStatsCalculator {
fileprivate struct MonthlyStatsDocument: Codable {
// A single sum or min/max/avg entry in the single-month stats document
struct StatEntry: Codable {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

TODO to sync with the main outline of how we want to define this & discuss with @pauljohanneskraft @eldcn @PaulGoldschmidt 👍

Comment on lines +234 to +235
// fileprivate static let statsIntervalHourly = Self(stringValue: "hourly")
// fileprivate static let statsIntervalDaily = Self(stringValue: "daily")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Remove or encode?

// (Moved aside, not deleted, so that the previously-pending uploads at least remain recoverable.)
for filename in ["db.sqlite", "db.sqlite-shm", "db.sqlite-wal"] {
let url = Self.databaseDirectory.appending(component: filename, directoryHint: .notDirectory)
let brokenUrl = Self.databaseDirectory.appending(component: "broken-\(filename)", directoryHint: .notDirectory)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Probably would override? So might be good to append a date or something there?

let validNames = Set(uploads.map { $0.id.uuidString })
for url in (try? fileManager.contents(of: Self.stagingDirectory)) ?? [] where !validNames.contains(url.lastPathComponent) {
logger.notice("Deleting orphaned staging file at \(url.path)")
try? fileManager.removeItem(at: url)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should probably somehow keep track how often that happens and be very sure that that's only when there was un very unfortunate things that it crashed between db removal and file removal.

let container = try decoder.container(keyedBy: CodingKey.self)
version = try container.decode(Int.self, forKey: .version)
metric = try container.decode(String.self, forKey: .metric)
let hmmm: [StatsTimeInterval: [String: [StatEntry]]] = try StatsTimeInterval.allCases.reduce(into: [:]) { result, timeInterval in

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hmmmmm 😄

let now = Date()
let startDate = cal.date(byAdding: .month, value: -numMonths, to: cal.startOfMonth(for: now))!
let months: some Sequence<Range<Date>> = cal
.dates(byAdding: .month, value: 1, startingAt: startDate, in: startDate..<cal.startOfNextMonth(for: now))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

we'll have to ensure that this sequence will properly contain the first occurrence (i.e., the startDate)

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

Labels

enhancement New feature or request

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants