Skip to content

About

No description, website, or topics provided.

Resources

Stars

111 stars

Watchers

0 watching

Forks

Repository files navigation

麦麦最优解 · Mcd Optimizer

在「预算 × 热量 × 人数 × 忌口」多重约束下,求解麦当劳最优下单方案的 Skill。 数据全部来自麦当劳 MCP Server 的真实接口——菜单、价格、营养、优惠券一律实时拉取,不做任何硬编码。 它还会先把券领了,再带着券求解。

Python 依赖 MCP 比赛


30 秒看懂效果

同一家门店、同一组约束(2 人 / 预算 ¥70 / 人均热量 ≤ 800 kcal), 差别只在于「有没有把券算进去」:

方案 实付 原价 热量 性价比
不用券 那么大鸡排 ×3 ¥42.00 ¥42.00 1155 kcal 27.5 kcal/元
用券 中薯条买一送一(券)×1 + 那么大鸡排 ×2 ¥41.50 ¥55.00 1348 kcal 32.5 kcal/元

少花 ¥0.5,多吃 193 kcal,每元买到的热量高 18%,同时省下 ¥13.5。

数据于 2026-10-15 由麦当劳 MCP 实时接口拉取,含真实领取的优惠券。


目录


它解决什么问题

减肥期想吃麦当劳、两个人只有 70 块、团队下午茶要凑够 8 份还不能太咸—— 这些本质上是同一个问题:带约束的组合优化。

菜单有上百个餐品、套餐还有隐藏的默认组成、卡包里躺着几张不知道能不能用的券, 靠人脑根本算不清。这个项目把它解出来,一次给出三套方案:

目标 含义
最省钱 满足热量下限前提下的最低实付,附人均花费
最扛饿 每元买到的千卡最高(性价比)
最均衡 蛋白供能比接近 20%、钠更低、总热量贴近目标

快速开始

纯 Python 标准库,零依赖,不用 pip 装任何东西。

# 1. 拿代码
git clone https://github.com/linghuchong6668/mcd-optimizer.git
cd mcd-optimizer

# 2. 准备麦当劳 MCP Token(获取方式见 M-China/mcd-mcp-server)
export MCD_MCP_TOKEN=<你的 Token>

# 3. 跑
python3 scripts/optimizer.py --city 上海 --keyword 人民广场 \
    --people 2 --budget 70 --kcal 800

把 MCP 接入任意支持 Streamable HTTP 的客户端,示例见 mcp-config.example.json (仅含环境变量占位符,不含任何真实凭据)。


为什么必须先领券

这是本项目和"随便挑几样"最大的区别。券链路用到三个 Tool:

auto-bind-coupons      → 一键领取可领的麦麦省券
query-store-coupons    → 查该门店当前可用券,以及每张券对应哪个商品
calculate-price        → 带 couponId / couponCode 真实核价

之所以必须单独处理,是因为实测发现两个反直觉的坑——这两个坑不修,券就等于白领:

坑 1:券对应的商品不在菜单里。 「中薯条买一送一」是一个券专属商品(编码 9900012387),query-meals 里根本查不到。 不单独建模,它永远进不了候选池——而它恰恰是最划算的选项(原价 ¥27,凭券 ¥13.5)。

坑 2:不带券核价会静默丢件。 把券商品塞进订单但不带券,接口会悄悄把它丢掉,只返回其余商品的合计:

鸡排×2 + 中薯条买一送一(不带券) → ¥28.00   ← 薯条凭空消失
鸡排×2 + 中薯条买一送一(带券)   → ¥41.50   ← 原价 ¥55,省 ¥13.5

照 ¥28 去比较,会得出「不用券更便宜」的荒谬结论——而这单根本下不了。 代码做了三层防护:price <= 0 判无效、比对 productList 条数发现丢件、 含券商品的方案只认带券那次核价。


输出示例

python3 scripts/optimizer.py --city 上海 --keyword 人民广场 \
    --people 2 --budget 70 --kcal 800
· 领券跳过:券已领过或当前没有可领活动
门店:麦当劳上海黄浦华旭国际大厦餐厅(1450713)|2 人|预算 ¥70|人均热量上限 800 kcal|券:3 张可用
==============================================================
可行方案 753 个,实际核价 12 个(预算内)
· 已剔除 9 个核价后超出预算的组合
--------------------------------------------------------------
【最省钱】那么大鸡排(椒盐风味)×2 + 派×1
  实付 ¥36.50|人均 ¥18.25
  热量 991 kcal(人均 496)|蛋白 50 g|钠 2139 mg|性价比 27 kcal/元
    - 那么大鸡排(椒盐风味) ×2  ¥14.00  385 kcal
    - 派 ×1  ¥8.50  221 kcal
【最扛饿】中薯条买一送一(券)×1 + 那么大鸡排(椒盐风味)×2
  实付 ¥41.50|人均 ¥20.75|省 ¥13.50(原价 ¥55.00,省 25%)
  热量 1348 kcal(人均 674)|蛋白 56 g|钠 2322 mg|性价比 32 kcal/元
    - 中薯条买一送一(券) ×1  ¥13.50(原价 ¥27.00)  578 kcal
    - 那么大鸡排(椒盐风味) ×2  ¥14.00  385 kcal
    ⓘ 用券:中薯条买一送一
【最均衡】麦香鸡×1 + 双层深海鳕鱼堡×1 + 巨无霸×1
  实付 ¥66.50|人均 ¥33.25
  热量 1367 kcal(人均 684)|蛋白 70 g|钠 2609 mg|性价比 21 kcal/元
    - 麦香鸡 ×1  ¥17.00  369 kcal
    - 双层深海鳕鱼堡 ×1  ¥24.00  485 kcal
    - 巨无霸 ×1  ¥25.50  513 kcal
