麦麦拼单官 (mcd-group-order-mate)
概述
把"一群人一起点麦当劳"变成一次结构化的拼单流程:收需求 → 定门店 → 拉菜单与券 → 券机会挖掘 → 生成方案 → 券与拆单优化 → 分账 → 确认下单。
差异化在于:不做简单的"推荐菜品",而是把人、券、单三者的拆分决策当作优化问题求解,并用麦当劳 MCP 的真实计价接口验证结果——所有金额都来自真实试算,不凭猜测承诺价格。
触发与边界
在以下场景启用:
- "我们办公室 5 个人一起点麦当劳"
- "帮我拼个单" / "AA 一下" / "算下每个人多少钱"
- "怎么点最便宜"(涉及多人共享优惠)
- "他们都要 XX,我要 YY,帮我一起点了"
不适用:单人点餐、不涉及券优化也不涉及分账——按普通点餐流程处理即可。
前置检查
- 确认麦当劳 MCP 已连接(工具名前缀
mcd-mcp)。未连接时提示用户在连接器中启用。 - 涉及写操作(
auto-bind-coupons、create-order)前,必须先取得用户明确同意。
工作流
Phase 0 · 收集需求
逐项收集,缺失就主动询问,不要臆造:
- 人数与每人称呼
- 每人的主食偏好与禁忌(如"不吃辣""去冰""不要生菜""不要酱")
- 是否共享小食
- 预算上限(可选)
- 取餐方式:到店自取 / 麦乐送到家 / 得来速 / 企业团餐
取餐方式决定后续所有调用的参数,映射如下(本期主实现「到店自取」,其余按同一映射扩展):
| 取餐方式 | beType | orderType | beCode | 门店来源 |
|---|---|---|---|---|
| 到店自取 | 1 | 1 | 不传 | query-nearby-stores |
| 麦乐送到家 | 2 | 2 | 必传 | delivery-query-stores |
| 得来速 | 5 | 1 | 必传 | query-nearby-stores |
| 企业团餐 | 6 | 2 | 必传 | delivery-query-stores |
Phase 1 · 定位门店
- 到店自取 / 得来速:调用
query-nearby-stores。默认用searchType=2+city+keyword;只有用户明确说"我的收藏门店"时才用searchType=1(账号无收藏会报600050,白跑一次) - 麦乐送 / 团餐:先
delivery-query-addresses选地址,再delivery-query-stores
必须校验 businessStatus=true。门店已打烊时,query-meals 会返回 600057 门店可能已关闭或不在营业时间;此时展示 businessStartTime/businessEndTime,请用户另选或改期。
展示门店时完整给出 storeCode(得来速还需给出 beCode)。
Phase 2 · 拉取菜单与券池
三步尽量并行:
query-meals(storeCode, orderType, beType)→ 全菜单(分类、餐品code、tags与价格)query-store-coupons(storeCode, orderType, beType)→ 本店可用券,这是寻优的唯一券源available-coupons→ 麦麦省可领券;经用户同意后调auto-bind-coupons一键领取,再重拉第 2 步刷新券池
调用 scripts/mcd_utils.py 的 normalize-coupons 把券池按 couponId 分组去重。
Phase 2.5 · 券机会挖掘 ★
目标:从券池里找出"本次拼单用得上、且真省钱"的券,并反推"要用上它该点什么"。只做匹配与算账,不改变用户已确定的购买意图。
★ 本阶段必须产出(缺一不可,不许凭判断跳过):
- 一个显式的下单时刻
now+ 渠道channel(任何改期都要回到这里重算) - 每条候选券的真实试算结果(来自
calculate-price) - 一次
coupon-opportunities调用(它内部控制时段/渠道过滤 + 合规过滤)
第 2、3 项合并在同一次调用里,所以只要跑了
coupon-opportunities,"筛券"就不会被漏掉。零收益也必须跑一次——它会如实返回totalSavingFen: 0。
为什么领券这一步不挑:auto-bind-coupons 无参数、只能全领;且 available-coupons 只返回标题与图片,不给券码 / 券商品码 / 券价——领之前无从判断值不值。领券免费且无副作用,故一律全领(Phase 2 第 3 步),"合适的券"在领之后筛。
- 先定下单时刻:取
now-time-info(或用户指定时间)得到now与channel。任何改期都要回到这一步重算 - 单券收益试算:对券池里每张券,用
calculate-price试算「其券商品 + 该券」,读回originalPrice(划线价)与productPrice(券价)。 ⚠️ 券商品码取自券的products[].productCode,不要用菜单商品码(会报600012) ⚠️ 券商品可能与它替代的菜单品规格不同(实测:「薯薯任选」券商品实为大薯条 ¥16.00,用户要买的是中薯条 ¥14.50)。此时省额有两个口径,都要算:- 订单口径 = 券商品划线价 − 券价(¥16.00 − ¥9.90 = ¥6.10)→ 用于与订单详情/App 账单对齐
- 用户口径 = 被替代菜单品菜单价 − 券价(¥14.50 − ¥9.90 = ¥4.60)→ 用于对外讲"相比原来少花多少"
两个口径下每人均摊完全相同;对外结论用用户口径,并显式说明规格变化,见
references/mcp-tool-map.md「两个口径」节
- 跑券机会核算(筛券 + 合规一次做完):
mcd_utils.py coupon-opportunities --now "<now>" --channel <pickup|delivery>- 必须传
demandMenuCodes(用户已确定要买的菜单商品码;数组或逗号分隔),否则脚本无法判定是否构成诱导加购 - 必须传
now/channel:脚本据此逐券判定有效期 / 星期 / 时段 / 渠道。券的时间字段(tradeDateTime/weekdays/timeStart/timeEnd/channels)可挂在每条 probe 上,也可用coupons传一份券池、按couponId自动合并 - 输出分四桶:
opportunities(真机会)/notApplicable(券商品不是用户要买的)/unavailable(有效期·星期·时段·渠道不通过,带reasons)/unverified(无法判定)。只有appliesToDemand=true的券计入totalSavingFen/ordersNeeded - 这是对外表述"能省多少"的唯一口径来源,与 Phase 4 的
split共用第 2 步那批 probes,不要重复调calculate-price - ⚠️ 对外只可用
totalSavingFen说"能省多少"——它只统计真正替代了已购商品的券。excludedSavingFen、notApplicable、unavailable的省额不得出现在"合计"里 unavailable里的券要连原因一起展示(已过期 / 尚未开始 / 限周几 / 限时段 / 限渠道),便于用户自行判断- 想看逐券时段明细,可另跑
mcd_utils.py coupon-availability(usable/excluded/unchecked)——它是可选的排查命令,不是必经步骤 - ⚠️
query-store-coupons只给tradeDateTime区间,看不到星期与时段窗口;只核"是否过期"会漏掉"今天不能用的券"(如早餐券限周六日 05:00–10:29)与渠道不符(到店专用/外送专用)。拿不到的维度会记入unchecked,必须人工核对券文案,不要猜成可用
- 必须传
- 并入方案:把有收益的券商品并入 Phase 3 的候选方案,作为 Phase 4 拆单的输入
★ 合规硬约束(赛事红线,务必遵守):
- 只优化用户已确定要买的东西。禁止以"更省"为由建议加购、凑单或超量购买
- 不得出现"多买多省""再加一份更划算"这类表述。正确说法是:"你本来要买的 X,用券后 ¥a,比菜单价 ¥b 省 ¥c"
- 若筛下来没有任何有收益的券,如实说明"本次没有可用券",不要编造机会
Phase 3 · 生成候选商品方案
按"每人一份主食 + 可选共享小食"的骨架组装,并把口味诉求转成硬约束:
- 需要换套餐内商品或做特调时,先
query-meal-detail(code)取modification,再按references/mcp-tool-map.md的「特调传参规则」回填 - 禁忌无法通过特调/替换满足时,更换主食
生成 2~3 套候选方案(如"最省 / 最稳 / 最贴合偏好")供用户选择。
★ 共享小食必须先和套餐对账,避免重复(2026-10-09 实跑暴露):
- 组装候选前,先把每份套餐已含的小食/配饮列出来(如"大堡口福三件套"已含中薯条 + 可乐中杯)
- 共享小食优先选套餐里没有的品类。若 3 人都点三件套(各含中薯条),再共享一份薯条就是 4 份薯条给 3 人——这既是体验问题,也容易被读成"奢靡浪费"
- 冲突时不要自己拍板:唯一能用券的品类恰好与套餐重复时(实测:3 份三件套已含中薯条,而本店唯一可用的券是「薯薯任选」),把两条路都摆出来让用户选——
- A:共享小食仍选薯条(能吃上券,省 ¥X,但与套餐重复一份薯条)
- B:换成套餐未含的品类(如麦乐鸡,则本次没有可用券) 两条都如实报,不要为了"能用券"而默默制造重复
★ 不主动造"加量/升级"方案:候选以"满足需求 + 不超预算"为目标。不要主动新增一个"加量共享小食、超预算"的推荐位(哪怕标注了"仅供参考")——这容易被读成诱导加量,踩赛事红线。用户主动说"想多吃点 / 不差钱"时再给。
★ 最终方案必须与预算对账(含共享项均摊)(2026-10-10 实跑暴露):
- 用户给了预算(人均或总额)时,出最终方案前用分账结果逐人核对,不能只核"每份主食单价 ≤ 人均预算"
- ★ 共享小食/甜品要计入每人均摊。实测:三份主食分别 ¥23.90 / ¥26.90 / ¥26.00(三项都 ≤ 人均 ¥30),但加上共享麦乐鸡 ¥14.50 三人均摊后,B 变成 ¥31.73、C 变成 ¥30.83 —— 两人都超了人均 ¥30,而方案只写了一句"预算 人均 ¥30 左右"、从头到尾没做核对
- 符合预算 → 明写"人均 ¥X,在预算内"
- 超出 → 明确标出"人均 ¥X,超出 ¥Y",并给一版贴合预算的备选(如换更便宜的主食/小食);不要劝说提高预算
Phase 4 · 券与拆单优化 ★
前提(实测,务必遵守):calculate-price 对同单多券直接报 600022 暂不支持多张券使用 —— 一张订单只能用一张券。因此"多张券叠在一单"不成立,省钱只能靠拆单:把人与券拆成若干单,每单用一张券。
scripts/coupon_optimizer.py 把这件事建模成 二分图最大权匹配(券 × 需求份,边权 = 该券顶替该份能省的金额),用匈牙利算法求精确最优,再派生备选交 calculate-price 判定。
前置:如何得到 substitutesMenuCodes(★ 这一步最容易做错)
券的 products[].productCode 是券商品码,与菜单商品码不同域、无法直接对应。所以只能靠语义(名称)匹配来建立"这张券的券商品,等于菜单里的哪一项":
- 从
query-store-coupons取券的products[].productName(如"麦旋风任选1""薯薯任选") - 在
query-meals的菜单里找到同一商品的菜单码(如 经典麦旋风9900008754、薯条4810) - 把匹配到的菜单码填进该券的
substitutesMenuCodes - 匹不上就留空——留空会让该券被剔除(收紧),比"猜一个"把券包装成省了钱要安全得多
- 只有当用户确实要买该商品时,这张券才既是 Phase 2.5 的券机会,又是 Phase 4 的匹配对象
例:用户要 1 份薯条(菜单码
4810,¥14.50),券"薯薯任选"的券商品是9900016370(划线 ¥16.00,实为大薯条)→ 该券substitutesMenuCodes = ["4810"],匹配成功,单独成一单。省额按用户口径为 ¥14.50 − ¥9.90 = ¥4.60(订单口径是 ¥16.00 − ¥9.90 = ¥6.10,用于与账单对齐)。
split子命令 —— 传入demand(要买的商品:menuCode/unitPriceFen/quantity/for)与coupons(每张券带couponProductCode、payFen、substitutesMenuCodes),输出候选拆单方案。脚本自动完成:- 过滤:剔除
available=false、缺券商品码/券价、以及未声明可替代菜单品的券(后者不替代已购商品即诱导加购,违反赛事合规,一律不纳入) - 求最优:匈牙利算法解最大权匹配 —— 保证"用哪些券、各顶替哪一份"是理论最优;每张券最多配一份、每份最多配一张券
- 派生候选:
minimal_orders(无券商品塞进券单,单数最少)与coupon_only(券单只放券商品,最保守)两种打包策略。已实测确认:券单可以夹杂普通商品(券只作用于券商品,其余按原价计;2026-10-09 实测单笔含券商品+5 个普通商品,总价 = 券价 + 其余原价,与拆成 2 单等价)——因此minimal_orders优先,但两者仍都生成候选交接口判定。另给出 Top-3「只用一张券」的兜底方案与 baseline - 控预算:按
maxProbes(默认 20)截断,避免打爆限流
- 过滤:剔除
- oracle 试算:对每个候选的每一张单分别调
calculate-price(单与单互不影响),把各单总价回填到该候选的orderTotalsFen select子命令 —— 汇总各候选的逐单总价,取真实总价最低者,并算出"省了多少 / 省了百分之几"
产出"商品原价 → 最优价 → 省了多少"的对比。禁止凭猜测宣称价格。 若某候选的某张单计价失败,该候选整体作废(不拿残缺结果充数)。
预算为 0 时的快捷路径:若
split返回optimalSavingFen == 0,说明没有任何券能替代已购商品——直接按 baseline 一单下单,无需再做 oracle 试算与select。同时要如实说明"本次没有可用券"及原因(券集中在小食/甜品/咖啡等用户未点的品类),不得由此引出"要不要加点什么"这类建议。
Phase 5 · 分账
调用 scripts/split_bill.py,把总价拆到每个人。默认规则:个人商品各自承担、配送费与餐具费均摊。
★ 优惠要按"它作用在哪个商品上"归属,不要一律按个人小计比例摊:
- 券/满减作用在共享小食上 → 优惠按
shared规则摊(默认三人均分) - 券/满减作用在某个人的个人商品上 → 优惠全额归该人(B、C 不该蹭 A 的券)
做法:在 bill.json 里用 couponDiscounts 逐笔声明,而不是把总额塞进全局 discount:
json"couponDiscounts": [ {"label": "薯薯任选", "amount": 6.1, "target": "shared"}, {"label": "A 的会员券", "amount": 5.0, "target": "A"} ]
target 取值:shared(跟随共享规则摊)/even(全员均分)/initiator(归发起人)/参与人姓名。amount 取自 select 结果里该券的真实省额(由 calculate-price 试算得出)。
★
bill.json必须用「订单口径」填(2026-10-09 实测):券商品按订单里的原价计入(薯薯任选写 ¥16.00,不是它替代的中薯条 ¥14.50),couponDiscounts.amount用订单优惠额(¥6.10)。这样分账表的"商品原价/优惠/实付"能和订单详情逐行对上。两种口径算出的每人均摊完全相同(券商品净额恒为 ¥9.90),差别只在合计行;混用则会出现"分账表说 ¥135.50、App 说 ¥137.00"的对不上。
反例(踩过):券优惠是共享薯条省下的 ¥4.60,却用全局
discount=4.6+rule.discount="proportional"摊 → 按个人小计 A ¥1.66 / B ¥1.50 / C ¥1.44,等于让 B、C 补贴 A。改用target:"shared"后为 ¥1.54 / ¥1.53 / ¥1.53,与共享小食的摊法一致。
全局 discount(配 rule.discount ∈ {proportional, initiator, even})仍保留,用于无法归属的满减;fee 支持 {even, by_subtotal}。输出分账明细表与可直接发群的 AA 文案,金额精确到分且合计与订单总额完全一致。
已知限制:当前
sharedItems只支持"全员共享",无法把某份共享小食限定给部分人;若券作用的共享商品并非全员都吃,需人工调整分摊结果并在文案中说明。
Phase 6 · 确认后下单
前置(硬性):完整展示方案、券优化对比与分账表,并等待用户明确的"下单"确认——未收到该字眼前不得调用 create-order。
第一步:下单前自检(逐条过一遍,缺一项就别调 create-order)
takeWayCode—— 取calculate-price返回的data.takeWayList[].code:eat-in堂食 /take-in-store外带 /take-dt-quick得来速。⚠️ 命名有坑,按subtitle判断,不要硬编码、不要按 code 字面猜- items 逐字一致 —— 与已试算通过的候选完全相同(
productCode、quantity、券的couponId+couponCode成对)。任何偏差都等于换了价格,必须重跑calculate-price - 券仍在有效期 —— 重新取一次
now-time-info复核。临界券尤其注意(实测出现过"23:07 下单、券 23:59 到期"),过期券会导致下单失败或价格跳变 - 记下预期金额 —— 把第 2 步对应的试算价写下来,用于下单后比对
- 门店与取餐方式与用户确认的一致
第二步:下单并核对
调 create-order 后,把返回交给 mcd_utils.py order-summary --expected-fen <试算价分>:
check.matches == true→ 按reportText汇报check.matches == false→ 明确告知差异,并主动给出cancel-order选项,不要默默接受
订单状态一律读 orderStatus(实测为中文字符串,如"待支付"),不要用 status 数字枚举判断。
第三步:汇报 —— 直接引用脚本给出的 reportText(含 orderId / 门店 / 取餐方式 / 商品 / 原价 / 优惠 / 实付 / expirePayTime 支付时限 / payH5Url)。
并提示:超时未支付订单会失效,需要取消请说一声;支付完成后可回复"支付完成",用 query-order 查最新状态。
关键规则(易错,务必遵守)
- 单位不统一:
query-meals的currentPrice是元(字符串,如"33.5");calculate-price返回的是分(整数),展示前除以 100。用scripts/mcd_utils.py的yuan_to_fen/fen_to_yuan转换,不要手算。 - 随单购价陷阱:菜单项的
currentPrice若带discountType且该餐品存在withOrder字段,说明此价依赖随单购麦金卡。未随单购时应按originalPrice计。做方案与算优惠时必须区分,否则会虚假承诺低价。 - 券按
couponId分组:一个couponId可能对应多个couponCode。展示时分组,寻优时取一张代表。 - 写操作二次确认:
auto-bind-coupons、create-order属于写操作,必须先取得用户同意。 - 限流:每分钟最多 600 次请求,超出返回 429。寻优必须设调用上限(默认 20 次)。
- 券商品码 ≠ 菜单商品码:券绑定的是独立的券商品(如麦旋风任选
9900014239),与菜单里同款商品的编码(9900008754)不同域。要用上券,必须点券商品码并把couponId+couponCode成对带上;拿券去套菜单商品会报600012 促销规则不支持该商品。一张券只覆盖一份(数量>1 也只按 1 份算)。 - 券的有效期与时段必须按"实际下单时刻"核对:券常限制星期与时段(如"周一~五 10:30-23:59""周六 05:00-10:29")。任何"预约明天 / 改期"的提议都必须重新校验券是否仍可用——今天的券明天未必有效。先确定下单时刻,再据此筛券,绝不能让"用今天的券"和"预约明天"同时出现在一个方案里。执行方式:
coupon-opportunities已内置该过滤,必须传--now与--channel,并用unavailable[].reasons说明排除原因;query-store-coupons只给tradeDateTime区间、看不到星期/时段窗口,光看"过期没"不够,也不要手工肉眼代替。 - 禁止诱导加购(赛事合规红线):只优化用户已确定要买的东西。不得以"更省"为由建议加购、凑单或超量购买,也不得出现"多买多省"这类表述;正确说法是"你本来要买的 X,用券后 ¥a,比菜单价 ¥b 省 ¥c"。
- 脚本用绝对路径、一次只跑一条命令:技能脚本位于
~/.workbuddy/skills/mcd-group-order-mate/scripts/。不要用cd xxx && python A.py && python B.py这类链式命令(在沙箱里可能整条被拦,且报错是环境层的、很难排查)。做法:把输入 JSON 写到临时文件,再一条命令跑一个脚本。 - 不主动建议提高预算:用户给的预算上限是硬约束。超预算方案可以列出并标注"超出 ¥X"供参考,但不要劝说"预算放宽到 ¥Y 更划算"——赛事禁止助长奢靡浪费,这类措辞踩红线。
- 券商品与菜单品规格可能不同:券商品是独立商品,它替代的菜单品可能规格不一样(实测:券"薯薯任选"给的是大薯条,用户要买的是中薯条)。此时"省了多少"有订单口径(券商品划线价 − 券价)与用户口径(被替代菜单品原价 − 券价)两个数:分账表与订单详情用订单口径,对外讲"省多少"用用户口径,并必须说明规格变化。两个口径下每人均摊相同。
- 下单后必须核对金额:
create-order返回的realTotalAmount必须与下单前的试算价一致;不一致要立即告知用户并提供cancel-order选项,不得默默接受。订单状态判断用orderDetail.orderStatus(中文字符串,如"待支付"),不要按 schema 里的数字枚举写逻辑。 - 共享小食要与套餐去重:套餐常自带小食(三件套含中薯条 + 可乐)。选定共享小食前先对账,优先避开套餐已含品类;若为用券而只能选重复品类,显式提示重复并给出替代选项,不要把重复当成理所当然(赛事禁止奢靡浪费)。
- 最终方案必须与预算对账,且把共享项算进人均:不能只核"每份主食单价 ≤ 人均预算"。共享小食/甜品要按人均摊后逐人核对(实测踩过:三份主食各 ≤ ¥26,加上共享麦乐鸡均摊后 B ¥31.73、C ¥30.83,两人超了人均 ¥30,方案却没做核对)。符合就写"人均 ¥X,在预算内",超出就标"超出 ¥Y"并给一版贴合预算的备选,但不要劝说提高预算(见规则 10)。
- 不构成专业建议:营养与价格信息以麦当劳官方渠道实时结果为准。
资源
references/mcp-tool-map.md— 麦当劳 MCP 工具速查、入参规则、takeWayCode取值、下单返回值用法、"省了多少"的两个口径、特调与**套餐换品(roundList)**传参规则、已知坑references/output-templates.md— 拼单方案 / 券对比 / 分账表 的输出模板scripts/mcd_utils.py— 单位换算、随单购价校正、券池归一化、营业时间校验、券机会清单(coupon-opportunities:内置有效期/星期/时段/渠道过滤 + 合规过滤,须传demandMenuCodes与--now/--channel)、券可用性明细(coupon-availability,可选排查)、下单核对与汇报(order-summary,须传--expected-fen)scripts/coupon_optimizer.py— 券与拆单优化(split最大权匹配生成候选+ select取真实最低总价)scripts/split_bill.py— 分账计算(个人/共享/费用多规则、券优惠按couponDiscounts归属到它作用的商品、分位取整)


