炸锅
那些炸锅和在它们上面做的油品质量检查——两个集合,以及为什么测得的值可以缺失、而那个决定绝不会。
煎炸用油会在使用中劣化,过了某个点就必须过滤掉或者倒掉。餐厅对它做检测、决定怎么 办,并且把两者都记下来。两个集合:那些炸锅,以及在它们上面做的那些检查。
| 集合 | 权限范围 | 一条记录是什么 |
|---|---|---|
fryer-equipment | fryer-equipment:read | 一台炸锅。 |
fryer-checks | fryer-checks:read | 对其中一台做的一次油品质量检查,以及当时的决定。 |
这两个共用同一个快照信封:id、deleted、capturedAt、
receivedAt、sequence 以及其余字段,都摆在下文描述的 data 旁边。
fryer-equipment
炸锅本身,按餐厅把它们列出来的样子。一条很短的记录——一个名字、在列表里的一个位 置,以及它站在哪里。
| 字段 | 类型 | 含义 |
|---|---|---|
index | integer, 必填 | 这台炸锅在列表里的位置,按应用给它们排的顺序。 |
name | string, 必填 | 餐厅管它叫什么。 |
areaId | string | null | 它所在的那个区域。在一家不把自己划分成区域的餐厅里为 null。 |
isDeleted | boolean, 必填 | 应用自己的软删除。请读下面那条说明。 |
{
"id": "6b1d0f54-8e27-4c93-a7b0-52fd3e1c9a86",
"collection": "fryer-equipment",
"deleted": false,
"capturedAt": "1789598411206",
"receivedAt": "1789598603471",
"sequence": "1",
"data": {
"index": 1,
"name": "Friteuse gauche",
"areaId": "7d2c0b61-4e8a-49f3-9c27-5a1b8e60d3f4",
"isDeleted": false
}
}
这里没有阈值。 和设备不一样,一台炸锅既不带 min 也不带
max:记录上没有任何东西说一个好的油品读数是什么。一次检查是否可接受,是餐厅的
判断,而它留下的唯一痕迹,就是他们采取的那个行动。
isDeleted 不是信封上的 deleted。 一台被餐厅移走的炸锅,回来时是
isDeleted: true 且 deleted: false:仍然是一条活着的记录,描述的是一台已经不再
服役的炸锅。两个都要筛,并且把移走的那些留着——老的检查仍然指向它们。
fryer-checks
对一台炸锅做的一次检查:这次检测显示了什么,以及为此做了什么。
| 字段 | 类型 | 含义 |
|---|---|---|
timestamp | string, 必填 | 这次检查是什么时候做的,以字符串表示的 Unix 毫秒时间戳。 |
testValue | string | null | 检测显示了什么,以文本记录。什么都没测时为 null。 |
action | string, 必填 | 对这油做了什么决定——把它过滤掉、把它换掉、就这么留着。 |
fryerEquipmentId | string, 必填 | 被检查的那台炸锅。 |
userId | string | null | 做这次检查的那名员工,在应用记下了一个的时候。 |
{
"id": "e07a5c92-4f61-48b8-9d3a-b18c60e5427f",
"collection": "fryer-checks",
"deleted": false,
"capturedAt": "1789777348215",
"receivedAt": "1789777510964",
"sequence": "2",
"data": {
"timestamp": "1789777200000",
"testValue": "18",
"action": "Filtration de l'huile",
"fryerEquipmentId": "6b1d0f54-8e27-4c93-a7b0-52fd3e1c9a86",
"userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
}
}
testValue 是一个字符串,不是一个数字。 它是试纸或者探针显示出来的东西,以
文本记下,而记录既不带单位也不带刻度——用试纸的餐厅和用极性化合物检测仪的餐厅,
都落在这里。把它当文本留着。跨餐厅把它解析成浮点数,只会给你一列彼此之间无法比较
的值。
action 是自由文本,用的是这家餐厅的语言。schema 不对它做约束,所以请读这个
值,而不要拿它去比对一份你自己的清单——也不要拿你在一家餐厅的数据里看到的东西
建枚举。
一次没有值的检查意味着什么
testValue 可以为 null,而 action 是必填的,这个不对称正是你要围着它来设计的那
件事。一次检查可以被记成一个采取过的行动,背后却没有任何测得的值:有人把油换了,
因为它看起来不对劲,因为到周末了,因为反正那台炸锅要清洗了。
所以一次检查不是一次测量。它是一个决定,有时附带着一次测量。如果你在做一张油品 读数随时间变化的表,就要预料到有相当一部分行在值那一列里什么都没有,并且把它们 计入,而不是把它们丢掉——一个没有值的行动,仍然是一次确实发生过的检查。
拿它们做事
按 fryerEquipmentId 做连接。 一次检查只点出它的那台炸锅,别的什么都没有;
炸锅的名字在 fryer-equipment 上,那个你要单独遍历。两次遍历,在你这边做连接。
按 capturedAt 排序,而不是 receivedAt。 一台平板会在找到信号时才上传,所以
一次 09:00 做的检查可能 14:00 才到。信封那一页讲了完整的来龙
去脉。
缺席证明不了什么。 一台这一周没有检查的炸锅,可能确实没被检查过,也可能正躺在 一台还没上传的设备里。不要把这类记录的计数变成一个合规数字——见 那条提醒。