feat(tools): import an Apple Health export into the Pulse HEALTH tab - #1754
Open
elhoim wants to merge 1 commit into
Open
feat(tools): import an Apple Health export into the Pulse HEALTH tab#1754elhoim wants to merge 1 commit into
elhoim wants to merge 1 commit into
Conversation
Adds AppleHealthImport.ts, which turns the Health app's 'Export All Health Data' backup into a generated APPLE_HEALTH.md that the existing health surface reads. There was no path for this before: healthsync/apple.ts expects a live-sync JSON file written by an iPhone Shortcut into iCloud Drive, not the export.zip the Health app produces, and no parser for export.xml existed anywhere in the tree. Two things the export makes non-obvious, both handled: Health stores every device's view of the same day side by side. A real export carried StepCount from six sources and BasalEnergyBurned from four, so summing samples double- and triple-counted: steps came out 62% high and basal energy at roughly twice a plausible BMR. Samples are now bucketed per source and collapsed per day, picking the device with the largest total for sums and the most samples for averages. The winning device and how many days it won are reported in the output. The export is written in the phone's locale, so a US export emits mi and lb where a metric one emits km and kg. The record's own unit attribute is now read and converted, and a sample in a unit that can be neither aliased nor converted is dropped and reported rather than printed under the wrong label. export.xml is routinely hundreds of megabytes, so it is streamed out of the zip via unzip -p and matched per record, carrying the trailing fragment across chunk boundaries. Peak memory stays flat regardless of input size. Writes only its own file; hand-authored METRICS.md and FITNESS.md are never read or touched.
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.
Why
There is no way to get an Apple Health export into LifeOS today.
healthsync/apple.tsreads a JSON file written by an iPhone Shortcut into iCloud Drive at a hardcoded macOS path; the Health app's Export All Health Data producesexport.zipwrappingexport.xml, which nothing in the tree parses.What
One new file,
install/LIFEOS/TOOLS/AppleHealthImport.ts:It streams the export, folds records into daily buckets across 18 HealthKit types, and writes a generated
APPLE_HEALTH.mdinto the health directory. It writes only that file and regenerates it end to end, so hand-authoredMETRICS.md/FITNESS.mdare never read or touched.The headline numbers go in the
##headings on purpose.handleLifeHealthreturns section bodies, but the health page'sFileCardrenders headings only, capped at 8. A heading of "Steps" would display the word "Steps" with the value invisible. Tables still sit under each heading for reading the file directly and for the DA.Two things real exports made non-obvious
Multi-source double counting. Health stores every device's view of the same day side by side. The export I tested against carried
StepCountfrom six sources andBasalEnergyBurnedfrom four — phones, a ring, and app-level writers. Summing samples inflated steps by 62% and put basal energy at roughly twice any plausible BMR. Samples are now bucketed per source and collapsed per day: largest total wins forsum, most samples wins foravg, globally-latest wins forlast. The winning device and the days it won are reported, so a wrong pick is visible rather than silent. Apple's own Health app solves this with a per-source priority list; this needs no configuration.Locale-dependent units. The export is written in the phone's locale, so a US export emits
miandlbwhere a metric one emitskmandkg. The record's ownunitattribute is now read and converted. A unit that can be neither aliased nor converted causes the sample to be dropped and reported rather than printed under the wrong label. Noteunit="count"is a bare tally whose meaning is per-metric (steps vs flights), so it resolves against the metric rather than through a global alias — getting that wrong silently dropped an entire metric in testing.Verification
Tested against a real 12-year export: 1,140,469 records, 358MB of XML in a 20MB zip, 773,282 samples used across 10 metrics, in 17s.
/api/life/healthreturned the generated file with all 11 sections; a DOM read of the running dashboard showed the card with its bullets, confirmed against a captured screenshot..zip(viaunzip -p) and raw.xmlinputs parse; a missingunzipgives an actionable error rather than a stack trace.Not run: a fresh-system install verification (step 3 of the contributing process).
Follow-up, not in this PR
The richer path is emitting the
healthsyncDayFileshape intoUSER/HEALTH/DATAso Apple data composes with the oura/eightsleep/function adapters. That needs a consumer first — nothing in Pulse currently reads that directory, and the health page would need timeseries rendering to show it.