Kühlen & Lebensmittelprozesse

Kühlen, Gefrieren, Wiedererhitzen und Transport — vier Collections, die einen Temperaturwechsel aufzeichnen, und wie ein unvollendeter aussieht.

Ein heißes Gericht, das langsam abkühlt, verbringt Stunden in dem Bereich, in dem sich Bakterien vermehren; die Regel lautet also, es schnell herunterzubringen und das auch belegen zu können. Vier Collections zeichnen einen solchen Wechsel auf. Drei davon sind ein Wechsel in der Zeit — herunter, weiter herunter, wieder hinauf — und die vierte ist derselbe Gedanke im Raum.

CollectionBerechtigungWas ein Datensatz ist
coolingcooling:readEin heißes Produkt, das auf Kühllagertemperatur heruntergebracht wurde.
freezingfreezing:readEin Produkt, das bis zum Gefrieren heruntergebracht wurde.
reheatingreheating:readEin kaltes Produkt, das wieder auf Ausgabetemperatur gebracht wurde.
transporttransport:readEin Produkt, das von einem Ort zum anderen bewegt wurde, mit der Temperatur an beiden Enden.

Alle vier teilen sich den Snapshot-Umschlag: id, deleted, capturedAt, receivedAt, sequence und der Rest stehen neben dem data, das unten beschrieben ist.

cooling

Ein Zyklus: ein Produkt, wann er begann und wie heiß es war, wann er endete und wie kalt es war.

