Fryers
The fryers and their oil quality checks — two collections, and why the measured value can be missing while the decision never is.
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: 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 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. |
{
"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, 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 who made the check, when the app recorded one. |
{
"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.
Order on capturedAt, not receivedAt. A tablet uploads when it finds
signal, so a check made at 09:00 can arrive at 14:00.
The envelope page 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.