核心算法
食堂运营优化的核心算法,主要涵盖三个关键部分:成本核算、营养估计和配餐算法。首先,成本核算模块精确计算每份菜品的直接与间接成本,为后续的智能定价与盈利分析奠定基础。其次,营养估计模块基于原料的基础营养数据和菜品配方,自底向上估算每道菜品的宏量营养素含量,确保饮食的健康指标可量化。最后,核心的配餐算法综合运用成本、营养信息以及用户配置的餐类要求、系统参数,旨在生成一份多日均衡、成本可控且具有多样性的智能配餐计划,有效提升食堂的运营效率和餐食质量。
🪙 成本核算
成本核算是菜品定价和盈利分析的基础。系统通过精确计算每份菜品的直接成本和间接成本,得到总成本,进而支撑自动定价或指导手动定价。
1. 直接成本 (Direct Cost)
直接成本指构成菜品本身所需的原料成本。计算方式如下:
单项原料成本计算: 对于02-配料清单
中关联到特定菜品的每一种原料,其成本 (金额
) 的计算公式为:
其中,原料单价(每kg)
来自 03-原料管理
表,净含量(g)
来自 02-配料清单
表。
菜品直接成本汇总: 单个菜品的直接成本
是其02-配料清单
中所有关联原料金额
的总和:
2. 间接成本 (Indirect Cost)
间接成本指除直接原料成本外,维持食堂运营所产生的各项费用,如人工、水电、房租、设备折旧等。这些成本需要通过合理的方式分摊到每份菜品上。**系统采用基于销量和烹饪时间两种方式进行分摊。**部分间接成本(如部分管理费用、销售相关费用、按整体客流量计算的清洁费等)与整体销售规模或服务人次更相关。销量越高的菜品,可以认为其消耗或受益于这部分资源的比例也越高。因此,将这部分成本按销量分摊更为合理。另一部分间接成本(如厨房设备占用与折旧、与烹饪过程直接相关的能耗、厨师等特定岗位工时成本等)与菜品制作所占用的时间资源密切相关。烹饪时间越长的菜品,对这些资源的占用也越久。因此,将这部分成本按烹饪时间分摊,能更准确地反映时间密集型菜品的成本构成。单独使用销量或时间作为唯一分摊依据都可能导致成本分配失衡。例如,仅按销量分摊,会使制作简单的畅销菜承担过多的设备等时间相关成本;仅按时间分摊,则可能让制作复杂但销量有限的特色菜承担与其销量不匹配的间接成本。通过将间接成本区分为适合按销量分摊和适合按时间分摊两类,并分别进行计算,可以得到更精确、更公允的单品间接成本。
间接成本项的日成本计算: 首先,对于04-间接成本
表中的每一项成本,其每日成本 (金额(每天)
) 的计算公式为:
其中,每月营业日数
来自 07-系统配置
表。
按销量分摊的总间接成本: 系统汇总所有分摊方式
为“销量”的间接成本项的金额(每天)
,然后根据单个菜品的预计销量占比进行分摊:
按时间分摊的总间接成本: 系统汇总所有分摊方式
为“时间”的间接成本项的金额(每天)
,然后根据单个菜品的烹饪时间占比进行分摊:
菜品承担的总间接成本: 单个菜品需要承担的总间接成本是按销量分摊和按时间分摊的总和:
分摊到每份菜品的间接成本: 将菜品承担的总间接成本
除以其预计日销量
,得到每份菜品需要分摊的间接成本:
3. 总成本 (Total Cost)
一份菜品的总成本
是其直接成本
和间接成本
之和:
4. 定价关联
总成本
是后续定价的基础。
自动定价: 当菜品的定价类型
设置为“自动”时,系统会根据总成本
和07-系统配置
中设定的目标利润率(%)
来计算自动定价
:
最终定价: 最终定价
根据定价类型
决定。若为“自动”,则取自动定价
;若为“手动”,则取人工录入的手动定价
。
此成本核算模型旨在提供一个全面且相对公平的成本分摊机制,为菜品定价和经营决策提供数据支持。
🥕 营养估计
系统对菜品营养成分的估计遵循一个自底向上的计算流程,其核心数据源于03-原料管理
表,并通过02-配料清单
表进行中间计算,最终汇总至01-菜品管理
表。
1. 基础营养数据
基础营养数据存储在03-原料管理
数据表中。该表记录了每种原料每100克所含的宏量营养素信息,主要包括:
能量(Kcal|100g)
蛋白质(g|100g)
脂肪(g|100g)
碳水化合物(g|100g)
这些数据是后续所有营养计算的基石。
2. 单项配料营养计算
02-配料清单
表作为连接菜品与原料的桥梁,负责计算构成特定菜品的单项原料所贡献的营养价值。对于清单中的每一条记录(即某菜品使用的某一种原料),其各项营养成分的计算公式如下:
令 代表某一种宏量营养素(如能量、蛋白质、脂肪、碳水化合物)。
令 代表该原料在03-原料管理
表中对应的每100克营养素含量(例如 能量(Kcal|100g)
)。
令 代表该原料在02-配料清单
表中为构成特定菜品所需的净含量(g)
。
则该项配料为菜品贡献的营养素 $N_{ingredient}$ 计算公式为:
此计算应用于02-配料清单
表中的能量(Kcal)
、蛋白质(g)
、脂肪(g)
和碳水化合物(g)
字段。
3. 菜品总营养汇总
一道菜品的总营养信息记录在01-菜品管理
数据表中。其营养字段(能量(Kcal)
、蛋白质(g)
、脂肪(g)
、碳水化合物(g)
)是通过汇总该菜品在02-配料清单
表中所有关联配料记录的相应营养成分计算得出的。
令 代表菜品的总营养素含量。 令 代表构成该菜品的第 $i$ 种配料按照步骤2计算得出的营养素含量。
则菜品的总营养素含量计算公式为:
其中,求和遍历该菜品在02-配料清单
中的所有配料记录。
通过这种分层计算方法,系统能够基于原料的基础营养信息和菜品的配料构成,精确估算每道菜品的宏量营养素含量。
🍜 配餐算法
配餐算法旨在根据用户配置的菜品库、餐类设置、营养标准、成本目标及系统参数,生成一份满足多日需求的、均衡营养、控制成本且具备一定多样性的配餐计划。该算法综合考虑了每日及每餐的营养需求、菜品的价格、菜品的历史选用情况以及用户定义的多样性偏好,通过一个精细化的评分和选择机制,力求在多重约束和目标之间达到平衡,为食堂运营提供科学合理的排餐建议。
算法首先对输入数据进行预处理。它将每餐营养标准 (meal_nutrition_std
) 转换为以餐时段和营养素名称为键的字典 meal_nutrition_std_dict
,方便快速查找。每日总营养标准 (nutrition_std
) 也被处理成以营养素名称为键的字典 nutrition_std_dict
。餐类配置 (meal_config
) 则按餐时段分组存储在 meal_time_configs
中,记录各餐时段所需的菜品类别及数量。特别地,算法会检查是否存在某个餐时段的所有菜品类别数量均配置为0。若存在这种情况,则认为该餐时不供餐,并从每日总营养标准 nutrition_std_dict
中扣除该餐时段对应的营养标准值,以确保后续营养计算基于实际供餐情况。同时,算法会构建一个多层嵌套的菜品映射 dish_map
,结构为 {餐时段: {菜品类别: [菜品列表]}}
,以便于后续按需查找候选菜品。
核心的配餐逻辑在一个按天迭代的循环中执行,循环次数由系统配置中的 配餐天数
决定。在每一天的配餐开始前,算法会初始化当天的计划 daily_plan
、已选菜品集合 selected_dishes
、当天累计营养 current_day_nutrition
、每餐累计营养 current_meal_nutrition
以及当天累计价格 current_day_price
。为了优先满足重要的餐次和核心菜品类别,每日的配餐并非按时间顺序,而是按照 午餐
、晚餐
、早餐
的优先级进行。在每个餐时段内,菜品类别的选择也遵循特定顺序:优先选择 荤
菜和 素
菜,其次是 主
食,最后处理其他类别。这种排序策略旨在确保核心营养和餐食结构的稳定。
对于每个餐时段的每种菜品类别,算法需要根据 meal_time_configs
中设定的 数量
来挑选菜品。首先,它会从 dish_map
中获取该餐时段该类别的所有候选菜品。接着,进行可用性筛选:对于非 主
食类的菜品,必须满足两个条件:未在当天其他餐次被选中(dish_id not in selected_dishes
),且距离上次被选中的天数大于等于系统配置的 菜品最小重复天数
。主
食类菜品(如米饭、馒头)由于其基础性和有限的可替代性,不受 菜品最小重复天数
的限制。如果筛选后可用菜品数量少于所需数量,算法将抛出错误,提示用户调整配置。
筛选出的可用菜品将进入评分环节,其核心作用是量化评估每一个候选菜品加入当前配餐方案后的综合效益,为后续的菜品选择提供依据。评分函数 compute_score
是算法的核心,它综合评估每个候选菜品加入当前配餐方案后的表现。评分涉及多个维度:
餐次营养得分 (nutri_score_meal
): 计算加入该菜品后,当前餐次的各项营养素(能量、蛋白质、脂肪、碳水化合物)与该餐次标准值 (meal_nutrition_std_dict
) 的接近程度。得分基于各项营养素比例与1的绝对差值之和,差值越小得分越高。
其中 代表四种宏量营养素集合。
当日营养得分 (nutri_score_day
): 类似地,计算加入该菜品后,当天已选菜品累计营养与每日总营养标准 (nutrition_std_dict
) 的接近程度。
整体营养得分 (nutri_score_total
): 考虑加入该菜品后,整个配餐周期(已完成天数+当前菜品)的平均每日营养与每日总营养标准的接近程度。
动态价格得分 (price_score_dynamic
): 引入动态平均价格概念。首先计算当天剩余待选菜品的平均预算(剩余预算 / 剩余菜品数)。然后比较候选菜品价格与此动态平均预算的接近程度,价格越接近动态预算得分越高。这有助于在配餐早期避免选过多高价菜,后期避免选过多低价菜。
当日价格得分 (price_score_day
): 计算加入该菜品后,当天总价与 每日餐标(元)
的接近程度。同时,为了平滑价格选择,该得分会与 price_score_dynamic
取平均值。
整体价格得分 (price_score_total
): 评估加入该菜品后,整个配餐周期的平均每日价格与 每日餐标(元)
的接近程度。
为了平衡短期(当天/当餐)和长期(整体)目标,算法引入了动态权重调整机制。根据 整体权重调整策略
(0为线性,1为指数)和 整体权重上限
,计算一个随配餐天数 day
递增的整体权重 total_weight
。
线性策略:
指数策略:
最终的营养得分 nutri_score
和价格得分 price_score
由当日/当餐得分和整体得分加权组合而成:
此外,为了鼓励菜品多样性(主
食除外),算法引入了 多样性权重
和相应的 diversity_score
。该得分会惩罚近期(过去3天内)使用过的菜品,并奖励超过一定天数(7天)未使用的或从未使用的菜品。
最终的综合得分 score
由营养分、价格分和多样性分加权得到:
计算出所有候选菜品的综合得分后,算法并非简单选择得分最高的菜品。它首先选取得分最高的 top_k
个候选菜品(k
值由系统配置 top_k
决定)。然后,利用带有温度系数 temperature
的 Softmax 函数计算这 top_k
个菜品的选择概率。温度系数控制随机性:值越低越倾向于选择得分最高的,值越高则选择越随机。
最后,根据计算出的概率分布,从中无重复地随机抽取所需数量的菜品。
选中的菜品被添加到当天的配餐计划 daily_plan
中,同时更新 selected_dishes
集合、菜品的最后使用日期 last_used
、当天累计营养 current_day_nutrition
、当天累计价格 current_day_price
、整体累计营养 total_nutrition
和整体累计价格 total_price
。
完成一天的配餐后,算法会进行价格和营养的符合性检查。它将当天的实际总价与 每日餐标(元)
对比,检查是否在 餐标浮动比例
允许的范围内。同样,将当天的各项营养素摄入量与每日营养标准 nutrition_std_dict
对比,检查是否在 营养素偏差比例
允许的范围内。检查结果(包括是否符合标准 ✅/❌,当前值,标准值,允许偏差)会以文本形式记录在 daily_plan
的 价格(当前值/标准值)
和 营养(当前值/标准值)
字段中。任何超出允许范围的情况会被记录到警告列表 warnings
中。
所有天数的配餐计划生成完毕后,算法还会计算整个配餐周期的平均每日价格和平均每日营养,并同样进行符合性检查,将结果字符串存储在返回字典的 avg_daily_price
和 avg_daily_nutrition
字段中。最终返回包含详细每日每餐计划 meal_plan
、总营养标准 nutrition_std_dict
、所有警告信息 warnings
以及平均指标对比结果的字典。
输入示例
# 示例菜品数据
dishes = [
# 主食
{
"菜品ID": "recuBWiXOs4GLd",
"最终定价": 10,
"菜品类别": "主",
"适用餐时段": ["早餐", "午餐", "晚餐"],
"能量(Kcal)": 220,
"蛋白质(g)": 6,
"脂肪(g)": 1,
"碳水化合物(g)": 48,
},
# 荤菜
{
"菜品ID": "recuGOUcprRpcg",
"最终定价": 9,
"菜品类别": "荤",
"适用餐时段": ["午餐", "晚餐"],
"能量(Kcal)": 350,
"蛋白质(g)": 15,
"脂肪(g)": 25,
"碳水化合物(g)": 10,
},
{
"菜品ID": "recuGOV9qibw2h",
"最终定价": 8,
"菜品类别": "荤",
"适用餐时段": ["午餐", "晚餐"],
"能量(Kcal)": 280,
"蛋白质(g)": 18,
"脂肪(g)": 12,
"碳水化合物(g)": 15,
},
]
# 示例餐类配置
meal_config = [
{"餐时段": "早餐", "菜品类别": "主", "数量": 0},
{"餐时段": "午餐", "菜品类别": "主", "数量": 1},
{"餐时段": "午餐", "菜品类别": "荤", "数量": 1},
{"餐时段": "午餐", "菜品类别": "素", "数量": 0},
{"餐时段": "午餐", "菜品类别": "汤", "数量": 0},
{"餐时段": "晚餐", "菜品类别": "主", "数量": 0},
{"餐时段": "晚餐", "菜品类别": "荤", "数量": 1},
{"餐时段": "晚餐", "菜品类别": "素", "数量": 0},
{"餐时段": "晚餐", "菜品类别": "汤", "数量": 0},
]
# 示例每日营养标准
nutrition_std = [
{"营养素名称": "能量(Kcal)", "标准值": 2025},
{"营养素名称": "蛋白质(g)", "标准值": 66},
{"营养素名称": "脂肪(g)", "标准值": 57.5},
{"营养素名称": "碳水化合物(g)", "标准值": 275},
]
# 示例每餐营养标准
meal_nutrition_std = [
{"餐时段": "早餐", "营养素名称": "能量(Kcal)", "标准值": 625},
{"餐时段": "早餐", "营养素名称": "蛋白质(g)", "标准值": 20},
{"餐时段": "早餐", "营养素名称": "脂肪(g)", "标准值": 17.5},
{"餐时段": "早餐", "营养素名称": "碳水化合物(g)", "标准值": 85},
{"餐时段": "午餐", "营养素名称": "能量(Kcal)", "标准值": 825},
{"餐时段": "午餐", "营养素名称": "蛋白质(g)", "标准值": 26},
{"餐时段": "午餐", "营养素名称": "脂肪(g)", "标准值": 22.5},
{"餐时段": "午餐", "营养素名称": "碳水化合物(g)", "标准值": 115},
{"餐时段": "晚餐", "营养素名称": "能量(Kcal)", "标准值": 575},
{"餐时段": "晚餐", "营养素名称": "蛋白质(g)", "标准值": 20},
{"餐时段": "晚餐", "营养素名称": "脂肪(g)", "标准值": 17.5},
{"餐时段": "晚餐", "营养素名称": "碳水化合物(g)", "标准值": 75},
]
# 示例系统配置
sys_config = {
"菜品最小重复天数": 1,
"营养权重": 0.2,
"每日餐标(元)": 20,
"配餐天数": 2,
"餐标浮动比例": 0.2,
"营养素偏差比例": {
"能量(Kcal)": 0.4,
"蛋白质(g)": 0.5,
"脂肪(g)": 0.5,
"碳水化合物(g)": 0.5,
},
"整体权重上限": 0.2,
"多样性权重": 0.2,
"top_k": 3,
"temperature": 0.3,
}
输出示例
// INFO:root:生成的配餐计划:
{
"meal_plan": [
{
"day": 1,
"meals": {
"午餐": [
{
"菜品ID": "3",
"菜品类别": "荤",
"最终定价": 8
},
{
"菜品ID": "1",
"菜品类别": "主",
"最终定价": 10
}
],
"晚餐": [
{
"菜品ID": "2",
"菜品类别": "荤",
"最终定价": 9
}
]
},
"价格(当前值/标准值)": "❌ 27.0/20.0 [±20.0%]",
"营养(当前值/标准值)": {
"能量(Kcal)": "✅ 850.0/1400.0 [±40.0%]",
"蛋白质(g)": "✅ 39.0/46.0 [±50.0%]",
"脂肪(g)": "✅ 38.0/40.0 [±50.0%]",
"碳水化合物(g)": "❌ 73.0/190.0 [±50.0%]"
}
},
{
"day": 2,
"meals": {
"午餐": [
{
"菜品ID": "3",
"菜品类别": "荤",
"最终定价": 8
},
{
"菜品ID": "1",
"菜品类别": "主",
"最终定价": 10
}
],
"晚餐": [
{
"菜品ID": "2",
"菜品类别": "荤",
"最终定价": 9
}
]
},
"价格(当前值/标准值)": "❌ 27.0/20.0 [±20.0%]",
"营养(当前值/标准值)": {
"能量(Kcal)": "✅ 850.0/1400.0 [±40.0%]",
"蛋白质(g)": "✅ 39.0/46.0 [±50.0%]",
"脂肪(g)": "✅ 38.0/40.0 [±50.0%]",
"碳水化合物(g)": "❌ 73.0/190.0 [±50.0%]"
}
}
],
"nutrition_std_dict": {
"能量(Kcal)": 1400,
"蛋白质(g)": 46,
"脂肪(g)": 40.0,
"碳水化合物(g)": 190
},
"warnings": [
"警告 [-]:Day 1 碳水化合物(g) 不在允许范围内 [±50.0%] (当前值/标准值:73.0/190.0)",
"警告 [+]:Day 1 价格 不在允许范围内 [±20.0%] (当前值/标准值:27.0/20.0)",
"警告 [-]:Day 2 碳水化合物(g) 不在允许范围内 [±50.0%] (当前值/标准值:73.0/190.0)",
"警告 [+]:Day 2 价格 不在允许范围内 [±20.0%] (当前值/标准值:27.0/20.0)"
],
"avg_daily_price": "❌ 27.0/20.0 [±20.0%]",
"avg_daily_nutrition": {
"能量(Kcal)": "✅ 850.0/1400.0 [±40.0%]",
"蛋白质(g)": "✅ 39.0/46.0 [±50.0%]",
"脂肪(g)": "✅ 38.0/40.0 [±50.0%]",
"碳水化合物(g)": "❌ 73.0/190.0 [±50.0%]"
}
}
最后更新于