FeldTypBedeutung
timestampstring, PflichtDer Zeitstempel des Datensatzes selbst, als Zeichenkette mit Millisekunden seit der Epoche. Nicht die Uhr des Zyklus — das sind die beiden Daten darunter.
productstring, PflichtWas gekühlt wurde, als Text.
beginDatestring, PflichtWann der Zyklus begann, als Zeichenkette mit Millisekunden seit der Epoche.
beginTemperaturenumber, PflichtDie Temperatur zu Beginn.
endDatestring | nullWann er endete. null, solange er das nicht hat.
endTemperaturenumber | nullDie Temperatur am Ende. null zusammen mit endDate.
reminderIdstring | nullDie Erinnerung, die die App an den Zyklus gehängt hat, sofern es eine gab.
userIdstring | nullDer Mitarbeiter, der ihn erfasst hat, sofern die App einen erfasst hat.
{
  "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 ist ein Name, keine Referenz. Auf diesen Datensätzen gibt es kein productId, Sie können sie also nicht mit products verbinden, ohne Zeichenketten abzugleichen — und die Zeichenketten werden während des Betriebs getippt, in der Sprache des Restaurants.

reminderId ist undurchsichtig. Keine Collection dieser API löst es auf. Führen Sie es mit, wenn es Ihnen beim Gruppieren von Datensätzen hilft; erwarten Sie nicht, es nachschlagen zu können.

freezing

Dieselbe Struktur, für ein Produkt, das bis zum Gefrieren heruntergebracht wurde statt auf Kühlschranktemperatur. Feld für Feld ist es cooling.

FeldTypBedeutung
timestampstring, PflichtDer Zeitstempel des Datensatzes selbst.
productstring, PflichtWas gefroren wurde.
beginDatestring, PflichtWann der Vorgang begann.
beginTemperaturenumber, PflichtDie Temperatur zu Beginn.
endDatestring | nullWann er endete, oder null.
endTemperaturenumber | nullDie Temperatur am Ende, oder null.
reminderIdstring | nullWie oben.
userIdstring | nullWer ihn erfasst hat.

Nichts im Datensatz sagt, welcher der drei Fälle vorliegt: Das ist die Collection, aus der er stammt, und das Feld collection im Umschlag spiegelt sie. Eine Pipeline, die alle drei in eine Tabelle zusammenführt, muss diese Spalte behalten.

reheating

Ein kaltes Produkt, das wieder hinaufgebracht wird. Wieder dieselbe Struktur, in der anderen Richtung gelesen: beginTemperature ist der niedrige Wert und endTemperature der hohe.

FeldTypBedeutung
timestampstring, PflichtDer Zeitstempel des Datensatzes selbst.
productstring, PflichtWas wiedererhitzt wurde.
beginDatestring, PflichtWann es hineinkam.
beginTemperaturenumber, PflichtDie Temperatur zu Beginn.
endDatestring | nullWann es herauskam, oder null.
endTemperaturenumber | nullDie Temperatur, die es erreicht hat, oder null.
reminderIdstring | nullWie oben.
userIdstring | nullWer es erfasst hat.

Ein Vorgang ohne Ende sieht so aus:

{
  "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

Derselbe Wechsel, im Raum statt in der Zeit: wo das Produkt losgefahren ist und wo es angekommen ist, mit einer Temperatur an jedem Ende.

FeldTypBedeutung
timestampstring, PflichtDer Zeitstempel des Datensatzes selbst.
productstring, PflichtWas transportiert wurde, als Text.
departureLocationstring, PflichtWo es losging, als Text.
departureTimestring, PflichtWann es losging, als Zeichenkette mit Millisekunden seit der Epoche.
departureTemperaturenumber, PflichtDie Temperatur beim Abgang.
arrivalLocationstring | nullWo es ankam. null, solange es das nicht ist.
arrivalTimestring | nullWann es ankam, oder null.
arrivalTemperaturenumber | nullDie Temperatur bei der Ankunft, oder null.
reminderIdstring | nullWie oben.
userIdstring | nullWer es erfasst hat.
{
  "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"
  }
}

Die beiden Orte sind Freitext, keine Bereiche und keine Adressen. Sie sind, was die Person getippt hat, und die Ankunftsseite ist genau so optional wie die Ankunftstemperatur: Ein Datensatz kann einen Abgang tragen und sonst nichts.

Damit arbeiten

Ein leeres Ende ist zweierlei. endDate und endTemperature — und arrivalTime, arrivalTemperature und arrivalLocation bei transport — dürfen null sein, und der Datensatz sagt Ihnen nicht, warum. Es kann ein Vorgang sein, der noch läuft, während Sie ihn lesen, oder einer, den nie jemand abgeschlossen hat. Beide sehen gleich aus.

Das zählt in dem Moment, in dem Sie eine Dauer berechnen. Die Daten sind Zeichenketten, endDate - beginDate subtrahiert bei einem abgeschlossenen Datensatz also sauber und wandelt bei einem offenen null in Null um und gibt Ihnen eine große negative Zahl statt eines Fehlers. Nichts wirft eine Ausnahme; Ihr Mittelwert ist einfach falsch. Filtern Sie zuerst auf abgeschlossene Datensätze, zählen Sie die offenen getrennt und entscheiden Sie selbst, ab welchem Alter ein offener Datensatz aufhört, in Arbeit zu sein, und anfängt, liegen geblieben zu sein — nichts in dieser API trifft diese Entscheidung für Sie.

Rechnen Sie mit beginDate und endDate, nicht mit timestamp. Die Daten sind die Uhr des Vorgangs. timestamp ist die des Datensatzes selbst, und daraus wird keine Dauer gemacht.

Ordnen Sie nach capturedAt, nicht nach receivedAt. Ein Tablet in einem Kühlraum lädt hoch, sobald es Empfang findet; ein Zyklus, der um 09:00 Uhr begonnen hat, kann also um 14:00 Uhr ankommen. Die Seite zum Umschlag erzählt die ganze Geschichte.

Abwesenheit beweist nichts. Ein Produkt ohne Kühldatensatz wurde vielleicht nie gekühlt oder liegt auf einem Gerät, das noch nicht hochgeladen hat. Machen Sie aus einer Anzahl davon keine Compliance-Kennzahl — siehe die Anmerkung zur Vollständigkeit.

Zuletzt aktualisiert am 2026-09-20.