fix(google-health): reconcile the daily-summary filter and two leaf fields with the v4 reference

Daily HRV and resting heart rate returned HTTP 200 with zero imports on a
live Fitbit-band account. Full reconciliation against the v4 reference
(data-types index, DataPoint schema, list/reconcile/dailyRollUp pages)
surfaces three defects:

- The daily-summary `.date` filter prefix is the docs' own contradiction:
  the data-types index's filter-parameter column says snake_case
  (`daily_heart_rate_variability`), while the list reference's only worked
  daily example is `dailyHeartRateVariability.date < "2024-08-15"` — the
  camelCase payload key. Live, the snake form is accepted (200) but matches
  nothing. Send the worked-example camelCase form first and re-walk once
  with snake_case if the very first page 400s; annotate the accepted style
  (`googleHealth.dateFilter.style`) and surface it through the structure
  probe as `requestShape` so a live account settles the grammar.
- `daily-respiratory-rate` reads `breathsPerMinute` (a plain number); the
  previously assumed `dailyRespiratoryRateBpm` leaf does not exist, so
  RESPIRATORY_RATE never parsed a single row.
- `height` carries `heightMillimeters` (int64 string), not `heightMeters`;
  the heightCm profile seed never fired. Convert mm -> cm.

Also cover the documented Exercise.ExerciseType catalogue in the sport map
(MOUNTAIN_BIKE, TRAIL_RUN, TREADMILL_WALK, STATIONARY_BIKE, SWIMMING_POOL,
FREE_WEIGHTS, CROSSFIT, TABATA_WORKOUT, ...) — the enum's multi-word values
previously fell through to "other".

Tests pin the documented field names per fixture, the camel-first/snake-
fallback walk (including no-fallback for sample shapes and unfiltered
backfills), and the extended sport map. mapping.md records the filter
contradiction and the corrected leaves.
This commit is contained in:
Marc-André Bombeck committed 2026-07-07 11:16:27 +02:00
1 parent 0da11811e2
commit 849607e7df
5 files changed
+466 -101

No files matched your search

@@ -81,9 +81,11 @@ type ProbeResult =
count: number;
structure: unknown;
/**
* Rollup types only: which documented dailyRollUp request shape Google
* Rollup types: which documented dailyRollUp request shape Google
* accepted (`days90` standard chunking vs `days14` conservative
* fallback) — the live verdict on the range constraint.
* fallback). Daily-summary types: which `.date` filter prefix style
* Google accepted (`camel` worked-example vs `snake` fallback) — the
* live verdict on the docs' self-contradiction.
*/
requestShape?: string;
}
@@ -141,6 +143,11 @@ async function runStructureProbe(
pageSize,
maxPages: 1,
tz,
// Daily-summary types report which `.date` filter prefix style
// Google accepted, so a live account settles the docs' conflict.
onDateFilterStyle: (style) => {
requestShape = style;
},
});
}
results[name] = {