# Kühlen & Lebensmittelprozesse

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

Source: https://developers.backresto.com/de/docs/food-processes

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.

| Collection | Scope | Was ein Datensatz ist |
| --- | --- | --- |
| `cooling` | `cooling:read` | Ein heißes Produkt, das auf Kühllagertemperatur heruntergebracht wurde. |
| `freezing` | `freezing:read` | Ein Produkt, das bis zum Gefrieren heruntergebracht wurde. |
| `reheating` | `reheating:read` | Ein kaltes Produkt, das wieder auf Ausgabetemperatur gebracht wurde. |
| `transport` | `transport:read` | Ein Produkt, das von einem Ort zum anderen bewegt wurde, mit der Temperatur an beiden Enden. |

Alle vier teilen sich den [Snapshot-Umschlag](https://developers.backresto.com/de/docs/collections): `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.

| Feld | Typ | Bedeutung |
| --- | --- | --- |
| `timestamp` | string, **Pflicht** | Der Zeitstempel des Datensatzes selbst, als String mit Millisekunden seit Epoch. Nicht die Uhr des Zyklus: Das sind die beiden Daten darunter. |
| `product` | string, **Pflicht** | Was gekühlt wurde, als Text. |
| `beginDate` | string, **Pflicht** | Wann der Zyklus begann, als String mit Millisekunden seit Epoch. |
| `beginTemperature` | number, **Pflicht** | Die Temperatur zu Beginn. |
| `endDate` | string \| null | Wann er endete. `null`, solange er das nicht hat. |
| `endTemperature` | number \| null | Die Temperatur am Ende. `null` zusammen mit `endDate`. |
| `reminderId` | string \| null | Die Erinnerung, die die App an den Zyklus gehängt hat, sofern es eine gab. |
| `userId` | string \| null | Der [Mitarbeiter](https://developers.backresto.com/de/docs/facilities), der ihn erfasst hat, sofern die App einen erfasst hat. |

```json
{
  "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 Strings
abzugleichen, und diese Strings 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`.

| Feld | Typ | Bedeutung |
| --- | --- | --- |
| `timestamp` | string, **Pflicht** | Der Zeitstempel des Datensatzes selbst. |
| `product` | string, **Pflicht** | Was gefroren wurde. |
| `beginDate` | string, **Pflicht** | Wann der Vorgang begann. |
| `beginTemperature` | number, **Pflicht** | Die Temperatur zu Beginn. |
| `endDate` | string \| null | Wann er endete, oder `null`. |
| `endTemperature` | number \| null | Die Temperatur am Ende, oder `null`. |
| `reminderId` | string \| null | Wie oben. |
| `userId` | string \| null | Wer 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.

| Feld | Typ | Bedeutung |
| --- | --- | --- |
| `timestamp` | string, **Pflicht** | Der Zeitstempel des Datensatzes selbst. |
| `product` | string, **Pflicht** | Was wiedererhitzt wurde. |
| `beginDate` | string, **Pflicht** | Wann es hineinkam. |
| `beginTemperature` | number, **Pflicht** | Die Temperatur zu Beginn. |
| `endDate` | string \| null | Wann es herauskam, oder `null`. |
| `endTemperature` | number \| null | Die Temperatur, die es erreicht hat, oder `null`. |
| `reminderId` | string \| null | Wie oben. |
| `userId` | string \| null | Wer es erfasst hat. |

Ein Vorgang ohne Ende sieht so aus:

```json
{
  "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.

| Feld | Typ | Bedeutung |
| --- | --- | --- |
| `timestamp` | string, **Pflicht** | Der Zeitstempel des Datensatzes selbst. |
| `product` | string, **Pflicht** | Was transportiert wurde, als Text. |
| `departureLocation` | string, **Pflicht** | Wo es losging, als Text. |
| `departureTime` | string, **Pflicht** | Wann es losging, als String mit Millisekunden seit Epoch. |
| `departureTemperature` | number, **Pflicht** | Die Temperatur beim Abgang. |
| `arrivalLocation` | string \| null | Wo es ankam. `null`, solange es das nicht ist. |
| `arrivalTime` | string \| null | Wann es ankam, oder `null`. |
| `arrivalTemperature` | number \| null | Die Temperatur bei der Ankunft, oder `null`. |
| `reminderId` | string \| null | Wie oben. |
| `userId` | string \| null | Wer es erfasst hat. |

```json
{
  "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](https://developers.backresto.com/de/docs/facilities) 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
Strings: Bei einem abgeschlossenen Datensatz subtrahiert `endDate - beginDate`
sauber, bei einem offenen wird `null` zu 0, und Sie bekommen eine große negative
Zahl statt eines Fehlers. Nichts wirft eine Exception; 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.

**Für Berichte `capturedAt`, für den Abgleich `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](https://developers.backresto.com/de/docs/collections) 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](https://developers.backresto.com/de/docs/support)).