--------------------------------------------------------------

生成可分享的 HTML 卡片

python3 scripts/optimizer.py --people 2 --budget 70 --kcal 800 --html docs/sample.html

会输出一张带券信息、性价比、营养指标的方案卡片,浏览器直接打开 docs/sample.html 即可看到效果。


常用参数

参数 说明 默认
--people 用餐人数 1
--budget 总预算上限(元),0 表示不限 0
--kcal 每人热量上限(kcal),0 表示不限 0
--min-kcal 每人热量下限,默认取上限的 60% 自动
--avoid 忌口关键词,逗号分隔 空
--extra 除主食外允许的共享附加件数 1
--main 主食件数下限 等于人数
--top 每种目标输出的方案数 1
--no-claim 跳过一键领券,只用现有卡包 关闭
--html 输出可视化 HTML 卡片到指定路径 无
--json 额外输出结构化结果,便于接自己的前端 关闭
--city / --keyword 定位门店 上海 / 人民广场
--store-code 直接指定门店编码,跳过搜索 无
--be-type 1 到店自取,2 麦乐送,5 得来速,6 团餐 1

更多场景

# 减脂期单人餐:热量封顶 500 kcal,必须有主食
python3 scripts/optimizer.py --people 1 --budget 30 --kcal 500 --main 1

# 4 人加班餐,允许 2 件共享小食
python3 scripts/optimizer.py --people 4 --budget 150 --kcal 900 --extra 2

# 忌口 + 输出 JSON
python3 scripts/optimizer.py --people 2 --budget 60 --avoid 辣,花生 --json

# 只想看现有卡包里的券,不想再领
python3 scripts/optimizer.py --people 2 --budget 60 --no-claim

求解原理

MCP 负责提供真实数据与权威核价,重计算放本地,两者通过约束求解衔接—— 而不是把大模型当计算器反复打接口。

  1. 真实数据:门店、菜单、现价/原价、营养成分全部实时拉取,菜单换了项目不用改。
  2. 套餐营养反推:营养表只有单品维度,菜单里大量是组合餐。命中失败时用 query-meal-detail 拆出套餐默认组成(主食+小食+饮料)逐项累加, 营养覆盖率从 32% 提到接近全覆盖。
  3. 券商品单独建模:菜单里买不到的券专属商品,带券核价拿到真实到手价后强制入池; 「买一送一」类券还会把营养翻倍,避免把两份薯条的热量算成一份。
  4. 约束建模:主食件数、预算上限、热量上下限、忌口关键词同时生效, 避免「最省钱 = 两个冰淇淋」这种废解。
  5. 多样性保留:三个目标各自用自己的代理指标先排一遍、各贡献 1/3 名额再统一核价。 否则先按价格砍到 N 个,最扛饿和最均衡就只能矮子里拔将军。
  6. 按实付复核预算:菜单价是「起价」,套餐真实核价往往更高 (实测有组合菜单合计 ¥128、实付 ¥155),所以核价后必须再复核一次预算。

调用量控制在单次 80 次以内,远低于 600 次/分钟的限额;客户端内置 429 指数退避, 营养表落盘缓存 12 小时,领券后查卡包会强制回源避免读到旧缓存。


项目结构

.
├── SKILL.md                 # WorkBuddy Skill 定义与使用说明
├── README.md
├── MCP_INTEGRATION.md       # MCP 调用流程与业务价值
├── CONTEST_DECLARATION.md   # 参赛声明(官方原文,未改动)
├── mcp-config.example.json  # 脱敏 MCP 配置示例
├── workbuddy.md             # WorkBuddy 开发上下文
├── docs/sample.html         # 真实跑出来的方案卡片样例
└── scripts/
    ├── mcd_mcp.py           # MCP Streamable HTTP 客户端(缓存 + 限流退避 + 安全调用)
    ├── nutrition.py         # 营养表解析与餐品名模糊匹配
    └── optimizer.py         # 领券、券建模、约束求解、核价与排序、HTML 渲染

FAQ

会帮我自动下单吗? 不会。项目定位是决策辅助,只用到了查询与核价类 Tool,不用下单类 Tool, 避免自动化下单带来的资金风险。方案出来后你自己确认再下单。

一键领券是真实操作吗? 是。auto-bind-coupons 会把券真实领进你的卡包。默认开启,不想要加 --no-claim。

为什么算出来的价格跟 App 里看到的不一样? 所有金额都以 calculate-price 的实时返回为准(接口单位是「分」)。 门店优惠、时段、随单购卡都会影响实付,同一方案换个时间可能不同。

改了菜单会不会失效? 不会。菜单、价格、营养、券全部实时拉取,项目里没有任何硬编码的餐品或价格。


适合谁用

  • 想控卡又想吃麦当劳的人(减脂 / 健身 / 体检后忌口)
  • 精打细算的学生、上班族(同样花钱,吃得更饱)
  • 帮团队订下午茶、加班餐的组长 / 行政(按人头与预算自动配平)

免责声明

本项目为麦当劳程序员节创意开发大赛参赛作品,由参赛者独立开发,非麦当劳官方产品。 输出仅供参考,不构成医疗、营养或其他专业建议;餐品信息、价格、优惠券及供应状态 以麦当劳官方渠道的实时结果为准。

About

No description, website, or topics provided.

Resources

Stars

111 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages