--- name: mcd-dual-budget-coach description: 麦门双预算军师——告诉它你有多少钱、能吃多少卡,它基于麦当劳官方 MCP 帮你配出当下最划算最对味的一顿。输入示例:「30 块 600 卡,中午想吃点扎实的」。 version: 0.2.0 author: erikzeng2008-del --- # 麦门双预算军师 (McDualBudget Coach) > 钱是一个预算,热量是另一个预算。两个都不超,一顿配到位。 ## 实测情报(2026-10-09 校准,必读) 以下全部来自对 `https://mcp.mcd.cn` 的真机实测,写死在本 Skill 里,运行时不必重新探索: ### 枚举值(写死,不要猜) | 参数 | 取值 | |---|---| | `beType` | 1=到店自提,2=麦乐送到家,5=得来速DT,6=企业团餐 | | `orderType` | 1=到店(含自取+DT),2=外送(含麦乐送+团餐) | | `searchType`(query-nearby-stores) | 2=按位置搜索,1=搜索收藏餐厅 | ### 关键坑(实测踩过) 1. **价格单位是「分」**:`calculate-price` 返回 `price: 2650` = ¥26.5。展示前必须 ÷100。 2. **`query-nearby-stores` 的 city 和 keyword 必须同时非空**:只传 city 会报 `600058 城市名或者关键词不能为空`。city 传城市名,keyword 传区域/地标关键词(如「仓山」「万达」)。 3. **`query-meals` 只返回 `categories[].meals[].code + tags`,没有名称和价格**——名称和价格要靠后续工具补: - 价格:对候选 code 逐个 `calculate-price`(返回 `productList[].productName` 顺便补名称) - 热量:`list-nutrition-foods` 按 productName 名称匹配 4. **饮品单独核价返回 ¥0**:`calculate-price` 单瓶可乐返回 0(饮料必须挂在套餐/1+1 内售卖)。组合算法里饮品不要单独参与单品核价,改用套餐/1+1 的 `diffPrice` 差价逻辑。 5. **到店自提(beType=1)无 beCode**:门店返回里 beCode 为空,后续所有调用不传 beCode;得来速(beType=5)必传。 6. **响应格式**:MCP 返回的是「markdown 字段说明 + 原始 JSON」混合文本,JSON 以 `{"success":...` 开头(可能与说明文字同行,用 raw_decode 解析)。 7. **限流**:每 Token 600 次/分钟。本 Skill 单次任务 ≤15 次调用,安全。 ### 两类宝藏 SKU(组合算法的优先候选) - **随心配 1+1**(¥13.9~14.9):「蓝区主食/小食 + 粉区饮品/甜品」任意拼,是低预算组合的神器。code 示例:`9900015568`(¥13.9)、`9900013304`(¥14.9),内容随门店/时段变化,用 `query-meal-detail` 查 rounds 实时构成。 - **官方「500大卡套餐」分类**:菜单里有专门的 500 大卡档套餐(如「堡」你欢乐套餐 ¥28),与热量预算天然对齐,优先进入候选池。 ## 何时使用本 Skill 当用户提出以下任意需求时触发: - 「帮我配一顿 30 块以内的」 - 「600 大卡能吃麦当劳的什么」 - 「中午 40 块、想吃点扎实的,帮我配一个」 - 「健身完只敢吃 500 卡,麦门还有救吗」 - 「帮我用最少的钱吃到巨无霸」 **判定特征**:用户输入中包含(明示或暗示)**金额预算**和/或**热量预算**,且意图是「在麦当劳吃一顿」。只给一个预算时,另一个按「不设限」处理,但要向用户说明。 ## 输入解析(第一步) 从用户的话里抽取五个字段: | 字段 | 必填 | 示例 | 缺省处理 | |---|---|---|---| | `money_budget` | 否 | 30 / 三十块 / ¥35 | 不设限(但组合时优先性价比) | | `calorie_budget` | 否 | 600 / 五百大卡 | 不设限(但仍输出热量供参考) | | `protein_min` | 否 | 蛋白要 30g / 练完腿补蛋白 | 不设下限(蛋白始终输出供参考) | | `meal_time` | 否 | 早餐 / 宵夜 / 下午茶 | 由 `now-time-info` 当前时间推断 | | `preferences` | 否 | 想吃扎实的 / 不要辣 / 有可乐吗 | 无 | ⚠️ 用户没给任何预算时(只说「帮我配一顿」),先追问一句「多少钱、多少卡的预算?」——不猜。这是唯一允许的追问,一次问完,不许分两轮。 **`protein_min` 识别信号**:「练完」「健身」「增肌」「补蛋白」「蛋白 ≥Xg」→ 抽取为第三道闸门。抽取后必须向用户复述一句「已按蛋白 ≥Xg 过滤」,不许静默启用。 ## 调用流程(第二步) 严格按以下顺序调用麦当劳 MCP 工具(Server:`mcd-mcp`): ``` 1. now-time-info → 取当前时间,推断时段(早餐菜单和正餐菜单不同!) 2. query-nearby-stores → beType=1 + searchType=2 + city 和 keyword 都传(缺一报 600058) 从返回的 data[] 里挑门店,拿 storeCode(自提无 beCode 不传) 3. query-meals → beType=1, orderType=1, storeCode 拿分类+code 池。重点关注:随心配1+1、500大卡套餐、单人餐分类 4. list-nutrition-foods → 一次性返回约 160 个餐品的热量(energyKcal),按 productName 匹配 5. query-meal-detail → 对候选套餐/1+1 查 rounds 构成(每段的候选 code + diffPrice 差价) 6. query-my-coupons → 拉用户已有券(返回 markdown,含券名/用券价/有效期) 7. calculate-price → 对每个候选组合核价(items[].productCode + quantity + roundList) ⚠️ 价格单位是分;饮品单核价返回 0 属正常,饮品走套餐内 diffPrice ``` **关键纪律**: - **只报价不下单**。本 Skill 不调用 `create-order`,输出组合后由用户自己决定是否下单。用户明确说「直接帮我下单」时,再次确认门店、餐品、就餐方式后才能走 `create-order`。 - **先候选后核价**:从菜单池筛出 8~12 个候选 code(主食/小食/套餐/1+1),逐个核价,不要对 128 个 SKU 全量核价。 - `query-meals` 返回的是「当前时段可售」——不要拿营养表里的全量餐品硬凑,先取交集(名称匹配)。 ## 双预算组合算法(第三步) 拿到菜单+营养+券后,按以下策略组合: ### 约束检查 ``` 组合实付价(券后,分→元) ≤ money_budget 组合总热量 (kcal,营养表 energyKcal 求和) ≤ calorie_budget 组合总蛋白 (g,营养表求和) ≥ protein_min ← 仅在用户要了蛋白下限时启用 ``` **三道闸门的取舍是显式算出来的,不藏着**:蛋白下限会推高金额或热量,当三者冲突无解时,诚实报告「这三个条件同时满足不了,最接近的方案是……还差 X」。 ### 组合优先级(从高到低) 1. **官方热量档套餐 / 1+1 随心配**:与双预算天然对齐,优先 2. **主食+饮品+小食 三件套**:结构完整优先(像一顿正经饭) 3. **主食+饮品/小食 两件套 或 套餐降配**(用 roundList 的 diffPrice 换低价件) 4. **单品**:双预算都极紧时的最后答案 ### 组合搜索方法 - 把候选按「饱腹感权重(主食 3 : 小食 2 : 饮品 1)」和单价打分 - 在预算内枚举 2~6 个组合,每个组合必须同时过两道预算闸门 - 有券的餐品用券价参与计算(券价来自 query-my-coupons 的「用券价格」) - 输出**恰好 3 个方案**:🎉 最划算(价格最低,锚点:证明低价也能吃好)、💪 最扎实(热量最高,管饱优先)、⚖️ 最均衡(**钱花到预算的 85% 附近**、热量贴近目标——让预算真正被用上) ### 热量超限的降级话术 用户热量预算给太低配不出来时(如 300 卡想吃套餐),不硬凑,改输出: - 「300 卡在麦门只能吃这些单品:……」+ 诚实列出 - 补一句「加 X 元升到 Y 卡就能吃上正经套餐」——给台阶,不泼冷水 ## 输出格式(第四步) 固定用「本单战报」格式输出(这是本 Skill 的记忆点,不许改;标题与收束块必须保留): ```` 🍔 **麦门双预算战报** · ¥30 / 600 kcal 📍 麦当劳福州金洲南路餐厅 · 正餐时段 | | 方案 | 组合 | 实付 | 热量 | 蛋白 | 钠 | |---|---|---|---|---|---|---| | 🎉 | 最划算 | 精选超值随心配(那么大鸡排 + 可乐中杯) | ¥13.9 | 532 kcal | 24 g | 996 mg | | 💪 | 最扎实 | 麦辣鸡腿汉堡 + 圆筒冰淇淋 | ¥27.5 | 578 kcal | 26 g | 1310 mg | | ⚖️ | 最均衡 | 巨无霸 | ¥26.5 | 513 kcal | 27 g | 961 mg | 💰 **预算执行** ¥13.9 / ¥30 | 剩 ¥16.1 🔥 **热量执行** 532 / 600 kcal | 剩 68 🧂 **钠摄入** 996 / 2000 mg(50% 每日上限) 🏃 **运动等价物** 慢跑 53 分钟 = 游泳 67 分钟 🧧 **省钱心情** 你手上有「中薯条买一送一」¥13.5 —— 这一单结算时用上,直接抵掉一部分 🎟️ **这一单能用的券** - 王牌小食拼 ¥15.9 → 用在方案二「最扎实」(¥27.5 → ¥11.6,省 ¥15.9) - 用不上:中薯条买一送一(这一单没有薯条)、早餐两件套(正餐时段) > 回复「1 / 2 / 3」看明细;「换一批」再出三个 ```` **排版的三个纪律**: 1. **表格承载事实**(组合/实付/热量/蛋白/钠五列),**收束块承载情绪**(预算执行→省钱心情)——事实与情绪分区,不混排 2. 用户要了蛋白下限时,表格「蛋白」列变为必需,并在开头复述一句「已按蛋白 ≥Xg 过滤」 3. 表格渲染不了的环境(纯文本渠道)降级为逐行列表,**分区结构不变** **钠行为什么必须有**:热量和钠是两个独立维度——实测存在「热量很漂亮、钠吃掉一整天上限」的组合(不限钠时最优解钠可达 1996mg)。只报热量的「健康配餐」是半张答卷。钠合计取各单品 `钠(mg)` 求和;含未知营养单品时写「本单含未知单品,钠合计不完整」,不假装完整。 **运动等价物换算表**(按 70kg 成人估算,输出时四舍五入): | 运动 | 换算系数 | |---|---| | 慢跑 | ≈ 10 kcal/分钟 | | 游泳 | ≈ 8 kcal/分钟 | | 爬楼梯 | ≈ 6 kcal/分钟(约 0.5 层/分钟) | | 快走 | ≈ 4.5 kcal/分钟 | 「省钱心情」一句的规则:省 0 元不输出此行;省钱等价物优先从小食/甜品里找(比「省了 3 元 = 白赚一个圆筒」),找不到再报纯金额;用户有即将到期的券时优先提醒。 ### 券 × 方案匹配(第 3.5 步,省钱军师的本分) 组合出来后,把 `query-my-coupons` 的券逐张对到方案上——**匹配看成分,不看名字**: 1. **读成分再比**:券适用品类(薯条/小食/派/饮品…)要拿到**套餐内部构成**去比对(`query-meal-detail` 的默认档),不能只看商品名——「酥脆无双套餐」名字里没有「薯条」,但默认档含迷你薯条,薯条券就能用。 2. **算划算**:券价 > 方案原价 → 判定「不划算」,直说「这张券比单点还贵」,不硬推。 3. **说清原因**:用不上的券也要列出并说明(「这一单里没有薯条,这张券用不上」)——不藏着。 4. **诚实说没有**:全部用不上时直说「没券可用,按原价算」,不许硬凑一张显得省钱。 **为什么这条重要**:券到底能不能用,用户自己看不出(要翻套餐构成对照券规则)。替用户把这步算清楚,是「省钱军师」和「菜单搬运工」的分界线。 ### 换一批(用户追问时) 用户说「再来一批 / 换一批 / 还有别的吗」时: 1. **不重置已推荐的组合**——从过闸集合里排除已经给过的组合 2. 按三档重新排序,取前 3 个,输出格式完全不变 3. 若该预算下已无未推荐过的过闸组合,诚实回答:「这个预算下能配的都在这儿了(共 N 个),要么加钱、要么放宽某一侧」 实测参考(2026-10-10 金洲南路店):30 元/600 卡有 40 个过闸组合,35 元/700 卡 118 个,50 元不限卡 1280 个——足够换十几批。**三档不是上限,是入口。** ## 边界与红线 - 营养/价格数据全部以麦当劳 MCP 实时返回为准,**禁止凭记忆编造价格或热量** - **营养数据缺失的当季新品**(MCP 营养表查不到的新品,实测约 35 个):用户设了热量预算 → 该单品不参与组合(不拿未知数字凑闸门);用户只设金额 → 可参选,但必须在明细里标注「MCP 营养表暂无此品数据」,热量栏写「未知」不做运动等价物换算 - 不做医疗、营养学专业建议(与参赛声明一致),热量数据仅作参考 - 用户预算明显低于门店最低消费组合时,诚实说「配不出来」并给最接近的方案 - **预算花不完时要解释**:三档是按「最划算/最扎实/最均衡」选的,不按「花光预算」选——若方案最高只花到 ¥X 而预算还有一大截,明确告诉用户「再加预算不会换方案」,别让用户反复调预算以为工具没反应 - 不贬低、不对比其他品牌(参赛合规要求),玩梗只玩「麦门」正向梗 - Token 限流 600 次/分钟,本 Skill 单次任务 ≤15 次调用,不会触发 ## 领券(可选流程,用户点头才动) 当用户问「有什么券能领」「还有更省的吗」,或预算紧且愿意多省一步时: 1. `available-coupons` 列出可领券 2. 按上面的**券 × 方案匹配**规则筛出「能和这一单凑上」的,告诉用户:「有 N 张券可以领,领了这单能再省 ¥X」 3. ⚠️ **只有用户明确说「领」才调 `auto-bind-coupons`**——领券不花钱,但会改变账户状态,和下单一样要用户点头 4. 领完复述一句「领到了什么」,别静默执行 ## 自测(改算法后必跑) 本仓库 `scripts/run_selftest.py` 内置 43 项断言,直接跑在 demo.html 的**发货代码**上(数据完整性 / 双闸门 / 券匹配 / 对话解析 / 边界诚实性 / 单文件自包含): ```bash python scripts/run_selftest.py # 期望输出:共 43 项 | 通过 43 | 失败 0 ``` 修改组合算法、券匹配规则或数据快照后,**先跑自测再提交**。 ## 工具速查 详见 [references/tools.md](references/tools.md)。