You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -60,11 +60,11 @@ Single binary HTTP server (`cmd/server/main.go`) that wires together several pac
60
60
```
61
61
`MaybeFireAll` is called from the morning scheduler tick (see `runMorningSmartRetry` in `cmd/server/main.go`). It honours per-rule cadence + eligibility and never bubbles errors — a misbehaving rule cannot block the morning report path. Render returning `""` is treated as "skip with no persist" (idiomatic when the render itself re-evaluates a condition). See `digest.go` and `energy_nudge.go` for production examples.
62
62
63
-
-**`internal/applehealth`** — streaming XML parser for Apple Health export files (`export.xml` or `.zip`). Memory-efficient, maps 100+ HK metric types to internal metric names. Normalizes fraction-based percentage metrics (SpO₂, body fat, etc.) to 0–100 scale during import.
63
+
-**`internal/applehealth`** — streaming XML parser for Apple Health export files (`export.xml` or `.zip`). Memory-efficient, maps 100+ HK metric types to internal metric names. Normalizes fraction-based percentage metrics (SpO₂, body fat, etc.) to 0–100 scale during import. Admin UI and CLI XML imports stage through `storage.ImportSession`: one logical `import_runs` row + one `health_records` row per import, `pgx.CopyFrom` temp staging, coverage metadata, set-based promote, and one cache rebuild after commit. XML snapshot freshness is time-based using `HealthData@exportDate` when available: XML supersedes older live/HAE exact rows, later live/mobile exact rows remain fresher, and a later XML snapshot may supersede them again. Do not reintroduce broad `RemoveAutoExportForRange` cleanup for XML imports.
-**`cmd/import`** — standalone CLI to import Apple Health export files. Flags: `--file`, `--batch`, `--pause`, `--dry-run`. Streams XML to avoid memory overload.
67
+
-**`cmd/import`** — standalone CLI to import Apple Health export files. Flags: `--file`, `--batch`, `--pause`, `--dry-run`; `--pause` is retained for compatibility but staged imports ignore per-batch sleeps. Streams XML to avoid memory overload and uses the same `storage.ImportSession` path as Admin UI.
Copy file name to clipboardExpand all lines: CLAUDE.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -60,11 +60,11 @@ Single binary HTTP server (`cmd/server/main.go`) that wires together several pac
60
60
```
61
61
`MaybeFireAll` is called from the morning scheduler tick (see `runMorningSmartRetry` in `cmd/server/main.go`). It honours per-rule cadence + eligibility and never bubbles errors — a misbehaving rule cannot block the morning report path. Render returning `""` is treated as "skip with no persist" (idiomatic when the render itself re-evaluates a condition). See `digest.go` and `energy_nudge.go` for production examples.
62
62
63
-
-**`internal/applehealth`** — streaming XML parser for Apple Health export files (`export.xml` or `.zip`). Memory-efficient, maps 100+ HK metric types to internal metric names. Normalizes fraction-based percentage metrics (SpO₂, body fat, etc.) to 0–100 scale during import.
63
+
-**`internal/applehealth`** — streaming XML parser for Apple Health export files (`export.xml` or `.zip`). Memory-efficient, maps 100+ HK metric types to internal metric names. Normalizes fraction-based percentage metrics (SpO₂, body fat, etc.) to 0–100 scale during import. Admin UI and CLI XML imports stage through `storage.ImportSession`: one logical `import_runs` row + one `health_records` row per import, `pgx.CopyFrom` temp staging, coverage metadata, set-based promote, and one cache rebuild after commit. XML snapshot freshness is time-based using `HealthData@exportDate` when available: XML supersedes older live/HAE exact rows, later live/mobile exact rows remain fresher, and a later XML snapshot may supersede them again. Do not reintroduce broad `RemoveAutoExportForRange` cleanup for XML imports.
-**`cmd/import`** — standalone CLI to import Apple Health export files. Flags: `--file`, `--batch`, `--pause`, `--dry-run`. Streams XML to avoid memory overload.
67
+
-**`cmd/import`** — standalone CLI to import Apple Health export files. Flags: `--file`, `--batch`, `--pause`, `--dry-run`; `--pause` is retained for compatibility but staged imports ignore per-batch sleeps. Streams XML to avoid memory overload and uses the same `storage.ImportSession` path as Admin UI.
Copy file name to clipboardExpand all lines: README.md
+20-3Lines changed: 20 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -225,7 +225,7 @@ The endpoint stores summary fields only (duration, distance, energy, average / m
225
225
226
226
If `HEALTH_HR_ZONES_BPM` is configured, the ingest path also computes time-in-zone (Z1..Z5) from the per-minute HR samples in the payload and stores five integer columns per workout. Pick zone borders that match your physiology — the Karvonen (HR Reserve) method using observed MaxHR and resting HR is more accurate than the textbook `220 - age` formula.
227
227
228
-
Apple Health XML imports (`cmd/import` and the Admin import UI) also ingest `<Workout>` summaries into the same `workouts` table. XML exports provide nested `WorkoutStatistics` for distance, energy, and aggregate heart-rate fields on some workouts, so imported rows may include `distance_km`, `energy_kcal`, `avg_hr_bpm`, and `max_hr_bpm`. GPX workout routes are deliberately not imported.
228
+
Apple Health XML imports (`cmd/import` and the Admin import UI) also ingest `<Workout>` summaries into the same `workouts` table. XML exports provide nested `WorkoutStatistics` for distance, energy, and aggregate heart-rate fields on some workouts, so imported rows may include `distance_km`, `energy_kcal`, `avg_hr_bpm`, and `max_hr_bpm`. GPX workout routes are deliberately not imported. XML workouts with stable IDs are upserted by `external_id`; synthetic `applexml:` workout IDs are additionally deduplicated by stable workout window fields so corrected XML snapshots do not leave duplicate rows behind.
229
229
230
230
The `/health` endpoint (no suffix) still accepts all metrics unfiltered for backward compatibility.
231
231
@@ -402,11 +402,21 @@ Import a full Apple Health export (from iPhone Settings > Health > Export All He
402
402
403
403
**Via CLI**:
404
404
```bash
405
-
DATABASE_URL=postgres://... go run ./cmd/import --file path/to/export.zip
405
+
DATABASE_URL=postgres://... go run ./cmd/import --file path/to/export.zip --batch 100000
406
406
```
407
407
408
408
The import streams the XML to avoid memory issues with large files. Percentage metrics (SpO2, body fat, walking asymmetry, etc.) are automatically normalized from Apple Health's fraction format (0.96) to percentage scale (96%).
409
409
410
+
Admin UI and CLI imports share the same staged storage path:
411
+
412
+
1. create one logical `import_runs` row and one `health_records` row for the export;
413
+
2. read `HealthData@exportDate` as the snapshot freshness timestamp, falling back to import start only when it is unavailable;
414
+
3. stream parsed metric points and workouts into temporary staging tables with bulk database operations;
415
+
4. promote staged rows in one final transaction, recording coverage metadata and provenance;
416
+
5. invalidate affected aggregate rows and run one cache rebuild after commit.
417
+
418
+
If XML parsing or staging fails, the import is marked failed and no cleanup/backfill is run. The old row-by-row `BulkInsertPoints` path is retained only for narrow compatibility helpers.
419
+
410
420
After import, run `make energy-backfill` (CLI) or open **Settings → Historical EnergyBank** (web UI) to compute retrospective EnergyBank snapshots from the imported daily scores. This unlocks per-user verdict band calibration; without it, the cold-start defaults are used until the live orchestrator has accumulated 30+ days of snapshots on its own.
411
421
412
422
## Multi-Device Source Priority
@@ -429,7 +439,14 @@ This priority is applied consistently across dashboard, daily scores, readiness
429
439
430
440
### Apple Health Import & Auto Export Conflict Resolution
431
441
432
-
When re-importing from Apple Health (e.g. to fill gaps from missed Auto Export days), the system automatically removes Auto Export (`Health dash - Hourly`/`Vitals`) data for overlapping dates. Apple Health export is treated as ground truth. A full cache rebuild (force backfill) runs after each import.
442
+
Apple Health XML export is treated as a timestamped HealthKit snapshot, not as a permanent lock on historical rows:
443
+
444
+
- XML import supersedes older live / Health Auto Export rows on the same exact `(metric_name, date, source)` key.
445
+
- Live/mobile data ingested after that XML snapshot is considered fresher and can overwrite the exact key.
446
+
- A later XML export may supersede those rows again as a newer HealthKit snapshot.
447
+
- Rows missing from a newer XML snapshot are cleaned up conservatively only within coverage groups proven by staging metadata; the importer does not delete every row in a broad `min_date` / `max_date` range.
448
+
449
+
This lets full exports repair old HealthKit history while still allowing the first-party app to keep recent data fresh.
0 commit comments