Raffreddamento e processi alimentari
Raffreddamento, congelamento, riscaldamento e trasporto — quattro collezioni che registrano una transizione di temperatura, e che aspetto ha quella rimasta incompiuta.
Un piatto caldo che si raffredda lentamente passa ore nell'intervallo in cui i batteri si moltiplicano, quindi la regola è abbassarne la temperatura in fretta e poterlo dimostrare. Quattro collezioni registrano questo tipo di transizione. Tre sono una transizione nel tempo — giù, ancora più giù, di nuovo su — e la quarta è la stessa idea nello spazio.
| Collezione | Ambito | Che cos'è un record |
|---|---|---|
cooling | cooling:read | Un prodotto caldo portato alla temperatura di conservazione a freddo. |
freezing | freezing:read | Un prodotto portato fino al congelamento. |
reheating | reheating:read | Un prodotto freddo riportato alla temperatura di servizio. |
transport | transport:read | Un prodotto spostato da un luogo a un altro, con la temperatura a ciascuna estremità. |
Tutte e quattro condividono l'involucro di istantanea:
id, deleted, capturedAt, receivedAt, sequence e gli altri stanno
accanto al data descritto qui sotto.
cooling
Un ciclo: un prodotto, quando è iniziato e quanto era caldo, quando è terminato e quanto era freddo.
| Campo | Tipo | Significato |
|---|---|---|
timestamp | stringa, obbligatorio | La marca temporale del record stesso, millisecondi dall'epoch come stringa. Non l'orologio del ciclo — quello sono le due date qui sotto. |
product | stringa, obbligatorio | Cosa è stato raffreddato, come testo. |
beginDate | stringa, obbligatorio | Quando è iniziato il ciclo, millisecondi dall'epoch come stringa. |
beginTemperature | numero, obbligatorio | La temperatura di partenza. |
endDate | stringa | null | Quando è terminato. null finché non lo è. |
endTemperature | numero | null | La temperatura di arrivo. null insieme a endDate. |
reminderId | stringa | null | Il promemoria che l'app ha associato al ciclo, quando ce n'era uno. |
userId | stringa | null | Il membro del personale che l'ha registrato, quando l'app ne ha registrato uno. |
{
"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 è un nome, non un riferimento. Su questi record non esiste alcun
productId, quindi non può unirli a products senza confrontare stringhe — e
le stringhe vengono digitate durante il servizio, nella lingua del ristorante.
reminderId è opaco. Nessuna collezione di questa API lo risolve. Se le
serve per raggruppare i record se lo porti dietro; non si aspetti di poterlo
consultare.
freezing
La stessa forma, per un prodotto portato al congelamento anziché alla
temperatura del frigorifero. Campo per campo, è cooling.
| Campo | Tipo | Significato |
|---|---|---|
timestamp | stringa, obbligatorio | La marca temporale del record stesso. |
product | stringa, obbligatorio | Cosa è stato congelato. |
beginDate | stringa, obbligatorio | Quando è iniziata l'operazione. |
beginTemperature | numero, obbligatorio | La temperatura di partenza. |
endDate | stringa | null | Quando è terminata, oppure null. |
endTemperature | numero | null | La temperatura di arrivo, oppure null. |
reminderId | stringa | null | Come sopra. |
userId | stringa | null | Chi l'ha registrata. |
Niente nel record dice quale delle tre sia: lo dice la collezione da cui
proviene, e il campo collection dell'involucro lo ripete. Una pipeline che
unisce tutte e tre in un'unica tabella deve conservare quella colonna.
reheating
Un prodotto freddo riportato su. Di nuovo la stessa forma, letta nell'altra
direzione: beginTemperature è quella bassa ed endTemperature quella alta.
| Campo | Tipo | Significato |
|---|---|---|
timestamp | stringa, obbligatorio | La marca temporale del record stesso. |
product | stringa, obbligatorio | Cosa è stato riscaldato. |
beginDate | stringa, obbligatorio | Quando è stato messo dentro. |
beginTemperature | numero, obbligatorio | La temperatura di partenza. |
endDate | stringa | null | Quando è stato tirato fuori, oppure null. |
endTemperature | numero | null | La temperatura raggiunta, oppure null. |
reminderId | stringa | null | Come sopra. |
userId | stringa | null | Chi l'ha registrato. |
Un'operazione senza fine si presenta così:
{
"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
La stessa transizione, nello spazio anziché nel tempo: da dove il prodotto è partito e dove è arrivato, con una temperatura a ciascuna estremità.
| Campo | Tipo | Significato |
|---|---|---|
timestamp | stringa, obbligatorio | La marca temporale del record stesso. |
product | stringa, obbligatorio | Cosa è stato trasportato, come testo. |
departureLocation | stringa, obbligatorio | Da dove è partito, come testo. |
departureTime | stringa, obbligatorio | Quando è partito, millisecondi dall'epoch come stringa. |
departureTemperature | numero, obbligatorio | La temperatura alla partenza. |
arrivalLocation | stringa | null | Dove è arrivato. null finché non è arrivato. |
arrivalTime | stringa | null | Quando è arrivato, oppure null. |
arrivalTemperature | numero | null | La temperatura all'arrivo, oppure null. |
reminderId | stringa | null | Come sopra. |
userId | stringa | null | Chi l'ha registrato. |
{
"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"
}
}
I due luoghi sono testo libero, non sono aree e non sono indirizzi. Sono quello che la persona ha digitato, e il lato dell'arrivo è opzionale esattamente quanto la temperatura di arrivo: un record può portare una partenza e nient'altro.
Lavorare con questi dati
Una fine nulla è due cose diverse. endDate ed endTemperature — e
arrivalTime, arrivalTemperature e arrivalLocation su transport —
ammettono null, e il record non le dice perché. Può trattarsi di
un'operazione ancora in corso mentre la legge, oppure di una che nessuno ha
mai chiuso. Le due sono identiche a vedersi.
La cosa conta nel momento in cui calcola una durata. Le date sono stringhe,
quindi endDate - beginDate sottrae senza problemi su un record chiuso e, su
uno aperto, converte null in zero e le restituisce un numero negativo molto
grande anziché un errore. Non viene sollevata alcuna eccezione; semplicemente
la sua media è sbagliata. Filtri prima sui record chiusi, conti quelli aperti a
parte, e decida lei a quale età un record aperto smette di essere in corso e
comincia a essere abbandonato — niente in questa API prende quella decisione al
posto suo.
Calcoli su beginDate ed endDate, non su timestamp. Le date sono
l'orologio dell'operazione. timestamp è quello del record, e non è la cosa di
cui è fatta una durata.
Ordini su capturedAt, non su receivedAt. Un tablet in una cella
frigorifera carica i dati quando trova segnale, quindi un ciclo iniziato alle
09:00 può arrivare alle 14:00.
La pagina sull'involucro racconta tutta la storia.
L'assenza non prova nulla. Un prodotto senza alcun record di raffreddamento può non essere mai stato raffreddato, oppure può trovarsi su un dispositivo che non ha ancora caricato i dati. Non trasformi un conteggio di questi record in un dato di conformità — veda l'avvertenza.