> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Data shape

> WearMetrics — the unified output every adapter normalises to

`WearMetrics` is the single shape every adapter writes into. Downstream
layers (`synheart-session`, `synheart-core`, the runtime ingest queue)
read this shape; they do not consume vendor-specific JSON.

## `WearMetrics`

```dart theme={null}
class WearMetrics {
  final DateTime timestamp;
  final String deviceId;
  final String source;
  final Map<String, num?> metrics;
  final Map<String, Object?> meta;
  final List<double>? rrIntervalsMs;
}
```

JSON shape (canonical):

```json theme={null}
{
  "timestamp": "2025-10-20T18:30:00Z",
  "device_id": "applewatch_1234",
  "source": "apple_healthkit",
  "metrics": {
    "hr": 72,
    "hrv_rmssd": 45,
    "hrv_sdnn": 62,
    "steps": 1045,
    "calories": 120.4,
    "distance": 2.5,
    "stress": 23
  },
  "meta": {
    "battery": 0.82,
    "synced": true
  },
  "rr_ms": [800, 850, 820]
}
```

## Top-level fields

| Field       | Type                       | Notes                                                                                                                                                             |
| ----------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `timestamp` | ISO-8601 UTC string        | Time the measurement applies to (sample time, not emit time). Always UTC.                                                                                         |
| `device_id` | string                     | Stable identifier for this physical device. Format is vendor-shaped (e.g. `"applewatch_1234"`, `"whoop_<uuid>"`, BLE MAC for HRM).                                |
| `source`    | string                     | Lowercase snake\_case adapter id. Stable across versions of the same adapter.                                                                                     |
| `metrics`   | object                     | Keyed by metric short-name (see below). Values may be `null` when the adapter knows the metric is not available; missing keys mean "not produced by this sample". |
| `meta`      | object                     | Free-form per-sample metadata. Stable keys are documented below; adapters may add vendor-specific keys.                                                           |
| `rr_ms`     | array of doubles \| absent | Inter-beat intervals in milliseconds when the source provides them. Absent when not available.                                                                    |

## `metrics` keys

Maps from the `MetricType` enum:

| Key         | Type   | Unit     | Source                                                                                      |
| ----------- | ------ | -------- | ------------------------------------------------------------------------------------------- |
| `hr`        | number | bpm      | HR sample.                                                                                  |
| `hrv_rmssd` | number | ms       | RMSSD over the adapter's window.                                                            |
| `hrv_sdnn`  | number | ms       | SDNN over the adapter's window.                                                             |
| `steps`     | number | count    | Period total (since the previous sample, or per-day depending on adapter).                  |
| `calories`  | number | kcal     | Active energy.                                                                              |
| `distance`  | number | km       | Total distance over the period.                                                             |
| `stress`    | number | unitless | Vendor-specific score (e.g. Garmin `0–100`); adapters do not normalise this across vendors. |

Adapters MAY emit additional vendor-specific metric keys but downstream consumers MUST tolerate unknown keys. Stable keys above are the normalised contract.

## `meta` keys (stable)

| Key       | Type       | Notes                                                                          |
| --------- | ---------- | ------------------------------------------------------------------------------ |
| `battery` | number 0–1 | Device battery level.                                                          |
| `synced`  | bool       | Whether this sample arrived via real-time path (true) or backlog/sync (false). |

Adapter-specific meta: Garmin adds `connection_state`; WHOOP adds `recovery_score`, `strain`, etc. — refer to the per-vendor models.

## `rr_ms` semantics

When present:

* Each value is one inter-beat interval in milliseconds.
* Order is the order observed by the device (chronological).
* The list is per sample, not cumulative — multiple samples at the same `timestamp` can share an `rr_ms` payload via the meta field.
* Range gates apply downstream: the runtime's RR push rejects values outside `[200, 6000]`.

## Adapter source IDs

The top-level `source` field carries the **adapter id** — typically the bare vendor/source name. More specific flavor information (cloud vs BLE, etc.) lives in `meta.source_type`.

| `source` value      | Adapter                                | `meta.source_type` flavor                                                                  |
| ------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------ |
| `"apple_healthkit"` | Apple HealthKit                        | —                                                                                          |
| `"health_connect"`  | Android Health Connect                 | —                                                                                          |
| `"ble_hrm"`         | BLE Heart Rate Profile                 | —                                                                                          |
| `"whoop"`           | WHOOP                                  | `"whoop_cloud"` for OAuth + webhooks; WHOOP via BLE Broadcast HR uses `source = "ble_hrm"` |
| `"garmin"`          | Garmin via Connect API                 | `"garmin_cloud"`                                                                           |
| `"garmin_sdk"`      | Garmin Health SDK (RTS, license-gated) | —                                                                                          |
| `"fitbit"`          | Fitbit                                 | (planned)                                                                                  |
| `"oura"`            | Oura                                   | proxied through HealthKit / Health Connect today                                           |

Consumers MUST tolerate unknown `source` and `meta.source_type` values.

When new adapters land, they take new stable strings. Existing strings do not change meaning.

## Compatibility rules

The shape is unversioned. Compatibility rules:

* Producers MAY add new metric keys, new meta keys, and new `source` values.
* Producers MUST NOT remove or rename existing keys.
* Consumers MUST tolerate unknown metric keys and unknown `source` values.
* A future change to the top-level shape would be a breaking change;
  the SDK would expose both producers in parallel during migration.

## Vendor → schema mapping (informal)

This table is informational only. Each adapter is the authoritative source for its mapping; this is a reading aid.

| Vendor field                                                 | Maps to               | Notes                                         |
| ------------------------------------------------------------ | --------------------- | --------------------------------------------- |
| HealthKit `HKQuantityTypeIdentifierHeartRate`                | `metrics.hr`          | Average per query window.                     |
| HealthKit RR samples                                         | `rr_ms`               | Workout-mode only on most Apple Watch models. |
| Health Connect `HeartRateRecord`                             | `metrics.hr`          | One sample per record.                        |
| WHOOP webhook `recovery_score`                               | `meta.recovery_score` | Not part of the canonical metric set.         |
| Garmin Connect `restingHeartRate`                            | `metrics.hr`          | Daily resting HR baseline.                    |
| BLE GATT 0x180D HR characteristic                            | `metrics.hr`          | Plus optional RR field → `rr_ms`.             |
| Apple XML `HKQuantityTypeIdentifierHeartRateVariabilitySDNN` | `metrics.hrv_sdnn`    | Backfill only.                                |

## How downstream uses this

`synheart-session`'s `WearBiosignalProvider` wraps `streamHR` / `streamHRV` and converts each `WearMetrics` into a `BiosignalSample`:

```dart theme={null}
BiosignalSample(
  timestampMs: metrics.timestamp.millisecondsSinceEpoch,
  bpm: metrics.getMetric(MetricType.hr) ?? 0.0,
  rrIntervalMs: metrics.rrIntervalsMs?.first,
  accelerometer: ...,
)
```

`synheart-core` forwards the same shape into the runtime via `pushHr` / `pushRr` after consent gating.

## Related

* [Wear Overview](/synheart-wear/overview) — full SDK surface.
* [Adapters](/synheart-wear/adapters) — per-vendor flow.
* [Errors](/synheart-wear/errors) — failure modes.
