冷却与食品处理流程

冷却、冷冻、复热与运输——记录一次温度转变的四个集合,以及一次没做完的转变长什么样。

一道慢慢冷下来的热菜,会在细菌繁殖的那个温度区间里待上好几个小时,所以规矩是把 它快速降下来,并且能拿得出证据说你做了。有四个集合记录这类转变。其中三个是时间 上的转变——降下来、再降下去、又升回去——第四个则是同一个想法,发生在空间上。

集合权限范围一条记录是什么
coolingcooling:read一件被降到冷藏温度的热产品。
freezingfreezing:read一件被降到冷冻状态的产品。
reheatingreheating:read一件被升回供餐温度的冷产品。
transporttransport:read一件从一处被运到另一处的产品,带着两端各自的温度。

这四个共用同一个快照信封iddeletedcapturedAtreceivedAtsequence 以及其余字段,都摆在下文描述的 data 旁边。

cooling

一次循环:一件产品,什么时候开始、当时有多热,什么时候结束、当时有多冷。

字段类型含义
timestampstring, 必填这条记录自己的时间戳,以字符串表示的 Unix 毫秒时间戳。不是这次循环的时钟——那是下面那两个日期。
productstring, 必填被冷却的是什么,以文本记录。
beginDatestring, 必填这次循环什么时候开始,以字符串表示的 Unix 毫秒时间戳。
beginTemperaturenumber, 必填它开始时的温度。
endDatestring | null它什么时候结束。还没结束时为 null
endTemperaturenumber | null它结束时的温度。与 endDate 一同为 null
reminderIdstring | null应用挂在这次循环上的那个提醒,在有一个的时候。
userIdstring | null记录它的那名员工,在应用记下了一个的时候。
{
  "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 是一个名字,不是一个引用。 这些记录上没有 productId,所以你没法在 不做字符串匹配的情况下把它们连到 products 上——而这些字符串是在营业中、用这家 餐厅的语言打出来的。

reminderId 是不透明的。 这个 API 里没有任何集合能解开它。如果它有助于你把 记录分组,就带着它;但别指望能查到它指向什么。

freezing

同样的结构,用于一件被降到冷冻、而不是降到冷藏温度的产品。逐字段看,它就是 cooling

字段类型含义
timestampstring, 必填这条记录自己的时间戳。
productstring, 必填被冷冻的是什么。
beginDatestring, 必填这次操作什么时候开始。
beginTemperaturenumber, 必填它开始时的温度。
endDatestring | null它什么时候结束,或者 null
endTemperaturenumber | null它结束时的温度,或者 null
reminderIdstring | null同上。
userIdstring | null是谁记录的。

记录里没有任何东西说它是这三者中的哪一个:那取决于它来自哪个集合,而信封的 collection 字段会把它复述出来。一条把三者并进同一张表的流水线,必须把那一列 留住。

reheating

一件被升回来的冷产品。结构还是那个,只是读的方向反过来:beginTemperature 是低 的那个,endTemperature 是高的那个。

字段类型含义
timestampstring, 必填这条记录自己的时间戳。
productstring, 必填被复热的是什么。
beginDatestring, 必填它什么时候进去的。
beginTemperaturenumber, 必填它开始时的温度。
endDatestring | null它什么时候出来的,或者 null
endTemperaturenumber | null它达到的温度,或者 null
reminderIdstring | null同上。
userIdstring | null是谁记录的。

一次没有结尾的操作长这样:

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

同一种转变,发生在空间而不是时间上:产品从哪里出发、到达了哪里,两端各有一个 温度。

字段类型含义
timestampstring, 必填这条记录自己的时间戳。
productstring, 必填被运输的是什么,以文本记录。
departureLocationstring, 必填它从哪里出发,以文本记录。
departureTimestring, 必填它什么时候出发,以字符串表示的 Unix 毫秒时间戳。
departureTemperaturenumber, 必填出发时的温度。
arrivalLocationstring | null它到达了哪里。还没到达时为 null
arrivalTimestring | null它什么时候到达,或者 null
arrivalTemperaturenumber | null到达时的温度,或者 null
reminderIdstring | null同上。
userIdstring | null是谁记录的。
{
  "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"
  }
}

这两个地点是自由文本,既不是区域,也不是地址。它们就是那个人 打进去的东西;而到达那一端的可选程度,和到达温度完全一样:一条记录可以只带着一次 出发,别的什么都没有。

拿它们做事

一个为 null 的结尾,是两件不同的事。 endDateendTemperature——以及 transport 上的 arrivalTimearrivalTemperaturearrivalLocation——都可以 为 null,而记录不会告诉你是为什么。可能是一次在你读它的时候仍在进行的操作,也可能 是一次从来没有人去收尾的操作。两者看起来一模一样。

这件事在你要算时长的那一刻就变得要紧。这些日期是字符串,所以 endDate - beginDate 在一条已结束的记录上减得好好的,而在一条未结束的记录上,它 会把 null 强制转成零,然后交给你一个很大的负数,而不是一个错误。没有任何东西 抛出异常;你的 平均值只是错的。先筛出已结束的记录,把未结束的单独计数,并且自己决定一条未结束的 记录到了多久就不再算进行中、而开始算被丢下了——这个 API 里没有任何东西会替你做 这个判断。

beginDateendDate 来算,而不是 timestamp 这两个日期是这次操作的 时钟。timestamp 是这条记录自己的时钟,而一段时长不是用它做出来的。

capturedAt 排序,而不是 receivedAt 一台放在冷库里的平板会在找到信号时 才上传,所以一次 09:00 开始的循环可能 14:00 才到。 信封那一页讲了完整的来龙去脉。

缺席证明不了什么。 一件没有冷却记录的产品,可能从来没被冷却过,也可能正躺在 一台还没上传的设备里。不要把这类记录的计数变成一个合规数字——见 那条提醒

最后更新于 2026-09-20。