# Fryers

> The fryers and their oil quality checks. Two collections, and why the measured value can be missing while the decision never is.

Source: https://developers.backresto.com/en/docs/fryers

Frying oil degrades as it is used, and past a point it has to be filtered or
thrown away. The restaurant tests it, decides what to do, and records both.
Two collections: the fryers, and the checks made on them.

| Collection | Scope | What a record is |
| --- | --- | --- |
| `fryer-equipment` | `fryer-equipment:read` | A fryer. |
| `fryer-checks` | `fryer-checks:read` | An oil quality check on one of them, and what was decided. |

Both share the [snapshot envelope](https://developers.backresto.com/en/docs/collections): `id`, `deleted`,
`capturedAt`, `receivedAt`, `sequence` and the rest sit beside the `data`
described below.

## `fryer-equipment`

The fryers themselves, as the restaurant lists them. A short record: a
name, a position in the list, and where it stands.

| Field | Type | Meaning |
| --- | --- | --- |
| `index` | integer, **required** | The fryer's position in the list, as the app orders it. |
| `name` | string, **required** | What the restaurant calls it. |
| `areaId` | string \| null | The [area](https://developers.backresto.com/en/docs/facilities) it stands in. `null` on a restaurant that does not split itself into areas. |
| `isDeleted` | boolean, **required** | The app's own soft delete. Read the note below. |

```json
{
  "id": "6b1d0f54-8e27-4c93-a7b0-52fd3e1c9a86",
  "collection": "fryer-equipment",
  "deleted": false,
  "capturedAt": "1789598411206",
  "receivedAt": "1789598603471",
  "sequence": "1",
  "data": {
    "index": 1,
    "name": "Friteuse gauche",
    "areaId": "7d2c0b61-4e8a-49f3-9c27-5a1b8e60d3f4",
    "isDeleted": false
  }
}
```

**There are no thresholds here.** Unlike [equipment](https://developers.backresto.com/en/docs/facilities), a fryer
carries no `min` and no `max`: nothing on the record says what a good oil
reading is. Whether a check was acceptable is the restaurant's judgement, and
the only trace of it is the action they took.

**`isDeleted` is not the envelope's `deleted`.** A fryer the restaurant removed
comes back with `isDeleted: true` and `deleted: false`: still a live record,
describing a fryer that is no longer in service. Filter on both, and keep the
removed ones. Old checks still point at them.

## `fryer-checks`

One check on one fryer: what the test showed, and what was done about it.

| Field | Type | Meaning |
| --- | --- | --- |
| `timestamp` | string, **required** | When the check was made, epoch milliseconds as a string. |
| `testValue` | string \| null | What the test showed, as text. `null` when nothing was measured. |
| `action` | string, **required** | What was decided about the oil: filtering it, changing it, leaving it as it is. |
| `fryerEquipmentId` | string, **required** | The fryer that was checked. |
| `userId` | string \| null | The [staff member](https://developers.backresto.com/en/docs/facilities) who made the check, when the app recorded one. |

```json
{
  "id": "e07a5c92-4f61-48b8-9d3a-b18c60e5427f",
  "collection": "fryer-checks",
  "deleted": false,
  "capturedAt": "1789777348215",
  "receivedAt": "1789777510964",
  "sequence": "2",
  "data": {
    "timestamp": "1789777200000",
    "testValue": "18",
    "action": "Filtration de l'huile",
    "fryerEquipmentId": "6b1d0f54-8e27-4c93-a7b0-52fd3e1c9a86",
    "userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
  }
}
```

**`testValue` is a string, not a number.** It is what the strip or the probe
showed, recorded as text, and the record carries no unit and no scale. A
restaurant using test strips and one using a polar-compound meter both land
here. Keep it as text. Parsing it into a float across restaurants gives you a
column whose values do not compare.

**`action` is free text** in the language of the restaurant. The schema does
not constrain it, so read the value rather than matching it against a list of
your own, and do not build an enum from what you see in one restaurant's
data.

## What a check without a value means

`testValue` is nullable and `action` is required, and that asymmetry is the
thing to design around. A check can be recorded as an action taken with no
measured value behind it: someone changed the oil because it looked wrong,
because it was the end of the week, because the fryer was being cleaned
anyway.

So a check is not a measurement. It is a decision, sometimes with a
measurement attached. If you are building a table of oil readings over time,
expect a good share of rows with nothing in the value column, and count them
rather than dropping them. An action with no value is still a check that
happened.

## Working with these

**Join on `fryerEquipmentId`.** A check names its fryer and nothing else; the
fryer's name is on `fryer-equipment`, which you walk separately. Two walks,
joined on your side.

**Report on `capturedAt`, sync on `receivedAt`.** A tablet uploads when it finds
signal, so a check made at 09:00 can arrive at 14:00.
[The envelope page](https://developers.backresto.com/en/docs/collections) has the full story.

**Absence proves nothing.** A fryer with no check this week may not have been
checked, or may be sitting on a device that has not uploaded. Do not turn a
count of these into a compliance figure. See
[the caveat](https://developers.backresto.com/en/docs/support).
