Commit Graph
7 Commits
Author SHA1 Message Date
Marc-André Bombeck b00be2863d perf(charts): bound health-chart fetches to the active range window
The chart-data query in health-chart.tsx walked the full measurement
history with a `while (true)` paginated loop on every visit. For
Apple-Health-rich accounts (pulse every minute, months of backfill)
this meant tens of thousands of rows per type per page navigation,
which is the root cause behind FB-D2's "pulse insights loads forever"
report.

Derive `from` / `to` from the active range tab (plus the
comparison-baseline shift when overlay is on), pass both to
/api/measurements, and drop the unbounded paginator. The query key
re-keys on the window so a range-tab change refetches the right
slice. staleTime 60s + gcTime 5min hold the bounded payload across
tab navigation between insights sub-pages.

New `aggregate` query param + range-aggregation helper let the GET
handler downsample server-side for ranges over 90 d (one row per
day) and over 365 d (one row per ISO week). The per-request `limit`
ceiling lifts from 500 to 5000 — still bounded, but enough headroom
for an explicit from/to window without re-introducing pagination.

iOS contract — GET /api/measurements: additive. The `from`, `to`,
`aggregate` params are all optional; callers that omit them (the
iOS client, the existing measurements page) see the unchanged
response shape and the historical 500-row default cap. The
aggregated branch is only entered when both from + to are present
AND the window is wide enough for the threshold ladder to engage.
2026-05-16 00:20:27 +02:00
Marc-André Bombeck b4df3ae8cd feat(apple-health): extend HK identifier mapping with iOS-17 + iOS-18 long-tail
Adds 32 HKQuantity / HKCategory identifiers to HK_QUANTITY_TYPE_DEFERRED
so the batch ingest endpoint logs a "deferred, not unknown" signal when
the iOS app passes a sample type HealthLog hasn't surfaced yet. None
currently have a HealthLog MeasurementType counterpart; mapping them
through requires the enum extension to land first (separate wave).

Buckets covered, with iOS version they shipped in:
  - Cardiovascular clinical (iOS 16): AFib burden, peripheral perfusion
  - Mobility (iOS 14/15): walking steadiness, falls, steadiness event
  - Pulmonary clinical (iOS 17): FEV1, FVC, peak expiratory flow,
    inhaler usage
  - Other quantity types: insulin delivery, UV exposure, electrodermal
    activity, blood-alcohol content, legacy Nike fuel
  - Heart-rhythm event flags (iOS 9+/17 refresh): low/high heart-rate
    events, irregular rhythm, low cardio fitness event
  - Audio-exposure event flags + environmental sound reduction
  - Behavioural: handwashing, toothbrushing
  - Reproductive / fertility / pregnancy: contraceptive, lactation,
    pregnancy, pregnancy/progesterone test, sexual activity, sleep
    changes, persistent intermenstrual bleeding, prolonged / irregular
    / infrequent menstrual cycles

Tests gain explicit coverage that every long-tail identifier sits in
the deferred set and that no identifier is duplicated.
2026-05-14 17:43:37 +02:00
Marc-André Bombeck 8c929fc554 refactor(apple-health): collapse sleep branch in mapAppleHealthEntry
Builds the base output object once with the four shared fields and
appends sleepStage only when the mapping carries a sleep-stage table.
Drops one duplicated object literal and one return path; the function
keeps the same null-on-bad-input semantics for unmapped, invalid date,
missing stage, and unknown stage cases.
2026-05-14 17:09:46 +02:00
Marc-André Bombeck 7851341d98 refactor(apple-health): drop redundant HK_QUANTITY_TYPE_TO_MEASUREMENT view
The derived map mirrored APPLE_HEALTH_TYPE_MAP one key for one key
with the value narrowed to the measurementType field. Every caller can
read that via APPLE_HEALTH_TYPE_MAP[id]?.measurementType — no external
consumer is depending on the compact view today. Drops the redundant
constant plus the mirror-check test block that only existed to keep
the two tables in sync.
2026-05-14 17:09:17 +02:00
Marc-André Bombeck c05292f608 feat(ingest): port HK identifier mapping from k0rventen + dogsheep with MIT/Apache-2.0 attribution
Extends src/lib/measurements/apple-health-mapping.ts with the W8d
mapping surface the v1.5 iOS-Swift session needs locked before it
starts:

  - Three new APPLE_HEALTH_TYPE_MAP entries —
    HKQuantityTypeIdentifierEnvironmentalAudioExposure,
    HeadphoneAudioExposure, TimeInDaylight — wired to the matching
    MeasurementType values landed in Migration 0052.
  - HK_QUANTITY_TYPE_TO_MEASUREMENT: a compact derived view of the
    same data keyed by HK identifier so a caller that only needs the
    enum value can skip instantiating the full mapping record (XML
    import worker, iOS DTO routing).
  - HK_QUANTITY_TYPE_DEFERRED: the curated "we know about it, we are
    not mapping it yet" set. Covers iOS-17/18 running form, cycling,
    paddle / row / ski distances + speeds, sleep apnea, scored
    assessments (GAD-7 / PHQ-9), nutrition, mental-state, FHIR
    clinical, ECG, pregnancy / cycle. Each block carries its planned
    release window inline.

File-header attribution credits the two open-source projects whose
identifier lists were consulted as data —
k0rventen/apple-health-grafana (MIT) and dogsheep/healthkit-to-sqlite
(Apache-2.0); only the lookup strings were ported, not code.

Test additions
(src/lib/measurements/__tests__/apple-health-mapping.test.ts):
  - exhaustiveness — every MeasurementType outside the
    Withings-only set has an HK counterpart;
  - dedup — no identifier appears in both the mapping table and the
    deferred set;
  - identity-conversion check for the three new entries;
  - HK_QUANTITY_TYPE_TO_MEASUREMENT key + value parity with the full
    map;
  - deferred-set sanity (every entry starts with a real HK prefix).
2026-05-14 15:54:52 +02:00
Marc-André BombeckandClaude Opus 4.7 f1e6630958 chore(format): prettier sweep across develop
Mostly table column normalisation under docs/audit/ + planning, object-
literal expansion in src/lib/ai/coach/snapshot.ts after the W4 F6 Apple
Health metric block landed, and small wrap/unwrap line adjustments in
the device + APNs routes / tests touched across W3/W4/W5 sessions.
No behavioural change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:27:51 +02:00
Marc-André BombeckandClaude Opus 4.7 30c0f76a34 feat(measurements): Apple Health identifier mapping helper
New `src/lib/measurements/apple-health-mapping.ts` translates inbound
HealthKit samples into the row shape the `Measurement` model expects.
Symmetrical to the Withings `MEASURE_TYPE_MAP` but keyed by HealthKit
string identifier so the upcoming batch endpoint can do a single
table lookup per entry.

Each mapping carries its canonical DB unit, a unit-conversion helper
(identity for SI-aligned types, ×100 for HealthKit's 0..1 fraction
quantities), an aggregation hint for downstream summarisation, and
flags the explicit-consent metrics (HRV, VO2 Max, sleep) so the audit
trail can pick them out.

Sleep is the one category type — the helper returns one row per stage
with the new `sleepStage` column populated; per-stage minutes go
straight in as `value`. Unknown identifiers return `null` so the
batch endpoint can mark the entry as skipped instead of failing the
whole batch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 00:17:56 +02:00