Cooling & food processes
Cooling, freezing, reheating and transport — four collections recording a temperature transition, and what an unfinished one looks like.
A hot dish that cools slowly spends hours in the range where bacteria multiply, so the rule is to bring it down fast and to be able to show you did. Four collections record that kind of transition. Three of them are a transition in time — down, down further, back up — and the fourth is the same idea in space.
| Collection | Scope | What a record is |
|---|---|---|
cooling | cooling:read | A hot product brought down to cold-storage temperature. |
freezing | freezing:read | A product taken down to frozen. |
reheating | reheating:read | A cold product brought back up to service temperature. |
transport | transport:read | A product moved from one place to another, with the temperature at each end. |
All four share the snapshot envelope: id, deleted,
capturedAt, receivedAt, sequence and the rest sit beside the data
described below.
cooling
One cycle: a product, when it started and how hot it was, when it finished and how cold it was.
| Field | Type | Meaning |
|---|---|---|
timestamp | string, required | The record's own stamp, epoch milliseconds as a string. Not the cycle's clock — that is the two dates below. |
product | string, required | What was cooled, as text. |
beginDate | string, required | When the cycle started, epoch milliseconds as a string. |
beginTemperature | number, required | The temperature it started at. |
endDate | string | null | When it finished. null while it has not. |
endTemperature | number | null | The temperature it finished at. null alongside endDate. |
reminderId | string | null | The reminder the app attached to the cycle, when there was one. |
userId | string | null | The staff member who recorded it, when the app recorded one. |
{
"id": "0e5d1a7c-9f24-4b13-8d60-c1a7f0b25e94",
"collection": "cooling",
"deleted": false,
"capturedAt": "1789779612441",
"receivedAt": "1789779910228",
"sequence": "1",
"data": {
"timestamp": "1789772400000",
"product": "Blanquette de veau",
"beginDate": "1789772400000",
"beginTemperature": 63.5,
"endDate": "1789779600000",
"endTemperature": 8.2,
"userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
}
}
product is a name, not a reference. There is no productId on these
records, so you cannot join them to products without matching strings — and
the strings are typed during service, in the language of the restaurant.
reminderId is opaque. No collection in this API resolves it. Carry it if
it helps you group records; do not expect to look it up.
freezing
The same shape, for a product taken down to frozen rather than to fridge
temperature. Field for field, it is cooling.
| Field | Type | Meaning |
|---|---|---|
timestamp | string, required | The record's own stamp. |
product | string, required | What was frozen. |
beginDate | string, required | When the operation started. |
beginTemperature | number, required | The temperature it started at. |
endDate | string | null | When it finished, or null. |
endTemperature | number | null | The temperature it finished at, or null. |
reminderId | string | null | As above. |
userId | string | null | Who recorded it. |
Nothing in the record says which of the three it is: that is the collection it
came from, and the envelope's collection field echoes it. A pipeline that
merges all three into one table needs to keep that column.
reheating
A cold product brought back up. Same shape again, read in the other direction:
beginTemperature is the low one and endTemperature the high one.
| Field | Type | Meaning |
|---|---|---|
timestamp | string, required | The record's own stamp. |
product | string, required | What was reheated. |
beginDate | string, required | When it went in. |
beginTemperature | number, required | The temperature it started at. |
endDate | string | null | When it came out, or null. |
endTemperature | number | null | The temperature it reached, or null. |
reminderId | string | null | As above. |
userId | string | null | Who recorded it. |
An operation with no end looks like this:
{
"id": "b4c8e21d-3a76-4f58-9c02-77ad5e1b8f30",
"collection": "reheating",
"deleted": false,
"capturedAt": "1789783221907",
"receivedAt": "1789783402115",
"sequence": "2",
"data": {
"timestamp": "1789783200000",
"product": "Gratin dauphinois",
"beginDate": "1789783200000",
"beginTemperature": 4.1,
"endDate": null,
"endTemperature": null,
"userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
}
}
transport
The same transition, in space rather than in time: where the product left from and where it arrived, with a temperature at each end.
| Field | Type | Meaning |
|---|---|---|
timestamp | string, required | The record's own stamp. |
product | string, required | What was transported, as text. |
departureLocation | string, required | Where it left from, as text. |
departureTime | string, required | When it left, epoch milliseconds as a string. |
departureTemperature | number, required | The temperature on departure. |
arrivalLocation | string | null | Where it arrived. null while it has not. |
arrivalTime | string | null | When it arrived, or null. |
arrivalTemperature | number | null | The temperature on arrival, or null. |
reminderId | string | null | As above. |
userId | string | null | Who recorded it. |
{
"id": "f31b6a08-5c47-4de9-b1a5-6e0c9d247b82",
"collection": "transport",
"deleted": false,
"capturedAt": "1789786944610",
"receivedAt": "1789787101883",
"sequence": "3",
"data": {
"timestamp": "1789785000000",
"product": "Plats préparés — service traiteur",
"departureLocation": "Cuisine centrale",
"departureTime": "1789785000000",
"departureTemperature": 3.4,
"arrivalLocation": "Salle Montparnasse",
"arrivalTime": "1789786800000",
"arrivalTemperature": 5.9,
"userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
}
}
The two locations are free text, not areas and not addresses. They are whatever the person typed, and the arrival end is exactly as optional as the arrival temperature: one record can carry a departure and nothing else.
Working with these
A null end is two different things. endDate and endTemperature — and
arrivalTime, arrivalTemperature and arrivalLocation on transport — are
nullable, and the record does not tell you why. It may be an operation still
running as you read it, or one nobody ever closed. Both look identical.
That matters the moment you compute a duration. The dates are strings, so
endDate - beginDate subtracts fine on a closed record and, on an open one,
coerces null to zero and hands you a large negative number instead of an
error. Nothing throws; your average is simply wrong. Filter to closed records
first, count the open ones separately, and decide for yourself at what age an
open record stops being in progress and starts being abandoned — nothing in
this API makes that call for you.
Compute on beginDate and endDate, not on timestamp. The dates are the
operation's clock. timestamp is the record's own, and it is not the thing a
duration is made of.
Order on capturedAt, not receivedAt. A tablet in a cold room uploads
when it finds signal, so a cycle started at 09:00 can arrive at 14:00.
The envelope page has the full story.
Absence proves nothing. A product with no cooling record may never have been cooled, or may be sitting on a device that has not uploaded yet. Do not turn a count of these into a compliance figure — see the caveat.