基于规则引擎与推荐算法的智能周度菜单生成器设计与实现
1. 项目概述从“今天吃什么”到一周菜单的自动化生成“今天吃什么”这个问题大概是每个需要自己动手解决三餐的人每天都要面对的灵魂拷问。无论是独居的上班族还是需要为全家掌勺的家庭主厨在忙碌的生活中既要考虑营养均衡又要兼顾口味喜好还得盘算冰箱里的存货和预算规划一周的菜单常常让人头疼。kwokronny/week-menu-generate这个项目正是为了解决这个普遍存在的痛点而生。它不是一个简单的菜谱集合而是一个智能化的周度菜单生成器旨在通过预设的规则和算法自动化地为你规划出一周七天的三餐方案。这个项目的核心价值在于“解放大脑”。它把我们从重复性的决策疲劳中拯救出来将“想吃什么”这个开放性问题转化为一个可配置、可执行的计划。你可以把它想象成一位不知疲倦的私人营养顾问兼膳食管家它基于你设定的约束条件——比如饮食偏好素食、低卡、高蛋白、忌口食材、烹饪复杂度、甚至是想清空冰箱里某样食材——来生成一份量身定制的菜单。对于有特殊饮食需求如健身增肌、控制血糖的人群或者只是想提升家庭饮食管理效率的人来说这无疑是一个极具吸引力的工具。从技术角度看它触及了规则引擎、推荐算法、数据建模和用户体验设计等多个领域。虽然表面上是生成菜谱列表但其背后涉及到如何结构化地描述一道菜包含食材、烹饪时间、口味、营养标签等如何定义用户画像和约束条件以及如何设计一个高效的匹配与排期算法确保生成的菜单既满足硬性限制又富有变化和惊喜感。接下来我们就深入拆解这个项目是如何一步步实现从“想法”到“菜单”的智能转换的。2. 核心设计思路规则驱动与个性化平衡的艺术设计一个菜单生成器最难的不是随机挑出几道菜而是在多重约束下找到最优解并且让这个“最优解”看起来合情合理甚至有点小惊喜。week-menu-generate项目的设计哲学可以概括为“规则驱动下的个性化探索”。2.1 数据层的结构化为每道菜打上多维标签一切智能推荐的基础都是数据。这里的核心数据就是“菜谱”。但简单的菜名列表毫无用处。项目首先需要建立一个结构化的菜谱数据库。每道菜都是一个数据对象至少包含以下维度基础信息菜名、所属菜系中餐、西餐、日料等、主要类别主食、主菜、汤羹、沙拉等。食材清单精确到主料、辅料、调味料。这是实现忌口过滤和库存管理的关键。烹饪属性预估准备时间、烹饪时间、总耗时、复杂度评分例如1-5星1星为快手菜5星为宴客大菜。营养与饮食标签是否为素食、纯素、低卡路里、高蛋白、低碳水、无麸质等。这些是匹配用户饮食目标的核心。风味与口感咸、甜、酸、辣、鲜等用于避免连续几天口味过于单一。在实际构建中我通常建议使用 JSON 或 YAML 这类易于读写和扩展的格式来存储菜谱。例如{ id: mapo_tofu, name: 麻婆豆腐, cuisine: sichuan, category: main, ingredients: { main: [豆腐, 猪肉末], accessory: [蒜苗], seasoning: [郫县豆瓣酱, 花椒粉, 辣椒粉, 生抽, 淀粉] }, time: { prep: 10, cook: 10, total: 20 }, difficulty: 2, tags: [spicy, high-protein, contains-meat], nutrition: { calories: 280, protein: 18, carbs: 10 } }注意初期不需要追求大而全的数据库。可以从20-30道你常做、爱吃的菜开始确保每道菜的信息准确、完整。数据的质量远比数量重要。2.2 用户画像与约束定义告诉系统“你是谁”和“你要什么”系统需要知道它为谁服务。因此一个清晰的用户配置模块必不可少。这通常包括饮食目标与限制这是硬性规则。例如素食主义、不吃海鲜、控制每日热量在1800大卡以内。口味偏好软性规则用于加权。例如喜欢辣味、讨厌过甜。实操条件时间预算工作日只接受总耗时 30分钟的菜周末可以接受 60分钟的。烹饪技能新手模式避开复杂菜式。设备限制如果没有烤箱则过滤掉所有需要烤箱的菜谱。多样性要求菜系轮换一周内避免同一菜系出现超过2次。食材复用与清库存允许用户指定“本周希望消耗掉冰箱里的鸡胸肉和菠菜”系统会优先推荐包含这些食材的菜谱。口味平衡避免连续两天都是重口味菜肴。这些配置可以通过一个配置文件或交互式问答来收集。例如一个简单的配置文件user_config.yamldietary_restrictions: - vegetarian: false - no_pork: true - no_shellfish: true preferences: favored_cuisines: [chinese, italian] disliked_flavors: [excessively_sweet] favored_cooking_methods: [stir_fry, bake] constraints: max_daily_cooking_time: weekday: 40 weekend: 90 max_difficulty: 3 objectives: diversify_cuisine: true use_ingredients: [chicken_breast, spinach] # 清库存目标2.3 生成引擎从筛选、评分到排期有了菜谱数据和用户配置核心算法就可以运行了。这个过程通常分为三步第一步硬性过滤根据用户的饮食限制如忌口、设备限制如无烤箱和绝对时间约束从整个菜谱池中筛掉所有不符合条件的菜。剩下的菜构成“候选池”。第二步软性评分与排序对候选池中的每道菜根据用户偏好进行加权打分。例如匹配用户喜欢的菜系20分。匹配用户喜欢的烹饪方式15分。符合“清库存”目标30分高优先级。与最近几天已安排菜品的相似度菜系、主料相似度越高得分越低以实现多样性。菜品复杂度与当日可用时间匹配度匹配度越高得分越高。最终每道菜获得一个综合得分。第三步贪心算法排期这是最考验设计的地方。一个简单有效的策略是“按天贪心”确定当天是工作日还是周末从而确定时间预算。从符合当天时间预算的菜品中选择综合得分最高的作为“主菜”。根据主菜搭配适合的“主食”如米饭、面条和“汤/蔬菜”选择口味互补、烹饪简单的。将选中的菜品从本周候选池中暂时移除或降低其后续得分以避免重复。循环7天完成一周排期。更复杂的算法可能会考虑营养素的每日均衡分布但这需要更精细的营养数据支持。对于大多数个人项目基于规则的贪心算法在效果和复杂度之间取得了很好的平衡。实操心得在开发初期不必追求完美的算法。可以先用一个非常简单的随机选择硬性过滤的版本跑起来生成几份菜单看看效果。你会发现很多逻辑问题比如连续三天吃鸡在直观的测试中会立刻暴露出来这比空想算法要有效得多。3. 技术实现选型轻量、灵活与可维护性这样一个项目技术栈的选择核心在于快速原型验证和易于维护扩展。它不是一个需要承受高并发的在线服务而更像一个个人或小范围使用的工具。3.1 编程语言Python 是首选Python 几乎是此类项目的标准答案原因如下生态丰富处理 JSON/YAML (PyYAML)、数据计算 (pandas,numpy)、甚至简单的机器学习库 (scikit-learn) 用于未来的智能推荐都有成熟的库。开发效率高语法简洁能快速实现业务逻辑专注于算法和规则本身。脚本化友好可以很容易地做成命令行工具通过cron任务每周自动运行生成菜单后发送邮件或同步到笔记软件。当然如果你对 JavaScript/Node.js 更熟悉用它来实现也完全可行尤其是如果你希望最终有一个简单的网页界面。但对于核心的生成引擎Python 在数据处理和科学计算方面的库支持更具优势。3.2 数据存储文件先行数据库备选在项目初期强烈建议使用纯文本文件JSON 或 YAML来存储菜谱和配置。优点无需搭建数据库服务版本控制清晰用 Git 管理菜谱库的变更历史一目了然读写简单直观。结构可以建立两个核心文件recipes/目录存放所有菜谱的.json文件每个文件一道菜。config/user_profile.yaml存放用户配置。generated_menus/目录存放历史生成的菜单方便回顾。只有当菜谱数量庞大比如上千条或需要实现复杂的多用户、实时查询功能时才需要考虑引入轻量级数据库如 SQLite或文档数据库如 MongoDB。3.3 输出与集成让菜单“活”起来生成的菜单不能只躺在命令行终端里。实用的输出形式至关重要Markdown 文件这是最通用、最友好的格式。生成的week_menu.md可以包含清晰的表格列出一日三餐并附上每道菜的简要说明和所需食材清单。这份文件可以直接放入 Obsidian、Notion 或任何 Markdown 阅读器中查看。JSON 结构化数据为后续自动化流程提供接口。例如另一个脚本可以读取这个 JSON自动生成购物清单或导入到日历应用中。邮件或消息推送通过smtplib(Python) 或第三方 API如 Server 酱、Pushover将每周菜单在周日晚上定时推送到你的邮箱或手机。同步到云笔记利用 Notion 或语雀的 API将菜单直接写入你指定的笔记页面实现无缝工作流。一个 Markdown 格式的菜单示例# 本周菜单 (2023-10-23 至 2023-10-29) ## 周一 * **早餐**牛奶燕麦粥水煮蛋 * **午餐**麻婆豆腐配米饭清炒菠菜 * **晚餐**番茄龙利鱼汤面 **购物提醒**需购买豆腐、猪肉末、龙利鱼、菠菜。3.4 项目结构规划一个清晰的项目结构有助于长期维护week-menu-generate/ ├── README.md ├── requirements.txt ├── menu_generator.py # 核心生成引擎 ├── config/ │ ├── user_profile.yaml # 用户配置 │ └── rules.yaml # 生成规则如多样性规则 ├── data/ │ └── recipes/ # 存放所有菜谱JSON文件 │ ├── mapo_tofu.json │ ├── tomato_egg.json │ └── ... ├── output/ │ ├── menus/ # 历史生成的菜单 │ └── shopping_lists/ # 历史生成的购物清单 ├── utils/ │ ├── recipe_loader.py # 加载菜谱 │ └── formatter.py # 格式化输出为MD/JSON └── scripts/ └── weekly_task.py # 用于cron定时执行的脚本4. 核心功能模块的深度实现让我们深入到几个关键模块看看代码层面如何实现。4.1 菜谱加载与管理器这个模块负责从文件系统读取所有菜谱并构建一个在内存中易于查询的数据结构。为了提高效率我们可以在加载时建立反向索引例如“食材-菜谱列表”、“标签-菜谱列表”。# utils/recipe_loader.py import json import os from typing import Dict, List, Set class RecipeManager: def __init__(self, recipe_dir: str): self.recipe_dir recipe_dir self.recipes {} # id - recipe dict self.index_by_ingredient {} self.index_by_tag {} self._load_all_recipes() def _load_all_recipes(self): for filename in os.listdir(self.recipe_dir): if filename.endswith(.json): filepath os.path.join(self.recipe_dir, filename) with open(filepath, r, encodingutf-8) as f: recipe json.load(f) recipe_id recipe[id] self.recipes[recipe_id] recipe # 建立食材索引 for ingredient in recipe.get(ingredients, {}).get(main, []): self.index_by_ingredient.setdefault(ingredient, []).append(recipe_id) # 建立标签索引 for tag in recipe.get(tags, []): self.index_by_tag.setdefault(tag, []).append(recipe_id) def get_recipes_by_ingredients(self, ingredients: List[str]) - List[Dict]: 根据食材查找菜谱包含任意给定食材即可 recipe_ids set() for ing in ingredients: recipe_ids.update(self.index_by_ingredient.get(ing, [])) return [self.recipes[rid] for rid in recipe_ids] def filter_recipes(self, filter_func) - List[Dict]: 通用的过滤函数传入一个判断函数 return [r for r in self.recipes.values() if filter_func(r)]4.2 用户配置与规则解析用户配置需要被解析成程序能理解的规则对象。这里我们可以定义一个UserContext类在初始化时载入配置并预处理一些规则。# config/user_context.py import yaml class UserContext: def __init__(self, config_path: str): with open(config_path, r) as f: self.config yaml.safe_load(f) self.dietary_restrictions self.config.get(dietary_restrictions, {}) self.preferences self.config.get(preferences, {}) self.constraints self.config.get(constraints, {}) self.objectives self.config.get(objectives, {}) # 预处理将“清库存”目标转换为小写便于匹配 self.use_ingredients [ing.lower() for ing in self.objectives.get(use_ingredients, [])] def is_recipe_allowed(self, recipe: Dict) - bool: 硬性规则检查 # 检查忌口 if self.dietary_restrictions.get(vegetarian) and contains-meat in recipe.get(tags, []): return False if self.dietary_restrictions.get(no_pork) and 猪肉 in str(recipe.get(ingredients, {})): # 这里需要更精细的食材匹配此处简化 return False # 检查烹饪时间需结合当天是 weekday/weekend # 此检查放在排期阶段更合适 # 检查难度 if recipe.get(difficulty, 5) self.constraints.get(max_difficulty, 5): return False return True def score_recipe(self, recipe: Dict, day_type: str) - float: 软性规则评分 score 0.0 # 口味偏好加分 favored_cuisines self.preferences.get(favored_cuisines, []) if recipe.get(cuisine) in favored_cuisines: score 20 # 清库存目标加分高权重 recipe_ingredients [ing.lower() for ing in recipe.get(ingredients, {}).get(main, [])] for target_ing in self.use_ingredients: if target_ing in recipe_ingredients: score 30 break # 找到一种目标食材即可 # 时间匹配度加分 max_time self.constraints.get(max_daily_cooking_time, {}).get(day_type, 120) recipe_time recipe.get(time, {}).get(total, 0) if recipe_time max_time: # 时间越充裕匹配度权重可以降低时间越紧张匹配度越重要 time_match_ratio 1.0 - (recipe_time / max_time) # 值越大说明时间越充裕 score 10 * time_match_ratio return score4.3 核心生成引擎这是大脑所在。我们实现一个基于贪心算法的生成器。# menu_generator.py import random from datetime import datetime, timedelta from typing import List, Dict from utils.recipe_loader import RecipeManager from config.user_context import UserContext class WeekMenuGenerator: def __init__(self, recipe_manager: RecipeManager, user_context: UserContext): self.rm recipe_manager self.uc user_context self.generated_menu {i: {breakfast: None, lunch: None, dinner: None} for i in range(7)} # 0Mon, 6Sun def _get_day_type(self, day_index: int) - str: 判断是工作日还是周末 return weekend if day_index 5 else weekday # 假设0-4为工作日5-6为周末 def _select_main_dish(self, candidate_recipes: List[Dict], day_type: str, used_recipe_ids: Set) - Dict: 从候选菜谱中选择一道主菜 # 过滤掉已使用的 available [r for r in candidate_recipes if r[id] not in used_recipe_ids] if not available: available candidate_recipes # 如果都用过了允许重复或可降低评分阈值 # 计算每道菜的得分 scored_recipes [] for recipe in available: if not self.uc.is_recipe_allowed(recipe): continue # 检查时间约束 max_time self.uc.constraints.get(max_daily_cooking_time, {}).get(day_type, 120) if recipe.get(time, {}).get(total, 0) max_time: continue score self.uc.score_recipe(recipe, day_type) scored_recipes.append((score, recipe)) if not scored_recipes: return None # 按分数降序排序选择最高分 scored_recipes.sort(keylambda x: x[0], reverseTrue) # 可以加入一点随机性从前N名中随机选避免每次都一模一样 top_n min(3, len(scored_recipes)) selected random.choice(scored_recipes[:top_n])[1] return selected def generate(self) - Dict: used_recipe_ids set() # 假设我们有一个“主菜”的类别 main_dish_candidates [r for r in self.rm.recipes.values() if r.get(category) main] for day in range(7): day_type self._get_day_type(day) main_dish self._select_main_dish(main_dish_candidates, day_type, used_recipe_ids) if main_dish: self.generated_menu[day][lunch] main_dish # 假设午餐是主菜 used_recipe_ids.add(main_dish[id]) # 这里可以继续添加选择早餐、晚餐、配菜的逻辑逻辑类似但候选池不同 # 早餐候选池categorybreakfast 或 time.total 15 # 晚餐候选池categorysoup/light 或 time.total 30 return self.generated_menu4.4 输出格式化与购物清单生成生成菜单对象后我们需要将其转化为对人类友好的格式并衍生出购物清单。# utils/formatter.py def format_menu_to_markdown(menu: Dict, start_date: datetime) - str: 将菜单字典格式化为Markdown字符串 days [周一, 周二, 周三, 周四, 周五, 周六, 周日] md_lines [f# 本周菜单 ({start_date.strftime(%Y-%m-%d)} 至 {(start_date timedelta(days6)).strftime(%Y-%m-%d)}), ] for i in range(7): current_date start_date timedelta(daysi) date_str current_date.strftime(%m月%d日) md_lines.append(f## {days[i]} ({date_str})) md_lines.append() for meal in [breakfast, lunch, dinner]: dish menu[i].get(meal) if dish: # 这里可以更详细比如列出食材 md_lines.append(f* **{meal.title()}**{dish[name]}约{dish[time][total]}分钟) else: md_lines.append(f* **{meal.title()}**待安排) md_lines.append() return \n.join(md_lines) def generate_shopping_list(menu: Dict, recipe_manager) - Dict: 根据一周菜单生成购物清单按食材分类 shopping_list {} for day in menu.values(): for meal, dish in day.items(): if dish: recipe recipe_manager.recipes.get(dish[id]) if recipe: for category, ingredients in recipe.get(ingredients, {}).items(): for ing in ingredients: # 简单去重和计数实际中可能需要处理单位如“鸡蛋 6个” shopping_list[ing] shopping_list.get(ing, 0) 1 return shopping_list5. 部署、使用与迭代优化5.1 本地运行与自动化项目开发完成后最简单的使用方式就是本地命令行运行。# 安装依赖如果有的话 pip install -r requirements.txt # 运行生成器 python main.py --config config/my_profile.yaml --output ./menu.md为了实现真正的“解放”我们可以设置定时任务。在 Linux/macOS 上使用cron在 Windows 上使用任务计划程序。# 编辑cron任务每周日晚上8点运行 crontab -e # 添加一行 0 20 * * 0 cd /path/to/week-menu-generate /usr/bin/python3 main.py /tmp/menu_gen.log 21main.py脚本可以集成生成、格式化和发送邮件的功能。5.2 常见问题与排查技巧在实际使用中你可能会遇到以下问题问题现象可能原因排查与解决思路生成的菜单总是重复那几道菜。1. 菜谱池太小。2. 评分算法中“多样性”权重太低或未实现。3. “清库存”等目标权重过高导致系统总是推荐包含特定食材的菜。1. 扩充菜谱库至少保证每个类别如主菜有15-20个选项。2. 在score_recipe函数中加入对“近期是否出现过”的惩罚项。例如记录过去N天内使用过的菜谱ID如果当前菜谱在其中则大幅扣分。3. 调整权重配置降低单一目标的绝对影响力让系统在多个目标间权衡。系统推荐了我不吃的食材如猪肉。忌口规则未生效或匹配不准确。1. 检查user_profile.yaml中的dietary_restrictions配置是否正确。2. 检查is_recipe_allowed函数中的逻辑。确保食材匹配是精确或包含关系如猪肉 in ingredient_string。建议将食材名称标准化并建立明确的忌口食材列表进行匹配。周末推荐了太复杂的菜但我周末也想休息。周末的max_daily_cooking_time设置过高或未区分午餐和晚餐的时间预算。1. 在用户配置中可以进一步细化时间约束例如区分max_lunch_time和max_dinner_time。2. 为菜谱增加“适合场合”标签如weekend-feast,quick-meal并在规则中加以利用。生成的购物清单里有“盐”、“油”这种常备调料。系统未区分“常备物资”和“需要采购的食材”。1. 在菜谱的ingredients结构中增加一个category如staple常备和purchase需购。2. 在生成购物清单时只汇总category为purchase的食材。可以维护一个“家庭常备清单”配置文件自动过滤。程序运行报错找不到菜谱文件。文件路径错误或菜谱JSON格式不正确。1. 使用绝对路径或确保运行时的工作目录正确。2. 在recipe_loader.py中加入try-except和更详细的错误日志打印出具体是哪个文件解析失败。3. 使用 JSON 校验工具预先检查所有菜谱文件格式。5.3 进阶优化方向当基础版本稳定运行后可以考虑以下方向进行深化学习与反馈机制增加一个简单的反馈功能。每次生成菜单后用户可以标记“喜欢”或“跳过”某道菜。系统记录这些反馈在下一次生成时为“喜欢”的菜加分为“跳过”的菜减分甚至暂时屏蔽实现个性化的渐进优化。营养计算与均衡为菜谱添加更详细的营养数据热量、蛋白质、脂肪、碳水、膳食纤维等。在生成菜单时不仅考虑单道菜还尝试使每日乃至每周的营养摄入接近目标值如健身者的蛋白质摄入量。智能搭配目前的搭配逻辑可能比较简单主菜米饭蔬菜。可以引入“食物搭配”规则库例如“西红柿配鸡蛋”、“羊肉配萝卜”让推荐的组合更符合饮食文化习惯。外部集成与在线菜谱网站需考虑API限制或本地食谱管理软件如 Paprika打通自动同步或导入菜谱丰富数据库。Web界面使用 Flask 或 FastAPI 搭建一个简单的网页提供更直观的配置界面、菜单预览和手动调整功能。这个项目的魅力在于它从一个具体的个人需求出发融合了数据处理、规则引擎和算法设计最终打造出一个能切实提升生活效率的工具。从最简单的脚本开始逐步添加功能、优化体验整个过程本身就是一次极佳的编程实践。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2562123.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!