Skip to content

feat: preserve original device source identity during import#146

Open
tobigs wants to merge 2 commits intoashtom:mainfrom
tobigs:feature/preserve-device-source-identity
Open

feat: preserve original device source identity during import#146
tobigs wants to merge 2 commits intoashtom:mainfrom
tobigs:feature/preserve-device-source-identity

Conversation

@tobigs
Copy link
Copy Markdown

@tobigs tobigs commented Apr 9, 2026

Problem

When importing Apple Health data via export.xml, all records are attributed to the import app itself. The original device source identity (e.g., Apple Watch, iPhone) is lost, making it impossible to distinguish which device originally recorded the data.

Solution

Parse the <Device> string from the Apple Health export XML to extract hardware model, software version, and device name. Attach the original HKDevice to each imported HKSample, preserving the source identity.

What's included

  • DeviceStringParser — parses the <<DeviceName>:...> format into HKDevice properties (hardware model, software version, name)
  • Importer update — uses the parsed device info when saving samples to HealthKit
  • Full test coverage for DeviceStringParser
  • SwiftLint build phase fix — makes linting optional so the project builds in environments without SwiftLint installed

Tobias added 2 commits April 7, 2026 11:34
SwiftLint is not always available in all development environments.
Gracefully skip linting with a warning instead of failing the build.
Parse the device string from Apple Health export XML to extract
hardware model, software version, and device name. This preserves
the original data source identity when importing records into
HealthKit, rather than attributing everything to the import app.

Adds DeviceStringParser with full test coverage.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Enhances Apple Health export.xml import so imported HealthKit samples retain original device identity (e.g., iPhone vs Apple Watch) by parsing the export’s serialized <Device> string and attaching an HKDevice to saved samples.

Changes:

  • Add DeviceStringParser to extract HKDevice-relevant fields from Apple Health export device strings.
  • Update Importer to attach an HKDevice and additional metadata when saving imported samples (including workouts).
  • Add comprehensive tests for DeviceStringParser and make SwiftLint build phase optional when SwiftLint isn’t installed.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
HKImportTests/DeviceStringParserTests.swift Adds unit tests covering realistic, partial, and invalid device strings.
HKImport/Importer.swift Captures source/device attributes from XML and attaches an HKDevice + metadata when creating samples.
HKImport/DeviceStringParser.swift Implements parsing for serialized HKDevice strings from Apple Health exports.
HKImport.xcodeproj/project.pbxproj Adds the new parser file to build sources and makes SwiftLint step non-blocking if missing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +233 to +242
let device = HKDevice(
name: item.sourceName,
manufacturer: parsed?.manufacturer,
model: parsed?.model,
hardwareVersion: parsed?.hardwareVersion,
firmwareVersion: nil,
softwareVersion: parsed?.softwareVersion,
localIdentifier: nil,
udiDeviceIdentifier: nil
)
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

HKDevice is being created with name: item.sourceName even though the export’s <Device> string includes a name: field (and the PR goal is to preserve the original device identity). This will typically attribute the device name to the original source/app rather than the actual hardware (e.g., Apple Watch vs iPhone). Consider using the parsed device name (e.g., parsed?.name) as the primary HKDevice name (fallback to sourceName only when the device string has no name), and avoid constructing a device at all (pass device: nil) when there’s no parsed device info.

Suggested change
let device = HKDevice(
name: item.sourceName,
manufacturer: parsed?.manufacturer,
model: parsed?.model,
hardwareVersion: parsed?.hardwareVersion,
firmwareVersion: nil,
softwareVersion: parsed?.softwareVersion,
localIdentifier: nil,
udiDeviceIdentifier: nil
)
let device: HKDevice? = parsed.map { parsed in
HKDevice(
name: parsed.name ?? item.sourceName,
manufacturer: parsed.manufacturer,
model: parsed.model,
hardwareVersion: parsed.hardwareVersion,
firmwareVersion: nil,
softwareVersion: parsed.softwareVersion,
localIdentifier: nil,
udiDeviceIdentifier: nil
)
}

Copilot uses AI. Check for mistakes.
Comment on lines +249 to +251
if let deviceString = item.deviceString {
metadata["HKImportOriginalDeviceString"] = deviceString
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

Saving the raw deviceString into HealthKit metadata can persist fields like UDIDeviceIdentifier (present in real exports). That can be sensitive/identifying data and isn’t necessary to achieve device attribution. Consider either not storing the full device string at all, or sanitizing it (e.g., strip UDID/localIdentifier/creation date) and/or store only the parsed, non-identifying components you need.

Suggested change
if let deviceString = item.deviceString {
metadata["HKImportOriginalDeviceString"] = deviceString
}
if let manufacturer = parsed?.manufacturer {
metadata["HKImportOriginalDeviceManufacturer"] = manufacturer
}
if let model = parsed?.model {
metadata["HKImportOriginalDeviceModel"] = model
}
if let hardwareVersion = parsed?.hardwareVersion {
metadata["HKImportOriginalDeviceHardwareVersion"] = hardwareVersion
}
if let softwareVersion = parsed?.softwareVersion {
metadata["HKImportOriginalDeviceSoftwareVersion"] = softwareVersion
}

Copilot uses AI. Check for mistakes.
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.

2 participants