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.

CollezioneAmbitoChe cos'è un record
coolingcooling:readUn prodotto caldo portato alla temperatura di conservazione a freddo.
freezingfreezing:readUn prodotto portato fino al congelamento.
reheatingreheating:readUn prodotto freddo riportato alla temperatura di servizio.
transporttransport:readUn 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.

CampoTipoSignificato
timestampstringa, obbligatorioLa marca temporale del record stesso, millisecondi dall'epoch come stringa. Non l'orologio del ciclo — quello sono le due date qui sotto.
productstringa, obbligatorioCosa è stato raffreddato, come testo.
beginDatestringa, obbligatorioQuando è iniziato il ciclo, millisecondi dall'epoch come stringa.
beginTemperaturenumero, obbligatorioLa temperatura di partenza.
endDatestringa | nullQuando è terminato. null finché non lo è.
endTemperaturenumero | nullLa temperatura di arrivo. null insieme a endDate.
reminderIdstringa | nullIl promemoria che l'app ha associato al ciclo, quando ce n'era uno.
userIdstringa | nullIl 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.

CampoTipoSignificato
timestampstringa, obbligatorioLa marca temporale del record stesso.
productstringa, obbligatorioCosa è stato congelato.
beginDatestringa, obbligatorioQuando è iniziata l'operazione.
beginTemperaturenumero, obbligatorioLa temperatura di partenza.
endDatestringa | nullQuando è terminata, oppure null.
endTemperaturenumero | nullLa temperatura di arrivo, oppure null.
reminderIdstringa | nullCome sopra.
userIdstringa | nullChi 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.

CampoTipoSignificato
timestampstringa, obbligatorioLa marca temporale del record stesso.
productstringa, obbligatorioCosa è stato riscaldato.
beginDatestringa, obbligatorioQuando è stato messo dentro.
beginTemperaturenumero, obbligatorioLa temperatura di partenza.
endDatestringa | nullQuando è stato tirato fuori, oppure null.
endTemperaturenumero | nullLa temperatura raggiunta, oppure null.
reminderIdstringa | nullCome sopra.
userIdstringa | nullChi 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à.

CampoTipoSignificato
timestampstringa, obbligatorioLa marca temporale del record stesso.
productstringa, obbligatorioCosa è stato trasportato, come testo.
departureLocationstringa, obbligatorioDa dove è partito, come testo.
departureTimestringa, obbligatorioQuando è partito, millisecondi dall'epoch come stringa.
departureTemperaturenumero, obbligatorioLa temperatura alla partenza.
arrivalLocationstringa | nullDove è arrivato. null finché non è arrivato.
arrivalTimestringa | nullQuando è arrivato, oppure null.
arrivalTemperaturenumero | nullLa temperatura all'arrivo, oppure null.
reminderIdstringa | nullCome sopra.
userIdstringa | nullChi 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.

Ultimo aggiornamento 2026-09-20.