# ovok.measurements

Canonical page: https://docs.ovok.com/web-sdk/core/namespaces/measurements

`ovok.measurements` turns a typed measurement into FHIR Observations, saves them safely and reads the latest values back. Patient apps call it for the signed-in user; a server machine client can call it with an explicit `patientId`.

## Save a measurement [#save-a-measurement]

`save` builds the Observations and sends them as one conditional-create transaction, so a retry cannot duplicate a reading.

```ts
import { MeasurementTypeKey } from '@ovok/core';

const result = await ovok.measurements.save({
  measurement: { measurementTypeKey: MeasurementTypeKey.heartRate, heartRate: 70 },
});

if (result.status === 'queued') {
  console.log('Saved for later', result.queueId);
}
```

- Without `patientId`, the reading belongs to the signed-in profile.
- `result.status` is `'saved'` (with the server `response` bundle) or `'queued'`.
- `build` returns the Observations without saving. `saveObservations` saves Observations you built yourself, main observation first.
- If the server refuses or leaves out an observation, the save throws an `OvokError` with the refused status; see [errors and retries](/web-sdk/core/errors-and-retries).
- Observations are `final`, carry a FHIR category (`vital-signs`, or `laboratory` for glucose and urine), code quantities in UCUM, and are dated by the measurement's `recordedAt` unless you pass `effectiveDateTime`.
- A reading of several observations (a blood pressure, a pulse oximeter reading, an ECG, a urine strip) ends with a group observation whose `hasMember` points at the others: a blood pressure's is the FHIR panel 85354-9 with the pressures as components, a urine strip's is 24357-6, and the rest use the code system `https://sdk.ovok.com/fhir/CodeSystem/measurement-type`. The other observations keep their codes and links, and `observationsToMeasurements` reads either shape. Readings saved before groups existed get theirs from `scripts/backfill-reading-groups.mjs` (a dry run unless `--apply`).
- A measurement's `memorySlot` (the monitor's user) is kept on its first observation. An ECG needs its `duration`; without it the save throws.

## Retry queued readings [#retry-queued-readings]

A connection failure queues the reading in `storage` and returns `{ status: 'queued' }`. Flush the queue when the device is back online.

```ts
const outcome = await ovok.measurements.offlineQueue.flush();

console.log(outcome.saved, outcome.queued, outcome.exhausted);
```

- The queue only sends readings for the account that queued them.
- The default storage is in memory. Pass persistent `storage` to the client to keep readings across restarts.
- `flush({ includeExhausted: true })` also retries readings that ran out of retries; `resetExhausted: true` gives them a fresh set first.

## Save many readings at once [#save-many-readings-at-once]

`saveObservationGroups` saves a monitor's stored readings in as few requests as fit. Each group is one reading with its main observation first.

```ts
await ovok.measurements.saveObservationGroups(groups, { identifierSystems: [APP_READING_SYSTEM] });
```

- It is not queued offline.
- A request with one reading is a transaction, atomic when the project enables FHIR transactions. A request with several is sent as two batches, every main observation first, so a reading's other observations never point at one that wasn't saved. If it fails, some readings may already be saved; saving them again is safe.
- A main observation that matches several saved copies counts as saved.
- It stops, without sending the rest, if another account signs in between requests.
- `identifierSystems` names the identifiers that make a reading unique; `replace: true` overwrites a reading saved under the same identifiers.

## Read the latest readings [#read-the-latest-readings]

```ts
const codes = ovok.measurements.codesFor([MeasurementTypeKey.heartRate]);

const newest = await ovok.measurements.latest({ me: true, codes: codes.split(',') });

const bundle = await ovok.measurements.lastn(patientId, { code: codes, max: 50, _elements: 'code,effectiveDateTime' });
```

- `latest` returns the newest observation per code for yourself (`me: true`) or a patient (`me: false` with `patientId`).
- `lastn` returns the newest `max` observations per code in one `$lastn` request and passes other search parameters on.
- `lastForPatient` reads `patient/{patientId}/observation`.

## Measurement categories [#measurement-categories]

`MeasurementTypeKey` has these keys: `babySleep`, `bloodGlucose`, `bloodOxygenPulseRate`, `bloodPressure`, `bodyWeight`, `cosinussMultiParam`, `ecg`, `heartRate`, `heartRateVariability`, `lipidPanel`, `photoUpload`, `respiratoryRate`, `respiratoryTherapy`, `restingHeartRate`, `spirometryPanel`, `stepCount`, `symptomQuestionnaire`, `temperature`, `urineAnalysis`, `vo2Max`, `walkingHeartRateAverage` and the deprecated `uricAcid`. The [measurement type list](/web-sdk/core/reference/core/measurements#measurementtypekey) gives each key's value, and the pages under measurements describe each type's fields.

## Reference [#reference]

- [measurements API reference](/web-sdk/core/reference/core/measurements): every method and type.
- [ovok.patient](/web-sdk/core/namespaces/patient) for reading a patient's saved records.
