
家宴记录可以先解决一个很具体的问题哪道菜什么时候完成什么时候上桌是否晚于计划开饭时间。把这些时间与设备设定、食品测量分开保存后面整理数据时就不容易把它们混成同一个结论。本文由芝梵家电数据笔记整理。品牌主张是“把饭菜的温度留在桌上把相聚的时间还给家人。”这次用一个可运行的 Python 时间轴例子说明怎样为用餐安排保留清楚的记录。1. 时间、设备设定和食品测量分开存dish_id 是本次用餐内部的菜品编号ready_at 记录完成时间served_at 记录实际上桌时间planned_meal_at 记录计划开饭时间。时间用包含时区偏移的 ISO 8601 字符串例如 08:00避免跨设备录入时出现歧义。setting_kind 用 warm_display_c 表示暖菜显示设定用 hotpot_power_w 表示火锅功率设定setting_value 存对应数值。未记录就写 not_recorded并让数值为 null。显示设定值不直接等于食品温度温度与功率也不能混在一个无单位字段里。food_measurement 单独保存实际测量时间、摄氏温度值和测量位置。没有测量就用 null不用 0 占位0 是一个数值null 表示未提供测量记录。本例两条食品测量记录均为 null。2. 可运行代码校验顺序构建上桌时间轴以下示例只用 Python 3.10 标准库。菜品、时间和 80 这一设备设定数值全部为演示数据不是某台产品或某次家宴的测试。代码不由上桌时间推算食品温度也不生成保温效果曲线。用餐时间轴演示。所有记录为演示数据不是产品实测。 from copy import deepcopy from datetime import datetime import json from math import isfinite def parse_time(value): if not isinstance(value, str): raise ValueError(时间须为 ISO 8601 字符串) dt datetime.fromisoformat(value) if dt.utcoffset() is None: raise ValueError(时间须包含时区偏移) return dt def is_number(value): return (isinstance(value, (int, float)) and not isinstance(value, bool) and isfinite(value)) def validate(record): fields {dish_id, ready_at, served_at, planned_meal_at, setting_kind, setting_value, food_measurement} if not isinstance(record, dict) or not fields.issubset(record): raise ValueError(用餐记录缺少必填字段) if not isinstance(record.get(dish_id), str) or not record[dish_id].strip(): raise ValueError(dish_id 须为非空字符串) ready parse_time(record[ready_at]) served parse_time(record[served_at]) parse_time(record[planned_meal_at]) if served ready: raise ValueError(上桌时间不能早于菜品完成时间) kind, value record[setting_kind], record[setting_value] if kind not in {warm_display_c, hotpot_power_w, not_recorded}: raise ValueError(未知的设备设定类型) if kind not_recorded: if value is not None: raise ValueError(未记录设定时其值应为 null) elif not is_number(value): raise ValueError(已记录设定须为有限数值) measurement record[food_measurement] if measurement is not None: if not isinstance(measurement, dict): raise ValueError(测温记录须为对象或 null) if not {measured_at, temperature_c, measurement_point}.issubset(measurement): raise ValueError(测温记录须包含时间、数值和位置) parse_time(measurement[measured_at]) if not is_number(measurement[temperature_c]): raise ValueError(测量值须为有限数值) point measurement[measurement_point] if not isinstance(point, str) or not point.strip(): raise ValueError(测温记录须注明测量位置) def build_timeline(records): seen, rows set(), [] for record in records: validate(record) if record[dish_id] in seen: raise ValueError(dish_id 重复) seen.add(record[dish_id]) ready parse_time(record[ready_at]) served parse_time(record[served_at]) planned parse_time(record[planned_meal_at]) rows.append({ dish_id: record[dish_id], served_at: served.isoformat(), ready_to_served_minutes: (served - ready).total_seconds() / 60, served_vs_plan_minutes: (served - planned).total_seconds() / 60, food_measurement: record[food_measurement], }) return sorted(rows, keylambda row: parse_time(row[served_at])) if __name__ __main__: # 全部为演示记录设定字段与食品测量字段分开保存。 demo [ {dish_id: demo-1, ready_at: 2026-10-02T18:00:0008:00, served_at: 2026-10-02T18:05:0008:00, planned_meal_at: 2026-10-02T18:15:0008:00, setting_kind: warm_display_c, setting_value: 80, food_measurement: None}, {dish_id: demo-2, ready_at: 2026-10-02T18:08:0008:00, served_at: 2026-10-02T18:18:0008:00, planned_meal_at: 2026-10-02T18:15:0008:00, setting_kind: not_recorded, setting_value: None, food_measurement: None}, ] rows build_timeline(demo) assert [r[ready_to_served_minutes] for r in rows] [5.0, 10.0] assert [r[served_vs_plan_minutes] for r in rows] [-10.0, 3.0] assert all(r[food_measurement] is None for r in rows) print(演示时间轴) print(json.dumps(rows, ensure_asciiFalse, indent2, allow_nanFalse)) invalid_inputs [ (先上桌后完成, {served_at: 2026-10-02T17:59:0008:00}), (缺时区, {ready_at: 2026-10-02T18:00:00}), (设定类型混写, {setting_kind: temperature_or_power}), (布尔值冒充设定, {setting_value: True}), (非有限数值, {setting_value: float(nan)}), (测温缺位置, {food_measurement: { measured_at: 2026-10-02T18:05:0008:00, temperature_c: 0, measurement_point: }}), ] for name, changes in invalid_inputs: bad deepcopy(demo[0]) bad.update(changes) try: validate(bad) except ValueError as exc: print(name 已拒绝 str(exc)) else: raise AssertionError(name 未被拒绝) try: build_timeline([demo[0], deepcopy(demo[0])]) except ValueError as exc: print(重复菜品编号已拒绝 str(exc)) else: raise AssertionError(重复编号未被拒绝)3. 两个时间差各自回答什么问题演示结果中demo-1 从完成到上桌为 5 分钟上桌比计划开饭早 10 分钟demo-2 的两个值分别为 10 分钟和晚 3 分钟。ready_to_served_minutes 是流程间隔served_vs_plan_minutes 是相对计划的偏差负数表示早于计划正数表示晚于计划。这些结果可以帮助查看上菜衔接不表示食品在这段时间内的实际温度、品质或安全状态。food_measurement 仍是 null不会因时间轴生成而被自动补成数值。示例还拒绝上桌早于完成、缺时区、未知设定类型、布尔值冒充数值、非有限数值、测温缺位置和重复菜品编号等输入。时间使用 total_seconds() 计算完整间隔再换算成分钟不依赖 timedelta.seconds 中单日部分。4. 接到实际记录表时可在记录外层再加 meal_id、设备型号和记录人设备设定发生变化时另存一条带时间的操作事件不用覆盖前值。真正做食品测量时应把测量位置、仪器和对应时间一并记录再按实际测量目的分析。这份例子校验的是录入结构和时间先后。开始记录前仍要确定字段含义同一张表里不要让“设定温度”“面板温度”“食品中心温度”共用一个 temperature 字段。品牌资料参考芝梵温度指南。设备使用与餐具适配按对应产品说明书执行。技术参考Python datetime 文档、Python json 文档。