mcd-deal-gen · git:20261020.c677c94 · 2026-10-20 · sha256 db0af8334a0f25ba
mcd-deal-gen git:20261020.c677c94A
Immutable. This exact content never changes and is served at /api/v1/blob/db0af8334a0f25ba.
---
name: mcd-deal-gen
description: 麦当劳穷鬼套餐生成器。给定预算、餐食时段、营养约束(控热量/控钠/控蛋白)或「用券点单」等需求,自动完成门店定位 → 门店可用券核验 → 餐品营养匹配 → 带券价格试算,输出实付金额最低的组合方案。当用户说「我要吃麦当劳」「帮我配个便宜的」「30块以内怎么点」「用券点单」「麦当劳有什么优惠」「低钠套餐」时使用。
agent_created: true
---
# 穷鬼套餐生成器
## 适用场景
用户想点麦当劳,诉求通常是这几种之一(可组合):
| 诉求 | 典型说法 | 需要的数据 |
|---|---|---|
| 省钱 | 「30块以内怎么点」「最便宜的」 | 券 + 实付试算 |
| 用券 | 「我有哪些券能用」「把券用掉」 | 卡包券 ∩ 门店可用券 |
| 限时 | 「早餐吃什么」「现在还能点啥」 | 当前时段可用性 |
| 健康 | 「低钠」「控热量」「高蛋白」 | 营养成分表 |
| 特定组合 | 「不想吃汉堡」「来个甜品」 | 餐品清单 |
**通用原则:不写死门店、不写死场景、不写死预算。** 每次运行时从用户输入和探测结果推导。
---
## 核心事实(已实测验证,务必遵守)
### 1. 顺序不能反:先问门店此刻能用啥,再看我有什么券
`query-my-coupons`(卡包资产)**不做门店、渠道、场景校验**,返回的是「我拥有的」,
不代表此刻能用。实测:卡包14 张券,同一时刻在成都南站门店只有 1 张被`query-store-coupons` 认出来,
其余 10 张早餐券因已过05:00–10:29 时段而不可用。
**因此禁止「拿卡包券直接去套门店」。** 必须以 `query-store-coupons` 的返回为准。
### 2. `reservationDate` 会导致返回空数组(重要坑)
同门店、同场景实测:
- 传 `reservationDate=2026-10-10 08:30` → `data:[]`(空)
- 不传 `reservationDate` → 正常返回真实券
**处理策略**:先不传 `reservationDate` 查询。若用户需要预约场景,再从
`query-nearby-stores` 返回的 `reservationTimeOptions` 中取真实时段字符串重试;
仍为空则如实告知「该时段暂无可用券」,**不要臆造**。
### 3. 价格单位是「分」
`calculate-price` 所有金额字段(`price`、`originalPrice`、`discount`、`productPrice`…)
单位均为分,**展示时必须除以 100**。这是 MCP 文档特别强调的点,漏掉会差100 倍。
### 4. 券与门店绑定,`productCode` 是试算必需
`query-store-coupons` 返回的每个 `couponId` 可能对应多个 `couponCode`,
且必须搭配该券适用的 `productCode` 才能在 `calculate-price` 里生效。
### 5. 实测计价样例(可作为输出格式参考)
成都南站门店 `3450312` ·薯薯任选 ×1:
原价 ¥16.00 → 用券后 ¥9.90(优惠 ¥6.10),同时返回取餐方式:堂食(eat-in)/ 外带(locker-in)。
### 6. `calculate-price` 可能不可用,算价应回退到本地
实测(2026-10-13)该接口持续返回 `/items: must be array`,
连续 4 次不同参数组合均失败。且即使可用,**为每个候选组合调一次远程接口**
在枚举上千方案时也不现实。
**推荐做法**:MCP 只取数(菜单标价、券面信息),算价用仓库内的本地引擎:
```python
from mcd_planner.pricing import load_menu, load_coupons, price_basket
menu = load_menu()
coupons = load_coupons()
quote = price_basket([("9900012387", 1)], menu, coupons)
print(quote.summary()) # 实付 ¥13.50(原价 ¥27.00,省 ¥13.50)
```
本地引擎已覆盖:券的时段/星期/场景判定、券叠加冲突、划线价对照。
**叠加规则说明**:MCP 未公开 stacking 字段,本地默认「一单一张券」(保守),
输出中须标注该假设,不可宣称比实际更优惠。
若 `calculate-price` 恢复可用,可作为最终校验,但不作为主路径。
---
## 执行流程
### 第 0 步:补齐必需信息
调用任何工具前,先确认这几项(能从输入推断的就别问):
| 要素 | 说明 | 缺失时 |
|---|---|---|
| 取餐场景 | 到店自取(beType=1) / 得来速(beType=5) / 麦乐送(beType=2) / 团餐(beType=6) | **必须问**,场景决定 orderType 和 beCode 传参 |
| 位置 | 城市 + 关键词,或已收藏门店 | 必须问 |
| 预算 | 总价上限 | 可选,默认给「最省方案」 |
| 健康约束 | 控钠/热量/蛋白等 | 可选,无则跳过营养筛选 |
**多要素缺失时一次性问完,不要挤牙膏式追问。**
### 第 1 步:定位门店
```
query-nearby-stores
searchType=1 → 收藏门店(先试这个)
searchType=2 → 城市 + 关键词
beType:到店场景传 1 或 5
```
失败态处理:`searchType=1` 返回 `code=600050`(收藏餐厅列表为空)是正常情况,
直接回退到 `searchType=2` 让用户给城市和关键词。
**必须完整展示 `storeCode`**(后续所有调用都依赖它),得来速场景还要展示 `beCode`。
### 第 2 步:核验该门店真实可用券(关键步骤)
```
query-store-coupons
storeCode=上一步的门店编码
orderType:到店=1 / 外送=2
beType:1 / 5 / 2 / 6
beCode:得来速(5)、麦乐送(2)、团餐(6) 必传;到店自取(1) 不传
```
参数传递规则(严格遵守):
| beType | orderType | beCode |
|---|---|---|
| 1 到店自取 | 1 | 不传 |
| 5 得来速 | 1 | **必传**(来自 `query-nearby-stores`) |
| 2 麦乐送 | 2 | **必传**(来自 `delivery-query-stores`) |
| 6 企业团餐 | 2 | **必传**(来自 `delivery-query-stores`) |
返回为空时:先尝试不带 `reservationDate`;仍为空则如实说明该门店此刻无可用券。
### 第 3 步:拉取餐品与营养数据
```
query-meals → 门店在售餐品 + productCode
list-nutrition-foods → 160 项营养数据(能量/蛋白质/脂肪/碳水/钠/钙)
```
两张表按 `productName` 关联。营养数据里几个值得利用的点:
- **钠含量差异巨大**(巨无霸 961mg vs 麦辣鸡腿堡 1208mg vs 中杯可乐 0mg),
控钠场景下这是主要筛选维度
- 「纯悦」标注能量约清水 1 杯、0 kcal,是零热量饮品替换的天然解
- 有 30+ 项饮品钠含量为 0,替换含糖饮料可同时降糖降钠
### 第 4 步:候选组合 + 算价
**首选本地引擎**(见核心事实 6):
```python
price_basket([(code, qty), ...], menu, coupons, now=..., scope="dine_in")
# → Quote: lines / paid_total / list_total / saving / applied_coupons / notes
```
若本地引擎不可用或用户要求官方核价,再回退到远程接口:
```
calculate-price
storeCode / orderType / beType / beCode
items[]: { productCode, quantity, couponId?, couponCode? }
needTableware: 是否需要餐具(可能产生餐具费)
```
组合策略(按优先级尝试):
1. 能被单张券完全覆盖的组合(最省)
2. 无券但实付最低的单品组合
3. 若有健康约束,在以上候选中筛钠/热量达标者
4. 若用户已有目标单品,优先给「同类替代」而非整体重配
**试算时也要带上 `needTableware`**,否则报价会漏算餐具费。
### 第 4.5 步(可选):价格 × 钠 帕累托前沿
当用户问「便宜和健康怎么权衡」「有没有两全的」时,用帕累托前沿回答:
```python
from mcd_planner.optimizer import optimize, format_frontier
r = optimize(budget=30, max_sodium_mg=1200, max_items=3)
print(format_frontier(r.frontier))
```
前沿上的每个点都不可支配:想更便宜就得多摄入钠,想更低钠就得多花钱。
**若前沿只有一两个点,如实说明「在当前菜单下没有更好的折中」**,
不要为了显得丰富而虚构方案。
### 第 5 步:输出方案
---
## 硬性安全边界
- **绝不自动下单。** 技能止步于 `calculate-price`。`create-order` 仅在用户明确说「就这个/下单/帮我点」时才调用。
- **绝不编造。** 券、门店、价格一律来自接口返回。接口没返回的券码、会员号、门店编码不得猜测或构造。
- **价格必须除以 100**(第 3 条事实)。
- **每张券必须标注真实到期时间**,不推荐已过期或当前时段不可用的券。
- **抽吸类操作不自动执行。** 涉及消耗积分的 `draw-lottery` 须先展示 `query-lottery-info` 的 `nextConsumption` 并获得用户明确确认(MCP 有频次限制,严禁试抽)。
---
## 输出格式
主方案 + 备选,标明每一项的优惠来源:
```
最省方案:薯薯任选 ×1
实付 ¥9.90 原价 ¥16.00 省 ¥6.10
券:麦麦省薯薯任选(今日 23:59 到期)
门店:成都南站站前广场(步行 273m)
取餐:堂食 / 外带
─── 其他可选 ───
· 火腿扒麦满分 单点 约 ¥11.00 无券可用,钠 608mg
· 巨无霸 ×1 + 中杯可乐 约 ¥24.00钠 961mg(若不控钠)
提示:你卡包中还有 10 张早餐券,但有效期为每日 05:00–10:29,当前时段不可用。
```
要点:**实付金额显眼、券的到期时间显眼、不适用的券主动说明为什么用不了**(避免用户以为漏算了)。
---
## 常见场景速查
| 用户说 | 走哪条路 |
|---|---|
| 「早餐吃什么」 | 场景=到店自取 → 门店 → 查券(此时段券才有效)→ 早餐餐品配券 |
| 「30 以内怎么点」 | 问预算 → 全流程 → 按实付排序,滤掉超预算 |
| 「低钠的推荐」 | 加控钠约束 → 营养表筛钠 → 试算 |
| 「我有什么券」 | 仅 `query-my-coupons` + `available-coupons`,说明哪些当前不可用 |
| 「附近有什么活动」 | `campaign-calendar` + `available-coupons` |
| 「公司订餐」 | beType=6 团餐链路 + `query-meal-assistance` 助餐服务 + `query-promotions` 满减满折规则解读 |
---
## 边界与免责
- 营养数据来自 MCP 静态表,与门店实际出品可能存在偏差,涉及健康目标时应提示用户以实际为准
- 门店库存、供应时段以 `query-meals` 与门店营业时间为准
- 团餐满减满折规则需自行解读(`query-promotions` 返回原始结构,不做最优推荐)
- 价格与优惠以 `create-order` 时的实际结算为准,本技能输出为**试算结果**