# Measurements

A FibriCheck measurement is performed in 3 steps:

* **Take the measurement:** the [Camera SDK](https://docs.fibricheck.com/introduction/camera-sdk/introduction) uses the smartphone's camera to take an on-device PPG-signal.
* **Process the measurement:** the measurement data is sent to the FibriCheck cloud where our AI algorithm will process the measurement and make a diagnosis. In some cases the measurement will be reviewed by a medical expert.
* **Visualise the measurement:** the measurement can be visualised in your application or through a generated PDF.

## Measurement properties

### Status

A measurement can have multiple statuses, depending in which phase of the review process it is in:

| Status                    | Description                                                                                                                                                                                                                                                |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `measured`                | The measurement is received. The status should change to `preprocessing_selection` immediately.                                                                                                                                                            |
| `preprocessing_selection` | The correct preprocessing algo is being determined. The status should change to `analysis_selection` immediately.                                                                                                                                          |
| `analysis_selection`      | The correct analysis algo is being determined. The status should change to `pending_analysis` immediately.                                                                                                                                                 |
| `pending_analysis`        | The measurement is waiting for the algorithm to analyze it. The status should change to `under_analysis` when the algorithm is ready to analyze                                                                                                            |
| `under_analysis`          | The measurement is being analyzed by the algo. The status will be transitioned to `analysis_failed` or `processing_results` depending on the response of the algorithm.                                                                                    |
| `analysis_failed`         | The algorithm was not able to analyze this measurement. The measurement will stay in this status until manually transitioned back to `pending_analysis`.                                                                                                   |
| `processing_results`      | The result of the analysis is being processed.  The status should change to `analyzed` when ready.                                                                                                                                                         |
| `analyzed`                | The measurement was successfully `analyzed`. Depending on the subscription, the measurement will immediately transition to `pending_review` or stay in this status. Upon request, the measurement can also be manually transitioned to`pending_review` *.* |
| `pending_review`          | The measurement is awaiting revision by a human medical expert. The human revision is currently meant to be completed within 48 hours and will transition the status to `reviewed`.                                                                        |
| `reviewed`                | The measurement is reviewed by a human medical expert. The measurement will stay in this status until manually transitioned back to `pending_review`*.*                                                                                                    |

### Indicator

This value represents the indicator that the FibriCheck algorithm has given to the measurement. When the `status` is `reviewed`, it means this indicator is validated by a medical professional. Possible values are `'normal' | 'quality' | 'urgent' | 'warning'`

### Diagnosis

After a measurement is analyzed the `mostSevereLabel` object will become available. This object will return the current diagnosis and take into account whether it's a measurement that will be reviewed (premium) or not (essential). To get the diagnosis, you can check the `mostSevereLabel` property from the measurement object:

{% hint style="info" %}
The `mostSevereLabel` is added and updated asynchronously after a measurement has been analyzed or reviewed respectively. It can take a couple of seconds before it's added or updated to the measurement result.
{% endhint %}

```json
{
...
  "mostSevereLabel" : {
    "key": "regular",
    "color": "green"
  }
}
```

&#x20;Possible values of `mostSevereLabel.key` are:&#x20;

```
  'regular'
  'possibly_irregular'
  'possible_atrial_fibrillation'
  'extrasystoles_trig_episode'
  'extrasystoles_isolated'
  'dubious_rhythm'
  'extrasystoles_trigeminy'
  'extrasystoles_frequent'
  'phone_incompatible'
  'extrasystoles_big_episode'
  'increased_hrv'
  'sinus'
  'atrial_flutter'
  'brady_episode'
  'tachycardia'
  'tachy_episode'
  'extrasystoles_bigminy'
  'bradycardia'
  'atrial_fibrillation'
  'other' 
  'no_diagnosis'
  'no_result'
  'quality_too_low'
  'quality_to_low'
```

Possible values of `mostSevereLabel.color` are:

```
'green'
'blue'
'orange'
'red'
'grey'
```

The colors give a visual indication of the meaning of the label. Green, orange, and red indicate the severity of the diagnosis. Blue indicates an issue with the quality of the measurement, and grey means that a result is not (yet) available.

{% hint style="info" %}
In older versions of the Cloud SDK, this property may not be visible yet. If so, you can always use the `getMostSevereLabel()` function.
{% endhint %}

### Heart rate

The algorithm returns the calculated heart rate in the `heartrate` property. This calculated value can differ from the real-time heart rate value that you can see while performing a measurement.&#x20;

The reason for the difference is that the `heartrate` property is provided by the FibriCheck Cloud AI algorithm, which provides a more accurate value than the lighter on-device algorithm.

When the cloud algorithm could not detect a proper heart rate, the resulting `heartrate` will be `-1`. This is usually the case in measurements that have the `quality` indicator.

### Measurement timestamp

The date and time of the measurement are given in epoch format under the `measurement_timestamp` property.

### Context

When a measurement has been performed, context can be added. The context can contain one activity and multiple symptoms.&#x20;

The context can be added directly to a measurement or can be updated at a later stage through the `updateMeasurementContext` method. This can be beneficial to the user experience. If you send the raw measurement data first, the processing of the measurement can already start in the FibriCheck Cloud. When the user has selected their symptoms, it can be added to the already processed measurement. &#x20;

[See below](#update-measurement-context) how to update the measurement context afterwards.

| Property          | Possible Values                                                                                                                                                        | Description                                                                                                 |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `activity`        | `resting`, `sleeping`, `sitting`, `walking` `working`, `exercising`  `other`, `standing`                                                                               | Describes what the user was doing just before taking the measurement. A single value can be selected.       |
| `symptoms`        | `no_symptoms`, `lightheaded`, `confused`, `fatigue`, `other`, `palpitations`, `chest_pains`, `shortness_of_breath`, `dizziness`, `feeling_of_fainting`, `racing_heart` | Symptoms that the user was experiencing at the time of the measurement. One or more values can be selected. |
| `symptomSeverity` | `1`, `2a`, `2b`, `3`, `4`                                                                                                                                              | A single severity score across the different symptoms a user is experiencing.                               |

#### Symptom Severity Score&#x20;

The symptom severity score follows the modified EHRA classification for symptom severity. The symptom severity score is a single score given across the symptoms a patient is experiencing.

| mEHRA score | Symptoms  | Description                                                             |
| ----------- | --------- | ----------------------------------------------------------------------- |
| 1           | None      | No symptoms reported.                                                   |
| 2a          | Mild      | Normal daily activity not affected, symptoms not troublesome to patient |
| 2b          | Moderate  | Normal daily activity not affected but patient troubled by symptoms     |
| 3           | Severe    | Normal daily activity affected                                          |
| 4           | Disabling | Normal daily activity discontinued                                      |

## Post a new measurement

When a measurement done by the [Camera SDK](https://docs.fibricheck.com/introduction/camera-sdk) is finished, it needs to be uploaded to the FibriCheck cloud for processing and analysis.

Take a look at the [Camera SDK documentation](https://docs.fibricheck.com/introduction/camera-sdk/introduction#perform-your-first-measurement) for a full code example on how to perform and post a FibriCheck measurement.

{% hint style="warning" %}
Measurements will only be analyzed by the algorithm when the authenticated user has an active prescription. See the [prescriptions documentation](https://docs.fibricheck.com/introduction/prescriptions) for more information on how to create and manage prescriptions.
{% endhint %}

### SDK

The Cloud SDK is aware of the data structure of the Camera SDK. Posting a measurement is nothing more than executing the `postMeasurement` function with the data object from the Camera SDK.

The SDK will automatically augment the measurement data with some meta-data like the device details and the application version.&#x20;

{% tabs %}
{% tab title="Flutter" %}

```dart
Future _onMeasurementFinished(String measurementString) async {
  var mCreationData = MeasurementCreationData.fromCameraSdk(measurementString);
  await widget.sdk.postMeasurement(mCreationData, "v0.0.1");
}
```

{% endtab %}

{% tab title="React Native" %}

```javascript
async function postMeasurement(cameraData, context) {
  const measurement = {
    ...cameraData,
    context
  };
  await sdk.postMeasurement(measurement, RNFibriCheckView.version); 
}
```

{% endtab %}
{% endtabs %}

### REST API

After a successful measurement, the platform-specific [FibriCheck Camera SDK](https://docs.fibricheck.com/introduction/camera-sdk) will output a structure that, converted to JSON, can be uploaded as-is to the FibriCheck cloud via the following `POST` call:

## Post a new measurement

<mark style="color:green;">`POST`</mark> `https://api.fibricheck.com/data/v1/documents/fibricheck-measurements/`

#### Request Body

| Name | Type   | Description |
| ---- | ------ | ----------- |
|      | String |             |

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
    "groupIds": [],
    "userIds": [],
    "creatorId": "5811c7a046e0fb000530a465",
    "status": "measured",
    "transitionLock": {
        "timestamp": "2023-02-23T10:21:59.526Z"
    },
    "statusChangedTimestamp": "2023-02-23T10:21:59.526Z",
    "data": {
        "quadrants": [
            [
                {
                    "id": "63f73e47e8c20b3c391c1f8e"
                },
                {
                    "id": "63f73e47e8c20bd8221c1f8f"
                },
                {
                    "id": "63f73e47e8c20b48d81c1f90"
                },
                {
                    "id": "63f73e47e8c20ba6411c1f91"
                }
            ],
            [
                {
                    "id": "63f73e47e8c20b75bd1c1f92"
                },
                {
                    "id": "63f73e47e8c20b6b271c1f93"
                },
                {
                    "id": "63f73e47e8c20bf3281c1f94"
                },
                {
                    "id": "63f73e47e8c20b34ad1c1f95"
                }
            ],
            [
                {
                    "id": "63f73e47e8c20b4afd1c1f96"
                },
                {
                    "id": "63f73e47e8c20bf25c1c1f97"
                },
                {
                    "id": "63f73e47e8c20b6cc51c1f98"
                },
                {
                    "id": "63f73e47e8c20b73041c1f99"
                }
            ],
            [
                {
                    "id": "63f73e47e8c20bfa5e1c1f9a"
                },
                {
                    "id": "63f73e47e8c20bec5e1c1f9b"
                },
                {
                    "id": "63f73e47e8c20b098e1c1f9c"
                },
                {
                    "id": "63f73e47e8c20b333a1c1f9d"
                }
            ]
        ],
        "measurement_timestamp": 1677147718937,
        "context": {
            "symptomSeverity": "1",
            "symptoms": [
                "no_symptoms"
            ],
            "activity": "sitting"
        },
        "attempts": 1,
        "app": {
            "name": "mobile-spot-check",
            "build": 836,
            "version": "2.6.0",
            "camera_sdk_version": "1.3.0-dev.80"
        },
        "heartrate": 63,
        "tags": [
            "context_handled",
            "patient-simulator",
            "original-id: 63ecc961e8c20b36071bf157"
        ],
        "device": {
            "os": "15.6.1",
            "model": "iPhone12,1",
            "manufacturer": "Apple",
            "type": "ios"
        },
        "viewResult": true,
        "skippedPulseDetection": true,
        "skippedFingerDetection": false
    },
    "updateTimestamp": "2023-02-23T10:21:59.526Z",
    "creationTimestamp": "2023-02-23T10:21:59.526Z",
    "id": "63f73e47e8c20b7d901c1f9e"
}
```

{% endtab %}
{% endtabs %}

Typically you want to add some meta-data to the measurement. This can be done by augmenting the `measurement` object from the Camera SDK in the following way:

```javascript
{
  ...measurement,
  device: {
    os: "12.5.5",
    model: "iPhone6,2",
    manufacturer: "Apple",
    type: "ios|android|ionic|versa|versa_lite|versa_2|Tizen|versa_3|sense",
  },
  app: {
    build: 10,
    name: 'mobile-spot-check',
    version: "2.6.0"
    fibricheck_sdk_version: "",
    camera_sdk_version: "1.2.0",
  },
  tags: ['custom-tag'],
}
```

{% hint style="warning" %}
Always include the device information. The algorithm requires this information to know which parameters to include in the analysis!
{% endhint %}

The `tags` parameter can be used to categorise sets of measurements for easier filtering later on.

Take a look at the documentation of your app development platform to find out how to retrieve metadata parameters like OS version, build number,... in your application

## Update measurement context

### SDK

{% tabs %}
{% tab title="Flutter" %}

```dart
class MeasurementContext {
  final List<Symptoms>? symptoms;
  final Activity? activity;
  final SymptomSeverity? symptomSeverity;

  MeasurementContext({
    required this.symptoms,
    required this.activity,
    this.symptomSeverity,
  });

  factory MeasurementContext.fromJson(Map<String, dynamic> json) => _$MeasurementContextFromJson(json);

  Map<String, dynamic> toJson() => _$MeasurementContextToJson(this);
}

await _sdk.updateMeasurementContext(
  "{measurementId}",
  MeasurementContext(
    symptoms: [Symptoms.chestPains],
    activity: Activity.resting
    symptomSeverity: SymptomSeverity.severity_1
  ));
```

{% endtab %}

{% tab title="React Native" %}

```typescript
await sdk.updateMeasurementContext('{measurementId}',{
  symptoms: ['fatigue'],
  activity: 'other',
  symptomSeverity: '1'
});
```

{% endtab %}
{% endtabs %}

### REST API

To update the measurement context through the API, you have to execute the API call below with the following body:&#x20;

```
{
    "context": {
        "activity": "sitting",
        "symptoms": ["fatigue"],
        "symptomSeverity": "2a"
    }
}
```

## Update the measurement context

<mark style="color:orange;">`PUT`</mark> `https://api.fibricheck.com/data/v1/fibricheck-measurements/documents/{measurementId}`

#### Path Parameters

| Name                                            | Type   | Description |
| ----------------------------------------------- | ------ | ----------- |
| measurementId<mark style="color:red;">\*</mark> | String |             |

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
    "affectedRecords": 1
}
```

{% endtab %}
{% endtabs %}

## Fetch measurements

### SDK

Use the `getMeasurement` method to get a single measurement based on an `id`. The SDK only allows to request measurements for the currently authenticated user.

To fetch multiple measurements, you can use the`getMeasurements` method. This will return a paginated result with all measurements for the currently authenticated user. You can find the measurements under the `data` property. You can also use the `next` and `previous` functions, present on the result, to navigate through the user's measurements.

{% tabs %}
{% tab title="Flutter" %}

```dart
// Fetch a single measurement
const measurementId = '0000';
const measurement = await _sdk.getMeasurement(measurementId);

// To fetch all your measurements
_sdk.getMeasurements(true); // true -> get newest measurements first
await res.getNextMeasurements(); // get next 20 measurements
await res.getPreviousMeasurements(); // get previous 20 measurements
```

{% endtab %}

{% tab title="React Native" %}

```typescript
// Fetch a single measurement
const measurementId = '0000';
const measurement = await sdk.getMeasurement(measurementId);


// Returns the first 20 measurements
const measurements = await sdk.getMeasurements();
// Returns the next 20 measurements
const nextMeasurements = await measurements.next();
```

{% endtab %}
{% endtabs %}

### REST API

## Fetch measurements

<mark style="color:blue;">`GET`</mark> `https://api.fibricheck.com/data/v1/fibricheck-measurements/documents`

{% tabs %}
{% tab title="200: OK Example endpoint response with measurements" %}
The following JSON result is an example response when a single measurement is requested from the API.

```json
{
    "query": "{\"$and\":[{\"$or\":[{\"$and\":[{\"userIds\":{\"$eq\":\"5811c7a046e0fb000530a465\"}}]},{\"$and\":[{\"groupIds\":{\"$in\":[\"5919759152faff000545b18c\"]}}]}]}]}",
    "page": {
        "total": 2836,
        "offset": 0,
        "limit": 1
    },
    "data": [
        {
            "id": "581c772c46e0fb00055d5082",
            "creatorId": "5811c7a046e0fb000530a465",
            "userIds": [
                "5811c7a046e0fb000530a465"
            ],
            "groupIds": [],
            "status": "reviewed",
            "data": {
                "abnormalities": [],
                "app": {
                    "version": "1.0.2"
                },
                "context": {
                    "symptoms": [
                        "confused"
                    ],
                    "stress": 0
                },
                "device": {
                    "manufacturer": "Sony",
                    "model": "E5603",
                    "os": "6.0",
                    "type": "android"
                },
                "heartrate": 78,
                "measurement_timestamp": 1478260462834,
                "af": 0.10810810810810789,
                "indicator": "urgent",
                "diagnosis": {
                    "text": "",
                    "label": [
                        "atrial_fibrillation"
                    ]
                },
                "extractionStatus": "finished",
                "extractionStatusChangedTime": 1554121926163,
                "extractedFileToken": "5ca204c534922758985a8299-db481a02-577f-40ac-82c5-b791bdd22624",
                "tags": []
            },
            "statusChangedTimestamp": "2020-10-05T11:37:35.232Z",
            "updateTimestamp": "2023-02-06T11:01:10.378Z",
            "creationTimestamp": "2016-11-04T11:55:24.320Z",
            "commentCount": 1
        }
    ]
}
```

{% endtab %}
{% endtabs %}

This endpoint uses [RQL](https://github.com/persvr/rql) as a query language and always returns a list of documents.

* In a single call, by default 20 items are returned, the maximum number of items that can be fetched in a single API call is 50. You can customise the number of returned items using the `limit()` operator.
* If there are more than 50 measurements in your query, you have to use an offset to fetch more than the first. For example `limit(10,50)` will return 10 results, with an offset of 50.
* By default the API returns the results in **ascending order**, the oldest measurement is returned first. Use the `sort(-creationTimestamp)` operator to return results in descending order.

The following example queries give an overview of how to use this endpoint. For brevity, the full URL is omitted.

* Fetch the latest measurement\
  `/documents?limit(1)&sort(-creationTimestamp)`
* Fetch the latest 50 measurements\
  `/documents?limit(50)&sort(-creationTimestamp)`
* Fetch the next 50 measurements\
  `/documents?limit(50,50)&sort(-creationTimestamp)`
* Fetch measurements where the analysis failed\
  `/documents?eq(status,analysis_failed)`
* Fetch measurement by id\
  `/documents?eq(id,{id})` or the shorthand `/documents?id={id}`

{% hint style="info" %}
**App Development Best Practice**\
By default, a registered user with no additional permissions will be able to only fetch his own measurements through the API. However, if at some point the user receives additional permissions, for example when the user is a doctor or nurse, that might change. \
Therefore it's strongly advised to always filter on `creatorId` using the following RQL query:`/documents?eq(creatorId,{userId})`
{% endhint %}

## Deleting a measurement

It is also possible to remove measurements that you are entitled to. To do this, you need to be a staff member of at least one group of the recorded measurement.

### REST API

## Delete a measurement

<mark style="color:red;">`DELETE`</mark> `https://api.fibricheck.com/data/v1/fibricheck-measurements/documents/{measurementId}`

{% tabs %}
{% tab title="200: OK " %}

{% endtab %}
{% endtabs %}

## Implementation Details

### CameraData Schema

The Camera SDK is the workhorse for generating the measurement data. After a PPG measurement has been completed, the Camera SDK will emit an `onMeasurementProcessed` event with a `CameraData` object that contains all data to process the the measurement.

The complete structure is exported as a `Measurement` type in the SDK. The `CameraData` object is explained here for informational purposes, the Camera SDK will populate all the fields.&#x20;

{% tabs %}
{% tab title="Flutter" %}

```dart
class CameraData {
  acc?: MotionData;
  rotation?: MotionData;
  grav?: MotionData;
  gyro?: MotionData;
  heartrate: num;
  measurement_timestamp: num;
  quadrants: Yuv[][];
  technical_details: {
    camera_exposure_time: num;
    camera_hardware_level: String;
    camera_iso: num;
  };
  time: num[];
  yList: num[];
  abnormalities?: Abnormalities[];
  attempts?: num;
  skippedPulseDetection: bool;
  skippedFingerDetection: bool;
  skippedMovementDetection: bool;
}
```

This structure can be used for creating `MeasurementCreationData,` which can be posted to our backend via the `postMeasurement` method

```dart
Future _onMeasurementProcessed(String measurementString) async {
  var json = jsonDecode(measurementString);
  var mCreationData = MeasurementCreationData.fromJson(json);

  await sdk.postMeasurement(mCreationData, "v0.0.1");
}
```

{% endtab %}

{% tab title="React Native" %}
This snippet shows the interface that is implemented when performing a measurement via the `react-native-camera-sdk`. This way, you don't have to worry about populating these fields.

```typescript
interface CameraData {
  acc?: MotionData;
  rotation?: MotionData;
  grav?: MotionData;
  gyro?: MotionData;
  heartrate: number;
  measurement_timestamp: number;
  quadrants: Yuv[][];
  technical_details: {
    camera_exposure_time: number;
    camera_hardware_level: string;
    camera_iso: number;
  };
  time: number[];
  yList: number[];
  abnormalities?: Abnormalities[];
  attempts?: number;
  skippedPulseDetection: boolean;
  skippedFingerDetection: boolean;
  skippedMovementDetection: boolean;
}
```

This structure can be used for creating `MeasurementCreationData,` which can be posted to our backend via the `sdk.postMeasurement()`
{% endtab %}

{% tab title="Cordova" %}
The `onMeasurementProcessed` event contains a `data` object with the results of the measurement. It has the following structure:

```json
{
    "acc": {
        "x": [],
        "y": [],
        "z": []
    },
    "attempts": 1,
    "grav": {
        "x": [],
        "y": [],
        "z": []
    },
    "gyro": {
        "x": [],
        "y": [],
        "z": []
    },
    "heartrate": 64,
    "measurement_timestamp": 1696239824855,
    "quadrants": [
        [
            {
                "u": [],
                "v": [],
                "y": []
            },
            {
                "u": [],
                "v": [],
                "y": []
            },
            {
                "u": [],
                "v": [],
                "y": []
            },
            {
                "u": [],
                "v": [],
                "y": []
            }
        ],
        [
            {
                "u": [],
                "v": [],
                "y": []
            },
            {
                "u": [],
                "v": [],
                "y": []
            },
            {
                "u": [],
                "v": [],
                "y": []
            },
            {
                "u": [],
                "v": [],
                "y": []
            }
        ]
    ],
    "rotation": {
        "x": [],
        "y": [],
        "z": []
    },
    "skippedFingerDetection": false,
    "skippedMovementDetection": false,
    "skippedPulseDetection": false,
    "technical_details": {
        "camera_hardware_level": "camera2 - limited",
        "camera_resolution": "176x144"
    },
    "time": []
}
```

**All the fields mentioned in this object are automatically filled in by the Camera SDK.** This object must be augmented with data as mentioned in the section "Post a new measurement"
{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.fibricheck.com/introduction/measurements.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
